Skip to main content

Grid

Struct Grid 

Source
pub struct Grid {
    pub transform: GridTransform,
    pub chunks: HashMap<IVec3, Vxl>,
    pub render_sky: bool,
    pub mip_levels_override: Option<u32>,
    pub lod_thresholds: LodThresholds,
    pub billboards: Option<BillboardCache>,
    pub generator: Option<Arc<dyn ChunkGenerator>>,
    pub name: Option<String>,
    pub store: Option<Arc<dyn ChunkStore>>,
    pub stream_radius: StreamRadius,
    pub pending_gen: HashSet<IVec3>,
    pub dda_brick_cache: BrickCache,
    /* private fields */
}
Expand description

One independent voxel grid in a scene. Holds its world placement and a sparse map of populated chunks. Empty chunk slots are implicit air and skipped during rendering / raycasts.

Each chunk is internally a Vxl with vsid = CHUNK_SIZE_XY — the existing per-chunk renderer (opticast + grouscan + sprites + lighting in roxlap-core) runs on each chunk unchanged. Vertical worlds are built by stacking chunks along grid-local +z.

Fields§

§transform: GridTransform

World placement (origin + rotation).

§chunks: HashMap<IVec3, Vxl>

Sparse chunk storage keyed by (chx, chy, chz) chunk coordinates. A missing entry means the chunk is fully air.

§render_sky: bool

Whether sky pixels rendered for this grid should be composited into the final framebuffer. true is the historical “grid owns its own sky” behaviour: ray misses inside this grid’s frustum paint sky_color into the temp buffer. Set false for grids that are a foreground object (e.g. a ship) — the sky is owned by a single “world” grid (the ground) and other grids should not contribute sky pixels, otherwise their grid-local-frame sky lookup rotates with the grid and visibly fights the world’s sky during compose. See crate::render::render_scene_composed for the masking implementation.

§mip_levels_override: Option<u32>

Override roxlap_core::opticast::OpticastSettings::mip_levels for this grid. None ⇒ use the caller’s value. Some(n) ⇒ cap at n (clamped to [1, settings.mip_levels]). Use to disable multi-mip on a per-grid basis — small grids (rotating ships, billboards) don’t benefit from deep mips and CAN trigger the [[project_axis_aligned_mip_beams]]-style cf-cancellation artifact when near-axis-aligned rays hit the rotated grid. Some(1) = mip-0 only, byte-stable to single-mip.

§lod_thresholds: LodThresholds

World-distance thresholds for per-grid LOD tier selection (S6.0). Defaults to LodThresholds::always_near, so a freshly-constructed grid always renders at full voxel (the S5-and-earlier byte-stable behaviour). S6.1 plugs Mid into the existing multi-mip path; S6.3 plugs Far into the billboard impostor cache. See crate::lod.

§billboards: Option<BillboardCache>

Lazy BillboardCache for the Lod::Far tier (S6.2). None until the first time S6.3’s render dispatch needs it; populated then via BillboardCache::build and cleared by edits (Self::set_voxel / Self::set_rect / Self::set_sphere) to force a rebuild on next Far use. Callers may also force-invalidate via direct assignment.

§generator: Option<Arc<dyn ChunkGenerator>>

Optional procedural generator (S7.0). When set, Self::ensure_chunk_generated uses it to materialise chunks that are still absent from Self::chunks.

Streaming layers (S7.1+) walk the active radius around the camera and call ensure_chunk_generated for missing chunks; later stages dispatch this onto a background rayon pool. The trait bound is Send + Sync (needed for S7.3 async dispatch) + Debug (needed so Grid keeps deriving Debug).

None is the default — a grid without a generator behaves exactly like the pre-S7 grids: absent chunks stay absent.

Arc (not Box) so S7.3’s async dispatch can clone the generator into background rayon tasks without moving it out of the grid. Trait bound Send + Sync (required at S7.0) already makes Arc<dyn ChunkGenerator> Send + Sync.

§name: Option<String>

QE.5b - optional host-assigned tag, carried through snapshots. Grid ids are runtime-opaque, so a save/load cycle gives hosts nothing to rebind their own per-grid data (generators, stores, gameplay state) against; a stable name closes that gap. Not interpreted by the engine.

