use std::collections::BTreeMap;
use crate::linalg::{Vec2, Vec3, cross, dot, normalize, length2};
use crate::math;
use crate::types::{next_halfedge, Halfedge, PolyVert, PolygonsIdx};
use crate::impl_mesh::ManifoldImpl;
#[derive(Clone, Copy, Debug)]
pub struct Proj2x3 {
pub row0: Vec3,
pub row1: Vec3,
}
impl Proj2x3 {
#[inline]
pub fn apply(&self, v: Vec3) -> Vec2 {
Vec2::new(dot(self.row0, v), dot(self.row1, v))
}
}
pub fn get_axis_aligned_projection(normal: Vec3) -> Proj2x3 {
let abs = Vec3::new(normal.x.abs(), normal.y.abs(), normal.z.abs());
let (row0, row1, xyz_max) = if abs.z > abs.x && abs.z > abs.y {
(Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0), normal.z)
} else if abs.y > abs.x {
(Vec3::new(0.0, 0.0, 1.0), Vec3::new(1.0, 0.0, 0.0), normal.y)
} else {
(Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.0, 0.0, 1.0), normal.x)
};
if xyz_max < 0.0 {
Proj2x3 { row0: Vec3::new(-row0.x, -row0.y, -row0.z), row1 }
} else {
Proj2x3 { row0, row1 }
}
}
pub fn set_normals_and_coplanar(mesh: &mut ManifoldImpl) {
let num_tri = mesh.num_tri();
mesh.face_normal.resize(num_tri, Vec3::new(0.0, 0.0, 1.0));
struct TriPriority {
area2: f64,
tri: usize,
}
let mut tri_priority: Vec<TriPriority> = (0..num_tri)
.map(|tri| {
if tri < mesh.mesh_relation.tri_ref.len() {
mesh.mesh_relation.tri_ref[tri].coplanar_id = -1;
}
if mesh.halfedge[3 * tri].start_vert < 0 {
return TriPriority { area2: 0.0, tri };
}
let v = mesh.vert_pos[mesh.halfedge[3 * tri].start_vert as usize];
let n = cross(
mesh.vert_pos[mesh.halfedge[3 * tri].end_vert as usize] - v,
mesh.vert_pos[mesh.halfedge[3 * tri + 1].end_vert as usize] - v,
);
let normal = normalize(n);
mesh.face_normal[tri] = if normal.x.is_nan() {
Vec3::new(0.0, 0.0, 1.0)
} else {
normal
};
TriPriority { area2: length2(n), tri }
})
.collect();
tri_priority.sort_by(|a, b| b.area2.partial_cmp(&a.area2).unwrap_or(std::cmp::Ordering::Equal));
let mut interior_halfedges: Vec<usize> = Vec::new();
for tp in &tri_priority {
let tri = tp.tri;
if tri >= mesh.mesh_relation.tri_ref.len() {
continue;
}
if mesh.mesh_relation.tri_ref[tri].coplanar_id >= 0 {
continue;
}
mesh.mesh_relation.tri_ref[tri].coplanar_id = tri as i32;
if mesh.halfedge[3 * tri].start_vert < 0 {
continue;
}
let base = mesh.vert_pos[mesh.halfedge[3 * tri].start_vert as usize];
let normal = mesh.face_normal[tri];
interior_halfedges.clear();
interior_halfedges.push(3 * tri);
interior_halfedges.push(3 * tri + 1);
interior_halfedges.push(3 * tri + 2);
while let Some(h) = interior_halfedges.pop() {
let paired = mesh.halfedge[h].paired_halfedge;
if paired < 0 {
continue;
}
let h2 = next_halfedge(paired) as usize;
let h2_tri = h2 / 3;
if h2_tri >= mesh.mesh_relation.tri_ref.len() {
continue;
}
if mesh.mesh_relation.tri_ref[h2_tri].coplanar_id >= 0 {
continue;
}
let v = mesh.vert_pos[mesh.halfedge[h2].end_vert as usize];
if (dot(v - base, normal)).abs() < mesh.tolerance {
mesh.mesh_relation.tri_ref[h2_tri].coplanar_id = tri as i32;
mesh.face_normal[h2_tri] = normal;
let last = interior_halfedges.last().copied();
if last == Some(mesh.halfedge[h2].paired_halfedge as usize) {
interior_halfedges.pop();
} else {
interior_halfedges.push(h2 as usize);
}
interior_halfedges.push(next_halfedge(h2 as i32) as usize);
}
}
}
calculate_vert_normals(mesh);
}
pub fn calculate_vert_normals(mesh: &mut ManifoldImpl) {
let num_vert = mesh.vert_pos.len();
mesh.vert_normal.resize(num_vert, Vec3::new(0.0, 0.0, 0.0));
let mut vert_first_edge = vec![i32::MAX; num_vert];
for (i, edge) in mesh.halfedge.iter().enumerate() {
let sv = edge.start_vert;
if sv >= 0 && (sv as usize) < num_vert {
let sv = sv as usize;
if (i as i32) < vert_first_edge[sv] {
vert_first_edge[sv] = i as i32;
}
}
}
let halfedge = &mesh.halfedge;
let vert_pos = &mesh.vert_pos;
let face_normal = &mesh.face_normal;
let normals: Vec<Vec3> = crate::par::maybe_par_map(num_vert, 10_000, |vert| {
let first_edge = vert_first_edge[vert];
if first_edge == i32::MAX {
return Vec3::new(0.0, 0.0, 0.0);
}
let mut normal = Vec3::new(0.0, 0.0, 0.0);
let mut current = first_edge as usize;
loop {
let paired = halfedge[current].paired_halfedge;
if paired < 0 {
break;
}
current = next_halfedge(paired) as usize;
let h = &halfedge[current];
let tri_verts = [
h.start_vert as usize,
h.end_vert as usize,
halfedge[next_halfedge(current as i32) as usize].end_vert as usize,
];
if tri_verts[0] < vert_pos.len()
&& tri_verts[1] < vert_pos.len()
&& tri_verts[2] < vert_pos.len()
{
let curr_edge_dir = vert_pos[tri_verts[1]] - vert_pos[tri_verts[0]];
let prev_edge_dir = vert_pos[tri_verts[0]] - vert_pos[tri_verts[2]];
let curr_len = length2(curr_edge_dir).sqrt();
let prev_len = length2(prev_edge_dir).sqrt();
if curr_len > 0.0 && prev_len > 0.0 {
let curr_norm = curr_edge_dir / curr_len;
let prev_norm = prev_edge_dir / prev_len;
if curr_norm.x.is_finite() && prev_norm.x.is_finite() {
let d = dot(prev_norm, curr_norm).clamp(-1.0, 1.0);
let phi = math::acos(-d);
if phi.is_finite() && current / 3 < face_normal.len() {
normal = normal + face_normal[current / 3] * phi;
}
}
}
}
if current == first_edge as usize {
break;
}
}
let len = length2(normal).sqrt();
if len > 0.0 { normal / len } else { Vec3::new(0.0, 0.0, 0.0) }
});
mesh.vert_normal = normals;
}
pub fn get_barycentric(v: Vec3, tri_pos: [Vec3; 3], tolerance: f64) -> Vec3 {
let edges = [
tri_pos[2] - tri_pos[1],
tri_pos[0] - tri_pos[2],
tri_pos[1] - tri_pos[0],
];
let d2 = [
dot(edges[0], edges[0]),
dot(edges[1], edges[1]),
dot(edges[2], edges[2]),
];
let long_side = if d2[0] > d2[1] && d2[0] > d2[2] {
0
} else if d2[1] > d2[2] {
1
} else {
2
};
let cross_p = cross(edges[0], edges[1]);
let area2 = dot(cross_p, cross_p);
let tol2 = tolerance * tolerance;
let mut uvw = Vec3::splat(0.0);
for i in 0..3 {
let dv = v - tri_pos[i];
if dot(dv, dv) < tol2 {
uvw = Vec3::splat(0.0);
match i {
0 => uvw.x = 1.0,
1 => uvw.y = 1.0,
_ => uvw.z = 1.0,
}
return uvw;
}
}
if d2[long_side] < tol2 {
return Vec3::new(1.0, 0.0, 0.0);
} else if area2 > d2[long_side] * tol2 {
for i in 0..3 {
let j = (i + 1) % 3;
let cross_pv = cross(edges[i], v - tri_pos[j]);
let area2v = dot(cross_pv, cross_pv);
let val = if area2v < d2[i] * tol2 {
0.0
} else {
dot(cross_pv, cross_p)
};
match i {
0 => uvw.x = val,
1 => uvw.y = val,
_ => uvw.z = val,
}
}
let sum = uvw.x + uvw.y + uvw.z;
uvw = uvw / sum;
return uvw;
} else {
let next_v = (long_side + 1) % 3;
let alpha = dot(v - tri_pos[next_v], edges[long_side]) / d2[long_side];
uvw = Vec3::splat(0.0);
let last_v = (next_v + 1) % 3;
match next_v {
0 => uvw.x = 1.0 - alpha,
1 => uvw.y = 1.0 - alpha,
_ => uvw.z = 1.0 - alpha,
}
match last_v {
0 => uvw.x = alpha,
1 => uvw.y = alpha,
_ => uvw.z = alpha,
}
return uvw;
}
}
pub fn assemble_halfedges(
halfedges: &[Halfedge],
start_halfedge_idx: i32,
) -> Vec<Vec<i32>> {
let mut vert_edge: BTreeMap<i32, Vec<usize>> = BTreeMap::new();
for (i, he) in halfedges.iter().enumerate() {
vert_edge.entry(he.start_vert).or_default().push(i);
}
let mut polys: Vec<Vec<i32>> = Vec::new();
let mut start_edge: usize = 0;
let mut this_edge: usize = start_edge;
loop {
if this_edge == start_edge {
if vert_edge.is_empty() {
break;
}
let (&_vert, edges) = vert_edge.iter().next().unwrap();
start_edge = edges[0];
this_edge = start_edge;
polys.push(Vec::new());
}
polys.last_mut().unwrap().push(start_halfedge_idx + this_edge as i32);
let end_vert = halfedges[this_edge].end_vert;
let edges = vert_edge.get_mut(&end_vert).expect("non-manifold edge");
let pos = 0; this_edge = edges.remove(pos);
if edges.is_empty() {
vert_edge.remove(&end_vert);
}
}
polys
}
pub fn project_polygons(
polys: &[Vec<i32>],
halfedge: &[Halfedge],
vert_pos: &[Vec3],
projection: &Proj2x3,
) -> PolygonsIdx {
let mut polygons: PolygonsIdx = Vec::new();
for poly in polys {
let mut simple_poly = Vec::new();
for &edge in poly {
let vert = halfedge[edge as usize].start_vert;
simple_poly.push(PolyVert {
pos: projection.apply(vert_pos[vert as usize]),
idx: edge,
});
}
polygons.push(simple_poly);
}
polygons
}
#[path = "face_op_triangulate.rs"]
mod face_op_triangulate;
pub use face_op_triangulate::{face2tri, face2tri_ct};
pub fn reorder_halfedges(mesh: &mut ManifoldImpl) {
let num_tri = mesh.halfedge.len() / 3;
for tri in 0..num_tri {
let base = tri * 3;
let face = [
mesh.halfedge[base],
mesh.halfedge[base + 1],
mesh.halfedge[base + 2],
];
if face[0].start_vert < 0 {
continue;
}
let mut index = 0;
for i in 1..3 {
if face[i].start_vert < face[index].start_vert {
index = i;
}
}
for i in 0..3 {
mesh.halfedge[base + i] = face[(index + i) % 3];
}
}
for tri in 0..num_tri {
for i in 0..3 {
let base = tri * 3 + i;
let curr = mesh.halfedge[base];
if curr.start_vert < 0 {
break; }
if curr.paired_halfedge < 0 {
continue; }
let opp_face = curr.paired_halfedge as usize / 3;
let mut index = -1i32;
for j in 0..3 {
if curr.start_vert == mesh.halfedge[opp_face * 3 + j].end_vert {
index = j as i32;
}
}
mesh.halfedge[base].paired_halfedge = opp_face as i32 * 3 + index;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::linalg::Mat3x4;
use crate::impl_mesh::ManifoldImpl;
#[test]
fn test_get_axis_aligned_projection_z() {
let proj = get_axis_aligned_projection(Vec3::new(0.0, 0.0, 1.0));
let v = Vec3::new(3.0, 4.0, 5.0);
let p = proj.apply(v);
assert!((p.x - 3.0).abs() < 1e-12);
assert!((p.y - 4.0).abs() < 1e-12);
}
#[test]
fn test_get_axis_aligned_projection_y() {
let proj = get_axis_aligned_projection(Vec3::new(0.0, 1.0, 0.0));
let v = Vec3::new(3.0, 4.0, 5.0);
let p = proj.apply(v);
assert!((p.x - 5.0).abs() < 1e-12, "expected z=5, got {}", p.x);
assert!((p.y - 3.0).abs() < 1e-12, "expected x=3, got {}", p.y);
}
#[test]
fn test_get_axis_aligned_projection_x() {
let proj = get_axis_aligned_projection(Vec3::new(1.0, 0.0, 0.0));
let v = Vec3::new(3.0, 4.0, 5.0);
let p = proj.apply(v);
assert!((p.x - 4.0).abs() < 1e-12, "expected y=4, got {}", p.x);
assert!((p.y - 5.0).abs() < 1e-12, "expected z=5, got {}", p.y);
}
#[test]
fn test_get_axis_aligned_projection_negative_z() {
let proj = get_axis_aligned_projection(Vec3::new(0.0, 0.0, -1.0));
let v = Vec3::new(3.0, 4.0, 5.0);
let p = proj.apply(v);
assert!((p.x + 3.0).abs() < 1e-12, "expected -x=-3, got {}", p.x);
assert!((p.y - 4.0).abs() < 1e-12);
}
#[test]
fn test_set_normals_tetrahedron() {
let mut m = ManifoldImpl::tetrahedron(&Mat3x4::identity());
set_normals_and_coplanar(&mut m);
for n in &m.face_normal {
let len = length2(*n).sqrt();
assert!((len - 1.0).abs() < 1e-10, "face normal not unit: len={}", len);
}
for n in &m.vert_normal {
let len = length2(*n).sqrt();
assert!(len > 0.0, "vert normal is zero");
}
}
#[test]
fn test_set_normals_cube() {
let mut m = ManifoldImpl::cube(&Mat3x4::identity());
set_normals_and_coplanar(&mut m);
assert_eq!(m.face_normal.len(), m.num_tri());
let any_coplanar_id_set = m.mesh_relation.tri_ref.iter()
.any(|r| r.coplanar_id >= 0);
assert!(any_coplanar_id_set, "no coplanar IDs were set");
}
}
use crate::types::{Box as BBox, Polygons, SimplePolygon};
impl ManifoldImpl {
pub fn slice(&self, height: f64) -> Polygons {
let num_tri = self.num_tri();
if num_tri == 0 {
return vec![];
}
let mut plane = self.bbox;
plane.min.z = height;
plane.max.z = height;
let collider = &self.collider;
let mut tris: std::collections::HashSet<usize> = std::collections::HashSet::new();
let query = vec![BBox::from_points(plane.min, plane.max)];
collider.collisions_with_boxes(&query, false, |_query_idx, tri| {
let mut min_z = f64::INFINITY;
let mut max_z = f64::NEG_INFINITY;
for j in 0..3 {
let z = self.vert_pos[self.halfedge[3 * tri + j].start_vert as usize].z;
min_z = min_z.min(z);
max_z = max_z.max(z);
}
if min_z <= height && max_z > height {
tris.insert(tri);
}
});
let mut polys: Polygons = Vec::new();
while !tris.is_empty() {
let start_tri = *tris.iter().next().unwrap();
let mut poly: SimplePolygon = Vec::new();
let mut k = 0usize;
for j in 0..3usize {
let next_j = (j + 1) % 3;
if self.vert_pos[self.halfedge[3 * start_tri + j].start_vert as usize].z > height
&& self.vert_pos[self.halfedge[3 * start_tri + next_j].start_vert as usize].z <= height
{
k = next_j;
break;
}
}
let mut tri = start_tri;
loop {
tris.remove(&tri);
if self.vert_pos[self.halfedge[3 * tri + k].end_vert as usize].z <= height {
k = (k + 1) % 3;
}
let up = &self.halfedge[3 * tri + k];
let below = self.vert_pos[up.start_vert as usize];
let above = self.vert_pos[up.end_vert as usize];
let a = (height - below.z) / (above.z - below.z);
let pt = Vec2::new(
below.x + a * (above.x - below.x),
below.y + a * (above.y - below.y),
);
poly.push(pt);
let pair = up.paired_halfedge;
tri = pair as usize / 3;
k = ((pair as usize) % 3 + 1) % 3;
if tri == start_tri {
break;
}
}
polys.push(poly);
}
polys
}
pub fn project(&self) -> Polygons {
if self.num_tri() == 0 || self.face_normal.is_empty() {
return vec![];
}
let projection = get_axis_aligned_projection(Vec3::new(0.0, 0.0, 1.0));
let mut cusps: Vec<crate::types::Halfedge> = Vec::new();
for edge in &self.halfedge {
let paired_face = self.halfedge[edge.paired_halfedge as usize].paired_halfedge as usize / 3;
let this_face = edge.paired_halfedge as usize / 3;
if self.face_normal[paired_face].z >= 0.0 && self.face_normal[this_face].z < 0.0 {
cusps.push(*edge);
}
}
if cusps.is_empty() {
return vec![];
}
let loops = assemble_halfedges(&cusps, 0);
let polys_indexed = project_polygons(&loops, &cusps, &self.vert_pos, &projection);
let mut polys: Polygons = Vec::new();
for poly in &polys_indexed {
let simple: SimplePolygon = poly.iter().map(|pv| pv.pos).collect();
polys.push(simple);
}
polys
}
}