Scatter Demo
App: apps/scatter_demo/
Demonstrates scatter / placement (engine PLM-246 / ADR 0085), the last of the Block SPL
trio: Scatter.onSurface
projects grass across a heightfield terrain, and
Scatter.alongSpline
lines a denser strip of foliage along a terrain-following path — ~10,600 instances placed
two ways into one InstancedMesh, so the whole field is a single
GPU-instanced draw with per-instance frustum culling and wind sway.
Run from root
Section titled “Run from root”% ./plume3d scatter_demoA raised camera looks across the valley. The demo captures a screenshot at frame 60 — printing
the placed count and the culled visibleCount — and exits at frame 80.
What it does
Section titled “What it does”-
Builds a heightfield terrain as a visual
MeshGen.heightmapmesh and a matchingPhysics.addStaticHeightfieldcollider from the same heights (scatter’s ground projection only sees registered colliders). -
Creates one
InstancedMeshfrom a blade prototype (two crossed upright quads) with GPU instancing and per-instance culling on, then fills it twice:_foliage = _scene.createInstancedMesh("foliage")_foliage.setMesh(blade)_foliage.useGpuInstancing = true_foliage.setCullingEnabled(true)// 8000 blades projected onto the terrain surface (slight slope-align):var onSurf = Scatter.onSurface(_foliage, _scene, -18, -18, 18, 18, 8000, 1234) -
Builds a path
Splinewhose control points are snapped onto the terrain withPhysics.groundPoint, then lines a denser strip of foliage along it:for (p in pts) {var g = Physics.groundPoint(_scene, p[0], p[1])path.addPoint(p[0], (g == null ? 0 : g[1]) + 0.02, p[1])}var onPath = Scatter.alongSpline(_foliage, path, 2600, 3.0, 0.8, 1.5, 77)// 8000 + 2600 = 10,600 instances in one mesh -
Sets a global wind (
Graphics.setWind) the grass shader reads to sway the blades, and reads back the diagnosticvisibleCountto show per-instance culling dropping the off-screen blades.
- Scatter —
onSurface(project a rect of candidates onto the ground) andalongSpline(line instances along a curve), both filling theInstancedMeshin C++ and returning the count placed. - InstancedMesh —
createInstancedMesh,setMesh,useGpuInstancing,setCullingEnabled,visibleCount,instanceCount— the single instanced draw the scatter fills. - Physics —
groundPoint(snap the path’s control points),addStaticHeightfield(the collider scatter projects onto). - Spline, Graphics (
setWind), MeshGen (heightmap).
- One
InstancedMesh, filled twice. Both scatter calls target the same mesh, so all ~10,600 blades draw in a single GPU-instanced pass — enhanced instancing writes one instance SSBO per frame, so a scene’s scatter should share one mesh. The blade prototype’s own vertex colour shows through (scatter leavesbaseColorat the default); scale is baked into each transform, so the shader reads scale from the transform and uses a params slot only for wind. - Scatter needs a collider.
Scatter.onSurfaceand thegroundPointsnaps hit only registered colliders — the visual heightmap mesh alone is invisible to them. The matchingaddStaticHeightfield(same heights) is what the rays see. - Placement is seed-deterministic — the same seeds reproduce the same field every run.
See Scatter for the full placement surface, InstancedMesh for the enhanced-instancing draw it fills, and the Grass Field example for hand-placed enhanced instancing without scatter.