§store: Option<Arc<dyn ChunkStore>>

QE.5a - optional persistence for edited streamed chunks: the eviction pass hands every chunk_version != 0 chunk to ChunkStore::store before dropping it, and stream-in asks ChunkStore::load before running the generator. None (the default) keeps the pre-QE.5 behaviour: evicting an edited chunk discards the edits.

§stream_radius: StreamRadius

Streaming activity / eviction radii used by Scene::pump_streaming_sync (S7.1). Defaults to StreamRadius::DISABLED so existing grids see no change in behaviour until the caller opts in.

§pending_gen: HashSet<IVec3>

In-flight background generation tasks (S7.3).

Populated by Scene::pump_streaming when it dispatches a generator call onto the streaming rayon pool, drained when the corresponding ChunkResult is received and processed (either installed or discarded). The set is consulted to avoid re-dispatching the same chunk while a previous task is still running.

Stays empty when only the synchronous Scene::pump_streaming_sync is used — that path generates inline on the calling thread.

§dda_brick_cache: BrickCache

Cross-frame DDA brick-occupancy cache (Substage DDA.7 perf). Keyed by (chunk, mip) + the chunk’s edit version, so a static chunk’s brick map is built once and reused every frame. Skipped entirely on the voxlap render path. Not serialised.

Implementations§

Source§

impl Grid

Source

pub fn voxel_solid(&self, voxel: IVec3) -> bool

True if the grid-local integer voxel voxel is solid (inside a solid run of its chunk). An implicit-air or absent chunk reads as false. voxel is a grid-local voxel coordinate (pre-transform) — get one from a world point via crate::world_to_grid_local + crate::voxel_global. Useful for picking, collision, and world queries.

Source

pub fn voxel_color(&self, voxel: IVec3) -> Option<u32>

Packed BGRA colour of the textured voxel at grid-local voxel, or None for air / untextured cells. Thin wrapper over roxlap_formats::vxl::Vxl::voxel_color after the chunk split — the colour-inspection companion to Self::voxel_solid. Use it to read back what a pick / raycast hit looks like.

Source

pub fn chunk(&self, chunk_idx: IVec3) -> Option<&Vxl>

Borrow the chunk at chunk_idx if it has been materialised. None means the chunk is implicitly all-air.

Source

pub fn bake_lightmode(&mut self, lightmode: u32)

Bake per-voxel lighting (voxlap updatevxl/estnorm shading) into every materialised chunk’s brightness bytes, in place. lightmode is voxlap’s mode (1 = directional estnorm shading, the look the cave + terrain demos use). Both the CPU rasteriser and the GPU marcher read these pre-baked brightness bytes, so call this once after building a grid and again over edited chunks after a carve (then bump their versions so the GPU re-uploads — edits already do, via Grid::set_voxel &c.).

Each chunk is baked neighbour-aware in all three axes: estnorm’s (and AO’s) ±2-voxel padding that crosses a chunk face reads the actual neighbour chunk (when populated) — XY neighbours and, for stacked grids, the chunks above/below on chz±1 — so brightness / occlusion is continuous across every seam. Point lights aren’t applied (directional-only) — matching the demos’ bake. No-op for an empty grid.

Source

pub fn bake_lightmode_with_ao(&mut self, lightmode: u32, ao: AoParams)

Self::bake_lightmode with explicit AoParams — the AO bake (lightmode 3) tunes strength/radius through these.

Source

pub fn bake_lightmode_bbox(&mut self, lo: IVec3, hi: IVec3, lightmode: u32)

PF.11 — re-bake lighting over just the grid-local voxel bbox [lo, hi] (inclusive), neighbour-aware across chunk seams in all three axes: the write region is padded by ±ESTNORMRAD internally (an edit changes the estnorm of nearby voxels too — pass only the geometric edit extent, mirroring update_lighting’s convention), and any chunk the padded region touches gets its strip re-baked. Touched chunks get their versions bumped so the GPU re-uploads.

