Skip to main content

Crate brodo

Crate brodo 

Source
Expand description

§brodo

Sparse volumes in three dimensions, in pure Rust. A grid stores values only where you activate voxels, in leaves of 8³, with a single value (a tile) standing for a whole region that is uniform. It is not tied to any one use: signed distance fields, fluid fields and smoke are all fields on its tree.

What sets it apart is that the set of active voxels, the Topology, is its own value, and any number of fields of any type sit on one topology. You edit the topology once, and every field on it is carried over to the new one; a field refuses a topology it does not belong to.

  • Tree and fields: a fixed four-level tree, tiles at every level. Editing a Topology returns the new one and a Remap, which carries each Field<T> over.
  • Work on leaves: value accessors, each leaf’s 26 neighbours, a leaf gathered with ghost layers, and leaf-parallel updates that give the same result at any thread count.
  • Analysis: connected components and flood fill, the Euler characteristic, and dilation and erosion, with 6- or 26-connectivity, tiles included.
  • Files, whose bytes docs/formats.md specifies for a reader in any language:
    • freeze packs a grid into one aligned buffer, read in place, with no copy, as a GridView.
    • A cache file holds several grids with their names, voxel sizes and units. It is checked whole on reading, and read by copy or by memory mapping.
    • An archive file is a cache file compressed, a floating grid’s values kept exact or quantised. It reads back whole, or as one grid’s region, decompressing only the parts that region needs.
    • OpenVDB’s .vdb: float and double grids, written for OpenVDB’s tools and read from files brodo and OpenVDB 12 to 14 write.
  • Hermite data: where a surface crosses each voxel edge, with its normal and a group naming the face, beside the signs at the voxels’ corners: what a reader needs to place the surface within a voxel, sharp edges kept.
  • Checking output: a slice drawn as PNG, and a field for ParaView, to look at a result.

Where to start:

ToUse
make and edit the set of active voxelstopology::Topology and its edit
hold values on itfield::Field
label components, find the Euler characteristic, dilate or erodeanalysis, morphology
save and loadcache, archive, vdb
read a saved grid in placefrozen::freeze, view::GridView
look at a fieldslice, vtk

Features, each a part a user may leave out or opt into:

FeatureDefaultGives
mmaponreading a cache file by memory mapping
archiveoffthe archive profile
vdboffOpenVDB’s .vdb files
slice-pngoffa slice as PNG
vtkoffa field for ParaView, as .vtu

§Example

A solid ball in two leaves: its components and Euler characteristic counted, and the grid saved as a cache file’s bytes and read back:

use brodo::analysis::{Connectivity, label};
use brodo::cache::{CacheEntry, CacheFile, to_bytes};
use brodo::analysis::euler;
use brodo::field::Field;
use brodo::meta::GridMeta;
use brodo::topology::Topology;

// Two leaves side by side, and a solid ball of radius 5 about (8, 4, 4).
let empty = Topology::new();
let mut edit = empty.edit();
edit.activate([0, 0, 0])?;
edit.activate([8, 0, 0])?;
let (t, _remap) = edit.commit()?;

let mut solid = Field::new(&t, 0_u8);
for n in t.leaf_numbers() {
    let voxels: Vec<_> = t.leaf_voxels(n)?.collect();
    for (value, v) in solid.leaf_mut(&t, n)?.iter_mut().zip(voxels) {
        let d2 = (v[0] - 8).pow(2) + (v[1] - 4).pow(2) + (v[2] - 4).pow(2);
        *value = u8::from(d2 <= 25);
    }
}
let inside = |x: u8| x == 1;
assert_eq!(label(&t, &solid, inside, Connectivity::TwentySix)?.count(), 1);
assert_eq!(euler(&t, &solid, inside, Connectivity::TwentySix)?, 1);

// Serialised as a cache file's bytes, and read back from a copy of them.
let meta = GridMeta::new("ball", [0.1; 3], "millimeter")?;
let bytes = to_bytes(&[CacheEntry::new(&meta, &t, &solid)?])?;
let file = brodo::cache::from_bytes(&bytes)?;
let grid = file.grids()[0];
assert_eq!(grid.meta().name, "ball");
assert_eq!(grid.view::<u8>()?.get([8, 4, 4])?, 1);

The examples diffuse several fields on one topology, checked against the analytic solution, and build the union of two spheres’ distance fields:

cargo run -p brodo --example diffusion
cargo run -p brodo --example spheres

It is written by a human, its owner, together with AI coding agents, chiefly Claude Code and Antigravity in support: the owner decides and approves, the agents write under the repository’s rules, and every change passes the same checks and tests (the repository’s README says more).

Issues are welcome at https://github.com/akahmys/brodo/issues, a bug with the steps that show it or a request. Pull requests are not accepted: code written elsewhere cannot be shown free of copyleft sources, so fixes are written here from issues.

Licensed under either of MIT or Apache-2.0, at your option.

The design is in the repository’s ARCHITECTURE.md §4.

Modules§

