brodo 1.0.0

Sparse volumes in pure Rust: a VDB-style sparse tree, fields that share a topology, and zero-copy grid files
Documentation
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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
//! The errors of brodo's operations (CODING.md Rule 8).

use crate::field::ValueType;
use crate::topology::Location;
use crate::tree::{Coord, TileLevel};

/// The kinds of node a tree keeps in arenas.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeKind {
    /// Internal nodes of 32³ children.
    Upper,
    /// Internal nodes of 16³ children.
    Lower,
    /// Leaves of 8³ voxels.
    Leaf,
}

/// Why an edit of the tree was refused.
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum TreeError {
    /// No tile contains the voxel to split or remove at.
    #[error("no tile contains voxel {0:?}")]
    NotATile(Coord),
    /// A tile holds the voxel to deactivate; it must be split or removed first (ADR-0025).
    #[error("a {level:?} tile holds voxel {voxel:?}; split or remove it before deactivating")]
    InTile {
        /// The tile's level.
        level: TileLevel,
        /// The voxel given.
        voxel: Coord,
    },
    /// The region a tile was to fill already holds a tile, a node or a leaf, or lies in a
    /// tile.
    #[error("the {level:?} region containing voxel {voxel:?} is not empty")]
    Occupied {
        /// The level of the tile asked for.
        level: TileLevel,
        /// The voxel given.
        voxel: Coord,
    },
    /// An arena would hold more nodes than a `u32` counts.
    #[error("more {0:?} nodes than a u32 counts")]
    ArenaFull(NodeKind),
    /// A leaf number the tree or topology did not give.
    #[error("no leaf numbered {0}")]
    UnknownLeaf(u32),
    /// A tile number the topology did not give.
    #[error("no tile numbered {0}")]
    UnknownTile(u32),
    /// More tiles than a `u32` numbers.
    #[error("more tiles than a u32 numbers")]
    TooManyTiles,
    /// More merged parts in one commit than a `u32` numbers.
    #[error("more merged parts than a u32 numbers")]
    TooManyParts,
    /// No child holds the voxel where a merge into a tile at this level was asked for.
    #[error("nothing to merge into a {level:?} tile at voxel {voxel:?}")]
    NotMergeable {
        /// The level of the tile asked for.
        level: TileLevel,
        /// The voxel given.
        voxel: Coord,
    },
    /// The node to merge holds a place with nothing in it (ADR-0014).
    #[error("the node merging into a {level:?} tile at voxel {voxel:?} holds an empty place")]
    HoldsEmpty {
        /// The level of the tile asked for.
        level: TileLevel,
        /// The voxel given.
        voxel: Coord,
    },
    /// Merging into a tile at this level is not built (ADR-0014).
    #[error("merging into a {0:?} tile is not built")]
    MergeUnsupported(TileLevel),
}

/// Why a field refused an access or a remap.
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum FieldError {
    /// The field belongs to another topology: it was not remapped after a commit, or the
    /// remap is not from its topology (ADR-0014).
    #[error("the field belongs to another topology")]
    StaleField,
    /// A leaf number its topology did not give.
    #[error("no leaf numbered {0}")]
    UnknownLeaf(u32),
    /// A tile number its topology did not give.
    #[error("no tile numbered {0}")]
    UnknownTile(u32),
    /// The merge rule refused the values of the new tile with this number.
    #[error("the merge rule refused new tile {0}")]
    MergeRefused(u32),
    /// A write to a voxel no leaf holds: values change only in leaves (ADR-0015).
    #[error("voxel {voxel:?} is not in a leaf but in {location:?}")]
    NotALeaf {
        /// The voxel written.
        voxel: Coord,
        /// What holds it.
        location: Location,
    },
}

