mapspinner 0.1.61 → 0.1.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapspinner",
3
- "version": "0.1.61",
3
+ "version": "0.1.63",
4
4
  "description": "WebGL2 Earth-scale terrain rendering SDK for interactive globe applications",
5
5
  "main": "src/index.js",
6
6
  "exports": {
package/src/gl-render.js CHANGED
@@ -1051,7 +1051,7 @@ export async function initMapspinnerRender(gl, opts = {}) {
1051
1051
  gl.uniform1f(U('oceanAmp'), (oc.oceanAmplitude != null) ? oc.oceanAmplitude : 1.0);
1052
1052
  gl.uniform1f(U('oceanChoppy'), (oc.oceanChoppiness != null) ? oc.oceanChoppiness : 0.5);
1053
1053
  gl.uniform1f(U('oceanFoam'), (oc.oceanFoam != null) ? oc.oceanFoam : 0.5);
1054
- gl.uniform1f(U('uBeachTopM'), _g('beachTop', 1000.0)); // beach ceiling: grass stops, sand to the waterline + under it. 30->90 (user 2026-06-14: 3x beach width)
1054
+ gl.uniform1f(U('uBeachTopM'), _g('beachTop', 80.0)); // beach ceiling: grass stops, sand to the waterline + under it. 30->90 (user 2026-06-14: 3x beach width)
1055
1055
 
1056
1056
  // SINGLE INSTANCED DRAW: the deform params that were per-quad uniforms (ox,oy,l,level + face)
1057
1057
  // are now PER-INSTANCE attributes. Build one interleaved instance buffer [ox,oy,l,level,face]
@@ -1395,6 +1395,7 @@ float canyonMask(vec3 worldPos, float h, float temp, float humid, float px, out
1395
1395
  #endif // biomeClassColor/riverMask/canyonMask: DEBUGVIEW-only (called solely from displayMode blocks) -- excluded from render FS cold-compile (FS-2, workflow w4y1bnrqc)
1396
1396
 
1397
1397
  vec3 terrainAlbedoClimate(float h, float slope, float rockSlope, float temp, float humid, highp vec3 worldPos, float pxWorld) { // highp: worldPos feeds normalize(worldPos)*freq noise UVs (mottle/river/canyon ridge) -- mediump scrambles the lattice up close
1398
+ highp vec3 nwp = normalize(worldPos); // hoisted: reused by the mottle + river/canyon ridge dirs (was recomputed)
1398
1399
  vec3 c = terrainAlbedo(h, slope, rockSlope, worldPos, pxWorld);
1399
1400
  if (h < 0.0) {
1400
1401
  // SEA ICE: near-polar ocean (very cold) freezes to white-blue pack ice. Pure fn of the
@@ -1430,7 +1431,7 @@ vec3 terrainAlbedoClimate(float h, float slope, float rockSlope, float temp, flo
1430
1431
  // soil/moisture/vegetation patchiness mottles albedo. Reuse the snoise3 already evaluated for
1431
1432
  // cliffRock (same normalize(worldPos)*~1k pattern, no new octave) as a VALUE-only multiplier (NO
1432
1433
  // hue jitter, low 1200 freq -> does NOT reintroduce the per-pixel swamp moire the pipeline removed).
1433
- float mot = snoise3(normalize(worldPos) * 120.0); // detail-tex-rockface-canyon-10x: *1200->*120 (~10x larger mottle features, user 2026-06-06)
1434
+ float mot = snoise3(nwp * 120.0); // detail-tex-rockface-canyon-10x: *1200->*120 (~10x larger mottle features, user 2026-06-06)
1434
1435
  c *= (1.0 + uVariationAmt * mot);
1435
1436
  // earlier snow/ice on high ground in cold regions.
1436
1437
  float coldSnow = (1.0 - smoothstep(0.30, 0.75, temp)) * smoothstep(snowEdges.x * 0.5, snowEdges.x, h);
@@ -1480,7 +1481,7 @@ vec3 terrainAlbedoClimate(float h, float slope, float rockSlope, float temp, flo
1480
1481
  // the deleted 2026-06-01 detail texturing), value-only (no hue shift), and each octave fades out
1481
1482
  // via a pxWorld Nyquist gate before its features go sub-pixel (no orbit speckle, no leopard band).
1482
1483
  {
1483
- highp vec3 od = normalize(worldPos);
1484
+ highp vec3 od = nwp;
1484
1485
  float ov = 0.0, oa = 0.0;
1485
1486
  float fq = 75.0, am = 1.0; // octaves: ~84km / 17km / 3.4km features (halved to match VS detailFbm)
1486
1487
  int fdOcts = (uFSDetailOcts > 0) ? uFSDetailOcts : 3;
@@ -1538,7 +1539,8 @@ void main() {
1538
1539
  // PER-FRAGMENT tangent frame from vWorld (the sphere position, C0 across quad edges):
1539
1540
  // uz = up (radial), ux = normalize(Y x uz), uy = uz x ux. Continuous across adjacent quads
1540
1541
  // -> no per-quad shading-baseline seam.
1541
- vec3 uz = normalize(vWorld);
1542
+ highp vec3 nWorld = normalize(vWorld); // computed ONCE, reused for the tangent up + every band/noise/lighting dir (perf: was ~7 separate normalize() calls)
1543
+ vec3 uz = nWorld;
1542
1544
  // AUDIT FIX 2026-06-06 (audit-tangent-frame-degeneracy): the reference axis was a FIXED vec3(0,1,0),
1543
1545
  // so cross(Y, uz) -> zero-length (NaN normal) wherever uz is parallel to Y -- i.e. the +Y/-Y cube
1544
1546
  // faces and the poles, exactly 'wrong normals in the wrong places'. Pick the world axis LEAST
@@ -1787,7 +1789,7 @@ void main() {
1787
1789
  // SHARED biome-band warp pattern (user 2026-06-14: the SAME warping on snow/rock bands AND the
1788
1790
  // beach->land crossover). 2-oct world-dir noise, 1/4 freq (~13km + ~5km waves). Computed once
1789
1791
  // here; the snow/rock bandWarp below reuses warpN. highp dir for the lattice precision.
1790
- highp vec3 bwDir = normalize(vWorld);
1792
+ highp vec3 bwDir = nWorld;
1791
1793
  // 3-oct (added the ~1km octave, user 2026-06-14 'make biome bands more interesting') -> the
1792
1794
  // texture-splat rock/snow/beach edges break into finer fingers as you approach, not a hard band.
1793
1795
  float warpN = snoise3(bwDir * 3325.0) + 0.5 * snoise3(bwDir * 7750.0) + 0.35 * snoise3(bwDir * 17000.0); // ~ +/-1.8
@@ -1877,38 +1879,26 @@ void main() {
1877
1879
  wt += vTexWarp * uTexWarp;
1878
1880
  vec3 tw = abs(n); tw = tw * tw; tw /= (tw.x + tw.y + tw.z + 1e-4);
1879
1881
  const vec3 LUMA = vec3(0.299, 0.587, 0.114);
1880
- float detailFade = (1.0 - smoothstep(1.0, 12.0, pxWorld));
1881
1882
  float bAB = clamp(wA / max(wA + wB, 1e-4), 0.0, 1.0);
1882
- // PER-LAYER material = base octave (NORMALS ONLY -> albedo flattened to luma, macro color carries
1883
- // chroma) + the 4x DETAIL octave (albedo STRUCTURE + strong normal) + a per-layer DISPLACEMENT
1884
- // (coarse base, refined by the 4x near the deck). Each top-2 layer is built FULLY here, then the
1885
- // two are HEIGHT-BLENDED (below) -- so the detail never flips at the boundary (the 'hard lines up
1886
- // close ever since the higher octave' bug = the detail used the dominant layer only).
1887
- vec4 albA = surfTriTap(uSurfAlb, wt, tw, lA);
1888
- vec3 cA = vec3(dot(albA.rgb, LUMA)); vec3 nA = surfTriNrm(uSurfNrm, wt, tw, lA, n); float dispA = albA.a;
1889
- if (detailFade > 0.01) {
1890
- vec4 dA = surfTriTap(uSurfAlb, wt * 4.0, tw, lA);
1891
- cA *= mix(1.0, clamp(dot(dA.rgb, LUMA) / max(dot(cA, LUMA), 0.04), 0.35, 2.4), detailFade * 1.3);
1892
- nA += surfTriNrm(uSurfNrm, wt * 4.0, tw, lA, n) * detailFade * 1.4;
1893
- dispA = mix(dispA, dA.a, detailFade); // fine displacement poke near the deck
1894
- }
1883
+ // SINGLE HIGH-FREQUENCY OCTAVE + MIPS (user 2026-06-14 'instead of swapping out texture octaves,
1884
+ // just use the highest one and let it mip and get rid of the fade-in'): sample the material at the
1885
+ // FINE scale (wt*4, ~0.6m/texel) at EVERY distance and let the GPU mip chain average it down far
1886
+ // off (= the old low-freq look) -- no second octave, no detailFade. Albedo = luma STRUCTURE (macro
1887
+ // color carries chroma); normal strong (x1.4); displacement drives the height-blend (mips soften
1888
+ // the displacement, and thus the blend, at distance automatically).
1889
+ highp vec3 wt4 = wt * 4.0;
1890
+ vec4 albA = surfTriTap(uSurfAlb, wt4, tw, lA);
1891
+ vec3 cA = vec3(dot(albA.rgb, LUMA)); vec3 nA = surfTriNrm(uSurfNrm, wt4, tw, lA, n) * 1.4; float dispA = albA.a;
1895
1892
  vec4 texAlb = vec4(cA, dispA); vec3 texNrm = nA;
1893
+ float splatRock = (abs(lA - 1.0) < 0.5) ? 1.0 : 0.0; // height-blend rock fraction (layer 1 = rock)
1896
1894
  if (wB > 0.02) { // second layer only where a real transition exists
1897
- vec4 albB = surfTriTap(uSurfAlb, wt, tw, lB);
1898
- vec3 cB = vec3(dot(albB.rgb, LUMA)); vec3 nB = surfTriNrm(uSurfNrm, wt, tw, lB, n); float dispB = albB.a;
1899
- if (detailFade > 0.01) {
1900
- vec4 dB = surfTriTap(uSurfAlb, wt * 4.0, tw, lB);
1901
- cB *= mix(1.0, clamp(dot(dB.rgb, LUMA) / max(dot(cB, LUMA), 0.04), 0.35, 2.4), detailFade * 1.3);
1902
- nB += surfTriNrm(uSurfNrm, wt * 4.0, tw, lB, n) * detailFade * 1.4;
1903
- dispB = mix(dispB, dB.a, detailFade);
1904
- }
1905
- // HEIGHT-BLEND POKE-THROUGH (user 2026-06-14 'each texture's higher areas should poke through
1906
- // the other, offset by the ramp'): each layer's height = its DISPLACEMENT + a weight-ramp
1907
- // offset (so the gate still positions the boundary). The higher height wins, blended over a
1908
- // soft WIDTH so the loser's high bumps still poke through near the ramp -> interlocking
1909
- // fingers, never a hard line. This is the ONE blend for ALL pairs (slope-rock, height-snow,
1910
- // climate/area-biome, beach). bw widens close-up (where the detail is rich) for a softer mesh.
1911
- float bw = mix(0.12, 0.30, detailFade);
1895
+ vec4 albB = surfTriTap(uSurfAlb, wt4, tw, lB);
1896
+ vec3 cB = vec3(dot(albB.rgb, LUMA)); vec3 nB = surfTriNrm(uSurfNrm, wt4, tw, lB, n) * 1.4; float dispB = albB.a;
1897
+ // HEIGHT-BLEND POKE-THROUGH (user 'each texture's higher areas should poke through the other,
1898
+ // offset by the ramp'): height = displacement + a weight-ramp offset (gate positions the
1899
+ // boundary); higher wins over a soft width so the loser's high bumps poke through = fingers,
1900
+ // no hard line. ONE blend for ALL pairs. Mips smooth dispA/dispB at distance -> soft far edge.
1901
+ float bw = 0.25;
1912
1902
  float hA = dispA + (bAB - 0.5) * 1.1;
1913
1903
  float hB = dispB + (0.5 - bAB) * 1.1;
1914
1904
  float mh = max(hA, hB) - bw;
@@ -1916,7 +1906,14 @@ void main() {
1916
1906
  float bSharp = waH / max(waH + wbH, 1e-4);
1917
1907
  texAlb = vec4(mix(cB, cA, bSharp), mix(dispB, dispA, bSharp));
1918
1908
  texNrm = mix(nB, nA, bSharp);
1909
+ splatRock = (abs(lA - 1.0) < 0.5 ? bSharp : 0.0) + (abs(lB - 1.0) < 0.5 ? (1.0 - bSharp) : 0.0);
1919
1910
  }
1911
+ // MATCH COLOR TO NORMAL (user 2026-06-14 'green grassy patches with the rock normals -- should be
1912
+ // rock colored or grass normals'): texNrm follows the displacement height-blend (rock on bumps in
1913
+ // the slope-transition band) but the macro albedo used the slope gate -> grass color under a rock
1914
+ // normal. Push the macro color toward bcRock by the splat's actual rock fraction so the COLOR
1915
+ // follows the same selection the NORMAL does (bounded -- splatRock is 0 where rock isn't in top-2).
1916
+ albedo = mix(albedo, bcRock, splatRock * 0.8);
1920
1917
  float k = uTexMix * texFarFade;
1921
1918
  // macro-tinted detail (user 2026-06-10 'the textured patch must be tinted to the same shade
1922
1919
  // as the spot its replacing'): the texture contributes STRUCTURE + relative chroma only,
@@ -1949,6 +1946,12 @@ void main() {
1949
1946
  // grey). The 4x detail carries the fine structure for every material.
1950
1947
  vec3 texIdent = texC * (albedo / max(texL, 0.02));
1951
1948
  albedo = clamp(mix(albedo, mix(detail, texIdent, photoF), k), 0.0, 1.0);
1949
+ // FAKE MIDDAY AO from the displacement (user 2026-06-14 'darken the deepest parts of the
1950
+ // displacement textures a little'): the texture's LOWEST displacement = crevices/pits; darken
1951
+ // them ~18% so the surface reads as sunlit-from-above with soft self-occlusion in the lows.
1952
+ // Faded by k so the far field (where the texture mips out) is untouched.
1953
+ float texAO = mix(1.0, mix(0.82, 1.0, smoothstep(0.0, 0.5, texAlb.a)), k);
1954
+ albedo *= texAO;
1952
1955
  // displacement-normal relief: WORLD-SPACE UDN perturbation from surfTriNrm (each projection
1953
1956
  // plane's tangent axes, not the radial frame). Amplitude capped low (scramble lesson d262b5e);
1954
1957
  // applied AFTER the uReliefShade exaggeration below so the exaggeration never amplifies it.
@@ -2209,11 +2212,11 @@ void main() {
2209
2212
  // peak at the terminator (N.sun~0), fall off toward both day-center and night-center, and
2210
2213
  // SQUARE it so the warm band is tight at the day/night line instead of a fat ring round the
2211
2214
  // whole limb. Warm amber (not pure red) so it reads as sunset haze, not a bruise.
2212
- float gz = 1.0 - abs(dot(normalize(vWorld), sunDir));
2215
+ float gz = 1.0 - abs(dot(nWorld, sunDir));
2213
2216
  float graze = smoothstep(0.55, 1.0, gz); graze *= graze;
2214
2217
  // only on the LIT side of the terminator (mu>0) -- past the line the surface is night and
2215
2218
  // additive amber on near-black extinct land reads as a scorched maroon rim. Day-gate kills it.
2216
- float termDay = smoothstep(-0.02, 0.18, dot(normalize(vWorld), sunDir));
2219
+ float termDay = smoothstep(-0.02, 0.18, dot(nWorld, sunDir));
2217
2220
  hazed += uTerminatorGlow * graze * termDay * vec3(1.0, 0.55, 0.34) * apGate;
2218
2221
  color = mix(lit, hazed, apGate * uHazeMul); // uHazeMul lever (2026-06-10 'pale hazy': full-strength haze milked the midground)
2219
2222
  }
@@ -2243,7 +2246,7 @@ void main() {
2243
2246
  // SEABED LIGHTING (user 'floor too flat/dim'): the deep floor gets almost no direct sun, so it
2244
2247
  // read dim+flat. Lift the brightness and add an up-facing fill (brighter where the surface faces
2245
2248
  // up, darker on slopes -> 3D relief) + a touch of slope contrast keyed on the lit normal vNrm.
2246
- float upFill = mix(0.75, 1.35, clamp(dot(vNrm, normalize(vWorld)) * 0.5 + 0.5, 0.0, 1.0));
2249
+ float upFill = mix(0.75, 1.35, clamp(dot(vNrm, nWorld) * 0.5 + 0.5, 0.0, 1.0));
2247
2250
  color *= upFill * 1.5;
2248
2251
  color = mix(color * uwTrans + uwFog * (1.0 - uwTrans), uwFog, smoothstep(100000.0, 500000.0, dKm * 1000.0));
2249
2252
  }
@@ -2311,7 +2314,7 @@ void main() {
2311
2314
  // SPHERE sun angle to EVERY fragment (land + ocean) so the globe reads 3D: bright at the sub-solar
2312
2315
  // point, falling to dark across the day side into the terminator. dayShade = smoothstep over the
2313
2316
  // surface-normal . sun, with a small ambient floor (0.10) so the night/terminator isn't pure black.
2314
- float macroMu = dot(normalize(vWorld), sunDir);
2317
+ float macroMu = dot(nWorld, sunDir);
2315
2318
  // SOFT FLOORED TERMINATOR (Real-World Look): a wider, smoother twilight band (uTermWidth) with a
2316
2319
  // night floor (uNightFloor) so framed night longitudes keep faint detail instead of crushing to
2317
2320
  // black (defect #4). The old hard 0.10..-0.12,0.55 band was too steep + too dark.
@@ -2320,7 +2323,7 @@ void main() {
2320
2323
  // rounded 3D form instead of a flat disc. viewGraze = surface-normal . view-dir, ->1 facing the
2321
2324
  // camera (disc centre), ->0 at the limb. Only bites near the limb so it does not dim the main face.
2322
2325
  vec3 viewDir = normalize(camWorld - vWorld);
2323
- float viewGraze = max(dot(normalize(vWorld), viewDir), 0.0);
2326
+ float viewGraze = max(dot(nWorld, viewDir), 0.0);
2324
2327
  float limb = 0.45 + 0.55 * smoothstep(0.0, 0.45, viewGraze); // 0.45 at the limb -> 1.0 facing camera
2325
2328
  // NIGHT / SHADOW FILL (user 2026-06-09: 'we want night lights in the SHADOW areas, not city lights'):
2326
2329
  // a UNIFORM dim fill that lifts the unlit hemisphere out of pure black -- a cool ambient night light,
@@ -37,7 +37,7 @@ const DEFAULTS = {
37
37
  bcLowland: [0.20,0.34,0.15], bcGrass: [0.26,0.40,0.17], bcRock: [0.52,0.43,0.34], // bcRock -> a clearly WARMER TAN-GREY (R>G>B) so it reads as ROCK when lit, not olive-green (the [0.46,0.42,0.37] near-grey read green next to the biome). state.biome OVERRIDES gl-render defaults via window.__gen.
38
38
  bcSnow: [0.92,0.94,0.97],
39
39
  bandEdgesLo: [150.0,1200.0], bandEdgesHi: [3500.0,6500.0], snowEdges: [6000.0,8500.0], // 8000/10500->6000/8500 (user 2026-06-11 'snowy mountains disappeared' -- see gl-render snowEdges note) // snowEdges 5200/7000->8000/10500 (user 2026-06-10 'entire terrain white': the rock-by-height fix unmasked snow gates tuned pre-4x; full snow from 5.2km whitened the 11.6km massifs; coldSnow onset = snowEdges.x*0.5 follows) // bandEdgesHi 1600/3200->3500/6500 (user 2026-06-10 'rockface everywhere'): tuned on the pre-4x terrain; with 11.6km peaks everything above 3200m read rock BY HEIGHT alone -- rescale the treeline to the new elevation range
40
- seaDepthM: 3000.0, slopeRock: [0.0,0.22], // hi 0.3->0.22 (user 2026-06-14 'rock face angle more sensitive, gentler slopes turn to rock'): rock fully engages by ~0.22 slope; lo stays 0.0 so truly-flat (rockSlope~0) keeps grass
40
+ seaDepthM: 3000.0, slopeRock: [-0.0,0.4], // [-0.0,0.4] USER-SET 2026-06-14 (wider rock-slope band)
41
41
  },
42
42
  // REAL-WORLD LOOK overhaul (terraformable lighting/shading levers; applyShaderGlobals sets window
43
43
  // globals; gl-render reads them via _g()). Beer-Lambert ocean, biome sat, mottle, sky-fill relief,