@d5techs/3dgs-lib 1.4.70 → 1.4.71

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
@@ -2096,103 +2096,6 @@ class SceneAidsRenderer {
2096
2096
  this.axesBindGroup = null;
2097
2097
  }
2098
2098
  }
2099
- async function loadHdr(url) {
2100
- const res = await fetch(url);
2101
- if (!res.ok) throw new Error(`HdrLoader: fetch failed ${url} (${res.status})`);
2102
- const buf = await res.arrayBuffer();
2103
- return parseHdr(new Uint8Array(buf));
2104
- }
2105
- function parseHdr(bytes) {
2106
- let pos = 0;
2107
- const readLine = () => {
2108
- let line = "";
2109
- while (pos < bytes.length) {
2110
- const c = bytes[pos++];
2111
- if (c === 10) return line;
2112
- line += String.fromCharCode(c);
2113
- }
2114
- return line;
2115
- };
2116
- const magic = readLine();
2117
- if (!magic.startsWith("#?")) throw new Error("HdrLoader: not a Radiance HDR file");
2118
- while (pos < bytes.length) {
2119
- const line = readLine();
2120
- if (line.length === 0) break;
2121
- }
2122
- const resLine = readLine();
2123
- const m = resLine.match(/-Y\s+(\d+)\s+\+X\s+(\d+)/);
2124
- if (!m) throw new Error("HdrLoader: unsupported resolution format: " + resLine);
2125
- const height = parseInt(m[1], 10);
2126
- const width = parseInt(m[2], 10);
2127
- const rgbe = new Uint8Array(width * height * 4);
2128
- for (let y = 0; y < height; y++) {
2129
- const scanline = decodeScanline(bytes, pos, width);
2130
- pos = scanline.newPos;
2131
- rgbe.set(scanline.pixels, y * width * 4);
2132
- }
2133
- const data = new Float32Array(width * height * 4);
2134
- for (let i = 0; i < width * height; i++) {
2135
- const r = rgbe[i * 4];
2136
- const g = rgbe[i * 4 + 1];
2137
- const b = rgbe[i * 4 + 2];
2138
- const e = rgbe[i * 4 + 3];
2139
- if (e === 0) {
2140
- data[i * 4] = 0;
2141
- data[i * 4 + 1] = 0;
2142
- data[i * 4 + 2] = 0;
2143
- } else {
2144
- const scale = Math.pow(2, e - 128 - 8);
2145
- data[i * 4] = r * scale;
2146
- data[i * 4 + 1] = g * scale;
2147
- data[i * 4 + 2] = b * scale;
2148
- }
2149
- data[i * 4 + 3] = 1;
2150
- }
2151
- return { width, height, data };
2152
- }
2153
- function decodeScanline(bytes, pos, width) {
2154
- const pixels = new Uint8Array(width * 4);
2155
- if (width < 8 || width > 32767) {
2156
- for (let i = 0; i < width * 4; i++) pixels[i] = bytes[pos++];
2157
- return { pixels, newPos: pos };
2158
- }
2159
- const b0 = bytes[pos], b1 = bytes[pos + 1], b22 = bytes[pos + 2], b3 = bytes[pos + 3];
2160
- if (b0 !== 2 || b1 !== 2 || (b22 & 128) !== 0) {
2161
- for (let i = 0; i < width * 4; i++) pixels[i] = bytes[pos++];
2162
- return { pixels, newPos: pos };
2163
- }
2164
- const lineWidth = b22 << 8 | b3;
2165
- if (lineWidth !== width) throw new Error("HdrLoader: scanline width mismatch");
2166
- pos += 4;
2167
- for (let ch = 0; ch < 4; ch++) {
2168
- let x = 0;
2169
- while (x < width) {
2170
- const code = bytes[pos++];
2171
- if (code > 128) {
2172
- const count = code - 128;
2173
- const val = bytes[pos++];
2174
- for (let i = 0; i < count; i++) pixels[(x + i) * 4 + ch] = val;
2175
- x += count;
2176
- } else {
2177
- for (let i = 0; i < code; i++) pixels[(x + i) * 4 + ch] = bytes[pos++];
2178
- x += code;
2179
- }
2180
- }
2181
- }
2182
- return { pixels, newPos: pos };
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
- }
2196
2099
  const CUBE_WGSL = (
2197
2100
  /* wgsl */
2198
2101
  `
@@ -2212,61 +2115,6 @@ struct V { @builtin(position) pos: vec4<f32>, @location(0) dir: vec3<f32> };
2212
2115
  return vec4(textureSample(cubeTexture, cubeSampler, normalize(i.dir)).rgb, 1.);
2213
2116
  }`
2214
2117
  );
