voxcov/
coverage.rs

1// Copyright (C) 2021 Thomas Mulvaney.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16//! Various data structures for calculating voxel coverage.
17//!
18//! # Defining voxel coverage
19//!
20//! We use the same definition as in TEMPy currently.
21//!
22//! A voxel can be described as coordinates and a transformation.
23//! The coordinates are an integer tuple (I, I, I) and the transformation
24//! is an origin plus scale both of which are real numbers (R,R,R).
25//!
26//! When determining whether a voxel is covered, the TEMPy approach is
27//! to check if a point at the center of the voxel is covered (inside
28//! the sphere).  In this case the center is defined as being:
29//!
30//! ```text
31//!   center = (Coord + [0.5, 0.5, 0.5]) * Scale + Translation
32//! ```
33//!
34//! In other words, a map with cell size of (2.2, 2.2, 2.2) and an origin of (0, 0, 0)
35//! the voxel with coordinates (3,3,3) would have have a center at (7.7, 7.7, 7.7).
36//!
37//! Note, that this definition of the center is different to the definition used by
38//! TEMPy when blurring which maps, which defines the center as:
39//!
40//! ```text
41//!   center = Coord * Scale + Translation
42//! ```
43//!
44//! In other words, a map with cell size of (2.2, 2.2, 2.2) and an origin of (0, 0, 0)
45//! the voxel with coordinates (3,3,3) would have have a center at (6.6, 6.6, 6.6).
46//!
47//! # Algorithm
48//!
49//! We define a map as being by recursively partitioning a cube into smaller and smaller
50//! octants until the leaves are the size of voxels.
51//! Because maps aren't always cubic, the starting cube must have a width equal to the
52//! longest dimension of the real map.  This does not incur a space penalty as voxels are not
53//! reified until covered.
54//!
55//! When a sphere is added to a map we determine how an octant is covered.  If it is
56//! not covered, we skip it. If it is fully covered we 'record' this.  If it is partially
57//! covered, we recurse, applying the same steps on the children octants.
58//! How we record the coverage depends on the implementation as outlined below.
59//!
60//! We extend the notion of voxel coverage to the groups of voxels in each quandant.  In
61//! this case a quadrant is fully covered if all voxels in the quadrant are covered, partially
62//! if some of the voxels are covered and not covered if no voxels are covered.
63//! This can be determined trivially by defining a bounding box where each corner is the center
64//! of the eight voxel extremes of the octant and using some simple geometric checks,
65//! avoiding checking every voxel in the octant.
66//!
67//! ## Coverage Map
68//!
69//! When a voxel is covered we set a flag to true.  If all the sibling octants
70//! are now covered, the parent becomes covered, and children are deleted to save space.
71//! This also makes future covering operations faster as larger and larger quadrants
72//! become covered.
73//!
74//! ## Differential coverage Map
75//!
76//! When a voxel is covered a counter is incremented. This allows removing spheres
77//! by decrementing the counter.  There is no procedure for compacting the tree
78//! when all children are covered as the information is required to make removing
79//! spheres possible (we are effectivle counting partial and total coverage for every node).
80
81use crate::geom::{BoundingBox, Coord, Point, Sphere, Transform, Vector};
82use crate::node::{CubeCounter, Node};
83use std::collections::HashMap;
84/// A volume may cover a region of voxels partially, totally or not at all.
85#[derive(PartialEq)]
86pub enum CoverageType {
87    None,
88    Partial,
89    Total,
90    Inside,
91}
92
93#[derive(Clone)]
94pub struct MapRoot<C> {
95    pub root: Node<C>,
96    transform: Transform,
97}
98
99/// All coverage maps implement this trait to allow initialisation.
100pub trait MapLike {
101    fn new(scale: Vector, origin: Point, width: usize) -> Self;
102    fn zero(&mut self);
103}
104
105pub trait AddCoverage {
106    fn add_cov<B: BoxCoverage + Radius>(&mut self, obj: &B, f: &mut dyn FnMut([usize; 3]))
107        -> usize;
108}
109
110pub trait DelCoverage {
111    fn del_cov<B: BoxCoverage + Radius>(&mut self, obj: &B, f: &mut dyn FnMut([usize; 3]))
112        -> usize;
113}
114
115impl AddCoverage for MapRoot<Coverage> {
116    fn add_cov<B: BoxCoverage + Radius>(
117        &mut self,
118        obj: &B,
119        f: &mut dyn FnMut([usize; 3]),
120    ) -> usize {
121        self.root.node_add_cov(&self.transform, obj, f)
122    }
123}
124
125impl AddCoverage for MapRoot<DiffCoverage> {
126    fn add_cov<B: BoxCoverage + Radius>(
127        &mut self,
128        obj: &B,
129        f: &mut dyn FnMut([usize; 3]),
130    ) -> usize {
131        self.root.node_add_cov(&self.transform, obj, f)
132    }
133}
134
135impl DelCoverage for MapRoot<DiffCoverage> {
136    fn del_cov<B: BoxCoverage + Radius>(
137        &mut self,
138        obj: &B,
139        f: &mut dyn FnMut([usize; 3]),
140    ) -> usize {
141        self.root.node_del_cov(&self.transform, obj, f)
142    }
143}
144
145pub trait NodeAddCoverage {
146    fn node_add_cov<B: BoxCoverage + Radius>(
147        &mut self,
148        transform: &Transform,
149        obj: &B,
150        f: &mut dyn FnMut([usize; 3]),
151    ) -> usize;
152}
153
154pub trait NodeDelCoverage {
155    fn node_del_cov<B: BoxCoverage + Radius>(
156        &mut self,
157        transform: &Transform,
158        obj: &B,
159        f: &mut dyn FnMut([usize; 3]),
160    ) -> usize;
161}
162
163impl<C: Default + CoverageCheck> MapLike for MapRoot<C> {
164    fn new(scale: Vector, origin: Point, width: usize) -> MapRoot<C> {
165        Self {
166            root: Node::new([0; 3], width),
167            transform: Transform { origin, scale },
168        }
169    }
170
171    fn zero(&mut self) {
172        self.root.zero();
173    }
174}
175
176/// A coverage map which supports adding and subtracting volumes.
177pub type DiffCoverageMap = MapRoot<DiffCoverage>;
178
179/// A coverage map which just supports adding volumes.
180///
181/// If being able to remove volumes is not important, use this
182/// data structure as insertion is faster and uses less space.
183pub type CoverageMap = MapRoot<Coverage>;
184
185pub struct GridCoverageMap {
186    width: usize,
187    grid_width: usize,
188    transform: Transform,
189    counter_map: HashMap<Coord, CubeCounter>,
190}
191
192impl GridCoverageMap {
193    fn new(scale: Vector, origin: Point, width: usize) -> Self {
194        Self {
195            width,
196            grid_width: 16,
197            transform: Transform { origin, scale },
198            counter_map: HashMap::new(),
199        }
200    }
201
202    fn add_cov<B: BoxCoverage + Radius>(
203        &mut self,
204        sphere: &B,
205        f: &mut dyn FnMut([usize; 3]),
206    ) -> usize {
207        let bb = sphere.bounding_box();
208
209        let lc = self.transform.to_voxel(&bb.lower);
210        let uc = self.transform.to_voxel(&bb.upper);
211
212        let lgx = lc[0] / self.grid_width;
213        let lgy = lc[1] / self.grid_width;
214        let lgz = lc[2] / self.grid_width;
215
216        let ugx = uc[0] / self.grid_width;
217        let ugy = uc[1] / self.grid_width;
218        let ugz = uc[2] / self.grid_width;
219
220        let mut added = 0;
221        for i in lgx..=ugx {
222            for j in lgy..=ugy {
223                for k in lgz..=ugz {
224                    let g = self
225                        .counter_map
226                        .entry([i, j, k])
227                        .or_insert_with(|| CubeCounter::new(self.grid_width));
228                    if !g.is_covered() {
229                        added += g.add_coverage(
230                            &[
231                                i * self.grid_width,
232                                j * self.grid_width,
233                                k * self.grid_width,
234                            ],
235                            self.grid_width,
236                            &self.transform,
237                            &sphere.point(),
238                            sphere.radius(),
239                            f,
240                        );
241                    }
242                }
243            }
244        }
245        added
246    }
247}
248
249impl MapLike for GridCoverageMap {
250    fn new(scale: Vector, origin: Point, width: usize) -> Self {
251        GridCoverageMap::new(scale, origin, width)
252    }
253
254    fn zero(&mut self) {}
255}
256
257impl AddCoverage for GridCoverageMap {
258    fn add_cov<B: BoxCoverage + Radius>(
259        &mut self,
260        obj: &B,
261        f: &mut dyn FnMut([usize; 3]),
262    ) -> usize {
263        GridCoverageMap::add_cov(self, obj, f)
264    }
265}
266
267/// A naive diff coverage map.  This is just a map
268/// of counters for each voxel.  Every time a voxel is covered
269/// or uncovered the counters are incremented and decremented.
270///
271/// This should outperform the other methods when spheres do not
272/// overlap significantly.
273///
274/// Note: This is not a sparse data structure!
275#[derive(Clone)]
276pub struct NaiveDiffCoverageMap {
277    width: usize,
278    // The number of actual voxels touched
279    voxel_counters: CubeCounter,
280    transform: Transform,
281}
282
283impl MapLike for NaiveDiffCoverageMap {
284    fn new(scale: Vector, origin: Point, width: usize) -> Self {
285        Self {
286            width,
287            voxel_counters: CubeCounter::new(width),
288            transform: Transform { origin, scale },
289        }
290    }
291
292    fn zero(&mut self) {
293        self.voxel_counters.zero();
294    }
295}
296
297impl AddCoverage for NaiveDiffCoverageMap {
298    fn add_cov<B: BoxCoverage + Radius>(
299        &mut self,
300        obj: &B,
301        f: &mut dyn FnMut([usize; 3]),
302    ) -> usize {
303        self.voxel_counters.add_coverage(
304            &[0; 3],
305            self.width,
306            &self.transform,
307            &obj.point(),
308            obj.radius(),
309            f,
310        )
311    }
312}
313
314impl DelCoverage for NaiveDiffCoverageMap {
315    fn del_cov<B: BoxCoverage + Radius>(
316        &mut self,
317        obj: &B,
318        f: &mut dyn FnMut([usize; 3]),
319    ) -> usize {
320        self.voxel_counters.del_coverage(
321            &[0; 3],
322            self.width,
323            &self.transform,
324            &obj.point(),
325            obj.radius(),
326            f,
327        )
328    }
329}
330impl Sphere {
331    /// Sphere is inside the box
332    fn inside_coverage(&self, l: &Point, u: &Point) -> bool {
333        self.point[0] - l[0] > self.radius
334            && self.point[1] - l[1] > self.radius
335            && self.point[2] - l[2] > self.radius
336            && u[0] - self.point[0] > self.radius
337            && u[1] - self.point[1] > self.radius
338            && u[2] - self.point[2] > self.radius
339    }
340
341    /// A bounding box is partially covered if any of the sphere is
342    /// within it.
343    fn partial_coverage(&self, l: &Point, u: &Point) -> bool {
344        let mut d = self.radius * self.radius;
345        if self.point[0] < l[0] {
346            d -= f64::powf(self.point[0] - l[0], 2.0);
347        } else if self.point[0] > u[0] {
348            d -= f64::powf(self.point[0] - u[0], 2.0);
349        }
350        if d < 0f64 {
351            return false;
352        }
353
354        if self.point[1] < l[1] {
355            d -= f64::powf(self.point[1] - l[1], 2.0);
356        } else if self.point[1] > u[1] {
357            d -= f64::powf(self.point[1] - u[1], 2.0);
358        }
359
360        if d < 0f64 {
361            return false;
362        }
363
364        if self.point[2] < l[2] {
365            d -= f64::powf(self.point[2] - l[2], 2.0);
366        } else if self.point[2] > u[2] {
367            d -= f64::powf(self.point[2] - u[2], 2.0);
368        }
369
370        d > 0f64
371    }
372
373    /// A bounding box is totally covered if the entire volume is
374    /// inside the sphere.
375    ///
376    /// A bounding box is entirely in the sphere when the farthest
377    /// corner from the center of the sphere is inside.
378    fn total_coverage(&self, l: &Point, u: &Point) -> bool {
379        let mut d = self.radius * self.radius;
380        let p = self.point;
381
382        d -= f64::max(f64::powf(l[0] - p[0], 2f64), f64::powf(u[0] - p[0], 2f64));
383        d -= f64::max(f64::powf(l[1] - p[1], 2f64), f64::powf(u[1] - p[1], 2f64));
384        d -= f64::max(f64::powf(l[2] - p[2], 2f64), f64::powf(u[2] - p[2], 2f64));
385
386        d > 0f64
387    }
388    /// Special case for points.  same idea less computation becuase l == u
389    fn point_coverage(&self, l: &Point) -> bool {
390        let mut d = self.radius * self.radius;
391        let p = self.point;
392
393        d -= f64::powf(l[0] - p[0], 2f64);
394        d -= f64::powf(l[1] - p[1], 2f64);
395        d -= f64::powf(l[2] - p[2], 2f64);
396
397        d > 0f64
398    }
399}
400
401pub trait Radius {
402    fn radius(&self) -> f64;
403    fn point(&self) -> Point;
404    fn bounding_box(&self) -> BoundingBox;
405}
406
407impl BoxCoverage for Sphere {
408    fn box_coverage(&self, bounding_box: &BoundingBox) -> CoverageType {
409        let l = bounding_box.lower;
410        let u = bounding_box.upper;
411
412        // special case for voxels to speed things up.
413        if (l[0] - u[0]).abs() < f64::EPSILON {
414            return if self.point_coverage(&l) {
415                CoverageType::Total
416            } else {
417                CoverageType::None
418            };
419        }
420
421        if self.inside_coverage(&l, &u) {
422            CoverageType::Inside
423        } else if self.total_coverage(&l, &u) {
424            CoverageType::Total
425        } else if self.partial_coverage(&l, &u) {
426            CoverageType::Partial
427        } else {
428            CoverageType::None
429        }
430    }
431    /// A bounding box is partially covered if any of the sphere is
432    /// within it.
433    fn partial_coverage(&self, bounding_box: &BoundingBox) -> bool {
434        let l = bounding_box.lower;
435        let u = bounding_box.upper;
436        let mut d = self.radius * self.radius;
437        if self.point[0] < l[0] {
438            d -= f64::powf(self.point[0] - l[0], 2.0);
439        } else if self.point[0] > u[0] {
440            d -= f64::powf(self.point[0] - u[0], 2.0);
441        }
442        if d < 0f64 {
443            return false;
444        }
445
446        if self.point[1] < l[1] {
447            d -= f64::powf(self.point[1] - l[1], 2.0);
448        } else if self.point[1] > u[1] {
449            d -= f64::powf(self.point[1] - u[1], 2.0);
450        }
451
452        if d < 0f64 {
453            return false;
454        }
455
456        if self.point[2] < l[2] {
457            d -= f64::powf(self.point[2] - l[2], 2.0);
458        } else if self.point[2] > u[2] {
459            d -= f64::powf(self.point[2] - u[2], 2.0);
460        }
461
462        d > 0f64
463    }
464
465    /// A bounding box is totally covered if the entire volume is
466    /// inside the sphere.
467    ///
468    /// A bounding box is entirely in the sphere when the farthest
469    /// corner from the center of the sphere is inside.
470    fn total_coverage(&self, bounding_box: &BoundingBox) -> bool {
471        let l = bounding_box.lower;
472        let u = bounding_box.upper;
473        let mut d = self.radius * self.radius;
474        let p = self.point;
475
476        d -= f64::max(f64::powf(l[0] - p[0], 2f64), f64::powf(u[0] - p[0], 2f64));
477        d -= f64::max(f64::powf(l[1] - p[1], 2f64), f64::powf(u[1] - p[1], 2f64));
478        d -= f64::max(f64::powf(l[2] - p[2], 2f64), f64::powf(u[2] - p[2], 2f64));
479
480        d > 0f64
481    }
482}
483
484impl Radius for Sphere {
485    fn radius(&self) -> f64 {
486        self.radius
487    }
488
489    fn point(&self) -> Point {
490        self.point
491    }
492
493    fn bounding_box(&self) -> BoundingBox {
494        self.bounding_box()
495    }
496}
497
498/// Determines how an object covers a bounding box.
499///
500/// If the volume is a voxel it is either Total, or None.
501/// If the volume is a group of voxels it can be Total, Partial or None.
502pub trait BoxCoverage {
503    fn box_coverage(&self, bounding_box: &BoundingBox) -> CoverageType;
504    fn total_coverage(&self, bounding_box: &BoundingBox) -> bool;
505    fn partial_coverage(&self, bounding_box: &BoundingBox) -> bool;
506}
507
508pub trait CoverageCheck {
509    fn is_covered(&self) -> bool;
510}
511
512#[derive(Default, Clone)]
513pub struct DiffCoverage {
514    partial: usize,
515    coverage: usize,
516}
517
518impl DiffCoverage {
519    fn is_dead(&self) -> bool {
520        !self.is_partial() && !self.is_covered()
521    }
522    fn add_part(&mut self) {
523        self.partial += 1;
524    }
525
526    fn del_part(&mut self) {
527        self.partial -= 1;
528    }
529
530    fn is_partial(&self) -> bool {
531        self.partial > 0
532    }
533
534    fn add_cov(&mut self) {
535        self.coverage += 1;
536    }
537
538    fn del_cov(&mut self) {
539        self.coverage -= 1;
540    }
541}
542
543impl CoverageCheck for DiffCoverage {
544    fn is_covered(&self) -> bool {
545        self.coverage > 0
546    }
547}
548
549#[derive(Default, Clone)]
550pub struct Coverage {
551    covered: bool,
552}
553
554impl Coverage {
555    fn add_cov(&mut self) {
556        self.covered = true;
557    }
558}
559
560impl CoverageCheck for Coverage {
561    fn is_covered(&self) -> bool {
562        self.covered
563    }
564}
565
566impl NodeAddCoverage for Node<DiffCoverage> {
567    fn node_add_cov<B: BoxCoverage + Radius>(
568        &mut self,
569        tf: &Transform,
570        obj: &B,
571        f: &mut dyn FnMut(Coord),
572    ) -> usize {
573        let bounding_box = self.bounding_box(tf);
574        match obj.box_coverage(&bounding_box) {
575            CoverageType::Total => {
576                if !self.coverage.is_covered() {
577                    self.coverage.add_cov();
578                    if let Some(children) = self.children() {
579                        let mut change = 0;
580                        for child in children.iter_mut() {
581                            change += child.node_add_cov(tf, obj, f);
582                        }
583                        return change;
584                    } else if self.width == 1 {
585                        f(self.coord);
586                        return 1;
587                    }
588                }
589                self.coverage.add_cov();
590                0
591            }
592
593            CoverageType::Inside | CoverageType::Partial => {
594                self.coverage.add_part();
595                let children = self
596                    .children()
597                    .expect("Partially covered nodes should have children");
598                let mut change = 0;
599                for child in children.iter_mut() {
600                    change += child.node_add_cov(tf, obj, f);
601                }
602                change
603            }
604
605            CoverageType::None => 0,
606        }
607    }
608}
609
610impl NodeDelCoverage for Node<DiffCoverage> {
611    fn node_del_cov<B: BoxCoverage + Radius>(
612        &mut self,
613        transform: &Transform,
614        obj: &B,
615        f: &mut dyn FnMut(Coord),
616    ) -> usize {
617        let bounding_box = self.bounding_box(transform);
618        match obj.box_coverage(&bounding_box) {
619            CoverageType::Total => {
620                assert!(
621                    self.coverage.is_covered(),
622                    "Deleted a sphere which was never added"
623                );
624
625                self.coverage.del_cov();
626
627                if !self.coverage.is_covered() {
628                    let mut change = 0;
629                    if let Some(children) = self.get_mut_children() {
630                        for child in children.iter_mut() {
631                            change += child.node_del_cov(transform, obj, f);
632                        }
633                        return change;
634                    } else if self.width == 1 {
635                        f(self.coord);
636                        return 1;
637                    } else {
638                        for i in self.coord[0]..self.coord[0] + self.width {
639                            for j in self.coord[1]..self.coord[1] + self.width {
640                                for k in self.coord[2]..self.coord[2] + self.width {
641                                    f([i, j, k]);
642                                }
643                            }
644                        }
645                        return self.width * self.width * self.width;
646                    }
647                }
648            }
649            CoverageType::Inside | CoverageType::Partial => {
650                self.coverage.del_part();
651                if let Some(children) = self.get_mut_children() {
652                    let mut change = 0;
653                    let mut all_dead = true;
654                    for child in children.iter_mut() {
655                        change += child.node_del_cov(transform, obj, f);
656                        if !child.coverage.is_dead() {
657                            all_dead = false;
658                        }
659                    }
660
661                    if all_dead {
662                        self.children = None;
663                    }
664                    return change;
665                }
666            }
667            CoverageType::None => (),
668        }
669        0
670    }
671}
672
673impl Node<Coverage> {
674    /// When a node becomes totally covered by an obj,
675    /// we need to find all its decendents and subtract their
676    /// volume from the total and then remove them.
677    ///
678    /// In the example below, Layer 2 has a node covered by A, then Layer 3 has a node covered by B,
679    /// then layer 1 by C.
680    ///
681    /// Layer 1 calls compact on its children in layer 2. One node is full thus has no children and
682    /// returns 4.  The other node is not full and has children which it calls compact on eventually
683    /// returning 2.
684    ///
685    /// Layer 1        =========== C(8)
686    /// Layer 2   A(4) ===== -----
687    /// Layer 3              -- == B(2)
688    fn fold_children(&self, f: &mut dyn FnMut(Coord)) -> usize {
689        if self.coverage.is_covered() {
690            self.width * self.width * self.width
691        } else {
692            let mut voxels_covered = 0;
693            if let Some(children) = self.get_children() {
694                for child in children.iter() {
695                    voxels_covered += child.fold_children(f);
696                }
697            } else {
698                for i in self.coord[0]..self.coord[0] + self.width {
699                    for j in self.coord[1]..self.coord[1] + self.width {
700                        for k in self.coord[2]..self.coord[2] + self.width {
701                            f([i, j, k]);
702                        }
703                    }
704                }
705            }
706            voxels_covered
707        }
708    }
709
710    /// If all children are covered, delete them and call this node covered
711    fn cleanup(&mut self) {
712        if let Some(children) = self.get_mut_children() {
713            let mut every_covered = true;
714            for child in children.iter_mut() {
715                if !child.coverage.is_covered() {
716                    every_covered = false;
717                    break;
718                }
719            }
720
721            if every_covered {
722                self.children = None;
723                self.coverage.add_cov();
724            }
725        }
726    }
727}
728
729impl NodeAddCoverage for Node<Coverage> {
730    fn node_add_cov<B: BoxCoverage + Radius>(
731        &mut self,
732        transform: &Transform,
733        obj: &B,
734        f: &mut dyn FnMut(Coord),
735    ) -> usize {
736        if self.coverage.is_covered() {
737            return 0;
738        }
739        let bounding_box = self.bounding_box(transform);
740        match obj.box_coverage(&bounding_box) {
741            CoverageType::Total => {
742                let fold = self.fold_children(f);
743                let vol = self.width * self.width * self.width;
744                self.coverage.add_cov();
745                self.children = None;
746                vol - fold
747            }
748
749            CoverageType::Inside | CoverageType::Partial => {
750                let mut change = 0;
751                let children = self
752                    .children()
753                    .expect("Partially covered nodes should have children");
754                for child in children.iter_mut() {
755                    change += child.node_add_cov(transform, obj, f);
756                }
757                self.cleanup();
758                change
759            }
760            CoverageType::None => 0,
761        }
762    }
763}
764
765#[cfg(test)]
766mod tests {
767    use crate::coverage::{
768        AddCoverage, Coverage, CoverageMap, DelCoverage, DiffCoverageMap, MapLike,
769        NaiveDiffCoverageMap, Node, Sphere, Transform,
770    };
771    use approx::relative_eq;
772    use std::collections::HashSet;
773
774    #[test]
775    fn node_to_bounding_box() {
776        // A node (octant) which has a width of 4, (4x4x4 voxels).
777        let node: Node<Coverage> = Node::new([0; 3], 4);
778
779        // If we say that the node has the following transformation to
780        // real coordinates
781        let transform = Transform {
782            origin: [0f64; 3],
783            scale: [2.2; 3],
784        };
785
786        // Then we would expect a bounding box from the center of the lowest
787        // voxel (0,0,0) which is (1.1, 1.1, 1.1) to the center of the upper
788        // voxel which is at (3,3,3) and would have a center at (7.7, 7.7, 7.7)
789        let aabb = node.bounding_box(&transform);
790        relative_eq!(aabb.lower[0], 1.1);
791        relative_eq!(aabb.lower[1], 1.1);
792        relative_eq!(aabb.lower[2], 1.1);
793
794        relative_eq!(aabb.upper[0], 7.7);
795        relative_eq!(aabb.upper[1], 7.7);
796        relative_eq!(aabb.upper[2], 7.7);
797    }
798
799    #[test]
800    fn naive_diff_map() {
801        let mut nmt = NaiveDiffCoverageMap::new([1.0120482f64; 3], [-9.0, 11.0, -18.0], 256);
802        assert_eq!(
803            nmt.add_cov(
804                &Sphere::new([35.319, 51.331, 18.794], 4.675f64),
805                &mut |_| ()
806            ),
807            412
808        );
809        assert_eq!(
810            nmt.add_cov(
811                &Sphere::new([34.624, 50.910, 20.029], 4.675f64),
812                &mut |_| ()
813            ),
814            93
815        );
816    }
817
818    // The numbers used here come from examples run in TEMPy.
819    #[test]
820    fn diff_map() {
821        let mut mt = DiffCoverageMap::new([1.0120482f64; 3], [-9.0, 11.0, -18.0], 256);
822        assert_eq!(
823            mt.add_cov(
824                &Sphere::new([35.319, 51.331, 18.794], 4.675f64),
825                &mut |_| ()
826            ),
827            412
828        );
829
830        assert_eq!(
831            mt.del_cov(
832                &Sphere::new([35.319, 51.331, 18.794], 4.675f64),
833                &mut |_| ()
834            ),
835            412
836        );
837
838        assert_eq!(
839            mt.add_cov(
840                &Sphere::new([34.624, 50.910, 20.029], 4.675f64),
841                &mut |_| ()
842            ),
843            418
844        );
845        assert_eq!(
846            mt.del_cov(
847                &Sphere::new([34.624, 50.910, 20.029], 4.675f64),
848                &mut |_| ()
849            ),
850            418
851        );
852
853        assert_eq!(
854            mt.add_cov(
855                &Sphere::new([35.227, 49.610, 20.593], 4.675f64),
856                &mut |_| ()
857            ),
858            406
859        );
860        assert_eq!(
861            mt.del_cov(
862                &Sphere::new([35.227, 49.610, 20.593], 4.675f64),
863                &mut |_| ()
864            ),
865            406
866        );
867
868        assert_eq!(
869            mt.add_cov(
870                &Sphere::new([35.319, 51.331, 18.794], 4.675f64),
871                &mut |_| ()
872            ),
873            412
874        );
875        assert_eq!(
876            mt.add_cov(
877                &Sphere::new([34.624, 50.910, 20.029], 4.675f64),
878                &mut |_| ()
879            ),
880            93
881        );
882        assert_eq!(
883            mt.del_cov(
884                &Sphere::new([35.319, 51.331, 18.794], 4.675f64),
885                &mut |_| ()
886            ),
887            87
888        );
889        assert_eq!(
890            mt.del_cov(
891                &Sphere::new([34.624, 50.910, 20.029], 4.675f64),
892                &mut |_| ()
893            ),
894            418
895        );
896    }
897
898    #[test]
899    fn mono_map() {
900        let mut mt = CoverageMap::new([1.0120482f64; 3], [-9.0, 11.0, -18.0], 256);
901        assert_eq!(
902            mt.add_cov(
903                &Sphere::new([35.319, 51.331, 18.794], 4.675f64),
904                &mut |_| ()
905            ),
906            412
907        );
908        assert_eq!(
909            mt.add_cov(
910                &Sphere::new([34.624, 50.910, 20.029], 4.675f64),
911                &mut |_| ()
912            ),
913            93
914        );
915        assert_eq!(
916            mt.add_cov(
917                &Sphere::new([35.227, 49.610, 20.593], 4.675f64),
918                &mut |_| ()
919            ),
920            87
921        );
922        mt.add_cov(
923            &Sphere::new([35.227, 49.610, 20.593], 4.675f64),
924            &mut |_| (),
925        );
926    }
927
928    #[test]
929    fn same_output_map() {
930        let mut dif = DiffCoverageMap::new([1.0120482f64; 3], [-9.0, 11.0, -18.0], 256);
931        let mut cov = CoverageMap::new([1.0120482f64; 3], [-9.0, 11.0, -18.0], 256);
932        let mut dif_out = HashSet::new();
933        let mut cov_out = HashSet::new();
934        assert_eq!(
935            dif.add_cov(
936                &Sphere::new([35.319, 51.331, 18.794], 4.675f64),
937                &mut |coord| {
938                    dif_out.insert(coord);
939                }
940            ),
941            412
942        );
943
944        assert_eq!(
945            dif.add_cov(
946                &Sphere::new([34.624, 50.910, 20.029], 4.675f64),
947                &mut |coord| {
948                    dif_out.insert(coord);
949                }
950            ),
951            93
952        );
953        assert_eq!(
954            cov.add_cov(
955                &Sphere::new([35.319, 51.331, 18.794], 4.675f64),
956                &mut |coord| {
957                    cov_out.insert(coord);
958                }
959            ),
960            412
961        );
962
963        assert_eq!(
964            cov.add_cov(
965                &Sphere::new([34.624, 50.910, 20.029], 4.675f64),
966                &mut |coord| {
967                    cov_out.insert(coord);
968                }
969            ),
970            93
971        );
972        assert_eq!(cov_out.len(), dif_out.len());
973    }
974}