This is the runtime-edit primitive the full-grid Self::bake_lightmode is far too heavy for: a bullet-hole rebake touches a few hundred columns instead of a whole chunk (the cave demo measured ~0.04 ms vs 4–7 ms). Mip regeneration is NOT performed — near-field renders read mip 0; callers streaming distant edited chunks should remip as they already do for edits.

Source

pub fn chunk_mut(&mut self, chunk_idx: IVec3) -> Option<&mut Vxl>

Mutably borrow a materialised chunk. Returns None for implicit-air chunks; use Grid::ensure_chunk when you need a &mut Vxl for an edit that may write voxels.

Source

pub fn ensure_chunk(&mut self, chunk_idx: IVec3) -> &mut Vxl

Borrow chunk_idx’s Vxl, creating an empty all-air chunk first if it doesn’t exist yet. The returned &mut is valid for editing via roxlap_formats::edit — the new chunk has Vxl::reserve_edit_capacity already applied.

Source

pub fn chunk_count(&self) -> usize

Number of materialised chunks. Implicit-air chunks don’t count.

Source

pub fn chunk_xy_backing(&self) -> Option<ChunkXyBacking<'_>>

S4B.2.c.3: build a per-chunk roxlap_core::GridView table over this grid’s XY chunk footprint at chz = 0.

Returns None if no chz=0 chunk is populated (the entire grid would render as implicit air anyway).

Iterates chunks once to find the chx/chy bounding box, then a second time to fill the row-major Vec<Option<GridView<'_>>>. Empty XY slots (implicit-air chunks inside the box) get None.

Pair with roxlap_core::ChunkGrid + [roxlap_core:: GridView::from_chunk_grid] to drive the Approach B render path:

let backing = grid.chunk_xy_backing().unwrap();
let cg = roxlap_core::ChunkGrid {
    chunks: &backing.chunks,
    origin_chunk_xy: backing.origin_chunk_xy,
    chunks_x: backing.chunks_x,
    chunks_y: backing.chunks_y,
};
let view = roxlap_core::GridView::from_chunk_grid(
    &cg, crate::CHUNK_SIZE_XY,
);

Only chz=0 chunks contribute (multi-z handoff lands in S4B.3); higher-chz chunks in Self::chunks are ignored here.

Source

pub fn chunk_xyz_backing(&self) -> Option<ChunkXyBacking<'_>>

S4B.6.a: 3D-aware version of Self::chunk_xy_backing. Enumerates ALL chunks across the chx/chy/chz bounding box (not just chz=0) so a stacked grid can be rendered once S4B.6.c switches the rasterizer to a chunk-z-aware column walker.

Iterates chunks once for the XYZ bbox, then a second time to fill the row-major-per-z Vec<Option<GridView<'_>>>. Index layout: [(dz * chunks_y + dy) * chunks_x + dx] — matches roxlap_core::ChunkGrid’s indexing exactly.

Returns None for empty grids.

Source§

impl Grid

Source

pub fn set_voxel(&mut self, voxel: IVec3, color: Option<u32>)

Set or carve a single voxel at grid-local coordinate voxel. color = Some(c) inserts a solid voxel of colour c; color = None carves to air.

Inserting in an implicit-air chunk materialises that chunk (allocates a fresh Vxl); carving from a missing chunk is a no-op.

Source

pub fn set_rect(&mut self, lo: IVec3, hi: IVec3, color: Option<u32>)

Set or carve an axis-aligned box [lo, hi] in grid-local voxel coordinates. Inclusive on both ends, like roxlap_formats::edit::set_rect.

The box is decomposed per chunk: each touched chunk receives a set_rect call with the box clipped to its footprint and translated to chunk-local. Inserts materialise missing chunks; carves skip them.

lo and hi may be in any order on each axis — the decomposition normalises them.

Source

pub fn set_sphere(&mut self, centre: IVec3, radius: u32, color: Option<u32>)

Set or carve a sphere of voxels at grid-local centre centre with the given radius. Euclidean distance, like roxlap_formats::edit::set_sphere.