2215
- const EQUIRECT_WGSL = (
2216
- /* wgsl */
2217
- `
2218
- struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32>, cam: vec4<f32> };
2219
- @group(0) @binding(0) var<uniform> u: Uniforms;
2220
- @group(0) @binding(1) var envSampler: sampler;
2221
- @group(0) @binding(2) var envTexture: texture_2d<f32>;
2222
- struct V { @builtin(position) pos: vec4<f32>, @location(0) dir: vec3<f32> };
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
-
2229
- @vertex fn vs(@builtin(vertex_index) vi: u32) -> V {
2230
- let ps = array<vec2<f32>,3>(vec2(-1.,-1.),vec2(3.,-1.),vec2(-1.,3.));
2231
- var o: V; let p = ps[vi]; o.pos = vec4(p, 0., 1.);
2232
- let e = vec3(p.x*u.col0.w, p.y*u.col1.w, -1.);
2233
- o.dir = vec3(dot(u.col0.xyz,e), dot(u.col1.xyz,e), dot(u.col2.xyz,e));
2234
- return o;
2235
- }
2236
-
2237
- @fragment fn fs(i: V) -> @location(0) vec4<f32> {
2238
- let d = normalize(i.dir);
2239
- let cx = u.cam.x;
2240
- let cy = max(u.cam.y, 0.1); // clamp: camera always above ground
2241
- let cz = u.cam.z;
2242
- let captureH = u.cam.w;
2243
-
2244
- // --- sky: sample HDR with original direction ---
2245
- let skyUv = dirToUv(d);
2246
-
2247
- // --- ground: camera-centered, height-independent projection ---
2248
- let dyClamped = min(d.y, -0.0001);
2249
- let ratio = d.x / dyClamped;
2250
- let ratioZ = d.z / dyClamped;
2251
-
2252
- // K = captureH controls ground spread (smaller = more horizon, larger = more nadir)
2253
- let groundDir = normalize(vec3(-ratio, -captureH, -ratioZ));
2254
- let groundUv = dirToUv(groundDir);
2255
-
2256
- // --- sample both (uniform control flow) ---
2257
- let skyColor = textureSample(envTexture, envSampler, skyUv).rgb;
2258
- let groundColor = textureSample(envTexture, envSampler, groundUv).rgb;
2259
-
2260
- // Sharp blend at horizon — cy is always > 0 so no aboveGround check needed
2261
- let horizonBlend = smoothstep(0.002, -0.002, d.y);
2262
- let c = mix(skyColor, groundColor, horizonBlend);
2263
-
2264
- // Reinhard tone mapping + gamma
2265
- let mapped = c / (c + vec3(1.));
2266
- let gamma = pow(mapped, vec3(1./2.2));
2267
- return vec4(gamma, 1.);
2268
- }`
2269
- );
2270
2118
  class SkyboxRenderer {
2271
2119
  constructor(device, format, depthFormat) {
2272
2120
  __publicField(this, "device");
@@ -2275,14 +2123,10 @@ class SkyboxRenderer {
2275
2123
  __publicField(this, "uniformData", new Float32Array(16));
2276
2124
  __publicField(this, "cubePipeline");
2277
2125
  __publicField(this, "cubeBindGroupLayout");
2278
- __publicField(this, "equirectPipeline");
2279
- __publicField(this, "equirectBindGroupLayout");
2280
2126
  __publicField(this, "texture", null);
2281
2127
  __publicField(this, "bindGroup", null);
2282
2128
  __publicField(this, "frameReady", false);
2283
- __publicField(this, "mode", "none");
2284
- /** Virtual HDR capture height (world units). Controls ground texture spread. */
2285
- __publicField(this, "groundRadius", 50);
2129
+ __publicField(this, "active", false);
2286
2130
  this.device = device;
2287
2131
  const depthStencil = {
2288
2132
  format: depthFormat,
@@ -2303,20 +2147,6 @@ class SkyboxRenderer {
2303
2147
  primitive: { topology: "triangle-list", cullMode: "none" },
2304
2148
  depthStencil
2305
2149
  });
2306
- this.equirectBindGroupLayout = device.createBindGroupLayout({
2307
- entries: [
2308
- { binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
2309
- { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
2310
- { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "2d" } }
2311
- ]
2312
- });
2313
- this.equirectPipeline = device.createRenderPipeline({
2314
- layout: device.createPipelineLayout({ bindGroupLayouts: [this.equirectBindGroupLayout] }),
2315
- vertex: { module: device.createShaderModule({ code: EQUIRECT_WGSL }), entryPoint: "vs" },
2316
- fragment: { module: device.createShaderModule({ code: EQUIRECT_WGSL }), entryPoint: "fs", targets: [{ format }] },
2317
- primitive: { topology: "triangle-list", cullMode: "none" },
2318
- depthStencil
2319
- });
2320
2150
  this.uniformBuffer = device.createBuffer({
2321
2151
  size: 64,
2322
2152
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
@@ -2324,9 +2154,8 @@ class SkyboxRenderer {
2324
2154
  this.sampler = device.createSampler({ magFilter: "linear", minFilter: "linear" });
2325
2155
  }
2326
2156
  get isActive() {
2327
- return this.mode !== "none" && this.texture !== null && this.bindGroup !== null;
2157
+ return this.active && this.texture !== null && this.bindGroup !== null;
2328
2158
  }
2329
- // ---- cubemap ----
2330
2159
  async loadCubemap(faceUrls) {
2331
2160
  this.clear();
2332
2161
  const bitmaps = await Promise.all(
@@ -2367,41 +2196,9 @@ class SkyboxRenderer {
2367
2196
  ]
2368
2197
  });
2369
2198
  this.texture = tex;
2370
- this.mode = "cubemap";
2371
- }
2372
- // ---- equirectangular HDR ----
2373
- async loadEquirectangular(url) {
2374
- this.clear();
2375
- const hdr = await loadHdr(url);
2376
- const pixelCount = hdr.width * hdr.height * 4;
2377
- const f16 = new Uint16Array(pixelCount);
2378
- for (let i = 0; i < pixelCount; i++) {
2379
- f16[i] = float32ToFloat16(hdr.data[i]);
2380
- }
2381
- const tex = this.device.createTexture({
2382
- size: [hdr.width, hdr.height],
2383
- format: "rgba16float",
2384
- usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
2385
- });
2386
- this.device.queue.writeTexture(
2387
- { texture: tex },
2388
- f16.buffer,
2389
- { bytesPerRow: hdr.width * 8, rowsPerImage: hdr.height },
2390
- { width: hdr.width, height: hdr.height }
2391
- );
2392
- this.bindGroup = this.device.createBindGroup({
2393
- layout: this.equirectBindGroupLayout,
2394
- entries: [
2395
- { binding: 0, resource: { buffer: this.uniformBuffer } },
2396
- { binding: 1, resource: this.sampler },
2397
- { binding: 2, resource: tex.createView() }
2398
- ]
2399
- });
2400
- this.texture = tex;
2401
- this.mode = "equirect";
2199
+ this.active = true;
2402
2200
  }
2403
- // ---- common ----
2404
- prepareFrame(viewMatrix, projectionMatrix, cameraPosition) {
2201
+ prepareFrame(viewMatrix, projectionMatrix) {
2405
2202
  if (!this.isActive) {
2406
2203
  this.frameReady = false;
2407
2204
  return;
@@ -2419,17 +2216,12 @@ class SkyboxRenderer {
2419
2216
  ud[9] = viewMatrix[9];
2420
2217
  ud[10] = viewMatrix[10];
2421
2218
  ud[11] = 0;
2422
- ud[12] = cameraPosition ? cameraPosition[0] : 0;
2423
- ud[13] = cameraPosition ? cameraPosition[1] : 0;
2424
- ud[14] = cameraPosition ? cameraPosition[2] : 0;
2425
- ud[15] = this.groundRadius;
2426
2219
  this.device.queue.writeBuffer(this.uniformBuffer, 0, ud);
2427
2220
  this.frameReady = true;
2428
2221
  }
2429
2222
  draw(pass) {
2430
2223
  if (!this.frameReady || !this.bindGroup) return;
2431
- const pipeline = this.mode === "cubemap" ? this.cubePipeline : this.equirectPipeline;
2432
- pass.setPipeline(pipeline);
2224
+ pass.setPipeline(this.cubePipeline);
2433
2225
  pass.setBindGroup(0, this.bindGroup);
2434
2226
  pass.draw(3);
2435
2227
  }
@@ -2440,7 +2232,7 @@ class SkyboxRenderer {
2440
2232
  }
2441
2233
  this.bindGroup = null;
2442
2234
  this.frameReady = false;
2443
- this.mode = "none";
2235
+ this.active = false;
2444
2236
  }
2445
2237
  destroy() {
2446
2238
  this.clear();
@@ -19059,36 +18851,9 @@ class App {
19059
18851
  this.updateAdaptivePerformance();
19060
18852
  this.hotspotManager.updateBillboards();
19061
18853
  if ((_a2 = this.skyboxRenderer) == null ? void 0 : _a2.isActive) {
19062
- const cam = this.camera.position;
19063
- let groundLevel = 0;
19064
- const gsRendererForGround = this.sceneManager.getGSRenderer();
19065
- const bbox = (gsRendererForGround == null ? void 0 : gsRendererForGround.getBoundingBox()) ?? null;
19066
- if (bbox) {
19067
- const m = gsRendererForGround.getModelMatrix();
19068
- groundLevel = m[5] * bbox.min[1] + m[13];
19069
- }
19070
- if (!this._hdrDebugLogged) {
19071
- this._hdrDebugLogged = true;
19072
- console.log(
19073
- "[HDR Ground Debug]",
19074
- "hasGsRenderer:",
19075
- !!gsRendererForGround,
19076
- "hasBbox:",
19077
- !!bbox,
19078
- "bbox:",
19079
- bbox ? { min: [...bbox.min], max: [...bbox.max], center: [...bbox.center] } : null,
19080
- "groundLevel:",
19081
- groundLevel,
19082
- "cam:",
19083
- [cam[0].toFixed(2), cam[1].toFixed(2), cam[2].toFixed(2)]
19084
- );
19085
- }
19086
- const camH = cam[1] - groundLevel;
19087
- this.skyboxRenderer.groundRadius = 0.3;
19088
18854
  this.skyboxRenderer.prepareFrame(
19089
18855
  this.camera.viewMatrix,
19090
- this.camera.projectionMatrix,
19091
- [cam[0], camH, cam[2]]
18856
+ this.camera.projectionMatrix
19092
18857
  );
19093
18858
  }
19094
18859
  const pass = this.renderer.beginFrame();
@@ -19273,21 +19038,6 @@ class App {
19273
19038
  faceUrls.negZ
19274
19039
  ]);
19275
19040
  }
19276
- async setHdrBackground(url) {
19277
- if (!this.skyboxRenderer) {
19278
- this.skyboxRenderer = new SkyboxRenderer(
19279
- this.renderer.device,
19280
- this.renderer.format,
19281
- "depth24plus"
19282
- );
19283
- }
19284
- await this.skyboxRenderer.loadEquirectangular(url);
19285
- }
19286
- setHdrGroundRadius(radius) {
19287
- if (this.skyboxRenderer) {
19288
- this.skyboxRenderer.groundRadius = radius;
19289
- }
19290
- }
19291
19041
  clearSkybox() {
19292
19042
  var _a2;
19293
19043
  (_a2 = this.skyboxRenderer) == null ? void 0 : _a2.clear();
@@ -19780,7 +19530,6 @@ export {
19780
19530
  getRecommendedDPR,
19781
19531
  isMobileDevice,
19782
19532
  isWebGPUSupported,
19783
- loadHdr,
19784
19533
  loadPLY,
19785
19534
  loadPLYMobile,
19786
19535
  loadSOG,