Skip to content

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, and 1.25. The light one floats high and pops out snappily; the dense one sits low and bobs sluggishly.
Terminal window
% ./plume3d buoyancy_demo

The 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).

  • Builds one Water as 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 the Water and the renderer with the same x, 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 query
    Graphics.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 density is the per-body float knob:

    _t = _t + dt
    for (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 the Graphics.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 last
    Graphics.addPostEffect("shaders/present", [])
KnobEffect
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. One Water drives render and query.
  • Physics.applyBuoyancy — multi-point Jolt buoyancy: offsets probe points, strength, and the density responsiveness knob.
  • Physics.addDynamicBox / setDamping — the floating bodies and their settle damping.
  • MeshGen — plane (the water + seabed) and box (the rafts + cubes).
  • Water Demo — the stylized water surface this floats on.
  • Render and query share one Water. applyToMesh writes the waves into the mesh’s set-5 p3 and heightAt reads the same params, so a float bobs on the waves you see. If you re-tune the waves, call applyToMesh again.
  • Buoyancy is a per-frame force. Like addForce, call applyBuoyancy every 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.