Guides
Rivers and lakes
A river is a ribbon built along a spline, and a lake is a flat surface inside a circle or a polygon. Both carry their own flow, which drives the ripples, the foam, the caustics and anything floating on them.
#Draw a river
addRiver() takes a centre line of THREE.Vector3 points. The y of each point is the water level there, not the bed. The system fits a centripetal Catmull-Rom curve through the points and resamples it by arc length, so the spacing of your points doesn't matter.
const river = water.addRiver({
name: 'creek',
path: [
new THREE.Vector3(-20, 1.2, -40),
new THREE.Vector3(-8, 0.8, -18),
new THREE.Vector3(4, 0.4, 0),
new THREE.Vector3(2, 0, 24),
],
width: 6, // full width in metres
depth: 1.5, // depth at the centre line (used by carveTerrain)
flowSpeed: 1.5, // surface speed at the centre, at the mean width (m/s)
});A path needs at least two points. The call returns a River and marks the system dirty. The mesh is built on the next update(), so you can add many bodies in a row at no extra cost.
#Width and depth along the path
Pass widths or depths instead of the constant to give one value per path point. Values between points are blended smoothly.
const pts = [0, 10, 20, 30, 40].map(z => new THREE.Vector3(Math.sin(z * 0.1) * 4, 0, z));
water.addRiver({
path: pts,
widths: [9, 6, 4, 6, 10], // a narrows in the middle
depths: [2.0, 1.4, 0.6, 1.4, 2.2],
});#How fast the water runs
flowSpeedis the centre-line speed at the river's mean width.- With
conserveFlow: true(the default) speed is inversely proportional to the width, which keeps the discharge constant. In the example above, the 4 m narrows run more than twice as fast as the 10 m pool at the end. - Speed falls off toward the banks, to about 65 % at the edge.
- Where the path descends, the ribbon gets a turbulence value from the slope. Rapids get extra ripple roughness and foam.
water.flowin the preset multiplies every speed at once, which is what the demo's Flow slider changes.
#Ends, fades and overlaps
| Option | Default | Use it to |
|---|---|---|
extend | 0.3 × half width | Run the ribbon past the first and last point, as one number or [start, end]. Use [0, 0] to stop exactly at a waterfall lip |
fade | 'auto' | Fade the ribbon's alpha in and out over [in, out] metres. 'auto' fades an end that lies inside a lake and leaves the others hard |
margin | 0.4 | Extend the ribbon past each bank, as a fraction of the half width. The terrain hides the extra, and the water never shows a hard edge |
Rivers draw over lakes where they meet, so a river that ends inside a lake fades into it. Two rivers overlapping at the same level z-fight. End one in a lake, or overlap them only where their levels differ.
#Add a lake
A lake is calm, flat water at one level. Give it a circle or an outline.
water.addLake({
center: new THREE.Vector3(0, 3, -24), // y is the level
radius: 9,
depth: 2.5,
});water.addLake({
polygon: [ // Vector2 (x, z) or Vector3 (y ignored)
new THREE.Vector2(-12, -30), new THREE.Vector2(6, -34),
new THREE.Vector2(14, -20), new THREE.Vector2(-4, -14),
],
level: 3,
depth: 3,
shelf: 5, // distance from the shore to full depth
});drift (default (0.03, 0.05) m/s) moves the surface texture slowly and pushes floaters. calm (default 1) removes the standing-wave detail that rivers have. Lakes also get softer normals and less foam by default (normalScale: 0.55, foamScale: 0.6).
y = 3: a circle of radius 10.5 m. The brook fades into it at the far end and the outlet leaves toward the lip in the foreground.#Chain bodies into a water system
The demo valley is built from five bodies in this order:
#A brook into the lake
A descending river whose last point lies inside the lake, so its end fades automatically.
#The lake
addLake({ center, radius: 10.5, depth: 3.2, drift }) at y = 3.
#An outlet to the lip
A short river at the lake's level with extend: [0, 0], starting inside the lake and ending at the waterfall's top.
#The cascade
addWaterfall() from the lip at y = 3 down to the pool at y = 0. See Waterfalls.
#The main run
A long river starting at the plunge point, at level 0.
#Change or remove a body
Bodies are built from their options once. To change a river's shape, remove it and add it again, then carve the terrain again if you use carveTerrain().
water.remove(river);
const river2 = water.addRiver({ ...options, widths: newWidths });#Ribbon attributes
If you write your own shader against the ribbon, these are its vertex attributes:
| Attribute | Contents |
|---|---|
aFlow | (across m, along m, speed, abs(lateral) / halfWidth): flow-space coordinates in metres |
aDir | (dir.x, dir.z, alpha): local flow direction and the end fade |
aTurb | Bed slope mapped to 0 – 1, which drives the rapids |
Lakes carry the same attributes, with the flow space aligned to their drift.