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