@d5techs/3dgs-lib 1.4.70 → 1.4.72

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
  `
@@ -2209,62 +2112,8 @@ struct V { @builtin(position) pos: vec4<f32>, @location(0) dir: vec3<f32> };
2209
2112
  return o;
2210
2113
  }
2211
2114
  @fragment fn fs(i: V) -> @location(0) vec4<f32> {
2212
- return vec4(textureSample(cubeTexture, cubeSampler, normalize(i.dir)).rgb, 1.);
2213
- }`
2214
- );
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.);
2115
+ let d = normalize(i.dir);
2116
+ return vec4(textureSample(cubeTexture, cubeSampler, vec3(-d.x, d.y, d.z)).rgb, 1.);
2268
2117
  }`
2269
2118
  );
2270
2119
  class SkyboxRenderer {
@@ -2275,14 +2124,10 @@ class SkyboxRenderer {
2275
2124
  __publicField(this, "uniformData", new Float32Array(16));
2276
2125
  __publicField(this, "cubePipeline");
2277
2126
  __publicField(this, "cubeBindGroupLayout");
2278
- __publicField(this, "equirectPipeline");
2279
- __publicField(this, "equirectBindGroupLayout");
2280
2127
  __publicField(this, "texture", null);
2281
2128
  __publicField(this, "bindGroup", null);
2282
2129
  __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);
2130
+ __publicField(this, "active", false);
2286
2131
  this.device = device;
2287
2132
  const depthStencil = {
2288
2133
  format: depthFormat,
@@ -2303,30 +2148,15 @@ class SkyboxRenderer {
2303
2148
  primitive: { topology: "triangle-list", cullMode: "none" },
2304
2149
  depthStencil
2305
2150
  });
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
2151
  this.uniformBuffer = device.createBuffer({
2321
2152
  size: 64,
2322
2153
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
2323
2154
  });
2324
- this.sampler = device.createSampler({ magFilter: "linear", minFilter: "linear" });
2155
+ this.sampler = device.createSampler({ magFilter: "linear", minFilter: "linear", mipmapFilter: "linear" });
2325
2156
  }
2326
2157
  get isActive() {
2327
- return this.mode !== "none" && this.texture !== null && this.bindGroup !== null;
2158
+ return this.active && this.texture !== null && this.bindGroup !== null;
2328
2159
  }
2329
- // ---- cubemap ----
2330
2160
  async loadCubemap(faceUrls) {
2331
2161
  this.clear();
2332
2162
  const bitmaps = await Promise.all(
@@ -2344,10 +2174,12 @@ class SkyboxRenderer {
2344
2174
  throw new Error("SkyboxRenderer: all cubemap faces must share the same dimensions");
2345
2175
  }
2346
2176
  }
2177
+ const mipLevelCount = Math.floor(Math.log2(Math.max(width, height))) + 1;
2347
2178
  const tex = this.device.createTexture({
2348
2179
  dimension: "2d",
2349
2180
  size: [width, height, 6],
2350
2181
  format: "rgba8unorm",
2182
+ mipLevelCount,
2351
2183
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
2352
2184
  });
2353
2185
  for (let layer = 0; layer < 6; layer++) {
@@ -2358,6 +2190,7 @@ class SkyboxRenderer {
2358
2190
  );
2359
2191
  }
2360
2192
  bitmaps.forEach((b) => b.close());
2193
+ this.generateMipmaps(tex, width, height, 6, mipLevelCount);
2361
2194
  this.bindGroup = this.device.createBindGroup({
2362
2195
  layout: this.cubeBindGroupLayout,
2363
2196
  entries: [
@@ -2367,41 +2200,9 @@ class SkyboxRenderer {
2367
2200
  ]
2368
2201
  });
2369
2202
  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";
2203
+ this.active = true;
2402
2204
  }
2403
- // ---- common ----
2404
- prepareFrame(viewMatrix, projectionMatrix, cameraPosition) {
2205
+ prepareFrame(viewMatrix, projectionMatrix) {
2405
2206
  if (!this.isActive) {
2406
2207
  this.frameReady = false;
2407
2208
  return;
@@ -2419,20 +2220,89 @@ class SkyboxRenderer {
2419
2220
  ud[9] = viewMatrix[9];
2420
2221
  ud[10] = viewMatrix[10];
2421
2222
  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
2223
  this.device.queue.writeBuffer(this.uniformBuffer, 0, ud);
2427
2224
  this.frameReady = true;
2428
2225
  }
2429
2226
  draw(pass) {
2430
2227
  if (!this.frameReady || !this.bindGroup) return;
2431
- const pipeline = this.mode === "cubemap" ? this.cubePipeline : this.equirectPipeline;
2432
- pass.setPipeline(pipeline);
2228
+ pass.setPipeline(this.cubePipeline);
2433
2229
  pass.setBindGroup(0, this.bindGroup);
2434
2230
  pass.draw(3);
2435
2231
  }
2232
+ generateMipmaps(tex, baseWidth, baseHeight, layerCount, mipLevelCount) {
2233
+ if (mipLevelCount <= 1) return;
2234
+ const module = this.device.createShaderModule({
2235
+ code: (
2236
+ /* wgsl */
2237
+ `
2238
+ @group(0) @binding(0) var src: texture_2d<f32>;
2239
+ @group(0) @binding(1) var srcSampler: sampler;
2240
+ struct V { @builtin(position) pos: vec4<f32>, @location(0) uv: vec2<f32> };
2241
+ @vertex fn vs(@builtin(vertex_index) vi: u32) -> V {
2242
+ let ps = array<vec2<f32>,3>(vec2(0.,0.),vec2(2.,0.),vec2(0.,2.));
2243
+ var o: V; let p = ps[vi];
2244
+ o.pos = vec4(p * 2. - 1., 0., 1.);
2245
+ o.uv = vec2(p.x, 1. - p.y);
2246
+ return o;
2247
+ }
2248
+ @fragment fn fs(i: V) -> @location(0) vec4<f32> {
2249
+ return textureSample(src, srcSampler, i.uv);
2250
+ }`
2251
+ )
2252
+ });
2253
+ const bgl = this.device.createBindGroupLayout({
2254
+ entries: [
2255
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
2256
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } }
2257
+ ]
2258
+ });
2259
+ const pipeline = this.device.createRenderPipeline({
2260
+ layout: this.device.createPipelineLayout({ bindGroupLayouts: [bgl] }),
2261
+ vertex: { module, entryPoint: "vs" },
2262
+ fragment: { module, entryPoint: "fs", targets: [{ format: "rgba8unorm" }] },
2263
+ primitive: { topology: "triangle-list" }
2264
+ });
2265
+ const linearSampler = this.device.createSampler({ magFilter: "linear", minFilter: "linear" });
2266
+ const encoder = this.device.createCommandEncoder();
2267
+ for (let level = 1; level < mipLevelCount; level++) {
2268
+ for (let layer = 0; layer < layerCount; layer++) {
2269
+ const srcView = tex.createView({
2270
+ dimension: "2d",
2271
+ baseMipLevel: level - 1,
2272
+ mipLevelCount: 1,
2273
+ baseArrayLayer: layer,
2274
+ arrayLayerCount: 1
2275
+ });
2276
+ const dstView = tex.createView({
2277
+ dimension: "2d",
2278
+ baseMipLevel: level,
2279
+ mipLevelCount: 1,
2280
+ baseArrayLayer: layer,
2281
+ arrayLayerCount: 1
2282
+ });
2283
+ const bg = this.device.createBindGroup({
2284
+ layout: bgl,
2285
+ entries: [
2286
+ { binding: 0, resource: srcView },
2287
+ { binding: 1, resource: linearSampler }
2288
+ ]
2289
+ });
2290
+ const pass = encoder.beginRenderPass({
2291
+ colorAttachments: [{
2292
+ view: dstView,
2293
+ loadOp: "clear",
2294
+ storeOp: "store",
2295
+ clearValue: { r: 0, g: 0, b: 0, a: 1 }
2296
+ }]
2297
+ });
2298
+ pass.setPipeline(pipeline);
2299
+ pass.setBindGroup(0, bg);
2300
+ pass.draw(3);
2301
+ pass.end();
2302
+ }
2303
+ }
2304
+ this.device.queue.submit([encoder.finish()]);
2305
+ }
2436
2306
  clear() {
2437
2307
  if (this.texture) {
2438
2308
  this.texture.destroy();
@@ -2440,7 +2310,7 @@ class SkyboxRenderer {
2440
2310
  }
2441
2311
  this.bindGroup = null;
2442
2312
  this.frameReady = false;
2443
- this.mode = "none";
2313
+ this.active = false;
2444
2314
  }
2445
2315
  destroy() {
2446
2316
  this.clear();
@@ -19059,36 +18929,9 @@ class App {
19059
18929
  this.updateAdaptivePerformance();
19060
18930
  this.hotspotManager.updateBillboards();
19061
18931
  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
18932
  this.skyboxRenderer.prepareFrame(
19089
18933
  this.camera.viewMatrix,
19090
- this.camera.projectionMatrix,
19091
- [cam[0], camH, cam[2]]
18934
+ this.camera.projectionMatrix
19092
18935
  );
19093
18936
  }
19094
18937
  const pass = this.renderer.beginFrame();
@@ -19273,21 +19116,6 @@ class App {
19273
19116
  faceUrls.negZ
19274
19117
  ]);
19275
19118
  }
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
19119
  clearSkybox() {
19292
19120
  var _a2;
19293
19121
  (_a2 = this.skyboxRenderer) == null ? void 0 : _a2.clear();
@@ -19780,7 +19608,6 @@ export {
19780
19608
  getRecommendedDPR,
19781
19609
  isMobileDevice,
19782
19610
  isWebGPUSupported,
19783
- loadHdr,
19784
19611
  loadPLY,
19785
19612
  loadPLYMobile,
19786
19613
  loadSOG,