accessor
Value accessors (ARCHITECTURE.md §4.4, ADR-0015): reads and writes by voxel that start from the regions last visited, so nearby accesses skip most of the tree. One per thread.
analysis
Topology analysis (ARCHITECTURE.md §4.5, ADR-0013): connected components of the voxels whose value meets a condition, tiles and the background included, flood fill, and the Euler characteristic (euler, ADR-0024).
archive
The archive profile (ARCHITECTURE.md §4.7, ADR-0016): grids with their metadata in one file, each frozen buffer cut into chunks compressed apart by lz4_flex’s block format, read back as the cache file of the same grids. This module parses untrusted bytes: it is listed in UNTRUSTED_INPUT, indexes nothing (CODING.md Rule 3), checks the whole file before it answers, and is fuzzed (TESTING.md T6).
cache
The cache profile (ARCHITECTURE.md §4.7, ADR-0016): grids with their metadata in one file, each grid’s frozen buffer on a 32-byte boundary. This module parses untrusted bytes: it is listed in UNTRUSTED_INPUT, indexes nothing (CODING.md Rule 3), checks the whole file before it answers, and is fuzzed (TESTING.md T6).
error
The errors of brodo’s operations (CODING.md Rule 8).
field
A field: one value per voxel of a topology’s leaves, one per tile, and a background (ARCHITECTURE.md §4.2, ADR-0014). It records its topology’s stamp and refuses any other topology as FieldError::StaleField.
frozen
The frozen layout (ARCHITECTURE.md §4.6, ADR-0016) and freeze, which packs a topology and a field into one 32-byte aligned buffer that a GridView reads in place.
gather
The gather (ARCHITECTURE.md §4.4, ADR-0015): a leaf and one ghost layer from its 26 neighbours, copied into a dense 10³ array, or one to three layers into a caller’s Vec (gather_wide). A neighbour that is not a leaf gives its tile’s value or the background. A leaf of 8³ reaches only its 26 neighbours up to eight layers; a read beyond the window goes through ValueAccessor, faster there than Field::value_at.
grid
A grid: one topology and one field on it, for single-field use (ARCHITECTURE.md §4.2). Editing a grid commits its topology and remaps its field in one step, so its field is never stale.
hermite
Hermite data on a grid’s voxel corners, a format any producer writes (ARCHITECTURE.md §4.9, ADR-0032): a sign at each lattice point and, on each edge whose ends differ, where the surface crosses it, its normal there and its group (Ju et al. 2002).
kind
A file’s kind, told by its first bytes, its magic number (docs/formats.md §2.1, §3.1, §8.1, §9.1; ADR-0056): each format’s magic number is here, and its module reads it from here, so that the kinds are told apart whichever features are built.
manifest
The leaf manifest (ARCHITECTURE.md §4.3, ADR-0014): what lies one leaf away from each leaf in each of the 26 directions, rebuilt with its topology at each commit.
meta
A grid’s metadata and bounding box (ARCHITECTURE.md §4.7, ADR-0016): what a file stores beside a grid’s topology and values.
morphology
Dilation and erosion of a topology (ARCHITECTURE.md §4.5, ADR-0025), on whole leaves: a leaf holds no per-voxel mask, so every voxel of a leaf or tile is active.
parallel
Leaf-parallel work (ARCHITECTURE.md §4.4, ADR-0015): an update that writes one field a leaf at a time on rayon’s threads, and a reduction whose result does not depend on the thread count.
slice
Checking output (ARCHITECTURE.md §6.1, ADR-0018): a slice of a scalar field drawn as an image and encoded as PNG, for a person or an agent to look at (TESTING.md T8).
topology
A topology: the tree, a stamp unique in the process, and its leaves and tiles numbered in the Morton order of their origins (ARCHITECTURE.md §4.2, ADR-0014). It is edited only through a TopologyEditor, whose commit gives a new topology and the Remap to it.
tree
The sparse tree (ARCHITECTURE.md §4.1, ADR-0013): a root map, internal nodes of 32³ and 16³ children, and leaves of 8³ voxels. The tree holds topology only: which regions are leaves, which are tiles, and which are the background. Values belong to fields (§4.2).
vdb
OpenVDB files (ARCHITECTURE.md §4.7, ADR-0045, ADR-0046, ADR-0047): grids of f32 or f64 written as one .vdb file at file version 225, as OpenVDB 14 reads it, and read from the files brodo and OpenVDB 12 to 14 write by from_bytes and read, which this module re-exports from the private module vdb_read, where reading lives.
view
Reading a frozen grid in place (ARCHITECTURE.md §4.6, ADR-0016). This module parses untrusted bytes: it is listed in UNTRUSTED_INPUT, indexes nothing (CODING.md Rule 3), checks the whole buffer before any read, and is fuzzed (TESTING.md T6).
vtk
Checking output (ARCHITECTURE.md §6.1, ADR-0018): a field written for ParaView as one ASCII UnstructuredGrid (.vtu), from VTK’s published file-format documentation.