terrain-bands
terrain-bands
PRIMITIVES — "One ground, three worlds." Three scenes on one tag: <heightfield>. A seeded-fBm plane is terrain, mountains and sea depending on two numbers — amplitude and ridged — and on the material you hang on it.
Read the rig's notes
1. TERRAIN (7.0s) — rolling dune country at amplitude 260, ridged 0.
Vertex colours ramp sand→ochre with a sun-dot shade
term, exactly the three.js terrain demo's canvas
shading, computed per vertex instead.
2. MOUNTAINS (7.0s) — the SAME generator at amplitude 980, ridged 1. The
1-|n| crease is the only thing that changed; fog
does the rest by stacking the ridgelines.
3. SEA (7.5s) — amplitude 22, animate 0.35, over a static seabed.
Displacement moves to the GPU here and rides inside
a MeshPhysicalMaterial, so transmission, ior and the
HDR environment survive on a MOVING surface — the
swell reflects and refracts instead of just wobbling.
Chains (match key → scenes):
land S1 dunes → S2 range → S3 sea surface. One classic match-move all the
way: the ground never leaves frame, it only changes what it is.
haze-a / haze-b — every scene, colour/pose morph.
Look: golden-hour desert cooling into night water.
S1 sand #efd1b5 sky over #c98f63/#7a4a33 ground · S2 the same sky pushed cold
(#cbb6c8 → #4a3a52) · S3 night water #0b1a2e/#123a5c under a #dfe8f5 horizon.
Every scene carries the same two-lobe haze rig:
haze-a warm key glow, sized so the radial falloff is in frame
haze-b the cold counter-lobe, low and wide behind the ridgeline
Units: THE MEASUREMENT STANDARD — the ground is 7500mm × 7500mm and the
camera stands 620mm above it, i.e. a 7.5m tabletop landscape seen from
knee height. Fog density 0.00026 puts the e-fold at ~3.8m, which is what
makes the far edge of the plane dissolve instead of ending on a hard line.
Multiply size, camera and amplitude by 10 (and divide density by 10) for an
epic-vista read at identical proportions.
── AUTHORED AS BANDS ────────────────────────────────────────────────────
This is promos/primitives/terrain/terrain.promo with every field written as a
TABLE OF NUMBERS instead of named scalars. perlin / fBm / ridging are not
three operators — they are one sum of noise bands, and a band is four
numbers:
bands // freq amp fold warp
4.5 368.59 0 0 one row = perlin
22.5 73.72 0 0 several = fBm, and the row RATIOS
112.5 14.74 0 0 are lacunarity and gain
colors // at colour
0 #6b3f2c
1 #f2d9bd
`amp` is that band's real contribution, so `amplitude` is the column sum.
`fold` is ridging. Nothing is named; the operator is the row's shape.
The ppug codec already parses this — any tag whose children are numeric
rows is a property channel — and packages/core/xml-scene.ts lowers the table
back onto the same generator scalars, so this reel and terrain.promo
parse to the SAME document and render the same frames. Every cell is
printed to the shortest decimal that still recovers those scalars at
float32, the width of a GLSL uniform.
Preview: /packages/runtime/index.html?project=promos/primitives/terrain/terrain-bands.promo- Topic
- terrain
- Scenes
- 3
- Kind
- reference rig
//
PRIMITIVES — "One ground, three worlds." Three scenes on one tag:
<heightfield>. A seeded-fBm plane is terrain, mountains and sea depending on
two numbers — amplitude and ridged — and on the material you hang on it.
1. TERRAIN (7.0s) — rolling dune country at amplitude 260, ridged 0.
Vertex colours ramp sand→ochre with a sun-dot shade
term, exactly the three.js terrain demo's canvas
shading, computed per vertex instead.
2. MOUNTAINS (7.0s) — the SAME generator at amplitude 980, ridged 1. The
1-|n| crease is the only thing that changed; fog
does the rest by stacking the ridgelines.
3. SEA (7.5s) — amplitude 22, animate 0.35, over a static seabed.
Displacement moves to the GPU here and rides inside
a MeshPhysicalMaterial, so transmission, ior and the
HDR environment survive on a MOVING surface — the
swell reflects and refracts instead of just wobbling.
Chains (match key → scenes):
land S1 dunes → S2 range → S3 sea surface. One classic match-move all the
way: the ground never leaves frame, it only changes what it is.
haze-a / haze-b — every scene, colour/pose morph.
Look: golden-hour desert cooling into night water.
S1 sand #efd1b5 sky over #c98f63/#7a4a33 ground · S2 the same sky pushed cold
(#cbb6c8 → #4a3a52) · S3 night water #0b1a2e/#123a5c under a #dfe8f5 horizon.
Every scene carries the same two-lobe haze rig:
haze-a warm key glow, sized so the radial falloff is in frame
haze-b the cold counter-lobe, low and wide behind the ridgeline
Units: THE MEASUREMENT STANDARD — the ground is 7500mm × 7500mm and the
camera stands 620mm above it, i.e. a 7.5m tabletop landscape seen from
knee height. Fog density 0.00026 puts the e-fold at ~3.8m, which is what
makes the far edge of the plane dissolve instead of ending on a hard line.
Multiply size, camera and amplitude by 10 (and divide density by 10) for an
epic-vista read at identical proportions.
── AUTHORED AS BANDS ────────────────────────────────────────────────────
This is promos/primitives/terrain/terrain.promo with every field written as a
TABLE OF NUMBERS instead of named scalars. perlin / fBm / ridging are not
three operators — they are one sum of noise bands, and a band is four
numbers:
bands // freq amp fold warp
4.5 368.59 0 0 one row = perlin
22.5 73.72 0 0 several = fBm, and the row RATIOS
112.5 14.74 0 0 are lacunarity and gain
colors // at colour
0 #6b3f2c
1 #f2d9bd
`amp` is that band's real contribution, so `amplitude` is the column sum.
`fold` is ridging. Nothing is named; the operator is the row's shape.
The ppug codec already parses this — any tag whose children are numeric
rows is a property channel — and packages/core/xml-scene.ts lowers the table
back onto the same generator scalars, so this reel and terrain.promo
parse to the SAME document and render the same frames. Every cell is
printed to the shortest decimal that still recovers those scalars at
float32, the width of a GLSL uniform.
Preview: /packages/runtime/index.html?project=promos/primitives/terrain/terrain-bands.promo
promo#terrain
// =======================================================================
// SCENE 1 · TERRAIN — rolling dunes, ridged=0, colours baked per vertex
// =======================================================================
scene#t1-terrain(duration=7 transition=match)
camera(type=perspective fov=55 near=5 far=24000 position="0 1250 3400" look_at="0 0 -1500")
position
0 0 12