use crate::model::{Bounds3, Point3, RigidTransform, TargetRef};
use crate::numeric::Tolerance;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FaceSource {
pub solid: String,
pub face: String,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub protected_only: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Triangle {
pub id: u32,
pub a: Point3,
pub b: Point3,
pub c: Point3,
pub bounds: Bounds3,
pub normal: Point3,
pub face: u32,
}
impl Triangle {
pub fn vertices(&self) -> [Point3; 3] {
[self.a, self.b, self.c]
}
pub fn area_vector(&self) -> Point3 {
self.b.sub(self.a).cross(self.c.sub(self.a))
}
pub fn area(&self) -> f64 {
0.5 * self.area_vector().length()
}
pub fn centroid(&self) -> Point3 {
self.a.add(self.b).add(self.c).scale(1.0 / 3.0)
}
pub fn is_vertical(&self, tol: &Tolerance) -> bool {
tol.is_horizontal(self.normal)
}
pub fn is_horizontal(&self, tol: &Tolerance) -> bool {
tol.is_vertical(self.normal)
}
pub fn min_z(&self) -> f64 {
self.bounds.min.z
}
pub fn max_z(&self) -> f64 {
self.bounds.max.z
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MeshError {
NonFinite { triangle: usize },
BufferShape(String),
IndexOutOfRange { triangle: usize, index: u32 },
FaceOutOfRange { triangle: usize, face: u32 },
}
impl std::fmt::Display for MeshError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MeshError::NonFinite { triangle } => write!(f, "triangle {triangle} has a non-finite coordinate"),
MeshError::BufferShape(msg) => write!(f, "mesh buffers: {msg}"),
MeshError::IndexOutOfRange { triangle, index } => {
write!(f, "triangle {triangle} references vertex {index} past the buffer")
}
MeshError::FaceOutOfRange { triangle, face } => {
write!(f, "triangle {triangle} references face {face} past the face table")
}
}
}
}
impl std::error::Error for MeshError {}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MeshBuildReport {
pub triangles_in: u64,
pub triangles_kept: u64,
pub degenerate_dropped: u64,
}
#[derive(Debug, Default)]
pub struct MeshBuilder {
faces: Vec<FaceSource>,
triangles: Vec<Triangle>,
bounds: Bounds3,
report: MeshBuildReport,
tolerance: Tolerance,
}
impl MeshBuilder {
pub fn new(tolerance: Tolerance) -> Self {
Self { tolerance, bounds: Bounds3::empty(), ..Default::default() }
}
pub fn add_face(&mut self, source: FaceSource) -> u32 {
self.faces.push(source);
(self.faces.len() - 1) as u32
}
pub fn add_triangle(&mut self, face: u32, a: Point3, b: Point3, c: Point3) -> Result<(), MeshError> {
let i = self.report.triangles_in as usize;
self.report.triangles_in += 1;
if face as usize >= self.faces.len() {
return Err(MeshError::FaceOutOfRange { triangle: i, face });
}
if !a.is_finite() || !b.is_finite() || !c.is_finite() {
return Err(MeshError::NonFinite { triangle: i });
}
let area_vector = b.sub(a).cross(c.sub(a));
let twice_area = area_vector.length();
if self.tolerance.is_zero_area(0.5 * twice_area) {
self.report.degenerate_dropped += 1;
return Ok(());
}
let normal = area_vector.scale(1.0 / twice_area);
let bounds = Bounds3::from_points([a, b, c]);
self.bounds = self.bounds.union(&bounds);
let id = self.triangles.len() as u32;
self.triangles.push(Triangle { id, a, b, c, bounds, normal, face });
self.report.triangles_kept += 1;
Ok(())
}
pub fn add_body_buffers(
&mut self,
solid: &str,
protected_only: bool,
positions: &[f64],
indices: &[u32],
face_ids: &[u32],
face_names: &[String],
) -> Result<(), MeshError> {
if positions.len() % 3 != 0 {
return Err(MeshError::BufferShape("positions length is not a multiple of three".into()));
}
if indices.len() % 3 != 0 {
return Err(MeshError::BufferShape("indices length is not a multiple of three".into()));
}
let triangle_count = indices.len() / 3;
if face_ids.len() != triangle_count {
return Err(MeshError::BufferShape(format!(
"face_ids has {} entries for {} triangles",
face_ids.len(),
triangle_count
)));
}
let base_face = self.faces.len() as u32;
for name in face_names {
self.add_face(FaceSource { solid: solid.to_string(), face: name.clone(), protected_only });
}
let vertex_count = (positions.len() / 3) as u32;
let point = |index: u32| -> Point3 {
let i = index as usize * 3;
Point3::new(positions[i], positions[i + 1], positions[i + 2])
};
for (t, tri) in indices.chunks_exact(3).enumerate() {
for &index in tri {
if index >= vertex_count {
return Err(MeshError::IndexOutOfRange { triangle: self.report.triangles_in as usize, index });
}
}
let face_local = face_ids[t];
if face_local as usize >= face_names.len() {
return Err(MeshError::FaceOutOfRange { triangle: self.report.triangles_in as usize, face: face_local });
}
self.add_triangle(base_face + face_local, point(tri[0]), point(tri[1]), point(tri[2]))?;
}
Ok(())
}
pub fn finish(self) -> MeshSnapshot {
MeshSnapshot { faces: self.faces, triangles: self.triangles, bounds: self.bounds, report: self.report }
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MeshSnapshot {
pub faces: Vec<FaceSource>,
pub triangles: Vec<Triangle>,
pub bounds: Bounds3,
#[serde(default)]
pub report: MeshBuildReport,
}
impl MeshSnapshot {
pub fn empty() -> Self {
Self::default()
}
pub fn builder(tolerance: Tolerance) -> MeshBuilder {
MeshBuilder::new(tolerance)
}
pub fn transformed(&self, transform: &RigidTransform) -> MeshSnapshot {
if transform.is_identity() {
return self.clone();
}
let mut bounds = Bounds3::empty();
let triangles = self
.triangles
.iter()
.map(|t| {
let a = transform.apply_point(t.a);
let b = transform.apply_point(t.b);
let c = transform.apply_point(t.c);
let tb = Bounds3::from_points([a, b, c]);
bounds = bounds.union(&tb);
Triangle { id: t.id, a, b, c, bounds: tb, normal: transform.apply_vector(t.normal), face: t.face }
})
.collect();
MeshSnapshot { faces: self.faces.clone(), triangles, bounds, report: self.report }
}
pub fn triangle_count(&self) -> usize {
self.triangles.len()
}
pub fn is_empty(&self) -> bool {
self.triangles.is_empty()
}
pub fn triangle(&self, id: u32) -> &Triangle {
&self.triangles[id as usize]
}
pub fn face_of(&self, id: u32) -> &FaceSource {
&self.faces[self.triangles[id as usize].face as usize]
}
pub fn resolve_faces(&self, target: &TargetRef) -> Vec<u32> {
self.faces
.iter()
.enumerate()
.filter(|(_, f)| f.solid == target.solid && target.face.as_ref().map_or(true, |name| *name == f.face))
.map(|(i, _)| i as u32)
.collect()
}
pub fn triangles_of_faces(&self, faces: &[u32]) -> Vec<u32> {
let mut mark = vec![false; self.faces.len()];
for &f in faces {
if let Some(m) = mark.get_mut(f as usize) {
*m = true;
}
}
self.triangles.iter().filter(|t| mark[t.face as usize]).map(|t| t.id).collect()
}
pub fn triangles_of(&self, target: &TargetRef) -> Vec<u32> {
self.triangles_of_faces(&self.resolve_faces(target))
}
pub fn solids(&self) -> Vec<String> {
let mut names: Vec<String> = self.faces.iter().map(|f| f.solid.clone()).collect();
names.sort();
names.dedup();
names
}
pub fn bounds_of(&self, ids: &[u32]) -> Bounds3 {
ids.iter().fold(Bounds3::empty(), |b, &id| b.union(&self.triangles[id as usize].bounds))
}
}
pub fn to_machine(setup: &crate::model::Setup, model: &MeshSnapshot) -> MeshSnapshot {
model.transformed(&setup.model_to_machine)
}
pub trait TriangleSource {
fn triangle_count(&self) -> usize;
fn triangle(&self, id: u32) -> &Triangle;
fn bounds(&self) -> Bounds3;
fn face_source(&self, face: u32) -> &FaceSource;
}
impl TriangleSource for MeshSnapshot {
fn triangle_count(&self) -> usize {
self.triangles.len()
}
fn triangle(&self, id: u32) -> &Triangle {
&self.triangles[id as usize]
}
fn bounds(&self) -> Bounds3 {
self.bounds
}
fn face_source(&self, face: u32) -> &FaceSource {
&self.faces[face as usize]
}
}