@d5techs/3dgs-lib 1.4.53 → 1.4.55

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/dist/3dgs-lib.js CHANGED
@@ -2181,10 +2181,22 @@ function decodeScanline(bytes, pos, width) {
2181
2181
  }
2182
2182
  return { pixels, newPos: pos };
2183
2183
  }
2184
+ const f32Buf = new Float32Array(1);
2185
+ const u32Buf = new Uint32Array(f32Buf.buffer);
2186
+ function float32ToFloat16(val) {
2187
+ f32Buf[0] = val;
2188
+ const f = u32Buf[0];
2189
+ const sign = f >> 16 & 32768;
2190
+ const exp = (f >> 23 & 255) - 127 + 15;
2191
+ const frac = f >> 13 & 1023;
2192
+ if (exp <= 0) return sign;
2193
+ if (exp >= 31) return sign | 31744;
2194
+ return sign | exp << 10 | frac;
2195
+ }
2184
2196
  const CUBE_WGSL = (
2185
2197
  /* wgsl */
2186
2198
  `
2187
- struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32> };
2199
+ struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32>, extra: vec4<f32> };
2188
2200
  @group(0) @binding(0) var<uniform> u: Uniforms;
2189
2201
  @group(0) @binding(1) var cubeSampler: sampler;
2190
2202
  @group(0) @binding(2) var cubeTexture: texture_cube<f32>;
@@ -2203,12 +2215,17 @@ struct V { @builtin(position) pos: vec4<f32>, @location(0) dir: vec3<f32> };
2203
2215
  const EQUIRECT_WGSL = (
2204
2216
  /* wgsl */
2205
2217
  `
2206
- struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32> };
2218
+ struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32>, extra: vec4<f32> };
2207
2219
  @group(0) @binding(0) var<uniform> u: Uniforms;
2208
2220
  @group(0) @binding(1) var envSampler: sampler;
2209
2221
  @group(0) @binding(2) var envTexture: texture_2d<f32>;
2210
2222
  struct V { @builtin(position) pos: vec4<f32>, @location(0) dir: vec3<f32> };
2211
2223
  const PI: f32 = 3.14159265358979;
2224
+
2225
+ fn dirToUv(d: vec3<f32>) -> vec2<f32> {
2226
+ return vec2(atan2(d.z, d.x) / (2.*PI) + .5, 1. - (asin(clamp(d.y,-1.,1.)) / PI + .5));
2227
+ }
2228
+
2212
2229
  @vertex fn vs(@builtin(vertex_index) vi: u32) -> V {
2213
2230
  let ps = array<vec2<f32>,3>(vec2(-1.,-1.),vec2(3.,-1.),vec2(-1.,3.));
2214
2231
  var o: V; let p = ps[vi]; o.pos = vec4(p, 0., 1.);
@@ -2216,10 +2233,44 @@ const PI: f32 = 3.14159265358979;
2216
2233
  o.dir = vec3(dot(u.col0.xyz,e), dot(u.col1.xyz,e), dot(u.col2.xyz,e));
2217
2234
  return o;
2218
2235
  }
