gloss_renderer/
builders.rs

1#![allow(clippy::missing_panics_doc)] //a lot of operations require inserting or removing component from a entity but
2                                      // the entity will for sure exists so it will never panic
3
4use crate::components::{Colors, Edges};
5use gloss_hecs::EntityBuilder;
6use gloss_utils::tensor::{DynamicMatrixOps, DynamicTensorFloat2D, DynamicTensorInt2D};
7use log::debug;
8use nalgebra_glm::{Vec2, Vec3};
9
10extern crate nalgebra as na;
11use crate::components::{Faces, ModelMatrix, Normals, UVs, Verts};
12use core::f32;
13use gloss_utils::io::FileType;
14#[allow(unused_imports)]
15use log::{error, info, warn};
16use na::DMatrix;
17use std::path::Path;
18use tobj;
19
20use ply_rs::{parser, ply};
21
22/// Creates a cube
23#[must_use]
24pub fn build_cube(center: na::Point3<f32>) -> EntityBuilder {
25    //makes a 1x1x1 vox in NDC. which has Z going into the screen
26    let verts = DMatrix::<f32>::from_row_slice(
27        8,
28        3,
29        &[
30            //behind face (which has negative Z as the camera is now looking in the positive Z direction and this will be the face that is
31            // behind the camera)
32            -1.0, -1.0, -1.0, //bottom-left
33            1.0, -1.0, -1.0, //bottom-right
34            1.0, 1.0, -1.0, //top-right
35            -1.0, 1.0, -1.0, //top-left
36            //front face
37            -1.0, -1.0, 1.0, //bottom-left
38            1.0, -1.0, 1.0, //bottom-right
39            1.0, 1.0, 1.0, //top-right
40            -1.0, 1.0, 1.0, //top-left
41        ],
42    );
43
44    //faces (2 triangles per faces, with 6 faces which makes 12 triangles)
45    let faces = DMatrix::<u32>::from_row_slice(
46        12,
47        3,
48        &[
49            2, 1, 0, //
50            2, 0, 3, //
51            4, 5, 6, //
52            7, 4, 6, //
53            5, 0, 1, //
54            4, 0, 5, //
55            7, 6, 3, //
56            3, 6, 2, //
57            3, 0, 4, //
58            3, 4, 7, //
59            6, 5, 1, //
60            6, 1, 2, //
61        ],
62    );
63
64    //since we want each vertex at the corner to have multiple normals, we just
65    // duplicate them
66    let mut verts_dup = Vec::new();
67    let mut faces_dup = Vec::new();
68    for f in faces.row_iter() {
69        let p1 = verts.row(f[0] as usize);
70        let p2 = verts.row(f[2] as usize);
71        let p3 = verts.row(f[1] as usize);
72        verts_dup.push(p1);
73        verts_dup.push(p2);
74        verts_dup.push(p3);
75
76        #[allow(clippy::cast_possible_truncation)]
77        let v_size = verts_dup.len() as u32;
78        // let new_face = na::RowDVector::<u32>::new(v_size-1, v_size-2,
79        // v_size-3);//corresponds to p3,p2 and p1
80        let new_face = na::RowDVector::<u32>::from_row_slice(&[v_size - 1, v_size - 2, v_size - 3]); //corresponds to p3,p2 and p1
81        faces_dup.push(new_face);
82    }
83
84    let verts = na::DMatrix::<f32>::from_rows(verts_dup.as_slice());
85    let faces = na::DMatrix::<u32>::from_rows(faces_dup.as_slice());
86
87    let mut model_matrix = na::SimilarityMatrix3::<f32>::identity();
88    // model_matrix.append_scaling_mut(0.1);
89    model_matrix.append_translation_mut(&center.coords.into());
90    let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
91    let faces_tensor = DynamicTensorInt2D::from_dmatrix(&faces);
92
93    let mut builder = EntityBuilder::new();
94    builder.add(Verts(verts_tensor)).add(Faces(faces_tensor)).add(ModelMatrix(model_matrix));
95
96    builder
97}
98
99/// Creates a plane based on a center and normal. If `transform_cpu_data` is
100/// true, then the vertices of the plane are actually translated and rotated
101/// on the CPU. Otherwise the transformation is done on the GPU using the
102/// model matrix. This is useful when creating light planes for example when
103/// we want the model matrix to be consistent with the orientation of the
104/// light and therefore we set `transform_cpu_data=false`
105#[must_use]
106pub fn build_plane(center: na::Point3<f32>, normal: na::Vector3<f32>, size_x: f32, size_y: f32, transform_cpu_data: bool) -> EntityBuilder {
107    //make 4 vertices
108    let mut verts = DMatrix::<f32>::from_row_slice(
109        4,
110        3,
111        &[
112            -1.0 * size_x,
113            0.0,
114            -1.0 * size_y, //
115            1.0 * size_x,
116            0.0,
117            -1.0 * size_y, //
118            1.0 * size_x,
119            0.0,
120            1.0 * size_y, //
121            -1.0 * size_x,
122            0.0,
123            1.0 * size_y, //
124        ],
125    );
126    //make 2 faces
127    let faces = DMatrix::<u32>::from_row_slice(
128        2,
129        3,
130        &[
131            2, 1, 0, //
132            3, 2, 0,
133        ],
134    );
135
136    //uvs
137    let uvs = DMatrix::<f32>::from_row_slice(
138        4,
139        2,
140        &[
141            0.0, 0.0, //
142            1.0, 0.0, //
143            1.0, 1.0, //
144            0.0, 1.0, //
145        ],
146    );
147
148    //make a model matrix
149    let up = na::Vector3::<f32>::new(0.0, 1.0, 0.0);
150    let lookat = center + normal * 1.0;
151    //up and normal are colinear so face_towards would fail, we just set to
152    // identity
153    let mut model_matrix = if up.angle(&normal.normalize()) < 1e-6 {
154        let mut mm = na::SimilarityMatrix3::<f32>::identity();
155        mm.append_translation_mut(&na::Translation3::from(center));
156        mm
157    } else {
158        let mut m = na::SimilarityMatrix3::<f32>::face_towards(&center, &lookat, &up, 1.0);
159        m = m
160            * na::Rotation3::<f32>::from_axis_angle(&na::Vector3::z_axis(), std::f32::consts::FRAC_PI_2)
161            * na::Rotation3::<f32>::from_axis_angle(&na::Vector3::x_axis(), std::f32::consts::FRAC_PI_2); //rotate 90 degrees
162        m
163    };
164
165    if transform_cpu_data {
166        //transform directly the verts
167        for mut vert in verts.row_iter_mut() {
168            let v_modif = model_matrix * na::Point3::from(vert.fixed_columns::<3>(0).transpose());
169            vert.copy_from_slice(v_modif.coords.as_slice());
170        }
171        //reset to identity
172        model_matrix = na::SimilarityMatrix3::<f32>::identity();
173    }
174    let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
175    let faces_tensor = DynamicTensorInt2D::from_dmatrix(&faces);
176    let uvs_tensor = DynamicTensorFloat2D::from_dmatrix(&uvs);
177
178    let mut builder = EntityBuilder::new();
179    builder
180        .add(Verts(verts_tensor))
181        .add(Faces(faces_tensor))
182        .add(UVs(uvs_tensor))
183        .add(ModelMatrix(model_matrix));
184
185    builder
186}
187
188#[must_use]
189pub fn build_floor() -> EntityBuilder {
190    //make 4 vertices
191    let verts = DMatrix::<f32>::from_row_slice(
192        4,
193        3,
194        &[
195            -1.0, 0.0, -1.0, //
196            1.0, 0.0, -1.0, //
197            1.0, 0.0, 1.0, //
198            -1.0, 0.0, 1.0, //
199        ],
200    );
201    //make 2 faces
202    let faces = DMatrix::<u32>::from_row_slice(
203        2,
204        3,
205        &[
206            2, 1, 0, //
207            3, 2, 0,
208        ],
209    );
210
211    //uvs
212    let uvs = DMatrix::<f32>::from_row_slice(
213        4,
214        2,
215        &[
216            0.0, 0.0, //
217            1.0, 0.0, //
218            1.0, 1.0, //
219            0.0, 1.0, //
220        ],
221    );
222    let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
223    let faces_tensor = DynamicTensorInt2D::from_dmatrix(&faces);
224    let uvs_tensor = DynamicTensorFloat2D::from_dmatrix(&uvs);
225    let mut builder = EntityBuilder::new();
226    builder.add(Verts(verts_tensor)).add(Faces(faces_tensor)).add(UVs(uvs_tensor));
227
228    builder
229}
230
231#[must_use]
232#[allow(clippy::cast_precision_loss)]
233pub fn build_grid(
234    center: na::Point3<f32>,
235    normal: na::Vector3<f32>,
236    nr_lines_x: u32,
237    nr_lines_y: u32,
238    size_x: f32,
239    size_y: f32,
240    transform_cpu_data: bool,
241) -> EntityBuilder {
242    //a grid has at least 2 lines in each dimension, because each square needs 2
243    // lines, so we cap the number
244    let nr_lines_x = nr_lines_x.max(2);
245    let nr_lines_y = nr_lines_y.max(2);
246
247    //in order to be consistent with build_plane we multiply the size in x and y by
248    // 2 since it's technically the size from the center until the outer edge
249    // let size_x = size_x * 2.0;
250    // let size_y = size_y * 2.0;
251
252    let size_cell_x = size_x / nr_lines_x as f32;
253    let size_cell_y = size_y / nr_lines_y as f32;
254    let grid_half_size_x = size_x / 2.0;
255    let grid_half_size_y = size_y / 2.0;
256
257    // println!("nr_linex_x {}", size_cell_x);
258    // println!("size_cell_x {}", size_cell_x);
259    // println!("grid_half_size_x {}", grid_half_size_x);
260
261    //make points
262    let mut verts = Vec::new();
263    for idx_y in 0..nr_lines_y {
264        for idx_x in 0..nr_lines_x {
265            verts.push(idx_x as f32 * size_cell_x - grid_half_size_x);
266            verts.push(0.0);
267            verts.push(idx_y as f32 * size_cell_y - grid_half_size_y);
268        }
269    }
270
271    //make edges horizontally
272    let mut edges_h = Vec::new();
273    for idx_y in 0..nr_lines_y {
274        for idx_x in 0..nr_lines_x - 1 {
275            let idx_cur = idx_y * nr_lines_x + idx_x;
276            let idx_next = idx_y * nr_lines_x + idx_x + 1;
277            edges_h.push(idx_cur);
278            edges_h.push(idx_next);
279        }
280    }
281
282    //make edges vertically
283    let mut edges_v = Vec::new();
284    for idx_y in 0..nr_lines_y - 1 {
285        for idx_x in 0..nr_lines_x {
286            let idx_cur = idx_y * nr_lines_x + idx_x;
287            let idx_next = (idx_y + 1) * nr_lines_x + idx_x;
288            edges_v.push(idx_cur);
289            edges_v.push(idx_next);
290        }
291    }
292
293    let mut edges = edges_h;
294    edges.extend(edges_v);
295
296    //make nalgebra matrices
297    let mut verts = DMatrix::<f32>::from_row_slice(verts.len() / 3, 3, &verts);
298    let edges = DMatrix::<u32>::from_row_slice(edges.len() / 2, 2, &edges);
299
300    //make a model matrix
301    let up = na::Vector3::<f32>::new(0.0, 1.0, 0.0);
302    let lookat = center + normal * 1.0;
303    //up and normal are colinear so face_towards would fail, we just set to
304    // identity
305    let mut model_matrix = if up.angle(&normal.normalize()) < 1e-6 {
306        let mut mm = na::SimilarityMatrix3::<f32>::identity();
307        mm.append_translation_mut(&na::Translation3::from(center));
308        mm
309    } else {
310        let mut m = na::SimilarityMatrix3::<f32>::face_towards(&center, &lookat, &up, 1.0);
311        m = m
312            * na::Rotation3::<f32>::from_axis_angle(&na::Vector3::z_axis(), std::f32::consts::FRAC_PI_2)
313            * na::Rotation3::<f32>::from_axis_angle(&na::Vector3::x_axis(), std::f32::consts::FRAC_PI_2); //rotate 90 degrees
314        m
315    };
316
317    if transform_cpu_data {
318        //transform directly the verts
319        for mut vert in verts.row_iter_mut() {
320            let v_modif = model_matrix * na::Point3::from(vert.fixed_columns::<3>(0).transpose());
321            vert.copy_from_slice(v_modif.coords.as_slice());
322        }
323        //reset to identity
324        model_matrix = na::SimilarityMatrix3::<f32>::identity();
325    }
326    let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
327    let edges_tensor = DynamicTensorInt2D::from_dmatrix(&edges);
328
329    let mut builder = EntityBuilder::new();
330    builder.add(Verts(verts_tensor)).add(Edges(edges_tensor)).add(ModelMatrix(model_matrix));
331
332    builder
333}
334
335pub fn build_from_file(path: &str) -> EntityBuilder {
336    //get filetype
337    let filetype = match Path::new(path).extension() {
338        Some(extension) => FileType::find_match(extension.to_str().unwrap_or("")),
339        None => FileType::Unknown,
340    };
341
342    #[allow(clippy::single_match_else)]
343    match filetype {
344        FileType::Obj => build_from_obj(Path::new(path)),
345        FileType::Ply => build_from_ply(Path::new(path)),
346        FileType::Unknown => {
347            error!("Could not read file {:?}", path);
348            EntityBuilder::new() //empty builder
349        }
350    }
351}
352
353/// # Panics
354/// Will panic if the path cannot be opened
355#[cfg(target_arch = "wasm32")]
356pub async fn build_from_file_async(path: &str) -> EntityBuilder {
357    //get filetype
358    let filetype = match Path::new(path).extension() {
359        Some(extension) => FileType::find_match(extension.to_str().unwrap_or("")),
360        _ => FileType::Unknown,
361    };
362
363    match filetype {
364        FileType::Obj => build_from_obj_async(Path::new(path)).await,
365        // FileType::Ply => (),
366        _ => {
367            error!("Could not read file {:?}", path);
368            EntityBuilder::new() //empty builder
369        }
370    }
371}
372
373/// # Panics
374/// Will panic if the path cannot be opened
375#[allow(clippy::unused_async)] //uses async for wasm
376#[allow(clippy::identity_op)] //identity ops makes some things more explixit
377fn build_from_obj(path: &Path) -> EntityBuilder {
378    info!("reading obj from {path:?}");
379
380    //Native read
381    let (models, _) = tobj::load_obj(path, &tobj::GPU_LOAD_OPTIONS).expect("Failed to OBJ load file");
382
383    model_obj_to_entity_builder(&models)
384}
385
386/// # Panics
387/// Will panic if the path cannot be opened
388#[allow(clippy::unused_async)] //uses async for wasm
389#[cfg(target_arch = "wasm32")]
390#[allow(deprecated)]
391async fn build_from_obj_async(path: &Path) -> EntityBuilder {
392    //WASM read
393    let mut file_wasm = gloss_utils::io::FileLoader::open(path.to_str().unwrap()).await;
394    let (models, _) = tobj::load_obj_buf_async(&mut file_wasm, &tobj::GPU_LOAD_OPTIONS, move |p| async move {
395        match p.as_str() {
396            _ => unreachable!(),
397        }
398    })
399    .await
400    .expect("Failed to OBJ load file");
401
402    model_obj_to_entity_builder(&models)
403}
404
405/// # Panics
406/// Will panic if the path cannot be opened
407#[allow(clippy::unused_async)] //uses async for wasm
408#[allow(clippy::identity_op)] //identity ops makes some things more explixit
409pub fn build_from_obj_buf(buf: &[u8]) -> EntityBuilder {
410    let mut reader = std::io::BufReader::new(buf);
411
412    //Native read
413    let (models, _) = tobj::load_obj_buf(&mut reader, &tobj::GPU_LOAD_OPTIONS, move |_p| Err(tobj::LoadError::MaterialParseError))
414        .expect("Failed to OBJ load file");
415
416    model_obj_to_entity_builder(&models)
417}
418
419#[allow(clippy::identity_op)] //identity ops makes some things more explixit
420fn model_obj_to_entity_builder(models: &[tobj::Model]) -> EntityBuilder {
421    // fn model_obj_to_entity_builder(model: &ObjData) -> EntityBuilder{
422
423    let mesh = &models[0].mesh;
424    debug!("obj: nr indices {}", mesh.indices.len() / 3);
425    debug!("obj: nr positions {}", mesh.positions.len() / 3);
426    debug!("obj: nr normals {}", mesh.normals.len() / 3);
427    debug!("obj: nr texcoords {}", mesh.texcoords.len() / 2);
428
429    let nr_verts = mesh.positions.len() / 3;
430    let nr_faces = mesh.indices.len() / 3;
431    let nr_normals = mesh.normals.len() / 3;
432    let nr_texcoords = mesh.texcoords.len() / 2;
433
434    let mut builder = EntityBuilder::new();
435
436    if nr_verts > 0 {
437        debug!("read_obj: file has verts");
438        let verts = DMatrix::<f32>::from_row_slice(nr_verts, 3, mesh.positions.as_slice());
439        let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
440        builder.add(Verts(verts_tensor));
441    }
442
443    if nr_faces > 0 {
444        debug!("read_obj: file has faces");
445        let faces = DMatrix::<u32>::from_row_slice(nr_faces, 3, mesh.indices.as_slice());
446        let faces_tensor = DynamicTensorInt2D::from_dmatrix(&faces);
447        builder.add(Faces(faces_tensor));
448    }
449
450    if nr_normals > 0 {
451        debug!("read_obj: file has normals");
452        let normals = DMatrix::<f32>::from_row_slice(nr_normals, 3, mesh.normals.as_slice());
453        let normals_tensor = DynamicTensorFloat2D::from_dmatrix(&normals);
454        builder.add(Normals(normals_tensor));
455    }
456
457    if nr_texcoords > 0 {
458        debug!("read_obj: file has texcoords");
459        let uv = DMatrix::<f32>::from_row_slice(nr_texcoords, 2, mesh.texcoords.as_slice());
460        let uvs_tensor = DynamicTensorFloat2D::from_dmatrix(&uv);
461        builder.add(UVs(uvs_tensor));
462    }
463
464    // if !mesh.faces_original_index.is_empty() {
465    //     builder.add(FacesOriginalIndex(mesh.faces_original_index.clone()));
466    // }
467
468    builder
469}
470
471/// # Panics
472/// Will panic if the path cannot be opened
473#[allow(clippy::unused_async)] //uses async for wasm
474#[allow(clippy::identity_op)] //identity ops makes some things more explixit
475#[allow(clippy::too_many_lines)] //identity ops makes some things more explixit
476fn build_from_ply(path: &Path) -> EntityBuilder {
477    #[derive(Debug, Default)]
478    pub struct Vertex {
479        pos: Vec3,
480        color: Vec3,
481        normal: Vec3,
482        uv: Vec2,
483    }
484
485    #[derive(Debug)]
486    pub struct Face {
487        vertex_index: Vec<u32>,
488    }
489
490    // The structs need to implement the PropertyAccess trait, otherwise the parser
491    // doesn't know how to write to them. Most functions have default, hence
492    // you only need to implement, what you expect to need.
493    impl ply::PropertyAccess for Vertex {
494        fn new() -> Self {
495            Self::default()
496        }
497        fn set_property(&mut self, key: String, property: ply::Property) {
498            match (key.as_ref(), property) {
499                ("x", ply::Property::Float(v)) => self.pos.x = v,
500                ("y", ply::Property::Float(v)) => self.pos.y = v,
501                ("z", ply::Property::Float(v)) => self.pos.z = v,
502                ("red", ply::Property::UChar(v)) => {
503                    self.color.x = f32::from(v) / 255.0;
504                }
505                ("green", ply::Property::UChar(v)) => self.color.y = f32::from(v) / 255.0,
506                ("blue", ply::Property::UChar(v)) => self.color.z = f32::from(v) / 255.0,
507                //normal
508                ("nx", ply::Property::Float(v)) => self.normal.x = v,
509                ("ny", ply::Property::Float(v)) => self.normal.y = v,
510                ("nz", ply::Property::Float(v)) => self.normal.z = v,
511                //uv
512                ("u" | "s", ply::Property::Float(v)) => self.uv.x = v,
513                ("v" | "t", ply::Property::Float(v)) => self.uv.y = v,
514                // (k, _) => panic!("Vertex: Unexpected key/value combination: key: {}", k),
515                // (k, prop) => {println!("unknown key {} of type {:?}", k, prop)},
516                (k, prop) => {
517                    warn!("unknown key {} of type {:?}", k, prop);
518                }
519            }
520        }
521    }
522
523    // same thing for Face
524    impl ply::PropertyAccess for Face {
525        fn new() -> Self {
526            Face { vertex_index: Vec::new() }
527        }
528        #[allow(clippy::cast_sign_loss)]
529        fn set_property(&mut self, key: String, property: ply::Property) {
530            match (key.as_ref(), property.clone()) {
531                ("vertex_indices" | "vertex_index", ply::Property::ListInt(vec)) => {
532                    self.vertex_index = vec.iter().map(|x| *x as u32).collect();
533                }
534                ("vertex_indices" | "vertex_index", ply::Property::ListUInt(vec)) => {
535                    self.vertex_index = vec;
536                }
537                (k, _) => {
538                    panic!("Face: Unexpected key/value combination: key, val: {k} {property:?}")
539                }
540            }
541        }
542    }
543
544    info!("reading ply from {path:?}");
545    // set up a reader, in this a file.
546    let f = std::fs::File::open(path).unwrap();
547    // The header of a ply file consists of ascii lines, BufRead provides useful
548    // methods for that.
549    let mut f = std::io::BufReader::new(f);
550
551    // Create a parser for each struct. Parsers are cheap objects.
552    let vertex_parser = parser::Parser::<Vertex>::new();
553    let face_parser = parser::Parser::<Face>::new();
554
555    // lets first consume the header
556    // We also could use `face_parser`, The configuration is a parser's only state.
557    // The reading position only depends on `f`.
558    let header = vertex_parser.read_header(&mut f).unwrap();
559
560    // Depending on the header, read the data into our structs..
561    let mut vertex_list = Vec::new();
562    let mut face_list = Vec::new();
563    for (_ignore_key, element) in &header.elements {
564        // we could also just parse them in sequence, but the file format might change
565        match element.name.as_ref() {
566            "vertex" | "point" => {
567                vertex_list = vertex_parser.read_payload_for_element(&mut f, element, &header).unwrap();
568            }
569            "face" => {
570                face_list = face_parser.read_payload_for_element(&mut f, element, &header).unwrap();
571            }
572            unknown_name => panic!("Unexpected element! {unknown_name}"),
573        }
574    }
575
576    let mut builder = EntityBuilder::new();
577
578    //pos
579    let mut verts = DMatrix::<f32>::zeros(vertex_list.len(), 3);
580    for (idx, v) in vertex_list.iter().enumerate() {
581        verts.row_mut(idx)[0] = v.pos.x;
582        verts.row_mut(idx)[1] = v.pos.y;
583        verts.row_mut(idx)[2] = v.pos.z;
584    }
585    let verts_tensor = DynamicTensorFloat2D::from_dmatrix(&verts);
586    builder.add(Verts(verts_tensor));
587    //color
588    let mut colors = DMatrix::<f32>::zeros(vertex_list.len(), 3);
589    for (idx, v) in vertex_list.iter().enumerate() {
590        colors.row_mut(idx)[0] = v.color.x;
591        colors.row_mut(idx)[1] = v.color.y;
592        colors.row_mut(idx)[2] = v.color.z;
593    }
594    //TODO need a better way to detect if we actually have color info
595    if colors.min() != 0.0 || colors.max() != 0.0 {
596        debug!("read_ply: file has colors");
597        let colors_tensor = DynamicTensorFloat2D::from_dmatrix(&colors);
598        builder.add(Colors(colors_tensor));
599    }
600    //normal
601    let mut normals = DMatrix::<f32>::zeros(vertex_list.len(), 3);
602    for (idx, v) in vertex_list.iter().enumerate() {
603        normals.row_mut(idx)[0] = v.normal.x;
604        normals.row_mut(idx)[1] = v.normal.y;
605        normals.row_mut(idx)[2] = v.normal.z;
606    }
607    //TODO need a better way to detect if we actually have normal info
608    if normals.min() != 0.0 || normals.max() != 0.0 {
609        debug!("read_ply: file has normals");
610        let normals_tensor = DynamicTensorFloat2D::from_dmatrix(&normals);
611        builder.add(Normals(normals_tensor));
612    }
613    //uv
614    let mut uvs = DMatrix::<f32>::zeros(vertex_list.len(), 2);
615    for (idx, v) in vertex_list.iter().enumerate() {
616        uvs.row_mut(idx)[0] = v.uv.x;
617        uvs.row_mut(idx)[1] = v.uv.y;
618    }
619    //TODO need a better way to detect if we actually have normal info
620    if uvs.min() != 0.0 || uvs.max() != 0.0 {
621        debug!("read_ply: file has uvs");
622        let uvs_tensor = DynamicTensorFloat2D::from_dmatrix(&uvs);
623        builder.add(UVs(uvs_tensor));
624    }
625
626    if !face_list.is_empty() {
627        debug!("read_ply: file has verts");
628        let mut faces = DMatrix::<u32>::zeros(face_list.len(), 3);
629        #[allow(clippy::cast_sign_loss)]
630        for (idx, f) in face_list.iter().enumerate() {
631            faces.row_mut(idx)[0] = f.vertex_index[0];
632            faces.row_mut(idx)[1] = f.vertex_index[1];
633            faces.row_mut(idx)[2] = f.vertex_index[2];
634        }
635        let faces_tensor = DynamicTensorInt2D::from_dmatrix(&faces);
636
637        builder.add(Faces(faces_tensor));
638    }
639
640    builder
641}