use crate::utils::FileLoc; use ply_rs::parser::Parser; use ply_rs::ply::{DefaultElement, Property}; use shared::utils::Transform; use shared::utils::mesh::{BilinearPatchMesh, TriangleMesh}; use shared::utils::sampling::PiecewiseConstant2D; use std::collections::HashMap; use std::fs::File; use std::path::Path; #[derive(Debug, Clone, Copy, Default)] pub struct TriQuadMesh { pub p: Vec, pub n: Vec, pub uv: Vec, pub face_indices: Vec, pub tri_indices: Vec, pub quad_indices: Vec, } fn get_float(elem: &DefaultElement, key: &str) -> Result { match elem.get(key) { Some(Property::Float(v)) => Ok(*v), Some(Property::Double(v)) => Ok(*v as f32), Some(_) => bail!("Property {} is not a float", key), None => bail!("Property {} not found", key), } } fn get_int(elem: &DefaultElement, key: &str) -> Result { match elem.get(key) { Some(Property::Int(v)) => Ok(*v), Some(Property::UInt(v)) => Ok(*v as i32), Some(Property::Short(v)) => Ok(*v as i32), Some(Property::UShort(v)) => Ok(*v as i32), Some(Property::Char(v)) => Ok(*v as i32), Some(Property::UChar(v)) => Ok(*v as i32), _ => bail!("Property {} not found or not integer", key), } } fn get_float_any(elem: &DefaultElement, keys: &[&str]) -> Option { for k in keys { if let Ok(val) = get_float(elem, k) { return Some(val); } } None } fn get_list_uint(elem: &DefaultElement, key: &str) -> Result> { match elem.get(key) { Some(Property::List_Int(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()), Some(Property::List_UInt(vec)) => Ok(vec.clone()), Some(Property::List_UChar(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()), Some(Property::List_Char(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()), _ => bail!("Property {} is not a list", key), } } impl TriQuadMesh { pub fn read_ply>(filename: P) -> Result { let filename_display = filename.as_ref().display().to_string(); let mut f = File::open(&filename) .with_context(|| format!("Couldn't open PLY file \"{}\"", filename_display))?; let p = Parser::::new(); let ply = p .read_ply(&mut f) .with_context(|| format!("Unable to read/parse PLY file \"{}\"", filename_display))?; let mut mesh = TriQuadMesh::default(); if let Some(vertices) = ply.payload.get("vertex") { let first = &vertices[0]; let has_uv = (first.contains_key("u") && first.contains_key("v")) || (first.contains_key("s") && first.contains_key("t")) || (first.contains_key("texture_u") && first.contains_key("texture_v")) || (first.contains_key("texture_s") && first.contains_key("texture_t")); let has_normal = first.contains_key("nx") && first.contains_key("ny") && first.contains_key("nz"); for v_elem in vertices { // Read Position (Required) let x = get_float(v_elem, "x")?; let y = get_float(v_elem, "y")?; let z = get_float(v_elem, "z")?; mesh.p.push([x, y, z]); // Read Normal (Optional) if has_normal { let nx = get_float(v_elem, "nx").unwrap_or(0.0); let ny = get_float(v_elem, "ny").unwrap_or(0.0); let nz = get_float(v_elem, "nz").unwrap_or(0.0); mesh.n.push([nx, ny, nz]); } // Read UVs (Optional, handling variable naming convention) if has_uv { let u = get_float_any(v_elem, &["u", "s", "texture_u", "texture_s"]).unwrap_or(0.0); let v = get_float_any(v_elem, &["v", "t", "texture_v", "texture_t"]).unwrap_or(0.0); mesh.uv.push([u, v]); } } } else { bail!( "{}: PLY file is invalid! No vertex elements found!", filename_display ); } if let Some(faces) = ply.payload.get("face") { mesh.tri_indices.reserve(faces.len() * 3); mesh.quad_indices.reserve(faces.len() * 4); for f_elem in faces { if let Ok(fi) = get_int(f_elem, "face_indices") { mesh.faceIndices.push(fi); } if let Ok(indices) = get_list_uint(f_elem, "vertex_indices") { match indices.len() { 3 => { mesh.tri_indices.extend_from_slice(&indices); } 4 => { mesh.quad_indices.extend_from_slice(&indices); } _ => {} } } else { bail!("{}: vertex indices not found in PLY file", filename_display); } } } else { bail!( "{}: PLY file is invalid! No face elements found!", filename_display ); } let vertex_count = mesh.p.len() as u32; for &idx in &mesh.tri_indices { if idx >= vertex_count { bail!( "plymesh: Vertex index {} is out of bounds! Valid range is [0..{})", idx, vertex_count ); } } for &idx in &mesh.quad_indices { if idx >= vertex_count { bail!( "plymesh: Vertex index {} is out of bounds! Valid range is [0..{})", idx, vertex_count ); } } Ok(mesh) } pub fn convert_to_only_triangles(&mut self) { if self.quad_indices.is_empty() { return; } for i in (0..self.quad_indices.len()).step_by(4) { // First triangle: 0, 1, 3 self.tri_indices.push(self.quad_indices[i]); self.tri_indices.push(self.quad_indices[i + 1]); self.tri_indices.push(self.quad_indices[i + 3]); // Second triangle: 0, 3, 2 self.tri_indices.push(self.quad_indices[i]); self.tri_indices.push(self.quad_indices[i + 3]); self.tri_indices.push(self.quad_indices[i + 2]); } self.quad_indices.clear(); } pub fn compute_normals(&mut self) { self.n.resize(self.p.len(), Normal3f::zero()); for i in (0..self.tri_indices.len()).step_by(3) { let v = vec![ self.tri_indices[i], self.tri_indices[i + 1], self.tri_indices[i + 2], ]; let v10 = self.p[v[1]] - self.p[v[0]]; let v21 = self.p[v[2]] - self.p[v[1]]; let mut vn = v10.cross(v21); if vn.norm_squared() > 0. { vn = vn.normalize(); self.n[v[0]] += vn; self.n[v[1]] += vn; self.n[v[2]] += vn; } } assert!(self.quad_indices == 0.); for i in 0..self.n.len() { if n[i].normalize() > 0. { self.n[i] = self.n[i].normalize() } } } } #[derive(Debug, Clone, Copy)] pub struct TriangleMeshStorage { vertex_indices: Vec, p: Vec, n: Vec, s: Vec, uv: Vec, face_indices: Vec, } #[derive(Debug, Clone, Copy)] pub struct TriangleMeshHost { pub view: TriangleMesh, _storage: Arc, } impl Deref for TriangleMeshHost { type Target = TriangleMesh; fn deref(&self) -> &Self::Target { &self.view } } impl TriangleMeshHost { pub fn new( render_from_object: Transform, reverse_orientation: bool, vertex_indices: Vec, mut p: Vec, mut s: Vec, mut n: Vec, uv: Vec, face_indices: Vec, ) -> Self { let n_triangles = indices.len() / 3; let n_vertices = p.len(); for pt in p.iter_mut() { *pt = render_from_object.apply_to_point(*pt); } let transform_swaps_handedness = render_from_object.swaps_handedness(); let uv = if !uv.is_empty() { assert_eq!(n_vertices, uv.len()); Some(uv) } else { None }; let n = if !n.is_empty() { assert_eq!(n_vertices, n.len()); for nn in n.iter_mut() { *nn = render_from_object.apply_to_normal(*nn); if reverse_orientation { *nn = -*nn; } } Some(n) } else { None }; let s = if !s.is_empty() { assert_eq!(n_vertices, s.len()); for ss in s.iter_mut() { *ss = render_from_object.apply_to_vector(*ss); } Some(s) } else { None }; let face_indices = if !face_indices.is_empty() { assert_eq!(n_triangles, face_indices.len()); Some(face_indices) } else { None }; let storage = Box::new(TriangleMeshStorage { p, vertex_indices, n, s, uv, face_indices, }); assert!(p.len() <= i32::MAX as usize); assert!(indices.len() <= i32::MAX as usize); let p_ptr = storage.p.as_ptr(); let idx_ptr = storage.vertex_indices.as_ptr(); let n_ptr = if storage.n.is_empty() { ptr::null() } else { storage.n.as_ptr() }; let uv_ptr = if storage.uv.is_empty() { ptr::null() } else { storage.uv.as_ptr() }; let s_ptr = if storage.s.is_empty() { ptr::null() } else { storage.s.as_ptr() }; let mesh = TriangleMeshHost::new( render_from_object, reverse_orientation, vertex_indices, p, s, n, uv, face_indices, ); } } #[derive(Debug, Clone, Copy)] pub struct BilinearMeshStorage { vertex_indices: Vec, p: Vec, n: Vec, uv: Vec, image_distribution: Option, } #[derive(Debug, Clone, Copy)] pub struct BilinearPatchMeshHost { pub view: BilinearPatchMesh, _storage: Box, } impl Deref for TriangleMeshHost { type Target = TriangleMesh; fn deref(&self) -> &Self::Target { &self.view } } impl BilinearPatchMeshHost { pub fn new( render_from_object: Transform, reverse_orientation: bool, vertex_indices: Vec, mut p: Vec, mut n: Vec, uv: Vec, image_distribution: Option, ) -> Self { let n_patches = indices.len() / 3; let n_vertices = p.len(); for pt in p.iter_mut() { *pt = render_from_object.apply_to_point(*pt); } if !n.is_empty() { assert_eq!(n_vertices, n.len(), "Normal count mismatch"); for nn in n.iter_mut() { *nn = render_from_object.apply_to_normal(*nn); if reverse_orientation { *nn = -*nn; } } } if !uv.is_empty() { assert_eq!(n_vertices, uv.len(), "UV count mismatch"); } let storage = Box::new(BilinearMeshStorage { vertex_indices, p, n, uv, image_distribution, }); let transform_swaps_handedness = render_from_object.swaps_handedness(); let p_ptr = storage.p.as_ptr(); let idx_ptr = storage.vertex_indices.as_ptr(); let n_ptr = if storage.n.is_empty() { ptr::null() } else { storage.n.as_ptr() }; let uv_ptr = if storage.uv.is_empty() { ptr::null() } else { storage.uv.as_ptr() }; let view = BilinearPatchMesh { n_patches: n_patches as u32, n_vertices: n_vertices as u32, vertex_indices: idx_ptr, p: p_ptr, n: n_ptr, uv: uv_ptr, reverse_orientation, transform_swaps_handedness, image_distribution: dist_ptr, }; Self { view, _storage } } }