Foliage + Terrain Demo
App: apps/foliage_terrain_demo/
Demonstrates the Block BLD Blender import end to end (engine PLM-249/251/252/253/254/267):
FoliageTerrain.blend carries a sculpted terrain mesh tagged plume3d_terrain, a hidden
grass-blade prototype, a Plume_Scatter Empty baking 256 blades seated on the terrain
surface, and two static rock props that each carry a
Blender-authored collider
(rock_a tagged plume3d_collider = "sphere", rock_b tagged "box", engine PLM-267). This
whole level comes in with one call:
_blend = _scene.loadBlendScene("Models/FoliageTerrain.blend")Scene.loadBlendScene composes the four BlendResult
primitives — it instantiates the terrain (a render node and an automatic static concave
triangle-mesh Jolt collider, creating the scene’s physics world if absent, via
instantiateTerrain), the baked scatter (one
GPU-instanced grass draw, via
instantiateScatter), and the static rock props
(via instantiateUnits) — and returns the BlendResult.
Coordinates convert automatically (Blender Z-up → engine Y-up). Call the primitives individually
when you want finer control (as this demo did before loadBlendScene existed).
Run from root
Section titled “Run from root”% ./plume3d foliage_terrain_demoThe demo drops three orange physics spheres from above; they settle on the hills at varying heights (printed, and drawn as boxes at their rest positions). It also drops a green probe straight onto each rock — proving the two rock props’ Blender-authored colliders. It captures a screenshot at frame 180 and exits at frame 200.
What it does
Section titled “What it does”-
Loads a grass shader (active for the instanced foliage) and a lit shader, then loads the entire level with a single
loadBlendScenecall — terrain (render + collider), grass (one instanced draw), and the two rock props — printing the blend’s node count. -
Drops physics probe spheres from just above the hilltops to prove the terrain collider:
for (p in [[-3.0, -2.0], [0.5, 1.5], [3.0, -1.0]]) {var n = _scene.addNode("probe")n.setPosition(p[0], 3.5, p[1]) // start above the hillsPhysics.addDynamicSphere(_scene, n, 0.35, 1.0)_probes.add(n)}At frame 180 each probe’s rest height is printed — they settle at the terrain’s varying surface height rather than falling through, which is the collider working.
-
Proves the rock props’ Blender-authored colliders (engine PLM-267): each rock prop carries a
plume3d_collidertag, soinstantiateUnitsbuilds a static Jolt collider sized from its bounds. The demo looks each rock up bynode_id, drops a green probe straight down onto it, and at frame 180 prints how far the probe rests above the rock centre —> 0means it landed on the rock (aspherecollider onrock_a, aboxonrock_b) instead of falling past:for (rp in [["rock_a", "sphere"], ["rock_b", "box"]]) {var rock = _scene.findNodeById(rp[0])var pos = rock.getPosition()var n = _scene.addNode("rockprobe")n.setPosition(pos[0], pos[1] + 4.0, pos[2]) // straight above the rockPhysics.addDynamicSphere(_scene, n, 0.3, 1.0)} -
Sets a global wind (
Graphics.setWind) and draws the scene (terrain + grass + rock props) plus the probe boxes at their physics-updated positions.
- Scene —
loadBlendScene(load a whole Blender environment — terrain + scatter + props — in one call). - BlendResult — the primitives
loadBlendScenecomposes:instantiateTerrain(renderable ground + an automatic static triangle-mesh collider, creating the physics world if needed),instantiateScatter(baked foliage as one GPU-instanced draw), andinstantiateUnits(the static rock props — each with a Blender-authoredplume3d_collider). - Physics —
addDynamicSphere(the probe spheres that rest on the collider). - InstancedMesh, Graphics (
setWind), MeshGen (boxfor the probe visuals).
- What this demo proves: one
loadBlendScenecall brings in a Blender-authored level — the terrain surface and grass and rock props all render,instantiateTerrainbuilds an automatic physics collider from the tagged mesh (proven by the orange probe spheres resting on the hilly surface at varying heights), the grass follows the terrain contour, and the two rock props are theinstantiateUnitswitness — and, tagged withplume3d_collider, each also gets a configurable static collider so a green probe rests on the rock (asphereonrock_a, aboxonrock_b), the movement-blocking half of Block BLD. class_name-scripted units stay explicit.loadBlendScenereturns theBlendResultso a caller instantiates characters/cameras/scripted props by hand withblend.instantiate(name, _scene)— they are intentionally not auto-instantiated. Give any such unit aclass_name(not just anode_id), orinstantiate-by-node_idafter the whole-scene load would draw it twice (see the caveat).
See Scene.loadBlendScene and BlendResult for the full blend surface, the Blend Scatter Demo for the baked-scatter path on its own, and Physics Mesh Terrain for a script-built triangle-mesh collider.