Skip to main content

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::{RenderHandle, RenderHints, ThreadPool},
15    shape::{
16        BoundShape, Shape, ShapeBulkEval, ShapeBulkEvalError, ShapeTracingEval,
17        ShapeTracingEvalError, ShapeVars,
18    },
19    types::Grad,
20};
21use std::collections::VecDeque;
22
23/// Octree storing occupancy and vertex positions for Manifold Dual Contouring
24#[derive(Debug)]
25pub struct Octree {
26    pub(crate) root: Cell<3>,
27    pub(crate) cells: Vec<[Cell<3>; 8]>,
28
29    /// Cell vertices, given as positions within the cell
30    ///
31    /// This is indexed by cell leaf index; the exact shape depends heavily on
32    /// the number of intersections and vertices within each leaf.
33    pub(crate) verts: Vec<CellVertex<3>>,
34}
35
36impl Octree {
37    /// Builds an octree with the root marked as invalid
38    pub(crate) fn new() -> Self {
39        Octree {
40            root: Cell::Invalid,
41            cells: vec![],
42            verts: vec![],
43        }
44    }
45
46    /// Builds an octree to the given depth
47    ///
48    /// The shape is evaluated on the region specified by `settings.bounds`.
49    ///
50    /// Returns `None` if processing is cancelled by the
51    /// [`CancelToken`](fidget_core::render::CancelToken) in [`Settings`].
52    pub fn build<F: Function + RenderHints + Clone>(
53        b: &BoundShape<F, f32>,
54        settings: &Settings,
55    ) -> Option<Self> {
56        let mut out = Self::build_inner(b, settings)?;
57
58        // Apply the transform from [-1, +1] back to model space
59        if settings.world_to_model != nalgebra::Matrix4::identity() {
60            for v in &mut out.verts {
61                let p: nalgebra::Point3<f32> = v.pos.into();
62                let q = settings.world_to_model.transform_point(&p);
63                v.pos = q.coords;
64            }
65        }
66        Some(out)
67    }
68
69    /// Performs the inner work of evaluation
70    ///
71    /// # Panics
72    /// If `shape` and `vars` are not compatible
73    fn build_inner<F: Function + RenderHints + Clone>(
74        b: &BoundShape<F, f32>,
75        settings: &Settings,
76    ) -> Option<Self> {
77        let shape = b.shape();
78        let vars = b.vars();
79        if let Some(threads) = settings.threads {
80            Self::build_inner_mt(shape, settings, vars, threads)
81        } else {
82            let mut eval = RenderHandle::new(shape.clone());
83            let mut out = OctreeBuilder::new(settings, vars);
84            let mut hermite = LeafHermiteData::default();
85            if out.recurse(&mut eval, CellIndex::default(), &mut hermite) {
86                Some(out.octree)
87            } else {
88                None
89            }
90        }
91    }
92
93    /// Multithreaded constructor
94    fn build_inner_mt<F: Function + RenderHints + Clone>(
95        shape: &Shape<F>,
96        settings: &Settings,
97        vars: &ShapeVars<f32>,
98        threads: &ThreadPool,
99    ) -> Option<Self> {
100        let mut root = Octree::new();
101        let mut todo = VecDeque::new();
102        todo.push_back(CellIndex::<3>::default());
103        let mut fixup = vec![];
104        let mut hermites = vec![];
105
106        // We want a number of tasks that's significantly larger than our thread
107        // count, so that we can fully saturate all cores even if tasks take
108        // different amounts of time.
109        let target_count = (8usize.pow(u32::from(settings.depth)))
110            .min(threads.thread_count() * 10);
111        while todo.len() < target_count {
112            let next = todo.pop_front().unwrap();
113
114            // Reserve new cells for the 8x children
115            let index = root.cells.len();
116            root.cells.push([Cell::Invalid; 8]);
117            hermites.push([LeafHermiteData::default(); 8]);
118            for i in Corner::<3>::iter() {
119                let cell = next.child(index, i);
120                todo.push_back(cell);
121            }
122            fixup.push((next, index));
123        }
124
125        use rayon::prelude::*;
126
127        struct Output {
128            cell: CellIndex<3>,
129            octree: Octree,
130            hermite: LeafHermiteData,
131        }
132        let mut rh = RenderHandle::new(shape.clone());
133        let _ = rh.i_tape(&mut vec![]); // pre-populate interval tape
134        let out = threads.run(|| {
135            todo.par_iter()
136                .map_init(
137                    || (OctreeBuilder::new(settings, vars), rh.clone()),
138                    |(builder, eval), cell| {
139                        let mut hermite = LeafHermiteData::default();
140                        // Patch our cell so that it builds at index 0
141                        let local_cell = CellIndex {
142                            index: None,
143                            ..*cell
144                        };
145                        if !builder.recurse(eval, local_cell, &mut hermite) {
146                            return None;
147                        }
148                        let octree = std::mem::replace(
149                            &mut builder.octree,
150                            Octree::new(),
151                        );
152                        Some(Output {
153                            octree,
154                            cell: *cell,
155                            hermite,
156                        })
157                    },
158                )
159                .collect::<Option<Vec<_>>>()
160        })?;
161
162        // Copy hermite data into arrays, and compute cumulative offsets
163        let mut cell_offsets = vec![root.cells.len()];
164        let mut vert_offsets = vec![0];
165        for o in &out {
166            let (i, j) = o.cell.index.unwrap();
167            hermites[i][j as usize] = o.hermite;
168            let c = cell_offsets.last().unwrap() + o.octree.cells.len();
169            cell_offsets.push(c);
170            let v = vert_offsets.last().unwrap() + o.octree.verts.len();
171            vert_offsets.push(v);
172        }
173        root.cells.reserve(*cell_offsets.last().unwrap());
174        root.verts.reserve(*vert_offsets.last().unwrap());
175
176        for (i, o) in out.into_iter().enumerate() {
177            assert_eq!(cell_offsets[i], root.cells.len());
178            assert_eq!(vert_offsets[i], root.verts.len());
179            let remap_cell = |c| match c {
180                Cell::Leaf(Leaf { mask, index }) => Cell::Leaf(Leaf {
181                    mask,
182                    index: index + vert_offsets[i],
183                }),
184                Cell::Branch { index } => Cell::Branch {
185                    index: index + cell_offsets[i],
186                },
187                Cell::Full | Cell::Empty => c,
188                Cell::Invalid => panic!(),
189            };
190            root.cells.extend(
191                o.octree.cells.into_iter().map(|cs| cs.map(remap_cell)),
192            );
193            root.verts.extend(o.octree.verts);
194            root[o.cell] = remap_cell(o.octree.root);
195        }
196
197        // Walk back up the tree, merging cells as we go
198        for (cell, index) in fixup.into_iter().rev() {
199            let h = hermites[index];
200            root[cell] = root.check_done(
201                cell,
202                index,
203                h,
204                cell.index
205                    .map(|(i, j)| &mut hermites[i][j as usize])
206                    .unwrap_or(&mut LeafHermiteData::default()),
207            );
208        }
209        Some(root)
210    }
211
212    /// Recursively walks the dual of the octree, building a mesh
213    pub fn walk_dual(&self) -> Mesh {
214        let mut mesh = MeshBuilder::default();
215
216        mesh.cell(self, CellIndex::default());
217        mesh.take()
218    }
219
220    pub(crate) fn is_leaf(&self, cell: CellIndex<3>) -> bool {
221        match self[cell] {
222            Cell::Leaf(..) | Cell::Full | Cell::Empty => true,
223            Cell::Branch { .. } => false,
224            Cell::Invalid => panic!(),
225        }
226    }
227
228    /// Looks up the given child of a cell.
229    ///
230    /// If the cell is a leaf node, returns that cell instead.
231    ///
232    /// # Panics
233    /// If the cell is [`Invalid`](Cell::Invalid)
234    pub(crate) fn child<C: Into<Corner<3>>>(
235        &self,
236        cell: CellIndex<3>,
237        child: C,
238    ) -> CellIndex<3> {
239        let child = child.into();
240
241        match self[cell] {
242            Cell::Leaf { .. } | Cell::Full | Cell::Empty => cell,
243            Cell::Branch { index } => cell.child(index, child),
244            Cell::Invalid => panic!(),
245        }
246    }
247
248    /// Checks the set of 8 children starting at the given index for completion
249    ///
250    /// If all are empty or full, then pro-actively collapses the cells (freeing
251    /// them if they're at the tail end of the array).
252    fn check_done(
253        &mut self,
254        cell: CellIndex<3>,
255        index: usize,
256        hermite_data: [LeafHermiteData; 8],
257        hermite: &mut LeafHermiteData, // output
258    ) -> Cell<3> {
259        // Check out the children
260        let mut full_count = 0;
261        let mut empty_count = 0;
262        for i in 0..8 {
263            match self.cells[index][i] {
264                Cell::Invalid => {
265                    panic!("found invalid cell during meshing")
266                }
267                Cell::Full => {
268                    full_count += 1;
269                }
270                Cell::Empty => {
271                    empty_count += 1;
272                }
273                Cell::Branch { .. } => return Cell::Branch { index },
274                Cell::Leaf(Leaf { .. }) => (),
275            }
276        }
277
278        // If all of the branches are empty or full, then we're going to
279        // record an Empty or Full cell in the parent and don't need the
280        // 8x children.  Drop them by resizing the array
281        let out = if full_count == 8 {
282            Cell::Full
283        } else if empty_count == 8 {
284            Cell::Empty
285        } else if let Some(leaf) =
286            self.try_collapse(cell, index, hermite_data, hermite)
287        {
288            Cell::Leaf(leaf)
289        } else {
290            Cell::Branch { index }
291        };
292
293        // If we can collapse the cell, then we'll be recording an Empty / Full
294        // / Leaf cell in the parent and don't need the 8x children.
295        //
296        // We can drop them if they happen to be at the tail end of the
297        // octree; otherwise, we'll satisfy ourselves with setting them to
298        // invalid.  We will always be at the tail end of the octree during
299        // single-threaded evaluation, it's only during merging octrees from
300        // multiple threads that we'd have cells midway through the array.
301        if !matches!(out, Cell::Branch { .. }) {
302            if index == self.cells.len() - 1 {
303                self.cells.resize(index, [Cell::Invalid; 8]);
304            } else {
305                self.cells[index] = [Cell::Invalid; 8];
306            }
307        }
308        out
309    }
310
311    /// Try to collapse the given cell
312    ///
313    /// Writes to `self.verts` and returns the leaf if successful; otherwise
314    /// returns `None`.
315    fn try_collapse(
316        &mut self,
317        cell: CellIndex<3>,
318        index: usize,
319        hermite_data: [LeafHermiteData; 8],
320        hermite: &mut LeafHermiteData, // output
321    ) -> Option<Leaf<3>> {
322        let mask = self.collapsible(index)?;
323        *hermite = LeafHermiteData::merge(hermite_data)?;
324
325        // Empty / full cells should never be produced here.  The
326        // only way to get an empty / full cell is for all eight
327        // corners to be empty / full; if that was the case, then
328        // either:
329        //
330        // - The interior vertices match, in which case this should
331        //   have been collapsed into a single empty / full cell
332        // - The interior vertices *do not* match, in which case the
333        //   cell should not be marked as collapsible.
334        let (pos, new_err) = hermite.solve();
335        if new_err >= hermite.qef_err * 2.0 || !cell.bounds.contains(pos) {
336            return None;
337        }
338        // Record the newly-collapsed leaf
339        hermite.qef_err = new_err;
340
341        let index = self.verts.len();
342        self.verts.push(pos);
343
344        // Install cell intersections, of which there must only
345        // be one (since we only collapse manifold cells)
346        let edges = &CELL_TO_VERT_TO_EDGES[mask.index()];
347        debug_assert_eq!(edges.len(), 1);
348        for e in edges[0] {
349            let i = hermite.intersections[e.to_undirected().index()];
350            self.verts.push(CellVertex { pos: i.pos.xyz() });
351        }
352
353        Some(Leaf { mask, index })
354    }
355
356    /// Checks whether the set of 8 cells beginning at `root` can be collapsed.
357    ///
358    /// Only topology is checked, based on the three predicates from "Dual
359    /// Contouring of Hermite Data" (Ju et al, 2002), §4.1
360    pub(crate) fn collapsible(&self, root: usize) -> Option<CellMask<3>> {
361        // Copy cells to a local variable for simplicity
362        let cells = self.cells[root];
363
364        let mut mask = 0;
365        for (i, &c) in cells.iter().enumerate() {
366            let b = match c {
367                Cell::Leaf(Leaf { mask, .. }) => {
368                    if CELL_TO_VERT_TO_EDGES[mask.index()].len() > 1 {
369                        return None;
370                    }
371                    (mask.index() & (1 << i) != 0) as u8
372                }
373                Cell::Empty => 0,
374                Cell::Full => 1,
375                Cell::Branch { .. } => return None,
376                Cell::Invalid => panic!(),
377            };
378            mask |= b << i;
379        }
380
381        use super::frame::{XYZ, YZX, ZXY};
382        for (t, u, v) in [XYZ::frame(), YZX::frame(), ZXY::frame()] {
383            //  - The sign in the middle of a coarse edge must agree with the
384            //    sign of at least one of the edge’s two endpoints.
385            for i in 0..4 {
386                let a = (u * ((i & 1) != 0)) | (v * ((i & 2) != 0));
387                let b = a | t;
388                let center = cells[a.index()].corner(b);
389
390                if [a, b]
391                    .iter()
392                    .all(|v| ((mask & (1 << v.index())) != 0) != center)
393                {
394                    return None;
395                }
396            }
397
398            //  - The sign in the middle of a coarse face must agree with the
399            //    sign of at least one of the face’s four corners.
400            for i in 0..2 {
401                let a: Corner<3> = (t * (i & 1 == 0)).into();
402                let b = a | u;
403                let c = a | v;
404                let d = a | u | v;
405
406                let center = cells[a.index()].corner(d);
407
408                if [a, b, c, d]
409                    .iter()
410                    .all(|v| ((mask & (1 << v.index())) != 0) != center)
411                {
412                    return None;
413                }
414            }
415            //  - The sign in the middle of a coarse cube must agree with the
416            //    sign of at least one of the cube’s eight corners.
417            for _i in 0..1 {
418                // Doing this in the t,u,v loop isn't strictly necessary, but it
419                // preserves the structure nicely.
420                let center = cells[0].corner(t | u | v);
421                if (0..8).all(|v| ((mask & (1 << v)) != 0) != center) {
422                    return None;
423                }
424            }
425        }
426
427        // The outer cell must not be empty or full at this point; if it was
428        // empty or full and the other conditions had been met, then it should
429        // have been collapsed already.
430        debug_assert_ne!(mask, 255);
431        debug_assert_ne!(mask, 0);
432
433        // TODO: this check may not be necessary, because we're doing *manifold*
434        // dual contouring; the collapsed cell can have multiple vertices.
435        if CELL_TO_VERT_TO_EDGES[mask as usize].len() == 1 {
436            Some(CellMask::new(mask))
437        } else {
438            None
439        }
440    }
441}
442
443impl std::ops::Index<CellIndex<3>> for Octree {
444    type Output = Cell<3>;
445
446    fn index(&self, i: CellIndex<3>) -> &Self::Output {
447        match i.index {
448            None => &self.root,
449            Some((i, j)) => &self.cells[i][j as usize],
450        }
451    }
452}
453
454impl std::ops::IndexMut<CellIndex<3>> for Octree {
455    fn index_mut(&mut self, i: CellIndex<3>) -> &mut Self::Output {
456        match i.index {
457            None => &mut self.root,
458            Some((i, j)) => &mut self.cells[i][j as usize],
459        }
460    }
461}
462
463/// Data structure for an under-construction octree
464pub(crate) struct OctreeBuilder<'a, F: Function + RenderHints> {
465    /// In-construction octree
466    pub(crate) octree: Octree,
467
468    // Values stolen from settings
469    cancel: fidget_core::render::CancelToken,
470    world_to_model: Option<&'a nalgebra::Matrix4<f32>>,
471    max_depth: u8,
472    vars: &'a ShapeVars<f32>,
473
474    eval_float_slice: ShapeBulkEval<F::FloatSliceEval>,
475    eval_interval: ShapeTracingEval<F::IntervalEval>,
476    eval_grad_slice: ShapeBulkEval<F::GradSliceEval>,
477
478    tape_storage: Vec<F::TapeStorage>,
479    shape_storage: Vec<F::Storage>,
480    workspace: F::Workspace,
481}
482
483impl<'a, F: Function + RenderHints> OctreeBuilder<'a, F> {
484    /// Builds a new octree builder, which allocates data for 8 root cells
485    pub(crate) fn new(
486        settings: &'a Settings,
487        vars: &'a ShapeVars<f32>,
488    ) -> Self {
489        let world_to_model =
490            if settings.world_to_model == nalgebra::Matrix4::identity() {
491                None
492            } else {
493                Some(&settings.world_to_model)
494            };
495        Self {
496            octree: Octree::new(),
497
498            cancel: settings.cancel.clone(),
499            max_depth: settings.depth,
500            world_to_model,
501            vars,
502
503            eval_float_slice: Shape::<F>::new_float_slice_eval(),
504            eval_grad_slice: Shape::<F>::new_grad_slice_eval(),
505            eval_interval: Shape::<F>::new_interval_eval(),
506            tape_storage: vec![],
507            shape_storage: vec![],
508            workspace: Default::default(),
509        }
510    }
511
512    /// Recurse down the octree, building the given cell
513    ///
514    /// Writes to `self.o.cells[cell]`, which must be reserved
515    ///
516    /// If a leaf is written, then `hermite` is populated
517    ///
518    /// Returns `true` if recursion completed normally, or `false` if it was
519    /// cancelled.
520    #[must_use]
521    fn recurse(
522        &mut self,
523        eval: &mut RenderHandle<F>,
524        cell: CellIndex<3>,
525        hermite: &mut LeafHermiteData,
526    ) -> bool {
527        if self.cancel.is_cancelled() {
528            return false;
529        }
530        let (i, r) = match self.eval_interval.eval_raw(
531            eval.i_tape(&mut self.tape_storage),
532            cell.bounds[crate::types::X],
533            cell.bounds[crate::types::Y],
534            cell.bounds[crate::types::Z],
535            self.world_to_model,
536            self.vars,
537        ) {
538            Ok(v) => v,
539            Err(ShapeTracingEvalError::MissingVar(..)) => unreachable!(),
540        };
541        self.octree[cell] = if i.upper() < 0.0 {
542            Cell::Full
543        } else if i.lower() > 0.0 {
544            Cell::Empty
545        } else {
546            let sub_tape = if F::simplify_tree_during_meshing(cell.depth) {
547                if let Some(trace) = r.as_ref() {
548                    eval.simplify(
549                        trace,
550                        &mut self.workspace,
551                        &mut self.shape_storage,
552                        &mut self.tape_storage,
553                    )
554                } else {
555                    eval
556                }
557            } else {
558                eval
559            };
560            if cell.depth == self.max_depth as usize {
561                self.leaf(sub_tape, cell, hermite)
562            } else {
563                // Reserve new cells for the 8x children
564                let index = self.octree.cells.len();
565                self.octree.cells.push([Cell::Invalid; 8]);
566                let mut hermite_child = [LeafHermiteData::default(); 8];
567                for i in Corner::<3>::iter() {
568                    let cell = cell.child(index, i);
569                    if !self.recurse(
570                        sub_tape,
571                        cell,
572                        &mut hermite_child[i.index()],
573                    ) {
574                        return false;
575                    }
576                }
577
578                // Figure out whether the children can be collapsed
579                self.octree.check_done(cell, index, hermite_child, hermite)
580            }
581        };
582        true
583    }
584
585    /// Evaluates the given leaf
586    ///
587    /// Writes the leaf vertex to `self.o.verts`, hermite data to
588    /// `self.hermite`, and the leaf data to `self.leafs`.  Does **not** write
589    /// anything to `self.o.cells`; the cell is returned instead.
590    fn leaf(
591        &mut self,
592        eval: &mut RenderHandle<F>,
593        cell: CellIndex<3>,
594        hermite_cell: &mut LeafHermiteData,
595    ) -> Cell<3> {
596        let mut xs = [0.0; 8];
597        let mut ys = [0.0; 8];
598        let mut zs = [0.0; 8];
599        for i in Corner::<3>::iter() {
600            let [x, y, z] = cell.corner(i);
601            xs[i.index()] = x;
602            ys[i.index()] = y;
603            zs[i.index()] = z;
604        }
605
606        let out = match self.eval_float_slice.eval_raw(
607            eval.f_tape(&mut self.tape_storage),
608            &xs,
609            &ys,
610            &zs,
611            self.world_to_model,
612            ShapeBulkEval::<F::FloatSliceEval>::var_value(self.vars),
613        ) {
614            Ok(v) => v,
615            Err(
616                ShapeBulkEvalError::MissingVar(..)
617                | ShapeBulkEvalError::MismatchedVarSlices { .. },
618            ) => unreachable!(),
619        };
620        debug_assert_eq!(out.len(), 8);
621
622        // Build a mask of active corners, which determines cell
623        // topology / vertex count / active edges / etc.
624        let mask = out
625            .iter()
626            .enumerate()
627            .filter(|(_i, v)| **v < 0.0)
628            .fold(0, |acc, (i, _v)| acc | (1 << i));
629
630        // Early exit if the cell is completely empty or full
631        if mask == 0 {
632            return Cell::Empty;
633        } else if mask == 255 {
634            return Cell::Full;
635        }
636
637        let mask = CellMask::new(mask);
638
639        // Start and endpoints in 3D space for intersection searches
640        let mut start = [nalgebra::Vector3::zeros(); 12];
641        let mut end = [nalgebra::Vector3::zeros(); 12];
642        let mut edge_count = 0;
643
644        // Convert from the corner mask to start and end-points in 3D space,
645        // relative to the cell bounds (i.e. as a `Matrix3<u16>`).
646        for vs in CELL_TO_VERT_TO_EDGES[mask.index()].iter() {
647            for e in *vs {
648                // Find the axis that's being used by this edge
649                let axis = e.start().index() ^ e.end().index();
650                debug_assert_eq!(axis.count_ones(), 1);
651                debug_assert!(axis < 8);
652
653                // Pick a position closer to the filled side
654                let (a, b) = if e.end().index() & axis != 0 {
655                    (0, u16::MAX)
656                } else {
657                    (u16::MAX, 0)
658                };
659
660                // Convert the intersection to a 3D position
661                let mut v = nalgebra::Vector3::zeros();
662                let i = (axis.trailing_zeros() + 1) % 3;
663                let j = (axis.trailing_zeros() + 2) % 3;
664                v[i as usize] = if e.start() & Axis::new(1 << i) {
665                    u16::MAX
666                } else {
667                    0
668                };
669                v[j as usize] = if e.start() & Axis::new(1 << j) {
670                    u16::MAX
671                } else {
672                    0
673                };
674
675                v[axis.trailing_zeros() as usize] = a;
676                start[edge_count] = v;
677                v[axis.trailing_zeros() as usize] = b;
678                end[edge_count] = v;
679                edge_count += 1;
680            }
681        }
682        // Slice off the unused sections of the arrays
683        let start = &mut start[..edge_count]; // always inside
684        let end = &mut end[..edge_count]; // always outside
685
686        // Scratch arrays for edge search
687        const EDGE_SEARCH_SIZE: usize = 16;
688        const EDGE_SEARCH_DEPTH: usize = 4;
689        let xs =
690            &mut [0.0; 12 * EDGE_SEARCH_SIZE][..edge_count * EDGE_SEARCH_SIZE];
691        let ys =
692            &mut [0.0; 12 * EDGE_SEARCH_SIZE][..edge_count * EDGE_SEARCH_SIZE];
693        let zs =
694            &mut [0.0; 12 * EDGE_SEARCH_SIZE][..edge_count * EDGE_SEARCH_SIZE];
695
696        // This part looks hairy, but it's just doing an N-ary search along each
697        // edge to find the intersection point.
698        for _ in 0..EDGE_SEARCH_DEPTH {
699            // Populate edge search arrays
700            let mut i = 0;
701            for (start, end) in start.iter().zip(end.iter()) {
702                for j in 0..EDGE_SEARCH_SIZE {
703                    let pos = ((start.map(|i| i as u32)
704                        * (EDGE_SEARCH_SIZE - j - 1) as u32)
705                        + (end.map(|i| i as u32) * j as u32))
706                        / ((EDGE_SEARCH_SIZE - 1) as u32);
707                    debug_assert!(pos.max() <= u16::MAX.into());
708
709                    let pos = cell.pos(pos.map(|p| p as u16));
710                    xs[i] = pos.x;
711                    ys[i] = pos.y;
712                    zs[i] = pos.z;
713                    i += 1;
714                }
715            }
716            debug_assert_eq!(i, EDGE_SEARCH_SIZE * edge_count);
717
718            // Do the actual evaluation
719            let out = match self.eval_float_slice.eval_raw(
720                eval.f_tape(&mut self.tape_storage),
721                xs,
722                ys,
723                zs,
724                self.world_to_model,
725                ShapeBulkEval::<F::FloatSliceEval>::var_value(self.vars),
726            ) {
727                Ok(v) => v,
728                Err(
729                    ShapeBulkEvalError::MissingVar(..)
730                    | ShapeBulkEvalError::MismatchedVarSlices { .. },
731                ) => unreachable!(),
732            };
733
734            // Update start and end positions based on evaluation
735            for ((start, end), search) in start
736                .iter_mut()
737                .zip(end.iter_mut())
738                .zip(out.chunks(EDGE_SEARCH_SIZE))
739            {
740                // The search must be inside-to-outside
741                debug_assert!(search[0] < 0.0);
742                debug_assert!(search[EDGE_SEARCH_SIZE - 1] >= 0.0);
743                let frac = search
744                    .iter()
745                    .enumerate()
746                    .find(|(_i, v)| **v >= 0.0)
747                    .unwrap()
748                    .0;
749                debug_assert!(frac > 0);
750                debug_assert!(frac < EDGE_SEARCH_SIZE);
751
752                let f = |frac| {
753                    ((start.map(|i| i as u32)
754                        * (EDGE_SEARCH_SIZE - frac - 1) as u32)
755                        + (end.map(|i| i as u32) * frac as u32))
756                        / ((EDGE_SEARCH_SIZE - 1) as u32)
757                };
758
759                let a = f(frac - 1);
760                let b = f(frac);
761
762                debug_assert!(a.max() <= u16::MAX.into());
763                debug_assert!(b.max() <= u16::MAX.into());
764                *start = a.map(|v| v as u16);
765                *end = b.map(|v| v as u16);
766            }
767        }
768
769        // Populate intersections to the average of start and end
770        let intersections: arrayvec::ArrayVec<nalgebra::Vector3<u16>, 12> =
771            start
772                .iter()
773                .zip(end.iter())
774                .map(|(a, b)| {
775                    ((a.map(|v| v as u32) + b.map(|v| v as u32)) / 2)
776                        .map(|v| v as u16)
777                })
778                .collect();
779
780        let xs = &mut [Grad::from(0.0); 12 * EDGE_SEARCH_SIZE]
781            [..intersections.len()];
782        let ys = &mut [Grad::from(0.0); 12 * EDGE_SEARCH_SIZE]
783            [..intersections.len()];
784        let zs = &mut [Grad::from(0.0); 12 * EDGE_SEARCH_SIZE]
785            [..intersections.len()];
786
787        for (i, xyz) in intersections.iter().enumerate() {
788            let pos = cell.pos(*xyz);
789            xs[i] = Grad::new(pos.x, 1.0, 0.0, 0.0);
790            ys[i] = Grad::new(pos.y, 0.0, 1.0, 0.0);
791            zs[i] = Grad::new(pos.z, 0.0, 0.0, 1.0);
792        }
793
794        // TODO: special case for cells with multiple gradients ("features")
795        let grads = match self.eval_grad_slice.eval_raw(
796            eval.g_tape(&mut self.tape_storage),
797            xs,
798            ys,
799            zs,
800            self.world_to_model,
801            ShapeBulkEval::<F::GradSliceEval>::var_value(self.vars),
802        ) {
803            Ok(v) => v,
804            Err(
805                ShapeBulkEvalError::MissingVar(..)
806                | ShapeBulkEvalError::MismatchedVarSlices { .. },
807            ) => unreachable!(),
808        };
809
810        let mut verts: arrayvec::ArrayVec<_, 4> = arrayvec::ArrayVec::new();
811        let mut i = 0;
812        for vs in CELL_TO_VERT_TO_EDGES[mask.index()].iter() {
813            let mut force_point = None;
814            let mut qef = QuadraticErrorSolver::new();
815            for e in vs.iter() {
816                let pos = nalgebra::Vector3::new(xs[i].v, ys[i].v, zs[i].v);
817                let grad: nalgebra::Vector4<f32> = grads[i].into();
818
819                // If a point has invalid gradients, then it's _probably_ on a
820                // sharp feature in the mesh, so we should just snap to that
821                // point specifically.  This means we don't solve the QEF, and
822                // instead mark it as invalid.
823                if grad.iter().any(|f| f.is_nan()) {
824                    force_point = Some(pos);
825                    hermite_cell.qef_err = QEF_ERR_INVALID;
826                    break;
827                }
828                qef.add_intersection(pos, grad);
829
830                // Record this intersection in the Hermite data for the leaf
831                let edge_index = e.to_undirected().index();
832                hermite_cell.intersections[edge_index] = LeafIntersection {
833                    pos: nalgebra::Vector4::new(pos.x, pos.y, pos.z, 1.0),
834                    grad,
835                };
836
837                i += 1;
838            }
839
840            if let Some(pos) = force_point {
841                verts.push(CellVertex { pos });
842            } else {
843                let (pos, err) = qef.solve();
844                verts.push(pos);
845
846                // We overwrite the error here, because it's only used when
847                // collapsing cells, which only occurs if there's a single
848                // vertex; last-error-wins works fine in that case.
849                hermite_cell.qef_err = err;
850            }
851        }
852
853        let index = self.octree.verts.len();
854        self.octree.verts.extend(verts);
855        self.octree.verts.extend(
856            intersections
857                .into_iter()
858                .map(|pos| CellVertex { pos: cell.pos(pos) }),
859        );
860
861        Cell::Leaf(Leaf { mask, index })
862    }
863}
864
865////////////////////////////////////////////////////////////////////////////////
866
867#[derive(Copy, Clone, Default, Debug)]
868struct LeafIntersection {
869    /// Intersection position is xyz; w is 1 if the intersection is present
870    pos: nalgebra::Vector4<f32>,
871    /// Gradient is xyz; w is the distance field value at this point
872    grad: nalgebra::Vector4<f32>,
873}
874
875impl From<LeafIntersection> for QuadraticErrorSolver {
876    fn from(i: LeafIntersection) -> Self {
877        let mut qef = QuadraticErrorSolver::default();
878        if i.pos.w != 0.0 {
879            qef.add_intersection(i.pos.xyz(), i.grad);
880        }
881        qef
882    }
883}
884
885#[derive(Copy, Clone, Debug)]
886pub(crate) struct LeafHermiteData {
887    intersections: [LeafIntersection; 12],
888    face_qefs: [QuadraticErrorSolver; 6],
889    center_qef: QuadraticErrorSolver,
890
891    /// Error found when solving this QEF (if positive), or a special value
892    qef_err: f32,
893}
894
895/// This QEF is not populated
896const QEF_ERR_EMPTY: f32 = -1.0;
897
898/// This QEF is known to be invalid and should be disregarded
899const QEF_ERR_INVALID: f32 = -2.0;
900
901impl Default for LeafHermiteData {
902    fn default() -> Self {
903        Self {
904            intersections: Default::default(),
905            face_qefs: Default::default(),
906            center_qef: Default::default(),
907            qef_err: QEF_ERR_EMPTY,
908        }
909    }
910}
911
912impl LeafHermiteData {
913    /// Merges an octree subdivision of leaf hermite data
914    ///
915    /// Returns `None` if any of the leafs have invalid QEFs (typically due to
916    /// NANs in normal computation).
917    fn merge(leafs: [LeafHermiteData; 8]) -> Option<Self> {
918        let mut out = Self::default();
919        use super::types::{X, Y, Z};
920
921        if leafs.iter().any(|v| v.qef_err == QEF_ERR_INVALID) {
922            return None;
923        }
924
925        // Accumulate intersections along edges
926        for t in [X, Y, Z] {
927            let u = t.next();
928            let v = u.next();
929            for edge in 0..4 {
930                let mut start = Corner::<3>::new(0);
931                if edge & 1 != 0 {
932                    start = start | u;
933                }
934                if edge & 2 != 0 {
935                    start = start | v;
936                }
937                let end = start | t;
938
939                // Canonical edge as a value in the 0-12 range
940                let edge = Edge::new((t.index() * 4 + edge) as u8);
941
942                // One or the other leaf has an intersection
943                let a = leafs[start.index()].intersections[edge.index()];
944                let b = leafs[end.index()].intersections[edge.index()];
945                match (a.pos.w > 0.0, b.pos.w > 0.0) {
946                    (true, false) => out.intersections[edge.index()] = a,
947                    (false, true) => out.intersections[edge.index()] = b,
948                    (false, false) => (),
949                    (true, true) => panic!("duplicate intersection"),
950                }
951            }
952        }
953
954        // Accumulate face QEFs along edges
955        for t in [X, Y, Z] {
956            let u = t.next();
957            let v = t.next();
958            for face in 0..2 {
959                let a = if face == 1 {
960                    t.into()
961                } else {
962                    Corner::<3>::new(0)
963                };
964                let b = a | u;
965                let c = a | v;
966                let d = a | u | v;
967                let f = t.index() * 2 + face;
968                for q in [a, b, c, d] {
969                    out.face_qefs[f] += leafs[q.index()].face_qefs[f];
970                }
971                // Edges oriented along the v axis on this face
972                let edge_index_v = v.index() * 4 + face * 2 + 1;
973                out.face_qefs[f] +=
974                    leafs[a.index()].intersections[edge_index_v].into();
975                out.face_qefs[f] +=
976                    leafs[b.index()].intersections[edge_index_v].into();
977
978                // Edges oriented along the u axis on this face
979                let edge_index_u = v.index() * 4 + face * 2 + 1;
980                out.face_qefs[f] +=
981                    leafs[a.index()].intersections[edge_index_u].into();
982                out.face_qefs[f] +=
983                    leafs[c.index()].intersections[edge_index_u].into();
984            }
985        }
986
987        // Accumulate center QEFs
988        for t in [X, Y, Z] {
989            let u = t.next();
990            let v = t.next();
991
992            // Accumulate the four inner face QEFs
993            let a = Corner::<3>::new(0);
994            let b = a | u;
995            let c = a | v;
996            let d = a | u | v;
997            for q in [a, b, c, d] {
998                out.center_qef += leafs[q.index()].face_qefs[t.index() * 2 + 1];
999            }
1000
1001            // Edges oriented along the u axis
1002            out.center_qef +=
1003                leafs[a.index()].intersections[u.index() * 4 + 3].into();
1004            out.center_qef +=
1005                leafs[b.index()].intersections[u.index() * 4 + 3].into();
1006
1007            // We skip edges oriented on the v axis, because they'll be counted
1008            // by one of the other iterations through the loop
1009        }
1010        for leaf in leafs {
1011            out.center_qef += leaf.center_qef;
1012        }
1013
1014        // Accumulate minimum QEF error among valid child QEFs
1015        out.qef_err = f32::INFINITY;
1016        for e in leafs.iter().map(|q| q.qef_err).filter(|&e| e >= 0.0) {
1017            out.qef_err = out.qef_err.min(e);
1018        }
1019
1020        Some(out)
1021    }
1022
1023    /// Solves the combined QEF
1024    pub fn solve(&self) -> (CellVertex<3>, f32) {
1025        let mut qef = self.center_qef;
1026        for &i in &self.intersections {
1027            qef += i.into();
1028        }
1029        for &f in &self.face_qefs {
1030            qef += f;
1031        }
1032        qef.solve()
1033    }
1034}
1035
1036////////////////////////////////////////////////////////////////////////////////
1037
1038#[cfg(test)]
1039mod test {
1040    use super::*;
1041    use crate::types::{Edge, X, Y, Z};
1042    use fidget_core::{
1043        context::{Context, Tree},
1044        render::ThreadPool,
1045        shape::EzShape,
1046        var::Var,
1047        vm::{VmFunction, VmShape},
1048    };
1049    use std::collections::BTreeMap;
1050
1051    fn depth0_single_thread() -> Settings<'static> {
1052        Settings {
1053            depth: 0,
1054            threads: None,
1055            ..Default::default()
1056        }
1057    }
1058
1059    fn depth1_single_thread() -> Settings<'static> {
1060        Settings {
1061            depth: 1,
1062            threads: None,
1063            ..Default::default()
1064        }
1065    }
1066
1067    /// Converts a [`Tree`] with no extra variables to a [`BoundShape`]
1068    ///
1069    /// # Panics
1070    /// If the tree uses variables
1071    fn tree_to_shape(t: Tree) -> BoundShape<'static, VmFunction, f32> {
1072        VmShape::from(t).try_into().expect("no variables allowed")
1073    }
1074
1075    fn sphere(center: [f32; 3], radius: f32) -> Tree {
1076        let (x, y, z) = Tree::axes();
1077        ((x - center[0]).square()
1078            + (y - center[1]).square()
1079            + (z - center[2]).square())
1080        .sqrt()
1081            - radius
1082    }
1083
1084    fn cube(bx: [f32; 2], by: [f32; 2], bz: [f32; 2]) -> Tree {
1085        let (x, y, z) = Tree::axes();
1086        let x_bounds = (bx[0] - x.clone()).max(x - bx[1]);
1087        let y_bounds = (by[0] - y.clone()).max(y - by[1]);
1088        let z_bounds = (bz[0] - z.clone()).max(z - bz[1]);
1089        x_bounds.max(y_bounds).max(z_bounds)
1090    }
1091
1092    #[test]
1093    fn test_cube_edge() {
1094        const EPSILON: f32 = 1e-3;
1095        let f = 2.0;
1096        let shape = tree_to_shape(cube([-f, f], [-f, 0.3], [-f, 0.6]));
1097        // This should be a cube with a single edge running through the root
1098        // node of the octree, with an edge vertex at [0, 0.3, 0.6]
1099        let octree = Octree::build(&shape, &depth0_single_thread()).unwrap();
1100        assert_eq!(octree.verts.len(), 5);
1101        let v = octree.verts[0].pos;
1102        let expected = nalgebra::Vector3::new(0.0, 0.3, 0.6);
1103        assert!(
1104            (v - expected).norm() < EPSILON,
1105            "bad edge vertex {v:?}; expected {expected:?}"
1106        );
1107    }
1108
1109    fn cone(
1110        corner: nalgebra::Vector3<f32>,
1111        tip: nalgebra::Vector3<f32>,
1112        radius: f32,
1113    ) -> Tree {
1114        let dir = tip - corner;
1115        let length = dir.norm();
1116        let dir = dir.normalize();
1117
1118        let corner = corner.map(|v| Tree::constant(v as f64));
1119        let dir = dir.map(|v| Tree::constant(v as f64));
1120
1121        let (x, y, z) = Tree::axes();
1122        let point = nalgebra::Vector3::new(x, y, z);
1123        let offset = point.clone() - corner.clone();
1124
1125        // a is the distance along the corner-tip direction
1126        let a = offset.x.clone() * dir.x.clone()
1127            + offset.y.clone() * dir.y.clone()
1128            + offset.z.clone() * dir.z.clone();
1129
1130        // Position of the nearest point on the corner-tip axis
1131        let a_pos = corner + dir * a.clone();
1132
1133        // b is the orthogonal distance
1134        let offset = point - a_pos;
1135        let b = (offset.x.clone().square()
1136            + offset.y.clone().square()
1137            + offset.z.clone().square())
1138        .sqrt();
1139
1140        b - radius * (1.0 - a / length)
1141    }
1142
1143    #[test]
1144    fn test_mesh_basic() {
1145        let shape = tree_to_shape(sphere([0.0; 3], 0.2));
1146
1147        // If we only build a depth-0 octree, then it's a leaf without any
1148        // vertices (since all the corners are empty)
1149        let octree = Octree::build(&shape, &depth0_single_thread()).unwrap();
1150        assert!(octree.cells.is_empty()); // root only
1151        assert_eq!(Cell::Empty, octree.root);
1152        assert!(octree.verts.is_empty());
1153
1154        let empty_mesh = octree.walk_dual();
1155        assert!(empty_mesh.vertices.is_empty());
1156        assert!(empty_mesh.triangles.is_empty());
1157
1158        // Now, at depth-1, each cell should be a Leaf with one vertex
1159        let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1160        assert_eq!(octree.cells.len(), 1); // one fanout of 8 cells
1161        assert_eq!(Cell::Branch { index: 0 }, octree.root);
1162
1163        // Each of the 6 edges is counted 4 times and each cell has 1 vertex
1164        assert_eq!(octree.verts.len(), 6 * 4 + 8, "incorrect vertex count");
1165
1166        // Each cell is a leaf with 4 vertices (3 edges, 1 center)
1167        for o in octree.cells[0].iter() {
1168            let Cell::Leaf(Leaf { index, mask }) = *o else {
1169                panic!()
1170            };
1171            assert_eq!(mask.count_ones(), 1);
1172            assert_eq!(index % 4, 0);
1173        }
1174
1175        let sphere_mesh = octree.walk_dual();
1176        assert!(sphere_mesh.vertices.len() > 1);
1177        assert!(!sphere_mesh.triangles.is_empty());
1178    }
1179
1180    #[test]
1181    fn test_sphere_verts() {
1182        let shape = tree_to_shape(sphere([0.0; 3], 0.2));
1183
1184        let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1185        let sphere_mesh = octree.walk_dual();
1186
1187        let mut edge_count = 0;
1188        for v in &sphere_mesh.vertices {
1189            // Edge vertices should be found via binary search and therefore
1190            // should be close to the true crossing point
1191            let x_edge = v.x != 0.0;
1192            let y_edge = v.y != 0.0;
1193            let z_edge = v.z != 0.0;
1194            let edge_sum = x_edge as u8 + y_edge as u8 + z_edge as u8;
1195            assert!(edge_sum == 1 || edge_sum == 3);
1196            if edge_sum == 1 {
1197                assert!(
1198                    (v.norm() - 0.2).abs() < 2.0 / u16::MAX as f32,
1199                    "edge vertex {v:?} is not at radius 0.2"
1200                );
1201                edge_count += 1;
1202            } else {
1203                // The sphere looks like a box at this sampling depth, since the
1204                // edge intersections are all axis-aligned; we'll check that
1205                // corner vertices are all at [±0.2, ±0.2, ±0.2]
1206                assert!(
1207                    (v.abs() - nalgebra::Vector3::new(0.2, 0.2, 0.2)).norm()
1208                        < 2.0 / 65535.0,
1209                    "cell vertex {v:?} is not at expected position"
1210                );
1211            }
1212        }
1213        assert_eq!(edge_count, 6);
1214    }
1215
1216    #[test]
1217    fn test_sphere_manifold() {
1218        let shape = tree_to_shape(sphere([0.0; 3], 0.85));
1219
1220        for threads in [None, Some(&ThreadPool::Global)] {
1221            let settings = Settings {
1222                depth: 5,
1223                threads,
1224                ..Default::default()
1225            };
1226            let octree = Octree::build(&shape, &settings).unwrap();
1227            let sphere_mesh = octree.walk_dual();
1228
1229            check_for_vertex_dupes(&sphere_mesh).unwrap();
1230            check_for_edge_matching(&sphere_mesh).unwrap();
1231        }
1232    }
1233
1234    #[test]
1235    fn test_cube_verts() {
1236        let shape = tree_to_shape(cube([-0.1, 0.6], [-0.2, 0.75], [-0.3, 0.4]));
1237
1238        let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1239        let mesh = octree.walk_dual();
1240        const EPSILON: f32 = 2.0 / u16::MAX as f32;
1241        assert!(!mesh.vertices.is_empty());
1242        for v in &mesh.vertices {
1243            // Edge vertices should be found via binary search and therefore
1244            // should be close to the true crossing point
1245            let x_edge = v.x != 0.0;
1246            let y_edge = v.y != 0.0;
1247            let z_edge = v.z != 0.0;
1248            let edge_sum = x_edge as u8 + y_edge as u8 + z_edge as u8;
1249            assert!(edge_sum == 1 || edge_sum == 3);
1250
1251            if edge_sum == 1 {
1252                assert!(
1253                    (x_edge
1254                        && ((v.x - -0.1).abs() < EPSILON
1255                            || (v.x - 0.6).abs() < EPSILON))
1256                        || (y_edge
1257                            && ((v.y - -0.2).abs() < EPSILON
1258                                || (v.y - 0.75).abs() < EPSILON))
1259                        || (z_edge
1260                            && ((v.z - -0.3).abs() < EPSILON
1261                                || (v.z - 0.4).abs() < EPSILON)),
1262                    "bad edge position {v:?}"
1263                );
1264            } else {
1265                assert!(
1266                    ((v.x - -0.1).abs() < EPSILON
1267                        || (v.x - 0.6).abs() < EPSILON)
1268                        && ((v.y - -0.2).abs() < EPSILON
1269                            || (v.y - 0.75).abs() < EPSILON)
1270                        && ((v.z - -0.3).abs() < EPSILON
1271                            || (v.z - 0.4).abs() < EPSILON),
1272                    "bad vertex position {v:?}"
1273                );
1274            }
1275        }
1276    }
1277
1278    #[test]
1279    fn test_plane_center() {
1280        const EPSILON: f32 = 1e-3;
1281        for dx in [0.0, 0.25, -0.25, 2.0, -2.0] {
1282            for dy in [0.0, 0.25, -0.25, 2.0, -2.0] {
1283                for offset in [0.0, -0.2, 0.2] {
1284                    let (x, y, z) = Tree::axes();
1285                    let f = x * dx + y * dy + z + offset;
1286                    let shape = tree_to_shape(f);
1287                    let octree =
1288                        Octree::build(&shape, &depth0_single_thread()).unwrap();
1289
1290                    assert!(octree.cells.is_empty()); // root only
1291                    let pos = octree.verts[0].pos;
1292                    let mut mass_point = nalgebra::Vector3::zeros();
1293                    for v in &octree.verts[1..] {
1294                        mass_point += v.pos;
1295                    }
1296                    mass_point /= (octree.verts.len() - 1) as f32;
1297                    assert!(
1298                        (pos - mass_point).norm() < EPSILON,
1299                        "bad vertex position at dx: {dx}, dy: {dy}, \
1300                         offset: {offset} => {pos:?} != {mass_point:?}"
1301                    );
1302                    let mut eval = VmShape::new_point_eval();
1303                    let tape = shape.shape().ez_point_tape();
1304                    for v in &octree.verts {
1305                        let v = v.pos;
1306                        let (r, _) = eval.eval(&tape, v.x, v.y, v.z).unwrap();
1307                        assert!(r.abs() < EPSILON, "bad value at {v:?}: {r}");
1308                    }
1309                }
1310            }
1311        }
1312    }
1313
1314    #[test]
1315    fn test_cone_vert() {
1316        // Test both in-cell and out-of-cell cone vertices
1317        for tip in [
1318            nalgebra::Vector3::new(0.2, 0.3, 0.4),
1319            nalgebra::Vector3::new(1.2, 1.3, 1.4),
1320        ] {
1321            let corner = nalgebra::Vector3::new(-1.0, -1.0, -1.0);
1322            let shape = tree_to_shape(cone(corner, tip, 0.1));
1323
1324            let mut eval = VmShape::new_point_eval();
1325            let tape = shape.shape().ez_point_tape();
1326            let (v, _) = eval.eval(&tape, tip.x, tip.y, tip.z).unwrap();
1327            assert!(v.abs() < 1e-6, "bad tip value: {v}");
1328            let (v, _) =
1329                eval.eval(&tape, corner.x, corner.y, corner.z).unwrap();
1330            assert!(v < 0.0, "bad corner value: {v}");
1331
1332            let octree =
1333                Octree::build(&shape, &depth0_single_thread()).unwrap();
1334            assert!(octree.cells.is_empty()); // root only
1335            assert_eq!(octree.verts.len(), 4);
1336
1337            let pos = octree.verts[0].pos;
1338            assert!(
1339                (pos - tip).norm() < 1e-3,
1340                "bad vertex position: expected {tip:?}, got {pos:?}"
1341            );
1342        }
1343    }
1344
1345    fn test_mesh_manifold_inner(threads: Option<&ThreadPool>, mask: u8) {
1346        let mut shape = vec![];
1347        for j in Corner::<3>::iter() {
1348            if mask & (1 << j.index()) != 0 {
1349                shape.push(sphere(
1350                    [
1351                        if j & X { 0.5 } else { 0.0 },
1352                        if j & Y { 0.5 } else { 0.0 },
1353                        if j & Z { 0.5 } else { 0.0 },
1354                    ],
1355                    0.1,
1356                ));
1357            }
1358        }
1359        let Some(start) = shape.pop() else { return };
1360        let shape = shape.into_iter().fold(start, |acc, s| acc.min(s));
1361
1362        // Now, we have our shape, which is 0-8 spheres placed at the
1363        // corners of the cell spanning [0, 0.25]
1364        let shape = tree_to_shape(shape);
1365        let settings = Settings {
1366            depth: 2,
1367            threads,
1368            ..Default::default()
1369        };
1370        let octree = Octree::build(&shape, &settings).unwrap();
1371
1372        let mesh = octree.walk_dual();
1373        if mask != 0 && mask != 255 {
1374            assert!(!mesh.vertices.is_empty());
1375            assert!(!mesh.triangles.is_empty());
1376        }
1377
1378        if let Err(e) = check_for_vertex_dupes(&mesh) {
1379            panic!("mask {mask:08b} has {e}");
1380        }
1381        if let Err(e) = check_for_edge_matching(&mesh) {
1382            panic!("mask {mask:08b} has {e}");
1383        }
1384    }
1385
1386    #[test]
1387    fn test_mesh_manifold_single_thread() {
1388        for mask in 0..=255 {
1389            test_mesh_manifold_inner(None, mask)
1390        }
1391    }
1392
1393    #[test]
1394    fn test_mesh_manifold_multi_thread() {
1395        for mask in 0..=255 {
1396            test_mesh_manifold_inner(Some(&ThreadPool::Global), mask)
1397        }
1398    }
1399
1400    #[test]
1401    fn test_collapsible() {
1402        let shape = tree_to_shape(sphere([0.0; 3], 0.1));
1403        let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1404        assert!(octree.collapsible(0).is_none());
1405
1406        // If we have a single corner sphere, then the builder will collapse the
1407        // branch for us, leaving just a leaf.
1408        let shape = tree_to_shape(sphere([-1.0; 3], 0.1));
1409        let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1410        assert!(matches!(octree.root, Cell::Leaf { .. }));
1411
1412        // Test with a single thread and a deeper tree, which should still work
1413        let octree = Octree::build(
1414            &shape,
1415            &Settings {
1416                depth: 4,
1417                threads: None,
1418                ..Default::default()
1419            },
1420        )
1421        .unwrap();
1422        assert!(
1423            matches!(octree.root, Cell::Leaf { .. }),
1424            "root should be a leaf, not {:?}",
1425            octree.root
1426        );
1427
1428        // This should also work with multiple threads!
1429        let octree = Octree::build(
1430            &shape,
1431            &Settings {
1432                depth: 4,
1433                threads: Some(&ThreadPool::Global),
1434                ..Default::default()
1435            },
1436        )
1437        .unwrap();
1438        assert!(
1439            matches!(octree.root, Cell::Leaf { .. }),
1440            "root should be a leaf, not {:?}",
1441            octree.root
1442        );
1443
1444        let shape = tree_to_shape(sphere([-1.0, 0.0, 1.0], 0.1));
1445        let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1446        assert!(octree.collapsible(0).is_none());
1447
1448        let a = sphere([-1.0; 3], 0.1);
1449        let b = sphere([1.0; 3], 0.1);
1450        let shape = tree_to_shape(a.min(b));
1451        let octree = Octree::build(&shape, &depth1_single_thread()).unwrap();
1452        assert!(octree.collapsible(0).is_none());
1453    }
1454
1455    #[test]
1456    fn test_empty_collapse() {
1457        // Make a very smol sphere that won't be sampled
1458        let shape = tree_to_shape(sphere([0.1; 3], 0.05));
1459
1460        for threads in [None, Some(&ThreadPool::Global)] {
1461            let settings = Settings {
1462                depth: 1,
1463                threads,
1464                ..Default::default()
1465            };
1466            let octree = Octree::build(&shape, &settings).unwrap();
1467            assert_eq!(
1468                octree.root,
1469                Cell::Empty,
1470                "failed to collapse octree with threads: {:?}",
1471                threads.map(|t| t.thread_count())
1472            );
1473        }
1474    }
1475
1476    #[test]
1477    fn test_colonnade_manifold() {
1478        const COLONNADE: &str = include_str!("../../models/colonnade.vm");
1479        let (ctx, root) = Context::from_text(COLONNADE.as_bytes()).unwrap();
1480        let tape = VmShape::new(&ctx, root).unwrap();
1481        let shape = tape.try_into().unwrap();
1482
1483        for threads in [None, Some(&ThreadPool::Global)] {
1484            let settings = Settings {
1485                depth: 5,
1486                threads,
1487                ..Default::default()
1488            };
1489            let octree = Octree::build(&shape, &settings).unwrap();
1490            let mesh = octree.walk_dual();
1491            // Note: the model has duplicate vertices!
1492            if let Err(e) = check_for_edge_matching(&mesh) {
1493                panic!(
1494                    "colonnade model has {e} with threads: {:?}",
1495                    threads.map(|t| t.thread_count())
1496                );
1497            }
1498        }
1499    }
1500
1501    #[test]
1502    fn colonnade_bounds() {
1503        const COLONNADE: &str = include_str!("../../models/colonnade.vm");
1504        let (ctx, root) = Context::from_text(COLONNADE.as_bytes()).unwrap();
1505        let tape = VmShape::new(&ctx, root).unwrap();
1506        let shape = tape.try_into().unwrap();
1507
1508        for threads in [None, Some(&ThreadPool::Global)] {
1509            let settings = Settings {
1510                depth: 8,
1511                threads,
1512                ..Default::default()
1513            };
1514            let octree = Octree::build(&shape, &settings).unwrap();
1515            let mesh = octree.walk_dual();
1516            for v in mesh.vertices.iter() {
1517                assert!(
1518                    v.x < 1.0
1519                        && v.x > -1.0
1520                        && v.y < 1.0
1521                        && v.y > -1.0
1522                        && v.z < 1.0
1523                        && v.z > -0.5,
1524                    "invalid vertex {v:?} with threads {:?}",
1525                    threads.map(|t| t.thread_count())
1526                );
1527            }
1528        }
1529    }
1530
1531    #[test]
1532    fn bear_bounds() {
1533        const COLONNADE: &str = include_str!("../../models/bear.vm");
1534        let (ctx, root) = Context::from_text(COLONNADE.as_bytes()).unwrap();
1535        let tape = VmShape::new(&ctx, root).unwrap();
1536        let shape = tape.try_into().unwrap();
1537
1538        for threads in [None, Some(&ThreadPool::Global)] {
1539            let settings = Settings {
1540                depth: 5,
1541                threads,
1542                ..Default::default()
1543            };
1544            let octree = Octree::build(&shape, &settings).unwrap();
1545            let mesh = octree.walk_dual();
1546            for v in mesh.vertices.iter() {
1547                assert!(
1548                    v.x < 1.0
1549                        && v.x > -0.75
1550                        && v.y < 1.0
1551                        && v.y > -0.75
1552                        && v.z < 0.75
1553                        && v.z > -0.75,
1554                    "invalid vertex {v:?} with threads {:?}",
1555                    threads.map(|t| t.thread_count())
1556                );
1557            }
1558        }
1559    }
1560
1561    fn check_for_vertex_dupes(mesh: &Mesh) -> Result<(), String> {
1562        let mut verts = mesh.vertices.clone();
1563        verts.sort_by_key(|k| (k.x.to_bits(), k.y.to_bits(), k.z.to_bits()));
1564        for i in 1..verts.len() {
1565            if verts[i - 1] == verts[i] {
1566                return Err(format!("duplicate vertices at {}", verts[i]));
1567            }
1568        }
1569        Ok(())
1570    }
1571
1572    fn check_for_edge_matching(mesh: &Mesh) -> Result<(), String> {
1573        let mut edges: BTreeMap<_, usize> = BTreeMap::new();
1574        for t in &mesh.triangles {
1575            for edge in [(t.x, t.y), (t.y, t.z), (t.z, t.x)] {
1576                if t.x == t.y || t.y == t.z || t.x == t.z {
1577                    return Err("triangle with duplicate edges".to_string());
1578                }
1579                *edges.entry(edge).or_default() += 1;
1580            }
1581        }
1582        for (&(a, b), &i) in &edges {
1583            if i != 1 {
1584                return Err(format!(
1585                    "duplicate edge ({a}, {b}) between {:?} {:?}",
1586                    mesh.vertices[a], mesh.vertices[b]
1587                ));
1588            }
1589            if !edges.contains_key(&(b, a)) {
1590                return Err("unpaired edges".to_owned());
1591            }
1592        }
1593        Ok(())
1594    }
1595
1596    #[test]
1597    fn test_qef_merging() {
1598        let mut hermite = LeafHermiteData::default();
1599
1600        // Add a dummy intersection with a non-zero normal; we'll be counting
1601        // mass points to check that the merging went smoothly
1602        let grad = nalgebra::Vector4::new(1.0, 0.0, 0.0, 0.0);
1603        let pos = nalgebra::Vector4::new(0.0, 0.0, 0.0, 1.0);
1604        hermite.intersections.fill(LeafIntersection { pos, grad });
1605
1606        // Ensure that only one of the two sub-edges per edge contains an
1607        // intersection, because that's checked for in `LeafHermiteData::merge`
1608        let mut hermites = [hermite; 8];
1609        for i in 0..12 {
1610            let e = Edge::new(i);
1611            let (_start, end) = e.corners();
1612            hermites[end.index()].intersections[i as usize] =
1613                LeafIntersection::default();
1614        }
1615
1616        let merged = LeafHermiteData::merge(hermites).unwrap();
1617        for i in merged.intersections {
1618            assert_eq!(i.grad, grad);
1619            assert_eq!(i.pos, pos);
1620        }
1621        // Each face in the merged cell should include the accumulation of four
1622        // edges from lower cells (but nothing more, because lower cells didn't
1623        // have face QEFs populated)
1624        for (i, f) in merged.face_qefs.iter().enumerate() {
1625            assert_eq!(
1626                f.mass_point().w,
1627                4.0,
1628                "bad accumulated QEF on face {i}"
1629            );
1630        }
1631        assert_eq!(
1632            merged.center_qef.mass_point().w,
1633            6.0,
1634            "bad accumulated QEF in center"
1635        );
1636    }
1637
1638    #[test]
1639    fn test_qef_near_planar() {
1640        let shape = tree_to_shape(sphere([0.0; 3], 0.75));
1641
1642        let settings = Settings {
1643            depth: 4,
1644            ..Default::default()
1645        };
1646
1647        let octree = Octree::build(&shape, &settings).unwrap().walk_dual();
1648        for v in octree.vertices.iter() {
1649            let n = v.norm();
1650            assert!(n > 0.7 && n < 0.8, "invalid vertex at {v:?}: {n}");
1651        }
1652    }
1653
1654    #[test]
1655    fn test_mesh_vars() {
1656        let (x, y, z) = Tree::axes();
1657        let v = Var::new();
1658        let c = Tree::from(v);
1659        let sphere = (x.square() + y.square() + z.square()).sqrt() - c;
1660        let shape = VmShape::from(sphere);
1661
1662        for threads in [None, Some(&ThreadPool::Global)] {
1663            let settings = Settings {
1664                depth: 4,
1665                threads,
1666                ..Default::default()
1667            };
1668
1669            for r in [0.5, 0.75] {
1670                let mut vars = ShapeVars::new();
1671                vars.insert(v.index().unwrap(), r);
1672                let shape = shape.bind(&vars).unwrap();
1673                let octree =
1674                    Octree::build(&shape, &settings).unwrap().walk_dual();
1675                for v in octree.vertices.iter() {
1676                    let n = v.norm();
1677                    assert!(
1678                        n > r - 0.05 && n < r + 0.05,
1679                        "invalid vertex at {v:?}: {n} != {r} with threads {:?}",
1680                        threads.map(|t| t.thread_count())
1681                    );
1682                }
1683            }
1684        }
1685    }
1686
1687    #[test]
1688    fn test_octree_cancel() {
1689        let (x, y, z) = Tree::axes();
1690        let sphere = (x.square() + y.square() + z.square()).sqrt() - 1.0;
1691        let shape = tree_to_shape(sphere);
1692
1693        for threads in [None, Some(&ThreadPool::Global)] {
1694            let settings = Settings {
1695                depth: 4,
1696                threads,
1697                ..Default::default()
1698            };
1699            let c = settings.cancel.clone();
1700            c.cancel();
1701
1702            assert!(Octree::build(&shape, &settings).is_none());
1703        }
1704    }
1705}