@d5techs/3dgs-lib 1.4.45 → 1.4.46

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
@@ -1013,6 +1013,12 @@ const _OrbitControls = class _OrbitControls {
1013
1013
  this.camera.position[2] = this.camera.target[2] + this.distance * sinPhi * cosTheta;
1014
1014
  this.camera.updateMatrix();
1015
1015
  }
1016
+ get modelCenter() {
1017
+ return [this._modelCenter[0], this._modelCenter[1], this._modelCenter[2]];
1018
+ }
1019
+ get modelRadius() {
1020
+ return this.zoomDistanceCap;
1021
+ }
1016
1022
  /**
1017
1023
  * 旋转速度缩放:当 target 远离模型时降低旋转灵敏度,
1018
1024
  * 使模型在屏幕上的视觉运动速度保持一致。
@@ -2097,6 +2103,7 @@ struct Uniforms {
2097
2103
  col0: vec4<f32>,
2098
2104
  col1: vec4<f32>,
2099
2105
  col2: vec4<f32>,
2106
+ extra: vec4<f32>,
2100
2107
  };
2101
2108
 
2102
2109
  @group(0) @binding(0) var<uniform> u: Uniforms;
@@ -2108,6 +2115,12 @@ struct VSOut {
2108
2115
  @location(0) dir: vec3<f32>,
2109
2116
  };
2110
2117
 
2118
+ fn rotateX(d: vec3<f32>, angle: f32) -> vec3<f32> {
2119
+ let c = cos(angle);
2120
+ let s = sin(angle);
2121
+ return vec3<f32>(d.x, c * d.y - s * d.z, s * d.y + c * d.z);
2122
+ }
2123
+
2111
2124
  @vertex
2112
2125
  fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
2113
2126
  let positions = array<vec2<f32>, 3>(
@@ -2119,15 +2132,17 @@ fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
2119
2132
  let p = positions[vi];
2120
2133
  out.position = vec4<f32>(p, 0.0, 1.0);
2121
2134
 
2122
- // col0.w = 1/proj[0] (aspect*tan(fov/2)), col1.w = 1/proj[5] (tan(fov/2))
2123
2135
  let eyeDir = vec3<f32>(p.x * u.col0.w, p.y * u.col1.w, -1.0);
2124
2136
 
2125
- // col0..2.xyz = rows of inverse view rotation (= transpose of view rotation)
2126
- out.dir = vec3<f32>(
2137
+ var worldDir = vec3<f32>(
2127
2138
  dot(u.col0.xyz, eyeDir),
2128
2139
  dot(u.col1.xyz, eyeDir),
2129
2140
  dot(u.col2.xyz, eyeDir),
2130
2141
  );
2142
+
2143
+ worldDir = rotateX(worldDir, u.extra.x);
2144
+
2145
+ out.dir = worldDir;
2131
2146
  return out;
2132
2147
  }
2133
2148
 
@@ -2145,31 +2160,57 @@ class SkyboxRenderer {
2145
2160
  __publicField(this, "uniformBuffer");
2146
2161
  __publicField(this, "sampler");
2147
2162
  __publicField(this, "bindGroupLayout");
2148
- __publicField(this, "uniformData", new Float32Array(12));
2163
+ __publicField(this, "uniformData", new Float32Array(16));
2149
2164
  __publicField(this, "cubeTexture", null);
2150
2165
  __publicField(this, "cubeBindGroup", null);
2151
2166
  __publicField(this, "frameReady", false);
2167
+ __publicField(this, "alignToGround", true);
2152
2168
  this.device = device;
2153
2169
  const shaderModule = device.createShaderModule({ code: WGSL });
2154
2170
  this.bindGroupLayout = device.createBindGroupLayout({
2155
2171
  entries: [
2156
- { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
2157
- { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
2158
- { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "cube" } }
2172
+ {
2173
+ binding: 0,
2174
+ visibility: GPUShaderStage.VERTEX,
2175
+ buffer: { type: "uniform" }
2176
+ },
2177
+ {
2178
+ binding: 1,
2179
+ visibility: GPUShaderStage.FRAGMENT,
2180
+ sampler: { type: "filtering" }
2181
+ },
2182
+ {
2183
+ binding: 2,
2184
+ visibility: GPUShaderStage.FRAGMENT,
2185
+ texture: { sampleType: "float", viewDimension: "cube" }
2186
+ }
2159
2187
  ]
2160
2188
  });
2161
2189
  this.pipeline = device.createRenderPipeline({
2162
- layout: device.createPipelineLayout({ bindGroupLayouts: [this.bindGroupLayout] }),
2190
+ layout: device.createPipelineLayout({
2191
+ bindGroupLayouts: [this.bindGroupLayout]
2192
+ }),
2163
2193
  vertex: { module: shaderModule, entryPoint: "vs" },
2164
- fragment: { module: shaderModule, entryPoint: "fs", targets: [{ format }] },
2194
+ fragment: {
2195
+ module: shaderModule,
2196
+ entryPoint: "fs",
2197
+ targets: [{ format }]
2198
+ },
2165
2199
  primitive: { topology: "triangle-list", cullMode: "none" },
2166
- depthStencil: { format: depthFormat, depthWriteEnabled: false, depthCompare: "always" }
2200
+ depthStencil: {
2201
+ format: depthFormat,
2202
+ depthWriteEnabled: false,
2203
+ depthCompare: "always"
2204
+ }
2167
2205
  });
2168
2206
  this.uniformBuffer = device.createBuffer({
2169
- size: 48,
2207
+ size: 64,
2170
2208
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
2171
2209
  });
2172
- this.sampler = device.createSampler({ magFilter: "linear", minFilter: "linear" });
2210
+ this.sampler = device.createSampler({
2211
+ magFilter: "linear",
2212
+ minFilter: "linear"
2213
+ });
2173
2214
  }
2174
2215
  get isActive() {
2175
2216
  return this.cubeTexture !== null && this.cubeBindGroup !== null;
@@ -2179,7 +2220,10 @@ class SkyboxRenderer {
2179
2220
  const bitmaps = await Promise.all(
2180
2221
  faceUrls.map(async (url) => {
2181
2222
  const res = await fetch(url);
2182
- if (!res.ok) throw new Error(`SkyboxRenderer: failed to fetch ${url} (${res.status})`);
2223
+ if (!res.ok)
2224
+ throw new Error(
2225
+ `SkyboxRenderer: failed to fetch ${url} (${res.status})`
2226
+ );
2183
2227
  return createImageBitmap(await res.blob());
2184
2228
  })
2185
2229
  );
@@ -2188,7 +2232,9 @@ class SkyboxRenderer {
2188
2232
  for (let i = 1; i < 6; i++) {
2189
2233
  if (bitmaps[i].width !== width || bitmaps[i].height !== height) {
2190
2234
  bitmaps.forEach((b) => b.close());
2191
- throw new Error("SkyboxRenderer: all cubemap faces must share the same dimensions");
2235
+ throw new Error(
2236
+ "SkyboxRenderer: all cubemap faces must share the same dimensions"
2237
+ );
2192
2238
  }
2193
2239
  }
2194
2240
  const tex = this.device.createTexture({
@@ -2216,10 +2262,11 @@ class SkyboxRenderer {
2216
2262
  this.cubeTexture = tex;
2217
2263
  }
2218
2264
  /**
2219
- * Write uniforms BEFORE beginFrame(). No matrix inversion needed —
2220
- * we extract the view rotation transpose and inverse projection scalars directly.
2265
+ * Write uniforms BEFORE beginFrame().
2266
+ * pitchOffset: radians to rotate sampling direction around world X axis,
2267
+ * used to align skybox horizon with model ground plane.
2221
2268
  */
