Repository navigation
Walking through a scan
extensions/scan/walk.mojo puts a person in a scanned building, as a game does. The person is a capsule in a physics world that holds the scan as a static collider. A tick of the world is a tick of the game loop. A camera at the walker's eye gives the first-person view.

The picture comes from examples/scan_walk.mojo. The scan is "Studio 11" by milanoski, under CC BY 4.0. The walker crosses the living room, with a rug, an armchair and a television, and turns round at a wall.
var scan = open_scan("studio-11")
var filled = fill_grid(scan)
var walker = Walker(
filled.grid,
filled.floor,
Vec3d(filled.region.low.x, filled.region.low.y, 0),
Vec3d(filled.region.high.x, filled.region.high.y, 0),
default_walk_options(),
PhysicsMaterial.default(),
)
for _ in range(frames):
walker.step(Duration(Float32(1.0 / 60), SECOND))
var eye = walker.eye() # meters east, north and up
var ahead = walker.forward() # a unit vector in the plan
# Draw the scene from the eye.open_scan and fill_grid are in tools/_scan_demo.mojo, which the examples share. They read a scan, clean it, and fill its occupancy grid with the voxel grown until it fits. A caller with its own grid passes it to Walker.
A tick does three things.
- The walker turns toward its goal, at most
turnradians a second. It stops and turns where its way is more than 0.6 radians from the goal. - It sets its horizontal velocity to
speedalong the way it faces. - The world steps. Gravity, the floor, the walls and the furniture of the scan decide the rest.
The capsule cannot turn over: its inverse inertia is locked, as locked_inverse_inertia gives it. The walker turns round when it comes within 0.3 m of an end.
The goal is one of two points of the floor. walk_ends finds them from the occupancy grid.
- A plan point is clear when the floor is under it. No solid voxel is within the walker's radius, from
steptoclearanceover the floor. - The clear points are tried on a lattice,
spacingapart. - The first end is the clear point nearest the middle of the clear points.
- The second is the clear point farthest from the first with a clear line between them.
is_walkablechecks the line every 0.1 m. - The first end then moves to the point farthest from the second in the same way.
A scan with fewer than two clear points has no walk, and walk_ends refuses it.
is_clear tests one point, and is_walkable tests one line. Each checks its options first. They refuse options that WalkOptions.check refuses.
WalkOptions holds sizes and paces as typed quantities. default_walk_options() gives an adult.
| Option | Default | Meaning |
|---|---|---|
radius |
0.3 m | The capsule's radius |
height |
1.8 m | Feet to crown |
eye |
0.75 m | The eye over the capsule's center, inside the capsule |
speed |
1.4 m/s | The walking pace |
turn |
2.5 rad/s | The most the walker turns in a second |
step |
0.25 m | The lowest solid voxel that stops the walker. A lower one is a step |
clearance |
1.7 m | The height over the floor that must be clear |
spacing |
0.5 m | The distance between the points tried for the ends |
mass |
80 kg | The walker's mass |
The numbers come from a Linux machine with 24 threads and an NVIDIA TITAN RTX. The scan holds 4,759,156 splats after the cleaning. Its tree for levels of detail holds 6,301,303.
| Draw | Size | Physics | Draw | The loop |
|---|---|---|---|---|
scan_walk.mojo, on every core |
480 by 270 | 0.7 ms | 24.3 ms | 40 frames a second |
scan_walk_gpu.mojo, on the GPU |
1280 by 720 | 0.7 ms | 13.2 ms | 72 frames a second |
The physics of the whole scan, as one static mesh of merged faces, costs about a millisecond a tick. The draw is the rest. See GPU splats for where its time goes.
- The walker follows two points and no more. It is a test of the scan's collider and of the frame rate, not a path finder.
- A scan holds surfaces, and a collider made of its voxels is a shell one voxel thick. A walker that walks at 1.4 m/s does not pass through it. A fast body can: see Scanned buildings.
- A stair is a sequence of steps. A step higher than
stepstops the walker. - A scan has holes where the camera did not see. A hole in a wall is a door to the walker.
Tutorials
How-to
- Install
- Run the checks
- GPU backend
- Coverage
- Measure examples
- Add a feature
- Add a scanned building
- Write documentation
Reference
- Scene graph
- Rotations
- Cameras
- Geometry
- Geometry addons
- Lofts and clipping groups
- Sculptor
- Procedural generators
- Meshes and assets
- Lines
- Points and sprites
- Helpers
- Scene objects
- Animated objects
- Raycasting
- Curves and paths
- Animation
- Skinning
- Materials
- Node materials
- TSL functions
- Lights
- Lighting addons
- Fog
- Textures
- Renderer
- Renderer hooks and material flags
- Rasterization
- GPU backend
- Render target
- Post-processing
- GPU computation
- Compute nodes
- Progressive light map
- Gaussian splats
- Voxel global illumination
- SVG renderer
- Image files
- Image decode queue
- Model files
- More model files
- Exporters
- Scene JSON
- Windowing and controls
- C ABI and hosts
- Math
- Norm consumers
- Math addons
- Units
- Coverage tool
- Commands
- Examples
- Benchmarks
- Extensions
- Shared physics
- Physics query snapshots
- Continuous collision
- Tick force restoration
- Water
- Vegetation
- Village
- Streamline
- Animals
- Buildings
- CARLA
- Femur
- Tibia
- Fibula
- Patella
- Knee
- Muscles
- Vessels
- Lymph
- Nerves
- Integument
- Leg
- Foot
- Pelvis
- Torso
- Arm
- Hand
- Head
- Genome
- Game humanoid
- Humanoid fidelity
- Mixamo animations
- Audio-aligned game faces
- Converted assets
- Mesh quality
- Garments
- Outfits and armor
- Cloth and spring bones
- Attire estimates
- Segment inertia
Explanation
- Software rasterizer
- Scene graph as an array
- Linear color
- Normals are not colors
- Perspective correction
- Fixed-point coverage
- Per-fragment shading
- Mipmaps
- Sorted transparency
- Types and checks
- Render hooks as a trait
- Shared CPU and GPU code
- Bands and threads
- PNG structure
- Extensions beside the port
- Scans as building models
- CUDA teardown hang
- Mojo compiler hang