Some checks failed
JPEG 2000 feature / linux (push) Has been cancelled
JPEG 2000 feature / macos (push) Has been cancelled
JPEG 2000 feature / windows (push) Has been cancelled
Skia feature / linux (push) Has been cancelled
Skia feature / macos (push) Has been cancelled
Skia feature / windows (push) Has been cancelled
168 lines
5.0 KiB
Rust
168 lines
5.0 KiB
Rust
//! 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<Self, Error> {
|
|
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<Self, Error> {
|
|
Ok(Self { v1, v2, v3 })
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq)]
|
|
pub struct VertexIndexer {
|
|
pub num_prim_faces: i32,
|
|
pub viewer_polygons: Vec<Option<Vec<ViewerPolygon>>>,
|
|
pub viewer_vertices: Vec<Vec<ViewerVertex>>,
|
|
}
|
|
|
|
impl VertexIndexer {
|
|
pub fn new_with_constructor() -> Result<Self, Error> {
|
|
Ok(Self::default())
|
|
}
|
|
|
|
pub fn new_with_prim_mesh(prim_mesh: PrimMesh) -> Result<Self, Error> {
|
|
Self::from_viewer_faces(&prim_mesh.viewer_faces)
|
|
}
|
|
|
|
pub(crate) fn from_viewer_faces(faces: &[crate::ViewerFace]) -> Result<Self, Error> {
|
|
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<Vec<ViewerVertex>> =
|
|
(0..face_count).map(|_| Vec::new()).collect();
|
|
let mut viewer_polygons: Vec<Option<Vec<ViewerPolygon>>> =
|
|
(0..face_count).map(|_| Some(Vec::new())).collect();
|
|
let mut indices: Vec<HashMap<i32, i32>> = (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<i32, i32>,
|
|
vertices: &mut Vec<ViewerVertex>,
|
|
coord_index: i32,
|
|
v: Coord,
|
|
n: Coord,
|
|
uv: UVCoord,
|
|
) -> Result<i32, Error> {
|
|
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
|
|
},
|
|
]
|
|
);
|
|
}
|
|
}
|