1use crate::MeshSimplifier;
8use nalgebra::{Matrix4, Vector4};
9use priority_queue::PriorityQueue;
10use std::cmp::Ordering;
11use std::collections::{HashMap, HashSet};
12use threecrate_core::{Error, Point3f, Result, TriangleMesh, Vector3f};
13
14pub(crate) const INVALID: usize = usize::MAX;
15
16#[derive(Debug, Clone)]
21pub(crate) struct HalfEdge {
22 pub(crate) target: usize,
23 pub(crate) twin: usize,
24 pub(crate) next: usize,
25 pub(crate) prev: usize,
26 pub(crate) face: usize,
27}
28
29pub(crate) struct HalfEdgeMesh {
31 pub(crate) half_edges: Vec<HalfEdge>,
32 pub(crate) vertex_edge: Vec<usize>,
34 pub(crate) face_edge: Vec<usize>,
36 pub(crate) active_face_count: usize,
37 pub(crate) positions: Vec<Point3f>,
38 pub(crate) normals: Option<Vec<Vector3f>>,
39 pub(crate) colors: Option<Vec<[u8; 3]>>,
40 pub(crate) quadrics: Vec<Matrix4<f64>>,
41 pub(crate) vertex_removed: Vec<bool>,
42}
43
44impl HalfEdgeMesh {
45 pub(crate) fn from_triangle_mesh(mesh: &TriangleMesh) -> Self {
46 let nv = mesh.vertices.len();
47 let nf = mesh.faces.len();
48
49 let mut half_edges = Vec::with_capacity(nf * 3);
50 let mut vertex_edge = vec![INVALID; nv];
51 let mut face_edge = Vec::with_capacity(nf);
52
53 for (fi, face) in mesh.faces.iter().enumerate() {
54 let base = fi * 3;
55 for j in 0..3usize {
56 half_edges.push(HalfEdge {
57 target: face[(j + 1) % 3],
58 twin: INVALID,
59 next: base + (j + 1) % 3,
60 prev: base + (j + 2) % 3,
61 face: fi,
62 });
63 if vertex_edge[face[j]] == INVALID {
64 vertex_edge[face[j]] = base + j;
65 }
66 }
67 face_edge.push(base);
68 }
69
70 let mut edge_map: HashMap<(usize, usize), usize> = HashMap::with_capacity(nf * 3);
72 for (he_idx, he) in half_edges.iter().enumerate() {
73 let src = half_edges[he.prev].target;
74 edge_map.insert((src, he.target), he_idx);
75 }
76 for he_idx in 0..half_edges.len() {
77 if half_edges[he_idx].twin != INVALID {
78 continue;
79 }
80 let src = half_edges[half_edges[he_idx].prev].target;
81 let tgt = half_edges[he_idx].target;
82 if let Some(&twin_idx) = edge_map.get(&(tgt, src)) {
83 half_edges[he_idx].twin = twin_idx;
84 half_edges[twin_idx].twin = he_idx;
85 }
86 }
87
88 let mut hem = HalfEdgeMesh {
89 half_edges,
90 vertex_edge,
91 face_edge,
92 active_face_count: nf,
93 positions: mesh.vertices.clone(),
94 normals: mesh.normals.clone(),
95 colors: mesh.colors.clone(),
96 quadrics: vec![Matrix4::zeros(); nv],
97 vertex_removed: vec![false; nv],
98 };
99 hem.initialize_quadrics();
100 hem
101 }
102
103 #[inline]
104 pub(crate) fn source(&self, he: usize) -> usize {
105 self.half_edges[self.half_edges[he].prev].target
106 }
107
108 pub(crate) fn compute_plane(v0: &Point3f, v1: &Point3f, v2: &Point3f) -> Vector4<f64> {
109 let e1 = v1 - v0;
110 let e2 = v2 - v0;
111 let n = e1.cross(&e2).normalize();
112 if !n.iter().all(|x| x.is_finite()) {
113 return Vector4::new(0.0, 0.0, 1.0, 0.0);
114 }
115 let d = -n.dot(&v0.coords);
116 Vector4::new(n.x as f64, n.y as f64, n.z as f64, d as f64)
117 }
118
119 pub(crate) fn plane_to_quadric(p: &Vector4<f64>) -> Matrix4<f64> {
120 let (a, b, c, d) = (p[0], p[1], p[2], p[3]);
121 Matrix4::new(
122 a * a,
123 a * b,
124 a * c,
125 a * d,
126 a * b,
127 b * b,
128 b * c,
129 b * d,
130 a * c,
131 b * c,
132 c * c,
133 c * d,
134 a * d,
135 b * d,
136 c * d,
137 d * d,
138 )
139 }
140
141 pub(crate) fn initialize_quadrics(&mut self) {
142 for fi in 0..self.face_edge.len() {
143 let he0 = self.face_edge[fi];
144 if he0 == INVALID {
145 continue;
146 }
147 let he1 = self.half_edges[he0].next;
148 let v0 = self.source(he0);
149 let v1 = self.half_edges[he0].target;
150 let v2 = self.half_edges[he1].target;
151 let plane = Self::compute_plane(
152 &self.positions[v0],
153 &self.positions[v1],
154 &self.positions[v2],
155 );
156 let q = Self::plane_to_quadric(&plane);
157 self.quadrics[v0] += q;
158 self.quadrics[v1] += q;
159 self.quadrics[v2] += q;
160 }
161 }
162
163 pub(crate) fn outgoing_half_edges(&self, v: usize) -> Vec<usize> {
165 let start = self.vertex_edge[v];
166 if start == INVALID {
167 return vec![];
168 }
169
170 let mut result = Vec::new();
171 let mut current = start;
172
173 loop {
175 result.push(current);
176 let prev = self.half_edges[current].prev;
177 let twin = self.half_edges[prev].twin;
178 if twin == INVALID {
179 break;
180 }
181 current = twin;
182 if current == start {
183 return result;
184 }
185 }
186
187 let twin_of_start = self.half_edges[start].twin;
189 if twin_of_start != INVALID {
190 let mut current = self.half_edges[twin_of_start].next;
191 loop {
192 if current == start {
193 break;
194 }
195 result.push(current);
196 let twin = self.half_edges[current].twin;
197 if twin == INVALID {
198 break;
199 }
200 current = self.half_edges[twin].next;
201 }
202 }
203
204 result
205 }
206
207 pub(crate) fn neighbors(&self, v: usize) -> HashSet<usize> {
208 self.outgoing_half_edges(v)
209 .iter()
210 .map(|&he| self.half_edges[he].target)
211 .collect()
212 }
213
214 pub(crate) fn is_boundary_vertex(&self, v: usize) -> bool {
215 for &he in &self.outgoing_half_edges(v) {
216 if self.half_edges[he].twin == INVALID {
217 return true;
218 }
219 }
220 false
221 }
222
223 pub(crate) fn check_link_condition(&self, v1: usize, v2: usize) -> bool {
226 let n1 = self.neighbors(v1);
227 let n2 = self.neighbors(v2);
228 let common_count = n1.intersection(&n2).count();
229
230 let h = match self.find_half_edge(v1, v2) {
231 Some(h) => h,
232 None => return false,
233 };
234 let is_boundary = self.half_edges[h].twin == INVALID;
235 let expected = if is_boundary { 1 } else { 2 };
236 common_count == expected
237 }
238
239 pub(crate) fn find_half_edge(&self, from: usize, to: usize) -> Option<usize> {
240 for &he in &self.outgoing_half_edges(from) {
241 if self.half_edges[he].target == to {
242 return Some(he);
243 }
244 }
245 None
246 }
247
248 pub(crate) fn compute_collapse_cost(&self, v1: usize, v2: usize) -> (Point3f, f64) {
249 let q = self.quadrics[v1] + self.quadrics[v2];
250 let q3 = q.fixed_view::<3, 3>(0, 0);
251 let q1 = q.fixed_view::<3, 1>(0, 3);
252
253 let optimal = if let Some(inv) = q3.try_inverse() {
254 let p = -inv * q1;
255 Point3f::new(p[0] as f32, p[1] as f32, p[2] as f32)
256 } else {
257 Point3f::from((self.positions[v1].coords + self.positions[v2].coords) * 0.5)
258 };
259
260 let vh = Vector4::new(optimal.x as f64, optimal.y as f64, optimal.z as f64, 1.0);
261 let cost = (vh.transpose() * q * vh)[0].max(0.0);
262 (optimal, cost)
263 }
264
265 pub(crate) fn find_valid_outgoing(&self, v: usize) -> usize {
267 for (i, he) in self.half_edges.iter().enumerate() {
268 if he.face != INVALID && self.source(i) == v {
269 return i;
270 }
271 }
272 INVALID
273 }
274
275 pub(crate) fn collapse_edge(&mut self, v1: usize, v2: usize, new_pos: Point3f) -> bool {
278 let h = match self.find_half_edge(v1, v2) {
279 Some(h) => h,
280 None => return false,
281 };
282
283 let h_twin = self.half_edges[h].twin;
284 let h_next = self.half_edges[h].next;
285 let h_prev = self.half_edges[h].prev;
286 let face_a = self.half_edges[h].face;
287 let h_next_twin = self.half_edges[h_next].twin;
288 let h_prev_twin = self.half_edges[h_prev].twin;
289 let c = self.half_edges[h_next].target;
290
291 let (face_b, ht_next, ht_prev, ht_next_twin, ht_prev_twin, d) = if h_twin != INVALID {
292 let hn = self.half_edges[h_twin].next;
293 let hp = self.half_edges[h_twin].prev;
294 (
295 self.half_edges[h_twin].face,
296 hn,
297 hp,
298 self.half_edges[hn].twin,
299 self.half_edges[hp].twin,
300 self.half_edges[hn].target,
301 )
302 } else {
303 (INVALID, INVALID, INVALID, INVALID, INVALID, INVALID)
304 };
305
306 let v2_outgoing = self.outgoing_half_edges(v2);
308
309 if h_next_twin != INVALID {
311 self.half_edges[h_next_twin].twin = h_prev_twin;
312 }
313 if h_prev_twin != INVALID {
314 self.half_edges[h_prev_twin].twin = h_next_twin;
315 }
316
317 self.half_edges[h].face = INVALID;
319 self.half_edges[h_next].face = INVALID;
320 self.half_edges[h_prev].face = INVALID;
321 self.face_edge[face_a] = INVALID;
322 self.active_face_count -= 1;
323
324 if face_b != INVALID {
326 if ht_next_twin != INVALID {
327 self.half_edges[ht_next_twin].twin = ht_prev_twin;
328 }
329 if ht_prev_twin != INVALID {
330 self.half_edges[ht_prev_twin].twin = ht_next_twin;
331 }
332 self.half_edges[h_twin].face = INVALID;
333 self.half_edges[ht_next].face = INVALID;
334 self.half_edges[ht_prev].face = INVALID;
335 self.face_edge[face_b] = INVALID;
336 self.active_face_count -= 1;
337 }
338
339 for &he in &v2_outgoing {
341 let prev = self.half_edges[he].prev;
342 self.half_edges[prev].target = v1;
343
344 let twin = self.half_edges[he].twin;
345 if twin != INVALID && self.half_edges[twin].face != INVALID {
346 self.half_edges[twin].target = v1;
347 }
348 }
349
350 if self.half_edges[self.vertex_edge[v1]].face == INVALID {
352 if h_prev_twin != INVALID && self.half_edges[h_prev_twin].face != INVALID {
353 self.vertex_edge[v1] = h_prev_twin;
354 } else {
355 self.vertex_edge[v1] = self.find_valid_outgoing(v1);
356 }
357 }
358
359 if c != INVALID
361 && self.vertex_edge[c] != INVALID
362 && self.half_edges[self.vertex_edge[c]].face == INVALID
363 {
364 if h_next_twin != INVALID && self.half_edges[h_next_twin].face != INVALID {
365 self.vertex_edge[c] = h_next_twin;
366 } else {
367 self.vertex_edge[c] = self.find_valid_outgoing(c);
368 }
369 }
370
371 if d != INVALID
373 && d != c
374 && self.vertex_edge[d] != INVALID
375 && self.half_edges[self.vertex_edge[d]].face == INVALID
376 {
377 if ht_next_twin != INVALID && self.half_edges[ht_next_twin].face != INVALID {
378 self.vertex_edge[d] = ht_next_twin;
379 } else {
380 self.vertex_edge[d] = self.find_valid_outgoing(d);
381 }
382 }
383
384 self.vertex_edge[v2] = INVALID;
386 self.vertex_removed[v2] = true;
387
388 let v2_quadric = self.quadrics[v2];
390 self.positions[v1] = new_pos;
391 self.quadrics[v1] += v2_quadric;
392
393 if let Some(ref mut normals) = self.normals {
395 let n1 = normals[v1];
396 let n2 = normals[v2];
397 let avg = (n1 + n2).normalize();
398 if avg.iter().all(|x| x.is_finite()) {
399 normals[v1] = avg;
400 }
401 }
402
403 if let Some(ref mut colors) = self.colors {
405 let c1 = colors[v1];
406 let c2 = colors[v2];
407 colors[v1] = [
408 ((c1[0] as u16 + c2[0] as u16) / 2) as u8,
409 ((c1[1] as u16 + c2[1] as u16) / 2) as u8,
410 ((c1[2] as u16 + c2[2] as u16) / 2) as u8,
411 ];
412 }
413
414 true
415 }
416
417 pub(crate) fn to_triangle_mesh(&self) -> TriangleMesh {
418 let mut old_to_new: HashMap<usize, usize> = HashMap::new();
419 let mut new_positions = Vec::new();
420 let mut new_normals = self.normals.as_ref().map(|_| Vec::new());
421 let mut new_colors = self.colors.as_ref().map(|_| Vec::new());
422
423 for (i, &removed) in self.vertex_removed.iter().enumerate() {
424 if !removed && self.vertex_edge[i] != INVALID {
425 old_to_new.insert(i, new_positions.len());
426 new_positions.push(self.positions[i]);
427 if let Some(ref normals) = self.normals {
428 new_normals.as_mut().unwrap().push(normals[i]);
429 }
430 if let Some(ref colors) = self.colors {
431 new_colors.as_mut().unwrap().push(colors[i]);
432 }
433 }
434 }
435
436 let mut new_faces = Vec::new();
437 for fi in 0..self.face_edge.len() {
438 let he0 = self.face_edge[fi];
439 if he0 == INVALID {
440 continue;
441 }
442 let he1 = self.half_edges[he0].next;
443 let v0 = self.source(he0);
444 let v1 = self.half_edges[he0].target;
445 let v2 = self.half_edges[he1].target;
446
447 if let (Some(&nv0), Some(&nv1), Some(&nv2)) = (
448 old_to_new.get(&v0),
449 old_to_new.get(&v1),
450 old_to_new.get(&v2),
451 ) {
452 if nv0 != nv1 && nv1 != nv2 && nv2 != nv0 {
453 new_faces.push([nv0, nv1, nv2]);
454 }
455 }
456 }
457
458 let mut mesh = TriangleMesh::from_vertices_and_faces(new_positions, new_faces);
459 if let Some(normals) = new_normals {
460 mesh.set_normals(normals);
461 }
462 if let Some(colors) = new_colors {
463 mesh.set_colors(colors);
464 }
465 mesh
466 }
467}
468
469#[derive(Debug, Clone)]
474pub(crate) struct EdgeCost {
475 pub(crate) v1: usize,
476 pub(crate) v2: usize,
477 #[allow(dead_code)]
478 pub(crate) position: Point3f,
479 pub(crate) cost: f64,
480}
481
482impl PartialEq for EdgeCost {
483 fn eq(&self, other: &Self) -> bool {
484 self.cost.total_cmp(&other.cost) == Ordering::Equal
485 }
486}
487impl Eq for EdgeCost {}
488
489impl PartialOrd for EdgeCost {
490 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
491 Some(self.cmp(other))
492 }
493}
494
495impl Ord for EdgeCost {
496 fn cmp(&self, other: &Self) -> Ordering {
497 other.cost.total_cmp(&self.cost)
499 }
500}
501
502pub struct EdgeCollapseSimplifier {
512 pub error_threshold: Option<f64>,
514 pub preserve_boundary: bool,
516 pub boundary_weight: f64,
518}
519
520impl Default for EdgeCollapseSimplifier {
521 fn default() -> Self {
522 Self {
523 error_threshold: None,
524 preserve_boundary: true,
525 boundary_weight: 100.0,
526 }
527 }
528}
529
530impl EdgeCollapseSimplifier {
531 pub fn new() -> Self {
532 Self::default()
533 }
534
535 pub fn with_params(
536 error_threshold: Option<f64>,
537 preserve_boundary: bool,
538 boundary_weight: f64,
539 ) -> Self {
540 Self {
541 error_threshold,
542 preserve_boundary,
543 boundary_weight,
544 }
545 }
546
547 fn build_queue(&self, hem: &HalfEdgeMesh) -> PriorityQueue<usize, EdgeCost> {
549 let mut queue = PriorityQueue::new();
550 let mut seen_edges: HashSet<(usize, usize)> = HashSet::new();
551 let mut edge_id = 0usize;
552
553 for vi in 0..hem.positions.len() {
554 if hem.vertex_removed[vi] {
555 continue;
556 }
557 for &he in &hem.outgoing_half_edges(vi) {
558 let target = hem.half_edges[he].target;
559 let key = (vi.min(target), vi.max(target));
560 if !seen_edges.insert(key) {
561 continue;
562 }
563
564 if self.preserve_boundary
566 && (hem.is_boundary_vertex(vi) || hem.is_boundary_vertex(target))
567 {
568 continue;
569 }
570
571 let (pos, mut cost) = hem.compute_collapse_cost(vi, target);
572
573 if !self.preserve_boundary
575 && (hem.is_boundary_vertex(vi) || hem.is_boundary_vertex(target))
576 {
577 cost += self.boundary_weight;
578 }
579
580 queue.push(
581 edge_id,
582 EdgeCost {
583 v1: vi,
584 v2: target,
585 position: pos,
586 cost,
587 },
588 );
589 edge_id += 1;
590 }
591 }
592
593 queue
594 }
595
596 fn rebuild_queue(
598 &self,
599 hem: &HalfEdgeMesh,
600 id_offset: usize,
601 ) -> PriorityQueue<usize, EdgeCost> {
602 let mut queue = PriorityQueue::new();
603 let mut seen_edges: HashSet<(usize, usize)> = HashSet::new();
604 let mut edge_id = id_offset;
605
606 for vi in 0..hem.positions.len() {
607 if hem.vertex_removed[vi] || hem.vertex_edge[vi] == INVALID {
608 continue;
609 }
610 for &he in &hem.outgoing_half_edges(vi) {
611 if hem.half_edges[he].face == INVALID {
612 continue;
613 }
614 let target = hem.half_edges[he].target;
615 let key = (vi.min(target), vi.max(target));
616 if !seen_edges.insert(key) {
617 continue;
618 }
619
620 if self.preserve_boundary
621 && (hem.is_boundary_vertex(vi) || hem.is_boundary_vertex(target))
622 {
623 continue;
624 }
625
626 let (pos, mut cost) = hem.compute_collapse_cost(vi, target);
627 if !self.preserve_boundary
628 && (hem.is_boundary_vertex(vi) || hem.is_boundary_vertex(target))
629 {
630 cost += self.boundary_weight;
631 }
632
633 queue.push(
634 edge_id,
635 EdgeCost {
636 v1: vi,
637 v2: target,
638 position: pos,
639 cost,
640 },
641 );
642 edge_id += 1;
643 }
644 }
645
646 queue
647 }
648}
649
650impl MeshSimplifier for EdgeCollapseSimplifier {
651 fn simplify(&self, mesh: &TriangleMesh, reduction_ratio: f32) -> Result<TriangleMesh> {
652 if mesh.is_empty() {
653 return Err(Error::InvalidData("Mesh is empty".to_string()));
654 }
655 if !(0.0..=1.0).contains(&reduction_ratio) {
656 return Err(Error::InvalidData(
657 "Reduction ratio must be between 0.0 and 1.0".to_string(),
658 ));
659 }
660 if reduction_ratio == 0.0 {
661 return Ok(mesh.clone());
662 }
663
664 let target_faces = ((1.0 - reduction_ratio) * mesh.faces.len() as f32) as usize;
665 let mut hem = HalfEdgeMesh::from_triangle_mesh(mesh);
666 let mut queue = self.build_queue(&hem);
667 let mut collapse_count = 0usize;
668
669 while hem.active_face_count > target_faces && !queue.is_empty() {
670 let (_, edge_cost) = match queue.pop() {
671 Some(item) => item,
672 None => break,
673 };
674
675 if let Some(threshold) = self.error_threshold {
677 if edge_cost.cost > threshold {
678 break;
679 }
680 }
681
682 let v1 = edge_cost.v1;
683 let v2 = edge_cost.v2;
684
685 if hem.vertex_removed[v1]
687 || hem.vertex_removed[v2]
688 || hem.vertex_edge[v1] == INVALID
689 || hem.vertex_edge[v2] == INVALID
690 {
691 continue;
692 }
693
694 if hem.find_half_edge(v1, v2).is_none() {
695 continue;
696 }
697
698 if !hem.check_link_condition(v1, v2) {
700 continue;
701 }
702
703 let (pos, _cost) = hem.compute_collapse_cost(v1, v2);
705
706 if hem.collapse_edge(v1, v2, pos) {
707 collapse_count += 1;
708
709 if collapse_count % 100 == 0 {
711 queue = self.rebuild_queue(&hem, collapse_count * 1000);
712 }
713 }
714 }
715
716 Ok(hem.to_triangle_mesh())
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723 use nalgebra::Point3;
724
725 fn make_single_triangle() -> TriangleMesh {
726 TriangleMesh::from_vertices_and_faces(
727 vec![
728 Point3::new(0.0, 0.0, 0.0),
729 Point3::new(1.0, 0.0, 0.0),
730 Point3::new(0.5, 1.0, 0.0),
731 ],
732 vec![[0, 1, 2]],
733 )
734 }
735
736 fn make_tetrahedron() -> TriangleMesh {
737 TriangleMesh::from_vertices_and_faces(
739 vec![
740 Point3::new(0.0, 0.0, 0.0),
741 Point3::new(1.0, 0.0, 0.0),
742 Point3::new(0.5, 1.0, 0.0),
743 Point3::new(0.5, 0.5, 1.0),
744 ],
745 vec![[0, 2, 1], [0, 1, 3], [0, 3, 2], [1, 2, 3]],
746 )
747 }
748
749 fn make_plane_grid(size: usize) -> TriangleMesh {
750 let mut vertices = Vec::new();
751 for y in 0..size {
752 for x in 0..size {
753 vertices.push(Point3::new(x as f32, y as f32, 0.0));
754 }
755 }
756 let mut faces = Vec::new();
757 for y in 0..(size - 1) {
758 for x in 0..(size - 1) {
759 let tl = y * size + x;
760 let tr = tl + 1;
761 let bl = (y + 1) * size + x;
762 let br = bl + 1;
763 faces.push([tl, bl, tr]);
764 faces.push([tr, bl, br]);
765 }
766 }
767 TriangleMesh::from_vertices_and_faces(vertices, faces)
768 }
769
770 fn make_curved_surface(size: usize) -> TriangleMesh {
771 let mut vertices = Vec::new();
772 for y in 0..size {
773 for x in 0..size {
774 let fx = x as f32 / (size - 1) as f32 * std::f32::consts::PI;
775 let fy = y as f32 / (size - 1) as f32 * std::f32::consts::PI;
776 vertices.push(Point3::new(x as f32, y as f32, (fx.sin() * fy.sin()) * 2.0));
777 }
778 }
779 let mut faces = Vec::new();
780 for y in 0..(size - 1) {
781 for x in 0..(size - 1) {
782 let tl = y * size + x;
783 let tr = tl + 1;
784 let bl = (y + 1) * size + x;
785 let br = bl + 1;
786 faces.push([tl, bl, tr]);
787 faces.push([tr, bl, br]);
788 }
789 }
790 TriangleMesh::from_vertices_and_faces(vertices, faces)
791 }
792
793 fn make_diamond() -> TriangleMesh {
794 TriangleMesh::from_vertices_and_faces(
796 vec![
797 Point3::new(0.0, 0.0, 0.0),
798 Point3::new(1.0, 0.0, 0.0),
799 Point3::new(0.5, 1.0, 0.0),
800 Point3::new(0.5, 0.5, 1.0),
801 Point3::new(0.5, 0.5, -1.0),
802 ],
803 vec![
804 [0, 1, 3],
805 [1, 2, 3],
806 [0, 3, 2],
807 [0, 4, 1],
808 [1, 4, 2],
809 [0, 2, 4],
810 ],
811 )
812 }
813
814 #[test]
817 fn test_creation() {
818 let s = EdgeCollapseSimplifier::new();
819 assert!(s.preserve_boundary);
820 assert!(s.error_threshold.is_none());
821 }
822
823 #[test]
824 fn test_with_params() {
825 let s = EdgeCollapseSimplifier::with_params(Some(0.01), false, 50.0);
826 assert_eq!(s.error_threshold, Some(0.01));
827 assert!(!s.preserve_boundary);
828 assert_eq!(s.boundary_weight, 50.0);
829 }
830
831 #[test]
834 fn test_halfedge_construction() {
835 let mesh = make_tetrahedron();
836 let hem = HalfEdgeMesh::from_triangle_mesh(&mesh);
837 assert_eq!(hem.half_edges.len(), 12); assert_eq!(hem.active_face_count, 4);
839 assert_eq!(hem.positions.len(), 4);
840
841 for he in &hem.half_edges {
843 assert_ne!(he.twin, INVALID, "interior half-edge should have twin");
844 }
845 }
846
847 #[test]
848 fn test_halfedge_boundary() {
849 let mesh = make_single_triangle();
850 let hem = HalfEdgeMesh::from_triangle_mesh(&mesh);
851 for he in &hem.half_edges {
853 assert_eq!(he.twin, INVALID);
854 }
855 assert!(hem.is_boundary_vertex(0));
856 assert!(hem.is_boundary_vertex(1));
857 assert!(hem.is_boundary_vertex(2));
858 }
859
860 #[test]
861 fn test_halfedge_neighbors() {
862 let mesh = make_tetrahedron();
863 let hem = HalfEdgeMesh::from_triangle_mesh(&mesh);
864 for v in 0..4 {
866 let nbrs = hem.neighbors(v);
867 assert_eq!(nbrs.len(), 3, "tetrahedron vertex should have 3 neighbors");
868 }
869 }
870
871 #[test]
872 fn test_link_condition_tetrahedron() {
873 let mesh = make_tetrahedron();
874 let hem = HalfEdgeMesh::from_triangle_mesh(&mesh);
875 assert!(hem.check_link_condition(0, 1));
878 assert!(hem.check_link_condition(1, 2));
879 }
880
881 #[test]
884 fn test_empty_mesh() {
885 let s = EdgeCollapseSimplifier::new();
886 let mesh = TriangleMesh::new();
887 assert!(s.simplify(&mesh, 0.5).is_err());
888 }
889
890 #[test]
891 fn test_invalid_reduction_ratio() {
892 let s = EdgeCollapseSimplifier::new();
893 let mesh = make_single_triangle();
894 assert!(s.simplify(&mesh, -0.1).is_err());
895 assert!(s.simplify(&mesh, 1.1).is_err());
896 }
897
898 #[test]
899 fn test_zero_reduction() {
900 let s = EdgeCollapseSimplifier::new();
901 let mesh = make_single_triangle();
902 let result = s.simplify(&mesh, 0.0).unwrap();
903 assert_eq!(result.vertex_count(), 3);
904 assert_eq!(result.face_count(), 1);
905 }
906
907 #[test]
908 fn test_tetrahedron_simplification() {
909 let s = EdgeCollapseSimplifier::with_params(None, false, 0.0);
910 let mesh = make_tetrahedron();
911 let result = s.simplify(&mesh, 0.5).unwrap();
912 assert!(result.face_count() <= mesh.face_count());
913 assert!(result.vertex_count() <= mesh.vertex_count());
914 }
915
916 #[test]
917 fn test_planar_grid_simplification() {
918 let s = EdgeCollapseSimplifier::new();
919 let mesh = make_plane_grid(6);
920 let original_faces = mesh.face_count();
921 assert_eq!(original_faces, 50); let result = s.simplify(&mesh, 0.5).unwrap();
924 assert!(result.face_count() < original_faces);
925 assert!(result.face_count() > 0);
926 }
927
928 #[test]
929 fn test_curved_surface_simplification() {
930 let s = EdgeCollapseSimplifier::new();
931 let mesh = make_curved_surface(8);
932 let original_faces = mesh.face_count();
933
934 let result = s.simplify(&mesh, 0.5).unwrap();
935 assert!(result.face_count() < original_faces);
936 assert!(result.face_count() > 0);
937 }
938
939 #[test]
940 fn test_complex_mesh_simplification() {
941 let s = EdgeCollapseSimplifier::with_params(None, false, 0.0);
942 let mesh = make_diamond();
943 let original_faces = mesh.face_count();
944
945 let result = s.simplify(&mesh, 0.3).unwrap();
946 assert!(result.face_count() <= original_faces);
947 assert!(result.face_count() > 0);
948 }
949
950 #[test]
951 fn test_boundary_preservation() {
952 let s = EdgeCollapseSimplifier::new(); let mesh = make_plane_grid(6);
954
955 let original_boundary: HashSet<(i32, i32, i32)> = {
957 let size = 6;
958 let mut set = HashSet::new();
959 for i in 0..size {
960 for j in 0..size {
961 if i == 0 || i == size - 1 || j == 0 || j == size - 1 {
962 let idx = i * size + j;
963 let p = mesh.vertices[idx];
964 set.insert((
965 (p.x * 100.0) as i32,
966 (p.y * 100.0) as i32,
967 (p.z * 100.0) as i32,
968 ));
969 }
970 }
971 }
972 set
973 };
974
975 let result = s.simplify(&mesh, 0.5).unwrap();
976 let result_positions: HashSet<(i32, i32, i32)> = result
977 .vertices
978 .iter()
979 .map(|p| {
980 (
981 (p.x * 100.0) as i32,
982 (p.y * 100.0) as i32,
983 (p.z * 100.0) as i32,
984 )
985 })
986 .collect();
987
988 let preserved = original_boundary.intersection(&result_positions).count();
989 let ratio = preserved as f32 / original_boundary.len() as f32;
990 assert!(
991 ratio > 0.9,
992 "Expected >90% boundary preservation, got {:.1}%",
993 ratio * 100.0
994 );
995 }
996
997 #[test]
998 fn test_error_threshold() {
999 let s = EdgeCollapseSimplifier::with_params(Some(0.0001), false, 0.0);
1000 let mesh = make_plane_grid(6);
1001 let result = s.simplify(&mesh, 0.99).unwrap();
1002 assert!(result.face_count() > 0);
1005 }
1006
1007 #[test]
1008 fn test_attribute_preservation_normals() {
1009 let mut mesh = make_plane_grid(5);
1010 let normals: Vec<Vector3f> = (0..mesh.vertex_count())
1011 .map(|_| Vector3f::new(0.0, 0.0, 1.0))
1012 .collect();
1013 mesh.set_normals(normals);
1014
1015 let s = EdgeCollapseSimplifier::new();
1016 let result = s.simplify(&mesh, 0.3).unwrap();
1017 assert!(result.normals.is_some(), "normals should be preserved");
1018 let result_normals = result.normals.as_ref().unwrap();
1019 assert_eq!(result_normals.len(), result.vertex_count());
1020 for n in result_normals {
1021 assert!(n.z > 0.9, "normal z should be close to 1.0, got {}", n.z);
1023 }
1024 }
1025
1026 #[test]
1027 fn test_attribute_preservation_colors() {
1028 let mut mesh = make_plane_grid(5);
1029 let colors: Vec<[u8; 3]> = (0..mesh.vertex_count()).map(|_| [128, 64, 200]).collect();
1030 mesh.set_colors(colors);
1031
1032 let s = EdgeCollapseSimplifier::new();
1033 let result = s.simplify(&mesh, 0.3).unwrap();
1034 assert!(result.colors.is_some(), "colors should be preserved");
1035 assert_eq!(result.colors.as_ref().unwrap().len(), result.vertex_count());
1036 }
1037
1038 #[test]
1039 fn test_large_grid_simplification() {
1040 let s = EdgeCollapseSimplifier::new();
1041 let mesh = make_plane_grid(11);
1042 let original = mesh.face_count(); let result = s.simplify(&mesh, 0.5).unwrap();
1045 assert!(result.face_count() < original);
1046 assert!(result.face_count() > 0);
1047 assert!(result.vertex_count() > 0);
1048 }
1049}