@d5techs/3dgs-lib 1.4.37 → 1.4.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/dist/3dgs-lib.cjs CHANGED
@@ -630,9 +630,10 @@ const _OrbitControls = class _OrbitControls {
630
630
  // 移动端触摸灵敏度
631
631
  __publicField(this, "touchZoomSpeed", 0.01);
632
632
  __publicField(this, "touchPanSpeed", 3e-3);
633
- // 缩放步长上限:用 min(distance, zoomDistanceCap) 计算每步的绝对位移,
634
- // 使远距离时缩放速度由模型尺寸而非 target 位置决定。frameModel 会自动设置为 radius。
633
+ // 缩放步长上限:取 min(distance, cameraToCenterDist, zoomDistanceCap)
634
+ // 使缩放速度始终与相机到模型的真实距离匹配,而非 target 位置。
635
635
  __publicField(this, "zoomDistanceCap", Infinity);
636
+ __publicField(this, "_modelCenter", [0, 0, 0]);
636
637
  // 阻尼:每帧应用全量 delta,然后 delta *= (1 - dampingFactor) 实现惯性
637
638
  __publicField(this, "enableDamping", true);
638
639
  __publicField(this, "dampingFactor", 0.1);
@@ -797,7 +798,7 @@ const _OrbitControls = class _OrbitControls {
797
798
  this.deltaDistance = Math.sign(this.deltaDistance) * Math.min(Math.abs(this.deltaDistance), maxAccum);
798
799
  } else {
799
800
  const factor = Math.exp(zoomDelta);
800
- const effectiveDist = Math.min(this.distance, this.zoomDistanceCap);
801
+ const effectiveDist = this.getEffectiveZoomDist();
801
802
  this.distance -= effectiveDist * (1 - factor);
802
803
  this.distance = Math.max(
803
804
  this.minDistance,
@@ -948,7 +949,7 @@ const _OrbitControls = class _OrbitControls {
948
949
  this.deltaDistance += -Math.log(ratio) * this.dampingFactor;
949
950
  } else {
950
951
  const factor = 1 / ratio;
951
- const effectiveDist = Math.min(this.distance, this.zoomDistanceCap);
952
+ const effectiveDist = this.getEffectiveZoomDist();
952
953
  this.distance -= effectiveDist * (1 - factor);
953
954
  this.distance = Math.max(
954
955
  this.minDistance,
@@ -1012,6 +1013,17 @@ const _OrbitControls = class _OrbitControls {
1012
1013
  this.camera.position[2] = this.camera.target[2] + this.distance * sinPhi * cosTheta;
1013
1014
  this.camera.updateMatrix();
1014
1015
  }
1016
+ /**
1017
+ * 缩放有效距离:取 distance、相机到模型中心距离、zoomDistanceCap 三者最小值。
1018
+ * 保证缩放步长始终与相机到模型的真实接近程度匹配。
1019
+ */
1020
+ getEffectiveZoomDist() {
1021
+ const cp = this.camera.position;
1022
+ const mc = this._modelCenter;
1023
+ const dx = cp[0] - mc[0], dy = cp[1] - mc[1], dz = cp[2] - mc[2];
1024
+ const camToCenter = Math.sqrt(dx * dx + dy * dy + dz * dz);
1025
+ return Math.min(this.distance, camToCenter, this.zoomDistanceCap);
1026
+ }
1015
1027
  /**
1016
1028
  * 每帧调用:应用阻尼衰减并更新相机
1017
1029
  * 在渲染循环中调用此方法以获得平滑惯性效果
@@ -1030,7 +1042,7 @@ const _OrbitControls = class _OrbitControls {
1030
1042
  if (Math.abs(this.deltaPhi) < EPS) this.deltaPhi = 0;
1031
1043
  if (Math.abs(this.deltaDistance) > EPS) {
1032
1044
  const factor = Math.exp(this.deltaDistance);
1033
- const effectiveDist = Math.min(this.distance, this.zoomDistanceCap);
1045
+ const effectiveDist = this.getEffectiveZoomDist();
1034
1046
  this.distance -= effectiveDist * (1 - factor);
1035
1047
  this.distance = Math.max(
1036
1048
  this.minDistance,
@@ -1132,6 +1144,7 @@ const _OrbitControls = class _OrbitControls {
1132
1144
  frameModel(center, radius, animate = true) {
1133
1145
  this.clearVelocity();
1134
1146
  this.zoomDistanceCap = radius;
1147
+ this._modelCenter = [center[0], center[1], center[2]];
1135
1148
  const fovRad = this.camera.fov;
1136
1149
  const halfFov = fovRad / 2;
1137
1150
  const marginFactor = 1.5;
@@ -2065,6 +2078,228 @@ class SceneAidsRenderer {
2065
2078
  this.axesBindGroup = null;
2066
2079
  }
2067
2080
  }
2081
+ const WGSL = (
2082
+ /* wgsl */
2083
+ `
2084
+ struct Uniforms {
2085
+ invViewProjNoTrans: mat4x4<f32>,
2086
+ };
2087
+
2088
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
2089
+ @group(0) @binding(1) var cubeSampler: sampler;
2090
+ @group(0) @binding(2) var cubeTexture: texture_cube<f32>;
2091
+
2092
+ struct VSOut {
2093
+ @builtin(position) position: vec4<f32>,
2094
+ @location(0) dir: vec3<f32>,
2095
+ };
2096
+
2097
+ @vertex
2098
+ fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
2099
+ let positions = array<vec2<f32>, 3>(
2100
+ vec2<f32>(-1.0, -1.0),
2101
+ vec2<f32>(3.0, -1.0),
2102
+ vec2<f32>(-1.0, 3.0),
2103
+ );
2104
+ var out: VSOut;
2105
+ let p = positions[vi];
2106
+ out.position = vec4<f32>(p, 0.0, 1.0);
2107
+ let unprojected = uniforms.invViewProjNoTrans * vec4<f32>(p, 1.0, 1.0);
2108
+ out.dir = unprojected.xyz / unprojected.w;
2109
+ return out;
2110
+ }
2111
+
2112
+ @fragment
2113
+ fn fs(input: VSOut) -> @location(0) vec4<f32> {
2114
+ return textureSample(cubeTexture, cubeSampler, normalize(input.dir));
2115
+ }
2116
+ `
2117
+ );
2118
+ class SkyboxRenderer {
2119
+ constructor(device, format, depthFormat) {
2120
+ __publicField(this, "device");
2121
+ __publicField(this, "format");
2122
+ __publicField(this, "depthFormat");
2123
+ __publicField(this, "shaderModule");
2124
+ __publicField(this, "bindGroupLayout");
2125
+ __publicField(this, "pipelineLayout");
2126
+ __publicField(this, "pipeline");
2127
+ __publicField(this, "uniformBuffer");
2128
+ __publicField(this, "sampler");
2129
+ __publicField(this, "scratchViewNoTrans", new Float32Array(16));
2130
+ __publicField(this, "scratchVp", new Float32Array(16));
2131
+ __publicField(this, "scratchInv", new Float32Array(16));
2132
+ __publicField(this, "cubeTexture", null);
2133
+ __publicField(this, "cubeBindGroup", null);
2134
+ this.device = device;
2135
+ this.format = format;
2136
+ this.depthFormat = depthFormat;
2137
+ this.shaderModule = device.createShaderModule({ code: WGSL });
2138
+ this.bindGroupLayout = device.createBindGroupLayout({
2139
+ entries: [
2140
+ {
2141
+ binding: 0,
2142
+ visibility: GPUShaderStage.VERTEX,
2143
+ buffer: { type: "uniform", minBindingSize: 64 }
2144
+ },
2145
+ {
2146
+ binding: 1,
2147
+ visibility: GPUShaderStage.FRAGMENT,
2148
+ sampler: { type: "filtering" }
2149
+ },
2150
+ {
2151
+ binding: 2,
2152
+ visibility: GPUShaderStage.FRAGMENT,
2153
+ texture: { sampleType: "float", viewDimension: "cube" }
2154
+ }
2155
+ ]
2156
+ });
2157
+ this.pipelineLayout = device.createPipelineLayout({
2158
+ bindGroupLayouts: [this.bindGroupLayout]
2159
+ });
2160
+ this.pipeline = device.createRenderPipeline({
2161
+ layout: this.pipelineLayout,
2162
+ vertex: {
2163
+ module: this.shaderModule,
2164
+ entryPoint: "vs"
2165
+ },
2166
+ fragment: {
2167
+ module: this.shaderModule,
2168
+ entryPoint: "fs",
2169
+ targets: [{ format: this.format }]
2170
+ },
2171
+ primitive: {
2172
+ topology: "triangle-list",
2173
+ cullMode: "none"
2174
+ },
2175
+ depthStencil: {
2176
+ format: this.depthFormat,
2177
+ depthWriteEnabled: false,
2178
+ depthCompare: "always"
2179
+ }
2180
+ });
2181
+ this.uniformBuffer = device.createBuffer({
2182
+ size: 64,
2183
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
2184
+ });
2185
+ this.sampler = device.createSampler({
2186
+ magFilter: "linear",
2187
+ minFilter: "linear"
2188
+ });
2189
+ }
2190
+ get isActive() {
2191
+ return this.cubeTexture !== null && this.cubeBindGroup !== null;
2192
+ }
2193
+ async loadCubemap(faceUrls) {
2194
+ this.clear();
2195
+ const bitmaps = await Promise.all(
2196
+ faceUrls.map(async (url) => {
2197
+ const res = await fetch(url);
2198
+ if (!res.ok) {
2199
+ throw new Error(`SkyboxRenderer: failed to fetch ${url} (${res.status})`);
2200
+ }
2201
+ const blob = await res.blob();
2202
+ return createImageBitmap(blob);
2203
+ })
2204
+ );
2205
+ const width = bitmaps[0].width;
2206
+ const height = bitmaps[0].height;
2207
+ for (let i = 1; i < 6; i++) {
2208
+ if (bitmaps[i].width !== width || bitmaps[i].height !== height) {
2209
+ bitmaps.forEach((b) => b.close());
2210
+ throw new Error("SkyboxRenderer: all cubemap faces must share the same dimensions");
2211
+ }
2212
+ }
2213
+ const tex = this.device.createTexture({
2214
+ dimension: "2d",
2215
+ size: [width, height, 6],
2216
+ format: "rgba8unorm",
2217
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
2218
+ });
2219
+ for (let layer = 0; layer < 6; layer++) {
2220
+ this.device.queue.copyExternalImageToTexture(
2221
+ { source: bitmaps[layer] },
2222
+ { texture: tex, mipLevel: 0, origin: { x: 0, y: 0, z: layer } },
2223
+ { width, height, depthOrArrayLayers: 1 }
2224
+ );
2225
+ }
2226
+ bitmaps.forEach((b) => b.close());
2227
+ const cubeView = tex.createView({ dimension: "cube" });
2228
+ const bindGroup = this.device.createBindGroup({
2229
+ layout: this.bindGroupLayout,
2230
+ entries: [
2231
+ { binding: 0, resource: { buffer: this.uniformBuffer } },
2232
+ { binding: 1, resource: this.sampler },
2233
+ { binding: 2, resource: cubeView }
2234
+ ]
2235
+ });
2236
+ this.cubeTexture = tex;
2237
+ this.cubeBindGroup = bindGroup;
2238
+ }
2239
+ render(pass, viewMatrix, projectionMatrix) {
2240
+ if (!this.isActive || !this.cubeBindGroup) {
2241
+ return;
2242
+ }
2243
+ this.scratchViewNoTrans.set(viewMatrix);
2244
+ this.scratchViewNoTrans[12] = 0;
2245
+ this.scratchViewNoTrans[13] = 0;
2246
+ this.scratchViewNoTrans[14] = 0;
2247
+ SkyboxRenderer.multiplyMat4(this.scratchVp, projectionMatrix, this.scratchViewNoTrans);
2248
+ if (!SkyboxRenderer.invertMat4(this.scratchInv, this.scratchVp)) {
2249
+ return;
2250
+ }
2251
+ this.device.queue.writeBuffer(this.uniformBuffer, 0, this.scratchInv);
2252
+ pass.setPipeline(this.pipeline);
2253
+ pass.setBindGroup(0, this.cubeBindGroup);
2254
+ pass.draw(3);
2255
+ }
2256
+ clear() {
2257
+ if (this.cubeTexture) {
2258
+ this.cubeTexture.destroy();
2259
+ this.cubeTexture = null;
2260
+ }
2261
+ this.cubeBindGroup = null;
2262
+ }
2263
+ destroy() {
2264
+ this.clear();
2265
+ this.uniformBuffer.destroy();
2266
+ }
2267
+ static invertMat4(out, e) {
2268
+ const inv = new Float32Array(16);
2269
+ inv[0] = e[5] * e[10] * e[15] - e[5] * e[11] * e[14] - e[9] * e[6] * e[15] + e[9] * e[7] * e[14] + e[13] * e[6] * e[11] - e[13] * e[7] * e[10];
2270
+ inv[4] = -e[4] * e[10] * e[15] + e[4] * e[11] * e[14] + e[8] * e[6] * e[15] - e[8] * e[7] * e[14] - e[12] * e[6] * e[11] + e[12] * e[7] * e[10];
2271
+ inv[8] = e[4] * e[9] * e[15] - e[4] * e[11] * e[13] - e[8] * e[5] * e[15] + e[8] * e[7] * e[13] + e[12] * e[5] * e[11] - e[12] * e[7] * e[9];
2272
+ inv[12] = -e[4] * e[9] * e[14] + e[4] * e[10] * e[13] + e[8] * e[5] * e[14] - e[8] * e[6] * e[13] - e[12] * e[5] * e[10] + e[12] * e[6] * e[9];
2273
+ inv[1] = -e[1] * e[10] * e[15] + e[1] * e[11] * e[14] + e[9] * e[2] * e[15] - e[9] * e[3] * e[14] - e[13] * e[2] * e[11] + e[13] * e[3] * e[10];
2274
+ inv[5] = e[0] * e[10] * e[15] - e[0] * e[11] * e[14] - e[8] * e[2] * e[15] + e[8] * e[3] * e[14] + e[12] * e[2] * e[11] - e[12] * e[3] * e[10];
2275
+ inv[9] = -e[0] * e[9] * e[15] + e[0] * e[11] * e[13] + e[8] * e[1] * e[15] - e[8] * e[3] * e[13] - e[12] * e[1] * e[11] + e[12] * e[3] * e[9];
2276
+ inv[13] = e[0] * e[9] * e[14] - e[0] * e[10] * e[13] - e[8] * e[1] * e[14] + e[8] * e[2] * e[13] + e[12] * e[1] * e[10] - e[12] * e[2] * e[9];
2277
+ inv[2] = e[1] * e[6] * e[15] - e[1] * e[7] * e[14] - e[5] * e[2] * e[15] + e[5] * e[3] * e[14] + e[13] * e[2] * e[7] - e[13] * e[3] * e[6];
2278
+ inv[6] = -e[0] * e[6] * e[15] + e[0] * e[7] * e[14] + e[4] * e[2] * e[15] - e[4] * e[3] * e[14] - e[12] * e[2] * e[7] + e[12] * e[3] * e[6];
2279
+ inv[10] = e[0] * e[5] * e[15] - e[0] * e[7] * e[13] - e[4] * e[1] * e[15] + e[4] * e[3] * e[13] + e[12] * e[1] * e[7] - e[12] * e[3] * e[5];
2280
+ inv[14] = -e[0] * e[5] * e[14] + e[0] * e[6] * e[13] + e[4] * e[1] * e[14] - e[4] * e[2] * e[13] - e[12] * e[1] * e[6] + e[12] * e[2] * e[5];
2281
+ inv[3] = -e[1] * e[6] * e[11] + e[1] * e[7] * e[10] + e[5] * e[2] * e[11] - e[5] * e[3] * e[10] - e[9] * e[2] * e[7] + e[9] * e[3] * e[6];
2282
+ inv[7] = e[0] * e[6] * e[11] - e[0] * e[7] * e[10] - e[4] * e[2] * e[11] + e[4] * e[3] * e[10] + e[8] * e[2] * e[7] - e[8] * e[3] * e[6];
2283
+ inv[11] = -e[0] * e[5] * e[11] + e[0] * e[7] * e[9] + e[4] * e[1] * e[11] - e[4] * e[3] * e[9] - e[8] * e[1] * e[7] + e[8] * e[3] * e[5];
2284
+ inv[15] = e[0] * e[5] * e[10] - e[0] * e[6] * e[9] - e[4] * e[1] * e[10] + e[4] * e[2] * e[9] + e[8] * e[1] * e[6] - e[8] * e[2] * e[5];
2285
+ const det = e[0] * inv[0] + e[1] * inv[4] + e[2] * inv[8] + e[3] * inv[12];
2286
+ if (Math.abs(det) < 1e-10) {
2287
+ return false;
2288
+ }
2289
+ const invDet = 1 / det;
2290
+ for (let i = 0; i < 16; i++) {
2291
+ out[i] = inv[i] * invDet;
2292
+ }
2293
+ return true;
2294
+ }
2295
+ static multiplyMat4(out, a, b) {
2296
+ for (let c = 0; c < 4; c++) {
2297
+ for (let r = 0; r < 4; r++) {
2298
+ out[c * 4 + r] = a[0 * 4 + r] * b[c * 4 + 0] + a[1 * 4 + r] * b[c * 4 + 1] + a[2 * 4 + r] * b[c * 4 + 2] + a[3 * 4 + r] * b[c * 4 + 3];
2299
+ }
2300
+ }
2301
+ }
2302
+ }
2068
2303
  class Mesh {
2069
2304
  constructor(vertexBuffer, vertexCount, indexBuffer = null, indexCount = 0, boundingBox) {
2070
2305
  __publicField(this, "vertexBuffer");
@@ -18231,6 +18466,7 @@ class App {
18231
18466
  __publicField(this, "gizmoManager");
18232
18467
  __publicField(this, "hotspotManager");
18233
18468
  __publicField(this, "sceneAids");
18469
+ __publicField(this, "skyboxRenderer", null);
18234
18470
  __publicField(this, "isRunning", false);
18235
18471
  __publicField(this, "animationId", 0);
18236
18472
  // 是否使用移动端渲染器
@@ -18670,11 +18906,15 @@ class App {
18670
18906
  }
18671
18907
  }
18672
18908
  render() {
18909
+ var _a2;
18673
18910
  this.camera.setAspect(this.renderer.getAspectRatio());
18674
18911
  this.controls.update();
18675
18912
  this.updateAdaptivePerformance();
18676
18913
  this.hotspotManager.updateBillboards();
18677
18914
  const pass = this.renderer.beginFrame();
18915
+ if ((_a2 = this.skyboxRenderer) == null ? void 0 : _a2.isActive) {
18916
+ this.skyboxRenderer.render(pass, this.camera.viewMatrix, this.camera.projectionMatrix);
18917
+ }
18678
18918
  const gsRenderer = this.sceneManager.getGSRenderer();
18679
18919
  if (gsRenderer) {
18680
18920
  gsRenderer.render(pass);
@@ -18834,6 +19074,34 @@ class App {
18834
19074
  return false;
18835
19075
  }
18836
19076
  // ============================================
19077
+ // Skybox
19078
+ // ============================================
19079
+ async setSkybox(faceUrls) {
19080
+ if (!this.skyboxRenderer) {
19081
+ this.skyboxRenderer = new SkyboxRenderer(
19082
+ this.renderer.device,
19083
+ this.renderer.format,
19084
+ "depth24plus"
19085
+ );
19086
+ }
19087
+ await this.skyboxRenderer.loadCubemap([
19088
+ faceUrls.posX,
19089
+ faceUrls.negX,
19090
+ faceUrls.posY,
19091
+ faceUrls.negY,
19092
+ faceUrls.posZ,
19093
+ faceUrls.negZ
19094
+ ]);
19095
+ }
19096
+ clearSkybox() {
19097
+ var _a2;
19098
+ (_a2 = this.skyboxRenderer) == null ? void 0 : _a2.clear();
19099
+ }
19100
+ isSkyboxActive() {
19101
+ var _a2;
19102
+ return ((_a2 = this.skyboxRenderer) == null ? void 0 : _a2.isActive) ?? false;
19103
+ }
19104
+ // ============================================
18837
19105
  // Gizmo(委托给 GizmoManager)
18838
19106
  // ============================================
18839
19107
  getTransformGizmo() {
@@ -19293,6 +19561,7 @@ exports.Renderer = Renderer;
19293
19561
  exports.SHMode = SHMode;
19294
19562
  exports.SceneAidsRenderer = SceneAidsRenderer;
19295
19563
  exports.SceneManager = SceneManager;
19564
+ exports.SkyboxRenderer = SkyboxRenderer;
19296
19565
  exports.SplatBoundingBoxProvider = SplatBoundingBoxProvider;
19297
19566
  exports.SplatEditor = SplatEditor;
19298
19567
  exports.SplatState = SplatState;