mapspinner 0.1.38 → 0.1.39

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.38",
3
+ "version": "0.1.39",
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
@@ -272,6 +272,72 @@ export async function initMapspinnerRender(gl, opts = {}) {
272
272
  return out[0];
273
273
  }
274
274
 
275
+ // ===== THC HEIGHT-CACHE BAKE (2026-06-14, NON-DESTRUCTIVE) =====
276
+ // A separate program renders composeHeight for one tile into an R32F grid (a fullscreen tri; each
277
+ // fragment = one tile parametric texel). Used FIRST as a readback witness (bake vs procedural
278
+ // sampleGroundM) to prove the bake matches the geometry; the pool/LRU + the VS-sample switch are
279
+ // later DAG nodes. _faceFrames mirror terrain.glsl faceFrame() columns (column-major mat3).
280
+ const THC_BAKE_RES = 130;
281
+ const _faceFrames = [
282
+ [0,0,-1, 0,1,0, 1,0,0], [0,0,1, 0,1,0, -1,0,0],
283
+ [1,0,0, 0,0,-1, 0,1,0], [1,0,0, 0,0,1, 0,-1,0],
284
+ [1,0,0, 0,1,0, 0,0,1], [-1,0,0, 0,1,0, 0,0,-1],
285
+ ];
286
+ let bakeProg=null, bakeTex=null, bakeFbo=null, _bakeBuilding=null; const _bakeUloc=new Map();
287
+ const BU = n => { let l=_bakeUloc.get(n); if(l===undefined){ l=gl.getUniformLocation(bakeProg,n); _bakeUloc.set(n,l);} return l; };
288
+ function ensureBake(){
289
+ if (bakeProg || _bakeBuilding) return;
290
+ _bakeBuilding = (async () => {
291
+ try {
292
+ const bvs = hdr + 'void main(){ vec2 p=vec2((gl_VertexID==1)?3.0:-1.0,(gl_VertexID==2)?3.0:-1.0); gl_Position=vec4(p,0.0,1.0); }';
293
+ const bfs = hdr + '\n#define _HEIGHTBAKE_\n' + src;
294
+ const bv=gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(bv,bvs); gl.compileShader(bv);
295
+ const bf=gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(bf,bfs); gl.compileShader(bf);
296
+ const bp=gl.createProgram(); gl.attachShader(bp,bv); gl.attachShader(bp,bf); gl.linkProgram(bp);
297
+ await awaitProgramLink(bp, bv, bf, 'bake');
298
+ const tex=gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex);
299
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R32F, THC_BAKE_RES, THC_BAKE_RES);
300
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
301
+ const fbo=gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
302
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
303
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
304
+ _bakeUloc.clear(); bakeTex=tex; bakeFbo=fbo; bakeProg=bp;
305
+ } catch(e){ bakeProg=null; try{ if(typeof window!=='undefined') window.__bakeErr=String(e.message||e); }catch(_){} }
306
+ finally { _bakeBuilding=null; }
307
+ })();
308
+ }
309
+ const bakeVao = gl.createVertexArray();
310
+ // bake ONE tile into bakeTex + read it back (Float32Array of THC_BAKE_RES^2 heights). Returns null
311
+ // until the program is built (lazy). NON-DESTRUCTIVE: does not touch the live render path.
312
+ function bakeTileReadback(face, ox, oy, l, level){
313
+ if (!bakeProg){ ensureBake(); return null; }
314
+ gl.bindFramebuffer(gl.FRAMEBUFFER, bakeFbo);
315
+ gl.viewport(0,0,THC_BAKE_RES,THC_BAKE_RES);
316
+ gl.useProgram(bakeProg);
317
+ gl.bindVertexArray(bakeVao);
318
+ if (_hpfTex){ gl.activeTexture(gl.TEXTURE3); gl.bindTexture(gl.TEXTURE_2D_ARRAY,_hpfTex); gl.uniform1i(BU('hpfPool'),3); }
319
+ if (_hpfTex2){ gl.activeTexture(gl.TEXTURE5); gl.bindTexture(gl.TEXTURE_2D_ARRAY,_hpfTex2); gl.uniform1i(BU('hpfPool2'),5); }
320
+ gl.uniform1i(BU('hasHpf'), _hpfTex?1:0);
321
+ setComposeHeightUniforms(BU);
322
+ gl.uniform1f(BU('defRadius'), R);
323
+ gl.uniformMatrix3fv(BU('uBakeFrame'), false, new Float32Array(_faceFrames[face|0]));
324
+ gl.uniform4f(BU('uBakeOffset'), ox, oy, l, level);
325
+ gl.uniform1f(BU('uBakeRes'), THC_BAKE_RES);
326
+ gl.disable(gl.DEPTH_TEST);
327
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
328
+ const buf = new Float32Array(THC_BAKE_RES*THC_BAKE_RES*4);
329
+ gl.readPixels(0,0,THC_BAKE_RES,THC_BAKE_RES, gl.RGBA, gl.FLOAT, buf);
330
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
331
+ gl.bindVertexArray(null);
332
+ const out = new Float32Array(THC_BAKE_RES*THC_BAKE_RES);
333
+ for (let i=0;i<out.length;i++) out[i]=buf[i*4];
334
+ let dbg=null; try{ dbg={ offLoc: BU('uBakeOffset')!=null, resLoc: BU('uBakeRes')!=null, frameLoc: BU('uBakeFrame')!=null,
335
+ offRead: BU('uBakeOffset')?Array.from(gl.getUniform(bakeProg, BU('uBakeOffset'))):null,
336
+ resRead: BU('uBakeRes')?gl.getUniform(bakeProg, BU('uBakeRes')):null }; }catch(e){ dbg={err:String(e)}; }
337
+ return { heights: out, res: THC_BAKE_RES, dbg };
338
+ }
339
+ if (typeof window !== 'undefined') { window.__thcBakeReadback = bakeTileReadback; window.__thcEnsureBake = ensureBake; }
340
+
275
341
  // FLOAT-LINEAR FORMAT PROBE (NOT a quality tier): OES_texture_float_linear lets the HPF atlas pools
