| … | … | @@ -168,22 +168,26 @@ kbd{font-family:"IBM Plex Mono",monospace;background:#0e1216;border:1px solid va |
| 168 | 168 | <button data-v="shaded-height" aria-pressed="true">Shaded height</button> |
| 169 | 169 | <button data-v="shade" aria-pressed="false">Hillshade</button> |
| 170 | 170 | <button data-v="slope" aria-pressed="false">Slope</button> |
| 171 | <button data-v="hypsometric" aria-pressed="false">Hypsometric</button> |
| 171 | 172 | <button data-v="height" aria-pressed="false">Height</button> |
| 172 | 173 | </div> |
| 174 | <label style="display:flex;gap:6px;align-items:center;font-size:11.5px;white-space:nowrap"> |
| 175 | exag <span class="v" id="v_exag2">1</span>x |
| 176 | <input type="range" id="exag2" min="1" max="14" step="0.5" value="1" style="width:90px"></label> |
| 173 | 177 | <div class="grow"></div> |
| 174 | 178 | <div class="stats" id="scale" style="border:0;padding:0"></div> |
| 175 | 179 | </div> |
| 176 | 180 | |
| 177 | 181 | <div class="panes"> |
| 178 | 182 | <section class="pane"> |
| 179 | | <h2>Our erosion filter <span class="tag">erosion_filter.inc · gpu</span></h2> |
| 183 | <h2>terrain-diffusion <span class="tag">30 m model · the large forms</span></h2> |
| 180 | 184 | <div class="imgwrap"><img id="img_e" alt="erosion render"> |
| 181 | 185 | <canvas id="cv_e" hidden></canvas></div> |
| 182 | 186 | <div class="stats" id="st_e">—</div> |
| 183 | 187 | <div class="busy" id="busy_e" hidden>rendering…</div> |
| 184 | 188 | </section> |
| 185 | 189 | <section class="pane"> |
| 186 | | <h2>terrain-diffusion <span class="tag">30 m model · seed 1234</span></h2> |
| 190 | <h2>+ erosion filter <span class="tag">erosion_filter.inc · the detail</span></h2> |
| 187 | 191 | <div class="imgwrap"><img id="img_d" alt="diffusion tile"> |
| 188 | 192 | <canvas id="cv_d" hidden></canvas></div> |
| 189 | 193 | <div class="stats" id="st_d">—</div> |
| … | … | @@ -208,7 +212,7 @@ const GROUPS = { |
| 208 | 212 | ["gn_gain",0.1,0,0.9,0.01],["gn_detail",3,1,8,1], |
| 209 | 213 | ["voxel_size",1,0.25,512,0.25]], |
| 210 | 214 | erode: [["e_upsample",2,1,4,1], |
| 211 | | ["e_gully_metres",240,30,20000,10],["e_strength",0.08,0,1,0.005], |
| 215 | ["e_gully_metres",240,30,20000,10],["e_depth_ratio",0.12,0,0.6,0.005], |
| 212 | 216 | ["e_octaves",5,1,10,1],["e_gain",0.5,0.2,0.9,0.01], |
| 213 | 217 | ["e_cell_scale",0.7,0.05,4,0.01],["e_rounding_input",0.1,0.005,4,0.005], |
| 214 | 218 | ["e_offset",0,-2,1,0.01]], |
| … | … | @@ -308,7 +312,12 @@ function derived(k) { |
| 308 | 312 | : ` grid Nyquist ${nyq.toFixed(0)} m`), |
| 309 | 313 | warn: fin < nyq || num("e_gully_metres") > 400}; |
| 310 | 314 | } |
| 311 | | case "e_strength": return {txt: "relative to the input tile's own relief; 0.22 is runevision's, far too strong here"}; |
| 315 | case "e_depth_ratio": { |
| 316 | const g = num("e_gully_metres"); |
| 317 | return {txt: `gully ${(num("e_depth_ratio")*g).toFixed(0)} m deep on ${g.toFixed(0)} m wide` + |
| 318 | ` — real valleys are 0.05–0.25`, |
| 319 | warn: num("e_depth_ratio") > 0.3}; |
| 320 | } |
| 312 | 321 | case "e_offset": return {txt: "DC bias x magnitude. -0.65 is the reference; it shifts a 2 km tile by ~800 m, so 0 for lookdev"}; |
| 313 | 322 | case "assumed_slope_mix":return {txt: num("assumed_slope_mix") >= 1 |
| 314 | 323 | ? "1.0 = input slope MAGNITUDE replaced; direction kept" |
| … | … | @@ -570,13 +579,26 @@ function render() { |
| 570 | 579 | if (RAMP && $("lockramp").checked) { |
| 571 | 580 | for (const u of [p, q]) { u.set("vmin", RAMP[0]); u.set("vmax", RAMP[1]); } |
| 572 | 581 | } |
| 582 | // BOTH panels are the diffusion field. Left is the model alone; right is the |
| 583 | // same ground with the erosion filter on top. The old left panel was our fBm |
| 584 | // mountain layer, and that comparison is finished: #46 measured that fBm |
| 585 | // produces texture (flat beta) where the model produces landscape (beta |
| 586 | // steepening 4.18 -> 5.10). The open question is no longer which generator |
| 587 | // wins, it is how the filter should sit on top of the one that does. |
| 588 | const bare = new URLSearchParams(q); // diffusion alone |
| 589 | const eroded = new URLSearchParams(q); // diffusion + erosion |
| 590 | eroded.set("erode", "1"); |
| 591 | for (const [key] of GROUPS.erode) eroded.set(key, $(key).value); |
| 592 | for (const u of [bare, eroded]) { |
| 593 | u.set("view", view); |
| 594 | u.set("exag", $("exag2").value); |
| 595 | } |
| 573 | 596 | if (mode === "3d") { |
| 574 | | load3d("/api/erosion.bin?" + p, "cv_e", "st_e", "busy_e"); |
| 575 | | load3d("/api/diffusion.bin?" + q, "cv_d", "st_d", "busy_d"); |
| 597 | load3d("/api/diffusion.bin?" + bare, "cv_e", "st_e", "busy_e"); |
| 598 | load3d("/api/diffusion.bin?" + eroded, "cv_d", "st_d", "busy_d"); |
| 576 | 599 | } else { |
| 577 | | p.set("view", view); q.set("view", view); |
| 578 | | load("e", "/api/erosion.png?" + p, "img_e", "st_e", "busy_e"); |
| 579 | | load("d", "/api/diffusion.png?" + q, "img_d", "st_d", "busy_d"); |
| 600 | load("e", "/api/diffusion.png?" + bare, "img_e", "st_e", "busy_e"); |
| 601 | load("d", "/api/diffusion.png?" + eroded, "img_d", "st_d", "busy_d"); |
| 580 | 602 | } |
| 581 | 603 | } |
| 582 | 604 | |
| … | … | @@ -588,6 +610,10 @@ function schedule() { |
| 588 | 610 | timer = setTimeout(render, 250); |
| 589 | 611 | } |
| 590 | 612 | |
| 613 | $("exag2").addEventListener("input", () => { |
| 614 | $("v_exag2").textContent = (+$("exag2").value).toFixed(1).replace(/\.0$/, ""); |
| 615 | schedule(); |
| 616 | }); |
| 591 | 617 | $("erode").addEventListener("change", render); |
| 592 | 618 | $("lockramp").addEventListener("change", () => { |
| 593 | 619 | if (!$("lockramp").checked) { RAMP = null; $("ramptxt").textContent = ""; } |