2222
- prepareFrame(viewMatrix, projectionMatrix) {
2269
+ prepareFrame(viewMatrix, projectionMatrix, pitchOffset = 0) {
2223
2270
  if (!this.isActive) {
2224
2271
  this.frameReady = false;
2225
2272
  return;
@@ -2237,6 +2284,10 @@ class SkyboxRenderer {
2237
2284
  ud[9] = viewMatrix[9];
2238
2285
  ud[10] = viewMatrix[10];
2239
2286
  ud[11] = 0;
2287
+ ud[12] = pitchOffset;
2288
+ ud[13] = 0;
2289
+ ud[14] = 0;
2290
+ ud[15] = 0;
2240
2291
  this.device.queue.writeBuffer(this.uniformBuffer, 0, ud);
2241
2292
  this.frameReady = true;
2242
2293
  }
@@ -18871,7 +18922,26 @@ class App {
18871
18922
  this.updateAdaptivePerformance();
18872
18923
  this.hotspotManager.updateBillboards();
18873
18924
  if ((_a2 = this.skyboxRenderer) == null ? void 0 : _a2.isActive) {
18874
- this.skyboxRenderer.prepareFrame(this.camera.viewMatrix, this.camera.projectionMatrix);
18925
+ let pitchOffset = 0;
18926
+ if (this.skyboxRenderer.alignToGround) {
18927
+ const cam = this.camera.position;
18928
+ const mc = this.controls.modelCenter;
18929
+ const mr = this.controls.modelRadius;
18930
+ if (mr !== Infinity) {
18931
+ const groundY = mc[1] - mr;
18932
+ const heightAboveGround = cam[1] - groundY;
18933
+ const dx = cam[0] - mc[0];
18934
+ const dz = cam[2] - mc[2];
18935
+ const hDist = Math.sqrt(dx * dx + dz * dz);
18936
+ const raw = Math.atan2(heightAboveGround, Math.max(hDist, 0.01));
18937
+ pitchOffset = Math.max(-1.2, Math.min(1.2, raw));
18938
+ }
18939
+ }
18940
+ this.skyboxRenderer.prepareFrame(
18941
+ this.camera.viewMatrix,
18942
+ this.camera.projectionMatrix,
18943
+ pitchOffset
18944
+ );
18875
18945
  }
18876
18946
  const pass = this.renderer.beginFrame();
18877
18947
  if ((_b2 = this.skyboxRenderer) == null ? void 0 : _b2.isActive) {
@@ -19063,6 +19133,11 @@ class App {
19063
19133
  var _a2;
19064
19134
  return ((_a2 = this.skyboxRenderer) == null ? void 0 : _a2.isActive) ?? false;
19065
19135
  }
19136
+ setSkyboxAlignToGround(value) {
19137
+ if (this.skyboxRenderer) {
19138
+ this.skyboxRenderer.alignToGround = value;
19139
+ }
19140
+ }
19066
19141
  // ============================================
19067
19142
  // Gizmo(委托给 GizmoManager)
19068
19143
  // ============================================