voxel-light 0.1.0

Generic 3D BFS light propagation with two-phase removal for voxel engines
Documentation
  • Coverage
  • 52.38%
    11 out of 21 items documented0 out of 7 items with examples
  • Size
  • Source code size: 83.25 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 537.08 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 21s Average build duration of successful builds.
  • all releases: 21s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • sevenevesai/voxel-light
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • sevenevesai

Trait-generic 3D BFS light propagation for voxel engines

BFS flood-fill light propagation with two-phase removal and re-propagation from surviving sources. Operates on any storage that implements a single trait method. Returns updates as a Vec — never mutates caller data.

Install

cargo add voxel-light

Usage

Implement the trait for your storage:

use voxel_light::{VoxelAccess, VoxelInfo};

impl VoxelAccess for MyWorld {
    fn get_voxel(&self, x: i32, y: i32, z: i32) -> Option<VoxelInfo> {
        let chunk = self.get_chunk(x.div_euclid(16), y.div_euclid(16), z.div_euclid(16))?;
        let block = chunk.get_block(x.rem_euclid(16) as usize, /* ... */);
        Some(VoxelInfo {
            transparent: block.is_transparent(),
            block_light: chunk.get_light(x, y, z),
            emission: block.emission(),
        })
    }
}

Call the algorithm and apply results:

use voxel_light::LightEngine;

let mut engine = LightEngine::new();

// Place a torch (level 14)
let updates = engine.place_light(&world, [10, 64, 10], 14);
for u in &updates {
    world.set_light(u.x, u.y, u.z, u.level);
}

// Remove it — two-phase removal zeroes dependents, then re-propagates
// from any neighboring sources that still exist
let updates = engine.remove_light(&world, [10, 64, 10]);
for u in &updates {
    world.set_light(u.x, u.y, u.z, u.level);
}

API

Method When to call
place_light(access, pos, level) Light-emitting block placed
remove_light(access, pos) Light-emitting block broken
block_placed(access, pos) Opaque block placed (may cut light paths)
block_removed(access, pos) Opaque block broken (light may flow in)
recalculate_area(access, center, radius) Clear and re-propagate all emitters in a cubic radius

Free functions (propagate, remove) are available if you don't need LightEngine's buffer reuse.

Features

Feature Effect
std (default) Uses ahash for the visited map (~2x faster than BTreeMap)
sky Column-first sky light propagation with horizontal BFS spread
colored Per-channel RGB light (3 channels, single BFS pass)

Core is no_std compatible (requires alloc). Without std, the visited map uses BTreeMap.

Performance

Open-air world (no obstructions, worst-case BFS expansion). AMD Ryzen 9 7900, 64 GB RAM, Windows 11:

Operation Level 7 Level 10 Level 14
Propagation 17 µs 60 µs 174 µs
Removal (single source) 105 µs 432 µs
Removal (neighboring source) 463 µs
Full place + remove cycle 585 µs

A level-14 torch touches ~11,500 voxels. Benchmarks use criterion: cargo bench.

Algorithm

Propagation. BFS from source, decaying by 1 per block. Stops at opaque voxels (transparent: false) and world boundaries (None from get_voxel). Visited map prevents re-processing.

Removal phase 1. BFS from removed source. For each neighbor: if light < node's old level, it was dependent — zero it and enqueue. If light >= old level, it has independent light — save as boundary source.

Removal phase 2. Build an overlay that returns zeroed values for all positions cleared in phase 1 (without requiring the caller to apply them first). BFS re-propagate from all boundary sources against this overlay.

Output. Flat Vec<LightUpdate>. Zeroing updates appear first, then re-propagation values. Apply in order.

Prior art

The algorithm is standard (Seed of Andromeda documented it in 2015). These projects implement it by hand:

  • Luanti/Minetestvoxelalgorithms.cpp, ~500 LOC, C++
  • Seed of Andromeda — three BFS passes per RGB channel, C++
  • PocketMine-MP — standalone specification document for the algorithm
  • Rust voxel engines (Rezcraft, Aern-do/voxel, Technici4n/voxel-rs) — each contains a bespoke implementation

No existing crate on crates.io provides this. block-mesh handles meshing. building-blocks handles storage. tapestry is 2D. pathfinding solves graph traversal without light-decay or removal semantics.

Known limitations

  • Not a spatial data structure. Queries your storage via the trait and returns updates. Does not store light values.

  • Visited map allocated per call. The AHashMap is not pooled across calls. Typical cost: ~11k entries for level-14 propagation.

  • Colored removal over-zeroes. A voxel is considered dependent if any channel is less than the corresponding old level. Edge cases with overlapping colored sources of different ranges may zero more than necessary.

  • No sky light removal. The sky feature provides propagation only. Placing a roof requires the caller to recalculate affected columns.

License