fidget_mesh/
octree.rs

1//! An octree data structure and implementation of Manifold Dual Contouring
2
3use 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/// Octree storing occupancy and vertex positions for Manifold Dual Contouring
21#[derive(Debug)]
22pub struct Octree {
23    pub(crate) root: Cell<3>,
24    pub(crate) cells: Vec<[Cell<3>; 8]>,
25
26    /// Cell vertices, given as positions within the cell
27    ///
28    /// This is indexed by cell leaf index; the exact shape depends heavily on
29    /// the number of intersections and vertices within each leaf.
30    pub(crate) verts: Vec<CellVertex<3>>,
31}
32
33impl Octree {
34    /// Builds an octree with the root marked as invalid
35    pub(crate) fn new() -> Self {
36        Octree {
37            root: Cell::Invalid,
38            cells: vec![],
39            verts: vec![],
40        }
41    }
42
43    /// Builds an octree to the given depth, with user-provided variables
44    ///
45    /// The shape is evaluated on the region specified by `settings.bounds`.
46    ///
47    /// Returns `None` if processing is cancelled by the [`CancelToken`] in
48    /// [`Settings`].
49    pub fn build_with_vars<F: Function + RenderHints + Clone>(
50        shape: &Shape<F>,
51        vars: &ShapeVars<f32>,
52        settings: &Settings,
53    ) -> Option<Self> {
54        // Transform the shape given our world-to-model matrix
55        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            // Apply the transform from [-1, +1] back to model space
63            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    /// Builds an octree to the given depth
73    ///
74    /// If the shape uses variables other than `x`, `y`, `z`, then
75    /// [`build_with_vars`](Octree::build_with_vars) should be used instead (and
76    /// this function will return an error).
77    ///
78    /// The shape is evaluated on the region specified by `settings.bounds`.
79    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    /// Multithreaded constructor
119    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        // We want a number of tasks that's significantly larger than our thread
133        // count, so that we can fully saturate all cores even if tasks take
134        // different amounts of time.
135        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            // Reserve new cells for the 8x children
141            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![]); // pre-populate interval tape
160        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                        // Patch our cell so that it builds at index 0
167                        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        // Copy hermite data into arrays, and compute cumulative offsets
196        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        // Walk back up the tree, merging cells as we go
231        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    /// Recursively walks the dual of the octree, building a mesh
246    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    /// Looks up the given child of a cell.
262    ///
263    /// If the cell is a leaf node, returns that cell instead.
264    ///
265    /// # Panics
266    /// If the cell is [`Invalid`](Cell::Invalid)
267    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    /// Checks the set of 8 children starting at the given index for completion
282    ///
283    /// If all are empty or full, then pro-actively collapses the cells (freeing
284    /// them if they're at the tail end of the array).
285    fn check_done(
286        &mut self,
287        cell: CellIndex<3>,
288        index: usize,
289        hermite_data: [LeafHermiteData; 8],
290        hermite: &mut LeafHermiteData, // output
291    ) -> Cell<3> {
292        // Check out the children
293        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        // If all of the branches are empty or full, then we're going to
312        // record an Empty or Full cell in the parent and don't need the
313        // 8x children.  Drop them by resizing the array
314        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 we can collapse the cell, then we'll be recording an Empty / Full
327        // / Leaf cell in the parent and don't need the 8x children.
328        //
329        // We can drop them if they happen to be at the tail end of the
330        // octree; otherwise, we'll satisfy ourselves with setting them to
331        // invalid.  We will always be at the tail end of the octree during
332        // single-threaded evaluation, it's only during merging octrees from
333        // multiple threads that we'd have cells midway through the array.
334        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    /// Try to collapse the given cell
345    ///
346    /// Writes to `self.verts` and returns the leaf if successful; otherwise
347    /// returns `None`.
348    fn try_collapse(
349        &mut self,
350        cell: CellIndex<3>,
351        index: usize,
352        hermite_data: [LeafHermiteData; 8],
353        hermite: &mut LeafHermiteData, // output
354    ) -> Option<Leaf<3>> {
355        let mask = self.collapsible(index)?;
356        *hermite = LeafHermiteData::merge(hermite_data)?;
357
358        // Empty / full cells should never be produced here.  The
359        // only way to get an empty / full cell is for all eight
360        // corners to be empty / full; if that was the case, then
361        // either:
362        //
363        // - The interior vertices match, in which case this should
364        //   have been collapsed into a single empty / full cell
365        // - The interior vertices *do not* match, in which case the
366        //   cell should not be marked as collapsible.
367        let (pos, new_err) = hermite.solve();
368        if new_err >= hermite.qef_err * 2.0 || !cell.bounds.contains(pos) {
369            return None;
370        }
371        // Record the newly-collapsed leaf
372        hermite.qef_err = new_err;
373
374        let index = self.verts.len();
375        self.verts.push(pos);
376
377        // Install cell intersections, of which there must only
378        // be one (since we only collapse manifold cells)
379        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    /// Checks whether the set of 8 cells beginning at `root` can be collapsed.
390    ///
391    /// Only topology is checked, based on the three predicates from "Dual
392    /// Contouring of Hermite Data" (Ju et al, 2002), §4.1
393    pub(crate) fn collapsible(&self, root: usize) -> Option<CellMask<3>> {
394        // Copy cells to a local variable for simplicity
395        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            //  - The sign in the middle of a coarse edge must agree with the
417            //    sign of at least one of the edge’s two endpoints.
418            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            //  - The sign in the middle of a coarse face must agree with the
432            //    sign of at least one of the face’s four corners.
433            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            //  - The sign in the middle of a coarse cube must agree with the
449            //    sign of at least one of the cube’s eight corners.
450            for _i in 0..1 {
451                // Doing this in the t,u,v loop isn't strictly necessary, but it
452                // preserves the structure nicely.
453                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        // The outer cell must not be empty or full at this point; if it was
461        // empty or full and the other conditions had been met, then it should
462        // have been collapsed already.
463        debug_assert_ne!(mask, 255);
464        debug_assert_ne!(mask, 0);
465
466        // TODO: this check may not be necessary, because we're doing *manifold*
467        // dual contouring; the collapsed cell can have multiple vertices.
468        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/// Data structure for an under-construction octree
497#[derive(Debug)]
498pub(crate) struct OctreeBuilder<F: Function + RenderHints> {
499    /// In-construction octree
500    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    /// Builds a new octree builder, which allocates data for 8 root cells
519    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    /// Recurse down the octree, building the given cell
532    ///
533    /// Writes to `self.o.cells[cell]`, which must be reserved
534    ///
535    /// If a leaf is written, then `hermite` is populated
536    ///
537    /// Returns `true` if recursion completed normally, or `false` if it was
538    /// cancelled.
539    #[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                // Reserve new cells for the 8x children
585                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                // Figure out whether the children can be collapsed
603                self.octree.check_done(cell, index, hermite_child, hermite)
604            }
605        };
606        true
607    }
608
609    /// Evaluates the given leaf
610    ///
611    /// Writes the leaf vertex to `self.o.verts`, hermite data to
612    /// `self.hermite`, and the leaf data to `self.leafs`.  Does **not** write
613    /// anything to `self.o.cells`; the cell is returned instead.
614    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        // Build a mask of active corners, which determines cell
638        // topology / vertex count / active edges / etc.
639        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        // Early exit if the cell is completely empty or full
646        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        // Start and endpoints in 3D space for intersection searches
655        let mut start = [nalgebra::Vector3::zeros(); 12];
656        let mut end = [nalgebra::Vector3::zeros(); 12];
657        let mut edge_count = 0;
658
659        // Convert from the corner mask to start and end-points in 3D space,
660        // relative to the cell bounds (i.e. as a `Matrix3<u16>`).
661        for vs in CELL_TO_VERT_TO_EDGES[mask.index()].iter() {
662            for e in *vs {
663                // Find the axis that's being used by this edge
664                let axis = e.start().index() ^ e.end().index();
665                debug_assert_eq!(axis.count_ones(), 1);
666                debug_assert!(axis < 8);
667
668                // Pick a position closer to the filled side
669                let (a, b) = if e.end().index() & axis != 0 {
670                    (0, u16::MAX)
671                } else {
672                    (u16::MAX, 0)
673                };
674
675                // Convert the intersection to a 3D position
676                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        // Slice off the unused sections of the arrays
698        let start = &mut start[..edge_count]; // always inside
699        let end = &mut end[..edge_count]; // always outside
700
701        // Scratch arrays for edge search
702        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        // This part looks hairy, but it's just doing an N-ary search along each
712        // edge to find the intersection point.
713        for _ in 0..EDGE_SEARCH_DEPTH {
714            // Populate edge search arrays
715            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            // Do the actual evaluation
734            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            // Update start and end positions based on evaluation
740            for ((start, end), search) in start
741                .iter_mut()
742                .zip(end.iter_mut())
743                .zip(out.chunks(EDGE_SEARCH_SIZE))
744            {
745                // The search must be inside-to-outside
746                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        // Populate intersections to the average of start and end
775        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        // TODO: special case for cells with multiple gradients ("features")
800        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 a point has invalid gradients, then it's _probably_ on a
815                // sharp feature in the mesh, so we should just snap to that
816                // point specifically.  This means we don't solve the QEF, and
817                // instead mark it as invalid.
818                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                // Record this intersection in the Hermite data for the leaf
826                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                // We overwrite the error here, because it's only used when
842                // collapsing cells, which only occurs if there's a single
843                // vertex; last-error-wins works fine in that case.
844                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////////////////////////////////////////////////////////////////////////////////
861
862#[derive(Copy, Clone, Default, Debug)]
863struct LeafIntersection {
864    /// Intersection position is xyz; w is 1 if the intersection is present
865    pos: nalgebra::Vector4<f32>,
866    /// Gradient is xyz; w is the distance field value at this point
867    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    /// Error found when solving this QEF (if positive), or a special value
887    qef_err: f32,
888}
889
890/// This QEF is not populated
891const QEF_ERR_EMPTY: f32 = -1.0;
892
893/// This QEF is known to be invalid and should be disregarded
894const 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    /// Merges an octree subdivision of leaf hermite data
909    ///
910    /// Returns `None` if any of the leafs have invalid QEFs (typically due to
911    /// NANs in normal computation).
912    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        // Accumulate intersections along edges
921        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                // Canonical edge as a value in the 0-12 range
935                let edge = Edge::new((t.index() * 4 + edge) as u8);
936
937                // One or the other leaf has an intersection
938                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        // Accumulate face QEFs along edges
950        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                // Edges oriented along the v axis on this face
967                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                // Edges oriented along the u axis on this face
974                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        // Accumulate center QEFs
983        for t in [X, Y, Z] {
984            let u = t.next();
985            let v = t.next();
986
987            // Accumulate the four inner face QEFs
988            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            // Edges oriented along the u axis
997            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            // We skip edges oriented on the v axis, because they'll be counted
1003            // by one of the other iterations through the loop
1004        }
1005        for leaf in leafs {
1006            out.center_qef += leaf.center_qef;
1007        }
1008
1009        // Accumulate minimum QEF error among valid child QEFs
1010        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    /// Solves the combined QEF
1019    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////////////////////////////////////////////////////////////////////////////////
1032
1033#[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        // This should be a cube with a single edge running through the root
1085        // node of the octree, with an edge vertex at [0, 0.3, 0.6]
1086        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        // a is the distance along the corner-tip direction
1113        let a = offset.x.clone() * dir.x.clone()
1114            + offset.y.clone() * dir.y.clone()
1115            + offset.z.clone() * dir.z.clone();
1116
1117        // Position of the nearest point on the corner-tip axis
1118        let a_pos = corner + dir * a.clone();
1119
1120        // b is the orthogonal distance
1121        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        // If we only build a depth-0 octree, then it's a leaf without any
1135        // vertices (since all the corners are empty)
1136        let octree = Octree::build(&shape, &depth0_single_thread()).unwrap();
1137        assert!(octree.cells.is_empty()); // root only
1138        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        // Now, at depth-1, each cell should be a Leaf with one vertex
1146        let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1147        assert_eq!(octree.cells.len(), 1); // one fanout of 8 cells
1148        assert_eq!(Cell::Branch { index: 0 }, octree.root);
1149
1150        // Each of the 6 edges is counted 4 times and each cell has 1 vertex
1151        assert_eq!(octree.verts.len(), 6 * 4 + 8, "incorrect vertex count");
1152
1153        // Each cell is a leaf with 4 vertices (3 edges, 1 center)
1154        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            // Edge vertices should be found via binary search and therefore
1177            // should be close to the true crossing point
1178            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                // The sphere looks like a box at this sampling depth, since the
1191                // edge intersections are all axis-aligned; we'll check that
1192                // corner vertices are all at [±0.2, ±0.2, ±0.2]
1193                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            // Edge vertices should be found via binary search and therefore
1231            // should be close to the true crossing point
1232            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()); // root only
1278                    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        // Test both in-cell and out-of-cell cone vertices
1304        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()); // root only
1322            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        // Now, we have our shape, which is 0-8 spheres placed at the
1350        // corners of the cell spanning [0, 0.25]
1351        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        // If we have a single corner sphere, then the builder will collapse the
1394        // branch for us, leaving just a leaf.
1395        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        // Test with a single thread and a deeper tree, which should still work
1400        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        // This should also work with multiple threads!
1416        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        // Make a very smol sphere that won't be sampled
1445        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            // Note: the model has duplicate vertices!
1478            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        // Add a dummy intersection with a non-zero normal; we'll be counting
1585        // mass points to check that the merging went smoothly
1586        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        // Ensure that only one of the two sub-edges per edge contains an
1591        // intersection, because that's checked for in `LeafHermiteData::merge`
1592        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        // Each face in the merged cell should include the accumulation of four
1606        // edges from lower cells (but nothing more, because lower cells didn't
1607        // have face QEFs populated)
1608        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}