From 9c00cd3e44337dec089d7d25dfcbb14afe79658c Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Thu, 10 Sep 2026 16:15:26 +0300 Subject: [PATCH] feat(render3d): terrain_island, a coastline out of the survey `terrain_island(cx, cz, r, fall)` keeps land out to `r` and then scales the terrain down into the water over `fall` metres and on to a shelf. Scaling rather than blending to a fixed bed is what makes the coastline come out of the terrain that is already there: low ground turns into beach and shallows, high ground into cliff. `r = 0` leaves the survey alone, so nothing that does not ask for an island is touched. Co-Authored-By: Claude Opus 5 --- packages/ludic.render3d/shaders/heightgen.frag | 12 ++++++++++++ packages/ludic.render3d/terrain.ludic | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/packages/ludic.render3d/shaders/heightgen.frag b/packages/ludic.render3d/shaders/heightgen.frag index 668e1702..9c1e791f 100644 --- a/packages/ludic.render3d/shaders/heightgen.frag +++ b/packages/ludic.render3d/shaders/heightgen.frag @@ -10,6 +10,7 @@ uniform float u_dem_base; // the elevation that becomes y = 0 uniform vec2 u_origin; // world x/z of the map's centre uniform vec4 u_lake; // a lake: centre x/z, half extents (zero = none) uniform float u_lake_level; // its surface height; the model records the surface, the bed is carved below it +uniform vec4 u_isle; // an island: centre x/z, radius, falloff width (radius 0 = none) // The DEM is Copernicus GLO-30 — 30 m data resampled onto this 4 m grid — so the stored // field is piecewise linear with a slope discontinuity every ~7 texels. Differencing it // for a shading normal turns each kink into a ridge, and on steep ground, where the same @@ -58,6 +59,17 @@ void main() { float bed = u_lake_level - 0.4 - (5.0 + 3.0 * (1.0 - dot(q, q)) + 1.5 * fbm(xz * 0.02, 3)) * basin * basin; // a gentle gravel ramp, then the drop h = mix(h, bed, basin); } + // The island. Outside the radius the terrain is scaled toward the water line and then + // taken below it, so the shoreline is not drawn on: it is wherever the survey's own + // relief, shrunk, happens to cross the water. Low ground becomes beach and shallows, + // high ground becomes cliff, and a bay stays a bay. + if (u_isle.z > 0.0) { + float d = distance(xz, u_isle.xy); + float t = smoothstep(u_isle.z, u_isle.z + u_isle.w, d); // 0 inland, 1 past the fall + float shelf = 26.0 + 34.0 * smoothstep(0.0, 1.0, (d - u_isle.z - u_isle.w) / max(u_isle.w, 1.0)); + float above = h - u_lake_level; + h = u_lake_level + above * (1.0 - t) - t * shelf; + } o = vec4(h, 0.0, 0.0, 1.0); #elif defined(SMOOTH) // A purpose-built test ground: 2 km square, analytically smooth everywhere. No survey diff --git a/packages/ludic.render3d/terrain.ludic b/packages/ludic.render3d/terrain.ludic index 83c91841..e5d86956 100644 --- a/packages/ludic.render3d/terrain.ludic +++ b/packages/ludic.render3d/terrain.ludic @@ -68,6 +68,17 @@ var ter_lake_ez: int = 0 function terrain_lake(level: int, cx: int, cz: int, ex: int, ez: int) -> void { ter_lake_level = level; ter_lake_cx = cx; ter_lake_cz = cz; ter_lake_ex = ex; ter_lake_ez = ez } +# An island: land out to `r` from (cx, cz), then the terrain scaled down into the water +# over `fall` metres and on down to a shelf. Scaling rather than blending to a fixed bed is +# what makes the coastline come out of the terrain that is already there — low ground turns +# into beach and shallows, high ground into cliff. `r = 0` leaves the survey alone. +var ter_isle_cx: int = 0 +var ter_isle_cz: int = 0 +var ter_isle_r: int = 0 +var ter_isle_fall: int = 0 +function terrain_island(cx: int, cz: int, r: int, fall: int) -> void { + ter_isle_cx = cx; ter_isle_cz = cz; ter_isle_r = r; ter_isle_fall = fall +} # Use a real place: a 16-bit PNG height map plus its elevation range (metres). The # elevation `base` becomes y = 0; `ox`/`oz` put the map's centre in the world. @@ -194,6 +205,7 @@ function terrain_generate() -> void { u_f2(gl_uniform(p, "u_origin"), ter_ox, ter_oz) u_f4(gl_uniform(p, "u_lake"), ter_lake_cx, ter_lake_cz, ter_lake_ex, ter_lake_ez) u_f(gl_uniform(p, "u_lake_level"), ter_lake_level) + u_f4(gl_uniform(p, "u_isle"), ter_isle_cx, ter_isle_cz, ter_isle_r, ter_isle_fall) u_f(gl_uniform(p, "u_dem_blur"), ter_dem_blur) } mesh_draw(sky_fullscreen)