Skip to content

Water Playground

App: apps/water_playground/

The interactive water capstone (engine Block WTR2 — Water Fidelity). Where the Water Demo shows the two surface variants side by side on a fixed path, the playground hands you the camera: free-fly across the water and dive beneath it, flip the Realistic ↔ Toon variant and the weather at runtime, and spawn buoyant crates and barrels that bob and rock on the very waves the surface is rendering. It composes every piece of the water program in one scene:

  • the two stylized water variants (the Water Demo surface pack);
  • camera-submersion detection via Graphics.setWaterPlane + Graphics.cameraWaterState (ADR 0094);
  • an underwater post effect cross-faded by the submersion amount;
  • a camera-facing waterline meniscus quad (the half-in/half-out line at the surface);
  • Jolt buoyancy — Physics.applyBuoyancy against a Water so the floaters ride the same Gerstner waves the shader draws;
  • a small pure-Wren weather driver (clear / overcast / storm) over the atmosphere knobs.
Terminal window
% ./plume3d water_playground

On launch it runs a short scripted tour — spawns floaters, shows both variants and a storm, dives under the surface, and captures screenshots — so a headless run self-verifies. Pressing any control key hands control to the player.

KeyAction
W A S Dmove (free-fly)
Q / Edescend / ascend (dive below, rise above)
Arrow keyslook (yaw / pitch)
Spacespawn a buoyant crate in front of the camera
Bspawn a buoyant barrel
1 / 2Realistic / Toon water variant
Tabcycle weather (clear → overcast → storm)
Rreset the floaters
Escquit
  • One water surface, both variants. A single large subdivided MeshGen.plane is marked transparent and tuned through the set-5 custom params (the same param layout the Water Demo documents, including the real Perlin foam textures in custom slots 0/1). Pressing 1/2 swaps which shader it is drawn with — shaders/water_realistic or shaders/water_toon — over the same mesh and the same waves.

  • Registers the water surface for submersion. At init it registers the infinite water plane and its underwater fog, then enables planar reflection and opaque capture (the gate the water shaders need):

    Graphics.setPlanarReflection(true, 0.0)
    Graphics.setWaterPlane(0.0, 0.05, 0.24, 0.32, 0.5) // surfaceY, fog rgb, fog density
    Graphics.opaqueCaptureEnabled(true)
  • One Water drives both the render and buoyancy. The Water CPU wave model is set to the same wave params as the realistic variant’s p3, so a floater bobs on the exact crests you see. Spawned crates/barrels are dynamic Jolt bodies; every frame Physics.applyBuoyancy samples probe points across each body against that Water, so they float, bob, and rock:

    _water = Water.new()
    _water.setWaves(0.6, 13.0, 0.55, 1.0) // matches the realistic p3
    _water.setWind(1.0, 0.25)
    _water.setBaseY(0.0)
    // …per frame, per floater:
    Physics.applyBuoyancy(node, _water, _t, probeOffsets, strength, density)
  • Dive below and the frame goes underwater. Each frame draw() reads the submersion state and cross-fades the underwater post on the amount value — so crossing the surface is a smooth fade, not a hard cut — feeding it the fog colour/density the plane was registered with:

    var ws = Graphics.cameraWaterState() // [submerged, amount, level, fogR, fogG, fogB, fogDensity]
    // …draw seabed + rocks + floaters (opaque), then the water surface (transparent) while shallow…
    if (ws[1] > 0.15) {
    var k = /* smoothstep on ws[1] */
    Graphics.addPostEffect("shaders/underwater", [ws[3], ws[4], ws[5], ws[6], 0.006, ws[1], 0.5, 120.0, 0.6 * k, 0.4 * k, 0.0, 0.0])
    } else {
    Graphics.addPostEffect("shaders/present", [])
    }

    The underwater post tints the frame toward the deep-water colour and adds fog, a caustic shimmer and god-rays, all faded in by the submersion amount. The water surface mesh is skipped once the camera is deeply submerged (its underside would band), so from below you see the fog and the meniscus rather than the plane’s back face.

  • The waterline meniscus. A small camera-facing quad (shaders/water_line) is drawn pinned in front of the camera; the shader raises a deep-water fill from the bottom as the camera’s world Y drops through the surface, giving the half-in / half-out waterline you see when your eye is right at the surface. It is a plain set-5 custom-material quad — no engine change — the camera Y is written into a param each frame.

  • Runtime weather. Tab cycles a pure-Wren weather driver (clear / overcast / storm) that drives only the engine’s neutral atmosphere knobs — setSky, setFog, setAmbient and global setWind (which also re-steers the water waves) — plus a storm lightning flash. The same policy-over-neutral-knobs pattern as the Sky / Weather and Weather packs.

  • The surface look is pure content; the submersion state is the engine bit. The two water variants, the underwater post, and the meniscus are composed from already-shipped capabilities (set-5 custom params, scene depth, opaque capture, planar reflection). The only new engine surface this app relies on is the WTR2 camera-submersion API — Graphics.setWaterPlane and Graphics.cameraWaterState (additive, frozen; ADR 0094) — plus the Water + buoyancy feature (ADR 0091).
  • Underwater is a screen-space approximation. The fog / caustics / god-rays live in a post effect, so they are screen-locked; a dedicated world-space underwater pass is a documented follow-up (ADR 0094). It reads convincingly for a dive, not as a full submerged-scene render.
  • Submersion uses an infinite plane. This demo registers a setWaterPlane; bounded water volumes (a box-tagged pool that also bounds “underwater” to the box footprint) are not yet wired into cameraWaterState (see the Graphics water API note).
  • Cam is not used here. Like the other immediate-mode water demos it drives view/projection explicitly each frame (Graphics.setViewMatrix / setProjectionMatrix) rather than Scene.draw() with a Cam rig, because it composes the opaque/transparent phases and the post swap by hand.