276
342
  // filter LINEAR in hardware -> hpfSample collapses to one texture() call. 0 = manual 4-tap fallback.
277
343
  const _halfFloatLinearOK = !!gl.getExtension('OES_texture_float_linear') || !!gl.getExtension('OES_texture_half_float_linear');
@@ -390,7 +390,7 @@ highp vec2 faceWarp(highp vec2 p){ return defRadius * tan((p / defRadius) * 0.78
390
390
  uniform float uDetailOverlay; // amplitude lever (user-tuned 6; 0 = off; __detailOverlay)
391
391
  uniform float uGrid; // interior cells per tile edge (GRID=16); for mesh-based vertex normals
392
392
  uniform float uNrmStepM; // lit-normal FD step in metres (150); uniform-fed to defeat FXC constant folding
393
- #if defined(_VERTEX_) || defined(_PROBE_)
393
+ #if defined(_VERTEX_) || defined(_PROBE_) || defined(_HEIGHTBAKE_)
394
394
  highp float broadShapeM(vec3 dir, float reliefMul, float ridgeMul){ // W7: returns metres (~13000) -> highp
395
395
  if (hasHpf == 0) return 0.0;
396
396
  float mtnAmp = 1.0; // mountain-amplitude (was a window.__mtnAmp uniform; inlined at neutral 1.0)
@@ -2229,3 +2229,22 @@ void main(){
2229
2229
  probeOut = vec4(h, 0.0, 0.0, 1.0);
2230
2230
  }
2231
2231
  #endif
2232
+
2233
+ #ifdef _HEIGHTBAKE_
2234
+ // THC HEIGHT-CACHE BAKE (2026-06-14): render ONE tile's composeHeight into a height-pool texel grid.
2235
+ // Each fragment = one parametric (u,v) of the tile; the dir + faceLocal mapping is IDENTICAL to the
2236
+ // mesh VS (faceWarp(vertex.xy*defOffset.z+defOffset.xy), normalize(frame*vec3(faceLocal,defRadius)))
2237
+ // so the baked height matches the procedural geometry exactly. NON-DESTRUCTIVE: this is a separate
2238
+ // program; the live VS still computes composeHeight until thc-vs-sample switches it to a pool sample.
2239
+ uniform mat3 uBakeFrame; // face-local -> world for this tile's face (== faceFrame(face))
2240
+ uniform vec4 uBakeOffset; // tile (ox, oy, l, level) face-local metres (== iOffset)
2241
+ uniform float uBakeRes; // bake grid resolution (texels per edge)
2242
+ out vec4 bakeOut;
2243
+ void main(){
2244
+ highp vec2 uv = (gl_FragCoord.xy - 0.5) / max(uBakeRes - 1.0, 1.0); // texel -> parametric [0,1]
2245
+ highp vec2 faceLocal = faceWarp(uv * uBakeOffset.z + uBakeOffset.xy);
2246
+ highp vec3 dir0 = normalize(uBakeFrame * vec3(faceLocal, defRadius));
2247
+ highp float h = composeHeight(dir0, faceLocal, uBakeOffset.z);
2248
+ bakeOut = vec4(h, 0.0, 0.0, 1.0);
2249
+ }
2250
+ #endif