@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
|
@@ -105235,14 +105235,24 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105235
105235
|
activateFlyMode() {
|
|
105236
105236
|
if (!this.fly)
|
|
105237
105237
|
return;
|
|
105238
|
-
|
|
105239
|
-
this.fly.activate();
|
|
105240
|
-
// Align fly camera internal orientation with the current camera rotation so that
|
|
105241
|
-
// switching from orbit -> fly does not snap the view back to the initial direction.
|
|
105238
|
+
// Preserve camera position and rotation when switching to fly mode
|
|
105242
105239
|
try {
|
|
105240
|
+
// Preserve position
|
|
105241
|
+
const pos = this.camera.getPosition
|
|
105242
|
+
? this.camera.getPosition().clone()
|
|
105243
|
+
: this.camera.getLocalPosition
|
|
105244
|
+
? this.camera.getLocalPosition().clone()
|
|
105245
|
+
: null;
|
|
105246
|
+
if (pos && this.camera.setPosition) {
|
|
105247
|
+
;
|
|
105248
|
+
this.camera.setPosition(pos);
|
|
105249
|
+
}
|
|
105250
|
+
// Preserve rotation (convert Euler to pitch/yaw)
|
|
105243
105251
|
const euler = this.camera.getEulerAngles
|
|
105244
105252
|
? this.camera.getEulerAngles()
|
|
105245
|
-
:
|
|
105253
|
+
: this.camera.getLocalEulerAngles
|
|
105254
|
+
? this.camera.getLocalEulerAngles()
|
|
105255
|
+
: null;
|
|
105246
105256
|
if (euler) {
|
|
105247
105257
|
// These properties are part of the FlyCamera runtime state
|
|
105248
105258
|
;
|
|
@@ -105253,6 +105263,8 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105253
105263
|
catch {
|
|
105254
105264
|
// Best-effort sync; ignore if camera or script API differs
|
|
105255
105265
|
}
|
|
105266
|
+
if (typeof this.fly.activate === 'function')
|
|
105267
|
+
this.fly.activate();
|
|
105256
105268
|
}
|
|
105257
105269
|
deactivateFlyMode() {
|
|
105258
105270
|
if (!this.fly)
|
|
@@ -105262,6 +105274,373 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105262
105274
|
}
|
|
105263
105275
|
}
|
|
105264
105276
|
|
|
105277
|
+
/**
|
|
105278
|
+
* Fly camera controller for environments where PlayCanvas ScriptComponentSystem is not available
|
|
105279
|
+
* (e.g. supersplat-core's custom PCApp, which omits ScriptComponentSystem).
|
|
105280
|
+
*
|
|
105281
|
+
* This controller attaches DOM input listeners and updates the camera entity via `app.on('update')`.
|
|
105282
|
+
*/
|
|
105283
|
+
class FlyCameraController {
|
|
105284
|
+
constructor(app, entity, emitFlyEvent, config) {
|
|
105285
|
+
// Config
|
|
105286
|
+
this.moveSpeed = 5.0;
|
|
105287
|
+
this.fastSpeedMultiplier = 3.0;
|
|
105288
|
+
this.slowSpeedMultiplier = 0.3;
|
|
105289
|
+
this.lookSensitivity = 0.2;
|
|
105290
|
+
this.invertY = false;
|
|
105291
|
+
this.keyBindings = {
|
|
105292
|
+
forward: 'KeyW',
|
|
105293
|
+
backward: 'KeyS',
|
|
105294
|
+
left: 'KeyA',
|
|
105295
|
+
right: 'KeyD',
|
|
105296
|
+
up: 'KeyE',
|
|
105297
|
+
down: 'KeyQ',
|
|
105298
|
+
fastMove: 'ShiftLeft',
|
|
105299
|
+
slowMove: 'ControlLeft',
|
|
105300
|
+
};
|
|
105301
|
+
this.smoothing = 0.8;
|
|
105302
|
+
this.friction = 0.85;
|
|
105303
|
+
this.enableCollision = false;
|
|
105304
|
+
this.minHeight = null;
|
|
105305
|
+
this.maxHeight = null;
|
|
105306
|
+
this._isActive = true;
|
|
105307
|
+
this._isPointerLocked = false;
|
|
105308
|
+
this._isLooking = false;
|
|
105309
|
+
this._pressed = new Set();
|
|
105310
|
+
this._velocity = new Vec3(0, 0, 0);
|
|
105311
|
+
this._targetVelocity = new Vec3(0, 0, 0);
|
|
105312
|
+
this._pitch = 0;
|
|
105313
|
+
this._yaw = 0;
|
|
105314
|
+
this._lastMoveEmitTime = 0;
|
|
105315
|
+
this._lastLookEmitTime = 0;
|
|
105316
|
+
this._updateHandler = null;
|
|
105317
|
+
this.app = app;
|
|
105318
|
+
this.entity = entity;
|
|
105319
|
+
this.emitFlyEvent = emitFlyEvent;
|
|
105320
|
+
if (config) {
|
|
105321
|
+
this.setConfig(config);
|
|
105322
|
+
}
|
|
105323
|
+
// Sync initial yaw/pitch from entity orientation if available
|
|
105324
|
+
try {
|
|
105325
|
+
const euler = this.entity?.getEulerAngles?.();
|
|
105326
|
+
if (euler) {
|
|
105327
|
+
this._pitch = euler.x || 0;
|
|
105328
|
+
this._yaw = euler.y || 0;
|
|
105329
|
+
}
|
|
105330
|
+
}
|
|
105331
|
+
catch {
|
|
105332
|
+
// ignore
|
|
105333
|
+
}
|
|
105334
|
+
this._bindInputListeners();
|
|
105335
|
+
}
|
|
105336
|
+
_bindInputListeners() {
|
|
105337
|
+
// Keyboard (capture phase so we see keys even when other handlers run)
|
|
105338
|
+
this._onKeyDown = this._handleKeyDown.bind(this);
|
|
105339
|
+
this._onKeyUp = this._handleKeyUp.bind(this);
|
|
105340
|
+
document.addEventListener('keydown', this._onKeyDown, true);
|
|
105341
|
+
document.addEventListener('keyup', this._onKeyUp, true);
|
|
105342
|
+
// Look: pointer events primary, mouse fallback
|
|
105343
|
+
this._onMouseMove = this._handleMouseMove.bind(this);
|
|
105344
|
+
this._onPointerMove = this._handlePointerMove.bind(this);
|
|
105345
|
+
document.addEventListener('mousemove', this._onMouseMove);
|
|
105346
|
+
document.addEventListener('pointermove', this._onPointerMove, true);
|
|
105347
|
+
const canvas = this.app?.graphicsDevice?.canvas;
|
|
105348
|
+
this._onMouseDown = (e) => {
|
|
105349
|
+
if (e.button === 0 && this._isActive)
|
|
105350
|
+
this._isLooking = true;
|
|
105351
|
+
};
|
|
105352
|
+
this._onPointerDown = (e) => {
|
|
105353
|
+
if (e.button === 0 && this._isActive) {
|
|
105354
|
+
this._isLooking = true;
|
|
105355
|
+
}
|
|
105356
|
+
};
|
|
105357
|
+
canvas?.addEventListener('mousedown', this._onMouseDown, true);
|
|
105358
|
+
canvas?.addEventListener('pointerdown', this._onPointerDown, true);
|
|
105359
|
+
this._onMouseUp = (e) => {
|
|
105360
|
+
if (e.button === 0)
|
|
105361
|
+
this._isLooking = false;
|
|
105362
|
+
};
|
|
105363
|
+
this._onPointerUp = (e) => {
|
|
105364
|
+
if (e.button === 0) {
|
|
105365
|
+
this._isLooking = false;
|
|
105366
|
+
}
|
|
105367
|
+
};
|
|
105368
|
+
document.addEventListener('mouseup', this._onMouseUp, true);
|
|
105369
|
+
document.addEventListener('pointerup', this._onPointerUp, true);
|
|
105370
|
+
}
|
|
105371
|
+
_unbindInputListeners() {
|
|
105372
|
+
document.removeEventListener('keydown', this._onKeyDown, true);
|
|
105373
|
+
document.removeEventListener('keyup', this._onKeyUp, true);
|
|
105374
|
+
document.removeEventListener('mousemove', this._onMouseMove);
|
|
105375
|
+
document.removeEventListener('pointermove', this._onPointerMove, true);
|
|
105376
|
+
document.removeEventListener('mouseup', this._onMouseUp, true);
|
|
105377
|
+
document.removeEventListener('pointerup', this._onPointerUp, true);
|
|
105378
|
+
const canvas = this.app?.graphicsDevice?.canvas;
|
|
105379
|
+
if (canvas && this._onMouseDown) {
|
|
105380
|
+
canvas.removeEventListener('mousedown', this._onMouseDown, true);
|
|
105381
|
+
}
|
|
105382
|
+
if (canvas && this._onPointerDown) {
|
|
105383
|
+
canvas.removeEventListener('pointerdown', this._onPointerDown, true);
|
|
105384
|
+
}
|
|
105385
|
+
}
|
|
105386
|
+
/**
|
|
105387
|
+
* Sync position and rotation from the camera entity.
|
|
105388
|
+
* Called when switching from orbit to fly mode to preserve camera state.
|
|
105389
|
+
*/
|
|
105390
|
+
syncFromEntity() {
|
|
105391
|
+
try {
|
|
105392
|
+
// Preserve position
|
|
105393
|
+
const pos = this.entity?.getPosition?.() || this.entity?.getLocalPosition?.();
|
|
105394
|
+
if (pos) {
|
|
105395
|
+
const posVec = pos.clone ? pos.clone() : new Vec3(pos.x || 0, pos.y || 0, pos.z || 0);
|
|
105396
|
+
this.entity?.setPosition?.(posVec);
|
|
105397
|
+
this.entity?.setLocalPosition?.(posVec);
|
|
105398
|
+
}
|
|
105399
|
+
// Preserve rotation (convert Euler to pitch/yaw)
|
|
105400
|
+
const euler = this.entity?.getEulerAngles?.() || this.entity?.getLocalEulerAngles?.();
|
|
105401
|
+
if (euler) {
|
|
105402
|
+
// PlayCanvas Euler: x=pitch, y=yaw, z=roll
|
|
105403
|
+
this._pitch = euler.x || 0;
|
|
105404
|
+
this._yaw = euler.y || 0;
|
|
105405
|
+
// Apply rotation immediately so view doesn't snap
|
|
105406
|
+
if (this.entity?.setLocalEulerAngles) {
|
|
105407
|
+
this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
|
|
105408
|
+
}
|
|
105409
|
+
else if (this.entity?.setEulerAngles) {
|
|
105410
|
+
this.entity.setEulerAngles(this._pitch, this._yaw, 0);
|
|
105411
|
+
}
|
|
105412
|
+
}
|
|
105413
|
+
}
|
|
105414
|
+
catch {
|
|
105415
|
+
// ignore
|
|
105416
|
+
}
|
|
105417
|
+
}
|
|
105418
|
+
activate() {
|
|
105419
|
+
if (this._isActive)
|
|
105420
|
+
return;
|
|
105421
|
+
this._isActive = true;
|
|
105422
|
+
// Sync position and rotation from current camera state before activating
|
|
105423
|
+
this.syncFromEntity();
|
|
105424
|
+
if (!this._updateHandler) {
|
|
105425
|
+
this._updateHandler = (dt) => this.update(dt);
|
|
105426
|
+
this.app?.on?.('update', this._updateHandler);
|
|
105427
|
+
}
|
|
105428
|
+
}
|
|
105429
|
+
deactivate() {
|
|
105430
|
+
if (!this._isActive)
|
|
105431
|
+
return;
|
|
105432
|
+
this._isActive = false;
|
|
105433
|
+
this._isLooking = false;
|
|
105434
|
+
this._pressed.clear();
|
|
105435
|
+
if (this._updateHandler) {
|
|
105436
|
+
try {
|
|
105437
|
+
this.app?.off?.('update', this._updateHandler);
|
|
105438
|
+
}
|
|
105439
|
+
catch {
|
|
105440
|
+
// ignore
|
|
105441
|
+
}
|
|
105442
|
+
this._updateHandler = null;
|
|
105443
|
+
}
|
|
105444
|
+
}
|
|
105445
|
+
destroy() {
|
|
105446
|
+
try {
|
|
105447
|
+
this.deactivate();
|
|
105448
|
+
}
|
|
105449
|
+
finally {
|
|
105450
|
+
this._unbindInputListeners();
|
|
105451
|
+
}
|
|
105452
|
+
}
|
|
105453
|
+
setConfig(config) {
|
|
105454
|
+
if (config.moveSpeed !== undefined)
|
|
105455
|
+
this.moveSpeed = config.moveSpeed;
|
|
105456
|
+
if (config.fastSpeedMultiplier !== undefined)
|
|
105457
|
+
this.fastSpeedMultiplier = config.fastSpeedMultiplier;
|
|
105458
|
+
if (config.slowSpeedMultiplier !== undefined)
|
|
105459
|
+
this.slowSpeedMultiplier = config.slowSpeedMultiplier;
|
|
105460
|
+
if (config.lookSensitivity !== undefined)
|
|
105461
|
+
this.lookSensitivity = config.lookSensitivity;
|
|
105462
|
+
if (config.invertY !== undefined)
|
|
105463
|
+
this.invertY = config.invertY;
|
|
105464
|
+
if (config.keyBindings !== undefined)
|
|
105465
|
+
this.keyBindings = config.keyBindings;
|
|
105466
|
+
if (config.smoothing !== undefined)
|
|
105467
|
+
this.smoothing = config.smoothing;
|
|
105468
|
+
if (config.friction !== undefined)
|
|
105469
|
+
this.friction = config.friction;
|
|
105470
|
+
if (config.enableCollision !== undefined)
|
|
105471
|
+
this.enableCollision = config.enableCollision;
|
|
105472
|
+
if (config.minHeight !== undefined)
|
|
105473
|
+
this.minHeight = config.minHeight;
|
|
105474
|
+
if (config.maxHeight !== undefined)
|
|
105475
|
+
this.maxHeight = config.maxHeight;
|
|
105476
|
+
}
|
|
105477
|
+
getState() {
|
|
105478
|
+
const pos = this.entity?.getPosition?.();
|
|
105479
|
+
return {
|
|
105480
|
+
position: { x: pos?.x ?? 0, y: pos?.y ?? 0, z: pos?.z ?? 0 },
|
|
105481
|
+
rotation: { pitch: this._pitch, yaw: this._yaw },
|
|
105482
|
+
velocity: { x: this._velocity.x, y: this._velocity.y, z: this._velocity.z },
|
|
105483
|
+
isMoving: Math.abs(this._velocity.x) +
|
|
105484
|
+
Math.abs(this._velocity.y) +
|
|
105485
|
+
Math.abs(this._velocity.z) >
|
|
105486
|
+
1e-4,
|
|
105487
|
+
};
|
|
105488
|
+
}
|
|
105489
|
+
_handleKeyDown(e) {
|
|
105490
|
+
const keys = [];
|
|
105491
|
+
if (e.code)
|
|
105492
|
+
keys.push(e.code);
|
|
105493
|
+
if (e.key) {
|
|
105494
|
+
keys.push(e.key);
|
|
105495
|
+
if (e.key.length === 1) {
|
|
105496
|
+
keys.push(`Key${e.key.toUpperCase()}`);
|
|
105497
|
+
keys.push(e.key.toUpperCase());
|
|
105498
|
+
keys.push(e.key.toLowerCase());
|
|
105499
|
+
}
|
|
105500
|
+
}
|
|
105501
|
+
for (const k of keys)
|
|
105502
|
+
this._pressed.add(k);
|
|
105503
|
+
}
|
|
105504
|
+
_handleKeyUp(e) {
|
|
105505
|
+
const keys = [];
|
|
105506
|
+
if (e.code)
|
|
105507
|
+
keys.push(e.code);
|
|
105508
|
+
if (e.key) {
|
|
105509
|
+
keys.push(e.key);
|
|
105510
|
+
if (e.key.length === 1) {
|
|
105511
|
+
keys.push(`Key${e.key.toUpperCase()}`);
|
|
105512
|
+
keys.push(e.key.toUpperCase());
|
|
105513
|
+
keys.push(e.key.toLowerCase());
|
|
105514
|
+
}
|
|
105515
|
+
}
|
|
105516
|
+
for (const k of keys)
|
|
105517
|
+
this._pressed.delete(k);
|
|
105518
|
+
}
|
|
105519
|
+
_handleMouseMove(e) {
|
|
105520
|
+
if (!this._isLooking || !this._isActive)
|
|
105521
|
+
return;
|
|
105522
|
+
const dx = e.movementX * this.lookSensitivity;
|
|
105523
|
+
const dy = e.movementY * this.lookSensitivity * (this.invertY ? 1 : -1);
|
|
105524
|
+
this._yaw = (this._yaw - dx) % 360;
|
|
105525
|
+
this._pitch = Math.max(-89, Math.min(89, this._pitch + dy));
|
|
105526
|
+
}
|
|
105527
|
+
_handlePointerMove(e) {
|
|
105528
|
+
if (!this._isLooking || !this._isActive)
|
|
105529
|
+
return;
|
|
105530
|
+
const dx = (e.movementX || 0) * this.lookSensitivity;
|
|
105531
|
+
const dy = (e.movementY || 0) * this.lookSensitivity * (this.invertY ? 1 : -1);
|
|
105532
|
+
this._yaw = (this._yaw - dx) % 360;
|
|
105533
|
+
this._pitch = Math.max(-89, Math.min(89, this._pitch + dy));
|
|
105534
|
+
}
|
|
105535
|
+
_getEffectiveSpeed() {
|
|
105536
|
+
const fast = this._pressed.has(this.keyBindings.fastMove);
|
|
105537
|
+
const slow = this._pressed.has(this.keyBindings.slowMove);
|
|
105538
|
+
let s = this.moveSpeed;
|
|
105539
|
+
if (fast)
|
|
105540
|
+
s *= this.fastSpeedMultiplier;
|
|
105541
|
+
if (slow)
|
|
105542
|
+
s *= this.slowSpeedMultiplier;
|
|
105543
|
+
return s;
|
|
105544
|
+
}
|
|
105545
|
+
_updateVelocity() {
|
|
105546
|
+
const kb = this.keyBindings;
|
|
105547
|
+
const isPressed = (binding, fallbacks) => {
|
|
105548
|
+
const all = [binding, ...fallbacks].filter(Boolean);
|
|
105549
|
+
return all.some(k => this._pressed.has(k));
|
|
105550
|
+
};
|
|
105551
|
+
const forward = isPressed(kb.forward, ['KeyW', 'w', 'W']) ? 1 : 0;
|
|
105552
|
+
const backward = isPressed(kb.backward, ['KeyS', 's', 'S']) ? 1 : 0;
|
|
105553
|
+
const left = isPressed(kb.left, ['KeyA', 'a', 'A']) ? 1 : 0;
|
|
105554
|
+
const right = isPressed(kb.right, ['KeyD', 'd', 'D']) ? 1 : 0;
|
|
105555
|
+
const up = isPressed(kb.up, ['KeyE', 'e', 'E']) ? 1 : 0;
|
|
105556
|
+
const down = isPressed(kb.down, ['KeyQ', 'q', 'Q']) ? 1 : 0;
|
|
105557
|
+
const inputZ = forward - backward;
|
|
105558
|
+
const inputX = right - left;
|
|
105559
|
+
const inputY = up - down;
|
|
105560
|
+
const planarLen = Math.hypot(inputX, inputZ);
|
|
105561
|
+
const nx = planarLen > 0 ? inputX / planarLen : 0;
|
|
105562
|
+
const nz = planarLen > 0 ? inputZ / planarLen : 0;
|
|
105563
|
+
const speed = this._getEffectiveSpeed() * 2;
|
|
105564
|
+
const entity = this.entity;
|
|
105565
|
+
const fwd = entity?.forward && entity.forward.clone
|
|
105566
|
+
? entity.forward.clone()
|
|
105567
|
+
: entity?.forward
|
|
105568
|
+
? new Vec3(entity.forward.x, entity.forward.y, entity.forward.z)
|
|
105569
|
+
: new Vec3(0, 0, -1);
|
|
105570
|
+
const rightVec = entity?.right && entity.right.clone
|
|
105571
|
+
? entity.right.clone()
|
|
105572
|
+
: entity?.right
|
|
105573
|
+
? new Vec3(entity.right.x, entity.right.y, entity.right.z)
|
|
105574
|
+
: new Vec3(1, 0, 0);
|
|
105575
|
+
const upVec = entity?.up && entity.up.clone
|
|
105576
|
+
? entity.up.clone()
|
|
105577
|
+
: entity?.up
|
|
105578
|
+
? new Vec3(entity.up.x, entity.up.y, entity.up.z)
|
|
105579
|
+
: Vec3.UP.clone();
|
|
105580
|
+
const target = new Vec3(0, 0, 0);
|
|
105581
|
+
target.add(fwd.mulScalar(nz * speed));
|
|
105582
|
+
target.add(rightVec.mulScalar(nx * speed));
|
|
105583
|
+
target.add(upVec.mulScalar(inputY * speed));
|
|
105584
|
+
this._targetVelocity.copy(target);
|
|
105585
|
+
this._velocity.lerp(this._velocity, this._targetVelocity, Math.min(1, this.smoothing));
|
|
105586
|
+
if (nx === 0 && nz === 0 && inputY === 0) {
|
|
105587
|
+
this._velocity.mulScalar(this.friction);
|
|
105588
|
+
if (this._velocity.length() < 0.0001)
|
|
105589
|
+
this._velocity.set(0, 0, 0);
|
|
105590
|
+
}
|
|
105591
|
+
}
|
|
105592
|
+
_applyMovement(dt) {
|
|
105593
|
+
if (this._velocity.length() === 0)
|
|
105594
|
+
return;
|
|
105595
|
+
const pos = this.entity?.getPosition?.()?.clone?.() ?? new Vec3(0, 0, 0);
|
|
105596
|
+
pos.add(this._velocity.clone().mulScalar(dt));
|
|
105597
|
+
this.entity?.setPosition?.(pos);
|
|
105598
|
+
}
|
|
105599
|
+
_applyRotation() {
|
|
105600
|
+
if (this.entity?.setLocalEulerAngles) {
|
|
105601
|
+
this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
|
|
105602
|
+
}
|
|
105603
|
+
else if (this.entity?.setEulerAngles) {
|
|
105604
|
+
this.entity.setEulerAngles(this._pitch, this._yaw, 0);
|
|
105605
|
+
}
|
|
105606
|
+
}
|
|
105607
|
+
_applyConstraints() {
|
|
105608
|
+
if (!this.enableCollision &&
|
|
105609
|
+
this.minHeight == null &&
|
|
105610
|
+
this.maxHeight == null) {
|
|
105611
|
+
return;
|
|
105612
|
+
}
|
|
105613
|
+
const pos = this.entity?.getPosition?.()?.clone?.() ?? new Vec3(0, 0, 0);
|
|
105614
|
+
if (this.minHeight != null)
|
|
105615
|
+
pos.y = Math.max(pos.y, this.minHeight);
|
|
105616
|
+
if (this.maxHeight != null)
|
|
105617
|
+
pos.y = Math.min(pos.y, this.maxHeight);
|
|
105618
|
+
this.entity?.setPosition?.(pos);
|
|
105619
|
+
}
|
|
105620
|
+
update(dt) {
|
|
105621
|
+
if (!this._isActive)
|
|
105622
|
+
return;
|
|
105623
|
+
this._updateVelocity();
|
|
105624
|
+
this._applyMovement(dt);
|
|
105625
|
+
this._applyRotation();
|
|
105626
|
+
this._applyConstraints();
|
|
105627
|
+
// Emit throttled movement/look events (100ms)
|
|
105628
|
+
const now = performance.now();
|
|
105629
|
+
if (!this._lastMoveEmitTime || now - this._lastMoveEmitTime >= 100) {
|
|
105630
|
+
const pos = this.entity?.getPosition?.();
|
|
105631
|
+
this.emitFlyEvent?.('fly-camera-move', {
|
|
105632
|
+
position: { x: pos?.x ?? 0, y: pos?.y ?? 0, z: pos?.z ?? 0 },
|
|
105633
|
+
velocity: { x: this._velocity.x, y: this._velocity.y, z: this._velocity.z },
|
|
105634
|
+
});
|
|
105635
|
+
this._lastMoveEmitTime = now;
|
|
105636
|
+
}
|
|
105637
|
+
if (!this._lastLookEmitTime || now - this._lastLookEmitTime >= 100) {
|
|
105638
|
+
this.emitFlyEvent?.('fly-camera-look', { pitch: this._pitch, yaw: this._yaw });
|
|
105639
|
+
this._lastLookEmitTime = now;
|
|
105640
|
+
}
|
|
105641
|
+
}
|
|
105642
|
+
}
|
|
105643
|
+
|
|
105265
105644
|
// FlyCamera PlayCanvas script: first-person WASD movement with mouse-look
|
|
105266
105645
|
function registerFlyCameraScript() {
|
|
105267
105646
|
if (typeof pc === 'undefined') {
|
|
@@ -105347,10 +105726,18 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105347
105726
|
this._onKeyUp = this._handleKeyUp.bind(this);
|
|
105348
105727
|
document.addEventListener('keydown', this._onKeyDown, true);
|
|
105349
105728
|
document.addEventListener('keyup', this._onKeyUp, true);
|
|
105350
|
-
// Mouse move for look (while
|
|
105729
|
+
// Mouse/pointer move for look (while primary button held).
|
|
105730
|
+
//
|
|
105731
|
+
// Important: SuperSplat camera controls are pointer-event based and may call
|
|
105732
|
+
// preventDefault() on pointer events. When that happens, browsers often
|
|
105733
|
+
// suppress the corresponding legacy mouse events (mousedown/mousemove).
|
|
105734
|
+
// So we listen to *pointer* events as the primary path, with mouse as a
|
|
105735
|
+
// fallback for older environments.
|
|
105351
105736
|
this._onMouseMove = this._handleMouseMove.bind(this);
|
|
105737
|
+
this._onPointerMove = this._handlePointerMove.bind(this);
|
|
105352
105738
|
document.addEventListener('mousemove', this._onMouseMove);
|
|
105353
|
-
|
|
105739
|
+
document.addEventListener('pointermove', this._onPointerMove, true);
|
|
105740
|
+
// Button handling: click + hold to look, release to stop
|
|
105354
105741
|
const canvas = this.app.graphicsDevice.canvas;
|
|
105355
105742
|
this._onClickToLock = (e) => {
|
|
105356
105743
|
// Left button enables look while held (no pointer lock)
|
|
@@ -105358,13 +105745,17 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105358
105745
|
this._isLooking = true;
|
|
105359
105746
|
}
|
|
105360
105747
|
};
|
|
105361
|
-
canvas.addEventListener('mousedown', this._onClickToLock);
|
|
105748
|
+
canvas.addEventListener('mousedown', this._onClickToLock, true);
|
|
105749
|
+
this._onPointerDownToLook = this._handlePointerDown.bind(this);
|
|
105750
|
+
canvas.addEventListener('pointerdown', this._onPointerDownToLook, true);
|
|
105362
105751
|
this._onMouseUp = (e) => {
|
|
105363
105752
|
if (e.button === 0) {
|
|
105364
105753
|
this._isLooking = false;
|
|
105365
105754
|
}
|
|
105366
105755
|
};
|
|
105367
|
-
document.addEventListener('mouseup', this._onMouseUp);
|
|
105756
|
+
document.addEventListener('mouseup', this._onMouseUp, true);
|
|
105757
|
+
this._onPointerUp = this._handlePointerUp.bind(this);
|
|
105758
|
+
document.addEventListener('pointerup', this._onPointerUp, true);
|
|
105368
105759
|
};
|
|
105369
105760
|
FlyCamera.prototype.update = function (dt) {
|
|
105370
105761
|
if (!this._isActive)
|
|
@@ -105460,9 +105851,11 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105460
105851
|
this._onKeyDown = this._onKeyDown || this._handleKeyDown.bind(this);
|
|
105461
105852
|
this._onKeyUp = this._onKeyUp || this._handleKeyUp.bind(this);
|
|
105462
105853
|
this._onMouseMove = this._onMouseMove || this._handleMouseMove.bind(this);
|
|
105854
|
+
this._onPointerMove = this._onPointerMove || this._handlePointerMove.bind(this);
|
|
105463
105855
|
document.addEventListener('keydown', this._onKeyDown, true);
|
|
105464
105856
|
document.addEventListener('keyup', this._onKeyUp, true);
|
|
105465
105857
|
document.addEventListener('mousemove', this._onMouseMove);
|
|
105858
|
+
document.addEventListener('pointermove', this._onPointerMove, true);
|
|
105466
105859
|
const canvas = this.app.graphicsDevice.canvas;
|
|
105467
105860
|
this._onClickToLock =
|
|
105468
105861
|
this._onClickToLock ||
|
|
@@ -105471,7 +105864,10 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105471
105864
|
this._isLooking = true;
|
|
105472
105865
|
}
|
|
105473
105866
|
});
|
|
105474
|
-
canvas.addEventListener('mousedown', this._onClickToLock);
|
|
105867
|
+
canvas.addEventListener('mousedown', this._onClickToLock, true);
|
|
105868
|
+
this._onPointerDownToLook =
|
|
105869
|
+
this._onPointerDownToLook || this._handlePointerDown.bind(this);
|
|
105870
|
+
canvas.addEventListener('pointerdown', this._onPointerDownToLook, true);
|
|
105475
105871
|
this._onMouseUp =
|
|
105476
105872
|
this._onMouseUp ||
|
|
105477
105873
|
((e) => {
|
|
@@ -105479,22 +105875,36 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105479
105875
|
this._isLooking = false;
|
|
105480
105876
|
}
|
|
105481
105877
|
});
|
|
105482
|
-
document.addEventListener('mouseup', this._onMouseUp);
|
|
105878
|
+
document.addEventListener('mouseup', this._onMouseUp, true);
|
|
105879
|
+
this._onPointerUp = this._onPointerUp || this._handlePointerUp.bind(this);
|
|
105880
|
+
document.addEventListener('pointerup', this._onPointerUp, true);
|
|
105483
105881
|
};
|
|
105484
105882
|
FlyCamera.prototype.deactivate = function () {
|
|
105485
105883
|
if (!this._isActive)
|
|
105486
105884
|
return;
|
|
105487
105885
|
this._isActive = false;
|
|
105886
|
+
this._isLooking = false;
|
|
105887
|
+
try {
|
|
105888
|
+
this._pressed?.clear?.();
|
|
105889
|
+
}
|
|
105890
|
+
catch {
|
|
105891
|
+
// ignore
|
|
105892
|
+
}
|
|
105488
105893
|
// Exit pointer lock when deactivating
|
|
105489
105894
|
this._exitPointerLock();
|
|
105490
105895
|
// Remove listeners
|
|
105491
105896
|
document.removeEventListener('keydown', this._onKeyDown, true);
|
|
105492
105897
|
document.removeEventListener('keyup', this._onKeyUp, true);
|
|
105493
105898
|
document.removeEventListener('mousemove', this._onMouseMove);
|
|
105494
|
-
document.removeEventListener('
|
|
105899
|
+
document.removeEventListener('pointermove', this._onPointerMove, true);
|
|
105900
|
+
document.removeEventListener('mouseup', this._onMouseUp, true);
|
|
105901
|
+
document.removeEventListener('pointerup', this._onPointerUp, true);
|
|
105495
105902
|
const canvas = this.app?.graphicsDevice?.canvas;
|
|
105496
105903
|
if (canvas && this._onClickToLock) {
|
|
105497
|
-
canvas.removeEventListener('mousedown', this._onClickToLock);
|
|
105904
|
+
canvas.removeEventListener('mousedown', this._onClickToLock, true);
|
|
105905
|
+
}
|
|
105906
|
+
if (canvas && this._onPointerDownToLook) {
|
|
105907
|
+
canvas.removeEventListener('pointerdown', this._onPointerDownToLook, true);
|
|
105498
105908
|
}
|
|
105499
105909
|
};
|
|
105500
105910
|
FlyCamera.prototype.setConfig = function (config) {
|
|
@@ -105583,6 +105993,27 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105583
105993
|
this._yaw = (this._yaw - dx) % 360;
|
|
105584
105994
|
this._pitch = Math.max(-89, Math.min(89, this._pitch + dy));
|
|
105585
105995
|
};
|
|
105996
|
+
FlyCamera.prototype._handlePointerMove = function (e) {
|
|
105997
|
+
if (!this._isLooking || !this._isActive)
|
|
105998
|
+
return;
|
|
105999
|
+
const dx = (e.movementX || 0) * this.lookSensitivity;
|
|
106000
|
+
const dy = (e.movementY || 0) * this.lookSensitivity * (this.invertY ? 1 : -1);
|
|
106001
|
+
this._yaw = (this._yaw - dx) % 360;
|
|
106002
|
+
this._pitch = Math.max(-89, Math.min(89, this._pitch + dy));
|
|
106003
|
+
};
|
|
106004
|
+
FlyCamera.prototype._handlePointerDown = function (e) {
|
|
106005
|
+
if (!this._isActive)
|
|
106006
|
+
return;
|
|
106007
|
+
// Primary button enables look while held (no pointer lock)
|
|
106008
|
+
if (e.button === 0) {
|
|
106009
|
+
this._isLooking = true;
|
|
106010
|
+
}
|
|
106011
|
+
};
|
|
106012
|
+
FlyCamera.prototype._handlePointerUp = function (e) {
|
|
106013
|
+
if (e.button === 0) {
|
|
106014
|
+
this._isLooking = false;
|
|
106015
|
+
}
|
|
106016
|
+
};
|
|
105586
106017
|
FlyCamera.prototype._handlePointerLockChange = function () {
|
|
105587
106018
|
const canvas = this.app.graphicsDevice.canvas;
|
|
105588
106019
|
this._isPointerLocked = document.pointerLockElement === canvas;
|
|
@@ -105879,7 +106310,7 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105879
106310
|
scope.resolve(key).setValue(values[key]);
|
|
105880
106311
|
}
|
|
105881
106312
|
};
|
|
105882
|
-
const createBlueNoiseTexture = (device) => {
|
|
106313
|
+
const createBlueNoiseTexture$1 = (device) => {
|
|
105883
106314
|
const size = 32;
|
|
105884
106315
|
const texture = new Texture$1(device, {
|
|
105885
106316
|
width: size,
|
|
@@ -105958,7 +106389,7 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105958
106389
|
throw new Error('InfiniteGrid: QuadRender is not available in this PlayCanvas version.');
|
|
105959
106390
|
}
|
|
105960
106391
|
this.quadRender = new QuadRender$1(this.shader);
|
|
105961
|
-
this.blueNoiseTexture = createBlueNoiseTexture(device);
|
|
106392
|
+
this.blueNoiseTexture = createBlueNoiseTexture$1(device);
|
|
105962
106393
|
this._createBlendState();
|
|
105963
106394
|
this._registerRenderHook();
|
|
105964
106395
|
}
|
|
@@ -140324,51 +140755,161 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
140324
140755
|
return fallback.clone();
|
|
140325
140756
|
}
|
|
140326
140757
|
|
|
140327
|
-
const
|
|
140328
|
-
|
|
140329
|
-
|
|
140330
|
-
|
|
140331
|
-
|
|
140758
|
+
const cache = new WeakMap();
|
|
140759
|
+
function createBlueNoiseTexture(device) {
|
|
140760
|
+
const size = 32;
|
|
140761
|
+
const texture = new Texture$1(device, {
|
|
140762
|
+
width: size,
|
|
140763
|
+
height: size,
|
|
140764
|
+
format: PIXELFORMAT_R8_G8_B8_A8,
|
|
140765
|
+
mipmaps: false,
|
|
140766
|
+
});
|
|
140767
|
+
texture.addressU = ADDRESS_REPEAT;
|
|
140768
|
+
texture.addressV = ADDRESS_REPEAT;
|
|
140769
|
+
texture.minFilter = FILTER_NEAREST;
|
|
140770
|
+
texture.magFilter = FILTER_NEAREST;
|
|
140771
|
+
const pixels = texture.lock();
|
|
140772
|
+
const seed = 1337;
|
|
140773
|
+
let value = seed;
|
|
140774
|
+
const random = () => {
|
|
140775
|
+
value ^= value << 13;
|
|
140776
|
+
value ^= value >>> 17;
|
|
140777
|
+
value ^= value << 5;
|
|
140778
|
+
return ((value >>> 0) % 256) / 255;
|
|
140779
|
+
};
|
|
140780
|
+
for (let i = 0; i < size * size; i++) {
|
|
140781
|
+
const noise = Math.floor(random() * 255);
|
|
140782
|
+
const idx = i * 4;
|
|
140783
|
+
pixels[idx + 0] = noise;
|
|
140784
|
+
pixels[idx + 1] = noise;
|
|
140785
|
+
pixels[idx + 2] = noise;
|
|
140786
|
+
pixels[idx + 3] = 255;
|
|
140787
|
+
}
|
|
140788
|
+
texture.unlock();
|
|
140789
|
+
texture.name = 'supersplat-blue-noise';
|
|
140790
|
+
return texture;
|
|
140791
|
+
}
|
|
140792
|
+
function getBlueNoiseTex32(device) {
|
|
140793
|
+
const existing = cache.get(device);
|
|
140794
|
+
if (existing)
|
|
140795
|
+
return existing;
|
|
140796
|
+
const tex = createBlueNoiseTexture(device);
|
|
140797
|
+
cache.set(device, tex);
|
|
140798
|
+
return tex;
|
|
140799
|
+
}
|
|
140332
140800
|
|
|
140333
|
-
|
|
140334
|
-
|
|
140801
|
+
// SuperSplat-like selection box shader: screen-space ray/box intersection that draws a thick
|
|
140802
|
+
// white wire/grid pattern (not dependent on WebGL line width).
|
|
140803
|
+
const BOX_SELECT_VS = /* glsl */ `
|
|
140804
|
+
attribute vec3 vertex_position;
|
|
140335
140805
|
|
|
140336
|
-
|
|
140806
|
+
uniform mat4 matrix_model;
|
|
140807
|
+
uniform mat4 matrix_viewProjection;
|
|
140337
140808
|
|
|
140338
|
-
void main(
|
|
140339
|
-
|
|
140340
|
-
|
|
140341
|
-
}
|
|
140809
|
+
void main() {
|
|
140810
|
+
gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
|
|
140811
|
+
}
|
|
140342
140812
|
`;
|
|
140343
|
-
const
|
|
140344
|
-
|
|
140345
|
-
|
|
140346
|
-
|
|
140813
|
+
const BOX_SELECT_FS = /* glsl */ `
|
|
140814
|
+
// ray-box intersection in box space
|
|
140815
|
+
bool intersectBox(out float t0, out float t1, out int axis0, out int axis1, vec3 pos, vec3 dir, vec3 boxCen, vec3 boxLen)
|
|
140816
|
+
{
|
|
140817
|
+
bvec3 validDir = notEqual(dir, vec3(0.0));
|
|
140818
|
+
vec3 absDir = abs(dir);
|
|
140819
|
+
vec3 signDir = sign(dir);
|
|
140820
|
+
vec3 m = vec3(
|
|
140821
|
+
validDir.x ? 1.0 / absDir.x : 0.0,
|
|
140822
|
+
validDir.y ? 1.0 / absDir.y : 0.0,
|
|
140823
|
+
validDir.z ? 1.0 / absDir.z : 0.0
|
|
140824
|
+
) * signDir;
|
|
140347
140825
|
|
|
140348
|
-
|
|
140826
|
+
vec3 n = m * (pos - boxCen);
|
|
140827
|
+
vec3 k = abs(m) * boxLen;
|
|
140349
140828
|
|
|
140350
|
-
|
|
140351
|
-
|
|
140352
|
-
uniform float opacity;
|
|
140829
|
+
vec3 v0 = -n - k;
|
|
140830
|
+
vec3 v1 = -n + k;
|
|
140353
140831
|
|
|
140354
|
-
|
|
140832
|
+
// replace invalid axes with -inf and +inf so the tests below ignore them
|
|
140833
|
+
v0 = mix(vec3(-1.0 / 0.0000001), v0, validDir);
|
|
140834
|
+
v1 = mix(vec3(1.0 / 0.0000001), v1, validDir);
|
|
140355
140835
|
|
|
140356
|
-
|
|
140357
|
-
|
|
140358
|
-
float d = min(min(vUv.x, 1.0 - vUv.x), min(vUv.y, 1.0 - vUv.y));
|
|
140836
|
+
axis0 = (v0.x > v0.y) ? ((v0.x > v0.z) ? 0 : 2) : ((v0.y > v0.z) ? 1 : 2);
|
|
140837
|
+
axis1 = (v1.x < v1.y) ? ((v1.x < v1.z) ? 0 : 2) : ((v1.y < v1.z) ? 1 : 2);
|
|
140359
140838
|
|
|
140360
|
-
|
|
140361
|
-
|
|
140362
|
-
#ifdef GL_OES_standard_derivatives
|
|
140363
|
-
aa = max(aa, fwidth(d) * 1.5);
|
|
140364
|
-
#endif
|
|
140365
|
-
float a = 1.0 - smoothstep(lineWidth, lineWidth + aa, d);
|
|
140366
|
-
a *= opacity;
|
|
140839
|
+
t0 = v0[axis0];
|
|
140840
|
+
t1 = v1[axis1];
|
|
140367
140841
|
|
|
140368
|
-
|
|
140369
|
-
|
|
140370
|
-
}
|
|
140842
|
+
if (t0 > t1 || t1 < 0.0) {
|
|
140843
|
+
return false;
|
|
140844
|
+
}
|
|
140845
|
+
|
|
140846
|
+
return true;
|
|
140847
|
+
}
|
|
140848
|
+
|
|
140849
|
+
float calcDepth(in vec3 pos, in mat4 viewProjection) {
|
|
140850
|
+
vec4 v = viewProjection * vec4(pos, 1.0);
|
|
140851
|
+
return (v.z / v.w) * 0.5 + 0.5;
|
|
140852
|
+
}
|
|
140853
|
+
|
|
140854
|
+
uniform sampler2D blueNoiseTex32;
|
|
140855
|
+
uniform mat4 matrix_viewProjection;
|
|
140856
|
+
uniform vec3 boxCen;
|
|
140857
|
+
uniform vec3 boxLen;
|
|
140858
|
+
|
|
140859
|
+
uniform vec3 near_origin;
|
|
140860
|
+
uniform vec3 near_x;
|
|
140861
|
+
uniform vec3 near_y;
|
|
140862
|
+
|
|
140863
|
+
uniform vec3 far_origin;
|
|
140864
|
+
uniform vec3 far_x;
|
|
140865
|
+
uniform vec3 far_y;
|
|
140866
|
+
|
|
140867
|
+
uniform vec2 targetSize;
|
|
140868
|
+
uniform vec3 lineColor;
|
|
140869
|
+
|
|
140870
|
+
bool writeDepth(float alpha) {
|
|
140871
|
+
ivec2 uv = ivec2(gl_FragCoord.xy);
|
|
140872
|
+
ivec2 size = textureSize(blueNoiseTex32, 0);
|
|
140873
|
+
return alpha > texelFetch(blueNoiseTex32, uv % size, 0).y;
|
|
140874
|
+
}
|
|
140875
|
+
|
|
140876
|
+
bool strips(vec3 pos, int axis) {
|
|
140877
|
+
// Thickness tuned to match SuperSplat viewer "thick wire"
|
|
140878
|
+
bvec3 b = lessThan(fract(pos * 2.0 + vec3(0.015)), vec3(0.06));
|
|
140879
|
+
b[axis] = false;
|
|
140880
|
+
return any(b);
|
|
140881
|
+
}
|
|
140882
|
+
|
|
140883
|
+
void main() {
|
|
140884
|
+
vec2 clip = gl_FragCoord.xy / targetSize;
|
|
140885
|
+
vec3 worldNear = near_origin + near_x * clip.x + near_y * clip.y;
|
|
140886
|
+
vec3 worldFar = far_origin + far_x * clip.x + far_y * clip.y;
|
|
140887
|
+
vec3 rayDir = normalize(worldFar - worldNear);
|
|
140888
|
+
|
|
140889
|
+
float t0, t1;
|
|
140890
|
+
int axis0, axis1;
|
|
140891
|
+
if (!intersectBox(t0, t1, axis0, axis1, worldNear, rayDir, boxCen, boxLen)) {
|
|
140892
|
+
discard;
|
|
140893
|
+
}
|
|
140894
|
+
|
|
140895
|
+
vec3 frontPos = worldNear + rayDir * t0;
|
|
140896
|
+
bool front = t0 > 0.0 && strips(frontPos - boxCen, axis0);
|
|
140897
|
+
|
|
140898
|
+
vec3 backPos = worldNear + rayDir * t1;
|
|
140899
|
+
bool back = strips(backPos - boxCen, axis1);
|
|
140900
|
+
|
|
140901
|
+
if (front) {
|
|
140902
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
140903
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(frontPos, matrix_viewProjection) : 1.0;
|
|
140904
|
+
} else if (back) {
|
|
140905
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
140906
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(backPos, matrix_viewProjection) : 1.0;
|
|
140907
|
+
} else {
|
|
140908
|
+
discard;
|
|
140909
|
+
}
|
|
140910
|
+
}
|
|
140371
140911
|
`;
|
|
140912
|
+
const tmpPos$1 = new Vec3();
|
|
140372
140913
|
class BoxSelectionAPI {
|
|
140373
140914
|
constructor() {
|
|
140374
140915
|
this.boxes = new Map();
|
|
@@ -140405,6 +140946,7 @@ void main(void) {
|
|
|
140405
140946
|
this.ensureTranslateGizmo();
|
|
140406
140947
|
this.updateGizmoSize();
|
|
140407
140948
|
this.updateGizmoAttachment();
|
|
140949
|
+
this.updateBoxShaderUniforms();
|
|
140408
140950
|
}
|
|
140409
140951
|
createBox(options = {}) {
|
|
140410
140952
|
if (!this.app || !this.parent) {
|
|
@@ -140427,7 +140969,7 @@ void main(void) {
|
|
|
140427
140969
|
entity.render.castShadows = false;
|
|
140428
140970
|
entity.render.receiveShadows = false;
|
|
140429
140971
|
entity.render.enabled = visible;
|
|
140430
|
-
// Render as a thick wireframe (shader-based)
|
|
140972
|
+
// Render as a thick wireframe (shader-based) to match SuperSplat viewer.
|
|
140431
140973
|
this.parent.addChild(entity);
|
|
140432
140974
|
const record = {
|
|
140433
140975
|
id,
|
|
@@ -140514,8 +141056,8 @@ void main(void) {
|
|
|
140514
141056
|
return false;
|
|
140515
141057
|
}
|
|
140516
141058
|
record.color.set(r, g, b);
|
|
140517
|
-
const mat =
|
|
140518
|
-
record.
|
|
141059
|
+
const mat = record.entity.render?.material;
|
|
141060
|
+
mat?.setParameter?.('lineColor', [record.color.x, record.color.y, record.color.z]);
|
|
140519
141061
|
this.requestRender();
|
|
140520
141062
|
return true;
|
|
140521
141063
|
}
|
|
@@ -140710,19 +141252,34 @@ void main(void) {
|
|
|
140710
141252
|
}
|
|
140711
141253
|
buildWireframeMaterial(color) {
|
|
140712
141254
|
const material = new ShaderMaterial$1({
|
|
140713
|
-
uniqueName: '
|
|
140714
|
-
vertexGLSL:
|
|
140715
|
-
fragmentGLSL:
|
|
141255
|
+
uniqueName: 'boxSelectionSupersplatWire',
|
|
141256
|
+
vertexGLSL: BOX_SELECT_VS,
|
|
141257
|
+
fragmentGLSL: BOX_SELECT_FS,
|
|
140716
141258
|
});
|
|
140717
|
-
material.cull =
|
|
140718
|
-
material.depthWrite = false;
|
|
141259
|
+
material.cull = CULLFACE_FRONT;
|
|
140719
141260
|
material.blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE_MINUS_SRC_ALPHA);
|
|
140720
|
-
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
140721
|
-
material.setParameter('lineWidth', THICK_WIREFRAME_LINE_WIDTH_UV$1);
|
|
140722
|
-
material.setParameter('opacity', THICK_WIREFRAME_OPACITY$1);
|
|
140723
141261
|
material.update();
|
|
141262
|
+
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
140724
141263
|
return material;
|
|
140725
141264
|
}
|
|
141265
|
+
updateBoxShaderUniforms() {
|
|
141266
|
+
if (!this.app)
|
|
141267
|
+
return;
|
|
141268
|
+
const device = this.app.graphicsDevice;
|
|
141269
|
+
// Ensure required global uniforms for selection shaders.
|
|
141270
|
+
device.scope.resolve('targetSize').setValue([device.width, device.height]);
|
|
141271
|
+
device.scope.resolve('blueNoiseTex32').setValue(getBlueNoiseTex32(device));
|
|
141272
|
+
for (const record of this.boxes.values()) {
|
|
141273
|
+
const mat = record.entity.render?.material;
|
|
141274
|
+
if (!mat?.setParameter)
|
|
141275
|
+
continue;
|
|
141276
|
+
// World-space center
|
|
141277
|
+
record.entity.getWorldTransform().getTranslation(tmpPos$1);
|
|
141278
|
+
mat.setParameter('boxCen', [tmpPos$1.x, tmpPos$1.y, tmpPos$1.z]);
|
|
141279
|
+
// Half extents
|
|
141280
|
+
mat.setParameter('boxLen', [record.lenX * 0.5, record.lenY * 0.5, record.lenZ * 0.5]);
|
|
141281
|
+
}
|
|
141282
|
+
}
|
|
140726
141283
|
emitEvent(event, detail) {
|
|
140727
141284
|
if (this.onEvent) {
|
|
140728
141285
|
this.onEvent(event, detail);
|
|
@@ -140734,59 +141291,110 @@ void main(void) {
|
|
|
140734
141291
|
}
|
|
140735
141292
|
}
|
|
140736
141293
|
|
|
140737
|
-
|
|
140738
|
-
|
|
140739
|
-
const
|
|
140740
|
-
|
|
140741
|
-
const THICK_WIREFRAME_SPHERE_VS = /* glsl */ `
|
|
140742
|
-
attribute vec3 vertex_position;
|
|
140743
|
-
attribute vec2 vertex_texCoord0;
|
|
140744
|
-
|
|
140745
|
-
uniform mat4 matrix_model;
|
|
140746
|
-
uniform mat4 matrix_viewProjection;
|
|
141294
|
+
// SuperSplat-like selection sphere shader: screen-space ray/sphere intersection that draws a thick
|
|
141295
|
+
// white wire/grid pattern (not dependent on WebGL line width).
|
|
141296
|
+
const SPHERE_SELECT_VS = /* glsl */ `
|
|
141297
|
+
attribute vec3 vertex_position;
|
|
140747
141298
|
|
|
140748
|
-
|
|
141299
|
+
uniform mat4 matrix_model;
|
|
141300
|
+
uniform mat4 matrix_viewProjection;
|
|
140749
141301
|
|
|
140750
|
-
void main(
|
|
140751
|
-
|
|
140752
|
-
|
|
140753
|
-
}
|
|
141302
|
+
void main() {
|
|
141303
|
+
gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
|
|
141304
|
+
}
|
|
140754
141305
|
`;
|
|
140755
|
-
const
|
|
140756
|
-
|
|
140757
|
-
|
|
140758
|
-
|
|
141306
|
+
const SPHERE_SELECT_FS = /* glsl */ `
|
|
141307
|
+
bool intersectSphere(out float t0, out float t1, vec3 pos, vec3 dir, vec4 sphere) {
|
|
141308
|
+
vec3 L = sphere.xyz - pos;
|
|
141309
|
+
float tca = dot(L, dir);
|
|
141310
|
+
|
|
141311
|
+
float d2 = sphere.w * sphere.w - (dot(L, L) - tca * tca);
|
|
141312
|
+
if (d2 <= 0.0) {
|
|
141313
|
+
return false;
|
|
141314
|
+
}
|
|
141315
|
+
|
|
141316
|
+
float thc = sqrt(d2);
|
|
141317
|
+
t0 = tca - thc;
|
|
141318
|
+
t1 = tca + thc;
|
|
141319
|
+
if (t1 <= 0.0) {
|
|
141320
|
+
return false;
|
|
141321
|
+
}
|
|
140759
141322
|
|
|
140760
|
-
|
|
141323
|
+
return true;
|
|
141324
|
+
}
|
|
140761
141325
|
|
|
140762
|
-
|
|
140763
|
-
|
|
140764
|
-
|
|
141326
|
+
float calcDepth(in vec3 pos, in mat4 viewProjection) {
|
|
141327
|
+
vec4 v = viewProjection * vec4(pos, 1.0);
|
|
141328
|
+
return (v.z / v.w) * 0.5 + 0.5;
|
|
141329
|
+
}
|
|
140765
141330
|
|
|
140766
|
-
|
|
141331
|
+
vec2 calcAzimuthElev(in vec3 dir) {
|
|
141332
|
+
float azimuth = atan(dir.z, dir.x);
|
|
141333
|
+
float elev = asin(dir.y);
|
|
141334
|
+
return vec2(azimuth, elev) * 180.0 / 3.14159;
|
|
141335
|
+
}
|
|
140767
141336
|
|
|
140768
|
-
|
|
140769
|
-
|
|
140770
|
-
|
|
140771
|
-
|
|
141337
|
+
uniform sampler2D blueNoiseTex32;
|
|
141338
|
+
uniform mat4 matrix_viewProjection;
|
|
141339
|
+
uniform vec4 sphere;
|
|
141340
|
+
uniform vec3 lineColor;
|
|
140772
141341
|
|
|
140773
|
-
|
|
140774
|
-
|
|
140775
|
-
|
|
140776
|
-
float dv = gridLineDist(vUv.y, ${SPHERE_GRID_V});
|
|
140777
|
-
float d = min(du, dv);
|
|
140778
|
-
|
|
140779
|
-
float aa = 0.002;
|
|
140780
|
-
#ifdef GL_OES_standard_derivatives
|
|
140781
|
-
aa = max(aa, fwidth(d) * 1.5);
|
|
140782
|
-
#endif
|
|
140783
|
-
float a = 1.0 - smoothstep(lineWidth, lineWidth + aa, d);
|
|
140784
|
-
a *= opacity;
|
|
141342
|
+
uniform vec3 near_origin;
|
|
141343
|
+
uniform vec3 near_x;
|
|
141344
|
+
uniform vec3 near_y;
|
|
140785
141345
|
|
|
140786
|
-
|
|
140787
|
-
|
|
140788
|
-
|
|
141346
|
+
uniform vec3 far_origin;
|
|
141347
|
+
uniform vec3 far_x;
|
|
141348
|
+
uniform vec3 far_y;
|
|
141349
|
+
|
|
141350
|
+
uniform vec2 targetSize;
|
|
141351
|
+
|
|
141352
|
+
bool writeDepth(float alpha) {
|
|
141353
|
+
vec2 uv = fract(gl_FragCoord.xy / 32.0);
|
|
141354
|
+
float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
|
|
141355
|
+
return alpha > noise;
|
|
141356
|
+
}
|
|
141357
|
+
|
|
141358
|
+
bool strips(vec3 lp) {
|
|
141359
|
+
vec2 ae = calcAzimuthElev(normalize(lp));
|
|
141360
|
+
|
|
141361
|
+
float spacing = 180.0 / (2.0 * 3.14159 * sphere.w);
|
|
141362
|
+
// Thickness tuned to match SuperSplat viewer "thick wire"
|
|
141363
|
+
float size = 0.06;
|
|
141364
|
+
return fract(ae.x / spacing) < size ||
|
|
141365
|
+
fract(ae.y / spacing) < size;
|
|
141366
|
+
}
|
|
141367
|
+
|
|
141368
|
+
void main() {
|
|
141369
|
+
vec2 clip = gl_FragCoord.xy / targetSize;
|
|
141370
|
+
vec3 worldNear = near_origin + near_x * clip.x + near_y * clip.y;
|
|
141371
|
+
vec3 worldFar = far_origin + far_x * clip.x + far_y * clip.y;
|
|
141372
|
+
|
|
141373
|
+
vec3 rayDir = normalize(worldFar - worldNear);
|
|
141374
|
+
|
|
141375
|
+
float t0, t1;
|
|
141376
|
+
if (!intersectSphere(t0, t1, worldNear, rayDir, sphere)) {
|
|
141377
|
+
discard;
|
|
141378
|
+
}
|
|
141379
|
+
|
|
141380
|
+
vec3 frontPos = worldNear + rayDir * t0;
|
|
141381
|
+
bool front = t0 > 0.0 && strips(frontPos - sphere.xyz);
|
|
141382
|
+
|
|
141383
|
+
vec3 backPos = worldNear + rayDir * t1;
|
|
141384
|
+
bool back = strips(backPos - sphere.xyz);
|
|
141385
|
+
|
|
141386
|
+
if (front) {
|
|
141387
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
141388
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(frontPos, matrix_viewProjection) : 1.0;
|
|
141389
|
+
} else if (back) {
|
|
141390
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
141391
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(backPos, matrix_viewProjection) : 1.0;
|
|
141392
|
+
} else {
|
|
141393
|
+
discard;
|
|
141394
|
+
}
|
|
141395
|
+
}
|
|
140789
141396
|
`;
|
|
141397
|
+
const tmpPos = new Vec3();
|
|
140790
141398
|
class SphereSelectionAPI {
|
|
140791
141399
|
constructor() {
|
|
140792
141400
|
this.spheres = new Map();
|
|
@@ -140822,6 +141430,7 @@ void main(void) {
|
|
|
140822
141430
|
this.ensureTranslateGizmo();
|
|
140823
141431
|
this.updateGizmoSize();
|
|
140824
141432
|
this.updateGizmoAttachment();
|
|
141433
|
+
this.updateSphereShaderUniforms();
|
|
140825
141434
|
}
|
|
140826
141435
|
createSphere(options = {}) {
|
|
140827
141436
|
if (!this.app || !this.parent) {
|
|
@@ -140834,7 +141443,8 @@ void main(void) {
|
|
|
140834
141443
|
const visible = options.visible ?? true;
|
|
140835
141444
|
const entity = new Entity(id);
|
|
140836
141445
|
entity.addComponent('render', {
|
|
140837
|
-
|
|
141446
|
+
// Use a box proxy like SuperSplat; shader ray-marches a true sphere.
|
|
141447
|
+
type: 'box',
|
|
140838
141448
|
material: this.buildWireframeMaterial(color),
|
|
140839
141449
|
});
|
|
140840
141450
|
entity.setLocalScale(radius * 2, radius * 2, radius * 2); // sphere diameter = radius * 2
|
|
@@ -140842,7 +141452,7 @@ void main(void) {
|
|
|
140842
141452
|
entity.render.castShadows = false;
|
|
140843
141453
|
entity.render.receiveShadows = false;
|
|
140844
141454
|
entity.render.enabled = visible;
|
|
140845
|
-
// Render as a thick wireframe (shader-based)
|
|
141455
|
+
// Render as a thick wireframe (shader-based) to match SuperSplat viewer.
|
|
140846
141456
|
this.parent.addChild(entity);
|
|
140847
141457
|
const record = {
|
|
140848
141458
|
id,
|
|
@@ -140925,8 +141535,12 @@ void main(void) {
|
|
|
140925
141535
|
return false;
|
|
140926
141536
|
}
|
|
140927
141537
|
record.color.set(r, g, b);
|
|
140928
|
-
const mat =
|
|
140929
|
-
|
|
141538
|
+
const mat = record.entity.render?.material;
|
|
141539
|
+
mat?.setParameter?.('lineColor', [
|
|
141540
|
+
record.color.x,
|
|
141541
|
+
record.color.y,
|
|
141542
|
+
record.color.z,
|
|
141543
|
+
]);
|
|
140930
141544
|
this.requestRender();
|
|
140931
141545
|
return true;
|
|
140932
141546
|
}
|
|
@@ -141124,19 +141738,30 @@ void main(void) {
|
|
|
141124
141738
|
}
|
|
141125
141739
|
buildWireframeMaterial(color) {
|
|
141126
141740
|
const material = new ShaderMaterial$1({
|
|
141127
|
-
uniqueName: '
|
|
141128
|
-
vertexGLSL:
|
|
141129
|
-
fragmentGLSL:
|
|
141741
|
+
uniqueName: 'sphereSelectionSupersplatWire',
|
|
141742
|
+
vertexGLSL: SPHERE_SELECT_VS,
|
|
141743
|
+
fragmentGLSL: SPHERE_SELECT_FS,
|
|
141130
141744
|
});
|
|
141131
|
-
material.cull =
|
|
141132
|
-
material.depthWrite = false;
|
|
141745
|
+
material.cull = CULLFACE_FRONT;
|
|
141133
141746
|
material.blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE_MINUS_SRC_ALPHA);
|
|
141134
|
-
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
141135
|
-
material.setParameter('lineWidth', THICK_WIREFRAME_LINE_WIDTH_UV);
|
|
141136
|
-
material.setParameter('opacity', THICK_WIREFRAME_OPACITY);
|
|
141137
141747
|
material.update();
|
|
141748
|
+
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
141138
141749
|
return material;
|
|
141139
141750
|
}
|
|
141751
|
+
updateSphereShaderUniforms() {
|
|
141752
|
+
if (!this.app)
|
|
141753
|
+
return;
|
|
141754
|
+
const device = this.app.graphicsDevice;
|
|
141755
|
+
device.scope.resolve('targetSize').setValue([device.width, device.height]);
|
|
141756
|
+
device.scope.resolve('blueNoiseTex32').setValue(getBlueNoiseTex32(device));
|
|
141757
|
+
for (const record of this.spheres.values()) {
|
|
141758
|
+
const mat = record.entity.render?.material;
|
|
141759
|
+
if (!mat?.setParameter)
|
|
141760
|
+
continue;
|
|
141761
|
+
record.entity.getWorldTransform().getTranslation(tmpPos);
|
|
141762
|
+
mat.setParameter('sphere', [tmpPos.x, tmpPos.y, tmpPos.z, record.radius]);
|
|
141763
|
+
}
|
|
141764
|
+
}
|
|
141140
141765
|
emitEvent(event, detail) {
|
|
141141
141766
|
if (this.onEvent) {
|
|
141142
141767
|
this.onEvent(event, detail);
|
|
@@ -146763,7 +147388,7 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
146763
147388
|
const { config } = this.scene;
|
|
146764
147389
|
const state = this.viewerEventState;
|
|
146765
147390
|
// Colors and view settings from scene config
|
|
146766
|
-
const selectedClr = config.selectedClr ?? { r: 1, g:
|
|
147391
|
+
const selectedClr = config.selectedClr ?? { r: 1, g: 0.5, b: 0, a: 1 };
|
|
146767
147392
|
const unselectedClr = config.unselectedClr ?? { r: 0, g: 0, b: 1, a: 0.5 };
|
|
146768
147393
|
const lockedClr = config.lockedClr ?? { r: 0, g: 0, b: 0, a: 0.05 };
|
|
146769
147394
|
const bgClr = config.bgClr ?? { r: 0, g: 0, b: 0, a: 1 };
|
|
@@ -147197,9 +147822,10 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
147197
147822
|
this.app = ctx.app;
|
|
147198
147823
|
this.entities.camera = ctx.camera;
|
|
147199
147824
|
const cameraAny = this.entities.camera;
|
|
147200
|
-
|
|
147201
|
-
|
|
147202
|
-
|
|
147825
|
+
// SuperSplat's PCApp omits ScriptComponentSystem, so `addComponent('script')`
|
|
147826
|
+
// will not produce `camera.script.create()`. We keep the attempt (in case
|
|
147827
|
+
// the underlying app changes), but also support a controller-based fallback.
|
|
147828
|
+
if (cameraAny && !cameraAny.script && typeof cameraAny.addComponent === 'function') {
|
|
147203
147829
|
try {
|
|
147204
147830
|
cameraAny.addComponent('script');
|
|
147205
147831
|
}
|
|
@@ -147222,16 +147848,23 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
147222
147848
|
minHeight: DEFAULT_FLY_CAMERA_CONFIG.minHeight,
|
|
147223
147849
|
maxHeight: DEFAULT_FLY_CAMERA_CONFIG.maxHeight,
|
|
147224
147850
|
};
|
|
147225
|
-
|
|
147226
|
-
|
|
147227
|
-
|
|
147228
|
-
|
|
147229
|
-
;
|
|
147230
|
-
this._fly
|
|
147231
|
-
|
|
147232
|
-
|
|
147233
|
-
|
|
147234
|
-
|
|
147851
|
+
// Prefer script-based fly when available; fallback to controller otherwise.
|
|
147852
|
+
const canCreateScript = typeof cameraAny?.script?.create === 'function';
|
|
147853
|
+
if (canCreateScript) {
|
|
147854
|
+
const created = cameraAny.script.create('flyCamera', { attributes: flyAttributes });
|
|
147855
|
+
this._fly = created;
|
|
147856
|
+
if (this._fly) {
|
|
147857
|
+
;
|
|
147858
|
+
this._fly.emitFlyEvent = (type, detail) => {
|
|
147859
|
+
this.emit({ type: type, detail });
|
|
147860
|
+
};
|
|
147861
|
+
this._fly.deactivate?.();
|
|
147862
|
+
}
|
|
147863
|
+
}
|
|
147864
|
+
else {
|
|
147865
|
+
const controller = new FlyCameraController(ctx.app, cameraAny, (type, detail) => this.emit({ type: type, detail }), flyAttributes);
|
|
147866
|
+
controller.deactivate();
|
|
147867
|
+
this._fly = controller;
|
|
147235
147868
|
}
|
|
147236
147869
|
this._cameraMode = 'orbit';
|
|
147237
147870
|
}
|
|
@@ -149100,17 +149733,31 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
149100
149733
|
// Stop supersplat orbit updates + input
|
|
149101
149734
|
this._supersplat.setCameraControlsEnabled(false);
|
|
149102
149735
|
this._supersplat.setCameraManualControl(true);
|
|
149103
|
-
//
|
|
149104
|
-
|
|
149105
|
-
|
|
149106
|
-
if (
|
|
149107
|
-
;
|
|
149108
|
-
|
|
149109
|
-
|
|
149736
|
+
// Preserve camera position and rotation when switching to fly mode
|
|
149737
|
+
if (this._fly) {
|
|
149738
|
+
// For FlyCameraController (fallback path)
|
|
149739
|
+
if (typeof this._fly.syncFromEntity === 'function') {
|
|
149740
|
+
this._fly.syncFromEntity();
|
|
149741
|
+
}
|
|
149742
|
+
else {
|
|
149743
|
+
// For FlyCameraScript (legacy path)
|
|
149744
|
+
try {
|
|
149745
|
+
const pos = this.entities.camera?.getPosition?.();
|
|
149746
|
+
if (pos) {
|
|
149747
|
+
const posVec = pos.clone ? pos.clone() : new Vec3(pos.x || 0, pos.y || 0, pos.z || 0);
|
|
149748
|
+
this.entities.camera?.setPosition?.(posVec);
|
|
149749
|
+
}
|
|
149750
|
+
const euler = this.entities.camera?.getEulerAngles?.();
|
|
149751
|
+
if (euler) {
|
|
149752
|
+
;
|
|
149753
|
+
this._fly._pitch = euler.x || 0;
|
|
149754
|
+
this._fly._yaw = euler.y || 0;
|
|
149755
|
+
}
|
|
149756
|
+
}
|
|
149757
|
+
catch {
|
|
149758
|
+
// ignore
|
|
149759
|
+
}
|
|
149110
149760
|
}
|
|
149111
|
-
}
|
|
149112
|
-
catch {
|
|
149113
|
-
// ignore
|
|
149114
149761
|
}
|
|
149115
149762
|
this._fly?.activate?.();
|
|
149116
149763
|
this._cameraMode = 'fly';
|
|
@@ -149227,9 +149874,48 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
149227
149874
|
this._cameraModeManager?.setFlyConfig(config);
|
|
149228
149875
|
}
|
|
149229
149876
|
getFlyCameraConfig() {
|
|
149877
|
+
// SuperSplat path: fly is either a script instance or controller fallback
|
|
149878
|
+
if (this._supersplat && this._fly) {
|
|
149879
|
+
try {
|
|
149880
|
+
const flyAny = this._fly;
|
|
149881
|
+
const cfg = {
|
|
149882
|
+
moveSpeed: flyAny.moveSpeed,
|
|
149883
|
+
fastSpeedMultiplier: flyAny.fastSpeedMultiplier,
|
|
149884
|
+
slowSpeedMultiplier: flyAny.slowSpeedMultiplier,
|
|
149885
|
+
lookSensitivity: flyAny.lookSensitivity,
|
|
149886
|
+
invertY: !!flyAny.invertY,
|
|
149887
|
+
keyBindings: { ...(flyAny.keyBindings || {}) },
|
|
149888
|
+
smoothing: flyAny.smoothing,
|
|
149889
|
+
friction: flyAny.friction,
|
|
149890
|
+
enableCollision: !!flyAny.enableCollision,
|
|
149891
|
+
minHeight: flyAny.minHeight ?? null,
|
|
149892
|
+
maxHeight: flyAny.maxHeight ?? null,
|
|
149893
|
+
};
|
|
149894
|
+
return cfg;
|
|
149895
|
+
}
|
|
149896
|
+
catch {
|
|
149897
|
+
return null;
|
|
149898
|
+
}
|
|
149899
|
+
}
|
|
149230
149900
|
return this._cameraModeManager?.getFlyConfig() || null;
|
|
149231
149901
|
}
|
|
149232
149902
|
getFlyCameraState() {
|
|
149903
|
+
// SuperSplat path: fly is either a script instance or controller fallback
|
|
149904
|
+
if (this._supersplat && this._fly?.getState) {
|
|
149905
|
+
try {
|
|
149906
|
+
const state = this._fly.getState();
|
|
149907
|
+
return {
|
|
149908
|
+
mode: this._cameraMode,
|
|
149909
|
+
position: state.position,
|
|
149910
|
+
rotation: state.rotation,
|
|
149911
|
+
velocity: state.velocity,
|
|
149912
|
+
isMoving: state.isMoving,
|
|
149913
|
+
};
|
|
149914
|
+
}
|
|
149915
|
+
catch {
|
|
149916
|
+
return null;
|
|
149917
|
+
}
|
|
149918
|
+
}
|
|
149233
149919
|
return this._cameraModeManager?.getFlyState() || null;
|
|
149234
149920
|
}
|
|
149235
149921
|
_setupScene() {
|