2236
+
2219
2237
  @fragment fn fs(i: V) -> @location(0) vec4<f32> {
2220
2238
  let d = normalize(i.dir);
2221
- let uv = vec2(atan2(d.z, d.x) / (2.*PI) + .5, asin(clamp(d.y,-1.,1.)) / PI + .5);
2222
- let c = textureSample(envTexture, envSampler, vec2(uv.x, 1. - uv.y)).rgb;
2239
+ let camY = u.extra.x;
2240
+ let sphereR = u.extra.y;
2241
+
2242
+ // --- sky UV (always computed) ---
2243
+ let skyUv = dirToUv(d);
2244
+
2245
+ // --- ground projection UV (always computed to satisfy uniform control flow) ---
2246
+ // ray-plane intersection: camera at height camY, plane y=0
2247
+ let dySafe = select(d.y, -0.0001, d.y > -0.0001);
2248
+ let t = -camY / dySafe;
2249
+ let hitX = t * d.x;
2250
+ let hitZ = t * d.z;
2251
+ let hitDist2 = hitX * hitX + hitZ * hitZ;
2252
+ let R2 = sphereR * sphereR;
2253
+ // clamp hit to sphere radius to avoid extreme stretching at edges
2254
+ let needClamp = hitDist2 > R2;
2255
+ let clampScale = select(1.0, sphereR / sqrt(max(hitDist2, 0.0001)), needClamp);
2256
+ let pX = hitX * clampScale;
2257
+ let pZ = hitZ * clampScale;
2258
+ // project back onto sphere: y = -sqrt(R^2 - x^2 - z^2)
2259
+ let projY = -sqrt(max(R2 - pX * pX - pZ * pZ, 0.0));
2260
+ let groundDir = normalize(vec3(pX, projY, pZ));
2261
+ let groundUv = dirToUv(groundDir);
2262
+
2263
+ // --- sample both (unconditional = uniform control flow) ---
2264
+ let skyColor = textureSample(envTexture, envSampler, skyUv).rgb;
2265
+ let groundColor = textureSample(envTexture, envSampler, groundUv).rgb;
2266
+
2267
+ // blend near horizon for smooth transition
2268
+ let blend = smoothstep(0.0, -0.08, d.y);
2269
+ let mixed = mix(skyColor, groundColor, blend);
2270
+ let useGround = d.y < 0.0 && camY > 0.01;
2271
+ let c = select(skyColor, mixed, useGround);
2272
+
2273
+ // Reinhard tone mapping + gamma
2223
2274
  let mapped = c / (c + vec3(1.));
2224
2275
  let gamma = pow(mapped, vec3(1./2.2));
2225
2276
  return vec4(gamma, 1.);
@@ -2230,7 +2281,7 @@ class SkyboxRenderer {
2230
2281
  __publicField(this, "device");
2231
2282
  __publicField(this, "uniformBuffer");
2232
2283
  __publicField(this, "sampler");
2233
- __publicField(this, "uniformData", new Float32Array(12));
2284
+ __publicField(this, "uniformData", new Float32Array(16));
2234
2285
  __publicField(this, "cubePipeline");
2235
2286
  __publicField(this, "cubeBindGroupLayout");
2236
2287
  __publicField(this, "equirectPipeline");
@@ -2239,6 +2290,8 @@ class SkyboxRenderer {
2239
2290
  __publicField(this, "bindGroup", null);
2240
2291
  __publicField(this, "frameReady", false);
2241
2292
  __publicField(this, "mode", "none");
2293
+ /** Ground projection sphere radius (world units). Larger = ground extends further. */
2294
+ __publicField(this, "groundRadius", 32);
2242
2295
  this.device = device;
2243
2296
  const depthStencil = {
2244
2297
  format: depthFormat,
@@ -2247,7 +2300,7 @@ class SkyboxRenderer {
2247
2300
  };
2248
2301
  this.cubeBindGroupLayout = device.createBindGroupLayout({
2249
2302
  entries: [
2250
- { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
2303
+ { binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
2251
2304
  { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
2252
2305
  { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "cube" } }
2253
2306
  ]
@@ -2261,7 +2314,7 @@ class SkyboxRenderer {
2261
2314
  });
2262
2315
  this.equirectBindGroupLayout = device.createBindGroupLayout({
2263
2316
  entries: [
2264
- { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
2317
+ { binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
2265
2318
  { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
2266
2319
  { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "2d" } }
2267
2320
  ]
@@ -2274,7 +2327,7 @@ class SkyboxRenderer {
2274
2327
  depthStencil
2275
2328
  });
2276
2329
  this.uniformBuffer = device.createBuffer({
2277
- size: 48,
2330
+ size: 64,
2278
2331
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
2279
2332
  });
2280
2333
  this.sampler = device.createSampler({ magFilter: "linear", minFilter: "linear" });
@@ -2329,15 +2382,20 @@ class SkyboxRenderer {
2329
2382
  async loadEquirectangular(url) {
2330
2383
  this.clear();
2331
2384
  const hdr = await loadHdr(url);
2385
+ const pixelCount = hdr.width * hdr.height * 4;
2386
+ const f16 = new Uint16Array(pixelCount);
2387
+ for (let i = 0; i < pixelCount; i++) {
2388
+ f16[i] = float32ToFloat16(hdr.data[i]);
2389
+ }
2332
2390
  const tex = this.device.createTexture({
2333
2391
  size: [hdr.width, hdr.height],
2334
- format: "rgba32float",
2392
+ format: "rgba16float",
2335
2393
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
2336
2394
  });
2337
2395
  this.device.queue.writeTexture(
2338
2396
  { texture: tex },
2339
- hdr.data.buffer,
2340
- { bytesPerRow: hdr.width * 16, rowsPerImage: hdr.height },
2397
+ f16.buffer,
2398
+ { bytesPerRow: hdr.width * 8, rowsPerImage: hdr.height },
2341
2399
  { width: hdr.width, height: hdr.height }
2342
2400
  );
2343
2401
  this.bindGroup = this.device.createBindGroup({
@@ -2352,7 +2410,7 @@ class SkyboxRenderer {
2352
2410
  this.mode = "equirect";
2353
2411
  }
2354
2412
  // ---- common ----
2355
- prepareFrame(viewMatrix, projectionMatrix) {
2413
+ prepareFrame(viewMatrix, projectionMatrix, cameraPosition) {
2356
2414
  if (!this.isActive) {
2357
2415
  this.frameReady = false;
2358
2416
  return;
@@ -2370,6 +2428,10 @@ class SkyboxRenderer {
2370
2428
  ud[9] = viewMatrix[9];
2371
2429
  ud[10] = viewMatrix[10];
2372
2430
  ud[11] = 0;
2431
+ ud[12] = cameraPosition ? cameraPosition[1] : 0;
2432
+ ud[13] = this.groundRadius;
2433
+ ud[14] = 0;
2434
+ ud[15] = 0;
2373
2435
  this.device.queue.writeBuffer(this.uniformBuffer, 0, ud);
2374
2436
  this.frameReady = true;
2375
2437
  }
@@ -19006,9 +19068,11 @@ class App {
19006
19068
  this.updateAdaptivePerformance();
19007
19069
  this.hotspotManager.updateBillboards();
19008
19070
  if ((_a2 = this.skyboxRenderer) == null ? void 0 : _a2.isActive) {
19071
+ const cam = this.camera.position;
19009
19072
  this.skyboxRenderer.prepareFrame(
19010
19073
  this.camera.viewMatrix,
19011
- this.camera.projectionMatrix
19074
+ this.camera.projectionMatrix,
19075
+ [cam[0], cam[1], cam[2]]
19012
19076
  );
19013
19077
  }
19014
19078
  const pass = this.renderer.beginFrame();
@@ -19203,6 +19267,11 @@ class App {
19203
19267
  }
19204
19268
  await this.skyboxRenderer.loadEquirectangular(url);
19205
19269
  }
19270
+ setHdrGroundRadius(radius) {
19271
+ if (this.skyboxRenderer) {
19272
+ this.skyboxRenderer.groundRadius = radius;
19273
+ }
19274
+ }
19206
19275
  clearSkybox() {
19207
19276
  var _a2;
19208
19277
  (_a2 = this.skyboxRenderer) == null ? void 0 : _a2.clear();