#[repr(transparent)]pub struct VoxColor(pub u32);Expand description
A voxel colour in voxlap’s packing: RGB in the low 24 bits, the
baked brightness/AO byte on top — 0x80 is neutral (“unlit”),
and lighting bakes rewrite it per voxel. NOT alpha: translucency
is a material property (see the material palette), never a colour
channel.
Tuple Fields§
§0: u32Implementations§
Source§impl VoxColor
impl VoxColor
Sourcepub const fn rgb(r: u8, g: u8, b: u8) -> VoxColor
pub const fn rgb(r: u8, g: u8, b: u8) -> VoxColor
A voxel colour at the neutral 0x80 brightness.
Examples found in repository?
examples/book_controller.rs (line 23)
23const WATER: VoxColor = VoxColor::rgb(0x30, 0x60, 0xc8);
24fn water_passes(c: VoxColor) -> bool {
25 c.rgb_part() == WATER.rgb_part()
26}
27// ANCHOR_END: veto
28
29fn build_world() -> Scene {
30 let mut scene = Scene::new();
31 let id = scene.add_grid(GridTransform::identity());
32 let g = scene.grid_mut(id).expect("grid");
33 // Ground slab: surface plane at z = 100 (+z is down, so the
34 // slab fills downward).
35 g.set_rect(
36 IVec3::new(20, 20, 100),
37 IVec3::new(140, 140, 120),
38 Some(VoxColor::rgb(0x4d, 0x8a, 0x3a)),
39 );
40 // A 1-voxel ledge to step onto, and a wall to slide along.
41 g.set_rect(
42 IVec3::new(90, 20, 99),
43 IVec3::new(140, 140, 99),
44 Some(VoxColor::rgb(0x77, 0x66, 0x55)),
45 );
46 g.set_rect(
47 IVec3::new(60, 70, 60),
48 IVec3::new(62, 140, 99),
49 Some(VoxColor::rgb(0x88, 0x44, 0x44)),
50 );
51 // A water curtain the veto lets the body wade through. Two
52 // format facts (both pinned as engine tests): pass-through
53 // geometry works up to 2 voxels thick — thicker walls grow a
54 // colourless UnexposedSolid core no veto can classify — and
55 // must sit ≥ 1 voxel inside the chunk (edge voxels lose their
56 // side colours).
57 g.set_rect(IVec3::new(40, 40, 80), IVec3::new(41, 60, 99), Some(WATER));
58 scene
59}More examples
examples/book_scene_graph.rs (line 23)
23const GRASS: VoxColor = VoxColor::rgb(0x4d, 0x8a, 0x3a);
24const STONE: VoxColor = VoxColor::rgb(0x8a, 0x8a, 0x8a);
25const RED: VoxColor = VoxColor::rgb(0xc0, 0x30, 0x30);
26
27// ANCHOR: generator
28/// A deterministic floor generator: chunk layer `z = 0` gets a solid
29/// slab, checkerboarded per chunk so streamed tiles are visible;
30/// every other layer is left as air. `generate` must be a pure
31/// function of `chunk_idx` (plus the generator's own config) — that
32/// determinism is what makes evict + re-stream sound.
33#[derive(Debug)]
34struct FloorGenerator;
35
36impl ChunkGenerator for FloorGenerator {
37 fn generate(&self, chunk_idx: IVec3) -> Vxl {
38 // `Vxl::empty(CHUNK_SIZE_XY)` is the canonical all-air chunk
39 // shape — the same one `Grid::ensure_chunk` materialises.
40 let mut vxl = Vxl::empty(CHUNK_SIZE_XY);
41 if chunk_idx.z == 0 {
42 let light = (chunk_idx.x + chunk_idx.y).rem_euclid(2) == 0;
43 let color = if light {
44 VoxColor::rgb(0x74, 0x9e, 0x58)
45 } else {
46 VoxColor::rgb(0x5d, 0x84, 0x46)
47 };
48 roxlap_formats::edit::set_rect(&mut vxl, [0, 0, 240], [127, 127, 255], Some(color));
49 }
50 vxl
51 }
52}
53// ANCHOR_END: generator
54
55// ANCHOR: chunk_store
56/// Minimal in-memory [`ChunkStore`]: edited chunks handed over at
57/// eviction, returned on stream-in (so player edits survive walking
58/// away). A real game would write a save-file region instead.
59#[derive(Debug, Default)]
60struct MemoryStore {
61 chunks: Mutex<HashMap<IVec3, (Vxl, u64)>>,
62}
63
64impl ChunkStore for MemoryStore {
65 fn store(&self, chunk_idx: IVec3, vxl: &Vxl, version: u64) {
66 let entry = (vxl.clone(), version);
67 self.chunks
68 .lock()
69 .expect("store poisoned")
70 .insert(chunk_idx, entry);
71 }
72
73 fn load(&self, chunk_idx: IVec3) -> Option<(Vxl, u64)> {
74 self.chunks
75 .lock()
76 .expect("store poisoned")
77 .get(&chunk_idx)
78 .cloned()
79 }
80}
81// ANCHOR_END: chunk_store
82
83fn main() {
84 // ANCHOR: scene_grids
85 let mut scene = Scene::new();
86 // A static "world" grid at the origin…
87 let ground_id = scene.add_grid(GridTransform::at(DVec3::ZERO));
88 // …and a second grid placed and rotated independently — its own
89 // f64 world position + quaternion, same chunked voxel payload.
90 let ship_id = scene.add_grid(GridTransform {
91 origin: DVec3::new(300.0, -80.0, -40.0),
92 rotation: DQuat::from_rotation_z(0.5),
93 voxel_world_size: 1.0,
94 });
95 // Object grids should not paint their own (grid-local, rotating)
96 // sky — leave the sky to the world grid.
97 scene.grid_mut(ship_id).expect("just added").render_sky = false;
98 // ANCHOR_END: scene_grids
99
100 // ANCHOR: edits
101 let ground = scene.grid_mut(ground_id).expect("just added");
102 // Solid slab: grid-local voxel coords, inclusive on both ends,
103 // decomposed across as many chunks as it spans (here 4×4).
104 ground.set_rect(
105 IVec3::new(-200, -200, 200),
106 IVec3::new(199, 199, 254),
107 Some(GRASS),
108 );
109 // `Some(colour)` inserts, `None` carves back to air.
110 ground.set_sphere(IVec3::new(0, 0, 195), 12, Some(STONE));
111 ground.set_rect(IVec3::new(-3, -200, 190), IVec3::new(3, 199, 196), None);
112 // ANCHOR_END: edits
113
114 // ANCHOR: queries
115 // Solid tests: inside the slab, then inside the carved tunnel.
116 assert!(ground.voxel_solid(IVec3::new(0, 0, 210)));
117 assert!(!ground.voxel_solid(IVec3::new(0, 100, 193)));
118 // Colour queries answer on surface voxels; deep interior cells
119 // are untextured in the slab format and read as `None`.
120 assert_eq!(ground.voxel_color(IVec3::new(50, 50, 200)), Some(GRASS));
121 // ANCHOR_END: queries
122
123 // ANCHOR: recolour
124 // GOTCHA: inserting over already-solid voxels does NOT repaint
125 // them — span insertion only fills air. Recolour = carve, then
126 // insert.
127 let (lo, hi) = (IVec3::new(20, 20, 200), IVec3::new(24, 24, 204));
128 ground.set_rect(lo, hi, Some(RED));
129 assert_eq!(ground.voxel_color(IVec3::new(22, 22, 200)), Some(GRASS)); // unchanged!
130 ground.set_rect(lo, hi, None); // carve…
131 ground.set_rect(lo, hi, Some(RED)); // …then insert
132 assert_eq!(ground.voxel_color(IVec3::new(22, 22, 200)), Some(RED));
133 // ANCHOR_END: recolour
134
135 // ANCHOR: colfunc
136 // A carve exposes fresh interior walls; the plain edits paint
137 // them colour 0 (black). The `_with_colfunc` variants ask a
138 // closure instead — it sees grid-local coordinates, so gradients
139 // stay continuous across chunk seams. Here: a crater whose walls
140 // darken with depth.
141 ground.set_sphere_with_colfunc(IVec3::new(60, 60, 200), 8, SpanOp::Carve, |_x, _y, z| {
142 let depth = (z - 192).clamp(0, 15) as u8;
143 VoxColor::rgb(0x6b, 0x40 + depth * 3, 0x2f)
144 });
145 // ANCHOR_END: colfunc
146
147 // ANCHOR: snapshot
148 // Name grids before saving: ids survive the round-trip, but the
149 // generator / store hooks are host code and cannot be serialised
150 // — the name is your key for rebinding them after a load.
151 scene.grid_mut(ground_id).expect("ground exists").name = Some("ground".into());
152 let bytes = scene.save_snapshot(); // versioned envelope
153 let restored = Scene::load_snapshot(&bytes).expect("self-authored snapshot");
154 assert_eq!(restored.grid_count(), 2);
155 let (_, ground2) = restored
156 .grids()
157 .find(|(_, g)| g.name.as_deref() == Some("ground"))
158 .expect("rebind by name");
159 assert_eq!(ground2.voxel_color(IVec3::new(22, 22, 200)), Some(RED));
160 // ANCHOR_END: snapshot
161
162 // ANCHOR: streaming
163 // Streaming: attach a generator + radii to a grid, then pump once
164 // per frame with the camera's world position. Chunks within
165 // `r_active` stream in; chunks beyond `r_evict` drop out; the band
166 // between is hysteresis so a camera hovering on a boundary
167 // doesn't thrash.
168 let stream_id = scene.add_grid(GridTransform::at(DVec3::new(0.0, 4096.0, 0.0)));
169 let store = Arc::new(MemoryStore::default());
170 let grid = scene.grid_mut(stream_id).expect("just added");
171 grid.set_generator(Some(Arc::new(FloorGenerator)));
172 grid.set_chunk_store(Some(store));
173 grid.stream_radius = StreamRadius::new(256.0, 384.0);
174
175 let camera_near = DVec3::new(0.0, 4096.0, 100.0);
176 scene.pump_streaming_sync(camera_near); // real games: `pump_streaming`
177 let grid = scene.grid(stream_id).expect("still there");
178 assert!(grid.chunk_count() > 0); // floor streamed in around the camera
179 assert!(grid.voxel_solid(IVec3::new(5, 5, 240)));
180 // ANCHOR_END: streaming
181
182 // ANCHOR: streaming_edit
183 // Edit the streamed floor, walk far away, come back. The active
184 // set follows the camera: the old region evicts (edited chunks
185 // are handed to the ChunkStore first) while a fresh set streams
186 // in around the new position. On return the store wins over the
187 // generator, so the edit survives; without a store, eviction
188 // would silently revert the chunk to generator output.
189 scene
190 .grid_mut(stream_id)
191 .expect("still there")
192 .set_voxel(IVec3::new(5, 5, 240), None); // dig a hole
193 scene.pump_streaming_sync(camera_near + DVec3::new(0.0, 100_000.0, 0.0));
194 let grid = scene.grid(stream_id).expect("still there");
195 assert!(grid.chunk(IVec3::ZERO).is_none()); // home chunk evicted…
196 assert!(grid.chunk_count() > 0); // …but the far region streamed in
197 scene.pump_streaming_sync(camera_near);
198 let grid = scene.grid(stream_id).expect("re-streamed");
199 // The hole survived evict + re-stream.
200 assert!(!grid.voxel_solid(IVec3::new(5, 5, 240)));
201 // ANCHOR_END: streaming_edit
202
203 // ANCHOR: grid_scale
204 // Per-grid scale: `voxel_world_size` is world units per voxel. A grid
205 // built with `at_scale(origin, 2.0)` has voxels twice world size — a
206 // coarse "planet" — while `0.25` gives a fine detail grid; both coexist
207 // at their true relative sizes. Only the world↔grid boundary scales:
208 // edits + queries stay in voxels, unchanged.
209 let mut scaled = Scene::new();
210 let planet = scaled.add_grid(GridTransform::at_scale(DVec3::ZERO, 2.0));
211 scaled
212 .grid_mut(planet)
213 .expect("just added")
214 // Same voxel edit API — grid-local coordinates.
215 .set_voxel(IVec3::new(5, 5, 10), Some(STONE));
216 // Grid-local voxel z=10 sits at WORLD z = 20 (10 voxels × 2.0). A world
217 // raycast down that column reports the grid-local voxel it hit AND a
218 // WORLD-space `t` — so hits across grids of different scale compare
219 // correctly (the raycaster marches voxels but returns world distance).
220 let hit = scaled
221 .raycast(DVec3::new(11.0, 11.0, 0.0), DVec3::new(0.0, 0.0, 1.0), 64.0)
222 .expect("ray hits the scaled voxel");
223 assert_eq!(hit.voxel, IVec3::new(5, 5, 10)); // grid-local, unscaled
224 assert!((hit.t - 20.0).abs() < 1e-4); // WORLD distance: 10 voxels × 2.0
225 // ANCHOR_END: grid_scale
226
227 println!("book_scene_graph: all scene-graph assertions hold");
228}Sourcepub const fn with_brightness(self, brightness: u8) -> VoxColor
pub const fn with_brightness(self, brightness: u8) -> VoxColor
Replace the brightness byte (bakes do this per voxel; hand-set it only for pre-shaded art).
Trait Implementations§
impl Copy for VoxColor
impl Eq for VoxColor
impl StructuralPartialEq for VoxColor
Auto Trait Implementations§
impl Freeze for VoxColor
impl RefUnwindSafe for VoxColor
impl Send for VoxColor
impl Sync for VoxColor
impl Unpin for VoxColor
impl UnsafeUnpin for VoxColor
impl UnwindSafe for VoxColor
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
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
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>
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 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>
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