mesh_loader/collada/
instance.rs

1use std::path::PathBuf;
2
3use super::*;
4use crate::ShadingModel;
5
6pub(super) fn build(doc: &mut Document<'_>, dir: Option<&Path>) -> common::Scene {
7    let mut meshes = Vec::with_capacity(doc.library_geometries.geometries.len());
8    let mut materials = Vec::with_capacity(doc.library_geometries.geometries.len());
9    let mut instance_geometry_map = HashMap::new();
10    let mut instance_material_map = HashMap::new();
11
12    if let Some(root) = &doc.scene.instance_visual_scene {
13        if let Some(root) = doc
14            .library_visual_scenes
15            .nodes
16            .iter_mut()
17            .find(|n| n.id == Some(root.url.as_str()))
18        {
19            root.transform *= Matrix4x4::new(
20                doc.asset.unit,
21                0.,
22                0.,
23                0.,
24                0.,
25                doc.asset.unit,
26                0.,
27                0.,
28                0.,
29                0.,
30                doc.asset.unit,
31                0.,
32                0.,
33                0.,
34                0.,
35                1.,
36            );
37        }
38    }
39
40    for node in &doc.library_visual_scenes.nodes {
41        for instance_geometry in &node.instance_geometry {
42            let mut transform = node.transform;
43            let mut parent = node.parent;
44            while let Some(p) = parent {
45                let node = &doc.library_visual_scenes.nodes[p];
46                transform *= node.transform;
47                parent = node.parent;
48            }
49            let transform = if transform.is_identity() {
50                None
51            } else {
52                Some(transform)
53            };
54            instance_geometry_map.insert(instance_geometry.url.as_str(), transform);
55            for (symbol, instance_material) in &instance_geometry.materials {
56                match doc.get(&instance_material.target) {
57                    Some(material) => {
58                        match doc.get(&material.instance_effect.url) {
59                            Some(effect) => {
60                                instance_material_map.insert(*symbol, (material.name, effect));
61                            }
62                            None => {
63                                // debug!(
64                                //     "not found effect instance '{}'",
65                                //     material.instance_effect.url.as_str()
66                                // );
67                            }
68                        }
69                    }
70                    None => {
71                        // debug!(
72                        //     "not found material instance '{}'",
73                        //     instance_material.target.as_str()
74                        // );
75                    }
76                }
77            }
78        }
79    }
80    for geometry in doc.library_geometries.geometries.values() {
81        if let Some(&transform) = instance_geometry_map.get(geometry.id) {
82            meshes.push(build_mesh(doc, geometry, transform));
83        } else {
84            meshes.push(build_mesh(doc, geometry, None));
85        }
86        let mut material = None;
87        for mat in geometry.mesh.primitives.iter().filter_map(|m| m.material) {
88            if let Some(&(name, effect)) = instance_material_map.get(mat) {
89                material = Some(build_material(doc, name, effect, dir));
90                // TODO: multiple materials from geometry.mesh.primitives?
91                break;
92            }
93        }
94        match material {
95            Some(material) => materials.push(material),
96            None => materials.push(common::Material::default()),
97        }
98    }
99
100    common::Scene { materials, meshes }
101}
102
103fn build_mesh(
104    doc: &Document<'_>,
105    geometry: &Geometry<'_>,
106    transform: Option<Matrix4x4>,
107) -> common::Mesh {
108    let mut mesh = common::Mesh {
109        name: geometry.id.to_owned(),
110        ..Default::default()
111    };
112
113    for prim in (iter::Mesh { doc, xml: geometry }).primitives() {
114        #[allow(clippy::cast_possible_truncation)]
115        let prev_positions_len = mesh.vertices.len() as u32;
116        let p: Vec<_> = prim.positions().collect();
117        let n: Vec<_> = prim.normals().collect();
118        let t: Vec<_> = prim.texcoords(0).collect();
119        let c: Vec<_> = prim.colors().collect();
120        let positions_indices = prim.vertex_indices();
121        let mut normal_indices = prim.normal_indices();
122        let mut texcoord_indices = prim.texcoord_indices(0);
123        let mut color_indices = prim.color_indices();
124        let mut idx = 0;
125
126        for vertex_idx in positions_indices {
127            for vertex_idx in vertex_idx {
128                let mut v = [
129                    p[vertex_idx as usize][0],
130                    p[vertex_idx as usize][1],
131                    p[vertex_idx as usize][2],
132                ];
133                if let Some(transform) = transform {
134                    v *= transform;
135                }
136                mesh.vertices.push(v);
137            }
138            if !n.is_empty() {
139                if let Some(normal_idx) = normal_indices.next() {
140                    for normal_idx in normal_idx {
141                        mesh.normals.push([
142                            n[normal_idx as usize][0],
143                            n[normal_idx as usize][1],
144                            n[normal_idx as usize][2],
145                        ]);
146                    }
147                } else {
148                    panic!()
149                }
150            }
151            if !t.is_empty() {
152                if let Some(texcoord_idx) = texcoord_indices.next() {
153                    for texcoord_idx in texcoord_idx {
154                        mesh.texcoords[0]
155                            .push([t[texcoord_idx as usize][0], t[texcoord_idx as usize][1]]);
156                    }
157                } else {
158                    panic!()
159                }
160            }
161            if !c.is_empty() {
162                if let Some(rgb_idx) = color_indices.next() {
163                    for rgb_idx in rgb_idx {
164                        mesh.colors[0].push([
165                            c[rgb_idx as usize][0],
166                            c[rgb_idx as usize][1],
167                            c[rgb_idx as usize][2],
168                            1.,
169                        ]);
170                    }
171                } else {
172                    panic!()
173                }
174            }
175            mesh.faces.push([
176                prev_positions_len + idx,
177                prev_positions_len + (idx + 1),
178                prev_positions_len + (idx + 2),
179            ]);
180            idx += 3;
181        }
182    }
183
184    mesh
185}
186
187fn build_material(
188    doc: &Document<'_>,
189    name: Option<&str>,
190    effect: &Effect<'_>,
191    dir: Option<&Path>,
192) -> common::Material {
193    fn texture(
194        doc: &Document<'_>,
195        effect: &Effect<'_>,
196        texture: &Texture<'_>,
197        dir: Option<&Path>,
198    ) -> Option<PathBuf> {
199        if texture.texture.is_empty() {
200            return None;
201        }
202        let mut image = None;
203        if let Some(sampler) = effect.profile.samplers.get(texture.texture) {
204            if let Some(surface) = effect.profile.surfaces.get(sampler.source) {
205                if let Some(i) = doc.get(&surface.init_from) {
206                    image = Some(i);
207                }
208            }
209        }
210        if image.is_none() {
211            if let Some(i) = doc.library_images.images.get(&texture.texture) {
212                image = Some(i);
213            }
214        }
215        if let Some(image) = image {
216            match &image.source {
217                ImageSource::Data(_data) => {} // TODO
218                ImageSource::InitFrom(mut p) => {
219                    // There is an exporter writes empty <init_from/> tag
220                    if p.is_empty() {
221                        return None;
222                    }
223                    match dir {
224                        Some(dir) => {
225                            // TODO
226                            p = p.strip_prefix("file://").unwrap_or(p);
227                            let tmp: String;
228                            if p.contains('\\') {
229                                tmp = p.replace('\\', "/");
230                                p = &*tmp;
231                            }
232                            if p.starts_with("/..") {
233                                p = p.strip_prefix('/').unwrap_or(p);
234                            }
235                            let p = dir.join(p);
236                            if p.exists() {
237                                return Some(p);
238                            }
239                        }
240                        None => return Some(p.into()),
241                    }
242                }
243                ImageSource::Skip => {}
244            }
245        }
246        None
247    }
248
249    let mut mat = common::Material::default();
250
251    if let Some(name) = name {
252        mat.name = name.to_owned();
253    }
254
255    mat.shading_model = match effect.profile.technique.ty {
256        _ if effect.profile.technique.faceted => Some(ShadingModel::Flat),
257        ShadeType::Constant => Some(ShadingModel::NoShading),
258        ShadeType::Lambert => Some(ShadingModel::Gouraud),
259        ShadeType::Blinn => Some(ShadingModel::Blinn),
260        ShadeType::Phong => Some(ShadingModel::Phong),
261    };
262
263    // mat.two_sided = Some(effect.profile.technique.double_sided);
264    // mat.wireframe = Some(effect.profile.technique.wireframe);
265
266    mat.color.ambient = Some(effect.profile.technique.ambient.color);
267    mat.color.diffuse = Some(effect.profile.technique.diffuse.color);
268    mat.color.specular = Some(effect.profile.technique.specular.color);
269    mat.color.emissive = Some(effect.profile.technique.emission.color);
270    mat.color.reflective = Some(effect.profile.technique.reflective.color);
271
272    mat.shininess = Some(effect.profile.technique.shininess);
273    mat.reflectivity = Some(effect.profile.technique.reflectivity);
274    mat.index_of_refraction = Some(effect.profile.technique.index_of_refraction);
275
276    // Refs: https://github.com/assimp/assimp/blob/v5.3.1/code/AssetLib/Collada/ColladaLoader.cpp#L1588
277    let mut transparency = effect.profile.technique.transparency;
278    let mut transparent = effect.profile.technique.transparent.color;
279    if transparency >= 0. && transparency <= 1. {
280        if effect.profile.technique.rgb_transparency {
281            transparency *=
282                0.212671 * transparent[0] + 0.715160 * transparent[1] + 0.072169 * transparent[2];
283            transparent[3] = 1.;
284            mat.color.transparent = Some(transparent);
285        } else {
286            transparency *= transparent[3];
287        }
288        if effect.profile.technique.invert_transparency {
289            transparency = 1. - transparency;
290        }
291        if effect.profile.technique.has_transparency || transparency < 1. {
292            mat.opacity = Some(transparency);
293        }
294    }
295
296    if let Some(p) = texture(doc, effect, &effect.profile.technique.ambient.texture, dir) {
297        // Refs: https://github.com/assimp/assimp/blob/v5.3.1/code/AssetLib/Collada/ColladaLoader.cpp#L1619
298        mat.texture.lightmap = Some(p);
299    }
300    if let Some(p) = texture(doc, effect, &effect.profile.technique.emission.texture, dir) {
301        mat.texture.emissive = Some(p);
302    }
303    if let Some(p) = texture(doc, effect, &effect.profile.technique.specular.texture, dir) {
304        mat.texture.specular = Some(p);
305    }
306    if let Some(p) = texture(doc, effect, &effect.profile.technique.diffuse.texture, dir) {
307        mat.texture.diffuse = Some(p);
308    }
309    if let Some(p) = texture(doc, effect, &effect.profile.technique.bump, dir) {
310        mat.texture.normal = Some(p);
311    }
312    if let Some(p) = texture(
313        doc,
314        effect,
315        &effect.profile.technique.transparent.texture,
316        dir,
317    ) {
318        mat.texture.opacity = Some(p);
319    }
320    if let Some(p) = texture(
321        doc,
322        effect,
323        &effect.profile.technique.reflective.texture,
324        dir,
325    ) {
326        mat.texture.reflection = Some(p);
327    }
328
329    mat
330}