// use crate::Arena; use crate::utils::sampling::PiecewiseConstant2D; use anyhow::{Context, Result as AnyResult, bail}; use ply_rs::parser::Parser; use ply_rs::ply::{DefaultElement, Property}; use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f, VectorLike}; use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh}; use shared::utils::{Ptr, Transform}; use std::fs::File; use std::ops::Deref; use std::path::Path; /// Intermediate mesh from PLY #[derive(Debug, Clone, 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, } #[derive(Debug)] struct TriangleMeshStorage { pub p: Vec, pub n: Vec, pub s: Vec, pub uv: Vec, pub vertex_indices: Vec, pub face_indices: Vec, } #[derive(Debug)] struct BilinearMeshStorage { pub vertex_indices: Vec, pub p: Vec, pub n: Vec, pub uv: Vec, pub image_distribution: Option, } #[derive(Debug)] pub struct TriangleMesh { pub storage: Box, pub device: DeviceTriangleMesh, } #[derive(Debug)] pub struct BilinearPatchMesh { pub storage: Box, pub device: DeviceBilinearPatchMesh, } impl Deref for TriangleMesh { type Target = DeviceTriangleMesh; #[inline] fn deref(&self) -> &Self::Target { &self.device } } impl Deref for BilinearPatchMesh { type Target = DeviceBilinearPatchMesh; #[inline] fn deref(&self) -> &Self::Target { &self.device } } impl TriangleMesh { pub fn new( render_from_object: &Transform, reverse_orientation: bool, vertex_indices: Vec, mut p: Vec, mut n: Vec, mut s: Vec, uv: Vec, face_indices: Vec, ) -> Self { let n_triangles = vertex_indices.len() / 3; let n_vertices = p.len(); // Transform positions to render space for pt in p.iter_mut() { *pt = render_from_object.apply_to_point(*pt); } // Transform and optionally flip normals 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; } } } // Transform tangents if !s.is_empty() { assert_eq!(n_vertices, s.len(), "Tangent count mismatch"); for ss in s.iter_mut() { *ss = render_from_object.apply_to_vector(*ss); } } // Validate UVs if !uv.is_empty() { assert_eq!(n_vertices, uv.len(), "UV count mismatch"); } // Validate face indices if !face_indices.is_empty() { assert_eq!(n_triangles, face_indices.len(), "Face index count mismatch"); } let transform_swaps_handedness = render_from_object.swaps_handedness(); let storage = Box::new(TriangleMeshStorage { vertex_indices, p, n, s, uv, face_indices, }); // Build device struct with pointers into storage let device = DeviceTriangleMesh { n_triangles: n_triangles as u32, n_vertices: n_vertices as u32, vertex_indices: storage.vertex_indices.as_ptr().into(), p: storage.p.as_ptr().into(), n: if storage.n.is_empty() { Ptr::null() } else { storage.n.as_ptr().into() }, s: if storage.s.is_empty() { Ptr::null() } else { storage.s.as_ptr().into() }, uv: if storage.uv.is_empty() { Ptr::null() } else { storage.uv.as_ptr().into() }, face_indices: if storage.face_indices.is_empty() { Ptr::null() } else { storage.face_indices.as_ptr().into() }, reverse_orientation, transform_swaps_handedness, }; Self { storage, device } } pub fn positions(&self) -> &[Point3f] { &self.storage.p } pub fn indices(&self) -> &[i32] { &self.storage.vertex_indices } pub fn normals(&self) -> &[Normal3f] { &self.storage.n } pub fn uvs(&self) -> &[Point2f] { &self.storage.uv } } impl BilinearPatchMesh { 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 = vertex_indices.len() / 4; 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 transform_swaps_handedness = render_from_object.swaps_handedness(); let storage = Box::new(BilinearMeshStorage { vertex_indices, p, n, uv, image_distribution, }); let device = DeviceBilinearPatchMesh { n_patches: n_patches as u32, n_vertices: n_vertices as u32, vertex_indices: storage.vertex_indices.as_ptr().into(), p: storage.p.as_ptr().into(), n: if storage.n.is_empty() { Ptr::null() } else { storage.n.as_ptr().into() }, uv: if storage.uv.is_empty() { Ptr::null() } else { storage.uv.as_ptr().into() }, reverse_orientation, transform_swaps_handedness, image_distribution: storage .image_distribution .as_ref() .map(|d| Ptr::from(&d.device)) .unwrap_or(Ptr::null()), }; Self { storage, device } } } // ============================================================================ // PLY Helper Functions // ============================================================================ fn get_float(elem: &DefaultElement, key: &str) -> AnyResult { 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) -> AnyResult { 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) -> AnyResult> { match elem.get(key) { Some(Property::ListInt(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()), Some(Property::ListUInt(vec)) => Ok(vec.clone()), Some(Property::ListUChar(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()), Some(Property::ListChar(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()), _ => bail!("Property {} is not a list", key), } } // ============================================================================ // TriQuadMesh Implementation // ============================================================================ impl TriQuadMesh { pub fn read_ply>(filename: P) -> AnyResult { let path = filename.as_ref(); let filename_display = path.display().to_string(); let mut f = File::open(path) .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(); // Parse vertices if let Some(vertices) = ply.payload.get("vertex") { if vertices.is_empty() { bail!("{}: PLY file has no vertices", filename_display); } 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"); mesh.p.reserve(vertices.len()); if has_normal { mesh.n.reserve(vertices.len()); } if has_uv { mesh.uv.reserve(vertices.len()); } for v_elem in vertices { let x = get_float(v_elem, "x")?; let y = get_float(v_elem, "y")?; let z = get_float(v_elem, "z")?; mesh.p.push(Point3f::new(x, y, z)); 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(Normal3f::new(nx, ny, nz)); } 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(Point2f::new(u, v)); } } } else { bail!("{}: PLY file has no vertex elements", filename_display); } // Parse faces 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.face_indices.push(fi); } if let Ok(indices) = get_list_uint(f_elem, "vertex_indices") { match indices.len() { 3 => mesh.tri_indices.extend(indices.iter().map(|&i| i as i32)), 4 => mesh.quad_indices.extend(indices.iter().map(|&i| i as i32)), n => { log::warn!( "{}: Skipping face with {} vertices (only 3 or 4 supported)", filename_display, n ); } } } else { bail!("{}: vertex_indices not found in face", filename_display); } } } else { bail!("{}: PLY file has no face elements", filename_display); } // Validate indices let vertex_count = mesh.p.len() as i32; for &idx in &mesh.tri_indices { if idx >= vertex_count { bail!( "{}: Vertex index {} out of bounds [0..{})", filename_display, idx, vertex_count ); } } for &idx in &mesh.quad_indices { if idx >= vertex_count { bail!( "{}: Vertex index {} out of bounds [0..{})", filename_display, idx, vertex_count ); } } Ok(mesh) } pub fn convert_to_only_triangles(&mut self) { if self.quad_indices.is_empty() { return; } // Each quad becomes 2 triangles self.tri_indices.reserve(self.quad_indices.len() / 4 * 6); for quad in self.quad_indices.chunks_exact(4) { let (v0, v1, v2, v3) = (quad[0], quad[1], quad[2], quad[3]); // Triangle 1: v0, v1, v2 self.tri_indices.push(v0); self.tri_indices.push(v1); self.tri_indices.push(v2); // Triangle 2: v0, v2, v3 self.tri_indices.push(v0); self.tri_indices.push(v2); self.tri_indices.push(v3); } self.quad_indices.clear(); } pub fn compute_normals(&mut self) { // Initialize normals to zero self.n = vec![Normal3f::new(0.0, 0.0, 0.0); self.p.len()]; // Accumulate face normals for triangles for tri in self.tri_indices.chunks_exact(3) { let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize); let p0 = self.p[i0]; let p1 = self.p[i1]; let p2 = self.p[i2]; let v10 = p1 - p0; let v20 = p2 - p0; let face_normal = v10.cross(v20); // Accumulate (will normalize later) self.n[i0] = self.n[i0] + Normal3f::from(face_normal); self.n[i1] = self.n[i1] + Normal3f::from(face_normal); self.n[i2] = self.n[i2] + Normal3f::from(face_normal); } // Accumulate face normals for quads for quad in self.quad_indices.chunks_exact(4) { let indices: Vec = quad.iter().map(|&i| i as usize).collect(); let p0 = self.p[indices[0]]; let p1 = self.p[indices[1]]; let p2 = self.p[indices[2]]; let v10 = p1 - p0; let v20 = p2 - p0; let face_normal = v10.cross(v20); for &idx in &indices { self.n[idx] = self.n[idx] + Normal3f::from(face_normal); } } // Normalize all normals for normal in &mut self.n { let len_sq = normal.norm_squared(); if len_sq > 0.0 { *normal = *normal / len_sq.sqrt(); } } } /// Convert to a TriangleMesh, consuming self pub fn into_triangle_mesh( mut self, render_from_object: &Transform, reverse_orientation: bool, ) -> TriangleMesh { self.convert_to_only_triangles(); if self.n.is_empty() { self.compute_normals(); } TriangleMesh::new( render_from_object, reverse_orientation, self.tri_indices, self.p, self.n, Vec::new(), // s (tangents) self.uv, self.face_indices, ) } } // ============================================================================ // Convenience: Create from PLY directly // ============================================================================ impl TriangleMesh { pub fn from_ply>( filename: P, render_from_object: &Transform, reverse_orientation: bool, ) -> AnyResult { let mesh = TriQuadMesh::read_ply(filename)?; Ok(mesh.into_triangle_mesh(render_from_object, reverse_orientation)) } }