The bounding box of the sphere is enumerated chunk by chunk; each touched chunk receives a set_sphere call with the centre re-expressed in chunk-local coords (the per-chunk call clips the sphere to the chunk’s footprint internally). Chunks the sphere doesn’t actually reach get materialised only if color.is_some() and they fall within the AABB — the per-chunk set_sphere is a no-op for non-overlapping chunks but the materialisation cost remains. A subsequent pre-pass that filters chunks against radius² could avoid this; out of scope for v1.

Source

pub fn set_sphere_with_colfunc<F>( &mut self, centre: IVec3, radius: u32, op: SpanOp, colfunc: F, )
where F: FnMut(i32, i32, i32) -> i32,

Carve or insert a sphere with a per-voxel colour callback — the colfunc counterpart of Grid::set_sphere, forwarding to roxlap_formats::edit::set_sphere_with_colfunc.

Use this (with SpanOp::Carve) to control the colour of the interior surface a carve newly exposes: a plain set_sphere carve paints those walls colour 0 (black), whereas this lets the closure return a crater colour, a depth gradient, jitter, or a texture lookup. With SpanOp::Insert the closure colours the inserted voxels.

colfunc(x, y, z) receives grid-local voxel coordinates (not chunk-local) and returns a voxlap-packed BGRA colour as i32 — the per-chunk decomposition translates coordinates back to grid-local before invoking the closure, so a position- or depth-dependent colour stays continuous across chunk seams.

Like Grid::set_sphere: SpanOp::Insert materialises missing chunks; SpanOp::Carve skips chunks that don’t yet exist (carving implicit air is a no-op).

Source

pub fn set_rect_with_colfunc<F>( &mut self, lo: IVec3, hi: IVec3, op: SpanOp, colfunc: F, )
where F: FnMut(i32, i32, i32) -> i32,

Carve or insert an axis-aligned box [lo, hi] (inclusive) with a per-voxel colour callback — the colfunc counterpart of Grid::set_rect, forwarding to roxlap_formats::edit::set_rect_with_colfunc. See Grid::set_sphere_with_colfunc for the coordinate and chunk-materialisation contract; colfunc likewise receives grid-local coordinates.

Source§

impl Grid

Source

pub fn new(transform: GridTransform) -> Self

New empty grid at the given transform — no chunks populated, render_sky = true, LOD thresholds default to LodThresholds::always_near, no billboard cache.

Source

pub fn ensure_dda_bricks(&mut self, requested_mip: u32) -> u32

Ensure the DDA brick cache holds current mip-requested_mip occupancy maps for every populated chunk, rebuilding only chunks whose edit version changed (Substage DDA.7). Clamps the mip to a level every chunk has built (so coarse rendering never holes) and returns that effective mip. Evicts cache entries for chunks no longer present. Call once per frame before the DDA render.

Source

pub fn chunk_version(&self, chunk_idx: IVec3) -> u64

Current per-chunk edit version (S7.2). Returns 0 for any chunk that hasn’t been edited yet (including absent chunks and chunks materialised only via Self::ensure_chunk_generated).

Used by S7.3’s async generation dispatch to detect “edit happened while we were generating” — the dispatcher snapshots this value, the background task carries it, and the result is discarded on install if the live counter has since moved.

Source

pub fn bump_chunk_version(&mut self, chunk_idx: IVec3)

Bump the edit version of chunk_idx (S7.2). Saturating add at u64::MAX — a chunk would need 10^11 edits per second for ~5 years to wrap, so saturation is a defensive cap, not a realistic concern.

Called by the edit API (Self::set_voxel / Self::set_rect / Self::set_sphere) after a chunk has actually been written to. Pure no-op edit paths (carving from an air chunk that doesn’t exist yet) skip the bump.

Exposed as pub (vs the historical pub(crate)) so hosts that mutate grid.chunks directly — e.g. roxlap-scene-demo’s StreamingBakeTracker writing lightmode-1 alphas via apply_lighting_with_cache — can signal “this chunk’s slab changed” to downstream consumers like the GPU dirty-chunk poller.

Source

pub fn mutation_counter(&self) -> u64

