Buoyancy Demo
App: apps/buoyancy_demo/
Demonstrates Jolt buoyancy on the stylized water (engine PLM-270 / PLM-271, ADR 0091,
Block WATER) — floating bodies that bob on the exact Gerstner waves the water shader
renders. A single Water holds the wave params that drive both the
rendered surface (water.applyToMesh → the mesh’s set-5 p3)
and the CPU height query (Water.heightAt), so there is
one source of truth and floats can’t drift off the visible waves.
The demo makes the two behaviours legible side by side:
- Rafts rock. Two flat, wide rafts each sample their four bottom corners, so differential submersion across the footprint produces real rocking/tilting torque — a wave lifts one corner before the other and the raft tips.
- Cubes bob at different heights. Three cubes sample a single centre point (a plain
bob) at densities
0.35,0.7, and1.25. The light one floats high and pops out snappily; the dense one sits low and bobs sluggishly.
Run from root
Section titled “Run from root”% ./plume3d buoyancy_demoThe rafts and cubes fall onto the water, settle, and start riding the swell; the demo lets
them settle, screenshots buoyancy_demo.png, and exits (around frame 220).
What it does
Section titled “What it does”-
Builds one
Wateras the single source of truth, and pushes its waves into the water mesh so render and query match. Wind is global, so it is set on both theWaterand the renderer with the samex, z:_water = Water.new()_water.setWaves(0.55, 13.0, 0.34, 1.2) // gain, wavelength, steepness, speed_water.setWind(1.0, 0.3) // matches Graphics.setWind's x,z_water.setBaseY(0.0)_water.applyToMesh(_waterMesh) // p3 waves → the rendered surface matches the queryGraphics.setWind(1.0, 0.0, 0.3, 1.0) // wind is global — keep it in sync -
Creates the floaters as dynamic Jolt bodies. Rafts get a flat wide box body and a set of four corner probes; cubes get a small box body and an empty probe list (a single centre bob). Angular/linear damping settles the motion:
// Raft: 4 bottom-corner probes (flat [x,y,z] × 4) → it rocks._raftProbes = [-2.1, -0.25, -2.1, 2.1, -0.25, -2.1,-2.1, -0.25, 2.1, 2.1, -0.25, 2.1]Physics.addDynamicBox(_scene, raftNode, 2.3, 0.25, 2.3, 26.0)Physics.setDamping(raftNode, 0.7, 0.9)// Cube: empty probe list → a single centre bob; density sets how it floats.Physics.addDynamicBox(_scene, cubeNode, 0.6, 0.6, 0.6, 4.0) -
Applies buoyancy every frame, per body — the corner probes make a raft rock, the empty list makes a cube bob, and
densityis the per-body float knob:_t = _t + dtfor (f in _floaters) {// f = [node, mesh, probes, strength, density]Physics.applyBuoyancy(f[0], _water, _t, f[2], f[3], f[4])} -
Draws the floaters at their full body transform (
node.getWorldMatrix()), so the rocking — the body’s rotation, not just its height — is visible. The seabed is opaque (the water reads its depth for the shoreline/foam) and the water is drawn transparent last, under theGraphics.opaqueCaptureEnabled(true)gate:Graphics.opaqueCaptureEnabled(true)Graphics.drawMesh(_seabed, _lit, /* … */) // opaque (depth for the shoreline)for (f in _floaters) Graphics.drawMesh(f[1], _lit, f[0].getWorldMatrix())Graphics.drawMesh(_waterMesh, _waterShader, /* … */) // transparent water lastGraphics.addPostEffect("shaders/present", [])
The two knobs that shape the feel
Section titled “The two knobs that shape the feel”| Knob | Effect |
|---|---|
probe count / layout (offsets) | Several probes across the footprint → rocking/tilting (a wave lifts one corner first). An empty list [] → a single centre probe, a plain bob. |
density (relative to water, default 1.0) | Force per probe is strength · submersion / density. Low → floats high, pops out fast, snappy. High → sits low, bobs sluggishly. Clamped ≥ 0.05. |
Water— the CPU mirror of the water shader’s waves;setWaves/setWind/setBaseY/applyToMesh/heightAt. OneWaterdrives render and query.Physics.applyBuoyancy— multi-point Jolt buoyancy:offsetsprobe points,strength, and thedensityresponsiveness knob.Physics.addDynamicBox/setDamping— the floating bodies and their settle damping.- MeshGen —
plane(the water + seabed) andbox(the rafts + cubes). - Water Demo — the stylized water surface this floats on.
Notes and limitations
Section titled “Notes and limitations”- Render and query share one
Water.applyToMeshwrites the waves into the mesh’s set-5p3andheightAtreads the same params, so a float bobs on the waves you see. If you re-tune the waves, callapplyToMeshagain. - Buoyancy is a per-frame force. Like
addForce, callapplyBuoyancyevery frame; it stops floating the moment you stop calling it. - v1 samples in the body’s level frame — probes are placed un-rotated; transforming them by the body’s full orientation is a documented refinement (the differential submersion still drives visible rocking).
- Still a PLM-273 follow-up list. Rain bloops on the water, a masked open-hull boat that keeps water out (see the Water Mask demo), custom water textures, and per-bend river flow are documented follow-ups, not yet shipped.