1#![allow(clippy::missing_panics_doc)] use crate::components::{Colors, DiffuseImg, Edges, ImgConfig};
5use gloss_hecs::EntityBuilder;
6use gloss_utils::tensor::{DynamicMatrixOps, DynamicTensorFloat2D, DynamicTensorInt2D};
7use log::debug;
8use nalgebra_glm::{Vec2, Vec3};
9
10use crate::components::{Faces, ModelMatrix, Normals, UVs, Verts};
11use core::f32;
12use gloss_utils::io::FileType;
13#[allow(unused_imports)]
14use log::{error, info, warn};
15use na::DMatrix;
16use nalgebra as na;
17use std::path::Path;
18use tobj;
19
20use ply_rs::{parser, ply};
21
22#[must_use]
24pub fn build_cube(center: na::Point3<f32>, scale: f32) -> EntityBuilder {
25 let mut verts = DMatrix::<f32>::from_row_slice(
27 8,
28 3,
29 &[
30 -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, ],
42 );
43 verts *= scale;
44
45 let faces = DMatrix::<u32>::from_row_slice(
47 12,
48 3,
49 &[
50 2, 1, 0, 2, 0, 3, 4, 5, 6, 7, 4, 6, 5, 0, 1, 4, 0, 5, 7, 6, 3, 3, 6, 2, 3, 0, 4, 3, 4, 7, 6, 5, 1, 6, 1, 2, ],
63 );
64
65 let mut verts_dup = Vec::new();
68 let mut faces_dup = Vec::new();
69 for f in faces.row_iter() {
70 let p1 = verts.row(f[0] as usize);
71 let p2 = verts.row(f[2] as usize);
72 let p3 = verts.row(f[1] as usize);
73 verts_dup.push(p1);
74 verts_dup.push(p2);
75 verts_dup.push(p3);
76
77 #[allow(clippy::cast_possible_truncation)]
78 let v_size = verts_dup.len() as u32;
79 let new_face = na::RowDVector::<u32>::from_row_slice(&[v_size - 1, v_size - 2, v_size - 3]); faces_dup.push(new_face);
83 }
84
85 let verts = na::DMatrix::<f32>::from_rows(verts_dup.as_slice());
86 let faces = na::DMatrix::<u32>::from_rows(faces_dup.as_slice());
87
88 let mut model_matrix = na::SimilarityMatrix3::<f32>::identity();
89 model_matrix.append_translation_mut(¢er.coords.into());
91 let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
92 let faces_tensor = DynamicTensorInt2D::from_dmatrix(&faces);
93
94 let mut builder = EntityBuilder::new();
95 builder.add(Verts(verts_tensor)).add(Faces(faces_tensor)).add(ModelMatrix(model_matrix));
96
97 builder
98}
99
100#[must_use]
107pub fn build_plane(center: na::Point3<f32>, normal: na::Vector3<f32>, size_x: f32, size_y: f32, transform_cpu_data: bool) -> EntityBuilder {
108 let mut verts = DMatrix::<f32>::from_row_slice(
110 4,
111 3,
112 &[
113 -1.0 * size_x,
114 0.0,
115 -1.0 * size_y, 1.0 * size_x,
117 0.0,
118 -1.0 * size_y, 1.0 * size_x,
120 0.0,
121 1.0 * size_y, -1.0 * size_x,
123 0.0,
124 1.0 * size_y, ],
126 );
127 let faces = DMatrix::<u32>::from_row_slice(
129 2,
130 3,
131 &[
132 2, 1, 0, 3, 2, 0,
134 ],
135 );
136
137 let uvs = DMatrix::<f32>::from_row_slice(
139 4,
140 2,
141 &[
142 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, ],
147 );
148
149 let up = na::Vector3::<f32>::new(0.0, 1.0, 0.0);
151 let lookat = center + normal * 1.0;
152 let mut model_matrix = if up.angle(&normal.normalize()) < 1e-6 {
155 let mut mm = na::SimilarityMatrix3::<f32>::identity();
156 mm.append_translation_mut(&na::Translation3::from(center));
157 mm
158 } else {
159 let mut m = na::SimilarityMatrix3::<f32>::face_towards(¢er, &lookat, &up, 1.0);
160 m = m
161 * na::Rotation3::<f32>::from_axis_angle(&na::Vector3::z_axis(), std::f32::consts::FRAC_PI_2)
162 * na::Rotation3::<f32>::from_axis_angle(&na::Vector3::x_axis(), std::f32::consts::FRAC_PI_2); m
164 };
165
166 if transform_cpu_data {
167 for mut vert in verts.row_iter_mut() {
169 let v_modif = model_matrix * na::Point3::from(vert.fixed_columns::<3>(0).transpose());
170 vert.copy_from_slice(v_modif.coords.as_slice());
171 }
172 model_matrix = na::SimilarityMatrix3::<f32>::identity();
174 }
175 let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
176 let faces_tensor = DynamicTensorInt2D::from_dmatrix(&faces);
177 let uvs_tensor = DynamicTensorFloat2D::from_dmatrix(&uvs);
178
179 let mut builder = EntityBuilder::new();
180 builder
181 .add(Verts(verts_tensor))
182 .add(Faces(faces_tensor))
183 .add(UVs(uvs_tensor))
184 .add(ModelMatrix(model_matrix));
185
186 builder
187}
188
189#[must_use]
190pub fn build_floor() -> EntityBuilder {
191 let verts = DMatrix::<f32>::from_row_slice(
193 4,
194 3,
195 &[
196 -1.0, 0.0, -1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, 1.0, ],
201 );
202 let faces = DMatrix::<u32>::from_row_slice(
204 2,
205 3,
206 &[
207 2, 1, 0, 3, 2, 0,
209 ],
210 );
211
212 let uvs = DMatrix::<f32>::from_row_slice(
214 4,
215 2,
216 &[
217 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, ],
222 );
223 let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
224 let faces_tensor = DynamicTensorInt2D::from_dmatrix(&faces);
225 let uvs_tensor = DynamicTensorFloat2D::from_dmatrix(&uvs);
226 let mut builder = EntityBuilder::new();
227 builder.add(Verts(verts_tensor)).add(Faces(faces_tensor)).add(UVs(uvs_tensor));
228
229 builder
230}
231
232#[must_use]
233#[allow(clippy::cast_precision_loss)]
234pub fn build_grid(
235 center: na::Point3<f32>,
236 normal: na::Vector3<f32>,
237 nr_lines_x: u32,
238 nr_lines_y: u32,
239 size_x: f32,
240 size_y: f32,
241 transform_cpu_data: bool,
242) -> EntityBuilder {
243 let nr_lines_x = nr_lines_x.max(2);
246 let nr_lines_y = nr_lines_y.max(2);
247
248 let size_cell_x = size_x / nr_lines_x as f32;
254 let size_cell_y = size_y / nr_lines_y as f32;
255 let grid_half_size_x = size_x / 2.0;
256 let grid_half_size_y = size_y / 2.0;
257
258 let mut verts = Vec::new();
264 for idx_y in 0..nr_lines_y {
265 for idx_x in 0..nr_lines_x {
266 verts.push(idx_x as f32 * size_cell_x - grid_half_size_x);
267 verts.push(0.0);
268 verts.push(idx_y as f32 * size_cell_y - grid_half_size_y);
269 }
270 }
271
272 let mut edges_h = Vec::new();
274 for idx_y in 0..nr_lines_y {
275 for idx_x in 0..nr_lines_x - 1 {
276 let idx_cur = idx_y * nr_lines_x + idx_x;
277 let idx_next = idx_y * nr_lines_x + idx_x + 1;
278 edges_h.push(idx_cur);
279 edges_h.push(idx_next);
280 }
281 }
282
283 let mut edges_v = Vec::new();
285 for idx_y in 0..nr_lines_y - 1 {
286 for idx_x in 0..nr_lines_x {
287 let idx_cur = idx_y * nr_lines_x + idx_x;
288 let idx_next = (idx_y + 1) * nr_lines_x + idx_x;
289 edges_v.push(idx_cur);
290 edges_v.push(idx_next);
291 }
292 }
293
294 let mut edges = edges_h;
295 edges.extend(edges_v);
296
297 let mut verts = DMatrix::<f32>::from_row_slice(verts.len() / 3, 3, &verts);
299 let edges = DMatrix::<u32>::from_row_slice(edges.len() / 2, 2, &edges);
300
301 let up = na::Vector3::<f32>::new(0.0, 1.0, 0.0);
303 let lookat = center + normal * 1.0;
304 let mut model_matrix = if up.angle(&normal.normalize()) < 1e-6 {
307 let mut mm = na::SimilarityMatrix3::<f32>::identity();
308 mm.append_translation_mut(&na::Translation3::from(center));
309 mm
310 } else {
311 let mut m = na::SimilarityMatrix3::<f32>::face_towards(¢er, &lookat, &up, 1.0);
312 m = m
313 * na::Rotation3::<f32>::from_axis_angle(&na::Vector3::z_axis(), std::f32::consts::FRAC_PI_2)
314 * na::Rotation3::<f32>::from_axis_angle(&na::Vector3::x_axis(), std::f32::consts::FRAC_PI_2); m
316 };
317
318 if transform_cpu_data {
319 for mut vert in verts.row_iter_mut() {
321 let v_modif = model_matrix * na::Point3::from(vert.fixed_columns::<3>(0).transpose());
322 vert.copy_from_slice(v_modif.coords.as_slice());
323 }
324 model_matrix = na::SimilarityMatrix3::<f32>::identity();
326 }
327 let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
328 let edges_tensor = DynamicTensorInt2D::from_dmatrix(&edges);
329
330 let mut builder = EntityBuilder::new();
331 builder.add(Verts(verts_tensor)).add(Edges(edges_tensor)).add(ModelMatrix(model_matrix));
332
333 builder
334}
335
336pub fn build_from_file(path: &str) -> EntityBuilder {
337 let filetype = match Path::new(path).extension() {
339 Some(extension) => FileType::find_match(extension.to_str().unwrap_or("")),
340 None => FileType::Unknown,
341 };
342
343 #[allow(clippy::single_match_else)]
344 match filetype {
345 FileType::Obj => build_from_obj(Path::new(path)),
346 FileType::Ply => build_from_ply(Path::new(path)),
347 FileType::Gltf => build_from_gltf(Path::new(path)),
348 FileType::Unknown => {
349 error!("Could not read file {path:?}");
350 EntityBuilder::new() }
352 }
353}
354
355#[cfg(target_arch = "wasm32")]
358pub async fn build_from_file_async(path: &str) -> EntityBuilder {
359 let filetype = match Path::new(path).extension() {
361 Some(extension) => FileType::find_match(extension.to_str().unwrap_or("")),
362 _ => FileType::Unknown,
363 };
364
365 match filetype {
366 FileType::Obj => build_from_obj_async(Path::new(path)).await,
367 _ => {
369 error!("Could not read file {:?}", path);
370 EntityBuilder::new() }
372 }
373}
374
375#[allow(clippy::unused_async)] #[allow(clippy::identity_op)] fn build_from_obj(path: &Path) -> EntityBuilder {
380 info!("reading obj from {path:?}");
381
382 let (models, _) = tobj::load_obj(path, &tobj::GPU_LOAD_OPTIONS).expect("Failed to OBJ load file");
384
385 model_obj_to_entity_builder(&models)
386}
387
388#[allow(clippy::unused_async)] #[cfg(target_arch = "wasm32")]
392#[allow(deprecated)]
393async fn build_from_obj_async(path: &Path) -> EntityBuilder {
394 let mut file_wasm = gloss_utils::io::FileLoader::open(path.to_str().unwrap()).await;
396 let (models, _) = tobj::load_obj_buf_async(&mut file_wasm, &tobj::GPU_LOAD_OPTIONS, move |p| async move {
397 match p.as_str() {
398 _ => unreachable!(),
399 }
400 })
401 .await
402 .expect("Failed to OBJ load file");
403
404 model_obj_to_entity_builder(&models)
405}
406
407#[allow(clippy::unused_async)] #[allow(clippy::identity_op)] pub fn build_from_obj_buf(buf: &[u8]) -> EntityBuilder {
412 let mut reader = std::io::BufReader::new(buf);
413
414 let (models, _) = tobj::load_obj_buf(&mut reader, &tobj::GPU_LOAD_OPTIONS, move |_p| Err(tobj::LoadError::MaterialParseError))
416 .expect("Failed to OBJ load file");
417
418 model_obj_to_entity_builder(&models)
419}
420
421#[allow(clippy::identity_op)] fn model_obj_to_entity_builder(models: &[tobj::Model]) -> EntityBuilder {
423 let mesh = &models[0].mesh;
426 debug!("obj: nr indices {}", mesh.indices.len() / 3);
427 debug!("obj: nr positions {}", mesh.positions.len() / 3);
428 debug!("obj: nr normals {}", mesh.normals.len() / 3);
429 debug!("obj: nr texcoords {}", mesh.texcoords.len() / 2);
430
431 let nr_verts = mesh.positions.len() / 3;
432 let nr_faces = mesh.indices.len() / 3;
433 let nr_normals = mesh.normals.len() / 3;
434 let nr_texcoords = mesh.texcoords.len() / 2;
435
436 let mut builder = EntityBuilder::new();
437
438 if nr_verts > 0 {
439 debug!("read_obj: file has verts");
440 let verts = DMatrix::<f32>::from_row_slice(nr_verts, 3, mesh.positions.as_slice());
441 let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
442 builder.add(Verts(verts_tensor));
443 }
444
445 if nr_faces > 0 {
446 debug!("read_obj: file has faces");
447 let faces = DMatrix::<u32>::from_row_slice(nr_faces, 3, mesh.indices.as_slice());
448 let faces_tensor = DynamicTensorInt2D::from_dmatrix(&faces);
449 builder.add(Faces(faces_tensor));
450 }
451
452 if nr_normals > 0 {
453 debug!("read_obj: file has normals");
454 let normals = DMatrix::<f32>::from_row_slice(nr_normals, 3, mesh.normals.as_slice());
455 let normals_tensor = DynamicTensorFloat2D::from_dmatrix(&normals);
456 builder.add(Normals(normals_tensor));
457 }
458
459 if nr_texcoords > 0 {
460 debug!("read_obj: file has texcoords");
461 let uv = DMatrix::<f32>::from_row_slice(nr_texcoords, 2, mesh.texcoords.as_slice());
462 let uvs_tensor = DynamicTensorFloat2D::from_dmatrix(&uv);
463 builder.add(UVs(uvs_tensor));
464 }
465
466 builder
471}
472
473#[allow(clippy::unused_async)] #[allow(clippy::identity_op)] #[allow(clippy::too_many_lines)] fn build_from_ply(path: &Path) -> EntityBuilder {
479 #[derive(Debug, Default)]
480 pub struct Vertex {
481 pos: Vec3,
482 color: Vec3,
483 normal: Vec3,
484 uv: Vec2,
485 }
486
487 #[derive(Debug)]
488 pub struct Face {
489 vertex_index: Vec<u32>,
490 }
491
492 impl ply::PropertyAccess for Vertex {
496 fn new() -> Self {
497 Self::default()
498 }
499 fn set_property(&mut self, key: String, property: ply::Property) {
500 match (key.as_ref(), property) {
501 ("x", ply::Property::Float(v)) => self.pos.x = v,
502 ("y", ply::Property::Float(v)) => self.pos.y = v,
503 ("z", ply::Property::Float(v)) => self.pos.z = v,
504 ("red", ply::Property::UChar(v)) => {
505 self.color.x = f32::from(v) / 255.0;
506 }
507 ("green", ply::Property::UChar(v)) => self.color.y = f32::from(v) / 255.0,
508 ("blue", ply::Property::UChar(v)) => self.color.z = f32::from(v) / 255.0,
509 ("nx", ply::Property::Float(v)) => self.normal.x = v,
511 ("ny", ply::Property::Float(v)) => self.normal.y = v,
512 ("nz", ply::Property::Float(v)) => self.normal.z = v,
513 ("u" | "s", ply::Property::Float(v)) => self.uv.x = v,
515 ("v" | "t", ply::Property::Float(v)) => self.uv.y = v,
516 (k, prop) => {
519 warn!("unknown key {k} of type {prop:?}");
520 }
521 }
522 }
523 }
524
525 impl ply::PropertyAccess for Face {
527 fn new() -> Self {
528 Face { vertex_index: Vec::new() }
529 }
530 #[allow(clippy::cast_sign_loss)]
531 fn set_property(&mut self, key: String, property: ply::Property) {
532 match (key.as_ref(), property.clone()) {
533 ("vertex_indices" | "vertex_index", ply::Property::ListInt(vec)) => {
534 self.vertex_index = vec.iter().map(|x| *x as u32).collect();
535 }
536 ("vertex_indices" | "vertex_index", ply::Property::ListUInt(vec)) => {
537 self.vertex_index = vec;
538 }
539 (k, _) => {
540 panic!("Face: Unexpected key/value combination: key, val: {k} {property:?}")
541 }
542 }
543 }
544 }
545
546 info!("reading ply from {path:?}");
547 let f = std::fs::File::open(path).unwrap();
549 let mut f = std::io::BufReader::new(f);
552
553 let vertex_parser = parser::Parser::<Vertex>::new();
555 let face_parser = parser::Parser::<Face>::new();
556
557 let header = vertex_parser.read_header(&mut f).unwrap();
561
562 let mut vertex_list = Vec::new();
564 let mut face_list = Vec::new();
565 for (_ignore_key, element) in &header.elements {
566 match element.name.as_ref() {
568 "vertex" | "point" => {
569 vertex_list = vertex_parser.read_payload_for_element(&mut f, element, &header).unwrap();
570 }
571 "face" => {
572 face_list = face_parser.read_payload_for_element(&mut f, element, &header).unwrap();
573 }
574 unknown_name => panic!("Unexpected element! {unknown_name}"),
575 }
576 }
577
578 let mut builder = EntityBuilder::new();
579
580 let mut verts = DMatrix::<f32>::zeros(vertex_list.len(), 3);
582 for (idx, v) in vertex_list.iter().enumerate() {
583 verts.row_mut(idx)[0] = v.pos.x;
584 verts.row_mut(idx)[1] = v.pos.y;
585 verts.row_mut(idx)[2] = v.pos.z;
586 }
587 let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
588 builder.add(Verts(verts_tensor));
589 let mut colors = DMatrix::<f32>::zeros(vertex_list.len(), 3);
591 for (idx, v) in vertex_list.iter().enumerate() {
592 colors.row_mut(idx)[0] = v.color.x;
593 colors.row_mut(idx)[1] = v.color.y;
594 colors.row_mut(idx)[2] = v.color.z;
595 }
596 if colors.min() != 0.0 || colors.max() != 0.0 {
598 debug!("read_ply: file has colors");
599 let colors_tensor = DynamicTensorFloat2D::from_dmatrix(&colors);
600 builder.add(Colors(colors_tensor));
601 }
602 let mut normals = DMatrix::<f32>::zeros(vertex_list.len(), 3);
604 for (idx, v) in vertex_list.iter().enumerate() {
605 normals.row_mut(idx)[0] = v.normal.x;
606 normals.row_mut(idx)[1] = v.normal.y;
607 normals.row_mut(idx)[2] = v.normal.z;
608 }
609 if normals.min() != 0.0 || normals.max() != 0.0 {
611 debug!("read_ply: file has normals");
612 let normals_tensor = DynamicTensorFloat2D::from_dmatrix(&normals);
613 builder.add(Normals(normals_tensor));
614 }
615 let mut uvs = DMatrix::<f32>::zeros(vertex_list.len(), 2);
617 for (idx, v) in vertex_list.iter().enumerate() {
618 uvs.row_mut(idx)[0] = v.uv.x;
619 uvs.row_mut(idx)[1] = v.uv.y;
620 }
621 if uvs.min() != 0.0 || uvs.max() != 0.0 {
623 debug!("read_ply: file has uvs");
624 let uvs_tensor = DynamicTensorFloat2D::from_dmatrix(&uvs);
625 builder.add(UVs(uvs_tensor));
626 }
627
628 if !face_list.is_empty() {
629 debug!("read_ply: file has verts");
630 let mut faces = DMatrix::<u32>::zeros(face_list.len(), 3);
631 #[allow(clippy::cast_sign_loss)]
632 for (idx, f) in face_list.iter().enumerate() {
633 faces.row_mut(idx)[0] = f.vertex_index[0];
634 faces.row_mut(idx)[1] = f.vertex_index[1];
635 faces.row_mut(idx)[2] = f.vertex_index[2];
636 }
637 let faces_tensor = DynamicTensorInt2D::from_dmatrix(&faces);
638
639 builder.add(Faces(faces_tensor));
640 }
641
642 builder
643}
644
645#[allow(clippy::too_many_lines)]
648fn build_from_gltf(path: &Path) -> EntityBuilder {
649 info!("reading gltf from {path:?}");
650
651 let (gltf, buffers, images) = gltf::import(path).expect("Failed to load GLTF file");
652
653 let mut builder = EntityBuilder::new();
654
655 let mut all_positions = Vec::new();
657 let mut all_indices = Vec::new();
658 let mut all_normals = Vec::new();
659 let mut all_tex_coords = Vec::new();
660 let mut all_colors = Vec::new();
661
662 let mut found_texture: Option<usize> = None;
664 let mut has_multiple_textures = false;
665
666 let mut vertex_offset = 0u32;
668
669 for scene in gltf.scenes() {
671 for node in scene.nodes() {
672 process_gltf_node(
673 &node,
674 &na::Matrix4::identity(),
675 &buffers,
676 &mut all_positions,
677 &mut all_indices,
678 &mut all_normals,
679 &mut all_tex_coords,
680 &mut all_colors,
681 &mut vertex_offset,
682 &mut found_texture,
683 &mut has_multiple_textures,
684 );
685 }
686 }
687
688 let nr_verts = all_positions.len() / 3;
690 if !all_positions.is_empty() {
691 debug!("gltf: total nr positions {nr_verts}");
692
693 let verts = DMatrix::<f32>::from_row_slice(nr_verts, 3, &all_positions);
694 let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
695 builder.add(Verts(verts_tensor));
696 }
697
698 if !all_indices.is_empty() {
699 let nr_faces = all_indices.len() / 3;
700 debug!("gltf: total nr indices {}", all_indices.len());
701
702 let faces = DMatrix::<u32>::from_row_slice(nr_faces, 3, &all_indices);
703 let faces_tensor = DynamicTensorInt2D::from_dmatrix(&faces);
704 builder.add(Faces(faces_tensor));
705 }
706
707 if !all_normals.is_empty() {
708 let nr_normals = all_normals.len() / 3;
709 debug!("gltf: total nr normals {nr_normals}");
710
711 let normals_mat = DMatrix::<f32>::from_row_slice(nr_normals, 3, &all_normals);
712 let normals_tensor = DynamicTensorFloat2D::from_dmatrix(&normals_mat);
713 builder.add(Normals(normals_tensor));
714 }
715
716 if !all_tex_coords.is_empty() {
717 let nr_uvs = all_tex_coords.len() / 2;
718 debug!("gltf: total nr tex_coords {nr_uvs}");
719
720 let uvs = DMatrix::<f32>::from_row_slice(nr_uvs, 2, &all_tex_coords);
721 let uvs_tensor = DynamicTensorFloat2D::from_dmatrix(&uvs);
722 if uvs.nrows() == nr_verts {
724 builder.add(UVs(uvs_tensor));
725 } else {
726 warn!(
727 "gltf: number of uvs {} does not match number of vertices {}, discarding uvs",
728 uvs.nrows(),
729 nr_verts
730 );
731 }
732 }
733
734 if !all_colors.is_empty() {
735 let nr_colors = all_colors.len() / 3;
736 debug!("gltf: total nr colors {nr_colors}");
737
738 let colors_mat = DMatrix::<f32>::from_row_slice(nr_colors, 3, &all_colors);
739 let colors_tensor = DynamicTensorFloat2D::from_dmatrix(&colors_mat);
740 builder.add(Colors(colors_tensor));
741 }
742
743 if let Some(texture_index) = found_texture {
745 if !has_multiple_textures && texture_index < images.len() {
746 debug!("gltf: adding diffuse texture from image index {texture_index}");
747 let image_data = &images[texture_index];
748
749 let img_data = match image_data.format {
752 gltf::image::Format::R8G8B8 => {
753 let mut rgba_data = Vec::with_capacity(image_data.pixels.len() * 4 / 3);
755 for chunk in image_data.pixels.chunks(3) {
756 rgba_data.extend_from_slice(chunk);
757 rgba_data.push(255); }
759 rgba_data
760 }
761 gltf::image::Format::R8G8B8A8 => image_data.pixels.clone(),
762 _ => {
763 warn!("gltf: unsupported image format {:?}, skipping texture", image_data.format);
764 Vec::new()
765 }
766 };
767
768 if !img_data.is_empty() {
769 let diffuse_img: DiffuseImg =
770 DiffuseImg::new_from_raw_pixels(img_data, image_data.width, image_data.height, 4, &ImgConfig::default());
771 builder.add(diffuse_img);
774 }
775 } else if has_multiple_textures {
776 warn!("gltf: multiple different textures found, discarding all textures");
777 }
778 }
779
780 builder
781}
782
783#[allow(clippy::too_many_arguments)]
785fn process_gltf_node(
786 node: &gltf::Node,
787 parent_transform: &na::Matrix4<f32>,
788 buffers: &[gltf::buffer::Data],
789 all_positions: &mut Vec<f32>,
790 all_indices: &mut Vec<u32>,
791 all_normals: &mut Vec<f32>,
792 all_tex_coords: &mut Vec<f32>,
793 all_colors: &mut Vec<f32>,
794 vertex_offset: &mut u32,
795 found_texture: &mut Option<usize>,
796 has_multiple_textures: &mut bool,
797) {
798 let transform_array = node.transform().matrix();
800 let transform_slice: Vec<f32> = transform_array.iter().flatten().copied().collect();
801 let node_transform = na::Matrix4::from_column_slice(&transform_slice);
802 let combined_transform = parent_transform * node_transform;
803
804 if let Some(mesh) = node.mesh() {
806 for primitive in mesh.primitives() {
809 let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
810
811 if let Some(base_color_texture) = primitive.material().pbr_metallic_roughness().base_color_texture() {
813 let texture_index = base_color_texture.texture().source().index();
814
815 match found_texture {
816 None => {
817 *found_texture = Some(texture_index);
818 debug!("gltf: found first texture at index {texture_index}");
819 }
820 Some(existing_index) => {
821 if *existing_index != texture_index {
822 *has_multiple_textures = true;
823 debug!("gltf: found different texture at index {texture_index}, marking as multiple textures");
824 }
825 }
826 }
827 }
828
829 if let Some(positions) = reader.read_positions() {
831 let positions: Vec<[f32; 3]> = positions.collect();
832 for pos in positions {
833 let point = na::Point3::new(pos[0], pos[1], pos[2]);
834 let transformed = combined_transform.transform_point(&point);
835 all_positions.extend_from_slice(transformed.coords.as_slice());
836 }
837 }
838
839 if let Some(indices) = reader.read_indices() {
841 let indices: Vec<u32> = indices.into_u32().map(|i| i + *vertex_offset).collect();
842 all_indices.extend(indices);
843 }
844
845 if let Some(normals) = reader.read_normals() {
847 let normals: Vec<[f32; 3]> = normals.collect();
848 let normal_transform = combined_transform.try_inverse().unwrap_or(na::Matrix4::identity()).transpose();
851 for normal in normals {
852 let n = na::Vector3::new(normal[0], normal[1], normal[2]);
853 let transformed = (normal_transform * n.to_homogeneous()).xyz().normalize();
854 all_normals.extend_from_slice(transformed.as_slice());
855 }
856 }
857
858 if let Some(tex_coords) = reader.read_tex_coords(0) {
860 let mut tex_coords: Vec<f32> = tex_coords.into_f32().flatten().collect();
861 tex_coords.chunks_exact_mut(2).for_each(|chunk| {
863 chunk[1] = 1.0 - chunk[1];
864 });
865 all_tex_coords.extend(tex_coords);
866 }
867
868 if let Some(colors) = reader.read_colors(0) {
870 let colors: Vec<f32> = colors.into_rgb_f32().flatten().collect();
871 all_colors.extend(colors);
872 }
873
874 if let Some(positions) = reader.read_positions() {
876 *vertex_offset += u32::try_from(positions.count()).unwrap();
877 }
878 }
879 }
880
881 for child in node.children() {
883 process_gltf_node(
884 &child,
885 &combined_transform,
886 buffers,
887 all_positions,
888 all_indices,
889 all_normals,
890 all_tex_coords,
891 all_colors,
892 vertex_offset,
893 found_texture,
894 has_multiple_textures,
895 );
896 }
897}