1use super::{
4 Mesh, Settings,
5 builder::MeshBuilder,
6 cell::{Cell, CellIndex, CellVertex, Leaf},
7 codegen::CELL_TO_VERT_TO_EDGES,
8 frame::Frame,
9 qef::QuadraticErrorSolver,
10 types::{Axis, CellMask, Corner, Edge},
11};
12use fidget_core::{
13 eval::Function,
14 render::{RenderHandle, RenderHints, ThreadPool},
15 shape::{
16 BoundShape, Shape, ShapeBulkEval, ShapeBulkEvalError, ShapeTracingEval,
17 ShapeTracingEvalError, ShapeVars,
18 },
19 types::Grad,
20};
21use std::collections::VecDeque;
22
23#[derive(Debug)]
25pub struct Octree {
26 pub(crate) root: Cell<3>,
27 pub(crate) cells: Vec<[Cell<3>; 8]>,
28
29 pub(crate) verts: Vec<CellVertex<3>>,
34}
35
36impl Octree {
37 pub(crate) fn new() -> Self {
39 Octree {
40 root: Cell::Invalid,
41 cells: vec![],
42 verts: vec![],
43 }
44 }
45
46 pub fn build<F: Function + RenderHints + Clone>(
53 b: &BoundShape<F, f32>,
54 settings: &Settings,
55 ) -> Option<Self> {
56 let mut out = Self::build_inner(b, settings)?;
57
58 if settings.world_to_model != nalgebra::Matrix4::identity() {
60 for v in &mut out.verts {
61 let p: nalgebra::Point3<f32> = v.pos.into();
62 let q = settings.world_to_model.transform_point(&p);
63 v.pos = q.coords;
64 }
65 }
66 Some(out)
67 }
68
69 fn build_inner<F: Function + RenderHints + Clone>(
74 b: &BoundShape<F, f32>,
75 settings: &Settings,
76 ) -> Option<Self> {
77 let shape = b.shape();
78 let vars = b.vars();
79 if let Some(threads) = settings.threads {
80 Self::build_inner_mt(shape, settings, vars, threads)
81 } else {
82 let mut eval = RenderHandle::new(shape.clone());
83 let mut out = OctreeBuilder::new(settings, vars);
84 let mut hermite = LeafHermiteData::default();
85 if out.recurse(&mut eval, CellIndex::default(), &mut hermite) {
86 Some(out.octree)
87 } else {
88 None
89 }
90 }
91 }
92
93 fn build_inner_mt<F: Function + RenderHints + Clone>(
95 shape: &Shape<F>,
96 settings: &Settings,
97 vars: &ShapeVars<f32>,
98 threads: &ThreadPool,
99 ) -> Option<Self> {
100 let mut root = Octree::new();
101 let mut todo = VecDeque::new();
102 todo.push_back(CellIndex::<3>::default());
103 let mut fixup = vec![];
104 let mut hermites = vec![];
105
106 let target_count = (8usize.pow(u32::from(settings.depth)))
110 .min(threads.thread_count() * 10);
111 while todo.len() < target_count {
112 let next = todo.pop_front().unwrap();
113
114 let index = root.cells.len();
116 root.cells.push([Cell::Invalid; 8]);
117 hermites.push([LeafHermiteData::default(); 8]);
118 for i in Corner::<3>::iter() {
119 let cell = next.child(index, i);
120 todo.push_back(cell);
121 }
122 fixup.push((next, index));
123 }
124
125 use rayon::prelude::*;
126
127 struct Output {
128 cell: CellIndex<3>,
129 octree: Octree,
130 hermite: LeafHermiteData,
131 }
132 let mut rh = RenderHandle::new(shape.clone());
133 let _ = rh.i_tape(&mut vec![]); let out = threads.run(|| {
135 todo.par_iter()
136 .map_init(
137 || (OctreeBuilder::new(settings, vars), rh.clone()),
138 |(builder, eval), cell| {
139 let mut hermite = LeafHermiteData::default();
140 let local_cell = CellIndex {
142 index: None,
143 ..*cell
144 };
145 if !builder.recurse(eval, local_cell, &mut hermite) {
146 return None;
147 }
148 let octree = std::mem::replace(
149 &mut builder.octree,
150 Octree::new(),
151 );
152 Some(Output {
153 octree,
154 cell: *cell,
155 hermite,
156 })
157 },
158 )
159 .collect::<Option<Vec<_>>>()
160 })?;
161
162 let mut cell_offsets = vec![root.cells.len()];
164 let mut vert_offsets = vec![0];
165 for o in &out {
166 let (i, j) = o.cell.index.unwrap();
167 hermites[i][j as usize] = o.hermite;
168 let c = cell_offsets.last().unwrap() + o.octree.cells.len();
169 cell_offsets.push(c);
170 let v = vert_offsets.last().unwrap() + o.octree.verts.len();
171 vert_offsets.push(v);
172 }
173 root.cells.reserve(*cell_offsets.last().unwrap());
174 root.verts.reserve(*vert_offsets.last().unwrap());
175
176 for (i, o) in out.into_iter().enumerate() {
177 assert_eq!(cell_offsets[i], root.cells.len());
178 assert_eq!(vert_offsets[i], root.verts.len());
179 let remap_cell = |c| match c {
180 Cell::Leaf(Leaf { mask, index }) => Cell::Leaf(Leaf {
181 mask,
182 index: index + vert_offsets[i],
183 }),
184 Cell::Branch { index } => Cell::Branch {
185 index: index + cell_offsets[i],
186 },
187 Cell::Full | Cell::Empty => c,
188 Cell::Invalid => panic!(),
189 };
190 root.cells.extend(
191 o.octree.cells.into_iter().map(|cs| cs.map(remap_cell)),
192 );
193 root.verts.extend(o.octree.verts);
194 root[o.cell] = remap_cell(o.octree.root);
195 }
196
197 for (cell, index) in fixup.into_iter().rev() {
199 let h = hermites[index];
200 root[cell] = root.check_done(
201 cell,
202 index,
203 h,
204 cell.index
205 .map(|(i, j)| &mut hermites[i][j as usize])
206 .unwrap_or(&mut LeafHermiteData::default()),
207 );
208 }
209 Some(root)
210 }
211
212 pub fn walk_dual(&self) -> Mesh {
214 let mut mesh = MeshBuilder::default();
215
216 mesh.cell(self, CellIndex::default());
217 mesh.take()
218 }
219
220 pub(crate) fn is_leaf(&self, cell: CellIndex<3>) -> bool {
221 match self[cell] {
222 Cell::Leaf(..) | Cell::Full | Cell::Empty => true,
223 Cell::Branch { .. } => false,
224 Cell::Invalid => panic!(),
225 }
226 }
227
228 pub(crate) fn child<C: Into<Corner<3>>>(
235 &self,
236 cell: CellIndex<3>,
237 child: C,
238 ) -> CellIndex<3> {
239 let child = child.into();
240
241 match self[cell] {
242 Cell::Leaf { .. } | Cell::Full | Cell::Empty => cell,
243 Cell::Branch { index } => cell.child(index, child),
244 Cell::Invalid => panic!(),
245 }
246 }
247
248 fn check_done(
253 &mut self,
254 cell: CellIndex<3>,
255 index: usize,
256 hermite_data: [LeafHermiteData; 8],
257 hermite: &mut LeafHermiteData, ) -> Cell<3> {
259 let mut full_count = 0;
261 let mut empty_count = 0;
262 for i in 0..8 {
263 match self.cells[index][i] {
264 Cell::Invalid => {
265 panic!("found invalid cell during meshing")
266 }
267 Cell::Full => {
268 full_count += 1;
269 }
270 Cell::Empty => {
271 empty_count += 1;
272 }
273 Cell::Branch { .. } => return Cell::Branch { index },
274 Cell::Leaf(Leaf { .. }) => (),
275 }
276 }
277
278 let out = if full_count == 8 {
282 Cell::Full
283 } else if empty_count == 8 {
284 Cell::Empty
285 } else if let Some(leaf) =
286 self.try_collapse(cell, index, hermite_data, hermite)
287 {
288 Cell::Leaf(leaf)
289 } else {
290 Cell::Branch { index }
291 };
292
293 if !matches!(out, Cell::Branch { .. }) {
302 if index == self.cells.len() - 1 {
303 self.cells.resize(index, [Cell::Invalid; 8]);
304 } else {
305 self.cells[index] = [Cell::Invalid; 8];
306 }
307 }
308 out
309 }
310
311 fn try_collapse(
316 &mut self,
317 cell: CellIndex<3>,
318 index: usize,
319 hermite_data: [LeafHermiteData; 8],
320 hermite: &mut LeafHermiteData, ) -> Option<Leaf<3>> {
322 let mask = self.collapsible(index)?;
323 *hermite = LeafHermiteData::merge(hermite_data)?;
324
325 let (pos, new_err) = hermite.solve();
335 if new_err >= hermite.qef_err * 2.0 || !cell.bounds.contains(pos) {
336 return None;
337 }
338 hermite.qef_err = new_err;
340
341 let index = self.verts.len();
342 self.verts.push(pos);
343
344 let edges = &CELL_TO_VERT_TO_EDGES[mask.index()];
347 debug_assert_eq!(edges.len(), 1);
348 for e in edges[0] {
349 let i = hermite.intersections[e.to_undirected().index()];
350 self.verts.push(CellVertex { pos: i.pos.xyz() });
351 }
352
353 Some(Leaf { mask, index })
354 }
355
356 pub(crate) fn collapsible(&self, root: usize) -> Option<CellMask<3>> {
361 let cells = self.cells[root];
363
364 let mut mask = 0;
365 for (i, &c) in cells.iter().enumerate() {
366 let b = match c {
367 Cell::Leaf(Leaf { mask, .. }) => {
368 if CELL_TO_VERT_TO_EDGES[mask.index()].len() > 1 {
369 return None;
370 }
371 (mask.index() & (1 << i) != 0) as u8
372 }
373 Cell::Empty => 0,
374 Cell::Full => 1,
375 Cell::Branch { .. } => return None,
376 Cell::Invalid => panic!(),
377 };
378 mask |= b << i;
379 }
380
381 use super::frame::{XYZ, YZX, ZXY};
382 for (t, u, v) in [XYZ::frame(), YZX::frame(), ZXY::frame()] {
383 for i in 0..4 {
386 let a = (u * ((i & 1) != 0)) | (v * ((i & 2) != 0));
387 let b = a | t;
388 let center = cells[a.index()].corner(b);
389
390 if [a, b]
391 .iter()
392 .all(|v| ((mask & (1 << v.index())) != 0) != center)
393 {
394 return None;
395 }
396 }
397
398 for i in 0..2 {
401 let a: Corner<3> = (t * (i & 1 == 0)).into();
402 let b = a | u;
403 let c = a | v;
404 let d = a | u | v;
405
406 let center = cells[a.index()].corner(d);
407
408 if [a, b, c, d]
409 .iter()
410 .all(|v| ((mask & (1 << v.index())) != 0) != center)
411 {
412 return None;
413 }
414 }
415 for _i in 0..1 {
418 let center = cells[0].corner(t | u | v);
421 if (0..8).all(|v| ((mask & (1 << v)) != 0) != center) {
422 return None;
423 }
424 }
425 }
426
427 debug_assert_ne!(mask, 255);
431 debug_assert_ne!(mask, 0);
432
433 if CELL_TO_VERT_TO_EDGES[mask as usize].len() == 1 {
436 Some(CellMask::new(mask))
437 } else {
438 None
439 }
440 }
441}
442
443impl std::ops::Index<CellIndex<3>> for Octree {
444 type Output = Cell<3>;
445
446 fn index(&self, i: CellIndex<3>) -> &Self::Output {
447 match i.index {
448 None => &self.root,
449 Some((i, j)) => &self.cells[i][j as usize],
450 }
451 }
452}
453
454impl std::ops::IndexMut<CellIndex<3>> for Octree {
455 fn index_mut(&mut self, i: CellIndex<3>) -> &mut Self::Output {
456 match i.index {
457 None => &mut self.root,
458 Some((i, j)) => &mut self.cells[i][j as usize],
459 }
460 }
461}
462
463pub(crate) struct OctreeBuilder<'a, F: Function + RenderHints> {
465 pub(crate) octree: Octree,
467
468 cancel: fidget_core::render::CancelToken,
470 world_to_model: Option<&'a nalgebra::Matrix4<f32>>,
471 max_depth: u8,
472 vars: &'a ShapeVars<f32>,
473
474 eval_float_slice: ShapeBulkEval<F::FloatSliceEval>,
475 eval_interval: ShapeTracingEval<F::IntervalEval>,
476 eval_grad_slice: ShapeBulkEval<F::GradSliceEval>,
477
478 tape_storage: Vec<F::TapeStorage>,
479 shape_storage: Vec<F::Storage>,
480 workspace: F::Workspace,
481}
482
483impl<'a, F: Function + RenderHints> OctreeBuilder<'a, F> {
484 pub(crate) fn new(
486 settings: &'a Settings,
487 vars: &'a ShapeVars<f32>,
488 ) -> Self {
489 let world_to_model =
490 if settings.world_to_model == nalgebra::Matrix4::identity() {
491 None
492 } else {
493 Some(&settings.world_to_model)
494 };
495 Self {
496 octree: Octree::new(),
497
498 cancel: settings.cancel.clone(),
499 max_depth: settings.depth,
500 world_to_model,
501 vars,
502
503 eval_float_slice: Shape::<F>::new_float_slice_eval(),
504 eval_grad_slice: Shape::<F>::new_grad_slice_eval(),
505 eval_interval: Shape::<F>::new_interval_eval(),
506 tape_storage: vec![],
507 shape_storage: vec![],
508 workspace: Default::default(),
509 }
510 }
511
512 #[must_use]
521 fn recurse(
522 &mut self,
523 eval: &mut RenderHandle<F>,
524 cell: CellIndex<3>,
525 hermite: &mut LeafHermiteData,
526 ) -> bool {
527 if self.cancel.is_cancelled() {
528 return false;
529 }
530 let (i, r) = match self.eval_interval.eval_raw(
531 eval.i_tape(&mut self.tape_storage),
532 cell.bounds[crate::types::X],
533 cell.bounds[crate::types::Y],
534 cell.bounds[crate::types::Z],
535 self.world_to_model,
536 self.vars,
537 ) {
538 Ok(v) => v,
539 Err(ShapeTracingEvalError::MissingVar(..)) => unreachable!(),
540 };
541 self.octree[cell] = if i.upper() < 0.0 {
542 Cell::Full
543 } else if i.lower() > 0.0 {
544 Cell::Empty
545 } else {
546 let sub_tape = if F::simplify_tree_during_meshing(cell.depth) {
547 if let Some(trace) = r.as_ref() {
548 eval.simplify(
549 trace,
550 &mut self.workspace,
551 &mut self.shape_storage,
552 &mut self.tape_storage,
553 )
554 } else {
555 eval
556 }
557 } else {
558 eval
559 };
560 if cell.depth == self.max_depth as usize {
561 self.leaf(sub_tape, cell, hermite)
562 } else {
563 let index = self.octree.cells.len();
565 self.octree.cells.push([Cell::Invalid; 8]);
566 let mut hermite_child = [LeafHermiteData::default(); 8];
567 for i in Corner::<3>::iter() {
568 let cell = cell.child(index, i);
569 if !self.recurse(
570 sub_tape,
571 cell,
572 &mut hermite_child[i.index()],
573 ) {
574 return false;
575 }
576 }
577
578 self.octree.check_done(cell, index, hermite_child, hermite)
580 }
581 };
582 true
583 }
584
585 fn leaf(
591 &mut self,
592 eval: &mut RenderHandle<F>,
593 cell: CellIndex<3>,
594 hermite_cell: &mut LeafHermiteData,
595 ) -> Cell<3> {
596 let mut xs = [0.0; 8];
597 let mut ys = [0.0; 8];
598 let mut zs = [0.0; 8];
599 for i in Corner::<3>::iter() {
600 let [x, y, z] = cell.corner(i);
601 xs[i.index()] = x;
602 ys[i.index()] = y;
603 zs[i.index()] = z;
604 }
605
606 let out = match self.eval_float_slice.eval_raw(
607 eval.f_tape(&mut self.tape_storage),
608 &xs,
609 &ys,
610 &zs,
611 self.world_to_model,
612 ShapeBulkEval::<F::FloatSliceEval>::var_value(self.vars),
613 ) {
614 Ok(v) => v,
615 Err(
616 ShapeBulkEvalError::MissingVar(..)
617 | ShapeBulkEvalError::MismatchedVarSlices { .. },
618 ) => unreachable!(),
619 };
620 debug_assert_eq!(out.len(), 8);
621
622 let mask = out
625 .iter()
626 .enumerate()
627 .filter(|(_i, v)| **v < 0.0)
628 .fold(0, |acc, (i, _v)| acc | (1 << i));
629
630 if mask == 0 {
632 return Cell::Empty;
633 } else if mask == 255 {
634 return Cell::Full;
635 }
636
637 let mask = CellMask::new(mask);
638
639 let mut start = [nalgebra::Vector3::zeros(); 12];
641 let mut end = [nalgebra::Vector3::zeros(); 12];
642 let mut edge_count = 0;
643
644 for vs in CELL_TO_VERT_TO_EDGES[mask.index()].iter() {
647 for e in *vs {
648 let axis = e.start().index() ^ e.end().index();
650 debug_assert_eq!(axis.count_ones(), 1);
651 debug_assert!(axis < 8);
652
653 let (a, b) = if e.end().index() & axis != 0 {
655 (0, u16::MAX)
656 } else {
657 (u16::MAX, 0)
658 };
659
660 let mut v = nalgebra::Vector3::zeros();
662 let i = (axis.trailing_zeros() + 1) % 3;
663 let j = (axis.trailing_zeros() + 2) % 3;
664 v[i as usize] = if e.start() & Axis::new(1 << i) {
665 u16::MAX
666 } else {
667 0
668 };
669 v[j as usize] = if e.start() & Axis::new(1 << j) {
670 u16::MAX
671 } else {
672 0
673 };
674
675 v[axis.trailing_zeros() as usize] = a;
676 start[edge_count] = v;
677 v[axis.trailing_zeros() as usize] = b;
678 end[edge_count] = v;
679 edge_count += 1;
680 }
681 }
682 let start = &mut start[..edge_count]; let end = &mut end[..edge_count]; const EDGE_SEARCH_SIZE: usize = 16;
688 const EDGE_SEARCH_DEPTH: usize = 4;
689 let xs =
690 &mut [0.0; 12 * EDGE_SEARCH_SIZE][..edge_count * EDGE_SEARCH_SIZE];
691 let ys =
692 &mut [0.0; 12 * EDGE_SEARCH_SIZE][..edge_count * EDGE_SEARCH_SIZE];
693 let zs =
694 &mut [0.0; 12 * EDGE_SEARCH_SIZE][..edge_count * EDGE_SEARCH_SIZE];
695
696 for _ in 0..EDGE_SEARCH_DEPTH {
699 let mut i = 0;
701 for (start, end) in start.iter().zip(end.iter()) {
702 for j in 0..EDGE_SEARCH_SIZE {
703 let pos = ((start.map(|i| i as u32)
704 * (EDGE_SEARCH_SIZE - j - 1) as u32)
705 + (end.map(|i| i as u32) * j as u32))
706 / ((EDGE_SEARCH_SIZE - 1) as u32);
707 debug_assert!(pos.max() <= u16::MAX.into());
708
709 let pos = cell.pos(pos.map(|p| p as u16));
710 xs[i] = pos.x;
711 ys[i] = pos.y;
712 zs[i] = pos.z;
713 i += 1;
714 }
715 }
716 debug_assert_eq!(i, EDGE_SEARCH_SIZE * edge_count);
717
718 let out = match self.eval_float_slice.eval_raw(
720 eval.f_tape(&mut self.tape_storage),
721 xs,
722 ys,
723 zs,
724 self.world_to_model,
725 ShapeBulkEval::<F::FloatSliceEval>::var_value(self.vars),
726 ) {
727 Ok(v) => v,
728 Err(
729 ShapeBulkEvalError::MissingVar(..)
730 | ShapeBulkEvalError::MismatchedVarSlices { .. },
731 ) => unreachable!(),
732 };
733
734 for ((start, end), search) in start
736 .iter_mut()
737 .zip(end.iter_mut())
738 .zip(out.chunks(EDGE_SEARCH_SIZE))
739 {
740 debug_assert!(search[0] < 0.0);
742 debug_assert!(search[EDGE_SEARCH_SIZE - 1] >= 0.0);
743 let frac = search
744 .iter()
745 .enumerate()
746 .find(|(_i, v)| **v >= 0.0)
747 .unwrap()
748 .0;
749 debug_assert!(frac > 0);
750 debug_assert!(frac < EDGE_SEARCH_SIZE);
751
752 let f = |frac| {
753 ((start.map(|i| i as u32)
754 * (EDGE_SEARCH_SIZE - frac - 1) as u32)
755 + (end.map(|i| i as u32) * frac as u32))
756 / ((EDGE_SEARCH_SIZE - 1) as u32)
757 };
758
759 let a = f(frac - 1);
760 let b = f(frac);
761
762 debug_assert!(a.max() <= u16::MAX.into());
763 debug_assert!(b.max() <= u16::MAX.into());
764 *start = a.map(|v| v as u16);
765 *end = b.map(|v| v as u16);
766 }
767 }
768
769 let intersections: arrayvec::ArrayVec<nalgebra::Vector3<u16>, 12> =
771 start
772 .iter()
773 .zip(end.iter())
774 .map(|(a, b)| {
775 ((a.map(|v| v as u32) + b.map(|v| v as u32)) / 2)
776 .map(|v| v as u16)
777 })
778 .collect();
779
780 let xs = &mut [Grad::from(0.0); 12 * EDGE_SEARCH_SIZE]
781 [..intersections.len()];
782 let ys = &mut [Grad::from(0.0); 12 * EDGE_SEARCH_SIZE]
783 [..intersections.len()];
784 let zs = &mut [Grad::from(0.0); 12 * EDGE_SEARCH_SIZE]
785 [..intersections.len()];
786
787 for (i, xyz) in intersections.iter().enumerate() {
788 let pos = cell.pos(*xyz);
789 xs[i] = Grad::new(pos.x, 1.0, 0.0, 0.0);
790 ys[i] = Grad::new(pos.y, 0.0, 1.0, 0.0);
791 zs[i] = Grad::new(pos.z, 0.0, 0.0, 1.0);
792 }
793
794 let grads = match self.eval_grad_slice.eval_raw(
796 eval.g_tape(&mut self.tape_storage),
797 xs,
798 ys,
799 zs,
800 self.world_to_model,
801 ShapeBulkEval::<F::GradSliceEval>::var_value(self.vars),
802 ) {
803 Ok(v) => v,
804 Err(
805 ShapeBulkEvalError::MissingVar(..)
806 | ShapeBulkEvalError::MismatchedVarSlices { .. },
807 ) => unreachable!(),
808 };
809
810 let mut verts: arrayvec::ArrayVec<_, 4> = arrayvec::ArrayVec::new();
811 let mut i = 0;
812 for vs in CELL_TO_VERT_TO_EDGES[mask.index()].iter() {
813 let mut force_point = None;
814 let mut qef = QuadraticErrorSolver::new();
815 for e in vs.iter() {
816 let pos = nalgebra::Vector3::new(xs[i].v, ys[i].v, zs[i].v);
817 let grad: nalgebra::Vector4<f32> = grads[i].into();
818
819 if grad.iter().any(|f| f.is_nan()) {
824 force_point = Some(pos);
825 hermite_cell.qef_err = QEF_ERR_INVALID;
826 break;
827 }
828 qef.add_intersection(pos, grad);
829
830 let edge_index = e.to_undirected().index();
832 hermite_cell.intersections[edge_index] = LeafIntersection {
833 pos: nalgebra::Vector4::new(pos.x, pos.y, pos.z, 1.0),
834 grad,
835 };
836
837 i += 1;
838 }
839
840 if let Some(pos) = force_point {
841 verts.push(CellVertex { pos });
842 } else {
843 let (pos, err) = qef.solve();
844 verts.push(pos);
845
846 hermite_cell.qef_err = err;
850 }
851 }
852
853 let index = self.octree.verts.len();
854 self.octree.verts.extend(verts);
855 self.octree.verts.extend(
856 intersections
857 .into_iter()
858 .map(|pos| CellVertex { pos: cell.pos(pos) }),
859 );
860
861 Cell::Leaf(Leaf { mask, index })
862 }
863}
864
865#[derive(Copy, Clone, Default, Debug)]
868struct LeafIntersection {
869 pos: nalgebra::Vector4<f32>,
871 grad: nalgebra::Vector4<f32>,
873}
874
875impl From<LeafIntersection> for QuadraticErrorSolver {
876 fn from(i: LeafIntersection) -> Self {
877 let mut qef = QuadraticErrorSolver::default();
878 if i.pos.w != 0.0 {
879 qef.add_intersection(i.pos.xyz(), i.grad);
880 }
881 qef
882 }
883}
884
885#[derive(Copy, Clone, Debug)]
886pub(crate) struct LeafHermiteData {
887 intersections: [LeafIntersection; 12],
888 face_qefs: [QuadraticErrorSolver; 6],
889 center_qef: QuadraticErrorSolver,
890
891 qef_err: f32,
893}
894
895const QEF_ERR_EMPTY: f32 = -1.0;
897
898const QEF_ERR_INVALID: f32 = -2.0;
900
901impl Default for LeafHermiteData {
902 fn default() -> Self {
903 Self {
904 intersections: Default::default(),
905 face_qefs: Default::default(),
906 center_qef: Default::default(),
907 qef_err: QEF_ERR_EMPTY,
908 }
909 }
910}
911
912impl LeafHermiteData {
913 fn merge(leafs: [LeafHermiteData; 8]) -> Option<Self> {
918 let mut out = Self::default();
919 use super::types::{X, Y, Z};
920
921 if leafs.iter().any(|v| v.qef_err == QEF_ERR_INVALID) {
922 return None;
923 }
924
925 for t in [X, Y, Z] {
927 let u = t.next();
928 let v = u.next();
929 for edge in 0..4 {
930 let mut start = Corner::<3>::new(0);
931 if edge & 1 != 0 {
932 start = start | u;
933 }
934 if edge & 2 != 0 {
935 start = start | v;
936 }
937 let end = start | t;
938
939 let edge = Edge::new((t.index() * 4 + edge) as u8);
941
942 let a = leafs[start.index()].intersections[edge.index()];
944 let b = leafs[end.index()].intersections[edge.index()];
945 match (a.pos.w > 0.0, b.pos.w > 0.0) {
946 (true, false) => out.intersections[edge.index()] = a,
947 (false, true) => out.intersections[edge.index()] = b,
948 (false, false) => (),
949 (true, true) => panic!("duplicate intersection"),
950 }
951 }
952 }
953
954 for t in [X, Y, Z] {
956 let u = t.next();
957 let v = t.next();
958 for face in 0..2 {
959 let a = if face == 1 {
960 t.into()
961 } else {
962 Corner::<3>::new(0)
963 };
964 let b = a | u;
965 let c = a | v;
966 let d = a | u | v;
967 let f = t.index() * 2 + face;
968 for q in [a, b, c, d] {
969 out.face_qefs[f] += leafs[q.index()].face_qefs[f];
970 }
971 let edge_index_v = v.index() * 4 + face * 2 + 1;
973 out.face_qefs[f] +=
974 leafs[a.index()].intersections[edge_index_v].into();
975 out.face_qefs[f] +=
976 leafs[b.index()].intersections[edge_index_v].into();
977
978 let edge_index_u = v.index() * 4 + face * 2 + 1;
980 out.face_qefs[f] +=
981 leafs[a.index()].intersections[edge_index_u].into();
982 out.face_qefs[f] +=
983 leafs[c.index()].intersections[edge_index_u].into();
984 }
985 }
986
987 for t in [X, Y, Z] {
989 let u = t.next();
990 let v = t.next();
991
992 let a = Corner::<3>::new(0);
994 let b = a | u;
995 let c = a | v;
996 let d = a | u | v;
997 for q in [a, b, c, d] {
998 out.center_qef += leafs[q.index()].face_qefs[t.index() * 2 + 1];
999 }
1000
1001 out.center_qef +=
1003 leafs[a.index()].intersections[u.index() * 4 + 3].into();
1004 out.center_qef +=
1005 leafs[b.index()].intersections[u.index() * 4 + 3].into();
1006
1007 }
1010 for leaf in leafs {
1011 out.center_qef += leaf.center_qef;
1012 }
1013
1014 out.qef_err = f32::INFINITY;
1016 for e in leafs.iter().map(|q| q.qef_err).filter(|&e| e >= 0.0) {
1017 out.qef_err = out.qef_err.min(e);
1018 }
1019
1020 Some(out)
1021 }
1022
1023 pub fn solve(&self) -> (CellVertex<3>, f32) {
1025 let mut qef = self.center_qef;
1026 for &i in &self.intersections {
1027 qef += i.into();
1028 }
1029 for &f in &self.face_qefs {
1030 qef += f;
1031 }
1032 qef.solve()
1033 }
1034}
1035
1036#[cfg(test)]
1039mod test {
1040 use super::*;
1041 use crate::types::{Edge, X, Y, Z};
1042 use fidget_core::{
1043 context::{Context, Tree},
1044 render::ThreadPool,
1045 shape::EzShape,
1046 var::Var,
1047 vm::{VmFunction, VmShape},
1048 };
1049 use std::collections::BTreeMap;
1050
1051 fn depth0_single_thread() -> Settings<'static> {
1052 Settings {
1053 depth: 0,
1054 threads: None,
1055 ..Default::default()
1056 }
1057 }
1058
1059 fn depth1_single_thread() -> Settings<'static> {
1060 Settings {
1061 depth: 1,
1062 threads: None,
1063 ..Default::default()
1064 }
1065 }
1066
1067 fn tree_to_shape(t: Tree) -> BoundShape<'static, VmFunction, f32> {
1072 VmShape::from(t).try_into().expect("no variables allowed")
1073 }
1074
1075 fn sphere(center: [f32; 3], radius: f32) -> Tree {
1076 let (x, y, z) = Tree::axes();
1077 ((x - center[0]).square()
1078 + (y - center[1]).square()
1079 + (z - center[2]).square())
1080 .sqrt()
1081 - radius
1082 }
1083
1084 fn cube(bx: [f32; 2], by: [f32; 2], bz: [f32; 2]) -> Tree {
1085 let (x, y, z) = Tree::axes();
1086 let x_bounds = (bx[0] - x.clone()).max(x - bx[1]);
1087 let y_bounds = (by[0] - y.clone()).max(y - by[1]);
1088 let z_bounds = (bz[0] - z.clone()).max(z - bz[1]);
1089 x_bounds.max(y_bounds).max(z_bounds)
1090 }
1091
1092 #[test]
1093 fn test_cube_edge() {
1094 const EPSILON: f32 = 1e-3;
1095 let f = 2.0;
1096 let shape = tree_to_shape(cube([-f, f], [-f, 0.3], [-f, 0.6]));
1097 let octree = Octree::build(&shape, &depth0_single_thread()).unwrap();
1100 assert_eq!(octree.verts.len(), 5);
1101 let v = octree.verts[0].pos;
1102 let expected = nalgebra::Vector3::new(0.0, 0.3, 0.6);
1103 assert!(
1104 (v - expected).norm() < EPSILON,
1105 "bad edge vertex {v:?}; expected {expected:?}"
1106 );
1107 }
1108
1109 fn cone(
1110 corner: nalgebra::Vector3<f32>,
1111 tip: nalgebra::Vector3<f32>,
1112 radius: f32,
1113 ) -> Tree {
1114 let dir = tip - corner;
1115 let length = dir.norm();
1116 let dir = dir.normalize();
1117
1118 let corner = corner.map(|v| Tree::constant(v as f64));
1119 let dir = dir.map(|v| Tree::constant(v as f64));
1120
1121 let (x, y, z) = Tree::axes();
1122 let point = nalgebra::Vector3::new(x, y, z);
1123 let offset = point.clone() - corner.clone();
1124
1125 let a = offset.x.clone() * dir.x.clone()
1127 + offset.y.clone() * dir.y.clone()
1128 + offset.z.clone() * dir.z.clone();
1129
1130 let a_pos = corner + dir * a.clone();
1132
1133 let offset = point - a_pos;
1135 let b = (offset.x.clone().square()
1136 + offset.y.clone().square()
1137 + offset.z.clone().square())
1138 .sqrt();
1139
1140 b - radius * (1.0 - a / length)
1141 }
1142
1143 #[test]
1144 fn test_mesh_basic() {
1145 let shape = tree_to_shape(sphere([0.0; 3], 0.2));
1146
1147 let octree = Octree::build(&shape, &depth0_single_thread()).unwrap();
1150 assert!(octree.cells.is_empty()); assert_eq!(Cell::Empty, octree.root);
1152 assert!(octree.verts.is_empty());
1153
1154 let empty_mesh = octree.walk_dual();
1155 assert!(empty_mesh.vertices.is_empty());
1156 assert!(empty_mesh.triangles.is_empty());
1157
1158 let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1160 assert_eq!(octree.cells.len(), 1); assert_eq!(Cell::Branch { index: 0 }, octree.root);
1162
1163 assert_eq!(octree.verts.len(), 6 * 4 + 8, "incorrect vertex count");
1165
1166 for o in octree.cells[0].iter() {
1168 let Cell::Leaf(Leaf { index, mask }) = *o else {
1169 panic!()
1170 };
1171 assert_eq!(mask.count_ones(), 1);
1172 assert_eq!(index % 4, 0);
1173 }
1174
1175 let sphere_mesh = octree.walk_dual();
1176 assert!(sphere_mesh.vertices.len() > 1);
1177 assert!(!sphere_mesh.triangles.is_empty());
1178 }
1179
1180 #[test]
1181 fn test_sphere_verts() {
1182 let shape = tree_to_shape(sphere([0.0; 3], 0.2));
1183
1184 let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1185 let sphere_mesh = octree.walk_dual();
1186
1187 let mut edge_count = 0;
1188 for v in &sphere_mesh.vertices {
1189 let x_edge = v.x != 0.0;
1192 let y_edge = v.y != 0.0;
1193 let z_edge = v.z != 0.0;
1194 let edge_sum = x_edge as u8 + y_edge as u8 + z_edge as u8;
1195 assert!(edge_sum == 1 || edge_sum == 3);
1196 if edge_sum == 1 {
1197 assert!(
1198 (v.norm() - 0.2).abs() < 2.0 / u16::MAX as f32,
1199 "edge vertex {v:?} is not at radius 0.2"
1200 );
1201 edge_count += 1;
1202 } else {
1203 assert!(
1207 (v.abs() - nalgebra::Vector3::new(0.2, 0.2, 0.2)).norm()
1208 < 2.0 / 65535.0,
1209 "cell vertex {v:?} is not at expected position"
1210 );
1211 }
1212 }
1213 assert_eq!(edge_count, 6);
1214 }
1215
1216 #[test]
1217 fn test_sphere_manifold() {
1218 let shape = tree_to_shape(sphere([0.0; 3], 0.85));
1219
1220 for threads in [None, Some(&ThreadPool::Global)] {
1221 let settings = Settings {
1222 depth: 5,
1223 threads,
1224 ..Default::default()
1225 };
1226 let octree = Octree::build(&shape, &settings).unwrap();
1227 let sphere_mesh = octree.walk_dual();
1228
1229 check_for_vertex_dupes(&sphere_mesh).unwrap();
1230 check_for_edge_matching(&sphere_mesh).unwrap();
1231 }
1232 }
1233
1234 #[test]
1235 fn test_cube_verts() {
1236 let shape = tree_to_shape(cube([-0.1, 0.6], [-0.2, 0.75], [-0.3, 0.4]));
1237
1238 let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1239 let mesh = octree.walk_dual();
1240 const EPSILON: f32 = 2.0 / u16::MAX as f32;
1241 assert!(!mesh.vertices.is_empty());
1242 for v in &mesh.vertices {
1243 let x_edge = v.x != 0.0;
1246 let y_edge = v.y != 0.0;
1247 let z_edge = v.z != 0.0;
1248 let edge_sum = x_edge as u8 + y_edge as u8 + z_edge as u8;
1249 assert!(edge_sum == 1 || edge_sum == 3);
1250
1251 if edge_sum == 1 {
1252 assert!(
1253 (x_edge
1254 && ((v.x - -0.1).abs() < EPSILON
1255 || (v.x - 0.6).abs() < EPSILON))
1256 || (y_edge
1257 && ((v.y - -0.2).abs() < EPSILON
1258 || (v.y - 0.75).abs() < EPSILON))
1259 || (z_edge
1260 && ((v.z - -0.3).abs() < EPSILON
1261 || (v.z - 0.4).abs() < EPSILON)),
1262 "bad edge position {v:?}"
1263 );
1264 } else {
1265 assert!(
1266 ((v.x - -0.1).abs() < EPSILON
1267 || (v.x - 0.6).abs() < EPSILON)
1268 && ((v.y - -0.2).abs() < EPSILON
1269 || (v.y - 0.75).abs() < EPSILON)
1270 && ((v.z - -0.3).abs() < EPSILON
1271 || (v.z - 0.4).abs() < EPSILON),
1272 "bad vertex position {v:?}"
1273 );
1274 }
1275 }
1276 }
1277
1278 #[test]
1279 fn test_plane_center() {
1280 const EPSILON: f32 = 1e-3;
1281 for dx in [0.0, 0.25, -0.25, 2.0, -2.0] {
1282 for dy in [0.0, 0.25, -0.25, 2.0, -2.0] {
1283 for offset in [0.0, -0.2, 0.2] {
1284 let (x, y, z) = Tree::axes();
1285 let f = x * dx + y * dy + z + offset;
1286 let shape = tree_to_shape(f);
1287 let octree =
1288 Octree::build(&shape, &depth0_single_thread()).unwrap();
1289
1290 assert!(octree.cells.is_empty()); let pos = octree.verts[0].pos;
1292 let mut mass_point = nalgebra::Vector3::zeros();
1293 for v in &octree.verts[1..] {
1294 mass_point += v.pos;
1295 }
1296 mass_point /= (octree.verts.len() - 1) as f32;
1297 assert!(
1298 (pos - mass_point).norm() < EPSILON,
1299 "bad vertex position at dx: {dx}, dy: {dy}, \
1300 offset: {offset} => {pos:?} != {mass_point:?}"
1301 );
1302 let mut eval = VmShape::new_point_eval();
1303 let tape = shape.shape().ez_point_tape();
1304 for v in &octree.verts {
1305 let v = v.pos;
1306 let (r, _) = eval.eval(&tape, v.x, v.y, v.z).unwrap();
1307 assert!(r.abs() < EPSILON, "bad value at {v:?}: {r}");
1308 }
1309 }
1310 }
1311 }
1312 }
1313
1314 #[test]
1315 fn test_cone_vert() {
1316 for tip in [
1318 nalgebra::Vector3::new(0.2, 0.3, 0.4),
1319 nalgebra::Vector3::new(1.2, 1.3, 1.4),
1320 ] {
1321 let corner = nalgebra::Vector3::new(-1.0, -1.0, -1.0);
1322 let shape = tree_to_shape(cone(corner, tip, 0.1));
1323
1324 let mut eval = VmShape::new_point_eval();
1325 let tape = shape.shape().ez_point_tape();
1326 let (v, _) = eval.eval(&tape, tip.x, tip.y, tip.z).unwrap();
1327 assert!(v.abs() < 1e-6, "bad tip value: {v}");
1328 let (v, _) =
1329 eval.eval(&tape, corner.x, corner.y, corner.z).unwrap();
1330 assert!(v < 0.0, "bad corner value: {v}");
1331
1332 let octree =
1333 Octree::build(&shape, &depth0_single_thread()).unwrap();
1334 assert!(octree.cells.is_empty()); assert_eq!(octree.verts.len(), 4);
1336
1337 let pos = octree.verts[0].pos;
1338 assert!(
1339 (pos - tip).norm() < 1e-3,
1340 "bad vertex position: expected {tip:?}, got {pos:?}"
1341 );
1342 }
1343 }
1344
1345 fn test_mesh_manifold_inner(threads: Option<&ThreadPool>, mask: u8) {
1346 let mut shape = vec![];
1347 for j in Corner::<3>::iter() {
1348 if mask & (1 << j.index()) != 0 {
1349 shape.push(sphere(
1350 [
1351 if j & X { 0.5 } else { 0.0 },
1352 if j & Y { 0.5 } else { 0.0 },
1353 if j & Z { 0.5 } else { 0.0 },
1354 ],
1355 0.1,
1356 ));
1357 }
1358 }
1359 let Some(start) = shape.pop() else { return };
1360 let shape = shape.into_iter().fold(start, |acc, s| acc.min(s));
1361
1362 let shape = tree_to_shape(shape);
1365 let settings = Settings {
1366 depth: 2,
1367 threads,
1368 ..Default::default()
1369 };
1370 let octree = Octree::build(&shape, &settings).unwrap();
1371
1372 let mesh = octree.walk_dual();
1373 if mask != 0 && mask != 255 {
1374 assert!(!mesh.vertices.is_empty());
1375 assert!(!mesh.triangles.is_empty());
1376 }
1377
1378 if let Err(e) = check_for_vertex_dupes(&mesh) {
1379 panic!("mask {mask:08b} has {e}");
1380 }
1381 if let Err(e) = check_for_edge_matching(&mesh) {
1382 panic!("mask {mask:08b} has {e}");
1383 }
1384 }
1385
1386 #[test]
1387 fn test_mesh_manifold_single_thread() {
1388 for mask in 0..=255 {
1389 test_mesh_manifold_inner(None, mask)
1390 }
1391 }
1392
1393 #[test]
1394 fn test_mesh_manifold_multi_thread() {
1395 for mask in 0..=255 {
1396 test_mesh_manifold_inner(Some(&ThreadPool::Global), mask)
1397 }
1398 }
1399
1400 #[test]
1401 fn test_collapsible() {
1402 let shape = tree_to_shape(sphere([0.0; 3], 0.1));
1403 let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1404 assert!(octree.collapsible(0).is_none());
1405
1406 let shape = tree_to_shape(sphere([-1.0; 3], 0.1));
1409 let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1410 assert!(matches!(octree.root, Cell::Leaf { .. }));
1411
1412 let octree = Octree::build(
1414 &shape,
1415 &Settings {
1416 depth: 4,
1417 threads: None,
1418 ..Default::default()
1419 },
1420 )
1421 .unwrap();
1422 assert!(
1423 matches!(octree.root, Cell::Leaf { .. }),
1424 "root should be a leaf, not {:?}",
1425 octree.root
1426 );
1427
1428 let octree = Octree::build(
1430 &shape,
1431 &Settings {
1432 depth: 4,
1433 threads: Some(&ThreadPool::Global),
1434 ..Default::default()
1435 },
1436 )
1437 .unwrap();
1438 assert!(
1439 matches!(octree.root, Cell::Leaf { .. }),
1440 "root should be a leaf, not {:?}",
1441 octree.root
1442 );
1443
1444 let shape = tree_to_shape(sphere([-1.0, 0.0, 1.0], 0.1));
1445 let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1446 assert!(octree.collapsible(0).is_none());
1447
1448 let a = sphere([-1.0; 3], 0.1);
1449 let b = sphere([1.0; 3], 0.1);
1450 let shape = tree_to_shape(a.min(b));
1451 let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1452 assert!(octree.collapsible(0).is_none());
1453 }
1454
1455 #[test]
1456 fn test_empty_collapse() {
1457 let shape = tree_to_shape(sphere([0.1; 3], 0.05));
1459
1460 for threads in [None, Some(&ThreadPool::Global)] {
1461 let settings = Settings {
1462 depth: 1,
1463 threads,
1464 ..Default::default()
1465 };
1466 let octree = Octree::build(&shape, &settings).unwrap();
1467 assert_eq!(
1468 octree.root,
1469 Cell::Empty,
1470 "failed to collapse octree with threads: {:?}",
1471 threads.map(|t| t.thread_count())
1472 );
1473 }
1474 }
1475
1476 #[test]
1477 fn test_colonnade_manifold() {
1478 const COLONNADE: &str = include_str!("../../models/colonnade.vm");
1479 let (ctx, root) = Context::from_text(COLONNADE.as_bytes()).unwrap();
1480 let tape = VmShape::new(&ctx, root).unwrap();
1481 let shape = tape.try_into().unwrap();
1482
1483 for threads in [None, Some(&ThreadPool::Global)] {
1484 let settings = Settings {
1485 depth: 5,
1486 threads,
1487 ..Default::default()
1488 };
1489 let octree = Octree::build(&shape, &settings).unwrap();
1490 let mesh = octree.walk_dual();
1491 if let Err(e) = check_for_edge_matching(&mesh) {
1493 panic!(
1494 "colonnade model has {e} with threads: {:?}",
1495 threads.map(|t| t.thread_count())
1496 );
1497 }
1498 }
1499 }
1500
1501 #[test]
1502 fn colonnade_bounds() {
1503 const COLONNADE: &str = include_str!("../../models/colonnade.vm");
1504 let (ctx, root) = Context::from_text(COLONNADE.as_bytes()).unwrap();
1505 let tape = VmShape::new(&ctx, root).unwrap();
1506 let shape = tape.try_into().unwrap();
1507
1508 for threads in [None, Some(&ThreadPool::Global)] {
1509 let settings = Settings {
1510 depth: 8,
1511 threads,
1512 ..Default::default()
1513 };
1514 let octree = Octree::build(&shape, &settings).unwrap();
1515 let mesh = octree.walk_dual();
1516 for v in mesh.vertices.iter() {
1517 assert!(
1518 v.x < 1.0
1519 && v.x > -1.0
1520 && v.y < 1.0
1521 && v.y > -1.0
1522 && v.z < 1.0
1523 && v.z > -0.5,
1524 "invalid vertex {v:?} with threads {:?}",
1525 threads.map(|t| t.thread_count())
1526 );
1527 }
1528 }
1529 }
1530
1531 #[test]
1532 fn bear_bounds() {
1533 const COLONNADE: &str = include_str!("../../models/bear.vm");
1534 let (ctx, root) = Context::from_text(COLONNADE.as_bytes()).unwrap();
1535 let tape = VmShape::new(&ctx, root).unwrap();
1536 let shape = tape.try_into().unwrap();
1537
1538 for threads in [None, Some(&ThreadPool::Global)] {
1539 let settings = Settings {
1540 depth: 5,
1541 threads,
1542 ..Default::default()
1543 };
1544 let octree = Octree::build(&shape, &settings).unwrap();
1545 let mesh = octree.walk_dual();
1546 for v in mesh.vertices.iter() {
1547 assert!(
1548 v.x < 1.0
1549 && v.x > -0.75
1550 && v.y < 1.0
1551 && v.y > -0.75
1552 && v.z < 0.75
1553 && v.z > -0.75,
1554 "invalid vertex {v:?} with threads {:?}",
1555 threads.map(|t| t.thread_count())
1556 );
1557 }
1558 }
1559 }
1560
1561 fn check_for_vertex_dupes(mesh: &Mesh) -> Result<(), String> {
1562 let mut verts = mesh.vertices.clone();
1563 verts.sort_by_key(|k| (k.x.to_bits(), k.y.to_bits(), k.z.to_bits()));
1564 for i in 1..verts.len() {
1565 if verts[i - 1] == verts[i] {
1566 return Err(format!("duplicate vertices at {}", verts[i]));
1567 }
1568 }
1569 Ok(())
1570 }
1571
1572 fn check_for_edge_matching(mesh: &Mesh) -> Result<(), String> {
1573 let mut edges: BTreeMap<_, usize> = BTreeMap::new();
1574 for t in &mesh.triangles {
1575 for edge in [(t.x, t.y), (t.y, t.z), (t.z, t.x)] {
1576 if t.x == t.y || t.y == t.z || t.x == t.z {
1577 return Err("triangle with duplicate edges".to_string());
1578 }
1579 *edges.entry(edge).or_default() += 1;
1580 }
1581 }
1582 for (&(a, b), &i) in &edges {
1583 if i != 1 {
1584 return Err(format!(
1585 "duplicate edge ({a}, {b}) between {:?} {:?}",
1586 mesh.vertices[a], mesh.vertices[b]
1587 ));
1588 }
1589 if !edges.contains_key(&(b, a)) {
1590 return Err("unpaired edges".to_owned());
1591 }
1592 }
1593 Ok(())
1594 }
1595
1596 #[test]
1597 fn test_qef_merging() {
1598 let mut hermite = LeafHermiteData::default();
1599
1600 let grad = nalgebra::Vector4::new(1.0, 0.0, 0.0, 0.0);
1603 let pos = nalgebra::Vector4::new(0.0, 0.0, 0.0, 1.0);
1604 hermite.intersections.fill(LeafIntersection { pos, grad });
1605
1606 let mut hermites = [hermite; 8];
1609 for i in 0..12 {
1610 let e = Edge::new(i);
1611 let (_start, end) = e.corners();
1612 hermites[end.index()].intersections[i as usize] =
1613 LeafIntersection::default();
1614 }
1615
1616 let merged = LeafHermiteData::merge(hermites).unwrap();
1617 for i in merged.intersections {
1618 assert_eq!(i.grad, grad);
1619 assert_eq!(i.pos, pos);
1620 }
1621 for (i, f) in merged.face_qefs.iter().enumerate() {
1625 assert_eq!(
1626 f.mass_point().w,
1627 4.0,
1628 "bad accumulated QEF on face {i}"
1629 );
1630 }
1631 assert_eq!(
1632 merged.center_qef.mass_point().w,
1633 6.0,
1634 "bad accumulated QEF in center"
1635 );
1636 }
1637
1638 #[test]
1639 fn test_qef_near_planar() {
1640 let shape = tree_to_shape(sphere([0.0; 3], 0.75));
1641
1642 let settings = Settings {
1643 depth: 4,
1644 ..Default::default()
1645 };
1646
1647 let octree = Octree::build(&shape, &settings).unwrap().walk_dual();
1648 for v in octree.vertices.iter() {
1649 let n = v.norm();
1650 assert!(n > 0.7 && n < 0.8, "invalid vertex at {v:?}: {n}");
1651 }
1652 }
1653
1654 #[test]
1655 fn test_mesh_vars() {
1656 let (x, y, z) = Tree::axes();
1657 let v = Var::new();
1658 let c = Tree::from(v);
1659 let sphere = (x.square() + y.square() + z.square()).sqrt() - c;
1660 let shape = VmShape::from(sphere);
1661
1662 for threads in [None, Some(&ThreadPool::Global)] {
1663 let settings = Settings {
1664 depth: 4,
1665 threads,
1666 ..Default::default()
1667 };
1668
1669 for r in [0.5, 0.75] {
1670 let mut vars = ShapeVars::new();
1671 vars.insert(v.index().unwrap(), r);
1672 let shape = shape.bind(&vars).unwrap();
1673 let octree =
1674 Octree::build(&shape, &settings).unwrap().walk_dual();
1675 for v in octree.vertices.iter() {
1676 let n = v.norm();
1677 assert!(
1678 n > r - 0.05 && n < r + 0.05,
1679 "invalid vertex at {v:?}: {n} != {r} with threads {:?}",
1680 threads.map(|t| t.thread_count())
1681 );
1682 }
1683 }
1684 }
1685 }
1686
1687 #[test]
1688 fn test_octree_cancel() {
1689 let (x, y, z) = Tree::axes();
1690 let sphere = (x.square() + y.square() + z.square()).sqrt() - 1.0;
1691 let shape = tree_to_shape(sphere);
1692
1693 for threads in [None, Some(&ThreadPool::Global)] {
1694 let settings = Settings {
1695 depth: 4,
1696 threads,
1697 ..Default::default()
1698 };
1699 let c = settings.cancel.clone();
1700 c.cancel();
1701
1702 assert!(Octree::build(&shape, &settings).is_none());
1703 }
1704 }
1705}