Skip to content

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.

Terminal window
% ./plume3d scatter_demo

A 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.

  • Builds a heightfield terrain as a visual MeshGen.heightmap mesh and a matching Physics.addStaticHeightfield collider from the same heights (scatter’s ground projection only sees registered colliders).

  • Creates one InstancedMesh from 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 Spline whose control points are snapped onto the terrain with Physics.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 diagnostic visibleCount to show per-instance culling dropping the off-screen blades.

  • Scatter — onSurface (project a rect of candidates onto the ground) and alongSpline (line instances along a curve), both filling the InstancedMesh in 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 leaves baseColor at 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.onSurface and the groundPoint snaps hit only registered colliders — the visual heightmap mesh alone is invisible to them. The matching addStaticHeightfield (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.