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 stream_radius: StreamRadius,
pub chunk_versions: HashMap<IVec3, u64>,
pub pending_gen: HashSet<IVec3>,
}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: GridTransformWorld 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: boolWhether 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: LodThresholdsWorld-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.
stream_radius: StreamRadiusStreaming 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.
chunk_versions: HashMap<IVec3, u64>Per-chunk edit version counter (S7.2). Each user edit
through Self::set_voxel / Self::set_rect /
Self::set_sphere bumps the counter for every chunk it
actually wrote to. Self::ensure_chunk_generated does
NOT bump — a freshly generated chunk has no edits and
reads as version 0.
Wired up here so the S7.3 async dispatch can detect “an edit happened while a chunk was being generated in the background” and discard the now-stale result: each background task captures the dispatch-time version and only installs its result iff the current version still matches.
Missing entries read as 0 via Self::chunk_version.
Evictions in Scene::pump_streaming_sync drop the
corresponding entry so the map stays bounded.
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.
Implementations§
Source§impl Grid
impl Grid
Sourcepub fn chunk(&self, chunk_idx: IVec3) -> Option<&Vxl>
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.
Sourcepub fn chunk_mut(&mut self, chunk_idx: IVec3) -> Option<&mut Vxl>
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.
Sourcepub fn ensure_chunk(&mut self, chunk_idx: IVec3) -> &mut Vxl
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.
Sourcepub fn chunk_count(&self) -> usize
pub fn chunk_count(&self) -> usize
Number of materialised chunks. Implicit-air chunks don’t count.
Sourcepub fn chunk_xy_backing(&self) -> Option<ChunkXyBacking<'_>>
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.
Sourcepub fn chunk_xyz_backing(&self) -> Option<ChunkXyBacking<'_>>
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
impl Grid
Sourcepub fn set_voxel(&mut self, voxel: IVec3, color: Option<u32>)
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.
Sourcepub fn set_rect(&mut self, lo: IVec3, hi: IVec3, color: Option<u32>)
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.
Sourcepub fn set_sphere(&mut self, centre: IVec3, radius: u32, color: Option<u32>)
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§impl Grid
impl Grid
Sourcepub fn new(transform: GridTransform) -> Self
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.
Sourcepub fn chunk_version(&self, chunk_idx: IVec3) -> u64
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.
Sourcepub fn set_generator(&mut self, generator: Option<Arc<dyn ChunkGenerator>>)
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.
Sourcepub fn ensure_chunk_generated(&mut self, chunk_idx: IVec3) -> bool
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_generatedcall 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. S7.3 will add an async
counterpart that dispatches the generator call to a
dedicated rayon pool and installs the result on the next
pump_streaming call.
Sourcepub fn bounding_radius(&self) -> f64
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).
Sourcepub fn select_lod(&self, camera_world_pos: DVec3) -> Lod
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§
Auto Trait Implementations§
impl Freeze for Grid
impl !RefUnwindSafe for Grid
impl Send for Grid
impl Sync for Grid
impl Unpin for Grid
impl UnsafeUnpin for Grid
impl !UnwindSafe for Grid
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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