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