| … | … | @@ -0,0 +1,152 @@ |
| 1 | // ========================================================================= |
| 2 | // hypso_curve.inc — the analytic land/sea transfer curve. |
| 3 | // |
| 4 | // ONE TEXT, compiled as GLSL (injected into the density shader) and as C++ |
| 5 | // (through erosion_cpp_shim.h, for the oracle). Same rule as |
| 6 | // erosion_filter.inc: only the subset both languages agree on. |
| 7 | // |
| 8 | // WHAT IT IS FOR. A plain fBm has a UNIMODAL height distribution, so |
| 9 | // intersecting it with a water plane at any level gives a coastline of |
| 10 | // uniform fractal character everywhere -- no shelves, no basins, and no |
| 11 | // reason for land to clump. Earth's hypsography is BIMODAL: ~85% of the |
| 12 | // surface sits in two narrow bands, because continental crust and oceanic |
| 13 | // crust have different densities. This curve puts that shape into the |
| 14 | // height field directly, with no real-world data. |
| 15 | // |
| 16 | // WHY A BLEND AND NOT A PIECEWISE JOIN. The obvious construction -- |
| 17 | // `h < sea ? ocean(h) : land(h)` -- is C0 but not C1: the slope jumps at |
| 18 | // the join, which is EXACTLY the waterline, and a slope discontinuity along |
| 19 | // every coastline renders as a crease ring. Blending two regimes with a |
| 20 | // smoothstep is C1 by construction because smoothstep is. |
| 21 | // |
| 22 | // WHERE THE BIMODALITY COMES FROM. Not from a threshold. Output values |
| 23 | // cluster where the curve is FLAT (low |dhv/dh|), so a flat abyssal |
| 24 | // plateau and a land ramp separated by a narrow STEEP section produce two |
| 25 | // modes with few samples between them -- the continental slope. `shelf` is |
| 26 | // the half-width of that steep section: smaller = sharper coast = more |
| 27 | // strongly bimodal. |
| 28 | // |
| 29 | // THE WATERLINE IS DERIVED. The curve is monotonic, so sea level in OUTPUT |
| 30 | // space is simply hypso_curve(sea).x. Do not also hand-set a water height; |
| 31 | // read it from here, or the two disagree and the shoreline renders where |
| 32 | // the terrain is not. |
| 33 | // |
| 34 | // PEAK LIFT is additive on the upper tail only, so it raises summits |
| 35 | // without steepening mid-slopes or deepening valleys. That matters beyond |
| 36 | // looks: total relief is bounded by the physics band (character movement |
| 37 | // degrades with |y|), and lifting only the tail is how peak height stops |
| 38 | // competing with valley depth for the same budget. |
| 39 | // |
| 40 | // h input height, nominally [-1, 1] |
| 41 | // sea input value at the middle of the continental slope |
| 42 | // shelf half-width of that slope, in input units (>0) |
| 43 | // abyss OUTPUT value of the abyssal plateau (negative) |
| 44 | // land_slope output units per input unit above sea |
| 45 | // peak_onset input value where the summit lift begins |
| 46 | // peak_gain output units added at h = 1 |
| 47 | // |
| 48 | // Returns (hv, dhv/dh). The derivative is not decoration: the erosion |
| 49 | // filter is slope-aware and the wrapper hands it a gradient, so remapping |
| 50 | // the height without remapping the gradient by the chain rule would carve |
| 51 | // gullies aligned to a surface that is no longer there. |
| 52 | // ========================================================================= |
| 53 | |
| 54 | // WHY THREE REGIMES AND NOT TWO. A first version had an abyssal plateau and |
| 55 | // a linear land ramp, and the gate measured its output histogram as ONE mode |
| 56 | // plus a smear -- because a ramp has no mode. Earth's bimodality is two |
| 57 | // PLATEAUS: the continental platform (~0..+1 km) and the abyssal plain |
| 58 | // (~-4..-5 km), with the continental slope between them. So the land side |
| 59 | // needs a low-gradient shelf of its own, and the mountains rise ABOVE that. |
| 60 | // |
| 61 | // abyss --[continental slope]-- platform --[range front]-- mountains |
| 62 | // flat steep gentle steeper + peak lift |
| 63 | // |
| 64 | // Two smoothsteps composing three lines. Both are C1, so the whole is. |
| 65 | |
| 66 | // smoothstep's derivative. GLSL has smoothstep but not its derivative, and |
| 67 | // writing 6t(1-t)/(hi-lo) at three call sites is how one of them ends up |
| 68 | // missing the 1/(hi-lo). |
| 69 | // ⚠ DO NOT NAME A LOCAL `out`. It is a GLSL storage qualifier and a perfectly |
| 70 | // ordinary identifier in C++, so a C++-only test compiles it and the SHADER |
| 71 | // dies with "syntax error, unexpected OUT" -- which is what happened: the GPU |
| 72 | // fell back to the CPU mesher and the only symptom was that terrain generation |
| 73 | // got slow. Same class as erosion_filter.inc's PN_TAU note. `in`, `inout`, |
| 74 | // `filter`, `sample` and `active` are reserved the same way. |
| 75 | |
| 76 | float hypso_dsmooth(float lo, float hi, float x) { |
| 77 | float d = hi - lo; |
| 78 | if (d <= 0.0) return 0.0; |
| 79 | float t = clamp((x - lo) / d, 0.0, 1.0); |
| 80 | return 6.0 * t * (1.0 - t) / d; |
| 81 | } |
| 82 | |
| 83 | // h input height, nominally [-1, 1] |
| 84 | // sea input value that IS sea level; the curve returns 0 there |
| 85 | // shelf_width how far below sea the gentle shelf runs before dropping |
| 86 | // slope_width half-width of the steep drop (smaller = sharper break) |
| 87 | // abyss OUTPUT value of the abyssal plain (negative) |
| 88 | // plat_slope output per input on the continental platform (small) |
| 89 | // mtn_onset input value where the mountain front begins |
| 90 | // mtn_width half-width of that front |
| 91 | // mtn_slope output per input in the mountains (larger) |
| 92 | // peak_onset input value where the summit lift begins |
| 93 | // peak_gain output added at h = 1 |
| 94 | // |
| 95 | // Returns (hv, dhv/dh). The derivative is not decoration: the erosion |
| 96 | // filter is slope-aware and the wrapper hands it a gradient, so remapping |
| 97 | // the height without remapping the gradient by the chain rule would carve |
| 98 | // gullies aligned to a surface that is no longer there. |
| 99 | vec2 hypso_curve(float h, float sea, float shelf_width, float slope_width, |
| 100 | float abyss, |
| 101 | float plat_slope, float mtn_onset, float mtn_width, |
| 102 | float mtn_slope, float peak_onset, float peak_gain) { |
| 103 | // --- regime 1: the continental platform, a gentle line through (sea, 0) |
| 104 | float plat = plat_slope * (h - sea); |
| 105 | |
| 106 | // --- the continental slope, CENTRED BELOW SEA LEVEL. |
| 107 | // |
| 108 | // ⚠ THIS IS WHY THE FIRST VERSION WAS A CLIFF. The blend was centred ON |
| 109 | // `sea`, so the steep section straddled the waterline and land met deep |
| 110 | // water with nothing in between -- a wall, roughly 50x steeper than the |
| 111 | // platform behind it. That steepness IS the bimodality, so it cannot be |
| 112 | // removed; it has to be MOVED. |
| 113 | // |
| 114 | // On Earth the steep part is submerged: land -> beach -> a gentle shelf |
| 115 | // ~100 m deep and tens of km wide -> shelf break -> continental slope -> |
| 116 | // abyssal plain. Putting the blend `shelf_width` BELOW sea leaves the |
| 117 | // gentle platform line running down through the waterline, and that |
| 118 | // stretch is the continental shelf. |
| 119 | // |
| 120 | // Requires shelf_width > slope_width, or the drop reaches back up through |
| 121 | // sea level and the shelf does not exist. |
| 122 | float slope_mid = sea - shelf_width; |
| 123 | float c0 = slope_mid - slope_width; |
| 124 | float c1 = slope_mid + slope_width; |
| 125 | float w = smoothstep(c0, c1, h); |
| 126 | float dw = hypso_dsmooth(c0, c1, h); |
| 127 | float base = abyss + (plat - abyss) * w; |
| 128 | float dbase = (plat - abyss) * dw + w * plat_slope; |
| 129 | |
| 130 | // --- the mountain front, ADDED not blended to. See the note above: |
| 131 | // blending toward a steeper LINE folds the terrain below its onset, |
| 132 | // measured at dhv/dh = -0.112. An additive ramp cannot fold. |
| 133 | float a = mtn_onset - mtn_width; |
| 134 | float b = mtn_onset + mtn_width; |
| 135 | float sm = smoothstep(a, b, h); |
| 136 | float dsm = hypso_dsmooth(a, b, h); |
| 137 | // (h-a)*smoothstep: exactly 0 below a (smoothstep clamps), asymptotically |
| 138 | // linear with unit slope above b, C1 throughout, never decreasing. |
| 139 | float ramp = (h - a) * sm; |
| 140 | float dramp = sm + (h - a) * dsm; |
| 141 | float extra = mtn_slope - plat_slope; // the slope the range ADDS |
| 142 | |
| 143 | float hv = base + extra * ramp; |
| 144 | float dhv = dbase + extra * dramp; |
| 145 | |
| 146 | // --- summit lift, upper tail only. Same additive shape, same reason. |
| 147 | if (peak_gain != 0.0) { |
| 148 | hv += peak_gain * smoothstep(peak_onset, 1.0, h); |
| 149 | dhv += peak_gain * hypso_dsmooth(peak_onset, 1.0, h); |
| 150 | } |
| 151 | return vec2(hv, dhv); |
| 152 | } |