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