1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! A grid's metadata and bounding box (ARCHITECTURE.md §4.7, ADR-0016): what a file stores
//! beside a grid's topology and values.
use std::collections::BTreeMap;
use crate::error::{MetaError, TreeError};
use crate::topology::Topology;
use crate::tree::Coord;
/// The key in `GridMeta::extra` that marks a grid as a narrow-band SDF written to brodo's
/// convention, docs/formats.md §5 (ADR-0039, ADR-0053). Its value is the convention's
/// version.
pub const SDF_KEY: &str = "brodo.sdf.version";
/// The version of the SDF convention this brodo states.
pub const SDF_VERSION: &str = "2";
/// What a file stores of a grid beside its topology and values.
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct GridMeta {
/// The grid's name.
pub name: String,
/// A voxel's size along x, y and z, in `unit`, as its writer states it: the transform
/// alone places the voxels, and nothing checks that the two agree.
pub voxel_size: [f64; 3],
/// The unit of length, a non-empty string brodo does not interpret.
pub unit: String,
/// From voxel to world coordinates: the rows of a 3×4 affine map.
pub transform: [[f64; 4]; 3],
/// Free string pairs.
pub extra: BTreeMap<String, String>,
}
impl GridMeta {
/// Metadata whose transform scales by the voxel size, with no rotation or translation.
pub fn new(name: &str, voxel_size: [f64; 3], unit: &str) -> Result<Self, MetaError> {
let [x, y, z] = voxel_size;
let meta = Self {
name: name.to_owned(),
voxel_size,
unit: unit.to_owned(),
transform: [[x, 0.0, 0.0, 0.0], [0.0, y, 0.0, 0.0], [0.0, 0.0, z, 0.0]],
extra: BTreeMap::new(),
};
meta.check()?;
Ok(meta)
}
/// Refuses an empty unit, and a voxel size or transform entry that is not finite or,
/// for the size, not positive.
pub fn check(&self) -> Result<(), MetaError> {
if self.unit.is_empty() {
return Err(MetaError::EmptyUnit);
}
if !self.voxel_size.iter().all(|s| s.is_finite() && *s > 0.0) {
return Err(MetaError::BadVoxelSize);
}
if !self.transform.iter().flatten().all(|t| t.is_finite()) {
return Err(MetaError::BadTransform);
}
Ok(())
}
}
/// The voxels a grid's leaves and tiles cover.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Bounds {
/// No leaf or tile: every voxel is the background.
Empty,
/// The smallest and largest voxel covered, inclusive.
Box {
/// The smallest voxel along each axis.
min: Coord,
/// The largest voxel along each axis.
max: Coord,
},
}
impl Bounds {
/// What the topology's leaves and tiles cover.
pub fn of(t: &Topology) -> Result<Bounds, TreeError> {
let mut cubes = Vec::new();
for n in t.leaf_numbers() {
cubes.push((t.leaf_origin(n)?, 7));
}
for n in t.tile_numbers() {
let (level, o) = t.tile(n)?;
cubes.push((o, level.edge() - 1));
}
Ok(cubes.into_iter().fold(Bounds::Empty, |b, (o, e)| b.with_cube(o, e)))
}
/// These bounds grown to hold the cube at `o` of edge `e` + 1.
pub(crate) fn with_cube(self, o: Coord, e: i32) -> Bounds {
let far = o.map(|c| c + e);
match self {
Bounds::Empty => Bounds::Box { min: o, max: far },
Bounds::Box { min, max } => Bounds::Box {
min: [0, 1, 2].map(|k| min[k].min(o[k])),
max: [0, 1, 2].map(|k| max[k].max(far[k])),
},
}
}
}