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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
//! The one error type.
//!
//! Every public entry point that can reject its input returns
//! [`Result`](crate::Result) rather than panicking. Internal invariants — things
//! that are this crate's own bugs rather than a caller's — stay as
//! `debug_assert!`, which compiles out in release and so cannot abort a shipped
//! build.
//!
//! "Fail loudly" is still the rule; a typed error at the call site is louder
//! than an abort, because the caller can print it, log it, or attach it to the
//! chunk that produced it. What is *not* acceptable is a degraded substitute —
//! no extractor here silently clamps a bad grid or invents a spacing.
//!
//! # Where errors are made impossible instead
//!
//! [`ValidateConfig`](crate::validate::ValidateConfig) has private fields and
//! one checked constructor, so a meaningless threshold cannot be constructed at
//! all and the validator needs no runtime check. Making a state unrepresentable
//! beats reporting it.
use core::fmt;
/// What went wrong.
///
/// Every variant carries the numbers that produced it, so the message is
/// actionable without reaching for a debugger.
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum Error {
/// A grid whose sample count does not fit in `u32`.
///
/// A silent wrap here would alias distinct cells onto the same index and
/// produce a mesh that is self-consistent and simply not the field's — which
/// no validity test can catch.
ShapeOverflow {
/// The requested size.
size: [u32; 3],
/// What `sx * sy * sz` actually comes to.
product: u64,
},
/// Fewer than two samples on some axis, so there is no cell to extract from.
GridTooSmall {
/// The requested size.
size: [u32; 3],
},
/// The output would need more vertices than a `u32` index can address.
IndexSpaceExhausted {
/// An upper bound on the vertices the extraction could produce.
needed: u64,
},
/// A cell size that is not finite and positive.
///
/// Thresholds in this crate are relative to the grid spacing, so a
/// meaningless spacing makes every threshold meaningless with it.
InvalidCellSize {
/// The value given.
value: f64,
},
/// A weld tolerance that is not finite and positive.
///
/// Zero is rejected rather than treated as "weld only exact matches",
/// because M-32 measured that seam vertices are bit-identical only for
/// power-of-two cell sizes — an exact-match weld is precisely the thing that
/// works on the fixture and fails in the field.
InvalidWeldEpsilon {
/// The value given.
value: f64,
},
/// A spacing that does not describe the mesh it was given.
///
/// Reported rather than absorbed: a broadphase grid finer than the mesh
/// would grow until it exhausted memory, and guessing a better spacing would
/// silently change what the measurement means.
CellSizeMismatch {
/// Index of the triangle that triggered it.
triangle: u64,
/// How many grid cells that triangle's bounding box spans.
cells: u128,
/// The spacing given.
cell_size: f64,
},
/// A sweep whose bilinear denominator vanishes on one of its two faces.
///
/// `A + C - B - D` being zero means that face's bilinear function has no
/// saddle point, so
/// [`SweptFaces::test`](crate::marching_cubes::interior::SweptFaces::test)'s
/// criterion -- *is there a cutting plane whose saddle is positive* -- has
/// no answer there rather than a hard one.
///
/// It cannot happen on an ambiguous face, where one diagonal is strictly
/// negative and the other non-negative. Reported rather than defaulted for
/// that reason: reaching it means the interior test was applied to a face
/// that is not ambiguous, and a default would hide the caller's mistake
/// behind a plausible topology.
DegenerateSweep,
/// A vertex whose normal cannot be derived, so there is nothing to normalise.
///
/// A zero or non-finite field gradient, or — under
/// [`AreaWeightedFaces`](crate::normals::NormalStrategy::AreaWeightedFaces) —
/// a vertex whose incident triangles have no area, or which no triangle
/// references at all.
///
/// Reported rather than substituted. Writing `[0, 0, 1]` there gives a mesh
/// that renders and is wrong in a way nothing downstream can attribute, which
/// is exactly the class of bug the one-path rule exists to prevent.
DegenerateNormal {
/// Index of the offending vertex.
vertex: u64,
},
/// Subgrid Marching Tetrahedra met a tetrahedron it could not triangulate.
///
/// Every case §3.2 defines is implemented, so this is not "an unsupported
/// configuration" — it is a defect, and it names the cell and tetrahedron so
/// the offending edge coordinates can be recovered. Reported rather than
/// skipped: a silently dropped tetrahedron is a hole in the mesh, and a hole
/// is indistinguishable from the thin feature this extractor exists to
/// resolve.
SubgridUnfilled {
/// The cell whose tetrahedron failed, in grid coordinates.
cell: [u32; 3],
/// Which of the six tetrahedra of that cell.
tet: u8,
/// What [`fill`](crate::subgrid::surface::fill) reported, as its
/// `Debug` form.
reason: &'static str,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ShapeOverflow { size, product } => write!(
f,
"grid {}x{}x{} has {product} samples, which does not fit in u32",
size[0], size[1], size[2]
),
Self::GridTooSmall { size } => write!(
f,
"grid {}x{}x{} has an axis with fewer than two samples, so it contains no cell",
size[0], size[1], size[2]
),
Self::IndexSpaceExhausted { needed } => write!(
f,
"extraction could need {needed} vertices, beyond the u32 index space"
),
Self::InvalidCellSize { value } => {
write!(f, "cell size must be finite and positive, got {value}")
}
Self::InvalidWeldEpsilon { value } => {
write!(f, "weld epsilon must be finite and positive, got {value}")
}
Self::CellSizeMismatch {
triangle,
cells,
cell_size,
} => write!(
f,
"triangle {triangle} spans {cells} grid cells at cell size {cell_size}; \
that spacing does not describe this mesh"
),
Self::DegenerateSweep => write!(
f,
"a swept face has a zero bilinear denominator, so it has no saddle point \
and the interior test has nothing to evaluate"
),
Self::DegenerateNormal { vertex } => write!(
f,
"vertex {vertex} has no normal to derive: a zero gradient, or no incident area"
),
Self::SubgridUnfilled { cell, tet, reason } => write!(
f,
"subgrid marching tetrahedra could not fill tetrahedron {tet} of cell \
[{}, {}, {}]: {reason}",
cell[0], cell[1], cell[2]
),
}
}
}
impl core::error::Error for Error {}
/// This crate's result type.
pub type Result<T> = core::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
/// Every variant has to say what happened *and* with which numbers, or the
/// error is a worse version of a panic.
#[test]
fn messages_carry_their_numbers() {
let cases = [
(
Error::ShapeOverflow {
size: [70000, 70000, 2],
product: 9_800_000_000,
},
"9800000000",
),
(Error::GridTooSmall { size: [1, 4, 4] }, "1x4x4"),
(
Error::IndexSpaceExhausted {
needed: 5_000_000_000,
},
"5000000000",
),
(Error::InvalidCellSize { value: 0.0 }, "got 0"),
(Error::InvalidWeldEpsilon { value: -1.0 }, "got -1"),
(
Error::CellSizeMismatch {
triangle: 7,
cells: 900,
cell_size: 0.001,
},
"900",
),
];
for (error, expected) in cases {
let text = format!("{error}");
assert!(text.contains(expected), "{error:?} rendered as {text:?}");
}
}
#[test]
fn errors_compare_by_value() {
assert_eq!(
Error::GridTooSmall { size: [1, 2, 2] },
Error::GridTooSmall { size: [1, 2, 2] }
);
assert_ne!(
Error::GridTooSmall { size: [1, 2, 2] },
Error::GridTooSmall { size: [2, 1, 2] }
);
}
}