1use std::collections::BTreeMap;
8
9use crate::linalg::{Vec2, Vec3, cross, dot, normalize, length2};
10use crate::math;
11use crate::polygon::{ccw, triangulate_idx_halfedges, HalfedgeTriangulation};
12use crate::types::{next_halfedge, Halfedge, PolyVert, PolygonsIdx, TriRef};
13use crate::impl_mesh::ManifoldImpl;
14
15#[derive(Clone, Copy, Debug)]
24pub struct Proj2x3 {
25 pub row0: Vec3,
26 pub row1: Vec3,
27}
28
29impl Proj2x3 {
30 #[inline]
32 pub fn apply(&self, v: Vec3) -> Vec2 {
33 Vec2::new(dot(self.row0, v), dot(self.row1, v))
34 }
35}
36
37pub fn get_axis_aligned_projection(normal: Vec3) -> Proj2x3 {
46 let abs = Vec3::new(normal.x.abs(), normal.y.abs(), normal.z.abs());
47
48 let (row0, row1, xyz_max) = if abs.z > abs.x && abs.z > abs.y {
50 (Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0), normal.z)
52 } else if abs.y > abs.x {
53 (Vec3::new(0.0, 0.0, 1.0), Vec3::new(1.0, 0.0, 0.0), normal.y)
55 } else {
56 (Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.0, 0.0, 1.0), normal.x)
58 };
59
60 if xyz_max < 0.0 {
63 Proj2x3 { row0: Vec3::new(-row0.x, -row0.y, -row0.z), row1 }
64 } else {
65 Proj2x3 { row0, row1 }
66 }
67}
68
69pub fn set_normals_and_coplanar(mesh: &mut ManifoldImpl) {
78 let num_tri = mesh.num_tri();
79 mesh.face_normal.resize(num_tri, Vec3::new(0.0, 0.0, 1.0));
80
81 struct TriPriority {
83 area2: f64,
84 tri: usize,
85 }
86
87 let mut tri_priority: Vec<TriPriority> = (0..num_tri)
89 .map(|tri| {
90 if tri < mesh.mesh_relation.tri_ref.len() {
92 mesh.mesh_relation.tri_ref[tri].coplanar_id = -1;
93 }
94
95 if mesh.halfedge[3 * tri].start_vert < 0 {
96 return TriPriority { area2: 0.0, tri };
97 }
98 let v = mesh.vert_pos[mesh.halfedge[3 * tri].start_vert as usize];
99 let n = cross(
100 mesh.vert_pos[mesh.halfedge[3 * tri].end_vert as usize] - v,
101 mesh.vert_pos[mesh.halfedge[3 * tri + 1].end_vert as usize] - v,
102 );
103 let normal = normalize(n);
104 mesh.face_normal[tri] = if normal.x.is_nan() {
105 Vec3::new(0.0, 0.0, 1.0)
106 } else {
107 normal
108 };
109 TriPriority { area2: length2(n), tri }
110 })
111 .collect();
112
113 tri_priority.sort_by(|a, b| b.area2.partial_cmp(&a.area2).unwrap_or(std::cmp::Ordering::Equal));
115
116 let mut interior_halfedges: Vec<usize> = Vec::new();
118 for tp in &tri_priority {
119 let tri = tp.tri;
120 if tri >= mesh.mesh_relation.tri_ref.len() {
121 continue;
122 }
123 if mesh.mesh_relation.tri_ref[tri].coplanar_id >= 0 {
124 continue;
125 }
126
127 mesh.mesh_relation.tri_ref[tri].coplanar_id = tri as i32;
128 if mesh.halfedge[3 * tri].start_vert < 0 {
129 continue;
130 }
131
132 let base = mesh.vert_pos[mesh.halfedge[3 * tri].start_vert as usize];
133 let normal = mesh.face_normal[tri];
134
135 interior_halfedges.clear();
136 interior_halfedges.push(3 * tri);
137 interior_halfedges.push(3 * tri + 1);
138 interior_halfedges.push(3 * tri + 2);
139
140 while let Some(h) = interior_halfedges.pop() {
141 let paired = mesh.halfedge[h].paired_halfedge;
142 if paired < 0 {
143 continue;
144 }
145 let h2 = next_halfedge(paired) as usize;
146 let h2_tri = h2 / 3;
147 if h2_tri >= mesh.mesh_relation.tri_ref.len() {
148 continue;
149 }
150 if mesh.mesh_relation.tri_ref[h2_tri].coplanar_id >= 0 {
151 continue;
152 }
153
154 let v = mesh.vert_pos[mesh.halfedge[h2].end_vert as usize];
155 if (dot(v - base, normal)).abs() < mesh.tolerance {
156 mesh.mesh_relation.tri_ref[h2_tri].coplanar_id = tri as i32;
157 mesh.face_normal[h2_tri] = normal;
158
159 let last = interior_halfedges.last().copied();
161 if last == Some(mesh.halfedge[h2].paired_halfedge as usize) {
162 interior_halfedges.pop();
163 } else {
164 interior_halfedges.push(h2 as usize);
165 }
166 interior_halfedges.push(next_halfedge(h2 as i32) as usize);
167 }
168 }
169 }
170
171 calculate_vert_normals(mesh);
172}
173
174pub fn calculate_vert_normals(mesh: &mut ManifoldImpl) {
182 let num_vert = mesh.vert_pos.len();
183 mesh.vert_normal.resize(num_vert, Vec3::new(0.0, 0.0, 0.0));
184
185 let mut vert_first_edge = vec![i32::MAX; num_vert];
187 for (i, edge) in mesh.halfedge.iter().enumerate() {
188 let sv = edge.start_vert;
189 if sv >= 0 && (sv as usize) < num_vert {
190 let sv = sv as usize;
191 if (i as i32) < vert_first_edge[sv] {
192 vert_first_edge[sv] = i as i32;
193 }
194 }
195 }
196
197 let halfedge = &mesh.halfedge;
201 let vert_pos = &mesh.vert_pos;
202 let face_normal = &mesh.face_normal;
203 let normals: Vec<Vec3> = crate::par::maybe_par_map(num_vert, 10_000, |vert| {
204 let first_edge = vert_first_edge[vert];
205 if first_edge == i32::MAX {
206 return Vec3::new(0.0, 0.0, 0.0);
207 }
208
209 let mut normal = Vec3::new(0.0, 0.0, 0.0);
210
211 let mut current = first_edge as usize;
217 loop {
218 let paired = halfedge[current].paired_halfedge;
219 if paired < 0 {
220 break;
221 }
222 current = next_halfedge(paired) as usize;
223 let h = &halfedge[current];
224 let tri_verts = [
225 h.start_vert as usize,
226 h.end_vert as usize,
227 halfedge[next_halfedge(current as i32) as usize].end_vert as usize,
228 ];
229
230 if tri_verts[0] < vert_pos.len()
232 && tri_verts[1] < vert_pos.len()
233 && tri_verts[2] < vert_pos.len()
234 {
235 let curr_edge_dir = vert_pos[tri_verts[1]] - vert_pos[tri_verts[0]];
236 let prev_edge_dir = vert_pos[tri_verts[0]] - vert_pos[tri_verts[2]];
237 let curr_len = length2(curr_edge_dir).sqrt();
238 let prev_len = length2(prev_edge_dir).sqrt();
239
240 if curr_len > 0.0 && prev_len > 0.0 {
241 let curr_norm = curr_edge_dir / curr_len;
242 let prev_norm = prev_edge_dir / prev_len;
243 if curr_norm.x.is_finite() && prev_norm.x.is_finite() {
244 let d = dot(prev_norm, curr_norm).clamp(-1.0, 1.0);
245 let phi = math::acos(-d);
247 if phi.is_finite() && current / 3 < face_normal.len() {
248 normal = normal + face_normal[current / 3] * phi;
249 }
250 }
251 }
252 }
253
254 if current == first_edge as usize {
255 break;
256 }
257 }
258
259 let len = length2(normal).sqrt();
260 if len > 0.0 { normal / len } else { Vec3::new(0.0, 0.0, 0.0) }
261 });
262 mesh.vert_normal = normals;
263}
264
265pub fn get_barycentric(v: Vec3, tri_pos: [Vec3; 3], tolerance: f64) -> Vec3 {
276 let edges = [
277 tri_pos[2] - tri_pos[1],
278 tri_pos[0] - tri_pos[2],
279 tri_pos[1] - tri_pos[0],
280 ];
281 let d2 = [
282 dot(edges[0], edges[0]),
283 dot(edges[1], edges[1]),
284 dot(edges[2], edges[2]),
285 ];
286 let long_side = if d2[0] > d2[1] && d2[0] > d2[2] {
287 0
288 } else if d2[1] > d2[2] {
289 1
290 } else {
291 2
292 };
293 let cross_p = cross(edges[0], edges[1]);
294 let area2 = dot(cross_p, cross_p);
295 let tol2 = tolerance * tolerance;
296
297 let mut uvw = Vec3::splat(0.0);
298 for i in 0..3 {
299 let dv = v - tri_pos[i];
300 if dot(dv, dv) < tol2 {
301 uvw = Vec3::splat(0.0);
302 match i {
303 0 => uvw.x = 1.0,
304 1 => uvw.y = 1.0,
305 _ => uvw.z = 1.0,
306 }
307 return uvw;
308 }
309 }
310
311 if d2[long_side] < tol2 {
312 return Vec3::new(1.0, 0.0, 0.0);
314 } else if area2 > d2[long_side] * tol2 {
315 for i in 0..3 {
317 let j = (i + 1) % 3;
318 let cross_pv = cross(edges[i], v - tri_pos[j]);
319 let area2v = dot(cross_pv, cross_pv);
320 let val = if area2v < d2[i] * tol2 {
321 0.0
322 } else {
323 dot(cross_pv, cross_p)
324 };
325 match i {
326 0 => uvw.x = val,
327 1 => uvw.y = val,
328 _ => uvw.z = val,
329 }
330 }
331 let sum = uvw.x + uvw.y + uvw.z;
332 uvw = uvw / sum;
333 return uvw;
334 } else {
335 let next_v = (long_side + 1) % 3;
337 let alpha = dot(v - tri_pos[next_v], edges[long_side]) / d2[long_side];
338 uvw = Vec3::splat(0.0);
339 let last_v = (next_v + 1) % 3;
340 match next_v {
341 0 => uvw.x = 1.0 - alpha,
342 1 => uvw.y = 1.0 - alpha,
343 _ => uvw.z = 1.0 - alpha,
344 }
345 match last_v {
346 0 => uvw.x = alpha,
347 1 => uvw.y = alpha,
348 _ => uvw.z = alpha,
349 }
350 return uvw;
351 }
352}
353
354pub fn assemble_halfedges(
365 halfedges: &[Halfedge],
366 start_halfedge_idx: i32,
367) -> Vec<Vec<i32>> {
368 let mut vert_edge: BTreeMap<i32, Vec<usize>> = BTreeMap::new();
370 for (i, he) in halfedges.iter().enumerate() {
371 vert_edge.entry(he.start_vert).or_default().push(i);
372 }
373
374 let mut polys: Vec<Vec<i32>> = Vec::new();
375 let mut start_edge: usize = 0;
376 let mut this_edge: usize = start_edge;
377
378 loop {
379 if this_edge == start_edge {
380 if vert_edge.is_empty() {
382 break;
383 }
384 let (&_vert, edges) = vert_edge.iter().next().unwrap();
385 start_edge = edges[0];
386 this_edge = start_edge;
387 polys.push(Vec::new());
388 }
389 polys.last_mut().unwrap().push(start_halfedge_idx + this_edge as i32);
390 let end_vert = halfedges[this_edge].end_vert;
391 let edges = vert_edge.get_mut(&end_vert).expect("non-manifold edge");
392 let pos = 0; this_edge = edges.remove(pos);
395 if edges.is_empty() {
396 vert_edge.remove(&end_vert);
397 }
398 }
399 polys
400}
401
402pub fn project_polygons(
406 polys: &[Vec<i32>],
407 halfedge: &[Halfedge],
408 vert_pos: &[Vec3],
409 projection: &Proj2x3,
410) -> PolygonsIdx {
411 let mut polygons: PolygonsIdx = Vec::new();
412 for poly in polys {
413 let mut simple_poly = Vec::new();
414 for &edge in poly {
415 let vert = halfedge[edge as usize].start_vert;
416 simple_poly.push(PolyVert {
417 pos: projection.apply(vert_pos[vert as usize]),
418 idx: edge,
419 });
420 }
421 polygons.push(simple_poly);
422 }
423 polygons
424}
425
426fn write_local_triangles(
436 output: &mut [Halfedge],
437 contour2tri: &mut [i32],
438 face_halfedge: &[Halfedge],
439 first_tri: usize,
440 triangles: &[[i32; 3]],
441) {
442 debug_assert!(triangles.len() <= 2, "local face path only handles tris/quads");
443 let first_out = 3 * first_tri as i32;
444 let mut local_edges: [[i32; 3]; 6] = [[0; 3]; 6];
447 let mut num_edge = 0usize;
448 for tri in triangles {
449 for i in 0..3 {
450 let out = first_out + num_edge as i32;
451 let start = tri[i];
452 let end = tri[(i + 1) % 3];
453 local_edges[num_edge] = [start, end, out];
454 output[out as usize].start_vert = face_halfedge[start as usize].start_vert;
455 output[out as usize].end_vert = face_halfedge[end as usize].start_vert;
458 output[out as usize].prop_vert = face_halfedge[start as usize].prop_vert;
459 output[out as usize].paired_halfedge = -1;
460 num_edge += 1;
461 }
462 }
463
464 for i in 0..num_edge {
465 let edge = local_edges[i];
466 let mut pair = -1;
467 for j in 0..num_edge {
468 if local_edges[j][0] == edge[1] && local_edges[j][1] == edge[0] {
469 pair = local_edges[j][2];
470 break;
471 }
472 }
473 if pair >= 0 {
474 output[edge[2] as usize].paired_halfedge = pair;
475 } else {
476 contour2tri[edge[0] as usize] = edge[2];
477 }
478 }
479}
480
481fn write_general_triangulation(
486 output: &mut [Halfedge],
487 contour2tri: &mut [i32],
488 face_halfedge: &[Halfedge],
489 first_tri: usize,
490 triangulation: &HalfedgeTriangulation,
491) {
492 let first_out = 3 * first_tri as i32;
493 let contour_end = triangulation.contour_end;
494 let num_tri_halfedge = 3 * triangulation.num_tri();
495
496 for local in 0..num_tri_halfedge {
497 let out = (first_out as usize) + local;
498 let edge = &triangulation.halfedges[contour_end + local];
499 output[out].start_vert = face_halfedge[edge.start_vert as usize].start_vert;
500 output[out].end_vert = face_halfedge[edge.end_vert as usize].start_vert;
501 output[out].prop_vert = face_halfedge[edge.start_vert as usize].prop_vert;
502 if edge.paired_halfedge >= contour_end as i32 {
503 output[out].paired_halfedge = first_out + edge.paired_halfedge - contour_end as i32;
504 } else {
505 output[out].paired_halfedge = -1;
506 }
507 }
508
509 for contour in 0..contour_end {
510 let edge = &triangulation.halfedges[contour];
511 if edge.paired_halfedge < 0 {
512 continue;
513 }
514 debug_assert!(
515 edge.paired_halfedge >= contour_end as i32,
516 "contour paired to another contour"
517 );
518 let boundary = edge.end_vert;
521 debug_assert!(
522 boundary >= 0 && (boundary as usize) < contour2tri.len(),
523 "contour edge index out of bounds"
524 );
525 contour2tri[boundary as usize] = first_out + edge.paired_halfedge - contour_end as i32;
526 }
527}
528
529pub fn face2tri(
541 mesh: &mut ManifoldImpl,
542 face_edge: &[i32],
543 halfedge_ref: &[TriRef],
544 allow_convex: bool,
545) {
546 let face_halfedge = std::mem::take(&mut mesh.halfedge);
549 let num_faces = face_edge.len() - 1;
550 let mut contour2tri = vec![-1i32; face_halfedge.len()];
551
552 let face_normal_ref = &mesh.face_normal;
557 let vert_pos_ref = &mesh.vert_pos;
558 let epsilon = mesh.epsilon;
559 let general: Vec<Option<HalfedgeTriangulation>> =
560 crate::par::maybe_par_map(num_faces, 512, |face| {
561 let num_edge = (face_edge[face + 1] - face_edge[face]) as usize;
562 if num_edge <= 4 {
563 return None;
564 }
565 let first_edge = face_edge[face] as usize;
566 let last_edge = face_edge[face + 1] as usize;
567 let projection = get_axis_aligned_projection(face_normal_ref[face]);
568 let polys_loops =
569 assemble_halfedges(&face_halfedge[first_edge..last_edge], first_edge as i32);
570 let polys = project_polygons(&polys_loops, &face_halfedge, vert_pos_ref, &projection);
571 Some(triangulate_idx_halfedges(&polys, epsilon, allow_convex))
572 });
573
574 let mut tri_offset = vec![0usize; face_edge.len()];
575 let mut results: std::collections::HashMap<usize, HalfedgeTriangulation> =
576 std::collections::HashMap::new();
577 for (face, triangulation) in general.into_iter().enumerate() {
578 let num_edge = (face_edge[face + 1] - face_edge[face]) as usize;
579 if num_edge == 0 {
580 continue;
581 }
582 debug_assert!(num_edge >= 3, "face has less than three edges");
583 tri_offset[face] = num_edge - 2;
584 if let Some(t) = triangulation {
585 tri_offset[face] = t.num_tri();
586 results.insert(face, t);
587 }
588 }
589
590 let mut acc = 0usize;
592 for entry in tri_offset.iter_mut() {
593 let count = *entry;
594 *entry = acc;
595 acc += count;
596 }
597 let num_tri = acc;
598
599 let mut new_halfedge = vec![
600 Halfedge {
601 start_vert: -1,
602 end_vert: -1,
603 paired_halfedge: -1,
604 prop_vert: -1,
605 };
606 3 * num_tri
607 ];
608 let mut tri_normal = vec![Vec3::new(0.0, 0.0, 0.0); num_tri];
609 let mut tri_ref = vec![TriRef::default(); num_tri];
610
611 for face in 0..num_faces {
612 let first_edge = face_edge[face] as usize;
613 let last_edge = face_edge[face + 1] as usize;
614 let num_edge = last_edge - first_edge;
615 if num_edge == 0 {
616 continue;
617 }
618 let normal = mesh.face_normal[face];
619 let first_tri = tri_offset[face];
620 let face_num_tri;
621
622 if num_edge == 3 {
623 let mut tri_edge = [first_edge as i32, first_edge as i32 + 1, first_edge as i32 + 2];
625 let mut tri = [
626 face_halfedge[first_edge].start_vert,
627 face_halfedge[first_edge + 1].start_vert,
628 face_halfedge[first_edge + 2].start_vert,
629 ];
630 let mut ends = [
631 face_halfedge[first_edge].end_vert,
632 face_halfedge[first_edge + 1].end_vert,
633 face_halfedge[first_edge + 2].end_vert,
634 ];
635 if ends[0] == tri[2] {
636 tri_edge.swap(1, 2);
637 tri.swap(1, 2);
638 ends.swap(1, 2);
639 }
640 debug_assert!(
641 ends[0] == tri[1] && ends[1] == tri[2] && ends[2] == tri[0],
642 "these 3 edges do not form a triangle!"
643 );
644 write_local_triangles(
645 &mut new_halfedge,
646 &mut contour2tri,
647 &face_halfedge,
648 first_tri,
649 &[tri_edge],
650 );
651 face_num_tri = 1;
652 } else if num_edge == 4 {
653 let projection = get_axis_aligned_projection(normal);
655 let tri_ccw = |t: [i32; 3]| -> bool {
656 ccw(
657 projection.apply(mesh.vert_pos[face_halfedge[t[0] as usize].start_vert as usize]),
658 projection.apply(mesh.vert_pos[face_halfedge[t[1] as usize].start_vert as usize]),
659 projection.apply(mesh.vert_pos[face_halfedge[t[2] as usize].start_vert as usize]),
660 mesh.epsilon,
661 ) >= 0
662 };
663
664 let quad_loops =
665 assemble_halfedges(&face_halfedge[first_edge..last_edge], first_edge as i32);
666 let quad = &quad_loops[0]; let tris0 = [
669 [quad[0], quad[1], quad[2]],
670 [quad[0], quad[2], quad[3]],
671 ];
672 let tris1 = [
673 [quad[1], quad[2], quad[3]],
674 [quad[0], quad[1], quad[3]],
675 ];
676
677 let choice = if !(tri_ccw(tris0[0]) && tri_ccw(tris0[1])) {
678 1
679 } else if tri_ccw(tris1[0]) && tri_ccw(tris1[1]) {
680 let diag0 = mesh.vert_pos[face_halfedge[quad[0] as usize].start_vert as usize]
681 - mesh.vert_pos[face_halfedge[quad[2] as usize].start_vert as usize];
682 let diag1 = mesh.vert_pos[face_halfedge[quad[1] as usize].start_vert as usize]
683 - mesh.vert_pos[face_halfedge[quad[3] as usize].start_vert as usize];
684 if length2(diag0) > length2(diag1) { 1 } else { 0 }
685 } else {
686 0
687 };
688
689 let chosen = if choice == 0 { &tris0 } else { &tris1 };
690 write_local_triangles(
691 &mut new_halfedge,
692 &mut contour2tri,
693 &face_halfedge,
694 first_tri,
695 chosen,
696 );
697 face_num_tri = 2;
698 } else {
699 let triangulation = results
701 .get(&face)
702 .expect("general face missing triangulation result");
703 write_general_triangulation(
704 &mut new_halfedge,
705 &mut contour2tri,
706 &face_halfedge,
707 first_tri,
708 triangulation,
709 );
710 face_num_tri = triangulation.num_tri();
711 }
712
713 let ref_tri = halfedge_ref[first_edge];
715 for t in 0..face_num_tri {
716 tri_normal[first_tri + t] = normal;
717 tri_ref[first_tri + t] = ref_tri;
718 }
719 }
720
721 for edge in 0..face_halfedge.len() {
724 let tri_edge = contour2tri[edge];
725 if tri_edge < 0 {
726 continue;
727 }
728 let pair = face_halfedge[edge].paired_halfedge;
729 if pair < 0 {
730 continue;
731 }
732 let pair_tri = contour2tri[pair as usize];
733 debug_assert!(pair_tri >= 0, "boundary edge did not triangulate with its pair");
734 new_halfedge[tri_edge as usize].paired_halfedge = pair_tri;
735 }
736
737 mesh.halfedge = new_halfedge;
738 mesh.face_normal = tri_normal;
739 mesh.mesh_relation.tri_ref = tri_ref;
740}
741
742pub fn reorder_halfedges(mesh: &mut ManifoldImpl) {
751 let num_tri = mesh.halfedge.len() / 3;
752
753 for tri in 0..num_tri {
755 let base = tri * 3;
756 let face = [
757 mesh.halfedge[base],
758 mesh.halfedge[base + 1],
759 mesh.halfedge[base + 2],
760 ];
761 if face[0].start_vert < 0 {
762 continue;
763 }
764 let mut index = 0;
765 for i in 1..3 {
766 if face[i].start_vert < face[index].start_vert {
767 index = i;
768 }
769 }
770 for i in 0..3 {
771 mesh.halfedge[base + i] = face[(index + i) % 3];
772 }
773 }
774
775 for tri in 0..num_tri {
777 for i in 0..3 {
778 let base = tri * 3 + i;
779 let curr = mesh.halfedge[base];
780 if curr.start_vert < 0 {
781 break; }
783 if curr.paired_halfedge < 0 {
784 continue; }
786 let opp_face = curr.paired_halfedge as usize / 3;
787 let mut index = -1i32;
788 for j in 0..3 {
789 if curr.start_vert == mesh.halfedge[opp_face * 3 + j].end_vert {
790 index = j as i32;
791 }
792 }
793 mesh.halfedge[base].paired_halfedge = opp_face as i32 * 3 + index;
794 }
795 }
796}
797
798#[cfg(test)]
803mod tests {
804 use super::*;
805 use crate::linalg::Mat3x4;
806 use crate::impl_mesh::ManifoldImpl;
807
808 #[test]
809 fn test_get_axis_aligned_projection_z() {
810 let proj = get_axis_aligned_projection(Vec3::new(0.0, 0.0, 1.0));
812 let v = Vec3::new(3.0, 4.0, 5.0);
813 let p = proj.apply(v);
814 assert!((p.x - 3.0).abs() < 1e-12);
815 assert!((p.y - 4.0).abs() < 1e-12);
816 }
817
818 #[test]
819 fn test_get_axis_aligned_projection_y() {
820 let proj = get_axis_aligned_projection(Vec3::new(0.0, 1.0, 0.0));
822 let v = Vec3::new(3.0, 4.0, 5.0);
823 let p = proj.apply(v);
824 assert!((p.x - 5.0).abs() < 1e-12, "expected z=5, got {}", p.x);
825 assert!((p.y - 3.0).abs() < 1e-12, "expected x=3, got {}", p.y);
826 }
827
828 #[test]
829 fn test_get_axis_aligned_projection_x() {
830 let proj = get_axis_aligned_projection(Vec3::new(1.0, 0.0, 0.0));
832 let v = Vec3::new(3.0, 4.0, 5.0);
833 let p = proj.apply(v);
834 assert!((p.x - 4.0).abs() < 1e-12, "expected y=4, got {}", p.x);
835 assert!((p.y - 5.0).abs() < 1e-12, "expected z=5, got {}", p.y);
836 }
837
838 #[test]
839 fn test_get_axis_aligned_projection_negative_z() {
840 let proj = get_axis_aligned_projection(Vec3::new(0.0, 0.0, -1.0));
842 let v = Vec3::new(3.0, 4.0, 5.0);
843 let p = proj.apply(v);
844 assert!((p.x + 3.0).abs() < 1e-12, "expected -x=-3, got {}", p.x);
846 assert!((p.y - 4.0).abs() < 1e-12);
847 }
848
849 #[test]
850 fn test_set_normals_tetrahedron() {
851 let mut m = ManifoldImpl::tetrahedron(&Mat3x4::identity());
852 set_normals_and_coplanar(&mut m);
853 for n in &m.face_normal {
855 let len = length2(*n).sqrt();
856 assert!((len - 1.0).abs() < 1e-10, "face normal not unit: len={}", len);
857 }
858 for n in &m.vert_normal {
860 let len = length2(*n).sqrt();
861 assert!(len > 0.0, "vert normal is zero");
862 }
863 }
864
865 #[test]
866 fn test_set_normals_cube() {
867 let mut m = ManifoldImpl::cube(&Mat3x4::identity());
868 set_normals_and_coplanar(&mut m);
869 assert_eq!(m.face_normal.len(), m.num_tri());
870 let any_coplanar_id_set = m.mesh_relation.tri_ref.iter()
872 .any(|r| r.coplanar_id >= 0);
873 assert!(any_coplanar_id_set, "no coplanar IDs were set");
874 }
875}
876
877use crate::collider::Collider;
882use crate::sort::get_face_box_morton;
883use crate::types::{Box as BBox, Polygons, SimplePolygon};
884
885impl ManifoldImpl {
886 pub fn slice(&self, height: f64) -> Polygons {
889 let num_tri = self.num_tri();
890 if num_tri == 0 {
891 return vec![];
892 }
893
894 let mut plane = self.bbox;
896 plane.min.z = height;
897 plane.max.z = height;
898
899 let (face_box, face_morton) = get_face_box_morton(self);
901 let collider = Collider::new(face_box, face_morton);
902
903 let mut tris: std::collections::HashSet<usize> = std::collections::HashSet::new();
905 let query = vec![BBox::from_points(plane.min, plane.max)];
906 collider.collisions_with_boxes(&query, false, |_query_idx, tri| {
907 let mut min_z = f64::INFINITY;
908 let mut max_z = f64::NEG_INFINITY;
909 for j in 0..3 {
910 let z = self.vert_pos[self.halfedge[3 * tri + j].start_vert as usize].z;
911 min_z = min_z.min(z);
912 max_z = max_z.max(z);
913 }
914 if min_z <= height && max_z > height {
915 tris.insert(tri);
916 }
917 });
918
919 let mut polys: Polygons = Vec::new();
921 while !tris.is_empty() {
922 let start_tri = *tris.iter().next().unwrap();
923 let mut poly: SimplePolygon = Vec::new();
924
925 let mut k = 0usize;
927 for j in 0..3usize {
928 let next_j = (j + 1) % 3;
929 if self.vert_pos[self.halfedge[3 * start_tri + j].start_vert as usize].z > height
930 && self.vert_pos[self.halfedge[3 * start_tri + next_j].start_vert as usize].z <= height
931 {
932 k = next_j;
933 break;
934 }
935 }
936
937 let mut tri = start_tri;
938 loop {
939 tris.remove(&tri);
940 if self.vert_pos[self.halfedge[3 * tri + k].end_vert as usize].z <= height {
941 k = (k + 1) % 3;
942 }
943
944 let up = &self.halfedge[3 * tri + k];
945 let below = self.vert_pos[up.start_vert as usize];
946 let above = self.vert_pos[up.end_vert as usize];
947 let a = (height - below.z) / (above.z - below.z);
948 let pt = Vec2::new(
950 below.x + a * (above.x - below.x),
951 below.y + a * (above.y - below.y),
952 );
953 poly.push(pt);
954
955 let pair = up.paired_halfedge;
956 tri = pair as usize / 3;
957 k = ((pair as usize) % 3 + 1) % 3;
958
959 if tri == start_tri {
960 break;
961 }
962 }
963
964 polys.push(poly);
965 }
966
967 polys
968 }
969
970 pub fn project(&self) -> Polygons {
973 if self.num_tri() == 0 || self.face_normal.is_empty() {
974 return vec![];
975 }
976
977 let projection = get_axis_aligned_projection(Vec3::new(0.0, 0.0, 1.0));
978
979 let mut cusps: Vec<crate::types::Halfedge> = Vec::new();
982 for edge in &self.halfedge {
983 let paired_face = self.halfedge[edge.paired_halfedge as usize].paired_halfedge as usize / 3;
984 let this_face = edge.paired_halfedge as usize / 3;
985 if self.face_normal[paired_face].z >= 0.0 && self.face_normal[this_face].z < 0.0 {
986 cusps.push(*edge);
987 }
988 }
989
990 if cusps.is_empty() {
991 return vec![];
992 }
993
994 let loops = assemble_halfedges(&cusps, 0);
995 let polys_indexed = project_polygons(&loops, &cusps, &self.vert_pos, &projection);
996
997 let mut polys: Polygons = Vec::new();
999 for poly in &polys_indexed {
1000 let simple: SimplePolygon = poly.iter().map(|pv| pv.pos).collect();
1001 polys.push(simple);
1002 }
1003
1004 polys
1005 }
1006}