@d5techs/3dgs-lib 1.4.85 → 1.4.86

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
@@ -793,6 +793,7 @@ const _OrbitControls = class _OrbitControls {
793
793
  // 键盘移动
794
794
  __publicField(this, "moveSpeed", 0.015);
795
795
  __publicField(this, "pressedKeys", /* @__PURE__ */ new Set());
796
+ __publicField(this, "_wasKeyboardMoving", false);
796
797
  // 触摸手势状态
797
798
  __publicField(this, "touchMode", "none");
798
799
  __publicField(this, "lastTouchDistance", 0);
@@ -969,7 +970,14 @@ const _OrbitControls = class _OrbitControls {
969
970
  this.pressedKeys.delete(e.key.toLowerCase());
970
971
  }
971
972
  applyKeyboardMovement() {
972
- if (this.pressedKeys.size === 0) return;
973
+ if (this.pressedKeys.size === 0) {
974
+ if (this._wasKeyboardMoving) {
975
+ this._wasKeyboardMoving = false;
976
+ this.recenterOrbitTarget();
977
+ }
978
+ return;
979
+ }
980
+ this._wasKeyboardMoving = true;
973
981
  const m = this.camera.viewMatrix;
974
982
  const right = [m[0], m[4], m[8]];
975
983
  const forward = [-m[2], -m[6], -m[10]];
@@ -1129,9 +1137,15 @@ const _OrbitControls = class _OrbitControls {
1129
1137
  onTouchEnd(e) {
1130
1138
  if (e.touches.length === 0) {
1131
1139
  this.isDragging = false;
1140
+ if (this.touchMode === "zoom-pan") {
1141
+ this.recenterOrbitTarget();
1142
+ }
1132
1143
  this.touchMode = "none";
1133
1144
  this.lastTouchDistance = 0;
1134
1145
  } else if (e.touches.length === 1) {
1146
+ if (this.touchMode === "zoom-pan") {
1147
+ this.recenterOrbitTarget();
1148
+ }
1135
1149
  this.touchMode = "rotate";
1136
1150
  this.lastX = e.touches[0].clientX;
1137
1151
  this.lastY = e.touches[0].clientY;
@@ -1148,6 +1162,34 @@ const _OrbitControls = class _OrbitControls {
1148
1162
  y: (touches[0].clientY + touches[1].clientY) / 2
1149
1163
  };
1150
1164
  }
1165
+ /**
1166
+ * 将 orbit 目标重新锚定到屏幕中心的模型表面点,
1167
+ * 保持相机世界坐标不变,仅重算 distance/theta/phi。
1168
+ * 用于 WASD 移动或触摸缩放结束后修复旋转中心偏移。
1169
+ */
1170
+ recenterOrbitTarget() {
1171
+ if (!this.pickWorldPosition) return;
1172
+ const rect = this.canvas.getBoundingClientRect();
1173
+ const hit = this.pickWorldPosition(
1174
+ rect.left + rect.width / 2,
1175
+ rect.top + rect.height / 2
1176
+ );
1177
+ if (!hit) return;
1178
+ const dx = this.camera.position[0] - hit[0];
1179
+ const dy = this.camera.position[1] - hit[1];
1180
+ const dz = this.camera.position[2] - hit[2];
1181
+ const newDist = Math.sqrt(dx * dx + dy * dy + dz * dz);
1182
+ if (newDist < this.minDistance) return;
1183
+ this.camera.target[0] = hit[0];
1184
+ this.camera.target[1] = hit[1];
1185
+ this.camera.target[2] = hit[2];
1186
+ this.distance = newDist;
1187
+ this.theta = Math.atan2(dx, dz);
1188
+ this.phi = Math.acos(Math.min(1, Math.max(-1, dy / newDist)));
1189
+ this.deltaPanX = 0;
1190
+ this.deltaPanY = 0;
1191
+ this.deltaPanZ = 0;
1192
+ }
1151
1193
  /**
1152
1194
  * 将球坐标写入相机位置(内部方法,不处理阻尼)
1153
1195
  */
@@ -19249,8 +19291,8 @@ class App {
19249
19291
  __publicField(this, "animationId", 0);
19250
19292
  // 是否使用移动端渲染器
19251
19293
  __publicField(this, "useMobileRenderer", false);
19252
- // 缓存移动端检测结果,避免每帧调用
19253
- __publicField(this, "isMobile", false);
19294
+ // 移动端优化开关(默认关闭,由外部显式启用)
19295
+ __publicField(this, "mobileOptimizationsEnabled", false);
19254
19296
  // 移动端可见 splat 硬上限(保证稳定帧率)
19255
19297
  __publicField(this, "mobileMaxVisibleCap", 0);
19256
19298
  // 最近加载的 CompactSplatData(用于编辑器导出)
@@ -19284,7 +19326,6 @@ class App {
19284
19326
  * 初始化应用
19285
19327
  */
19286
19328
  async init() {
19287
- var _a2;
19288
19329
  this.renderer = new Renderer(this.canvas);
19289
19330
  await this.renderer.init();
19290
19331
  this.camera = new Camera();
@@ -19313,14 +19354,6 @@ class App {
19313
19354
  if (this.renderer.isAppleGPU) {
19314
19355
  this.applyAppleGPUDefaults();
19315
19356
  }
19316
- this.isMobile = isMobileDevice();
19317
- if (this.isMobile) {
19318
- createDebugOverlay();
19319
- (_a2 = getDebugOverlay()) == null ? void 0 : _a2.info(
19320
- `Mobile detected. DPR=${window.devicePixelRatio}, canvas=${this.canvas.width}x${this.canvas.height}`
19321
- );
19322
- this.applyMobileDefaults();
19323
- }
19324
19357
  }
19325
19358
  /**
19326
19359
  * Apple GPU (M1/M2/M3 等) 自动优化配置
@@ -19339,12 +19372,26 @@ class App {
19339
19372
  );
19340
19373
  }
19341
19374
  /**
19342
- * 移动端自动性能优化
19343
- * 核心策略:降 DPR + 降分辨率 + 激进剔除 + 降排序频率
19375
+ * 启用/禁用移动端性能优化(默认关闭)
19376
+ * 开启后,加载模型时将自动应用:f16 半精度、16-bit 排序、像素剔除、动态分辨率等。
19377
+ * 应在 init() 之后、加载模型之前调用。
19344
19378
  */
19345
- applyMobileDefaults() {
19346
- this.dynamicResolutionEnabled = true;
19347
- console.log(`[3DGS] Mobile: f16, sortBits=16, pixelThreshold=2.5, DPR=1.5, dynamicRes=ON`);
19379
+ enableMobileOptimizations(enabled = true) {
19380
+ var _a2;
19381
+ this.mobileOptimizationsEnabled = enabled;
19382
+ if (enabled) {
19383
+ this.dynamicResolutionEnabled = true;
19384
+ createDebugOverlay();
19385
+ (_a2 = getDebugOverlay()) == null ? void 0 : _a2.info(
19386
+ `Mobile optimizations enabled. DPR=${window.devicePixelRatio}, canvas=${this.canvas.width}x${this.canvas.height}`
19387
+ );
19388
+ console.log(`[3DGS] Mobile optimizations: f16, sortBits=16, pixelThreshold=1.5, dynamicRes=ON`);
19389
+ } else {
19390
+ this.dynamicResolutionEnabled = false;
19391
+ }
19392
+ }
19393
+ isMobileOptimized() {
19394
+ return this.mobileOptimizationsEnabled;
19348
19395
  }
19349
19396
  /**
19350
19397
  * 创建 GSSplatRenderer 并自动应用平台优化
@@ -19407,7 +19454,7 @@ class App {
19407
19454
  */
19408
19455
  async addPLY(urlOrBuffer, onProgress, isLocalFile = false, coordinateSystem = "blender") {
19409
19456
  try {
19410
- const isMobile = isMobileDevice();
19457
+ const useMobileOpt = this.mobileOptimizationsEnabled;
19411
19458
  let buffer;
19412
19459
  if (typeof urlOrBuffer === "string") {
19413
19460
  buffer = await this.fetchWithProgress(
@@ -19430,16 +19477,16 @@ class App {
19430
19477
  onProgress(50 + parseProgress, "parse");
19431
19478
  }
19432
19479
  };
19433
- if (isMobile) {
19480
+ if (useMobileOpt) {
19434
19481
  console.log(
19435
- "[3DGS] Mobile device detected, using unified desktop renderer with SH L0"
19482
+ "[3DGS] Mobile optimizations active, using SH L0"
19436
19483
  );
19437
19484
  }
19438
- const gsRenderer = this.createGSRendererUnified(isMobile);
19485
+ const gsRenderer = this.createGSRendererUnified(useMobileOpt);
19439
19486
  this.useMobileRenderer = false;
19440
19487
  const compactData = await this.parsePLYBuffer(buffer, {
19441
19488
  maxSplats: Infinity,
19442
- loadSH: !isMobile,
19489
+ loadSH: !useMobileOpt,
19443
19490
  onProgress: parseProgressCallback,
19444
19491
  coordinateSystem
19445
19492
  });
@@ -19460,7 +19507,7 @@ class App {
19460
19507
  */
19461
19508
  async addSplat(urlOrBuffer, onProgress, isLocalFile = false, coordinateSystem = "blender") {
19462
19509
  try {
19463
- const isMobile = isMobileDevice();
19510
+ const useMobileOpt = this.mobileOptimizationsEnabled;
19464
19511
  let buffer;
19465
19512
  if (typeof urlOrBuffer === "string") {
19466
19513
  buffer = await this.fetchWithProgress(
@@ -19491,7 +19538,7 @@ class App {
19491
19538
  }
19492
19539
  if (onProgress) onProgress(90, "parse");
19493
19540
  if (onProgress) onProgress(90, "upload");
19494
- const gsRenderer = this.createGSRendererUnified(isMobile);
19541
+ const gsRenderer = this.createGSRendererUnified(useMobileOpt);
19495
19542
  this.useMobileRenderer = false;
19496
19543
  gsRenderer.setData(splats);
19497
19544
  this.sceneManager.setGSRenderer(gsRenderer);
@@ -19538,7 +19585,7 @@ class App {
19538
19585
  */
19539
19586
  async addSOG(urlOrBuffer, onProgress, isLocalFile = false, coordinateSystem = "blender") {
19540
19587
  try {
19541
- const isMobile = isMobileDevice();
19588
+ const useMobileOpt = this.mobileOptimizationsEnabled;
19542
19589
  let buffer;
19543
19590
  if (typeof urlOrBuffer === "string") {
19544
19591
  buffer = await this.fetchWithProgress(
@@ -19580,7 +19627,7 @@ class App {
19580
19627
  }
19581
19628
  }
19582
19629
  if (onProgress) onProgress(90, "upload");
19583
- const gsRenderer = this.createGSRendererUnified(isMobile);
19630
+ const gsRenderer = this.createGSRendererUnified(useMobileOpt);
19584
19631
  this.useMobileRenderer = false;
19585
19632
  gsRenderer.setCompactData(compactData);
19586
19633
  this.lastCompactData = compactData;