//! Viewer-face indexing compatible with `VertexIndexer.cs`. #![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility map. #![allow(clippy::must_use_candidate)] // Attributes are not part of the mapped C# surface. #![allow(clippy::needless_pass_by_value)] // PrimMesh ownership matches the mapped constructor. use crate::{Coord, Error, PrimMesh, UVCoord}; use std::collections::HashMap; const MAX_PRIM_FACES: usize = 65_536; #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct ViewerVertex { pub n: Coord, pub uv: UVCoord, pub v: Coord, } impl ViewerVertex { pub fn new(coord: Coord, normal: Coord, uv: UVCoord) -> Result { Ok(Self { n: normal, uv, v: coord, }) } } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct ViewerPolygon { pub v1: i32, pub v2: i32, pub v3: i32, } impl ViewerPolygon { pub fn new(v1: i32, v2: i32, v3: i32) -> Result { Ok(Self { v1, v2, v3 }) } } #[derive(Clone, Debug, Default, PartialEq)] pub struct VertexIndexer { pub num_prim_faces: i32, pub viewer_polygons: Vec>>, pub viewer_vertices: Vec>, } impl VertexIndexer { pub fn new_with_constructor() -> Result { Ok(Self::default()) } pub fn new_with_prim_mesh(prim_mesh: PrimMesh) -> Result { Self::from_viewer_faces(&prim_mesh.viewer_faces) } pub(crate) fn from_viewer_faces(faces: &[crate::ViewerFace]) -> Result { let max_face = faces .iter() .map(|face| face.prim_face_number) .max() .unwrap_or(0); let num_prim_faces = max_face.checked_add(1).ok_or(Error::Argument)?; let face_count = usize::try_from(num_prim_faces).map_err(|_| Error::Argument)?; if face_count > MAX_PRIM_FACES { return Err(Error::Argument); } let mut viewer_vertices: Vec> = (0..face_count).map(|_| Vec::new()).collect(); let mut viewer_polygons: Vec>> = (0..face_count).map(|_| Some(Vec::new())).collect(); let mut indices: Vec> = (0..face_count).map(|_| HashMap::new()).collect(); for face in faces { let face_index = usize::try_from(face.prim_face_number).map_err(|_| Error::IndexOutOfRange)?; if face_index >= face_count { return Err(Error::IndexOutOfRange); } let vertices = &mut viewer_vertices[face_index]; let map = &mut indices[face_index]; let v1 = indexed_vertex(map, vertices, face.coord_index1, face.v1, face.n1, face.uv1)?; let v2 = indexed_vertex(map, vertices, face.coord_index2, face.v2, face.n2, face.uv2)?; let v3 = indexed_vertex(map, vertices, face.coord_index3, face.v3, face.n3, face.uv3)?; viewer_polygons[face_index] .as_mut() .ok_or(Error::InvalidOperation)? .push(ViewerPolygon { v1, v2, v3 }); } Ok(Self { num_prim_faces, viewer_polygons, viewer_vertices, }) } } fn indexed_vertex( indices: &mut HashMap, vertices: &mut Vec, coord_index: i32, v: Coord, n: Coord, uv: UVCoord, ) -> Result { if coord_index < 0 { return Err(Error::IndexOutOfRange); } if let Some(index) = indices.get(&coord_index) { return Ok(*index); } let index = i32::try_from(vertices.len()).map_err(|_| Error::Argument)?; vertices.push(ViewerVertex { n, uv, v }); indices.insert(coord_index, index); Ok(index) } #[cfg(test)] mod tests { use super::*; use crate::ViewerFace; #[test] fn direct_constructor_deduplicates_coordinate_indices_per_prim_face() { let mut first = ViewerFace::new(0).expect("face"); first.coord_index1 = 0; first.coord_index2 = 1; first.coord_index3 = 2; first.v1 = Coord { x: 0.0, y: 0.0, z: 0.0, }; first.v2 = Coord { x: 1.0, y: 0.0, z: 0.0, }; first.v3 = Coord { x: 0.0, y: 1.0, z: 0.0, }; let mut second = first; second.coord_index2 = 2; second.coord_index3 = 3; let indexer = VertexIndexer::from_viewer_faces(&[first, second]).expect("indexer"); assert_eq!(indexer.num_prim_faces, 1); assert_eq!(indexer.viewer_vertices[0].len(), 4); assert_eq!( indexer.viewer_polygons[0].as_ref().expect("polygons"), &vec![ ViewerPolygon { v1: 0, v2: 1, v3: 2 }, ViewerPolygon { v1: 0, v2: 2, v3: 3 }, ] ); } }