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 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: 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.

§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.

§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

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 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§

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 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 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 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. 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.

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 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> 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.