Repository navigation
Scanned buildings
extensions/scan/ and extensions/building/scan/ turn a Gaussian splat scan of a real building into a canonical building model with a static collider. They also cut it into parts to weigh, simulate and use again. The model has storeys, walls, slabs and a roof of a reusable construction kit. The structural, bearing, thermal and render views and the IFC writer then read the scan as they read a procedural tower. tools/building_scans.mojo downloads the scans, from SuperSplat and from any other URL, because they are too large for git.

The picture comes from examples/building_scan.mojo with no scan argument: a scan of a two-storey room that the example makes. Give it the id of a cached scan to fit that scan.

examples/scan_parts.mojo finds the ground of a scanned site, cuts the scan into parts and weighs them. It stands the largest part on its bed in a physics world. Then it draws the scan on every core, with levels of detail, and a copy of the part beside it. With no scan, it makes a hillside site with a hall and a tower. The C ABI's splat app draws a scan on a GPU; see C ABI and hosts.
| Id | Place | Source |
|---|---|---|
reading-room |
A reading room | SuperSplat scene 9d370db9
|
studio-11 |
A studio apartment, made in Unreal Engine 5 | SuperSplat scene c37cb759
|
pantheon |
The inside of the Pantheon in Rome | SuperSplat scene dac6e508
|
seem-church |
A brick church in Denmark, from a drone | SuperSplat scene e832997f
|
roxby-church |
St Nicholas Church, Roxby, in the North York Moors | SuperSplat scene 4c2fbd16
|
front-house |
The front of a timber house with a lawn | SuperSplat scene 6ec0c97a
|
The manifest pins the SHA-256 of every file of these scans. building_scans fetch downloads them into .cache/building-scans/, about 330 MB in all. Each scan's page on SuperSplat offers it for download under CC BY 4.0, and the tool refuses every other license. Seem Kirke and Roxby give the best pictures of a building from outside, and the Pantheon, the Reading Room and Studio 11 are inside. All six have up along -y, as SuperSplat's frame says. To add another scan, see How to add a scanned building.
Many newer scenes on SuperSplat are streamed levels of detail, with a lod-meta.json and a folder for each chunk. ThreeMojo does not read them yet. The tool refuses such a scene and says so.
| Module | What it gives |
|---|---|
scan/scene |
ScanScene, SplatModel and splat_model_from_comments
|
scan/files |
read_scan, parse_scan, scan_format and ScanFormat; see Compressed PLY files
|
scan/sog |
read_sog, parse_sog and parse_sog_bundle; see SOG files
|
scan/compressed_ply |
read_compressed_ply, parse_compressed_ply and is_compressed_ply; see Compressed PLY files
|
scan/parts |
scan_parts, scan_parts_on, part_mass, part_footing, part_body, part_bed and part_splats; see Scan parts
|
scan/terrain |
terrain_ground, TerrainOptions, GroundSurface, flat_ground and ground_grid; see Scan parts
|
scan/frame |
ScanFrame, ScanAxis, ScaleSource, supersplat_frame, estimate_heading and AxisMap
|
scan/clean, scan/orient, scan/place
|
clean_scan, estimate_up and scan_placement; see Cleaning and orienting scans
|
scan/walk |
Walker, walk_ends and WalkOptions; see Walking through a scan
|
scan/occupancy |
OccupancyGrid, OccupancyOptions, ScanRegion, trimmed_region and occupancy_grid
|
scan/colliders |
collision_triangles, scan_collider and add_scan_collider
|
building/scan/levels |
detect_levels, ScanLevels, LevelOptions and is_floor_face
|
building/scan/fit |
fit_building, ScanFit and ScanFitOptions
|
building/scan/catalog |
ScanCatalog, ScanEntry and space_use
|
building/kits |
construction_kit and KitKind; see Construction kits
|
building/views/bearing |
bearing_check and element_mass; see Bearing walls
|
Read the splats and the frame. Turn the frame by the walls' heading. Fill a grid, find the levels and fit the model.
var catalog = ScanCatalog.open(CATALOG_MANIFEST, CATALOG_CACHE)
var entry = catalog.find("studio-11")
var splats = read_scan(catalog.scene("studio-11").value()).geometry.copy()
var frame = entry.frame
frame.heading = estimate_heading(splats, frame)
var options = default_occupancy_options()
var region = trimmed_region(splats, frame, options)
var grid = occupancy_grid(splats, frame, options, region)
var levels = detect_levels(grid, default_level_options())
var fit = fit_building(
grid,
levels,
construction_kit(entry.kit),
frame.scale,
ScanFitOptions("studio", site, entry.use, 0.01, default_level_options()),
)
var check = bearing_check(fit.building, default_bearing_options())fit.notes says what a reader must know before using a number from the model. Read them. fit.fill gives the share of each storey's plan that the scan saw as floor.
add_scan_collider adds one static triangle mesh body to a PhysicsWorld. Its triangles are the exposed faces of the grid's solid voxels, in building coordinates.
var world = PhysicsWorld()
_ = add_scan_collider(world, grid, PhysicsMaterial.default())A face is exposed when the voxel beyond it is empty or outside the grid. In each slice and direction, the exposed faces merge into rectangles, so a flat floor is two triangles. Each triangle's front faces out of the solid, as a mesh shape reads it.
A scan holds surfaces, not solids. A solid voxel is a shell one voxel thick. A fast small body can pass through it between two steps. Set the world's collision_detection to SPHERE_MESH_CCD for one. See Continuous collision.
A ScanFrame maps scan coordinates to building coordinates: meters, z up, +y north. It takes away origin, reads the building axes along up and north, multiplies by unit, and turns the plan by heading. +x is north cross up, so the axes are always right-handed.
supersplat_frame is the frame of a scene that looks upright in SuperSplat: up along the scan's -y, north along -z, one unit to the meter. SuperSplat shows a SOG or PLY scene turned half a turn about z, as the PlayCanvas engine shows a PLY file.
A photo scan has no scale of its own. scale is a ScaleSource:
| Source | Meaning |
|---|---|
SCALE_DECLARED |
A publisher's convention, such as one unit to the meter, that nobody checked |
SCALE_MEASURED |
A person measured a length in the place and in the scan |
SCALE_ASSUMED |
A length was assumed, such as a storey height or a published size, and matched to the scan |
fit_building writes a note for a declared or assumed scale. A load or a mass grows with the cube of a scale error.
estimate_heading finds the walls' plan angle. A wall splat is at least four times longer than thick, in plan and up the wall. Each one adds its opacity, times how much longer than thick it is, to the bin of its long axis's angle. The angle is modulo a quarter turn, in one-degree bins. The weighted mean angle of the five heaviest neighboring bins gives the heading, from -45 to +45 degrees.
An OccupancyGrid divides a box of building coordinates into cubes of one edge, the voxels. Each splat adds its opacity to the voxel of its center. It also adds it to the voxels within one standard deviation along each axis, up to reach voxels on each side. A voxel whose sum reaches solid_weight is solid.
| Option | Default | Effect |
|---|---|---|
voxel |
0.1 m | The voxel edge |
min_opacity |
0.25 | A fainter splat adds nothing; most floaters are faint |
solid_weight |
0.5 | The summed opacity of a solid voxel |
reach |
2 | The most voxels a splat reaches on each side |
trim |
0.005 | The share of splats left out at each end of each axis by trimmed_region
|
max_voxels |
2^27 | A region that needs more voxels is refused |
The grid is dense, four bytes a voxel. Grow the voxel for a large scan, such as a cathedral. The example doubles it until the region fits.
detect_levels looks down each column of voxels. A solid voxel with an empty voxel above it has a face that looks up. It is a floor face when the next solid voxel above it is at least min_clear above it. A ceiling face is the same, upside down.
This rule leaves out two faces that look like floors. The face on top of the highest ceiling looks up at nothing. The top of a ceiling, under the unseen inside of a slab, has the floor above it too close.
| Option | Default | Effect |
|---|---|---|
min_fraction |
0.25 | The least share of the plan columns that a floor or a ceiling covers |
min_storey |
2 m | Floors closer than this are one floor |
min_clear |
1 m | The least empty height above a floor face or below a ceiling face |
A layer is a floor when its count of floor faces reaches min_fraction of the solid plan columns. Of floors closer than min_storey, the layer with the most faces stays, and a tie keeps the lower layer. A table top covers too little of the plan to count.
A drone flies around a building, not through it, so no floor has a ceiling above it. Then the lowest layer whose upward faces reach min_fraction is the ground, and ScanLevels.exterior is True.
fit_building makes one storey for each floor, and one space for each storey.
- A storey ends at the next floor. The top storey ends at the first ceiling above its floor, or at the top of the scan.
- A storey's plan is the rectangle that holds its floor faces. A fraction
trimof those columns is left out at each end of each axis. - An outside scan's plan holds the columns with a solid voxel at least half of
min_storeyabove the ground. - The walls, slabs and roof come from
assemble, with the kit's construction set.
Furniture hides the floor under it, so the rectangle is the floor and not only what the scan saw. A wall's voxels lie on both sides of the wall's surface, so the plan of a room is its inside, less half a voxel.
assets/building_scans/manifest.json names each scan: its source, credit, license, kit, use, frame and files. A scan's files are one splat file of any format that read_scan reads, or a SOG folder: one meta file and its image files.
scene is a SuperSplat scene id, or null for a scan from elsewhere. ScanCatalog.open reads the manifest as typed entries. It refuses a kit, a use or a frame that the kits and frames refuse. ScanEntry.file is the file that reads the scan. catalog.scene(id) gives its path in the cache, or nothing.
tools/building_scans.mojo downloads and checks the files, with the rules of tools/carla_assets.mojo:
sh tools/tool.sh .venv/bin/mojo building_scans check
sh tools/tool.sh .venv/bin/mojo building_scans status
sh tools/tool.sh .venv/bin/mojo building_scans fetch [ID ...] [--pin | --offline]
sh tools/tool.sh .venv/bin/mojo building_scans verify [ID ...] [--strict]
sh tools/tool.sh .venv/bin/mojo building_scans credits [--all]- An entry with no files is resolved only with
--pin. The tool reads its SuperSplat scene page, finds thecontentUrlthat its viewer loads, and adds the scene'smeta.jsonand images as files. An entry with no files and no scene is an error. - A file is written to the cache only after its SHA-256 matches. A verified file is never overwritten.
- A file with no sum is fetched only with
--pin, which writes the sum into the manifest. - A file whose URL is null is not hosted. Put it in the cache by hand, and the next fetch verifies it.
The tool refuses every license other than CC BY 4.0 and CC0 1.0. A CC BY-ND scene forbids changes, and a CC BY-NC scene forbids commercial use. See How to add a scanned building.
building_scans credits prints the credit of each cached scan. assets/building_scans/ATTRIBUTION.md holds the credit of every entry. Credit the author, link the source and the license, and say what you changed, when you share a scan or a render of it.
- Each storey has one space, and its plan is a rectangle on the building axes. Interior walls are not found.
- A sloped roof, a dome or a vault is the top of a storey, not a roof shape.
- The constructions come from the kit. They are assumed, not measured.
- The scale of a scan is only as good as its source. See the table of scale sources.
- An outside scan gives one storey from the ground to the top. A hillside has no flat ground layer: find its ground with
terrain_groundand cut its parts withscan_parts_on, asexamples/scan_parts.mojodoes. - The grid is dense, so a large scan needs a larger voxel.
See Why scans become building models for the reasons behind the design.
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