# LEVIATHAN TIDAL WAVE — Technical Notes **Which equations and which numbers actually move the sea and the body in this piece.** The explainer, *A deep dive into high-school physics and mathematics*, is written so a high-school student can read it. These notes are what sits behind it: the code and the numbers as they really run. Made by aTELiER FACTORY. - The piece and the explainer: `/3d/creature/leviathan/` - Where it runs: the browser (WebGL2 / three.js). Nothing is computed on a server - The .md file of this document: downloadable from this page --- ## 0. How it is put together ``` page/ index.html the piece + the explainer (one address; it switches views internally) sea.js sea, sky, foam, spray, the heart of the rendering (~2,800 lines) creature.js the shape of the body (segments, thickness, spines, fins) rig.js attaches bones to the exported body (glb) route.js the path the body travels (the order of the set pieces) swim.js rippling, orientation, accumulation muscle.js how segments connect (smoothness) glsl.js noise and sky colour (read from the shaders) web/ lev_body.glb the body mesh (~20MB) ``` Everything is computed inside the browser. Each frame runs in this order: position along the path → body segments → deformation of the water surface → foam and spray → render. --- ## 1. The shape of the sea ### 1-1. The waves are a sum of eight The sea in this piece is a superposition of **eight sine waves** differing in direction, height and wavelength. These are all the numbers there are (`WAVES` in `sea.js`). | # | dir x | dir z | height A (m) | wavelength λ (m) | steepness | |---|---|---|---|---|---| | 1 | 1.00 | 0.10 | 1.144 | 31.7 | 0.42 | | 2 | 0.90 | −0.42 | 0.638 | 16.7 | 0.46 | | 3 | 1.00 | 0.52 | 0.361 | 9.2 | 0.51 | | 4 | 0.78 | −0.62 | 0.194 | 5.1 | 0.56 | | 5 | 1.00 | 0.20 | 0.106 | 2.7 | 0.72 | | 6 | 0.58 | 0.82 | 0.055 | 1.5 | 0.74 | | 7 | 1.00 | −0.30 | 0.026 | 0.8 | 0.74 | | 8 | 0.40 | 0.92 | 0.012 | 0.4 | 0.75 | Both the heights and the wavelengths are **a stormy sea on Earth scaled down to one fifth**: the body is 300m, and at full scale the waves would have to be tens of metres high. Gravity is not scaled — it stays at 9.81 m/s². ### 1-2. How fast the waves travel In deep water the phase speed depends only on the wavelength. ``` ω = √(g k), k = 2π / λ, c = ω / k = √(g / k) ``` The 31.7m wave runs at about 7.0 m/s, the 0.4m wave at about 0.8 m/s. **Longer waves are faster**, so the eight crests keep sliding relative to one another and the same shape never comes back. ### 1-3. Sharp crests: Gerstner waves Adding sines alone gives crests and troughs of equal roundness. A real sea has **sharp crests and flat troughs**. So each water particle is moved not only up and down but **along its direction of travel as well** (a Gerstner, or trochoidal, wave). ```glsl // for one wave (Q is the steepness, the "steepness" column above) float phase = k * dot(dir, xz) - omega * t; y += A * cos(phase); xz += dir * (Q * A * sin(phase)); // <- this is what sharpens the crest ``` ### 1-4. Whitecaps are "where the area collapses" In a Gerstner wave the surface area collapses at the top of the crest. How far it has collapsed is measured by the Jacobian — how much area stretches or shrinks when you change coordinates. ``` J = 1 − Σ Qᵢ Aᵢ kᵢ cos(phaseᵢ) J close to 0 = the area has collapsed = that is a whitecap ``` The smaller J gets, the denser the white foam laid on top. **The positions of the whitecaps are never painted by hand.** --- ## 2. Sky, rain, haze ### 2-1. Cloud The same noise is layered eight times, each layer **half the size and 1/1.35 the strength** (the fbm in `glsl.js`). ``` value = Σ (1/1.35)ⁿ · noise(2ⁿ · p) ``` From the strength ratio g and the size ratio 2, the Hurst exponent is H = log(1/g) / log 2 ≈ 0.43 — chosen to match the fractal dimension measured for real clouds (Lovejoy 1982, about 1.35). ### 2-2. Rain Raindrops fall at 7m/s and the wind blows at 17m/s. **Adding the two vectors** slants the rain. ``` tanθ = 17 / 7 → θ ≈ 68° (from the vertical) ``` ### 2-3. Haze (the further, the darker) Travelling through murky water or air, light weakens **exponentially** with the distance covered (the Beer–Lambert law). ``` T = exp( −(0.012 d + 0.0069 D) ) d: distance travelled sideways (m) D: depth of water (m) ``` In water it halves about every 100m (0.0069 × 100 ≈ 0.69 = ln 2). --- ## 3. The body ### 3-1. Dimensions ```js const SEG = 240; // number of segments const STEP = 1.25; // spacing between segments (m) // total length = 240 × 1.25 = 300 m ``` Thickness runs from head to tail by this formula (`creature.js`). ```js const radiusAt = i => { const s = i / SEG; return 5.2 * Math.pow(Math.sin(Math.PI * Math.pow(Math.min(s + 0.030, 1), 0.42)), 0.62); }; ``` At its thickest the radius is 5.2m (10.4m across). The exponent `0.42` puts **the thickest part towards the front**, after which it tapers slowly to the tail. ### 3-2. Orientation of the body (which way the back faces) Each segment takes the orientation of the segment in front and rotates it by however much the path has turned — close in spirit to a parallel-transport (Bishop) frame. Angles are carried as unbounded numbers and the nearest representative is chosen each time, so nothing flips over at the moment 360° is crossed. --- ## 4. Swimming ### 4-1. Rippling and speed For an animal that swims by rippling (the eel type), speed follows this relation. ``` U = f · λ · slip f: strokes per second (1/s) λ: wavelength of the wave travelling down the body (m) slip: 0–1 ``` The individual swimming on the last page of the explainer uses `λ = 195m`, `slip = 0.75` and `U = 24 m/s`, and from those the stroke period of **6.1 seconds** is calculated — none of it is hard-coded. ```js const om = 2 * Math.PI * (U / SLIP) / LAM; // 1.03 rad/s -> period 6.1 s const off = AMP * Math.sin(2 * Math.PI * back / LAM - om * t); ``` The sideways amplitude is `AMP = 14m` (±14m, about 9% of body length). Real eels also sweep their tails by roughly a tenth of their body length. ### 4-2. Where the 24 m/s estimate comes from The larger the animal, the slower each stroke. The power it can produce scales with muscle volume (length cubed), while the power lost to drag scales with cross-section × speed cubed (length squared × v³), so ``` L³ ∝ L² v³ → v ∝ L^(1/3) ``` Taking a 30m blue whale sprinting at 10 m/s as the reference, a 300m body gives 10 × 10^(1/3) ≈ **21–24 m/s**. The 150 m/s reached in the set pieces is far beyond that estimate — it is staging (see "9. Where the physics was set aside"). --- ## 5. Interaction with the water ### 5-1. Float and sink ``` F = ρ V g (Archimedes' principle) a = ( s − 1 ) g s: how much of the segment is submerged (0–1) ``` The body is given the same density as water (ρ_body = 1). **Fully submerged, it neither rises nor sinks**; only the part out of the water tips the balance towards weight, and falls. ### 5-2. Water resistance The velocity is split into a component along the body and one across it, and each is applied squared. **Across the body it counts 25 times more**, because pushing water sideways with a long thin body is very hard work. ``` v = v∥ + v⊥ a∥ = −k v∥² a⊥ = −25 k v⊥² k ≈ 0.010 (derived from the radius + 1m) ``` That difference is what makes the body follow the track the head has cut. ### 5-3. Pulling the face back into the water When a peak in the path rises above the surface, the head comes out into the air. A real animal would tire of holding its neck up, so it is brought down **faster than falling alone**. ``` Δy = −½ A u² A = 5g ≈ 49 m/s² u: seconds since the snout left the water ``` The push continues only until the snout reaches 10m below the surface, after which a cubic joins it smoothly back to the original path. ### 5-4. Breaking the surface As the body enters the water, **only the leading edge** opens a hole. The segments behind pass through the hole already open. ``` entering: leading edge π r², body behind π ( r² − r_hole² ) leaving: the lifted sheet of water tears away and follows y = v₀ t − ½ g t² ``` ### 5-5. The water pushed aside (the flow of marine snow) Every metre travelled displaces π r² of water, which flows around the sides of the body. The radius of the spreading ring is ``` r(d) = √(g d) × (distance behind) ÷ v ``` --- ## 6. Bubbles (cavitation) Pressure drops around anything moving fast, and water boils while still at room temperature. Whether it happens is decided by the **cavitation number σ**. ```js const RHO = 1025, P_ATM = 101325, P_VAP = 2300; // sea-water density / atmospheric pressure / vapour pressure at 20°C const cavSigma = (depth, v) => (P_ATM + RHO * 9.81 * depth - P_VAP) / Math.max(0.5 * RHO * v * v, 1); ``` Boiling occurs where σ falls below the local threshold σᵢ. In this piece σᵢ = 0.6 **just behind the neck (0–12m from the head)**, 0.3 further back, and 1.0 at the tips of the spines — so the spine tips boil the most readily. The bubbles are kept in three separate pools. ```js const CAV_P = 40000; // bubbles boiled off by pressure (short-lived, thousands per frame) const CAV_AIR = 20000; // air dragged down from the surface (lasts 5–15 s) const CAV_BRK = 30000; // foam from breaking waves ``` --- ## 7. Light ### 7-1. Reflection off the water (Fresnel) Not the exact Fresnel equations, but **Schlick's approximation**. ```glsl float fres = 0.022 + 0.978 * pow(1.0 - clamp(dot(N, V), 0.0, 1.0), 5.0); ``` 0.022 is water's reflectance at normal incidence (it follows from the refractive index 1.33). The shallower the angle, the closer to 1, which is why the distant surface mirrors the sky. ### 7-2. Wet skin Anywhere that has just left the water stays looking wet for a while. ``` light returned = light arriving × 0.78 (wet is darker) reflection from running water = 1 − (seconds since leaving) ÷ 3 (dry in 3 s) ``` --- ## 8. The explainer's 16 sections ↔ the implementation | # | How the explainer puts it | The formula actually used | Where | |---|---|---|---| | 01 | Waves add up | eight superposed sines + Gerstner | `sea.js WAVES` | | 02 | Further is darker | T = exp(−(0.012d + 0.0069D)) | haze in `sea.js` | | 03 | Paths add up too | the path is built from sines of differing periods | `route.js` | | 04 | Corkscrew | rotate around the direction of travel (rotation matrix) | `route.js MOVE_HELIX` | | 05 | A ceiling on height | y′ = B + K(1 − e^(−(y−B)/K)) | `route.js` | | 06 | How sharp the turn | R = h / (2 sin(θ/2)) (sine rule) | `swim.js` | | 07 | Back into the water | Δy = −½ A u², A = 5g | `route.js` | | 08 | Float or sink | a = (s − 1) g | `swim.js` | | 09 | Water resistance | a∥ = −k v∥², a⊥ = −25k v⊥² | `swim.js` | | 10 | Which way up | a = v²/R, cos τ = 2g ÷ 0.6a | `swim.js` | | 11 | Speed boils water | σ = (p₀ + ρgh − pᵥ) ÷ ½ρv² | `sea.js cavSigma` | | 12 | Breaking the surface | π r² and π(r² − r_hole²), projectile motion | `sea.js emitAirSheet` | | 13 | Getting wet | ×0.78, dry in 3 s | `rig.js` | | 14 | The water pushed aside | π r², spreading as √(g d) | `sea.js updateSnow` | | 15 | Painting the sky | fbm (half the size, 1/1.35 the strength) | `glsl.js NOISE` | | 16 | University tools | Jacobian, potential flow, Fresnel, turbulence | in the explainer | --- ## 9. Where the physics was set aside (an honest list) The piece cares about running on high-school equations, but there are **places where that does not hold as it stands**. Here they are, unhidden. 1. **The sea was scaled down to one fifth.** Gravity stayed at 9.81. Match a real storm to a 300m body and the waves have to be tens of metres high — a sea that could not exist on Earth 2. **The body is not solved as a dynamical system.** The path is decided first and the body is laid onto it (the "swims on physics alone" mode you can see with `?knobs=1` is experimental). Drag, buoyancy and orientation are used to decide how it looks *on* that path 3. **Drag does not use the Reynolds number.** It is approximated by a coefficient k and "25× across the body" 4. **Orientation is set by an angle, not by balancing forces** (cos τ = 2g ÷ 0.6a), with gravity strengthened and centrifugal force weakened 5. **The number and lifetime of the bubbles were chosen by eye.** The condition for boiling (σ) is physics; how many appear per frame and how long they last were picked for how they look 6. **The light is approximate.** Fresnel is Schlick's approximation, and the scattering inside the body (SSS) is a simple approximation too 7. **The 150 m/s top speed is staging.** The estimate from body length (4-2) is 21–24 m/s, and even the fastest class of underwater vehicle (a torpedo travelling inside a bubble) manages about 100 m/s --- ## 10. A working demo (it runs from one file) **→ [Open the working demo](demo/index.en.html)** **One single file containing all sixteen sections of the explainer.** Open it in a browser and it runs. All the code is **inside that one file**, numbered `【01】` to `【16】` so you can see which part is which. | No. | What the demo does there | |---|---| | 01 | eight Gerstner waves (the same numbers as the table in 1-1) | | 02 | darkening with depth (e^(−0.0069 D)) and distant haze | | 03 | **one of the real paths from the piece, unchanged** (a set piece from `page/route.js`): it rises out of the dark at 68m, sweeps past the window and dives again | | 04 | twisting the body around its direction of travel (the "twist" slider) | | 05 | capping the path exponentially so it does not rise above the surface | | 06 | finding the radius of the turn from the circle through three points (the circumcircle) | | 07 | pulling the head back at 5g once it leaves the water | | 08 | a = (s − 1) g from each segment's submerged fraction s | | 09 | drag −k v² (25× vertically). The forward speed also settles where drag balances it | | 10 | banking the body from the radius of the turn | | 11 | emitting bubbles from the cavitation number σ | | 12 | spray flying as a projectile from wherever the body crosses the surface | | 13 | anything out of the water darkens to ×0.78 and recovers over 3 seconds | | 14 | particles around the body being pushed aside and flowing outwards | | 15 | cloud in the sky (six layers, each half the size and 1/1.35 the strength) | | 16 | whitecaps wherever the Jacobian has collapsed | **Controls**: drag to orbit, wheel to zoom, **R** to return to the chase camera. There are four sliders (forward speed, wave height, rippling amplitude, twist). The top left of the screen shows the numbers being computed right now: stroke period, submerged fraction, σ, radius of the turn, bank. **To run it on your own machine**: save the file and replace the single import line at the top with this. ```html ``` **What is not in it**: the look of the body (scales, spines, eyes, the inside of the mouth), the exported mesh (~20MB), the rain, and the finer detail of the marine snow. The body is **just a tube**. This demo exists **to be read**, so you can see how it works. ## 11. Terms of use **The text of these technical notes, and the code of the working demo, are CC0 1.0 (public domain).** Anyone may use them, for anything, **without attribution**. Commercial use is fine. No need to get in touch. For the **form, the look, the exported data (.glb) and the images** of the piece *Leviathan*, **no rights are claimed** either. If you want to use them, use them. One thing stated plainly. All of this assumes that **the people who made it (aTELiER FACTORY) remain free to keep using this piece and these methods**. No one may claim rights over the same material and stop us from using it. This document is published with a date, and that in itself is a record that it was out in the world first. Note that the **three.js used by the demo is MIT-licensed** (Copyright 2010–2024 Three.js Authors). If you redistribute three.js alongside it, keep that notice. --- ## 12. Sources - Gerstner waves: F. J. Gerstner (1802). For ocean rendering, J. Tessendorf's papers are the well-known reference - Schlick's approximation: C. Schlick (1994) - Fractal dimension 1.35 for clouds: S. Lovejoy (1982) - The −5/3 law of turbulence: A. N. Kolmogorov (1941) - Buoyancy: Archimedes' principle - Leviathan: Job 41 in the Hebrew Bible. No dimensions are given there; "300 parasangs" comes from the Talmud --- Made by aTELiER FACTORY