PF.13 (H9) — the grid’s monotonic mutation counter: bumped by every chunk edit, install, and eviction. Per-frame consumers snapshot it to skip their whole-grid scans on quiet frames.

Source

pub fn bump_chunk_version_bbox( &mut self, chunk_idx: IVec3, lo: IVec3, hi: IVec3, )

PF.12 — Self::bump_chunk_version with the edit’s CHUNK-LOCAL voxel extent (inclusive), so partial-refresh consumers (the GPU facade) and incremental re-mip know how little actually changed. Extents accumulate (bbox union) until a consumer Self::take_chunk_dirtys them; an extent-less bump upgrades the entry to DirtyExtent::Full.

Source

pub fn take_chunk_dirty(&mut self, chunk_idx: IVec3) -> Option<DirtyExtent>

PF.12 — take (and clear) the extent accumulated for chunk_idx since the last take. None ⇒ no recorded change (a consumer that still observed a version bump should treat that as DirtyExtent::Full — e.g. a change recorded before the consumer first synced the chunk).

Source

pub fn chunk_versions(&self) -> &HashMap<IVec3, u64>

The full per-chunk edit-version map (QE.3b — the read half of the previously pub field). Consumers seeding a sync tracker iterate this; per-chunk reads go through Self::chunk_version.

Source

pub fn set_generator(&mut self, generator: Option<Arc<dyn ChunkGenerator>>)

Attach (or detach) the procedural generator used by Self::ensure_chunk_generated (S7.0).

Pass Some(Arc::new(generator)) to enable on-demand chunk generation; pass None to revert to the “absent stays absent” behaviour. Replacing an existing generator drops the previous Arc clone without touching already-materialised chunks. Any background tasks dispatched by a prior Scene::pump_streaming hold their own clones of the old generator and finish naturally.

Source

pub fn set_chunk_store(&mut self, store: Option<Arc<dyn ChunkStore>>)

Attach (or detach) the persistence store for edited streamed chunks (QE.5a; see ChunkStore). Without one, evicting an edited chunk discards the edits — the pre-QE.5 default.

Source

pub fn ensure_chunk_generated(&mut self, chunk_idx: IVec3) -> bool

Materialise the chunk at chunk_idx by running Self::generator if (a) the chunk is not already present and (b) a generator is attached. Returns true iff a chunk was newly generated.

No-ops in all other cases:

  • chunk already present (caller edits / a previous ensure_chunk_generated call already populated it),
  • no generator attached (the chunk stays implicit-air per the existing convention — does NOT fall through to Self::ensure_chunk’s empty-chunk constructor).

This is the synchronous S7.0 path; Scene::pump_streaming is the async counterpart (generation + store loads on a dedicated rayon pool).

QE.5a: with a ChunkStore attached, a stored chunk is installed (with its persisted edit version) before the generator is consulted — including for indices the generator’s ChunkGenerator::should_generate declines, since edits can materialise chunks the generator never would.

Source

pub fn bounding_radius(&self) -> f64

Bounding-sphere radius of the populated chunk set in grid-local space.

Walks the sparse chunk map once, computes the chunk-index AABB, converts to voxel-space half-extent, returns its Euclidean length. Empty grid → 0.0.

Conservative — bounds the full chunk volume, not just its populated voxels (a chunk containing one voxel still contributes CHUNK_SIZE_XY × CHUNK_SIZE_XY × CHUNK_SIZE_Z to the bbox). For LOD picking that’s fine: an over-bound sphere errs on the side of Near.

Cost: O(chunks.len()); recomputed on every call. Callers who need this every frame should memoize at the Scene-level cache (added when S6.2 needs it).

Source

pub fn select_lod(&self, camera_world_pos: DVec3) -> Lod

Pick this grid’s LOD tier for the given world-space camera position. Convenience wrapper around crate::select_lod that pulls Self::lod_thresholds from the grid.

Trait Implementations§

Source§

impl Debug for Grid

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl !RefUnwindSafe for Grid

§

impl !UnwindSafe for Grid

§

impl Freeze for Grid

§

impl Send for Grid

§

impl Sync for Grid

§

impl Unpin for Grid

§

impl UnsafeUnpin for Grid

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.