Picking & world queries
“What did the player click?” and “what’s in front of me?” are the two questions every interactive voxel game asks constantly. roxlap answers them at two levels: screen→world picking on the renderer (unproject a pixel) and world queries on the scene (march a ray, inspect a voxel). Both resolve to the same currency: a grid id plus a grid-local voxel coordinate — exactly what the edit API from chapter 3 consumes, so pick → carve is two calls.
The snippets come from a runnable example — an orbiting camera that outlines the voxel under the screen centre every frame, and carves a crater wherever you click:
cargo run --release -p roxlap-render --example book_picking
Two picking paths — and when to use each
Path 1: view_ray + Scene::raycast — geometric. Unproject the
pixel into a world-space ray under whichever projection the last
frame used (CPU and GPU project differently; view_ray hides that),
then march the scene’s voxels:
// Per-frame hover: unproject the screen centre with
// `view_ray`, march the scene with `Scene::raycast`.
// No depth buffer involved — identical on CPU and GPU
// and cheap enough to run every frame.
let centre = (f64::from(w) * 0.5, f64::from(h) * 0.5);
let hover = renderer
.view_ray(&camera, centre.0, centre.1)
.and_then(|ray| scene.raycast(ray.origin, ray.dir, 2048.0));
if let Some(hit) = hover {
// `hit.voxel` is grid-local; this grid sits at the
// world origin, so the outline needs no transform.
renderer.draw_lines(
&camera,
&voxel_outline(hit.voxel, OverlayColor(0xff_ff_e0_40)),
);
}
No depth buffer involved, identical on both backends, cheap enough
for every frame — hover highlights, crosshair targeting, AI
line-of-sight. The RayHit gives you the grid, the grid-local voxel,
the world-space hit point, the distance t, and the voxel’s colour.
Path 2: pick — depth-based. Read the rendered frame’s z-buffer
at the pixel, reconstruct the world point, resolve it to a voxel:
// Click-time picking: `pick` unprojects the pixel with
// the last frame's projection, reads its depth there,
// and resolves the hit to (grid, grid-local voxel).
// Depth is free on CPU; on GPU it blocks on a readback
// — a click-time call, not a per-frame one.
if let Some(hit) = renderer.pick(scene, &camera, mx, my) {
let grid = scene.grid_mut(hit.grid).expect("hit grid exists");
// Carve a crater, then re-light just the hole.
grid.set_sphere(hit.voxel, 4, None);
let r = IVec3::splat(4);
grid.bake_bbox(
hit.voxel - r,
hit.voxel + r,
roxlap_scene::BakeMode::Directional,
);
}
pick returns exactly what the player sees (it reads the frame that
was actually rendered), but its cost is backend-dependent: the CPU
backend reads an in-memory z-buffer (free), the GPU backend stages
the depth buffer and blocks on a device poll. That makes it a
click-time call, not a per-frame one — the
Feature::FreePickDepth probe from chapter 4 is the
queryable form of this distinction. On the wasm GPU path the
readback cannot block at all, so the pick answers with one frame of
latency — call it again next frame; the semantics table lives in the
Platforms chapter. Two more semantics to know:
sprites don’t occlude the pick (a cursor sprite under the pointer is
transparent to it), and the depth belongs to the last rendered
frame — pass the camera that frame used.
The lower-level pieces are exposed too: pick_depth (just the
distance), pixel_ray (just the un-normalised direction) — for tile
selection, intersect the view ray with a plane instead of the voxels.
World queries without a screen
Both scene-level queries work headless (tools, servers, AI):
Scene::raycast(origin, dir, max_dist)— the same march as path 1, minus the unprojection. Grid rotations are handled: the ray is transformed into each grid’s local frame, and the nearest hit across all grids wins.Scene::resolve_voxel(world, ray_dir)— “which voxel is this world point on?”: used internally bypickto turn a depth hit into a voxel address; useful whenever you already have a world point from other means. Theray_dirnudges the sample off the surface so a point on a face resolves to the solid side.Grid::voxel_solid/voxel_color— the point queries from chapter 3, for when you already know the grid.
Collision: use the controller
Movement collision is not a picking pattern anymore: the engine ships
CharacterBody — a walking body with move-and-slide collision,
gravity, jumping and step-up, covered in
the scene-graph chapter (with a runnable
book_controller example). Reach for the raw queries on this page
only for non-character shapes (projectiles want Scene::raycast,
area effects want the point queries).
Further reading
- The Picking demo scene — cursor-follow pick mode over a
multi-grid world
(
ROXLAP_SCENE=Picking cargo run --release -p roxlap-scene-demo). - docs.rs/roxlap-render (
pick,view_ray,pick_depth,pixel_ray,PickHit,Ray) and docs.rs/roxlap-scene (raycast,resolve_voxel,RayHit).