use glam::Vec3;
use itertools::Itertools;
use parry3d::bounding_volume::Aabb;
use tracing::{error, instrument};
use crate::{CircularHalfedgesIterator, MeshGraph, error_none};
use super::{FaceId, HalfedgeId, VertexId};
#[derive(Default, Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Face {
pub halfedge: HalfedgeId,
pub index: u32,
pub id: FaceId,
}
impl Face {
#[instrument(skip(mesh_graph))]
pub fn halfedges<'a>(&self, mesh_graph: &'a MeshGraph) -> CircularHalfedgesIterator<'a> {
CircularHalfedgesIterator::new(
Some(self.halfedge),
mesh_graph,
|he, mesh_graph| {
mesh_graph
.halfedges
.get(he)
.or_else(error_none!("Halfedge not found"))?
.next
},
3,
)
}
#[instrument(skip(mesh_graph))]
pub fn vertices(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = VertexId> {
self.halfedges(mesh_graph).filter_map(|he| {
mesh_graph
.halfedges
.get(he)
.or_else(error_none!("Halfedge not found"))
.map(|he| he.end_vertex)
})
}
#[instrument(skip(mesh_graph))]
pub fn center(&self, mesh_graph: &MeshGraph) -> Vec3 {
self.vertex_positions(mesh_graph).sum::<Vec3>() / 3.0
}
#[instrument(skip(mesh_graph))]
pub fn aabb(&self, mesh_graph: &MeshGraph) -> Aabb {
Aabb::from_points(
self.vertex_positions(mesh_graph)
.map(|p| Vec3::new(p.x, p.y, p.z)),
)
}
#[instrument(skip(mesh_graph))]
pub fn vertex_positions(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = Vec3> {
self.vertices(mesh_graph).filter_map(|v| {
mesh_graph
.positions
.get(v)
.or_else(error_none!("Position not found"))
.copied()
})
}
#[instrument(skip(mesh_graph))]
pub fn normal(&self, mesh_graph: &MeshGraph) -> Option<Vec3> {
let positions = self.vertex_positions(mesh_graph).collect_vec();
if positions.len() < 3 {
error!("Face has less than 3 vertex positions");
return None;
}
Some(Self::normal_from_positions(&positions))
}
#[inline]
pub fn normal_from_positions(positions: &[Vec3]) -> Vec3 {
let a = positions[1] - positions[0];
let b = positions[2] - positions[0];
a.cross(b).normalize()
}
#[instrument(skip(mesh_graph))]
pub fn is_degenerate(&self, mesh_graph: &MeshGraph, epsilon_sqr: f32) -> bool {
let positions = self.vertex_positions(mesh_graph).collect_vec();
if positions.len() < 3 {
error!("Face has less than 3 vertex positions");
return true;
}
let p0 = positions[0];
let p1 = positions[1];
let p2 = positions[2];
if p0.distance_squared(p1) < epsilon_sqr
|| p0.distance_squared(p2) < epsilon_sqr
|| p1.distance_squared(p2) < epsilon_sqr
{
return true;
}
let a = p1 - p0;
let b = p2 - p0;
let cross = a.cross(b);
cross.length_squared() < epsilon_sqr
}
#[instrument(skip(mesh_graph))]
pub fn halfedge_between(
&self,
vertex_id1: VertexId,
vertex_id2: VertexId,
mesh_graph: &MeshGraph,
) -> Option<HalfedgeId> {
for he_id in self.halfedges(mesh_graph) {
let he = mesh_graph
.halfedges
.get(he_id)
.or_else(error_none!("Halfedge not found"))?;
if he.end_vertex == vertex_id1 || he.end_vertex == vertex_id2 {
let prev_he_id = he
.prev(mesh_graph)
.or_else(error_none!("Prev halfedge not found"))?;
let prev_he = mesh_graph
.halfedges
.get(prev_he_id)
.or_else(error_none!("Halfedge not found"))?;
if prev_he.end_vertex == vertex_id1 || prev_he.end_vertex == vertex_id2 {
return Some(he_id);
}
}
}
None
}
}