@heliguy-xyz/splat-viewer 1.0.0-rc.24 → 1.0.0-rc.25
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/CHANGELOG.md +1 -0
- package/dist/web-component/splat-viewer.esm.js +825 -139
- package/dist/web-component/splat-viewer.esm.min.js +2 -2
- package/dist/web-component/splat-viewer.js +825 -139
- package/dist/web-component/splat-viewer.min.js +2 -2
- package/dist/web-component/supersplat-core/doc.d.ts.map +1 -1
- package/dist/web-component/supersplat-core/file-handler.d.ts.map +1 -1
- package/dist/web-component/supersplat-core/index.d.ts +1 -1
- package/dist/web-component/supersplat-core/index.d.ts.map +1 -1
- package/dist/web-component/supersplat-core/main.d.ts.map +1 -1
- package/dist/web-component/supersplat-core/publish.d.ts.map +1 -1
- package/dist/web-component/supersplat-core/render.d.ts.map +1 -1
- package/dist/web-component/supersplat-core/tools/measure-tool.d.ts.map +1 -1
- package/dist/web-component/types/supersplat-core/doc.d.ts.map +1 -1
- package/dist/web-component/types/supersplat-core/file-handler.d.ts.map +1 -1
- package/dist/web-component/types/supersplat-core/index.d.ts +1 -1
- package/dist/web-component/types/supersplat-core/index.d.ts.map +1 -1
- package/dist/web-component/types/supersplat-core/main.d.ts.map +1 -1
- package/dist/web-component/types/supersplat-core/publish.d.ts.map +1 -1
- package/dist/web-component/types/supersplat-core/render.d.ts.map +1 -1
- package/dist/web-component/types/supersplat-core/tools/measure-tool.d.ts.map +1 -1
- package/dist/web-component/types/web-component/CameraModeManager.d.ts.map +1 -1
- package/dist/web-component/types/web-component/FlyCameraController.d.ts +85 -0
- package/dist/web-component/types/web-component/FlyCameraController.d.ts.map +1 -0
- package/dist/web-component/types/web-component/FlyCameraScript.d.ts.map +1 -1
- package/dist/web-component/types/web-component/SplatViewerCore.d.ts.map +1 -1
- package/dist/web-component/types/web-component/supersplat/BoxSelectionAPI.d.ts +1 -0
- package/dist/web-component/types/web-component/supersplat/BoxSelectionAPI.d.ts.map +1 -1
- package/dist/web-component/types/web-component/supersplat/SphereSelectionAPI.d.ts +1 -0
- package/dist/web-component/types/web-component/supersplat/SphereSelectionAPI.d.ts.map +1 -1
- package/dist/web-component/types/web-component/supersplat/blue-noise.d.ts +3 -0
- package/dist/web-component/types/web-component/supersplat/blue-noise.d.ts.map +1 -0
- package/dist/web-component/web-component/CameraModeManager.d.ts.map +1 -1
- package/dist/web-component/web-component/FlyCameraController.d.ts +85 -0
- package/dist/web-component/web-component/FlyCameraController.d.ts.map +1 -0
- package/dist/web-component/web-component/FlyCameraScript.d.ts.map +1 -1
- package/dist/web-component/web-component/SplatViewerCore.d.ts.map +1 -1
- package/dist/web-component/web-component/supersplat/BoxSelectionAPI.d.ts +1 -0
- package/dist/web-component/web-component/supersplat/BoxSelectionAPI.d.ts.map +1 -1
- package/dist/web-component/web-component/supersplat/SphereSelectionAPI.d.ts +1 -0
- package/dist/web-component/web-component/supersplat/SphereSelectionAPI.d.ts.map +1 -1
- package/dist/web-component/web-component/supersplat/blue-noise.d.ts +3 -0
- package/dist/web-component/web-component/supersplat/blue-noise.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/web-component/supersplat-core/ui/bottom-toolbar.d.ts +0 -8
- package/dist/web-component/supersplat-core/ui/bottom-toolbar.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/color-panel.d.ts +0 -8
- package/dist/web-component/supersplat-core/ui/color-panel.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/color.d.ts +0 -20
- package/dist/web-component/supersplat-core/ui/color.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/data-panel.d.ts +0 -7
- package/dist/web-component/supersplat-core/ui/data-panel.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/editor.d.ts +0 -14
- package/dist/web-component/supersplat-core/ui/editor.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/export-popup.d.ts +0 -11
- package/dist/web-component/supersplat-core/ui/export-popup.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/histogram.d.ts +0 -32
- package/dist/web-component/supersplat-core/ui/histogram.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/image-settings-dialog.d.ts +0 -11
- package/dist/web-component/supersplat-core/ui/image-settings-dialog.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/localization.d.ts +0 -9
- package/dist/web-component/supersplat-core/ui/localization.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/menu-panel.d.ts +0 -21
- package/dist/web-component/supersplat-core/ui/menu-panel.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/menu.d.ts +0 -7
- package/dist/web-component/supersplat-core/ui/menu.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/mode-toggle.d.ts +0 -8
- package/dist/web-component/supersplat-core/ui/mode-toggle.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/popup.d.ts +0 -16
- package/dist/web-component/supersplat-core/ui/popup.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/progress.d.ts +0 -9
- package/dist/web-component/supersplat-core/ui/progress.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/publish-settings-dialog.d.ts +0 -11
- package/dist/web-component/supersplat-core/ui/publish-settings-dialog.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/right-toolbar.d.ts +0 -8
- package/dist/web-component/supersplat-core/ui/right-toolbar.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/scene-panel.d.ts +0 -8
- package/dist/web-component/supersplat-core/ui/scene-panel.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/shortcuts-popup.d.ts +0 -6
- package/dist/web-component/supersplat-core/ui/shortcuts-popup.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/spinner.d.ts +0 -6
- package/dist/web-component/supersplat-core/ui/spinner.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/splat-list.d.ts +0 -25
- package/dist/web-component/supersplat-core/ui/splat-list.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/timeline-panel.d.ts +0 -8
- package/dist/web-component/supersplat-core/ui/timeline-panel.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/tooltips.d.ts +0 -10
- package/dist/web-component/supersplat-core/ui/tooltips.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/transform.d.ts +0 -7
- package/dist/web-component/supersplat-core/ui/transform.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/video-settings-dialog.d.ts +0 -11
- package/dist/web-component/supersplat-core/ui/video-settings-dialog.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/view-cube.d.ts +0 -9
- package/dist/web-component/supersplat-core/ui/view-cube.d.ts.map +0 -1
- package/dist/web-component/supersplat-core/ui/view-panel.d.ts +0 -8
- package/dist/web-component/supersplat-core/ui/view-panel.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/bottom-toolbar.d.ts +0 -8
- package/dist/web-component/types/supersplat-core/ui/bottom-toolbar.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/color-panel.d.ts +0 -8
- package/dist/web-component/types/supersplat-core/ui/color-panel.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/color.d.ts +0 -20
- package/dist/web-component/types/supersplat-core/ui/color.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/data-panel.d.ts +0 -7
- package/dist/web-component/types/supersplat-core/ui/data-panel.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/editor.d.ts +0 -14
- package/dist/web-component/types/supersplat-core/ui/editor.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/export-popup.d.ts +0 -11
- package/dist/web-component/types/supersplat-core/ui/export-popup.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/histogram.d.ts +0 -32
- package/dist/web-component/types/supersplat-core/ui/histogram.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/image-settings-dialog.d.ts +0 -11
- package/dist/web-component/types/supersplat-core/ui/image-settings-dialog.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/localization.d.ts +0 -9
- package/dist/web-component/types/supersplat-core/ui/localization.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/menu-panel.d.ts +0 -21
- package/dist/web-component/types/supersplat-core/ui/menu-panel.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/menu.d.ts +0 -7
- package/dist/web-component/types/supersplat-core/ui/menu.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/mode-toggle.d.ts +0 -8
- package/dist/web-component/types/supersplat-core/ui/mode-toggle.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/popup.d.ts +0 -16
- package/dist/web-component/types/supersplat-core/ui/popup.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/progress.d.ts +0 -9
- package/dist/web-component/types/supersplat-core/ui/progress.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/publish-settings-dialog.d.ts +0 -11
- package/dist/web-component/types/supersplat-core/ui/publish-settings-dialog.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/right-toolbar.d.ts +0 -8
- package/dist/web-component/types/supersplat-core/ui/right-toolbar.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/scene-panel.d.ts +0 -8
- package/dist/web-component/types/supersplat-core/ui/scene-panel.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/shortcuts-popup.d.ts +0 -6
- package/dist/web-component/types/supersplat-core/ui/shortcuts-popup.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/spinner.d.ts +0 -6
- package/dist/web-component/types/supersplat-core/ui/spinner.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/splat-list.d.ts +0 -25
- package/dist/web-component/types/supersplat-core/ui/splat-list.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/timeline-panel.d.ts +0 -8
- package/dist/web-component/types/supersplat-core/ui/timeline-panel.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/tooltips.d.ts +0 -10
- package/dist/web-component/types/supersplat-core/ui/tooltips.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/transform.d.ts +0 -7
- package/dist/web-component/types/supersplat-core/ui/transform.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/video-settings-dialog.d.ts +0 -11
- package/dist/web-component/types/supersplat-core/ui/video-settings-dialog.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/view-cube.d.ts +0 -9
- package/dist/web-component/types/supersplat-core/ui/view-cube.d.ts.map +0 -1
- package/dist/web-component/types/supersplat-core/ui/view-panel.d.ts +0 -8
- package/dist/web-component/types/supersplat-core/ui/view-panel.d.ts.map +0 -1
|
@@ -105228,14 +105228,24 @@ class CameraModeManager {
|
|
|
105228
105228
|
activateFlyMode() {
|
|
105229
105229
|
if (!this.fly)
|
|
105230
105230
|
return;
|
|
105231
|
-
|
|
105232
|
-
this.fly.activate();
|
|
105233
|
-
// Align fly camera internal orientation with the current camera rotation so that
|
|
105234
|
-
// switching from orbit -> fly does not snap the view back to the initial direction.
|
|
105231
|
+
// Preserve camera position and rotation when switching to fly mode
|
|
105235
105232
|
try {
|
|
105233
|
+
// Preserve position
|
|
105234
|
+
const pos = this.camera.getPosition
|
|
105235
|
+
? this.camera.getPosition().clone()
|
|
105236
|
+
: this.camera.getLocalPosition
|
|
105237
|
+
? this.camera.getLocalPosition().clone()
|
|
105238
|
+
: null;
|
|
105239
|
+
if (pos && this.camera.setPosition) {
|
|
105240
|
+
;
|
|
105241
|
+
this.camera.setPosition(pos);
|
|
105242
|
+
}
|
|
105243
|
+
// Preserve rotation (convert Euler to pitch/yaw)
|
|
105236
105244
|
const euler = this.camera.getEulerAngles
|
|
105237
105245
|
? this.camera.getEulerAngles()
|
|
105238
|
-
:
|
|
105246
|
+
: this.camera.getLocalEulerAngles
|
|
105247
|
+
? this.camera.getLocalEulerAngles()
|
|
105248
|
+
: null;
|
|
105239
105249
|
if (euler) {
|
|
105240
105250
|
// These properties are part of the FlyCamera runtime state
|
|
105241
105251
|
;
|
|
@@ -105246,6 +105256,8 @@ class CameraModeManager {
|
|
|
105246
105256
|
catch {
|
|
105247
105257
|
// Best-effort sync; ignore if camera or script API differs
|
|
105248
105258
|
}
|
|
105259
|
+
if (typeof this.fly.activate === 'function')
|
|
105260
|
+
this.fly.activate();
|
|
105249
105261
|
}
|
|
105250
105262
|
deactivateFlyMode() {
|
|
105251
105263
|
if (!this.fly)
|
|
@@ -105255,6 +105267,373 @@ class CameraModeManager {
|
|
|
105255
105267
|
}
|
|
105256
105268
|
}
|
|
105257
105269
|
|
|
105270
|
+
/**
|
|
105271
|
+
* Fly camera controller for environments where PlayCanvas ScriptComponentSystem is not available
|
|
105272
|
+
* (e.g. supersplat-core's custom PCApp, which omits ScriptComponentSystem).
|
|
105273
|
+
*
|
|
105274
|
+
* This controller attaches DOM input listeners and updates the camera entity via `app.on('update')`.
|
|
105275
|
+
*/
|
|
105276
|
+
class FlyCameraController {
|
|
105277
|
+
constructor(app, entity, emitFlyEvent, config) {
|
|
105278
|
+
// Config
|
|
105279
|
+
this.moveSpeed = 5.0;
|
|
105280
|
+
this.fastSpeedMultiplier = 3.0;
|
|
105281
|
+
this.slowSpeedMultiplier = 0.3;
|
|
105282
|
+
this.lookSensitivity = 0.2;
|
|
105283
|
+
this.invertY = false;
|
|
105284
|
+
this.keyBindings = {
|
|
105285
|
+
forward: 'KeyW',
|
|
105286
|
+
backward: 'KeyS',
|
|
105287
|
+
left: 'KeyA',
|
|
105288
|
+
right: 'KeyD',
|
|
105289
|
+
up: 'KeyE',
|
|
105290
|
+
down: 'KeyQ',
|
|
105291
|
+
fastMove: 'ShiftLeft',
|
|
105292
|
+
slowMove: 'ControlLeft',
|
|
105293
|
+
};
|
|
105294
|
+
this.smoothing = 0.8;
|
|
105295
|
+
this.friction = 0.85;
|
|
105296
|
+
this.enableCollision = false;
|
|
105297
|
+
this.minHeight = null;
|
|
105298
|
+
this.maxHeight = null;
|
|
105299
|
+
this._isActive = true;
|
|
105300
|
+
this._isPointerLocked = false;
|
|
105301
|
+
this._isLooking = false;
|
|
105302
|
+
this._pressed = new Set();
|
|
105303
|
+
this._velocity = new Vec3(0, 0, 0);
|
|
105304
|
+
this._targetVelocity = new Vec3(0, 0, 0);
|
|
105305
|
+
this._pitch = 0;
|
|
105306
|
+
this._yaw = 0;
|
|
105307
|
+
this._lastMoveEmitTime = 0;
|
|
105308
|
+
this._lastLookEmitTime = 0;
|
|
105309
|
+
this._updateHandler = null;
|
|
105310
|
+
this.app = app;
|
|
105311
|
+
this.entity = entity;
|
|
105312
|
+
this.emitFlyEvent = emitFlyEvent;
|
|
105313
|
+
if (config) {
|
|
105314
|
+
this.setConfig(config);
|
|
105315
|
+
}
|
|
105316
|
+
// Sync initial yaw/pitch from entity orientation if available
|
|
105317
|
+
try {
|
|
105318
|
+
const euler = this.entity?.getEulerAngles?.();
|
|
105319
|
+
if (euler) {
|
|
105320
|
+
this._pitch = euler.x || 0;
|
|
105321
|
+
this._yaw = euler.y || 0;
|
|
105322
|
+
}
|
|
105323
|
+
}
|
|
105324
|
+
catch {
|
|
105325
|
+
// ignore
|
|
105326
|
+
}
|
|
105327
|
+
this._bindInputListeners();
|
|
105328
|
+
}
|
|
105329
|
+
_bindInputListeners() {
|
|
105330
|
+
// Keyboard (capture phase so we see keys even when other handlers run)
|
|
105331
|
+
this._onKeyDown = this._handleKeyDown.bind(this);
|
|
105332
|
+
this._onKeyUp = this._handleKeyUp.bind(this);
|
|
105333
|
+
document.addEventListener('keydown', this._onKeyDown, true);
|
|
105334
|
+
document.addEventListener('keyup', this._onKeyUp, true);
|
|
105335
|
+
// Look: pointer events primary, mouse fallback
|
|
105336
|
+
this._onMouseMove = this._handleMouseMove.bind(this);
|
|
105337
|
+
this._onPointerMove = this._handlePointerMove.bind(this);
|
|
105338
|
+
document.addEventListener('mousemove', this._onMouseMove);
|
|
105339
|
+
document.addEventListener('pointermove', this._onPointerMove, true);
|
|
105340
|
+
const canvas = this.app?.graphicsDevice?.canvas;
|
|
105341
|
+
this._onMouseDown = (e) => {
|
|
105342
|
+
if (e.button === 0 && this._isActive)
|
|
105343
|
+
this._isLooking = true;
|
|
105344
|
+
};
|
|
105345
|
+
this._onPointerDown = (e) => {
|
|
105346
|
+
if (e.button === 0 && this._isActive) {
|
|
105347
|
+
this._isLooking = true;
|
|
105348
|
+
}
|
|
105349
|
+
};
|
|
105350
|
+
canvas?.addEventListener('mousedown', this._onMouseDown, true);
|
|
105351
|
+
canvas?.addEventListener('pointerdown', this._onPointerDown, true);
|
|
105352
|
+
this._onMouseUp = (e) => {
|
|
105353
|
+
if (e.button === 0)
|
|
105354
|
+
this._isLooking = false;
|
|
105355
|
+
};
|
|
105356
|
+
this._onPointerUp = (e) => {
|
|
105357
|
+
if (e.button === 0) {
|
|
105358
|
+
this._isLooking = false;
|
|
105359
|
+
}
|
|
105360
|
+
};
|
|
105361
|
+
document.addEventListener('mouseup', this._onMouseUp, true);
|
|
105362
|
+
document.addEventListener('pointerup', this._onPointerUp, true);
|
|
105363
|
+
}
|
|
105364
|
+
_unbindInputListeners() {
|
|
105365
|
+
document.removeEventListener('keydown', this._onKeyDown, true);
|
|
105366
|
+
document.removeEventListener('keyup', this._onKeyUp, true);
|
|
105367
|
+
document.removeEventListener('mousemove', this._onMouseMove);
|
|
105368
|
+
document.removeEventListener('pointermove', this._onPointerMove, true);
|
|
105369
|
+
document.removeEventListener('mouseup', this._onMouseUp, true);
|
|
105370
|
+
document.removeEventListener('pointerup', this._onPointerUp, true);
|
|
105371
|
+
const canvas = this.app?.graphicsDevice?.canvas;
|
|
105372
|
+
if (canvas && this._onMouseDown) {
|
|
105373
|
+
canvas.removeEventListener('mousedown', this._onMouseDown, true);
|
|
105374
|
+
}
|
|
105375
|
+
if (canvas && this._onPointerDown) {
|
|
105376
|
+
canvas.removeEventListener('pointerdown', this._onPointerDown, true);
|
|
105377
|
+
}
|
|
105378
|
+
}
|
|
105379
|
+
/**
|
|
105380
|
+
* Sync position and rotation from the camera entity.
|
|
105381
|
+
* Called when switching from orbit to fly mode to preserve camera state.
|
|
105382
|
+
*/
|
|
105383
|
+
syncFromEntity() {
|
|
105384
|
+
try {
|
|
105385
|
+
// Preserve position
|
|
105386
|
+
const pos = this.entity?.getPosition?.() || this.entity?.getLocalPosition?.();
|
|
105387
|
+
if (pos) {
|
|
105388
|
+
const posVec = pos.clone ? pos.clone() : new Vec3(pos.x || 0, pos.y || 0, pos.z || 0);
|
|
105389
|
+
this.entity?.setPosition?.(posVec);
|
|
105390
|
+
this.entity?.setLocalPosition?.(posVec);
|
|
105391
|
+
}
|
|
105392
|
+
// Preserve rotation (convert Euler to pitch/yaw)
|
|
105393
|
+
const euler = this.entity?.getEulerAngles?.() || this.entity?.getLocalEulerAngles?.();
|
|
105394
|
+
if (euler) {
|
|
105395
|
+
// PlayCanvas Euler: x=pitch, y=yaw, z=roll
|
|
105396
|
+
this._pitch = euler.x || 0;
|
|
105397
|
+
this._yaw = euler.y || 0;
|
|
105398
|
+
// Apply rotation immediately so view doesn't snap
|
|
105399
|
+
if (this.entity?.setLocalEulerAngles) {
|
|
105400
|
+
this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
|
|
105401
|
+
}
|
|
105402
|
+
else if (this.entity?.setEulerAngles) {
|
|
105403
|
+
this.entity.setEulerAngles(this._pitch, this._yaw, 0);
|
|
105404
|
+
}
|
|
105405
|
+
}
|
|
105406
|
+
}
|
|
105407
|
+
catch {
|
|
105408
|
+
// ignore
|
|
105409
|
+
}
|
|
105410
|
+
}
|
|
105411
|
+
activate() {
|
|
105412
|
+
if (this._isActive)
|
|
105413
|
+
return;
|
|
105414
|
+
this._isActive = true;
|
|
105415
|
+
// Sync position and rotation from current camera state before activating
|
|
105416
|
+
this.syncFromEntity();
|
|
105417
|
+
if (!this._updateHandler) {
|
|
105418
|
+
this._updateHandler = (dt) => this.update(dt);
|
|
105419
|
+
this.app?.on?.('update', this._updateHandler);
|
|
105420
|
+
}
|
|
105421
|
+
}
|
|
105422
|
+
deactivate() {
|
|
105423
|
+
if (!this._isActive)
|
|
105424
|
+
return;
|
|
105425
|
+
this._isActive = false;
|
|
105426
|
+
this._isLooking = false;
|
|
105427
|
+
this._pressed.clear();
|
|
105428
|
+
if (this._updateHandler) {
|
|
105429
|
+
try {
|
|
105430
|
+
this.app?.off?.('update', this._updateHandler);
|
|
105431
|
+
}
|
|
105432
|
+
catch {
|
|
105433
|
+
// ignore
|
|
105434
|
+
}
|
|
105435
|
+
this._updateHandler = null;
|
|
105436
|
+
}
|
|
105437
|
+
}
|
|
105438
|
+
destroy() {
|
|
105439
|
+
try {
|
|
105440
|
+
this.deactivate();
|
|
105441
|
+
}
|
|
105442
|
+
finally {
|
|
105443
|
+
this._unbindInputListeners();
|
|
105444
|
+
}
|
|
105445
|
+
}
|
|
105446
|
+
setConfig(config) {
|
|
105447
|
+
if (config.moveSpeed !== undefined)
|
|
105448
|
+
this.moveSpeed = config.moveSpeed;
|
|
105449
|
+
if (config.fastSpeedMultiplier !== undefined)
|
|
105450
|
+
this.fastSpeedMultiplier = config.fastSpeedMultiplier;
|
|
105451
|
+
if (config.slowSpeedMultiplier !== undefined)
|
|
105452
|
+
this.slowSpeedMultiplier = config.slowSpeedMultiplier;
|
|
105453
|
+
if (config.lookSensitivity !== undefined)
|
|
105454
|
+
this.lookSensitivity = config.lookSensitivity;
|
|
105455
|
+
if (config.invertY !== undefined)
|
|
105456
|
+
this.invertY = config.invertY;
|
|
105457
|
+
if (config.keyBindings !== undefined)
|
|
105458
|
+
this.keyBindings = config.keyBindings;
|
|
105459
|
+
if (config.smoothing !== undefined)
|
|
105460
|
+
this.smoothing = config.smoothing;
|
|
105461
|
+
if (config.friction !== undefined)
|
|
105462
|
+
this.friction = config.friction;
|
|
105463
|
+
if (config.enableCollision !== undefined)
|
|
105464
|
+
this.enableCollision = config.enableCollision;
|
|
105465
|
+
if (config.minHeight !== undefined)
|
|
105466
|
+
this.minHeight = config.minHeight;
|
|
105467
|
+
if (config.maxHeight !== undefined)
|
|
105468
|
+
this.maxHeight = config.maxHeight;
|
|
105469
|
+
}
|
|
105470
|
+
getState() {
|
|
105471
|
+
const pos = this.entity?.getPosition?.();
|
|
105472
|
+
return {
|
|
105473
|
+
position: { x: pos?.x ?? 0, y: pos?.y ?? 0, z: pos?.z ?? 0 },
|
|
105474
|
+
rotation: { pitch: this._pitch, yaw: this._yaw },
|
|
105475
|
+
velocity: { x: this._velocity.x, y: this._velocity.y, z: this._velocity.z },
|
|
105476
|
+
isMoving: Math.abs(this._velocity.x) +
|
|
105477
|
+
Math.abs(this._velocity.y) +
|
|
105478
|
+
Math.abs(this._velocity.z) >
|
|
105479
|
+
1e-4,
|
|
105480
|
+
};
|
|
105481
|
+
}
|
|
105482
|
+
_handleKeyDown(e) {
|
|
105483
|
+
const keys = [];
|
|
105484
|
+
if (e.code)
|
|
105485
|
+
keys.push(e.code);
|
|
105486
|
+
if (e.key) {
|
|
105487
|
+
keys.push(e.key);
|
|
105488
|
+
if (e.key.length === 1) {
|
|
105489
|
+
keys.push(`Key${e.key.toUpperCase()}`);
|
|
105490
|
+
keys.push(e.key.toUpperCase());
|
|
105491
|
+
keys.push(e.key.toLowerCase());
|
|
105492
|
+
}
|
|
105493
|
+
}
|
|
105494
|
+
for (const k of keys)
|
|
105495
|
+
this._pressed.add(k);
|
|
105496
|
+
}
|
|
105497
|
+
_handleKeyUp(e) {
|
|
105498
|
+
const keys = [];
|
|
105499
|
+
if (e.code)
|
|
105500
|
+
keys.push(e.code);
|
|
105501
|
+
if (e.key) {
|
|
105502
|
+
keys.push(e.key);
|
|
105503
|
+
if (e.key.length === 1) {
|
|
105504
|
+
keys.push(`Key${e.key.toUpperCase()}`);
|
|
105505
|
+
keys.push(e.key.toUpperCase());
|
|
105506
|
+
keys.push(e.key.toLowerCase());
|
|
105507
|
+
}
|
|
105508
|
+
}
|
|
105509
|
+
for (const k of keys)
|
|
105510
|
+
this._pressed.delete(k);
|
|
105511
|
+
}
|
|
105512
|
+
_handleMouseMove(e) {
|
|
105513
|
+
if (!this._isLooking || !this._isActive)
|
|
105514
|
+
return;
|
|
105515
|
+
const dx = e.movementX * this.lookSensitivity;
|
|
105516
|
+
const dy = e.movementY * this.lookSensitivity * (this.invertY ? 1 : -1);
|
|
105517
|
+
this._yaw = (this._yaw - dx) % 360;
|
|
105518
|
+
this._pitch = Math.max(-89, Math.min(89, this._pitch + dy));
|
|
105519
|
+
}
|
|
105520
|
+
_handlePointerMove(e) {
|
|
105521
|
+
if (!this._isLooking || !this._isActive)
|
|
105522
|
+
return;
|
|
105523
|
+
const dx = (e.movementX || 0) * this.lookSensitivity;
|
|
105524
|
+
const dy = (e.movementY || 0) * this.lookSensitivity * (this.invertY ? 1 : -1);
|
|
105525
|
+
this._yaw = (this._yaw - dx) % 360;
|
|
105526
|
+
this._pitch = Math.max(-89, Math.min(89, this._pitch + dy));
|
|
105527
|
+
}
|
|
105528
|
+
_getEffectiveSpeed() {
|
|
105529
|
+
const fast = this._pressed.has(this.keyBindings.fastMove);
|
|
105530
|
+
const slow = this._pressed.has(this.keyBindings.slowMove);
|
|
105531
|
+
let s = this.moveSpeed;
|
|
105532
|
+
if (fast)
|
|
105533
|
+
s *= this.fastSpeedMultiplier;
|
|
105534
|
+
if (slow)
|
|
105535
|
+
s *= this.slowSpeedMultiplier;
|
|
105536
|
+
return s;
|
|
105537
|
+
}
|
|
105538
|
+
_updateVelocity() {
|
|
105539
|
+
const kb = this.keyBindings;
|
|
105540
|
+
const isPressed = (binding, fallbacks) => {
|
|
105541
|
+
const all = [binding, ...fallbacks].filter(Boolean);
|
|
105542
|
+
return all.some(k => this._pressed.has(k));
|
|
105543
|
+
};
|
|
105544
|
+
const forward = isPressed(kb.forward, ['KeyW', 'w', 'W']) ? 1 : 0;
|
|
105545
|
+
const backward = isPressed(kb.backward, ['KeyS', 's', 'S']) ? 1 : 0;
|
|
105546
|
+
const left = isPressed(kb.left, ['KeyA', 'a', 'A']) ? 1 : 0;
|
|
105547
|
+
const right = isPressed(kb.right, ['KeyD', 'd', 'D']) ? 1 : 0;
|
|
105548
|
+
const up = isPressed(kb.up, ['KeyE', 'e', 'E']) ? 1 : 0;
|
|
105549
|
+
const down = isPressed(kb.down, ['KeyQ', 'q', 'Q']) ? 1 : 0;
|
|
105550
|
+
const inputZ = forward - backward;
|
|
105551
|
+
const inputX = right - left;
|
|
105552
|
+
const inputY = up - down;
|
|
105553
|
+
const planarLen = Math.hypot(inputX, inputZ);
|
|
105554
|
+
const nx = planarLen > 0 ? inputX / planarLen : 0;
|
|
105555
|
+
const nz = planarLen > 0 ? inputZ / planarLen : 0;
|
|
105556
|
+
const speed = this._getEffectiveSpeed() * 2;
|
|
105557
|
+
const entity = this.entity;
|
|
105558
|
+
const fwd = entity?.forward && entity.forward.clone
|
|
105559
|
+
? entity.forward.clone()
|
|
105560
|
+
: entity?.forward
|
|
105561
|
+
? new Vec3(entity.forward.x, entity.forward.y, entity.forward.z)
|
|
105562
|
+
: new Vec3(0, 0, -1);
|
|
105563
|
+
const rightVec = entity?.right && entity.right.clone
|
|
105564
|
+
? entity.right.clone()
|
|
105565
|
+
: entity?.right
|
|
105566
|
+
? new Vec3(entity.right.x, entity.right.y, entity.right.z)
|
|
105567
|
+
: new Vec3(1, 0, 0);
|
|
105568
|
+
const upVec = entity?.up && entity.up.clone
|
|
105569
|
+
? entity.up.clone()
|
|
105570
|
+
: entity?.up
|
|
105571
|
+
? new Vec3(entity.up.x, entity.up.y, entity.up.z)
|
|
105572
|
+
: Vec3.UP.clone();
|
|
105573
|
+
const target = new Vec3(0, 0, 0);
|
|
105574
|
+
target.add(fwd.mulScalar(nz * speed));
|
|
105575
|
+
target.add(rightVec.mulScalar(nx * speed));
|
|
105576
|
+
target.add(upVec.mulScalar(inputY * speed));
|
|
105577
|
+
this._targetVelocity.copy(target);
|
|
105578
|
+
this._velocity.lerp(this._velocity, this._targetVelocity, Math.min(1, this.smoothing));
|
|
105579
|
+
if (nx === 0 && nz === 0 && inputY === 0) {
|
|
105580
|
+
this._velocity.mulScalar(this.friction);
|
|
105581
|
+
if (this._velocity.length() < 0.0001)
|
|
105582
|
+
this._velocity.set(0, 0, 0);
|
|
105583
|
+
}
|
|
105584
|
+
}
|
|
105585
|
+
_applyMovement(dt) {
|
|
105586
|
+
if (this._velocity.length() === 0)
|
|
105587
|
+
return;
|
|
105588
|
+
const pos = this.entity?.getPosition?.()?.clone?.() ?? new Vec3(0, 0, 0);
|
|
105589
|
+
pos.add(this._velocity.clone().mulScalar(dt));
|
|
105590
|
+
this.entity?.setPosition?.(pos);
|
|
105591
|
+
}
|
|
105592
|
+
_applyRotation() {
|
|
105593
|
+
if (this.entity?.setLocalEulerAngles) {
|
|
105594
|
+
this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
|
|
105595
|
+
}
|
|
105596
|
+
else if (this.entity?.setEulerAngles) {
|
|
105597
|
+
this.entity.setEulerAngles(this._pitch, this._yaw, 0);
|
|
105598
|
+
}
|
|
105599
|
+
}
|
|
105600
|
+
_applyConstraints() {
|
|
105601
|
+
if (!this.enableCollision &&
|
|
105602
|
+
this.minHeight == null &&
|
|
105603
|
+
this.maxHeight == null) {
|
|
105604
|
+
return;
|
|
105605
|
+
}
|
|
105606
|
+
const pos = this.entity?.getPosition?.()?.clone?.() ?? new Vec3(0, 0, 0);
|
|
105607
|
+
if (this.minHeight != null)
|
|
105608
|
+
pos.y = Math.max(pos.y, this.minHeight);
|
|
105609
|
+
if (this.maxHeight != null)
|
|
105610
|
+
pos.y = Math.min(pos.y, this.maxHeight);
|
|
105611
|
+
this.entity?.setPosition?.(pos);
|
|
105612
|
+
}
|
|
105613
|
+
update(dt) {
|
|
105614
|
+
if (!this._isActive)
|
|
105615
|
+
return;
|
|
105616
|
+
this._updateVelocity();
|
|
105617
|
+
this._applyMovement(dt);
|
|
105618
|
+
this._applyRotation();
|
|
105619
|
+
this._applyConstraints();
|
|
105620
|
+
// Emit throttled movement/look events (100ms)
|
|
105621
|
+
const now = performance.now();
|
|
105622
|
+
if (!this._lastMoveEmitTime || now - this._lastMoveEmitTime >= 100) {
|
|
105623
|
+
const pos = this.entity?.getPosition?.();
|
|
105624
|
+
this.emitFlyEvent?.('fly-camera-move', {
|
|
105625
|
+
position: { x: pos?.x ?? 0, y: pos?.y ?? 0, z: pos?.z ?? 0 },
|
|
105626
|
+
velocity: { x: this._velocity.x, y: this._velocity.y, z: this._velocity.z },
|
|
105627
|
+
});
|
|
105628
|
+
this._lastMoveEmitTime = now;
|
|
105629
|
+
}
|
|
105630
|
+
if (!this._lastLookEmitTime || now - this._lastLookEmitTime >= 100) {
|
|
105631
|
+
this.emitFlyEvent?.('fly-camera-look', { pitch: this._pitch, yaw: this._yaw });
|
|
105632
|
+
this._lastLookEmitTime = now;
|
|
105633
|
+
}
|
|
105634
|
+
}
|
|
105635
|
+
}
|
|
105636
|
+
|
|
105258
105637
|
// FlyCamera PlayCanvas script: first-person WASD movement with mouse-look
|
|
105259
105638
|
function registerFlyCameraScript() {
|
|
105260
105639
|
if (typeof pc === 'undefined') {
|
|
@@ -105340,10 +105719,18 @@ function registerFlyCameraScript() {
|
|
|
105340
105719
|
this._onKeyUp = this._handleKeyUp.bind(this);
|
|
105341
105720
|
document.addEventListener('keydown', this._onKeyDown, true);
|
|
105342
105721
|
document.addEventListener('keyup', this._onKeyUp, true);
|
|
105343
|
-
// Mouse move for look (while
|
|
105722
|
+
// Mouse/pointer move for look (while primary button held).
|
|
105723
|
+
//
|
|
105724
|
+
// Important: SuperSplat camera controls are pointer-event based and may call
|
|
105725
|
+
// preventDefault() on pointer events. When that happens, browsers often
|
|
105726
|
+
// suppress the corresponding legacy mouse events (mousedown/mousemove).
|
|
105727
|
+
// So we listen to *pointer* events as the primary path, with mouse as a
|
|
105728
|
+
// fallback for older environments.
|
|
105344
105729
|
this._onMouseMove = this._handleMouseMove.bind(this);
|
|
105730
|
+
this._onPointerMove = this._handlePointerMove.bind(this);
|
|
105345
105731
|
document.addEventListener('mousemove', this._onMouseMove);
|
|
105346
|
-
|
|
105732
|
+
document.addEventListener('pointermove', this._onPointerMove, true);
|
|
105733
|
+
// Button handling: click + hold to look, release to stop
|
|
105347
105734
|
const canvas = this.app.graphicsDevice.canvas;
|
|
105348
105735
|
this._onClickToLock = (e) => {
|
|
105349
105736
|
// Left button enables look while held (no pointer lock)
|
|
@@ -105351,13 +105738,17 @@ function registerFlyCameraScript() {
|
|
|
105351
105738
|
this._isLooking = true;
|
|
105352
105739
|
}
|
|
105353
105740
|
};
|
|
105354
|
-
canvas.addEventListener('mousedown', this._onClickToLock);
|
|
105741
|
+
canvas.addEventListener('mousedown', this._onClickToLock, true);
|
|
105742
|
+
this._onPointerDownToLook = this._handlePointerDown.bind(this);
|
|
105743
|
+
canvas.addEventListener('pointerdown', this._onPointerDownToLook, true);
|
|
105355
105744
|
this._onMouseUp = (e) => {
|
|
105356
105745
|
if (e.button === 0) {
|
|
105357
105746
|
this._isLooking = false;
|
|
105358
105747
|
}
|
|
105359
105748
|
};
|
|
105360
|
-
document.addEventListener('mouseup', this._onMouseUp);
|
|
105749
|
+
document.addEventListener('mouseup', this._onMouseUp, true);
|
|
105750
|
+
this._onPointerUp = this._handlePointerUp.bind(this);
|
|
105751
|
+
document.addEventListener('pointerup', this._onPointerUp, true);
|
|
105361
105752
|
};
|
|
105362
105753
|
FlyCamera.prototype.update = function (dt) {
|
|
105363
105754
|
if (!this._isActive)
|
|
@@ -105453,9 +105844,11 @@ function registerFlyCameraScript() {
|
|
|
105453
105844
|
this._onKeyDown = this._onKeyDown || this._handleKeyDown.bind(this);
|
|
105454
105845
|
this._onKeyUp = this._onKeyUp || this._handleKeyUp.bind(this);
|
|
105455
105846
|
this._onMouseMove = this._onMouseMove || this._handleMouseMove.bind(this);
|
|
105847
|
+
this._onPointerMove = this._onPointerMove || this._handlePointerMove.bind(this);
|
|
105456
105848
|
document.addEventListener('keydown', this._onKeyDown, true);
|
|
105457
105849
|
document.addEventListener('keyup', this._onKeyUp, true);
|
|
105458
105850
|
document.addEventListener('mousemove', this._onMouseMove);
|
|
105851
|
+
document.addEventListener('pointermove', this._onPointerMove, true);
|
|
105459
105852
|
const canvas = this.app.graphicsDevice.canvas;
|
|
105460
105853
|
this._onClickToLock =
|
|
105461
105854
|
this._onClickToLock ||
|
|
@@ -105464,7 +105857,10 @@ function registerFlyCameraScript() {
|
|
|
105464
105857
|
this._isLooking = true;
|
|
105465
105858
|
}
|
|
105466
105859
|
});
|
|
105467
|
-
canvas.addEventListener('mousedown', this._onClickToLock);
|
|
105860
|
+
canvas.addEventListener('mousedown', this._onClickToLock, true);
|
|
105861
|
+
this._onPointerDownToLook =
|
|
105862
|
+
this._onPointerDownToLook || this._handlePointerDown.bind(this);
|
|
105863
|
+
canvas.addEventListener('pointerdown', this._onPointerDownToLook, true);
|
|
105468
105864
|
this._onMouseUp =
|
|
105469
105865
|
this._onMouseUp ||
|
|
105470
105866
|
((e) => {
|
|
@@ -105472,22 +105868,36 @@ function registerFlyCameraScript() {
|
|
|
105472
105868
|
this._isLooking = false;
|
|
105473
105869
|
}
|
|
105474
105870
|
});
|
|
105475
|
-
document.addEventListener('mouseup', this._onMouseUp);
|
|
105871
|
+
document.addEventListener('mouseup', this._onMouseUp, true);
|
|
105872
|
+
this._onPointerUp = this._onPointerUp || this._handlePointerUp.bind(this);
|
|
105873
|
+
document.addEventListener('pointerup', this._onPointerUp, true);
|
|
105476
105874
|
};
|
|
105477
105875
|
FlyCamera.prototype.deactivate = function () {
|
|
105478
105876
|
if (!this._isActive)
|
|
105479
105877
|
return;
|
|
105480
105878
|
this._isActive = false;
|
|
105879
|
+
this._isLooking = false;
|
|
105880
|
+
try {
|
|
105881
|
+
this._pressed?.clear?.();
|
|
105882
|
+
}
|
|
105883
|
+
catch {
|
|
105884
|
+
// ignore
|
|
105885
|
+
}
|
|
105481
105886
|
// Exit pointer lock when deactivating
|
|
105482
105887
|
this._exitPointerLock();
|
|
105483
105888
|
// Remove listeners
|
|
105484
105889
|
document.removeEventListener('keydown', this._onKeyDown, true);
|
|
105485
105890
|
document.removeEventListener('keyup', this._onKeyUp, true);
|
|
105486
105891
|
document.removeEventListener('mousemove', this._onMouseMove);
|
|
105487
|
-
document.removeEventListener('
|
|
105892
|
+
document.removeEventListener('pointermove', this._onPointerMove, true);
|
|
105893
|
+
document.removeEventListener('mouseup', this._onMouseUp, true);
|
|
105894
|
+
document.removeEventListener('pointerup', this._onPointerUp, true);
|
|
105488
105895
|
const canvas = this.app?.graphicsDevice?.canvas;
|
|
105489
105896
|
if (canvas && this._onClickToLock) {
|
|
105490
|
-
canvas.removeEventListener('mousedown', this._onClickToLock);
|
|
105897
|
+
canvas.removeEventListener('mousedown', this._onClickToLock, true);
|
|
105898
|
+
}
|
|
105899
|
+
if (canvas && this._onPointerDownToLook) {
|
|
105900
|
+
canvas.removeEventListener('pointerdown', this._onPointerDownToLook, true);
|
|
105491
105901
|
}
|
|
105492
105902
|
};
|
|
105493
105903
|
FlyCamera.prototype.setConfig = function (config) {
|
|
@@ -105576,6 +105986,27 @@ function registerFlyCameraScript() {
|
|
|
105576
105986
|
this._yaw = (this._yaw - dx) % 360;
|
|
105577
105987
|
this._pitch = Math.max(-89, Math.min(89, this._pitch + dy));
|
|
105578
105988
|
};
|
|
105989
|
+
FlyCamera.prototype._handlePointerMove = function (e) {
|
|
105990
|
+
if (!this._isLooking || !this._isActive)
|
|
105991
|
+
return;
|
|
105992
|
+
const dx = (e.movementX || 0) * this.lookSensitivity;
|
|
105993
|
+
const dy = (e.movementY || 0) * this.lookSensitivity * (this.invertY ? 1 : -1);
|
|
105994
|
+
this._yaw = (this._yaw - dx) % 360;
|
|
105995
|
+
this._pitch = Math.max(-89, Math.min(89, this._pitch + dy));
|
|
105996
|
+
};
|
|
105997
|
+
FlyCamera.prototype._handlePointerDown = function (e) {
|
|
105998
|
+
if (!this._isActive)
|
|
105999
|
+
return;
|
|
106000
|
+
// Primary button enables look while held (no pointer lock)
|
|
106001
|
+
if (e.button === 0) {
|
|
106002
|
+
this._isLooking = true;
|
|
106003
|
+
}
|
|
106004
|
+
};
|
|
106005
|
+
FlyCamera.prototype._handlePointerUp = function (e) {
|
|
106006
|
+
if (e.button === 0) {
|
|
106007
|
+
this._isLooking = false;
|
|
106008
|
+
}
|
|
106009
|
+
};
|
|
105579
106010
|
FlyCamera.prototype._handlePointerLockChange = function () {
|
|
105580
106011
|
const canvas = this.app.graphicsDevice.canvas;
|
|
105581
106012
|
this._isPointerLocked = document.pointerLockElement === canvas;
|
|
@@ -105872,7 +106303,7 @@ const resolveUniforms = (scope, values) => {
|
|
|
105872
106303
|
scope.resolve(key).setValue(values[key]);
|
|
105873
106304
|
}
|
|
105874
106305
|
};
|
|
105875
|
-
const createBlueNoiseTexture = (device) => {
|
|
106306
|
+
const createBlueNoiseTexture$1 = (device) => {
|
|
105876
106307
|
const size = 32;
|
|
105877
106308
|
const texture = new Texture$1(device, {
|
|
105878
106309
|
width: size,
|
|
@@ -105951,7 +106382,7 @@ let InfiniteGrid$1 = class InfiniteGrid {
|
|
|
105951
106382
|
throw new Error('InfiniteGrid: QuadRender is not available in this PlayCanvas version.');
|
|
105952
106383
|
}
|
|
105953
106384
|
this.quadRender = new QuadRender$1(this.shader);
|
|
105954
|
-
this.blueNoiseTexture = createBlueNoiseTexture(device);
|
|
106385
|
+
this.blueNoiseTexture = createBlueNoiseTexture$1(device);
|
|
105955
106386
|
this._createBlendState();
|
|
105956
106387
|
this._registerRenderHook();
|
|
105957
106388
|
}
|
|
@@ -140317,51 +140748,161 @@ function toPcVec3(value, fallback) {
|
|
|
140317
140748
|
return fallback.clone();
|
|
140318
140749
|
}
|
|
140319
140750
|
|
|
140320
|
-
const
|
|
140321
|
-
|
|
140322
|
-
const
|
|
140323
|
-
|
|
140324
|
-
|
|
140751
|
+
const cache = new WeakMap();
|
|
140752
|
+
function createBlueNoiseTexture(device) {
|
|
140753
|
+
const size = 32;
|
|
140754
|
+
const texture = new Texture$1(device, {
|
|
140755
|
+
width: size,
|
|
140756
|
+
height: size,
|
|
140757
|
+
format: PIXELFORMAT_R8_G8_B8_A8,
|
|
140758
|
+
mipmaps: false,
|
|
140759
|
+
});
|
|
140760
|
+
texture.addressU = ADDRESS_REPEAT;
|
|
140761
|
+
texture.addressV = ADDRESS_REPEAT;
|
|
140762
|
+
texture.minFilter = FILTER_NEAREST;
|
|
140763
|
+
texture.magFilter = FILTER_NEAREST;
|
|
140764
|
+
const pixels = texture.lock();
|
|
140765
|
+
const seed = 1337;
|
|
140766
|
+
let value = seed;
|
|
140767
|
+
const random = () => {
|
|
140768
|
+
value ^= value << 13;
|
|
140769
|
+
value ^= value >>> 17;
|
|
140770
|
+
value ^= value << 5;
|
|
140771
|
+
return ((value >>> 0) % 256) / 255;
|
|
140772
|
+
};
|
|
140773
|
+
for (let i = 0; i < size * size; i++) {
|
|
140774
|
+
const noise = Math.floor(random() * 255);
|
|
140775
|
+
const idx = i * 4;
|
|
140776
|
+
pixels[idx + 0] = noise;
|
|
140777
|
+
pixels[idx + 1] = noise;
|
|
140778
|
+
pixels[idx + 2] = noise;
|
|
140779
|
+
pixels[idx + 3] = 255;
|
|
140780
|
+
}
|
|
140781
|
+
texture.unlock();
|
|
140782
|
+
texture.name = 'supersplat-blue-noise';
|
|
140783
|
+
return texture;
|
|
140784
|
+
}
|
|
140785
|
+
function getBlueNoiseTex32(device) {
|
|
140786
|
+
const existing = cache.get(device);
|
|
140787
|
+
if (existing)
|
|
140788
|
+
return existing;
|
|
140789
|
+
const tex = createBlueNoiseTexture(device);
|
|
140790
|
+
cache.set(device, tex);
|
|
140791
|
+
return tex;
|
|
140792
|
+
}
|
|
140325
140793
|
|
|
140326
|
-
|
|
140327
|
-
|
|
140794
|
+
// SuperSplat-like selection box shader: screen-space ray/box intersection that draws a thick
|
|
140795
|
+
// white wire/grid pattern (not dependent on WebGL line width).
|
|
140796
|
+
const BOX_SELECT_VS = /* glsl */ `
|
|
140797
|
+
attribute vec3 vertex_position;
|
|
140328
140798
|
|
|
140329
|
-
|
|
140799
|
+
uniform mat4 matrix_model;
|
|
140800
|
+
uniform mat4 matrix_viewProjection;
|
|
140330
140801
|
|
|
140331
|
-
void main(
|
|
140332
|
-
|
|
140333
|
-
|
|
140334
|
-
}
|
|
140802
|
+
void main() {
|
|
140803
|
+
gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
|
|
140804
|
+
}
|
|
140335
140805
|
`;
|
|
140336
|
-
const
|
|
140337
|
-
|
|
140338
|
-
|
|
140339
|
-
|
|
140806
|
+
const BOX_SELECT_FS = /* glsl */ `
|
|
140807
|
+
// ray-box intersection in box space
|
|
140808
|
+
bool intersectBox(out float t0, out float t1, out int axis0, out int axis1, vec3 pos, vec3 dir, vec3 boxCen, vec3 boxLen)
|
|
140809
|
+
{
|
|
140810
|
+
bvec3 validDir = notEqual(dir, vec3(0.0));
|
|
140811
|
+
vec3 absDir = abs(dir);
|
|
140812
|
+
vec3 signDir = sign(dir);
|
|
140813
|
+
vec3 m = vec3(
|
|
140814
|
+
validDir.x ? 1.0 / absDir.x : 0.0,
|
|
140815
|
+
validDir.y ? 1.0 / absDir.y : 0.0,
|
|
140816
|
+
validDir.z ? 1.0 / absDir.z : 0.0
|
|
140817
|
+
) * signDir;
|
|
140340
140818
|
|
|
140341
|
-
|
|
140819
|
+
vec3 n = m * (pos - boxCen);
|
|
140820
|
+
vec3 k = abs(m) * boxLen;
|
|
140342
140821
|
|
|
140343
|
-
|
|
140344
|
-
|
|
140345
|
-
uniform float opacity;
|
|
140822
|
+
vec3 v0 = -n - k;
|
|
140823
|
+
vec3 v1 = -n + k;
|
|
140346
140824
|
|
|
140347
|
-
|
|
140825
|
+
// replace invalid axes with -inf and +inf so the tests below ignore them
|
|
140826
|
+
v0 = mix(vec3(-1.0 / 0.0000001), v0, validDir);
|
|
140827
|
+
v1 = mix(vec3(1.0 / 0.0000001), v1, validDir);
|
|
140348
140828
|
|
|
140349
|
-
|
|
140350
|
-
|
|
140351
|
-
float d = min(min(vUv.x, 1.0 - vUv.x), min(vUv.y, 1.0 - vUv.y));
|
|
140829
|
+
axis0 = (v0.x > v0.y) ? ((v0.x > v0.z) ? 0 : 2) : ((v0.y > v0.z) ? 1 : 2);
|
|
140830
|
+
axis1 = (v1.x < v1.y) ? ((v1.x < v1.z) ? 0 : 2) : ((v1.y < v1.z) ? 1 : 2);
|
|
140352
140831
|
|
|
140353
|
-
|
|
140354
|
-
|
|
140355
|
-
#ifdef GL_OES_standard_derivatives
|
|
140356
|
-
aa = max(aa, fwidth(d) * 1.5);
|
|
140357
|
-
#endif
|
|
140358
|
-
float a = 1.0 - smoothstep(lineWidth, lineWidth + aa, d);
|
|
140359
|
-
a *= opacity;
|
|
140832
|
+
t0 = v0[axis0];
|
|
140833
|
+
t1 = v1[axis1];
|
|
140360
140834
|
|
|
140361
|
-
|
|
140362
|
-
|
|
140363
|
-
}
|
|
140835
|
+
if (t0 > t1 || t1 < 0.0) {
|
|
140836
|
+
return false;
|
|
140837
|
+
}
|
|
140838
|
+
|
|
140839
|
+
return true;
|
|
140840
|
+
}
|
|
140841
|
+
|
|
140842
|
+
float calcDepth(in vec3 pos, in mat4 viewProjection) {
|
|
140843
|
+
vec4 v = viewProjection * vec4(pos, 1.0);
|
|
140844
|
+
return (v.z / v.w) * 0.5 + 0.5;
|
|
140845
|
+
}
|
|
140846
|
+
|
|
140847
|
+
uniform sampler2D blueNoiseTex32;
|
|
140848
|
+
uniform mat4 matrix_viewProjection;
|
|
140849
|
+
uniform vec3 boxCen;
|
|
140850
|
+
uniform vec3 boxLen;
|
|
140851
|
+
|
|
140852
|
+
uniform vec3 near_origin;
|
|
140853
|
+
uniform vec3 near_x;
|
|
140854
|
+
uniform vec3 near_y;
|
|
140855
|
+
|
|
140856
|
+
uniform vec3 far_origin;
|
|
140857
|
+
uniform vec3 far_x;
|
|
140858
|
+
uniform vec3 far_y;
|
|
140859
|
+
|
|
140860
|
+
uniform vec2 targetSize;
|
|
140861
|
+
uniform vec3 lineColor;
|
|
140862
|
+
|
|
140863
|
+
bool writeDepth(float alpha) {
|
|
140864
|
+
ivec2 uv = ivec2(gl_FragCoord.xy);
|
|
140865
|
+
ivec2 size = textureSize(blueNoiseTex32, 0);
|
|
140866
|
+
return alpha > texelFetch(blueNoiseTex32, uv % size, 0).y;
|
|
140867
|
+
}
|
|
140868
|
+
|
|
140869
|
+
bool strips(vec3 pos, int axis) {
|
|
140870
|
+
// Thickness tuned to match SuperSplat viewer "thick wire"
|
|
140871
|
+
bvec3 b = lessThan(fract(pos * 2.0 + vec3(0.015)), vec3(0.06));
|
|
140872
|
+
b[axis] = false;
|
|
140873
|
+
return any(b);
|
|
140874
|
+
}
|
|
140875
|
+
|
|
140876
|
+
void main() {
|
|
140877
|
+
vec2 clip = gl_FragCoord.xy / targetSize;
|
|
140878
|
+
vec3 worldNear = near_origin + near_x * clip.x + near_y * clip.y;
|
|
140879
|
+
vec3 worldFar = far_origin + far_x * clip.x + far_y * clip.y;
|
|
140880
|
+
vec3 rayDir = normalize(worldFar - worldNear);
|
|
140881
|
+
|
|
140882
|
+
float t0, t1;
|
|
140883
|
+
int axis0, axis1;
|
|
140884
|
+
if (!intersectBox(t0, t1, axis0, axis1, worldNear, rayDir, boxCen, boxLen)) {
|
|
140885
|
+
discard;
|
|
140886
|
+
}
|
|
140887
|
+
|
|
140888
|
+
vec3 frontPos = worldNear + rayDir * t0;
|
|
140889
|
+
bool front = t0 > 0.0 && strips(frontPos - boxCen, axis0);
|
|
140890
|
+
|
|
140891
|
+
vec3 backPos = worldNear + rayDir * t1;
|
|
140892
|
+
bool back = strips(backPos - boxCen, axis1);
|
|
140893
|
+
|
|
140894
|
+
if (front) {
|
|
140895
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
140896
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(frontPos, matrix_viewProjection) : 1.0;
|
|
140897
|
+
} else if (back) {
|
|
140898
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
140899
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(backPos, matrix_viewProjection) : 1.0;
|
|
140900
|
+
} else {
|
|
140901
|
+
discard;
|
|
140902
|
+
}
|
|
140903
|
+
}
|
|
140364
140904
|
`;
|
|
140905
|
+
const tmpPos$1 = new Vec3();
|
|
140365
140906
|
class BoxSelectionAPI {
|
|
140366
140907
|
constructor() {
|
|
140367
140908
|
this.boxes = new Map();
|
|
@@ -140398,6 +140939,7 @@ class BoxSelectionAPI {
|
|
|
140398
140939
|
this.ensureTranslateGizmo();
|
|
140399
140940
|
this.updateGizmoSize();
|
|
140400
140941
|
this.updateGizmoAttachment();
|
|
140942
|
+
this.updateBoxShaderUniforms();
|
|
140401
140943
|
}
|
|
140402
140944
|
createBox(options = {}) {
|
|
140403
140945
|
if (!this.app || !this.parent) {
|
|
@@ -140420,7 +140962,7 @@ class BoxSelectionAPI {
|
|
|
140420
140962
|
entity.render.castShadows = false;
|
|
140421
140963
|
entity.render.receiveShadows = false;
|
|
140422
140964
|
entity.render.enabled = visible;
|
|
140423
|
-
// Render as a thick wireframe (shader-based)
|
|
140965
|
+
// Render as a thick wireframe (shader-based) to match SuperSplat viewer.
|
|
140424
140966
|
this.parent.addChild(entity);
|
|
140425
140967
|
const record = {
|
|
140426
140968
|
id,
|
|
@@ -140507,8 +141049,8 @@ class BoxSelectionAPI {
|
|
|
140507
141049
|
return false;
|
|
140508
141050
|
}
|
|
140509
141051
|
record.color.set(r, g, b);
|
|
140510
|
-
const mat =
|
|
140511
|
-
record.
|
|
141052
|
+
const mat = record.entity.render?.material;
|
|
141053
|
+
mat?.setParameter?.('lineColor', [record.color.x, record.color.y, record.color.z]);
|
|
140512
141054
|
this.requestRender();
|
|
140513
141055
|
return true;
|
|
140514
141056
|
}
|
|
@@ -140703,19 +141245,34 @@ class BoxSelectionAPI {
|
|
|
140703
141245
|
}
|
|
140704
141246
|
buildWireframeMaterial(color) {
|
|
140705
141247
|
const material = new ShaderMaterial$1({
|
|
140706
|
-
uniqueName: '
|
|
140707
|
-
vertexGLSL:
|
|
140708
|
-
fragmentGLSL:
|
|
141248
|
+
uniqueName: 'boxSelectionSupersplatWire',
|
|
141249
|
+
vertexGLSL: BOX_SELECT_VS,
|
|
141250
|
+
fragmentGLSL: BOX_SELECT_FS,
|
|
140709
141251
|
});
|
|
140710
|
-
material.cull =
|
|
140711
|
-
material.depthWrite = false;
|
|
141252
|
+
material.cull = CULLFACE_FRONT;
|
|
140712
141253
|
material.blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE_MINUS_SRC_ALPHA);
|
|
140713
|
-
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
140714
|
-
material.setParameter('lineWidth', THICK_WIREFRAME_LINE_WIDTH_UV$1);
|
|
140715
|
-
material.setParameter('opacity', THICK_WIREFRAME_OPACITY$1);
|
|
140716
141254
|
material.update();
|
|
141255
|
+
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
140717
141256
|
return material;
|
|
140718
141257
|
}
|
|
141258
|
+
updateBoxShaderUniforms() {
|
|
141259
|
+
if (!this.app)
|
|
141260
|
+
return;
|
|
141261
|
+
const device = this.app.graphicsDevice;
|
|
141262
|
+
// Ensure required global uniforms for selection shaders.
|
|
141263
|
+
device.scope.resolve('targetSize').setValue([device.width, device.height]);
|
|
141264
|
+
device.scope.resolve('blueNoiseTex32').setValue(getBlueNoiseTex32(device));
|
|
141265
|
+
for (const record of this.boxes.values()) {
|
|
141266
|
+
const mat = record.entity.render?.material;
|
|
141267
|
+
if (!mat?.setParameter)
|
|
141268
|
+
continue;
|
|
141269
|
+
// World-space center
|
|
141270
|
+
record.entity.getWorldTransform().getTranslation(tmpPos$1);
|
|
141271
|
+
mat.setParameter('boxCen', [tmpPos$1.x, tmpPos$1.y, tmpPos$1.z]);
|
|
141272
|
+
// Half extents
|
|
141273
|
+
mat.setParameter('boxLen', [record.lenX * 0.5, record.lenY * 0.5, record.lenZ * 0.5]);
|
|
141274
|
+
}
|
|
141275
|
+
}
|
|
140719
141276
|
emitEvent(event, detail) {
|
|
140720
141277
|
if (this.onEvent) {
|
|
140721
141278
|
this.onEvent(event, detail);
|
|
@@ -140727,59 +141284,110 @@ class BoxSelectionAPI {
|
|
|
140727
141284
|
}
|
|
140728
141285
|
}
|
|
140729
141286
|
|
|
140730
|
-
|
|
140731
|
-
|
|
140732
|
-
const
|
|
140733
|
-
|
|
140734
|
-
const THICK_WIREFRAME_SPHERE_VS = /* glsl */ `
|
|
140735
|
-
attribute vec3 vertex_position;
|
|
140736
|
-
attribute vec2 vertex_texCoord0;
|
|
140737
|
-
|
|
140738
|
-
uniform mat4 matrix_model;
|
|
140739
|
-
uniform mat4 matrix_viewProjection;
|
|
141287
|
+
// SuperSplat-like selection sphere shader: screen-space ray/sphere intersection that draws a thick
|
|
141288
|
+
// white wire/grid pattern (not dependent on WebGL line width).
|
|
141289
|
+
const SPHERE_SELECT_VS = /* glsl */ `
|
|
141290
|
+
attribute vec3 vertex_position;
|
|
140740
141291
|
|
|
140741
|
-
|
|
141292
|
+
uniform mat4 matrix_model;
|
|
141293
|
+
uniform mat4 matrix_viewProjection;
|
|
140742
141294
|
|
|
140743
|
-
void main(
|
|
140744
|
-
|
|
140745
|
-
|
|
140746
|
-
}
|
|
141295
|
+
void main() {
|
|
141296
|
+
gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
|
|
141297
|
+
}
|
|
140747
141298
|
`;
|
|
140748
|
-
const
|
|
140749
|
-
|
|
140750
|
-
|
|
140751
|
-
|
|
141299
|
+
const SPHERE_SELECT_FS = /* glsl */ `
|
|
141300
|
+
bool intersectSphere(out float t0, out float t1, vec3 pos, vec3 dir, vec4 sphere) {
|
|
141301
|
+
vec3 L = sphere.xyz - pos;
|
|
141302
|
+
float tca = dot(L, dir);
|
|
141303
|
+
|
|
141304
|
+
float d2 = sphere.w * sphere.w - (dot(L, L) - tca * tca);
|
|
141305
|
+
if (d2 <= 0.0) {
|
|
141306
|
+
return false;
|
|
141307
|
+
}
|
|
141308
|
+
|
|
141309
|
+
float thc = sqrt(d2);
|
|
141310
|
+
t0 = tca - thc;
|
|
141311
|
+
t1 = tca + thc;
|
|
141312
|
+
if (t1 <= 0.0) {
|
|
141313
|
+
return false;
|
|
141314
|
+
}
|
|
140752
141315
|
|
|
140753
|
-
|
|
141316
|
+
return true;
|
|
141317
|
+
}
|
|
140754
141318
|
|
|
140755
|
-
|
|
140756
|
-
|
|
140757
|
-
|
|
141319
|
+
float calcDepth(in vec3 pos, in mat4 viewProjection) {
|
|
141320
|
+
vec4 v = viewProjection * vec4(pos, 1.0);
|
|
141321
|
+
return (v.z / v.w) * 0.5 + 0.5;
|
|
141322
|
+
}
|
|
140758
141323
|
|
|
140759
|
-
|
|
141324
|
+
vec2 calcAzimuthElev(in vec3 dir) {
|
|
141325
|
+
float azimuth = atan(dir.z, dir.x);
|
|
141326
|
+
float elev = asin(dir.y);
|
|
141327
|
+
return vec2(azimuth, elev) * 180.0 / 3.14159;
|
|
141328
|
+
}
|
|
140760
141329
|
|
|
140761
|
-
|
|
140762
|
-
|
|
140763
|
-
|
|
140764
|
-
|
|
141330
|
+
uniform sampler2D blueNoiseTex32;
|
|
141331
|
+
uniform mat4 matrix_viewProjection;
|
|
141332
|
+
uniform vec4 sphere;
|
|
141333
|
+
uniform vec3 lineColor;
|
|
140765
141334
|
|
|
140766
|
-
|
|
140767
|
-
|
|
140768
|
-
|
|
140769
|
-
float dv = gridLineDist(vUv.y, ${SPHERE_GRID_V});
|
|
140770
|
-
float d = min(du, dv);
|
|
140771
|
-
|
|
140772
|
-
float aa = 0.002;
|
|
140773
|
-
#ifdef GL_OES_standard_derivatives
|
|
140774
|
-
aa = max(aa, fwidth(d) * 1.5);
|
|
140775
|
-
#endif
|
|
140776
|
-
float a = 1.0 - smoothstep(lineWidth, lineWidth + aa, d);
|
|
140777
|
-
a *= opacity;
|
|
141335
|
+
uniform vec3 near_origin;
|
|
141336
|
+
uniform vec3 near_x;
|
|
141337
|
+
uniform vec3 near_y;
|
|
140778
141338
|
|
|
140779
|
-
|
|
140780
|
-
|
|
140781
|
-
|
|
141339
|
+
uniform vec3 far_origin;
|
|
141340
|
+
uniform vec3 far_x;
|
|
141341
|
+
uniform vec3 far_y;
|
|
141342
|
+
|
|
141343
|
+
uniform vec2 targetSize;
|
|
141344
|
+
|
|
141345
|
+
bool writeDepth(float alpha) {
|
|
141346
|
+
vec2 uv = fract(gl_FragCoord.xy / 32.0);
|
|
141347
|
+
float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
|
|
141348
|
+
return alpha > noise;
|
|
141349
|
+
}
|
|
141350
|
+
|
|
141351
|
+
bool strips(vec3 lp) {
|
|
141352
|
+
vec2 ae = calcAzimuthElev(normalize(lp));
|
|
141353
|
+
|
|
141354
|
+
float spacing = 180.0 / (2.0 * 3.14159 * sphere.w);
|
|
141355
|
+
// Thickness tuned to match SuperSplat viewer "thick wire"
|
|
141356
|
+
float size = 0.06;
|
|
141357
|
+
return fract(ae.x / spacing) < size ||
|
|
141358
|
+
fract(ae.y / spacing) < size;
|
|
141359
|
+
}
|
|
141360
|
+
|
|
141361
|
+
void main() {
|
|
141362
|
+
vec2 clip = gl_FragCoord.xy / targetSize;
|
|
141363
|
+
vec3 worldNear = near_origin + near_x * clip.x + near_y * clip.y;
|
|
141364
|
+
vec3 worldFar = far_origin + far_x * clip.x + far_y * clip.y;
|
|
141365
|
+
|
|
141366
|
+
vec3 rayDir = normalize(worldFar - worldNear);
|
|
141367
|
+
|
|
141368
|
+
float t0, t1;
|
|
141369
|
+
if (!intersectSphere(t0, t1, worldNear, rayDir, sphere)) {
|
|
141370
|
+
discard;
|
|
141371
|
+
}
|
|
141372
|
+
|
|
141373
|
+
vec3 frontPos = worldNear + rayDir * t0;
|
|
141374
|
+
bool front = t0 > 0.0 && strips(frontPos - sphere.xyz);
|
|
141375
|
+
|
|
141376
|
+
vec3 backPos = worldNear + rayDir * t1;
|
|
141377
|
+
bool back = strips(backPos - sphere.xyz);
|
|
141378
|
+
|
|
141379
|
+
if (front) {
|
|
141380
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
141381
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(frontPos, matrix_viewProjection) : 1.0;
|
|
141382
|
+
} else if (back) {
|
|
141383
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
141384
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(backPos, matrix_viewProjection) : 1.0;
|
|
141385
|
+
} else {
|
|
141386
|
+
discard;
|
|
141387
|
+
}
|
|
141388
|
+
}
|
|
140782
141389
|
`;
|
|
141390
|
+
const tmpPos = new Vec3();
|
|
140783
141391
|
class SphereSelectionAPI {
|
|
140784
141392
|
constructor() {
|
|
140785
141393
|
this.spheres = new Map();
|
|
@@ -140815,6 +141423,7 @@ class SphereSelectionAPI {
|
|
|
140815
141423
|
this.ensureTranslateGizmo();
|
|
140816
141424
|
this.updateGizmoSize();
|
|
140817
141425
|
this.updateGizmoAttachment();
|
|
141426
|
+
this.updateSphereShaderUniforms();
|
|
140818
141427
|
}
|
|
140819
141428
|
createSphere(options = {}) {
|
|
140820
141429
|
if (!this.app || !this.parent) {
|
|
@@ -140827,7 +141436,8 @@ class SphereSelectionAPI {
|
|
|
140827
141436
|
const visible = options.visible ?? true;
|
|
140828
141437
|
const entity = new Entity(id);
|
|
140829
141438
|
entity.addComponent('render', {
|
|
140830
|
-
|
|
141439
|
+
// Use a box proxy like SuperSplat; shader ray-marches a true sphere.
|
|
141440
|
+
type: 'box',
|
|
140831
141441
|
material: this.buildWireframeMaterial(color),
|
|
140832
141442
|
});
|
|
140833
141443
|
entity.setLocalScale(radius * 2, radius * 2, radius * 2); // sphere diameter = radius * 2
|
|
@@ -140835,7 +141445,7 @@ class SphereSelectionAPI {
|
|
|
140835
141445
|
entity.render.castShadows = false;
|
|
140836
141446
|
entity.render.receiveShadows = false;
|
|
140837
141447
|
entity.render.enabled = visible;
|
|
140838
|
-
// Render as a thick wireframe (shader-based)
|
|
141448
|
+
// Render as a thick wireframe (shader-based) to match SuperSplat viewer.
|
|
140839
141449
|
this.parent.addChild(entity);
|
|
140840
141450
|
const record = {
|
|
140841
141451
|
id,
|
|
@@ -140918,8 +141528,12 @@ class SphereSelectionAPI {
|
|
|
140918
141528
|
return false;
|
|
140919
141529
|
}
|
|
140920
141530
|
record.color.set(r, g, b);
|
|
140921
|
-
const mat =
|
|
140922
|
-
|
|
141531
|
+
const mat = record.entity.render?.material;
|
|
141532
|
+
mat?.setParameter?.('lineColor', [
|
|
141533
|
+
record.color.x,
|
|
141534
|
+
record.color.y,
|
|
141535
|
+
record.color.z,
|
|
141536
|
+
]);
|
|
140923
141537
|
this.requestRender();
|
|
140924
141538
|
return true;
|
|
140925
141539
|
}
|
|
@@ -141117,19 +141731,30 @@ class SphereSelectionAPI {
|
|
|
141117
141731
|
}
|
|
141118
141732
|
buildWireframeMaterial(color) {
|
|
141119
141733
|
const material = new ShaderMaterial$1({
|
|
141120
|
-
uniqueName: '
|
|
141121
|
-
vertexGLSL:
|
|
141122
|
-
fragmentGLSL:
|
|
141734
|
+
uniqueName: 'sphereSelectionSupersplatWire',
|
|
141735
|
+
vertexGLSL: SPHERE_SELECT_VS,
|
|
141736
|
+
fragmentGLSL: SPHERE_SELECT_FS,
|
|
141123
141737
|
});
|
|
141124
|
-
material.cull =
|
|
141125
|
-
material.depthWrite = false;
|
|
141738
|
+
material.cull = CULLFACE_FRONT;
|
|
141126
141739
|
material.blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE_MINUS_SRC_ALPHA);
|
|
141127
|
-
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
141128
|
-
material.setParameter('lineWidth', THICK_WIREFRAME_LINE_WIDTH_UV);
|
|
141129
|
-
material.setParameter('opacity', THICK_WIREFRAME_OPACITY);
|
|
141130
141740
|
material.update();
|
|
141741
|
+
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
141131
141742
|
return material;
|
|
141132
141743
|
}
|
|
141744
|
+
updateSphereShaderUniforms() {
|
|
141745
|
+
if (!this.app)
|
|
141746
|
+
return;
|
|
141747
|
+
const device = this.app.graphicsDevice;
|
|
141748
|
+
device.scope.resolve('targetSize').setValue([device.width, device.height]);
|
|
141749
|
+
device.scope.resolve('blueNoiseTex32').setValue(getBlueNoiseTex32(device));
|
|
141750
|
+
for (const record of this.spheres.values()) {
|
|
141751
|
+
const mat = record.entity.render?.material;
|
|
141752
|
+
if (!mat?.setParameter)
|
|
141753
|
+
continue;
|
|
141754
|
+
record.entity.getWorldTransform().getTranslation(tmpPos);
|
|
141755
|
+
mat.setParameter('sphere', [tmpPos.x, tmpPos.y, tmpPos.z, record.radius]);
|
|
141756
|
+
}
|
|
141757
|
+
}
|
|
141133
141758
|
emitEvent(event, detail) {
|
|
141134
141759
|
if (this.onEvent) {
|
|
141135
141760
|
this.onEvent(event, detail);
|
|
@@ -146756,7 +147381,7 @@ class SupersplatAdapter {
|
|
|
146756
147381
|
const { config } = this.scene;
|
|
146757
147382
|
const state = this.viewerEventState;
|
|
146758
147383
|
// Colors and view settings from scene config
|
|
146759
|
-
const selectedClr = config.selectedClr ?? { r: 1, g:
|
|
147384
|
+
const selectedClr = config.selectedClr ?? { r: 1, g: 0.5, b: 0, a: 1 };
|
|
146760
147385
|
const unselectedClr = config.unselectedClr ?? { r: 0, g: 0, b: 1, a: 0.5 };
|
|
146761
147386
|
const lockedClr = config.lockedClr ?? { r: 0, g: 0, b: 0, a: 0.05 };
|
|
146762
147387
|
const bgClr = config.bgClr ?? { r: 0, g: 0, b: 0, a: 1 };
|
|
@@ -147190,9 +147815,10 @@ class SplatViewerCore {
|
|
|
147190
147815
|
this.app = ctx.app;
|
|
147191
147816
|
this.entities.camera = ctx.camera;
|
|
147192
147817
|
const cameraAny = this.entities.camera;
|
|
147193
|
-
|
|
147194
|
-
|
|
147195
|
-
|
|
147818
|
+
// SuperSplat's PCApp omits ScriptComponentSystem, so `addComponent('script')`
|
|
147819
|
+
// will not produce `camera.script.create()`. We keep the attempt (in case
|
|
147820
|
+
// the underlying app changes), but also support a controller-based fallback.
|
|
147821
|
+
if (cameraAny && !cameraAny.script && typeof cameraAny.addComponent === 'function') {
|
|
147196
147822
|
try {
|
|
147197
147823
|
cameraAny.addComponent('script');
|
|
147198
147824
|
}
|
|
@@ -147215,16 +147841,23 @@ class SplatViewerCore {
|
|
|
147215
147841
|
minHeight: DEFAULT_FLY_CAMERA_CONFIG.minHeight,
|
|
147216
147842
|
maxHeight: DEFAULT_FLY_CAMERA_CONFIG.maxHeight,
|
|
147217
147843
|
};
|
|
147218
|
-
|
|
147219
|
-
|
|
147220
|
-
|
|
147221
|
-
|
|
147222
|
-
;
|
|
147223
|
-
this._fly
|
|
147224
|
-
|
|
147225
|
-
|
|
147226
|
-
|
|
147227
|
-
|
|
147844
|
+
// Prefer script-based fly when available; fallback to controller otherwise.
|
|
147845
|
+
const canCreateScript = typeof cameraAny?.script?.create === 'function';
|
|
147846
|
+
if (canCreateScript) {
|
|
147847
|
+
const created = cameraAny.script.create('flyCamera', { attributes: flyAttributes });
|
|
147848
|
+
this._fly = created;
|
|
147849
|
+
if (this._fly) {
|
|
147850
|
+
;
|
|
147851
|
+
this._fly.emitFlyEvent = (type, detail) => {
|
|
147852
|
+
this.emit({ type: type, detail });
|
|
147853
|
+
};
|
|
147854
|
+
this._fly.deactivate?.();
|
|
147855
|
+
}
|
|
147856
|
+
}
|
|
147857
|
+
else {
|
|
147858
|
+
const controller = new FlyCameraController(ctx.app, cameraAny, (type, detail) => this.emit({ type: type, detail }), flyAttributes);
|
|
147859
|
+
controller.deactivate();
|
|
147860
|
+
this._fly = controller;
|
|
147228
147861
|
}
|
|
147229
147862
|
this._cameraMode = 'orbit';
|
|
147230
147863
|
}
|
|
@@ -149093,17 +149726,31 @@ class SplatViewerCore {
|
|
|
149093
149726
|
// Stop supersplat orbit updates + input
|
|
149094
149727
|
this._supersplat.setCameraControlsEnabled(false);
|
|
149095
149728
|
this._supersplat.setCameraManualControl(true);
|
|
149096
|
-
//
|
|
149097
|
-
|
|
149098
|
-
|
|
149099
|
-
if (
|
|
149100
|
-
;
|
|
149101
|
-
|
|
149102
|
-
|
|
149729
|
+
// Preserve camera position and rotation when switching to fly mode
|
|
149730
|
+
if (this._fly) {
|
|
149731
|
+
// For FlyCameraController (fallback path)
|
|
149732
|
+
if (typeof this._fly.syncFromEntity === 'function') {
|
|
149733
|
+
this._fly.syncFromEntity();
|
|
149734
|
+
}
|
|
149735
|
+
else {
|
|
149736
|
+
// For FlyCameraScript (legacy path)
|
|
149737
|
+
try {
|
|
149738
|
+
const pos = this.entities.camera?.getPosition?.();
|
|
149739
|
+
if (pos) {
|
|
149740
|
+
const posVec = pos.clone ? pos.clone() : new Vec3(pos.x || 0, pos.y || 0, pos.z || 0);
|
|
149741
|
+
this.entities.camera?.setPosition?.(posVec);
|
|
149742
|
+
}
|
|
149743
|
+
const euler = this.entities.camera?.getEulerAngles?.();
|
|
149744
|
+
if (euler) {
|
|
149745
|
+
;
|
|
149746
|
+
this._fly._pitch = euler.x || 0;
|
|
149747
|
+
this._fly._yaw = euler.y || 0;
|
|
149748
|
+
}
|
|
149749
|
+
}
|
|
149750
|
+
catch {
|
|
149751
|
+
// ignore
|
|
149752
|
+
}
|
|
149103
149753
|
}
|
|
149104
|
-
}
|
|
149105
|
-
catch {
|
|
149106
|
-
// ignore
|
|
149107
149754
|
}
|
|
149108
149755
|
this._fly?.activate?.();
|
|
149109
149756
|
this._cameraMode = 'fly';
|
|
@@ -149220,9 +149867,48 @@ class SplatViewerCore {
|
|
|
149220
149867
|
this._cameraModeManager?.setFlyConfig(config);
|
|
149221
149868
|
}
|
|
149222
149869
|
getFlyCameraConfig() {
|
|
149870
|
+
// SuperSplat path: fly is either a script instance or controller fallback
|
|
149871
|
+
if (this._supersplat && this._fly) {
|
|
149872
|
+
try {
|
|
149873
|
+
const flyAny = this._fly;
|
|
149874
|
+
const cfg = {
|
|
149875
|
+
moveSpeed: flyAny.moveSpeed,
|
|
149876
|
+
fastSpeedMultiplier: flyAny.fastSpeedMultiplier,
|
|
149877
|
+
slowSpeedMultiplier: flyAny.slowSpeedMultiplier,
|
|
149878
|
+
lookSensitivity: flyAny.lookSensitivity,
|
|
149879
|
+
invertY: !!flyAny.invertY,
|
|
149880
|
+
keyBindings: { ...(flyAny.keyBindings || {}) },
|
|
149881
|
+
smoothing: flyAny.smoothing,
|
|
149882
|
+
friction: flyAny.friction,
|
|
149883
|
+
enableCollision: !!flyAny.enableCollision,
|
|
149884
|
+
minHeight: flyAny.minHeight ?? null,
|
|
149885
|
+
maxHeight: flyAny.maxHeight ?? null,
|
|
149886
|
+
};
|
|
149887
|
+
return cfg;
|
|
149888
|
+
}
|
|
149889
|
+
catch {
|
|
149890
|
+
return null;
|
|
149891
|
+
}
|
|
149892
|
+
}
|
|
149223
149893
|
return this._cameraModeManager?.getFlyConfig() || null;
|
|
149224
149894
|
}
|
|
149225
149895
|
getFlyCameraState() {
|
|
149896
|
+
// SuperSplat path: fly is either a script instance or controller fallback
|
|
149897
|
+
if (this._supersplat && this._fly?.getState) {
|
|
149898
|
+
try {
|
|
149899
|
+
const state = this._fly.getState();
|
|
149900
|
+
return {
|
|
149901
|
+
mode: this._cameraMode,
|
|
149902
|
+
position: state.position,
|
|
149903
|
+
rotation: state.rotation,
|
|
149904
|
+
velocity: state.velocity,
|
|
149905
|
+
isMoving: state.isMoving,
|
|
149906
|
+
};
|
|
149907
|
+
}
|
|
149908
|
+
catch {
|
|
149909
|
+
return null;
|
|
149910
|
+
}
|
|
149911
|
+
}
|
|
149226
149912
|
return this._cameraModeManager?.getFlyState() || null;
|
|
149227
149913
|
}
|
|
149228
149914
|
_setupScene() {
|