@d5techs/3dgs-lib 1.4.52 → 1.4.53

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,122 +2096,199 @@ class SceneAidsRenderer {
2096
2096
  this.axesBindGroup = null;
2097
2097
  }
2098
2098
  }
2099
- const WGSL = (
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 CUBE_WGSL = (
2100
2185
  /* wgsl */
2101
2186
  `
2102
- struct Uniforms {
2103
- col0: vec4<f32>,
2104
- col1: vec4<f32>,
2105
- col2: vec4<f32>,
2106
- };
2107
-
2187
+ struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32> };
2108
2188
  @group(0) @binding(0) var<uniform> u: Uniforms;
2109
2189
  @group(0) @binding(1) var cubeSampler: sampler;
2110
2190
  @group(0) @binding(2) var cubeTexture: texture_cube<f32>;
2111
-
2112
- struct VSOut {
2113
- @builtin(position) position: vec4<f32>,
2114
- @location(0) dir: vec3<f32>,
2115
- };
2116
-
2117
- @vertex
2118
- fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
2119
- let positions = array<vec2<f32>, 3>(
2120
- vec2<f32>(-1.0, -1.0),
2121
- vec2<f32>(3.0, -1.0),
2122
- vec2<f32>(-1.0, 3.0),
2123
- );
2124
- var out: VSOut;
2125
- let p = positions[vi];
2126
- out.position = vec4<f32>(p, 0.0, 1.0);
2127
-
2128
- let eyeDir = vec3<f32>(p.x * u.col0.w, p.y * u.col1.w, -1.0);
2129
-
2130
- out.dir = vec3<f32>(
2131
- dot(u.col0.xyz, eyeDir),
2132
- dot(u.col1.xyz, eyeDir),
2133
- dot(u.col2.xyz, eyeDir),
2134
- );
2135
- return out;
2191
+ struct V { @builtin(position) pos: vec4<f32>, @location(0) dir: vec3<f32> };
2192
+ @vertex fn vs(@builtin(vertex_index) vi: u32) -> V {
2193
+ let ps = array<vec2<f32>,3>(vec2(-1.,-1.),vec2(3.,-1.),vec2(-1.,3.));
2194
+ var o: V; let p = ps[vi]; o.pos = vec4(p, 0., 1.);
2195
+ let e = vec3(p.x*u.col0.w, p.y*u.col1.w, -1.);
2196
+ o.dir = vec3(dot(u.col0.xyz,e), dot(u.col1.xyz,e), dot(u.col2.xyz,e));
2197
+ return o;
2136
2198
  }
2137
-
2138
- @fragment
2139
- fn fs(input: VSOut) -> @location(0) vec4<f32> {
2140
- let color = textureSample(cubeTexture, cubeSampler, normalize(input.dir));
2141
- return vec4<f32>(color.rgb, 1.0);
2199
+ @fragment fn fs(i: V) -> @location(0) vec4<f32> {
2200
+ return vec4(textureSample(cubeTexture, cubeSampler, normalize(i.dir)).rgb, 1.);
2201
+ }`
2202
+ );
2203
+ const EQUIRECT_WGSL = (
2204
+ /* wgsl */
2205
+ `
2206
+ struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32> };
2207
+ @group(0) @binding(0) var<uniform> u: Uniforms;
2208
+ @group(0) @binding(1) var envSampler: sampler;
2209
+ @group(0) @binding(2) var envTexture: texture_2d<f32>;
2210
+ struct V { @builtin(position) pos: vec4<f32>, @location(0) dir: vec3<f32> };
2211
+ const PI: f32 = 3.14159265358979;
2212
+ @vertex fn vs(@builtin(vertex_index) vi: u32) -> V {
2213
+ let ps = array<vec2<f32>,3>(vec2(-1.,-1.),vec2(3.,-1.),vec2(-1.,3.));
2214
+ var o: V; let p = ps[vi]; o.pos = vec4(p, 0., 1.);
2215
+ let e = vec3(p.x*u.col0.w, p.y*u.col1.w, -1.);
2216
+ o.dir = vec3(dot(u.col0.xyz,e), dot(u.col1.xyz,e), dot(u.col2.xyz,e));
2217
+ return o;
2142
2218
  }
2143
- `
2219
+ @fragment fn fs(i: V) -> @location(0) vec4<f32> {
2220
+ 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;
2223
+ let mapped = c / (c + vec3(1.));
2224
+ let gamma = pow(mapped, vec3(1./2.2));
2225
+ return vec4(gamma, 1.);
2226
+ }`
2144
2227
  );
2145
2228
  class SkyboxRenderer {
2146
2229
  constructor(device, format, depthFormat) {
2147
2230
  __publicField(this, "device");
2148
- __publicField(this, "pipeline");
2149
2231
  __publicField(this, "uniformBuffer");
2150
2232
  __publicField(this, "sampler");
2151
- __publicField(this, "bindGroupLayout");
2152
2233
  __publicField(this, "uniformData", new Float32Array(12));
2153
- __publicField(this, "cubeTexture", null);
2154
- __publicField(this, "cubeBindGroup", null);
2234
+ __publicField(this, "cubePipeline");
2235
+ __publicField(this, "cubeBindGroupLayout");
2236
+ __publicField(this, "equirectPipeline");
2237
+ __publicField(this, "equirectBindGroupLayout");
2238
+ __publicField(this, "texture", null);
2239
+ __publicField(this, "bindGroup", null);
2155
2240
  __publicField(this, "frameReady", false);
2241
+ __publicField(this, "mode", "none");
2156
2242
  this.device = device;
2157
- const shaderModule = device.createShaderModule({ code: WGSL });
2158
- this.bindGroupLayout = device.createBindGroupLayout({
2243
+ const depthStencil = {
2244
+ format: depthFormat,
2245
+ depthWriteEnabled: false,
2246
+ depthCompare: "always"
2247
+ };
2248
+ this.cubeBindGroupLayout = device.createBindGroupLayout({
2159
2249
  entries: [
2160
- {
2161
- binding: 0,
2162
- visibility: GPUShaderStage.VERTEX,
2163
- buffer: { type: "uniform" }
2164
- },
2165
- {
2166
- binding: 1,
2167
- visibility: GPUShaderStage.FRAGMENT,
2168
- sampler: { type: "filtering" }
2169
- },
2170
- {
2171
- binding: 2,
2172
- visibility: GPUShaderStage.FRAGMENT,
2173
- texture: { sampleType: "float", viewDimension: "cube" }
2174
- }
2250
+ { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
2251
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
2252
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "cube" } }
2175
2253
  ]
2176
2254
  });
2177
- this.pipeline = device.createRenderPipeline({
2178
- layout: device.createPipelineLayout({
2179
- bindGroupLayouts: [this.bindGroupLayout]
2180
- }),
2181
- vertex: { module: shaderModule, entryPoint: "vs" },
2182
- fragment: {
2183
- module: shaderModule,
2184
- entryPoint: "fs",
2185
- targets: [{ format }]
2186
- },
2255
+ this.cubePipeline = device.createRenderPipeline({
2256
+ layout: device.createPipelineLayout({ bindGroupLayouts: [this.cubeBindGroupLayout] }),
2257
+ vertex: { module: device.createShaderModule({ code: CUBE_WGSL }), entryPoint: "vs" },
2258
+ fragment: { module: device.createShaderModule({ code: CUBE_WGSL }), entryPoint: "fs", targets: [{ format }] },
2187
2259
  primitive: { topology: "triangle-list", cullMode: "none" },
2188
- depthStencil: {
2189
- format: depthFormat,
2190
- depthWriteEnabled: false,
2191
- depthCompare: "always"
2192
- }
2260
+ depthStencil
2261
+ });
2262
+ this.equirectBindGroupLayout = device.createBindGroupLayout({
2263
+ entries: [
2264
+ { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
2265
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
2266
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "2d" } }
2267
+ ]
2268
+ });
2269
+ this.equirectPipeline = device.createRenderPipeline({
2270
+ layout: device.createPipelineLayout({ bindGroupLayouts: [this.equirectBindGroupLayout] }),
2271
+ vertex: { module: device.createShaderModule({ code: EQUIRECT_WGSL }), entryPoint: "vs" },
2272
+ fragment: { module: device.createShaderModule({ code: EQUIRECT_WGSL }), entryPoint: "fs", targets: [{ format }] },
2273
+ primitive: { topology: "triangle-list", cullMode: "none" },
2274
+ depthStencil
2193
2275
  });
2194
2276
  this.uniformBuffer = device.createBuffer({
2195
2277
  size: 48,
2196
2278
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
2197
2279
  });
2198
- this.sampler = device.createSampler({
2199
- magFilter: "linear",
2200
- minFilter: "linear"
2201
- });
2280
+ this.sampler = device.createSampler({ magFilter: "linear", minFilter: "linear" });
2202
2281
  }
2203
2282
  get isActive() {
2204
- return this.cubeTexture !== null && this.cubeBindGroup !== null;
2283
+ return this.mode !== "none" && this.texture !== null && this.bindGroup !== null;
2205
2284
  }
2285
+ // ---- cubemap ----
2206
2286
  async loadCubemap(faceUrls) {
2207
2287
  this.clear();
2208
2288
  const bitmaps = await Promise.all(
2209
2289
  faceUrls.map(async (url) => {
2210
2290
  const res = await fetch(url);
2211
- if (!res.ok)
2212
- throw new Error(
2213
- `SkyboxRenderer: failed to fetch ${url} (${res.status})`
2214
- );
2291
+ if (!res.ok) throw new Error(`SkyboxRenderer: failed to fetch ${url} (${res.status})`);
2215
2292
  return createImageBitmap(await res.blob());
2216
2293
  })
2217
2294
  );
@@ -2220,9 +2297,7 @@ class SkyboxRenderer {
2220
2297
  for (let i = 1; i < 6; i++) {
2221
2298
  if (bitmaps[i].width !== width || bitmaps[i].height !== height) {
2222
2299
  bitmaps.forEach((b) => b.close());
2223
- throw new Error(
2224
- "SkyboxRenderer: all cubemap faces must share the same dimensions"
2225
- );
2300
+ throw new Error("SkyboxRenderer: all cubemap faces must share the same dimensions");
2226
2301
  }
2227
2302
  }
2228
2303
  const tex = this.device.createTexture({
@@ -2239,16 +2314,44 @@ class SkyboxRenderer {
2239
2314
  );
2240
2315
  }
2241
2316
  bitmaps.forEach((b) => b.close());
2242
- this.cubeBindGroup = this.device.createBindGroup({
2243
- layout: this.bindGroupLayout,
2317
+ this.bindGroup = this.device.createBindGroup({
2318
+ layout: this.cubeBindGroupLayout,
2244
2319
  entries: [
2245
2320
  { binding: 0, resource: { buffer: this.uniformBuffer } },
2246
2321
  { binding: 1, resource: this.sampler },
2247
2322
  { binding: 2, resource: tex.createView({ dimension: "cube" }) }
2248
2323
  ]
2249
2324
  });
2250
- this.cubeTexture = tex;
2325
+ this.texture = tex;
2326
+ this.mode = "cubemap";
2251
2327
  }
2328
+ // ---- equirectangular HDR ----
2329
+ async loadEquirectangular(url) {
2330
+ this.clear();
2331
+ const hdr = await loadHdr(url);
2332
+ const tex = this.device.createTexture({
2333
+ size: [hdr.width, hdr.height],
2334
+ format: "rgba32float",
2335
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
2336
+ });
2337
+ this.device.queue.writeTexture(
2338
+ { texture: tex },
2339
+ hdr.data.buffer,
2340
+ { bytesPerRow: hdr.width * 16, rowsPerImage: hdr.height },
2341
+ { width: hdr.width, height: hdr.height }
2342
+ );
2343
+ this.bindGroup = this.device.createBindGroup({
2344
+ layout: this.equirectBindGroupLayout,
2345
+ entries: [
2346
+ { binding: 0, resource: { buffer: this.uniformBuffer } },
2347
+ { binding: 1, resource: this.sampler },
2348
+ { binding: 2, resource: tex.createView() }
2349
+ ]
2350
+ });
2351
+ this.texture = tex;
2352
+ this.mode = "equirect";
2353
+ }
2354
+ // ---- common ----
2252
2355
  prepareFrame(viewMatrix, projectionMatrix) {
2253
2356
  if (!this.isActive) {
2254
2357
  this.frameReady = false;
@@ -2271,18 +2374,20 @@ class SkyboxRenderer {
2271
2374
  this.frameReady = true;
2272
2375
  }
2273
2376
  draw(pass) {
2274
- if (!this.frameReady || !this.cubeBindGroup) return;
2275
- pass.setPipeline(this.pipeline);
2276
- pass.setBindGroup(0, this.cubeBindGroup);
2377
+ if (!this.frameReady || !this.bindGroup) return;
2378
+ const pipeline = this.mode === "cubemap" ? this.cubePipeline : this.equirectPipeline;
2379
+ pass.setPipeline(pipeline);
2380
+ pass.setBindGroup(0, this.bindGroup);
2277
2381
  pass.draw(3);
2278
2382
  }
2279
2383
  clear() {
2280
- if (this.cubeTexture) {
2281
- this.cubeTexture.destroy();
2282
- this.cubeTexture = null;
2384
+ if (this.texture) {
2385
+ this.texture.destroy();
2386
+ this.texture = null;
2283
2387
  }
2284
- this.cubeBindGroup = null;
2388
+ this.bindGroup = null;
2285
2389
  this.frameReady = false;
2390
+ this.mode = "none";
2286
2391
  }
2287
2392
  destroy() {
2288
2393
  this.clear();
@@ -19088,6 +19193,16 @@ class App {
19088
19193
  faceUrls.negZ
19089
19194
  ]);
19090
19195
  }
19196
+ async setHdrBackground(url) {
19197
+ if (!this.skyboxRenderer) {
19198
+ this.skyboxRenderer = new SkyboxRenderer(
19199
+ this.renderer.device,
19200
+ this.renderer.format,
19201
+ "depth24plus"
19202
+ );
19203
+ }
19204
+ await this.skyboxRenderer.loadEquirectangular(url);
19205
+ }
19091
19206
  clearSkybox() {
19092
19207
  var _a2;
19093
19208
  (_a2 = this.skyboxRenderer) == null ? void 0 : _a2.clear();
@@ -19580,6 +19695,7 @@ export {
19580
19695
  getRecommendedDPR,
19581
19696
  isMobileDevice,
19582
19697
  isWebGPUSupported,
19698
+ loadHdr,
19583
19699
  loadPLY,
19584
19700
  loadPLYMobile,
19585
19701
  loadSOG,