/// Why an edit of a grid was refused.
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum GridError {
    /// The edit of the topology was refused.
    #[error(transparent)]
    Tree(#[from] TreeError),
    /// The field could not be remapped.
    #[error(transparent)]
    Field(#[from] FieldError),
}

/// Why an analysis failed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum AnalysisError {
    /// The field does not belong to the topology.
    #[error(transparent)]
    Field(#[from] FieldError),
    /// The topology refused a lookup by number.
    #[error(transparent)]
    Tree(#[from] TreeError),
    /// The root's keys are spread so widely that the compressed root grid would be too
    /// large.
    #[error("the root's keys are too spread out to label")]
    TooSpread,
    /// More units than a `u32` numbers.
    #[error("too many voxels, tiles and empty regions to label")]
    TooLarge,
    /// A voxel no unit stands for: the labels and topology disagree.
    #[error("voxel {0:?} has no unit")]
    Unlabelled(Coord),
    /// The voxels meeting the condition include the background, so are unbounded and have
    /// no Euler characteristic (ADR-0024).
    #[error("the background meets the condition: the set is unbounded")]
    Unbounded,
}

/// Why a grid could not be frozen.
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum FreezeError {
    /// The field does not belong to the topology.
    #[error(transparent)]
    Field(#[from] FieldError),
    /// The topology refused a lookup by number.
    #[error(transparent)]
    Tree(#[from] TreeError),
    /// A count or offset does not fit the layout's `u32` words, or a value its header's
    /// background: a buffer is at most 2³² blocks of 32 bytes, 128 GiB, some 60 million
    /// leaves of `f32` (docs/formats.md §2).
    #[error("the grid is too large for the frozen layout")]
    TooLarge,
}

/// Why bytes were refused as a frozen grid (ARCHITECTURE.md §4.6).
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum FormatError {
    /// The bytes do not start on a 32-byte boundary, or are not whole blocks.
    #[error("the bytes are not 32-byte aligned whole blocks")]
    Misaligned,
    /// Too few bytes for the header or a section.
    #[error("the bytes end before the {0} section does")]
    Truncated(&'static str),
    /// Not a frozen grid.
    #[error("magic number {0:#010x} is not a frozen grid's")]
    BadMagic(u32),
    /// A layout version this build does not read (ADR-0016).
    #[error("version {0} is not read by this build")]
    UnsupportedVersion(u32),
    /// The values are of a type no brodo type code names.
    #[error("the values are of type {0}, which this brodo does not know")]
    UnknownValueType(u32),
    /// The values are of another type.
    #[error("the values are of type {found}, not {expected}")]
    WrongValueType {
        /// The type asked for.
        expected: ValueType,
        /// The type found.
        found: ValueType,
    },
    /// A count, offset or index points outside its section, or contradicts another.
    #[error("the {0} section is inconsistent")]
    Inconsistent(&'static str),
    /// A topology asked to hold a view's values does not hold the view's leaves and tiles.
    #[error("the topology does not hold the view's leaves and tiles")]
    OtherTopology,
    /// A region's view was asked for a voxel outside its box, or a leaf that does not meet
    /// it (docs/formats.md §8.6).
    #[error("the voxel or leaf lies outside the region read")]
    OutsideRegion,
}

/// Why a grid's metadata was refused.
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum MetaError {
    /// The unit is empty: a file always stores its physical size (ADR-0016).
    #[error("the unit is empty")]
    EmptyUnit,
    /// A voxel size is not finite and positive.
    #[error("a voxel size is not finite and positive")]
    BadVoxelSize,
    /// A transform entry is not finite.
    #[error("a transform entry is not finite")]
    BadTransform,
}

/// Why a cache file could not be written or read (ARCHITECTURE.md §4.7).
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CacheError {
    /// Reading or writing the file failed.
    #[error(transparent)]
    Io(#[from] std::io::Error),
    /// A grid could not be frozen.
    #[error(transparent)]
    Freeze(#[from] FreezeError),
    /// A grid's metadata was refused.
    #[error(transparent)]
    Meta(#[from] MetaError),
    /// The topology refused a lookup by number.
    #[error(transparent)]
    Tree(#[from] TreeError),
    /// Not a file of the kind read: a cache file, or an archive.
    #[error("magic number {0:#010x} is not that of the kind of file read")]
    BadMagic(u32),
    /// A format version this build does not read (ADR-0016).
    #[error("version {0} is not read by this build")]
    UnsupportedVersion(u32),
    /// The file ends before a part of it does.
    #[error("the file ends before its {0} does")]
    Truncated(&'static str),
    /// A part of the file contradicts another, or overlaps one.
    #[error("the file's {0} is inconsistent")]
    Inconsistent(&'static str),
    /// A string is not UTF-8.
    #[error("a string in the {0} is not UTF-8")]
    BadText(&'static str),
    /// A grid's frozen buffer was refused.
    #[error("grid {index}: {error}")]
    Grid {
        /// The grid's place in the file.
        index: usize,
        /// Why its buffer was refused.
        error: FormatError,
    },
    /// No grid has this place in the file.
    #[error("no grid numbered {0}")]
    NoGrid(usize),
    /// A length does not fit the format's `u32` fields: a file is at most 2³² blocks of 32
    /// bytes, 128 GiB (docs/formats.md §3).
    #[error("the file is too large for its u32 fields")]
    TooLarge,
    /// The file is longer than the reader's limit, and was not read beyond it, or an
    /// archive would decompress to a cache file longer than it, and was not (ADR-0036).
    #[error("the file holds {length} bytes or more, more than the limit of {limit}")]
    FileTooLarge {
        /// The file's length, or, where it reported less, the bytes read before the limit
        /// stopped the reading.
        length: u64,
        /// The limit given.
        limit: u64,
    },
    /// An archive's chunk does not decompress to the length its table states.
    #[cfg(feature = "archive")]
    #[error("grid {index}'s chunk {chunk} does not decompress to its length")]
    Decompress {
        /// The grid's place in the file.
        index: usize,
        /// The chunk's place in the grid's table.
        chunk: usize,
    },
    /// An archive's grid is not cut into chunks as its writer cuts it (docs/formats.md §8.3).
    #[cfg(feature = "archive")]
    #[error("grid {0} is not cut into chunks as the format cuts it")]
    Chunks(usize),
    /// An archive's value encoding this build does not know (docs/formats.md §8.5).
    #[cfg(feature = "archive")]
    #[error("value encoding {0} is not read by this build")]
    UnknownEncoding(u32),
    /// An entry asked to be quantised holds values of a type that is not, or bytes: only
    /// floating values are (docs/formats.md §8.5).
    #[cfg(feature = "archive")]
    #[error("entry {index}'s values, of type {}, are not quantised", value_type.map_or("bytes", ValueType::name))]
    NotQuantised {
        /// The entry's place in what was written.
        index: usize,
        /// Its value type; none for an entry of bytes.
        value_type: Option<ValueType>,
    },
    /// A grid's leaf value is one its encoding cannot hold: a NaN, a value beyond ±65504 in
    /// binary16, an infinity or a range wider than an `f64` holds in 8 bits.
    #[cfg(feature = "archive")]
    #[error("grid {0} holds a leaf value its encoding cannot hold")]
    Unrepresentable(usize),
    /// Thawing a grid is counted to make more bytes than the limit given
    /// (`GridEntry::thaw`), and it was not thawed.
    #[error("grid {index} would thaw to {need} bytes or more, more than the limit of {limit}")]
    ThawTooLarge {
        /// The grid's place in the file.
        index: usize,
        /// The bytes thawing it is counted to make (`GridEntry::thaw_size`).
        need: u64,
        /// The limit given.
        limit: u64,
    },
    /// The metadata holds a key beginning `brodo.`, which `CacheEntry::new` leaves to
    /// brodo's formats built on the cache profile.
    #[error("the metadata holds the key {0:?}, which brodo's formats reserve")]
    ReservedKey(String),
}

/// Which rule of the Hermite data format a set breaks (ADR-0032).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Rule {
    /// A sign other than 0 or 1, or a background sign other than 0.
    Sign,
    /// An edge holding a crossing where its ends' signs agree, or none where they differ.
    NoCrossing,
    /// A crossing's fraction outside [0, 1].
    Fraction,
    /// A crossing's normal not of unit length.
    NormalLength,
    /// A crossing's normal pointing from the outside end to the inside end.
    NormalInward,
    /// A crossing's group with no name in the table.
    UnnamedGroup,
    /// Signs that differ across an edge whose owner is not a leaf point.
    Owner,
}

/// Why a set of Hermite data was refused (ARCHITECTURE.md §4.9, ADR-0032).
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum HermiteError {
    /// A field refused an access.
    #[error(transparent)]
    Field(#[from] FieldError),
    /// The topology refused a lookup by number.
    #[error(transparent)]
    Tree(#[from] TreeError),
    /// The cache file refused a read, or an entry could not be made.
    #[error(transparent)]
    Cache(#[from] CacheError),
    /// A frozen grid refused a read.
    #[error(transparent)]
    Format(FormatError),
    /// The set breaks a rule of the format, at a point, on an axis's edge, or at the
    /// background when neither is given.
    #[error("{rule:?} broken at {at:?}, axis {axis:?}")]
    Violation {
        /// The point, or the tile's origin; none for the background.
        at: Option<Coord>,
        /// The axis of the edge, when an edge's values break it.
        axis: Option<crate::tree::Axis>,
        /// The rule broken.
        rule: Rule,
    },
    /// A field of the set is on another topology.
    #[error("the {0} field is on another topology")]
    OtherTopology(&'static str),
    /// A field of the set is not in the file.
    #[error("no {0} grid in the set")]
    Missing(&'static str),
    /// A field of the set is in the file twice.
    #[error("two {0} grids in the set")]
    Twice(&'static str),
    /// A grid names a field the format does not.
    #[error("a grid names field {0:?}, which the format does not")]
    UnknownField(String),
    /// A grid states a format version this build does not read.
    #[error("version {0} is not read by this build")]
    UnsupportedVersion(String),
    /// A group's name is empty, repeated, or missing from the ids' run, or group 0 is named.
    #[error("group {0} has no name of its own")]
    BadGroupName(u32),
    /// A group key whose id is not a number from 1 written plainly.
    #[error("bad group key {0:?}")]
    BadGroupKey(String),
    /// The metadata given for the entries holds a key beginning `brodo.`, which brodo's
    /// formats reserve, or an entry of the set's crossings holds one of the format's
    /// besides its own.
    #[error("the metadata already holds the reserved key {0:?}")]
    ReservedKey(String),
    /// The metadata given for the entries states the SDF convention, `SDF_KEY`: a set
    /// written with it would be taken for an SDF.
    #[error("the metadata states the SDF convention, which a Hermite set does not keep")]
    ClaimsSdf,
    /// A set's crossings were built out of the builder's order: leaves in number order,
    /// each once, places below 512 and ascending on each axis (`CrossingsBuilder`).
    #[error("the crossings were built with {0}")]
    Build(&'static str),
}

/// Why a slice could not be drawn or encoded (ARCHITECTURE.md §6.1).
#[cfg(feature = "slice-png")]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SliceError {
    /// The field does not belong to the topology.
    #[error(transparent)]
    Field(#[from] FieldError),
    /// The range drawn holds no pixel.
    #[error("the slice's range holds no pixel")]
    EmptyRange,
    /// The range drawn is wider or higher than a `u32` counts, or its image does not fit in
    /// memory.
    #[error("the slice's range holds more pixels than an image can")]
    TooLarge,
    /// The colour map's range is not finite, or is empty (ARCHITECTURE.md §6.1).
    #[error("the colour map {0:?} has no finite, non-empty range")]
    BadColorMap(crate::slice::ColorMap),
    /// The PNG encoder refused the image: its own error inside, by `io::Error::other`, so
    /// that the encoder's crate is not a public dependency.
    #[error(transparent)]
    Encode(std::io::Error),
    /// Writing the file failed.
    #[error(transparent)]
    Io(#[from] std::io::Error),
}

/// Why grids could not be read or written as an OpenVDB file (ARCHITECTURE.md §4.7, ADR-0046).
#[cfg(feature = "vdb")]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum VdbError {
    /// Reading or writing the file failed.
    #[error(transparent)]
    Io(#[from] std::io::Error),
    /// A grid's metadata was refused.
    #[error(transparent)]
    Meta(#[from] MetaError),
    /// The field does not belong to the topology.
    #[error(transparent)]
    Field(#[from] FieldError),
    /// The topology refused a lookup by number.
    #[error(transparent)]
    Tree(#[from] TreeError),
    /// Two grids share a name, by which OpenVDB tells a file's grids apart.
    #[error("two grids are named {0:?}")]
    DuplicateName(String),
    /// A count, length or offset does not fit the format's field for it.
    #[error("the {0} does not fit the file format")]
    TooLarge(&'static str),
    /// A file version this build does not read (docs/formats.md §9.7).
    #[error("version {0} is not read by this build")]
    UnsupportedVersion(u32),
    /// The file holds what brodo does not read, named (docs/formats.md §9.7, ADR-0047).
    #[error("{0} is not read")]
    Unsupported(&'static str),
    /// A grid's transform is a map brodo does not read, named.
    #[error("the map {0:?} is not read")]
    Map(String),
    /// The file breaks docs/formats.md §9.7 or §9.8 at the part named.
    #[error("the file is malformed at its {0}")]
    Malformed(&'static str),
    /// The file is longer than the limit it was read under, and was not read beyond it, or
    /// its grids would make more than it, and were not (ARCHITECTURE.md §4.7).
    #[error("the file holds or makes {length} bytes or more, more than the limit of {limit}")]
    FileTooLarge {
        /// The file's length, or the bytes read past the limit, or what its grids were
        /// counted to make when the count passed the limit.
        length: u64,
        /// The limit.
        limit: u64,
    },
}

/// Why a field could not be written as `.vtu` (ARCHITECTURE.md §6.1).
#[cfg(feature = "vtk")]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum VtkError {
    /// The field does not belong to the topology.
    #[error(transparent)]
    Field(#[from] FieldError),
    /// The topology refused a lookup by number.
    #[error(transparent)]
    Tree(#[from] TreeError),
    /// The name is empty, or holds a character other than printable ASCII and space, or one
    /// of `<>&"'`.
    #[error("the name {0:?} is not one or more printable ASCII characters, none of <>&\"'")]
    Name(String),
    /// A cell's value is infinite, which VTK does not read back as written (ADR-0018).
    #[error("the cell at {origin:?} holds an infinite value, which VTK reads back wrongly")]
    Infinite {
        /// The cell's lowest corner.
        origin: Coord,
    },
    /// A cell's corner was not among the shared points.
    #[error("a cell's corner is missing from the shared points")]
    Inconsistent,
    /// Writing failed.
    #[error(transparent)]
    Io(#[from] std::io::Error),
}