feo_oop_engine/components/
material.rs

1//! GameObject material container
2//! 
3//! The only good mtl file documentation I found:
4//! <http://paulbourke.net/dataformats/mtl/>
5//! 
6//! 
7//! | illum | Interpretation                            |
8//! |-------|-------------------------------------------|
9//! | 0     | Color on and Ambient off                  |
10//! | 1     | Color on and Ambient on                   |
11//! | 2     | Highlight on                              |
12//! | 3     | Reflection on and Ray trace on            |
13//! | 4     | Transparency: Glass on                    |
14//! |       | Reflection: Ray trace on                  |
15//! | 5     | Reflection: Fresnel on and Ray trace on   |
16//! | 6     | Transparency: Refraction on               |
17//! |       | Reflection: Fresnel off and Ray trace on  |
18//! | 7     | Transparency: Refraction on               |
19//! |       | Reflection: Fresnel on and Ray trace on   |
20//! | 8     | Reflection on and Ray trace off           |
21//! | 9     | Transparency: Glass on                    |
22//! |       | Reflection: Ray trace off                 |
23//! | 10    | Casts shadows onto invisible surfaces     |
24
25use std::{sync::Arc, collections::HashMap, fs};
26use vulkano::{device::Queue, sync::{self, GpuFuture}};
27use crate::{components::RGB, shaders::fs_draw};
28use super::texture::Texture;
29
30
31/// A material is a construct that describes the color and 
32/// distortion of the surface of a model.
33#[derive(Debug, Clone)]
34pub struct Material{
35    #[allow(dead_code)]
36    name: String,
37    illum: u8, // illumination model  
38    
39    kd: Option<RGB>, // diffuse color not rly an option
40    ka: Option<RGB>, // ambient color
41    ks: Option<RGB>, // specular color
42    #[allow(dead_code)]
43    ke: Option<RGB>, // emissive color // TODO Unimplemented
44    #[allow(dead_code)]
45    km: Option<f32>, // Bump strength // TODO
46    ns: Option<f32>, // focus of specular highlights 0..1000
47    ni: Option<f32>, // optical density / index of refraction 0..1000
48    d: Option<f32>,  // dissolve / alpha transparency 0 (transparent) .. 1 (opaque)
49
50    map_kd: Option<Arc<Texture>>,
51    map_ka: Option<Arc<Texture>>,
52    map_ks: Option<Arc<Texture>>,
53    #[allow(dead_code)] // TODO
54    map_ke: Option<Arc<Texture>>,
55    #[allow(dead_code)] // TODO
56    map_km: Option<Arc<Texture>>,
57    map_ns: Option<Arc<Texture>>,
58    // map_ni not sure if this would make sense
59    #[allow(dead_code)] // TODO
60    map_d: Option<Arc<Texture>>,
61    #[allow(dead_code)] // TODO
62    map_refl: Option<Arc<Texture>>,
63    
64    #[allow(dead_code)] // TODO
65    halo: bool,
66}
67
68impl Default for Material {
69    fn default() -> Self {
70        Material{
71            name: String::from("default"),
72            illum: 1,
73            kd: Some(RGB::new(1.0, 0.0, 1.0)),
74            ka: Some(RGB::new(1.0, 0.0, 1.0)),
75            ks: None,
76            ke: None,
77            km: None,
78            ns: None,
79            ni: None,
80            d: None,
81            map_kd: None,
82            map_ka: None,
83            map_ks: None,
84            map_ke: None,
85            map_km: None,
86            map_ns: None,
87            map_d: None,
88            map_refl: None,
89            halo: false
90        }
91    }
92}
93
94impl Material {
95    pub fn into_set(&self, queue: Arc<Queue>) -> (fs_draw::ty::Material, [Arc<Texture>; 4]) {
96        let default_texture = Texture::default(queue);
97
98        let (diffuse, diffuse_map): ([f32; 4], _) = match (self.kd, self.map_kd.clone()) {
99            (Some(kd), None) => ([kd.r, kd.g, kd.b, 0.0], default_texture.clone()),
100            (None, Some(map_kd)) => ([1.0, 1.0, 1.0, 1.0], map_kd),
101            (Some(kd), Some(map_kd)) => ([kd.r, kd.g, kd.b, 1.0], map_kd),
102            _ => panic!("Either Kd, map_Kd, or both must be defined")
103        };
104
105        let (ambient, ambient_map) = match (self.ka, self.map_ka.clone()) {
106            (None, None) =>  ([0.0, 0.0, 0.0, 0.0], default_texture.clone()),
107            (Some(ka), None) => ([ka.r, ka.g, ka.b, 1.0], default_texture.clone()),
108            (None, Some(map_ka)) => ([1.0, 1.0, 1.0, 2.0], map_ka),
109            (Some(ka), Some(map_ka)) => ([ka.r, ka.g, ka.b, 2.0], map_ka),
110        };
111
112        let (specular, specular_map, specular_highlight_focus_map) = match (self.ks, self.ns, self.map_ks.clone(), self.map_ns.clone()) { // TODO maps
113            (Some(ks), Some(ns), Some(map_ks), Some(map_ns)) => ([ks.r, ks.g, ks.b, -(ns + 1001.0)], map_ks, map_ns),
114            (Some(ks), Some(ns), Some(map_ks), None) => ([ks.r, ks.g, ks.b, -ns], map_ks, default_texture),
115            (Some(ks), Some(ns), None, Some(map_ns)) => ([ks.r, ks.g, ks.b, ns + 1001.0], default_texture, map_ns),
116            (Some(ks), Some(ns), None, None) => ([ks.r, ks.g, ks.b, ns], default_texture.clone(), default_texture),
117            (None, Some(ns), Some(map_ks), Some(map_ns)) => ([1.0, 1.0, 1.0, -(ns + 1001.0)], map_ks, map_ns),
118            (None, Some(ns), Some(map_ks), None) => ([1.0, 1.0, 1.0, -ns], map_ks, default_texture),
119            (Some(ks), None, Some(map_ks), Some(map_ns)) => ([ks.r, ks.g, ks.b, 1001.0], map_ks, map_ns),
120            (Some(ks), None, None, Some(map_ns)) => ([ks.r, ks.g, ks.b, 1001.0], default_texture, map_ns),
121            (None, None, Some(map_ks), Some(map_ns)) => ([1.0, 1.0, 1.0, -1001.0], map_ks, map_ns),
122            (_, None, _, None) | (None, _, None, _) => ([0.0, 0.0, 0.0, 0.0], default_texture.clone(), default_texture),
123        };
124
125        let other = [self.d.unwrap_or(1.0), self.ni.unwrap_or(0.0), 0.0, self.illum as f32];
126        
127        (
128            fs_draw::ty::Material {
129                diffuse,
130                ambient,
131                specular,
132                other
133            },
134            [
135                diffuse_map,
136                ambient_map,
137                specular_map,
138                specular_highlight_focus_map,
139            ]
140        )
141    }
142
143    /// Parse an mtl file
144    pub fn from_mtllib(path: &str, gfx_queue: Arc<Queue>) -> HashMap<String, (Arc<Self>, Box<dyn GpuFuture>)> {
145        let content = fs::read_to_string(path).unwrap_or_else(|_| panic!("Something went wrong when trying to read {}.", path));
146
147        let mut mtls = content.split("newmtl ");
148        mtls.next();
149
150        mtls.into_iter().map(|block| {
151            let block = String::from("newmtl ") + block;
152            Material::from_mtlblock(block.as_str(), path, gfx_queue.clone())
153        }).collect::<HashMap<String, (Arc<Material>, Box<dyn GpuFuture>)>>()
154    }
155
156    /// Parse an mtl block.
157    pub fn from_mtlblock(block: &str, path: &str, gfx_queue: Arc<Queue>) -> (String, (Arc<Material>, Box<dyn GpuFuture>)) {
158        let mut lines = block.lines().filter(|s| !(*s).is_empty() && !s.starts_with('#') );
159
160        let name = lines.next().unwrap_or_else(|| panic!("formatting error in {}", path)).split_whitespace().nth(1).unwrap().to_string();
161        
162        let mut ka: Option<RGB> = None;
163        let mut kd: Option<RGB> = None;
164        let mut ks: Option<RGB> = None;
165        let mut ke: Option<RGB> = None;
166        let mut km: Option<f32> = None;
167        let mut ns: Option<f32> = None;
168        let mut ni: Option<f32> = None;
169        let mut d: Option<f32> = None; // unset
170
171        let mut map_kd: Option<Arc<Texture>> = None;
172        let mut map_ka: Option<Arc<Texture>> = None;
173        let mut map_ks: Option<Arc<Texture>> = None;
174        let mut map_ke: Option<Arc<Texture>> = None;
175        let mut map_km: Option<Arc<Texture>> = None;
176        let mut map_ns: Option<Arc<Texture>> = None;
177        let mut map_d: Option<Arc<Texture>> = None;
178        let mut map_refl: Option<Arc<Texture>> = None;
179
180        let mut illum: Option<u8> = None; // unset
181
182        let mut halo: bool = false;
183
184        let mut future: Box<dyn GpuFuture> = sync::now(gfx_queue.device().clone()).boxed();
185
186        for line in lines {
187            let mut line_parts = line.split_whitespace();
188            match line_parts.next().unwrap() {
189                "Kd" => kd = Some(RGB::from_parts(line_parts).unwrap_or_else(|_| panic!("formatting error in {}", path))),
190                "Ka" => ka = Some(RGB::from_parts(line_parts).unwrap_or_else(|_| panic!("formatting error in {}", path))),
191                "Ks" => ks = Some(RGB::from_parts(line_parts).unwrap_or_else(|_| panic!("formatting error in {}", path))),
192                "Ke" => ke = Some(RGB::from_parts(line_parts).unwrap_or_else(|_| panic!("formatting error in {}", path))),
193                "Km" => km = Some(line_parts.next().unwrap_or_else(|| panic!("formatting error in {}", path)).parse::<f32>().unwrap_or_else(|_| panic!("formatting error in {}", path))),
194                "Ns" => ns = Some(line_parts.next().unwrap_or_else(|| panic!("formatting error in {}", path)).parse::<f32>().unwrap_or_else(|_| panic!("formatting error in {}", path))),
195                "Ni" => ni = Some(line_parts.next().unwrap_or_else(|| panic!("formatting error in {}", path)).parse::<f32>().unwrap_or_else(|_| panic!("formatting error in {}", path))),
196                "d" => d = match line_parts.clone().count() {
197                    1 => Some(line_parts.next().unwrap().parse::<f32>().unwrap_or_else(|_| panic!("formatting error in {}", path))), // halo length
198                    2 => {
199                        line_parts.next().unwrap().to_string();
200                        halo = true;
201                        Some(line_parts.next().unwrap().parse::<f32>().unwrap_or_else(|_| panic!("formatting error in {}", path)))
202                    },
203                    _ => panic!("formatting error in {}", path)
204                }, // rmb other transparency type
205
206                // maps
207                "map_Kd" => {
208                    let (new_map_kd, tex_future) = Texture::from_mtl_line(&mut line_parts, path, gfx_queue.clone()).unwrap();
209                    map_kd = Some(new_map_kd);
210                    future = future.join(tex_future).boxed();
211                },
212                "map_Ka" => {
213                    let (new_map_ka, tex_future) = Texture::from_mtl_line(&mut line_parts, path, gfx_queue.clone()).unwrap();
214                    map_ka = Some(new_map_ka);
215                    future = future.join(tex_future).boxed();
216                },
217                "map_Ks" => {
218                    let (new_map_ks, tex_future) = Texture::from_mtl_line(&mut line_parts, path, gfx_queue.clone()).unwrap();
219                    map_ks = Some(new_map_ks);
220                    future = future.join(tex_future).boxed();
221                },
222                "map_Ke" => {
223                    let (new_map_ke, tex_future) = Texture::from_mtl_line(&mut line_parts, path, gfx_queue.clone()).unwrap();
224                    map_ke = Some(new_map_ke);
225                    future = future.join(tex_future).boxed();
226                },
227                "map_Km" | "map_Bump" | "map_bump" => {
228                    let (new_map_km, tex_future) = Texture::from_mtl_line(&mut line_parts, path, gfx_queue.clone()).unwrap();
229                    map_km = Some(new_map_km);
230                    future = future.join(tex_future).boxed();
231                },
232                "map_Ns" => {
233                    let (new_map_ns, tex_future) = Texture::from_mtl_line(&mut line_parts, path, gfx_queue.clone()).unwrap();
234                    map_ns = Some(new_map_ns);
235                    future = future.join(tex_future).boxed();
236                },
237                "map_d" => {
238                    let (new_map_d, tex_future) = Texture::from_mtl_line(&mut line_parts, path, gfx_queue.clone()).unwrap();
239                    map_d = Some(new_map_d);
240                    future = future.join(tex_future).boxed();
241                },
242                "map_refl" | "refl" => {
243                    let (new_map_refl, tex_future) = Texture::from_mtl_line(&mut line_parts, path, gfx_queue.clone()).unwrap();
244                    // TODO type
245                    map_refl = Some(new_map_refl);
246                    future = future.join(tex_future).boxed();
247                },
248
249                // required
250                "illum" => illum = Some(line_parts.next().unwrap_or_else(|| panic!("formatting error in {}", path)).parse::<i8>().unwrap_or_else(|_| panic!("formatting error in {}", path)) as u8),
251
252                // unsupported
253                a => panic!("formatting error in {}. {} is not supported", path, a),
254            };
255        }
256
257        let mut material = Material{
258            name: name.clone(),
259            illum: 11,
260            kd,
261            ka,
262            ks,
263            ke,
264            km,
265            ns,
266            ni,
267            d,
268
269            map_kd,
270            map_ka,
271            map_ks,
272            map_ke,
273            map_km,
274            map_ns,
275            map_d,
276            map_refl,
277            
278            halo,
279        };
280
281        material.set_illumination_model(illum).unwrap_or_else(|_| panic!("formatting error in {}", path));
282
283        (name, (Arc::new(material), future))
284    }
285
286    pub fn set_illumination_model(&mut self, illum: Option<u8>) -> Result<(), ()> {
287        match illum {
288            Some(illum) => {
289                self.illum = match illum {
290                    0 => 0,
291                    1 if self.ka.is_some() || self.map_ka.is_some() => 1,
292                    2 if (self.ks.is_some() || self.map_ks.is_some()) && self.ns.is_some() => 2,
293                    3 /* TODO */ => 3,
294                    4 /* TODO */ => 4,
295                    5 /* TODO */ => 5,
296                    6 /* TODO */ => 6,
297                    7 /* TODO */ => 7,
298                    8 /* TODO */ => 8,
299                    9 /* TODO */ => 9,
300                    10 /* TODO */ => 10,
301                    _ => return Err(())
302                };
303                Ok(())
304            },
305            None => { // chose the best model
306                todo!()
307            }
308        }
309    }
310}