Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 by pick to turn a depth hit into a voxel address; useful whenever you already have a world point from other means. The ray_dir nudges 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).