@heliguy-xyz/splat-viewer 1.0.0-rc.24 → 1.0.0-rc.26
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/README.md +39 -0
- package/dist/web-component/splat-viewer.esm.js +1420 -681
- package/dist/web-component/splat-viewer.esm.min.js +2 -2
- package/dist/web-component/splat-viewer.js +1420 -681
- 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 +1 -0
- package/dist/web-component/types/web-component/SplatViewerCore.d.ts.map +1 -1
- package/dist/web-component/types/web-component/SplatViewerElement.d.ts +2 -0
- package/dist/web-component/types/web-component/SplatViewerElement.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/types/web-component/types/attributes.d.ts +3 -0
- package/dist/web-component/types/web-component/types/attributes.d.ts.map +1 -1
- package/dist/web-component/types/web-component/types/core.d.ts +2 -0
- package/dist/web-component/types/web-component/types/core.d.ts.map +1 -1
- package/dist/web-component/types/web-component/utils/config.d.ts.map +1 -1
- 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 +1 -0
- package/dist/web-component/web-component/SplatViewerCore.d.ts.map +1 -1
- package/dist/web-component/web-component/SplatViewerElement.d.ts +2 -0
- package/dist/web-component/web-component/SplatViewerElement.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/dist/web-component/web-component/types/attributes.d.ts +3 -0
- package/dist/web-component/web-component/types/attributes.d.ts.map +1 -1
- package/dist/web-component/web-component/types/core.d.ts +2 -0
- package/dist/web-component/web-component/types/core.d.ts.map +1 -1
- package/dist/web-component/web-component/utils/config.d.ts.map +1 -1
- 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
|
}
|
|
@@ -139088,6 +139519,7 @@ const DEFAULT_CONFIG = {
|
|
|
139088
139519
|
enableStats: false,
|
|
139089
139520
|
autoFocus: true,
|
|
139090
139521
|
maxSplats: 2000000,
|
|
139522
|
+
previewMode: false,
|
|
139091
139523
|
camera: {
|
|
139092
139524
|
position: { x: 0, y: 0, z: 10 },
|
|
139093
139525
|
target: { x: 0, y: 0, z: 0 },
|
|
@@ -139177,6 +139609,10 @@ function getConfigFromAttributes(element) {
|
|
|
139177
139609
|
if (enableStats !== null) {
|
|
139178
139610
|
config.enableStats = parseBoolean(enableStats);
|
|
139179
139611
|
}
|
|
139612
|
+
const previewMode = element.getAttribute('preview-mode');
|
|
139613
|
+
if (previewMode !== null) {
|
|
139614
|
+
config.previewMode = parseBoolean(previewMode);
|
|
139615
|
+
}
|
|
139180
139616
|
// Parse number attributes
|
|
139181
139617
|
const maxSplats = element.getAttribute('max-splats');
|
|
139182
139618
|
if (maxSplats !== null) {
|
|
@@ -140317,51 +140753,161 @@ function toPcVec3(value, fallback) {
|
|
|
140317
140753
|
return fallback.clone();
|
|
140318
140754
|
}
|
|
140319
140755
|
|
|
140320
|
-
const
|
|
140321
|
-
|
|
140322
|
-
const
|
|
140323
|
-
|
|
140324
|
-
|
|
140756
|
+
const cache = new WeakMap();
|
|
140757
|
+
function createBlueNoiseTexture(device) {
|
|
140758
|
+
const size = 32;
|
|
140759
|
+
const texture = new Texture$1(device, {
|
|
140760
|
+
width: size,
|
|
140761
|
+
height: size,
|
|
140762
|
+
format: PIXELFORMAT_R8_G8_B8_A8,
|
|
140763
|
+
mipmaps: false,
|
|
140764
|
+
});
|
|
140765
|
+
texture.addressU = ADDRESS_REPEAT;
|
|
140766
|
+
texture.addressV = ADDRESS_REPEAT;
|
|
140767
|
+
texture.minFilter = FILTER_NEAREST;
|
|
140768
|
+
texture.magFilter = FILTER_NEAREST;
|
|
140769
|
+
const pixels = texture.lock();
|
|
140770
|
+
const seed = 1337;
|
|
140771
|
+
let value = seed;
|
|
140772
|
+
const random = () => {
|
|
140773
|
+
value ^= value << 13;
|
|
140774
|
+
value ^= value >>> 17;
|
|
140775
|
+
value ^= value << 5;
|
|
140776
|
+
return ((value >>> 0) % 256) / 255;
|
|
140777
|
+
};
|
|
140778
|
+
for (let i = 0; i < size * size; i++) {
|
|
140779
|
+
const noise = Math.floor(random() * 255);
|
|
140780
|
+
const idx = i * 4;
|
|
140781
|
+
pixels[idx + 0] = noise;
|
|
140782
|
+
pixels[idx + 1] = noise;
|
|
140783
|
+
pixels[idx + 2] = noise;
|
|
140784
|
+
pixels[idx + 3] = 255;
|
|
140785
|
+
}
|
|
140786
|
+
texture.unlock();
|
|
140787
|
+
texture.name = 'supersplat-blue-noise';
|
|
140788
|
+
return texture;
|
|
140789
|
+
}
|
|
140790
|
+
function getBlueNoiseTex32(device) {
|
|
140791
|
+
const existing = cache.get(device);
|
|
140792
|
+
if (existing)
|
|
140793
|
+
return existing;
|
|
140794
|
+
const tex = createBlueNoiseTexture(device);
|
|
140795
|
+
cache.set(device, tex);
|
|
140796
|
+
return tex;
|
|
140797
|
+
}
|
|
140325
140798
|
|
|
140326
|
-
|
|
140327
|
-
|
|
140799
|
+
// SuperSplat-like selection box shader: screen-space ray/box intersection that draws a thick
|
|
140800
|
+
// white wire/grid pattern (not dependent on WebGL line width).
|
|
140801
|
+
const BOX_SELECT_VS = /* glsl */ `
|
|
140802
|
+
attribute vec3 vertex_position;
|
|
140328
140803
|
|
|
140329
|
-
|
|
140804
|
+
uniform mat4 matrix_model;
|
|
140805
|
+
uniform mat4 matrix_viewProjection;
|
|
140330
140806
|
|
|
140331
|
-
void main(
|
|
140332
|
-
|
|
140333
|
-
|
|
140334
|
-
}
|
|
140807
|
+
void main() {
|
|
140808
|
+
gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
|
|
140809
|
+
}
|
|
140335
140810
|
`;
|
|
140336
|
-
const
|
|
140337
|
-
|
|
140338
|
-
|
|
140339
|
-
|
|
140811
|
+
const BOX_SELECT_FS = /* glsl */ `
|
|
140812
|
+
// ray-box intersection in box space
|
|
140813
|
+
bool intersectBox(out float t0, out float t1, out int axis0, out int axis1, vec3 pos, vec3 dir, vec3 boxCen, vec3 boxLen)
|
|
140814
|
+
{
|
|
140815
|
+
bvec3 validDir = notEqual(dir, vec3(0.0));
|
|
140816
|
+
vec3 absDir = abs(dir);
|
|
140817
|
+
vec3 signDir = sign(dir);
|
|
140818
|
+
vec3 m = vec3(
|
|
140819
|
+
validDir.x ? 1.0 / absDir.x : 0.0,
|
|
140820
|
+
validDir.y ? 1.0 / absDir.y : 0.0,
|
|
140821
|
+
validDir.z ? 1.0 / absDir.z : 0.0
|
|
140822
|
+
) * signDir;
|
|
140340
140823
|
|
|
140341
|
-
|
|
140824
|
+
vec3 n = m * (pos - boxCen);
|
|
140825
|
+
vec3 k = abs(m) * boxLen;
|
|
140342
140826
|
|
|
140343
|
-
|
|
140344
|
-
|
|
140345
|
-
uniform float opacity;
|
|
140827
|
+
vec3 v0 = -n - k;
|
|
140828
|
+
vec3 v1 = -n + k;
|
|
140346
140829
|
|
|
140347
|
-
|
|
140830
|
+
// replace invalid axes with -inf and +inf so the tests below ignore them
|
|
140831
|
+
v0 = mix(vec3(-1.0 / 0.0000001), v0, validDir);
|
|
140832
|
+
v1 = mix(vec3(1.0 / 0.0000001), v1, validDir);
|
|
140348
140833
|
|
|
140349
|
-
|
|
140350
|
-
|
|
140351
|
-
float d = min(min(vUv.x, 1.0 - vUv.x), min(vUv.y, 1.0 - vUv.y));
|
|
140834
|
+
axis0 = (v0.x > v0.y) ? ((v0.x > v0.z) ? 0 : 2) : ((v0.y > v0.z) ? 1 : 2);
|
|
140835
|
+
axis1 = (v1.x < v1.y) ? ((v1.x < v1.z) ? 0 : 2) : ((v1.y < v1.z) ? 1 : 2);
|
|
140352
140836
|
|
|
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;
|
|
140837
|
+
t0 = v0[axis0];
|
|
140838
|
+
t1 = v1[axis1];
|
|
140360
140839
|
|
|
140361
|
-
|
|
140362
|
-
|
|
140363
|
-
}
|
|
140840
|
+
if (t0 > t1 || t1 < 0.0) {
|
|
140841
|
+
return false;
|
|
140842
|
+
}
|
|
140843
|
+
|
|
140844
|
+
return true;
|
|
140845
|
+
}
|
|
140846
|
+
|
|
140847
|
+
float calcDepth(in vec3 pos, in mat4 viewProjection) {
|
|
140848
|
+
vec4 v = viewProjection * vec4(pos, 1.0);
|
|
140849
|
+
return (v.z / v.w) * 0.5 + 0.5;
|
|
140850
|
+
}
|
|
140851
|
+
|
|
140852
|
+
uniform sampler2D blueNoiseTex32;
|
|
140853
|
+
uniform mat4 matrix_viewProjection;
|
|
140854
|
+
uniform vec3 boxCen;
|
|
140855
|
+
uniform vec3 boxLen;
|
|
140856
|
+
|
|
140857
|
+
uniform vec3 near_origin;
|
|
140858
|
+
uniform vec3 near_x;
|
|
140859
|
+
uniform vec3 near_y;
|
|
140860
|
+
|
|
140861
|
+
uniform vec3 far_origin;
|
|
140862
|
+
uniform vec3 far_x;
|
|
140863
|
+
uniform vec3 far_y;
|
|
140864
|
+
|
|
140865
|
+
uniform vec2 targetSize;
|
|
140866
|
+
uniform vec3 lineColor;
|
|
140867
|
+
|
|
140868
|
+
bool writeDepth(float alpha) {
|
|
140869
|
+
ivec2 uv = ivec2(gl_FragCoord.xy);
|
|
140870
|
+
ivec2 size = textureSize(blueNoiseTex32, 0);
|
|
140871
|
+
return alpha > texelFetch(blueNoiseTex32, uv % size, 0).y;
|
|
140872
|
+
}
|
|
140873
|
+
|
|
140874
|
+
bool strips(vec3 pos, int axis) {
|
|
140875
|
+
// Thickness tuned to match SuperSplat viewer "thick wire"
|
|
140876
|
+
bvec3 b = lessThan(fract(pos * 2.0 + vec3(0.015)), vec3(0.06));
|
|
140877
|
+
b[axis] = false;
|
|
140878
|
+
return any(b);
|
|
140879
|
+
}
|
|
140880
|
+
|
|
140881
|
+
void main() {
|
|
140882
|
+
vec2 clip = gl_FragCoord.xy / targetSize;
|
|
140883
|
+
vec3 worldNear = near_origin + near_x * clip.x + near_y * clip.y;
|
|
140884
|
+
vec3 worldFar = far_origin + far_x * clip.x + far_y * clip.y;
|
|
140885
|
+
vec3 rayDir = normalize(worldFar - worldNear);
|
|
140886
|
+
|
|
140887
|
+
float t0, t1;
|
|
140888
|
+
int axis0, axis1;
|
|
140889
|
+
if (!intersectBox(t0, t1, axis0, axis1, worldNear, rayDir, boxCen, boxLen)) {
|
|
140890
|
+
discard;
|
|
140891
|
+
}
|
|
140892
|
+
|
|
140893
|
+
vec3 frontPos = worldNear + rayDir * t0;
|
|
140894
|
+
bool front = t0 > 0.0 && strips(frontPos - boxCen, axis0);
|
|
140895
|
+
|
|
140896
|
+
vec3 backPos = worldNear + rayDir * t1;
|
|
140897
|
+
bool back = strips(backPos - boxCen, axis1);
|
|
140898
|
+
|
|
140899
|
+
if (front) {
|
|
140900
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
140901
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(frontPos, matrix_viewProjection) : 1.0;
|
|
140902
|
+
} else if (back) {
|
|
140903
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
140904
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(backPos, matrix_viewProjection) : 1.0;
|
|
140905
|
+
} else {
|
|
140906
|
+
discard;
|
|
140907
|
+
}
|
|
140908
|
+
}
|
|
140364
140909
|
`;
|
|
140910
|
+
const tmpPos$1 = new Vec3();
|
|
140365
140911
|
class BoxSelectionAPI {
|
|
140366
140912
|
constructor() {
|
|
140367
140913
|
this.boxes = new Map();
|
|
@@ -140398,6 +140944,7 @@ class BoxSelectionAPI {
|
|
|
140398
140944
|
this.ensureTranslateGizmo();
|
|
140399
140945
|
this.updateGizmoSize();
|
|
140400
140946
|
this.updateGizmoAttachment();
|
|
140947
|
+
this.updateBoxShaderUniforms();
|
|
140401
140948
|
}
|
|
140402
140949
|
createBox(options = {}) {
|
|
140403
140950
|
if (!this.app || !this.parent) {
|
|
@@ -140420,7 +140967,7 @@ class BoxSelectionAPI {
|
|
|
140420
140967
|
entity.render.castShadows = false;
|
|
140421
140968
|
entity.render.receiveShadows = false;
|
|
140422
140969
|
entity.render.enabled = visible;
|
|
140423
|
-
// Render as a thick wireframe (shader-based)
|
|
140970
|
+
// Render as a thick wireframe (shader-based) to match SuperSplat viewer.
|
|
140424
140971
|
this.parent.addChild(entity);
|
|
140425
140972
|
const record = {
|
|
140426
140973
|
id,
|
|
@@ -140507,8 +141054,8 @@ class BoxSelectionAPI {
|
|
|
140507
141054
|
return false;
|
|
140508
141055
|
}
|
|
140509
141056
|
record.color.set(r, g, b);
|
|
140510
|
-
const mat =
|
|
140511
|
-
record.
|
|
141057
|
+
const mat = record.entity.render?.material;
|
|
141058
|
+
mat?.setParameter?.('lineColor', [record.color.x, record.color.y, record.color.z]);
|
|
140512
141059
|
this.requestRender();
|
|
140513
141060
|
return true;
|
|
140514
141061
|
}
|
|
@@ -140703,19 +141250,34 @@ class BoxSelectionAPI {
|
|
|
140703
141250
|
}
|
|
140704
141251
|
buildWireframeMaterial(color) {
|
|
140705
141252
|
const material = new ShaderMaterial$1({
|
|
140706
|
-
uniqueName: '
|
|
140707
|
-
vertexGLSL:
|
|
140708
|
-
fragmentGLSL:
|
|
141253
|
+
uniqueName: 'boxSelectionSupersplatWire',
|
|
141254
|
+
vertexGLSL: BOX_SELECT_VS,
|
|
141255
|
+
fragmentGLSL: BOX_SELECT_FS,
|
|
140709
141256
|
});
|
|
140710
|
-
material.cull =
|
|
140711
|
-
material.depthWrite = false;
|
|
141257
|
+
material.cull = CULLFACE_FRONT;
|
|
140712
141258
|
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
141259
|
material.update();
|
|
141260
|
+
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
140717
141261
|
return material;
|
|
140718
141262
|
}
|
|
141263
|
+
updateBoxShaderUniforms() {
|
|
141264
|
+
if (!this.app)
|
|
141265
|
+
return;
|
|
141266
|
+
const device = this.app.graphicsDevice;
|
|
141267
|
+
// Ensure required global uniforms for selection shaders.
|
|
141268
|
+
device.scope.resolve('targetSize').setValue([device.width, device.height]);
|
|
141269
|
+
device.scope.resolve('blueNoiseTex32').setValue(getBlueNoiseTex32(device));
|
|
141270
|
+
for (const record of this.boxes.values()) {
|
|
141271
|
+
const mat = record.entity.render?.material;
|
|
141272
|
+
if (!mat?.setParameter)
|
|
141273
|
+
continue;
|
|
141274
|
+
// World-space center
|
|
141275
|
+
record.entity.getWorldTransform().getTranslation(tmpPos$1);
|
|
141276
|
+
mat.setParameter('boxCen', [tmpPos$1.x, tmpPos$1.y, tmpPos$1.z]);
|
|
141277
|
+
// Half extents
|
|
141278
|
+
mat.setParameter('boxLen', [record.lenX * 0.5, record.lenY * 0.5, record.lenZ * 0.5]);
|
|
141279
|
+
}
|
|
141280
|
+
}
|
|
140719
141281
|
emitEvent(event, detail) {
|
|
140720
141282
|
if (this.onEvent) {
|
|
140721
141283
|
this.onEvent(event, detail);
|
|
@@ -140727,59 +141289,110 @@ class BoxSelectionAPI {
|
|
|
140727
141289
|
}
|
|
140728
141290
|
}
|
|
140729
141291
|
|
|
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;
|
|
141292
|
+
// SuperSplat-like selection sphere shader: screen-space ray/sphere intersection that draws a thick
|
|
141293
|
+
// white wire/grid pattern (not dependent on WebGL line width).
|
|
141294
|
+
const SPHERE_SELECT_VS = /* glsl */ `
|
|
141295
|
+
attribute vec3 vertex_position;
|
|
140740
141296
|
|
|
140741
|
-
|
|
141297
|
+
uniform mat4 matrix_model;
|
|
141298
|
+
uniform mat4 matrix_viewProjection;
|
|
140742
141299
|
|
|
140743
|
-
void main(
|
|
140744
|
-
|
|
140745
|
-
|
|
140746
|
-
}
|
|
141300
|
+
void main() {
|
|
141301
|
+
gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
|
|
141302
|
+
}
|
|
140747
141303
|
`;
|
|
140748
|
-
const
|
|
140749
|
-
|
|
140750
|
-
|
|
140751
|
-
|
|
141304
|
+
const SPHERE_SELECT_FS = /* glsl */ `
|
|
141305
|
+
bool intersectSphere(out float t0, out float t1, vec3 pos, vec3 dir, vec4 sphere) {
|
|
141306
|
+
vec3 L = sphere.xyz - pos;
|
|
141307
|
+
float tca = dot(L, dir);
|
|
140752
141308
|
|
|
140753
|
-
|
|
141309
|
+
float d2 = sphere.w * sphere.w - (dot(L, L) - tca * tca);
|
|
141310
|
+
if (d2 <= 0.0) {
|
|
141311
|
+
return false;
|
|
141312
|
+
}
|
|
140754
141313
|
|
|
140755
|
-
|
|
140756
|
-
|
|
140757
|
-
|
|
141314
|
+
float thc = sqrt(d2);
|
|
141315
|
+
t0 = tca - thc;
|
|
141316
|
+
t1 = tca + thc;
|
|
141317
|
+
if (t1 <= 0.0) {
|
|
141318
|
+
return false;
|
|
141319
|
+
}
|
|
140758
141320
|
|
|
140759
|
-
|
|
141321
|
+
return true;
|
|
141322
|
+
}
|
|
140760
141323
|
|
|
140761
|
-
float
|
|
140762
|
-
|
|
140763
|
-
|
|
140764
|
-
}
|
|
141324
|
+
float calcDepth(in vec3 pos, in mat4 viewProjection) {
|
|
141325
|
+
vec4 v = viewProjection * vec4(pos, 1.0);
|
|
141326
|
+
return (v.z / v.w) * 0.5 + 0.5;
|
|
141327
|
+
}
|
|
140765
141328
|
|
|
140766
|
-
|
|
140767
|
-
|
|
140768
|
-
|
|
140769
|
-
|
|
140770
|
-
|
|
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;
|
|
141329
|
+
vec2 calcAzimuthElev(in vec3 dir) {
|
|
141330
|
+
float azimuth = atan(dir.z, dir.x);
|
|
141331
|
+
float elev = asin(dir.y);
|
|
141332
|
+
return vec2(azimuth, elev) * 180.0 / 3.14159;
|
|
141333
|
+
}
|
|
140778
141334
|
|
|
140779
|
-
|
|
140780
|
-
|
|
140781
|
-
|
|
141335
|
+
uniform sampler2D blueNoiseTex32;
|
|
141336
|
+
uniform mat4 matrix_viewProjection;
|
|
141337
|
+
uniform vec4 sphere;
|
|
141338
|
+
uniform vec3 lineColor;
|
|
141339
|
+
|
|
141340
|
+
uniform vec3 near_origin;
|
|
141341
|
+
uniform vec3 near_x;
|
|
141342
|
+
uniform vec3 near_y;
|
|
141343
|
+
|
|
141344
|
+
uniform vec3 far_origin;
|
|
141345
|
+
uniform vec3 far_x;
|
|
141346
|
+
uniform vec3 far_y;
|
|
141347
|
+
|
|
141348
|
+
uniform vec2 targetSize;
|
|
141349
|
+
|
|
141350
|
+
bool writeDepth(float alpha) {
|
|
141351
|
+
vec2 uv = fract(gl_FragCoord.xy / 32.0);
|
|
141352
|
+
float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
|
|
141353
|
+
return alpha > noise;
|
|
141354
|
+
}
|
|
141355
|
+
|
|
141356
|
+
bool strips(vec3 lp) {
|
|
141357
|
+
vec2 ae = calcAzimuthElev(normalize(lp));
|
|
141358
|
+
|
|
141359
|
+
float spacing = 180.0 / (2.0 * 3.14159 * sphere.w);
|
|
141360
|
+
// Thickness tuned to match SuperSplat viewer "thick wire"
|
|
141361
|
+
float size = 0.06;
|
|
141362
|
+
return fract(ae.x / spacing) < size ||
|
|
141363
|
+
fract(ae.y / spacing) < size;
|
|
141364
|
+
}
|
|
141365
|
+
|
|
141366
|
+
void main() {
|
|
141367
|
+
vec2 clip = gl_FragCoord.xy / targetSize;
|
|
141368
|
+
vec3 worldNear = near_origin + near_x * clip.x + near_y * clip.y;
|
|
141369
|
+
vec3 worldFar = far_origin + far_x * clip.x + far_y * clip.y;
|
|
141370
|
+
|
|
141371
|
+
vec3 rayDir = normalize(worldFar - worldNear);
|
|
141372
|
+
|
|
141373
|
+
float t0, t1;
|
|
141374
|
+
if (!intersectSphere(t0, t1, worldNear, rayDir, sphere)) {
|
|
141375
|
+
discard;
|
|
141376
|
+
}
|
|
141377
|
+
|
|
141378
|
+
vec3 frontPos = worldNear + rayDir * t0;
|
|
141379
|
+
bool front = t0 > 0.0 && strips(frontPos - sphere.xyz);
|
|
141380
|
+
|
|
141381
|
+
vec3 backPos = worldNear + rayDir * t1;
|
|
141382
|
+
bool back = strips(backPos - sphere.xyz);
|
|
141383
|
+
|
|
141384
|
+
if (front) {
|
|
141385
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
141386
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(frontPos, matrix_viewProjection) : 1.0;
|
|
141387
|
+
} else if (back) {
|
|
141388
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
141389
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(backPos, matrix_viewProjection) : 1.0;
|
|
141390
|
+
} else {
|
|
141391
|
+
discard;
|
|
141392
|
+
}
|
|
141393
|
+
}
|
|
140782
141394
|
`;
|
|
141395
|
+
const tmpPos = new Vec3();
|
|
140783
141396
|
class SphereSelectionAPI {
|
|
140784
141397
|
constructor() {
|
|
140785
141398
|
this.spheres = new Map();
|
|
@@ -140815,6 +141428,7 @@ class SphereSelectionAPI {
|
|
|
140815
141428
|
this.ensureTranslateGizmo();
|
|
140816
141429
|
this.updateGizmoSize();
|
|
140817
141430
|
this.updateGizmoAttachment();
|
|
141431
|
+
this.updateSphereShaderUniforms();
|
|
140818
141432
|
}
|
|
140819
141433
|
createSphere(options = {}) {
|
|
140820
141434
|
if (!this.app || !this.parent) {
|
|
@@ -140827,7 +141441,8 @@ class SphereSelectionAPI {
|
|
|
140827
141441
|
const visible = options.visible ?? true;
|
|
140828
141442
|
const entity = new Entity(id);
|
|
140829
141443
|
entity.addComponent('render', {
|
|
140830
|
-
|
|
141444
|
+
// Use a box proxy like SuperSplat; shader ray-marches a true sphere.
|
|
141445
|
+
type: 'box',
|
|
140831
141446
|
material: this.buildWireframeMaterial(color),
|
|
140832
141447
|
});
|
|
140833
141448
|
entity.setLocalScale(radius * 2, radius * 2, radius * 2); // sphere diameter = radius * 2
|
|
@@ -140835,7 +141450,7 @@ class SphereSelectionAPI {
|
|
|
140835
141450
|
entity.render.castShadows = false;
|
|
140836
141451
|
entity.render.receiveShadows = false;
|
|
140837
141452
|
entity.render.enabled = visible;
|
|
140838
|
-
// Render as a thick wireframe (shader-based)
|
|
141453
|
+
// Render as a thick wireframe (shader-based) to match SuperSplat viewer.
|
|
140839
141454
|
this.parent.addChild(entity);
|
|
140840
141455
|
const record = {
|
|
140841
141456
|
id,
|
|
@@ -140918,8 +141533,12 @@ class SphereSelectionAPI {
|
|
|
140918
141533
|
return false;
|
|
140919
141534
|
}
|
|
140920
141535
|
record.color.set(r, g, b);
|
|
140921
|
-
const mat =
|
|
140922
|
-
|
|
141536
|
+
const mat = record.entity.render?.material;
|
|
141537
|
+
mat?.setParameter?.('lineColor', [
|
|
141538
|
+
record.color.x,
|
|
141539
|
+
record.color.y,
|
|
141540
|
+
record.color.z,
|
|
141541
|
+
]);
|
|
140923
141542
|
this.requestRender();
|
|
140924
141543
|
return true;
|
|
140925
141544
|
}
|
|
@@ -141117,19 +141736,30 @@ class SphereSelectionAPI {
|
|
|
141117
141736
|
}
|
|
141118
141737
|
buildWireframeMaterial(color) {
|
|
141119
141738
|
const material = new ShaderMaterial$1({
|
|
141120
|
-
uniqueName: '
|
|
141121
|
-
vertexGLSL:
|
|
141122
|
-
fragmentGLSL:
|
|
141739
|
+
uniqueName: 'sphereSelectionSupersplatWire',
|
|
141740
|
+
vertexGLSL: SPHERE_SELECT_VS,
|
|
141741
|
+
fragmentGLSL: SPHERE_SELECT_FS,
|
|
141123
141742
|
});
|
|
141124
|
-
material.cull =
|
|
141125
|
-
material.depthWrite = false;
|
|
141743
|
+
material.cull = CULLFACE_FRONT;
|
|
141126
141744
|
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
141745
|
material.update();
|
|
141746
|
+
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
141131
141747
|
return material;
|
|
141132
141748
|
}
|
|
141749
|
+
updateSphereShaderUniforms() {
|
|
141750
|
+
if (!this.app)
|
|
141751
|
+
return;
|
|
141752
|
+
const device = this.app.graphicsDevice;
|
|
141753
|
+
device.scope.resolve('targetSize').setValue([device.width, device.height]);
|
|
141754
|
+
device.scope.resolve('blueNoiseTex32').setValue(getBlueNoiseTex32(device));
|
|
141755
|
+
for (const record of this.spheres.values()) {
|
|
141756
|
+
const mat = record.entity.render?.material;
|
|
141757
|
+
if (!mat?.setParameter)
|
|
141758
|
+
continue;
|
|
141759
|
+
record.entity.getWorldTransform().getTranslation(tmpPos);
|
|
141760
|
+
mat.setParameter('sphere', [tmpPos.x, tmpPos.y, tmpPos.z, record.radius]);
|
|
141761
|
+
}
|
|
141762
|
+
}
|
|
141133
141763
|
emitEvent(event, detail) {
|
|
141134
141764
|
if (this.onEvent) {
|
|
141135
141765
|
this.onEvent(event, detail);
|
|
@@ -142536,222 +143166,222 @@ class AssetLoader {
|
|
|
142536
143166
|
}
|
|
142537
143167
|
}
|
|
142538
143168
|
|
|
142539
|
-
const vertexShader$6 = /* glsl */ `
|
|
142540
|
-
attribute vec2 vertex_position;
|
|
142541
|
-
void main(void) {
|
|
142542
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
142543
|
-
}
|
|
142544
|
-
`;
|
|
142545
|
-
const fragmentShader$6 = /* glsl */ `
|
|
142546
|
-
uniform highp usampler2D transformA; // splat center x, y, z
|
|
142547
|
-
uniform highp usampler2D splatTransform; // transform palette index
|
|
142548
|
-
uniform sampler2D transformPalette; // palette of transforms
|
|
142549
|
-
uniform sampler2D splatState; // per-splat state
|
|
142550
|
-
uniform highp ivec3 splat_params; // texture width, texture height, num splats
|
|
142551
|
-
uniform highp uint mode; // 0: selected, 1: visible
|
|
142552
|
-
|
|
142553
|
-
// calculate min and max for a single column of splats
|
|
142554
|
-
void main(void) {
|
|
142555
|
-
|
|
142556
|
-
vec3 boundMin = vec3(1e6);
|
|
142557
|
-
vec3 boundMax = vec3(-1e6);
|
|
142558
|
-
|
|
142559
|
-
for (int id = 0; id < splat_params.y; id++) {
|
|
142560
|
-
// calculate splatUV
|
|
142561
|
-
ivec2 splatUV = ivec2(gl_FragCoord.x, id);
|
|
142562
|
-
|
|
142563
|
-
// skip out-of-range splats
|
|
142564
|
-
if ((splatUV.x + splatUV.y * splat_params.x) >= splat_params.z) {
|
|
142565
|
-
continue;
|
|
142566
|
-
}
|
|
142567
|
-
|
|
142568
|
-
// read splat state
|
|
142569
|
-
uint state = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
|
|
142570
|
-
|
|
142571
|
-
// skip deleted or locked splats
|
|
142572
|
-
if (((mode == 0u) && (state != 1u)) || ((mode == 1u) && ((state & 4u) != 0u))) {
|
|
142573
|
-
continue;
|
|
142574
|
-
}
|
|
142575
|
-
|
|
142576
|
-
// read splat center
|
|
142577
|
-
vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
|
|
142578
|
-
|
|
142579
|
-
// apply optional per-splat transform
|
|
142580
|
-
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
142581
|
-
if (transformIndex > 0u) {
|
|
142582
|
-
// read transform matrix
|
|
142583
|
-
int u = int(transformIndex % 512u) * 3;
|
|
142584
|
-
int v = int(transformIndex / 512u);
|
|
142585
|
-
|
|
142586
|
-
mat3x4 t;
|
|
142587
|
-
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
142588
|
-
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
142589
|
-
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
142590
|
-
|
|
142591
|
-
center = vec4(center, 1.0) * t;
|
|
142592
|
-
}
|
|
142593
|
-
|
|
142594
|
-
boundMin = min(boundMin, mix(center, boundMin, isinf(center)));
|
|
142595
|
-
boundMax = max(boundMax, mix(center, boundMax, isinf(center)));
|
|
142596
|
-
}
|
|
142597
|
-
|
|
142598
|
-
pcFragColor0 = vec4(boundMin, 0.0);
|
|
142599
|
-
pcFragColor1 = vec4(boundMax, 0.0);
|
|
142600
|
-
}
|
|
142601
|
-
`;
|
|
142602
|
-
|
|
142603
|
-
const vertexShader$5 = /* glsl */ `
|
|
142604
|
-
attribute vec2 vertex_position;
|
|
142605
|
-
void main(void) {
|
|
142606
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
142607
|
-
}
|
|
142608
|
-
`;
|
|
142609
|
-
const fragmentShader$5 = /* glsl */ `
|
|
142610
|
-
uniform highp usampler2D transformA; // splat center x, y, z
|
|
142611
|
-
uniform highp usampler2D splatTransform; // transform palette index
|
|
142612
|
-
uniform sampler2D transformPalette; // palette of transforms
|
|
142613
|
-
uniform uvec2 splat_params; // splat texture width, num splats
|
|
142614
|
-
|
|
142615
|
-
uniform mat4 matrix_model;
|
|
142616
|
-
uniform mat4 matrix_viewProjection;
|
|
142617
|
-
|
|
142618
|
-
uniform uvec2 output_params; // output width, height
|
|
142619
|
-
|
|
142620
|
-
// 0: mask, 1: rect, 2: sphere
|
|
142621
|
-
uniform int mode;
|
|
142622
|
-
|
|
142623
|
-
// mask params
|
|
142624
|
-
uniform sampler2D mask; // mask in alpha channel
|
|
142625
|
-
uniform vec2 mask_params; // mask width, height
|
|
142626
|
-
|
|
142627
|
-
// rect params
|
|
142628
|
-
uniform vec4 rect_params; // rect x, y, width, height
|
|
142629
|
-
|
|
142630
|
-
// sphere params
|
|
142631
|
-
uniform vec4 sphere_params; // sphere x, y, z, radius
|
|
142632
|
-
|
|
142633
|
-
// box params
|
|
142634
|
-
uniform vec4 box_params; // box x, y, z
|
|
142635
|
-
uniform vec4 aabb_params; // len x, y, z
|
|
142636
|
-
|
|
142637
|
-
void main(void) {
|
|
142638
|
-
// calculate output id
|
|
142639
|
-
uvec2 outputUV = uvec2(gl_FragCoord);
|
|
142640
|
-
uint outputId = (outputUV.x + outputUV.y * output_params.x) * 4u;
|
|
142641
|
-
|
|
142642
|
-
vec4 clr = vec4(0.0);
|
|
142643
|
-
|
|
142644
|
-
for (uint i = 0u; i < 4u; i++) {
|
|
142645
|
-
uint id = outputId + i;
|
|
142646
|
-
|
|
142647
|
-
if (id >= splat_params.y) {
|
|
142648
|
-
continue;
|
|
142649
|
-
}
|
|
142650
|
-
|
|
142651
|
-
// calculate splatUV
|
|
142652
|
-
ivec2 splatUV = ivec2(
|
|
142653
|
-
int(id % splat_params.x),
|
|
142654
|
-
int(id / splat_params.x)
|
|
142655
|
-
);
|
|
142656
|
-
|
|
142657
|
-
// read splat center
|
|
142658
|
-
vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
|
|
142659
|
-
|
|
142660
|
-
// apply optional per-splat transform
|
|
142661
|
-
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
142662
|
-
if (transformIndex > 0u) {
|
|
142663
|
-
// read transform matrix
|
|
142664
|
-
int u = int(transformIndex % 512u) * 3;
|
|
142665
|
-
int v = int(transformIndex / 512u);
|
|
142666
|
-
|
|
142667
|
-
mat3x4 t;
|
|
142668
|
-
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
142669
|
-
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
142670
|
-
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
142671
|
-
|
|
142672
|
-
center = vec4(center, 1.0) * t;
|
|
142673
|
-
}
|
|
142674
|
-
|
|
142675
|
-
// transform to clip space and discard if outside
|
|
142676
|
-
vec3 world = (matrix_model * vec4(center, 1.0)).xyz;
|
|
142677
|
-
vec4 clip = matrix_viewProjection * vec4(world, 1.0);
|
|
142678
|
-
vec3 ndc = clip.xyz / clip.w;
|
|
142679
|
-
|
|
142680
|
-
// skip offscreen fragments
|
|
142681
|
-
if (!any(greaterThan(abs(ndc), vec3(1.0)))) {
|
|
142682
|
-
if (mode == 0) {
|
|
142683
|
-
// select by mask
|
|
142684
|
-
ivec2 maskUV = ivec2((ndc.xy * vec2(0.5, -0.5) + 0.5) * mask_params);
|
|
142685
|
-
clr[i] = texelFetch(mask, maskUV, 0).a < 1.0 ? 0.0 : 1.0;
|
|
142686
|
-
} else if (mode == 1) {
|
|
142687
|
-
// select by rect
|
|
142688
|
-
clr[i] = all(greaterThan(ndc.xy * vec2(1.0, -1.0), rect_params.xy)) && all(lessThan(ndc.xy * vec2(1.0, -1.0), rect_params.zw)) ? 1.0 : 0.0;
|
|
142689
|
-
} else if (mode == 2) {
|
|
142690
|
-
// select by sphere
|
|
142691
|
-
clr[i] = length(world - sphere_params.xyz) < sphere_params.w ? 1.0 : 0.0;
|
|
142692
|
-
} else if (mode == 3) {
|
|
142693
|
-
// select by box
|
|
142694
|
-
vec3 relativePosition = world - box_params.xyz;
|
|
142695
|
-
bool isInsideCube = true;
|
|
142696
|
-
if (relativePosition.x < -aabb_params.x || relativePosition.x > aabb_params.x) {
|
|
142697
|
-
isInsideCube = false;
|
|
142698
|
-
}
|
|
142699
|
-
if (relativePosition.y < -aabb_params.y || relativePosition.y > aabb_params.y) {
|
|
142700
|
-
isInsideCube = false;
|
|
142701
|
-
}
|
|
142702
|
-
if (relativePosition.z < -aabb_params.z || relativePosition.z > aabb_params.z) {
|
|
142703
|
-
isInsideCube = false;
|
|
142704
|
-
}
|
|
142705
|
-
clr[i] = isInsideCube ? 1.0 : 0.0;
|
|
142706
|
-
}
|
|
142707
|
-
}
|
|
142708
|
-
}
|
|
142709
|
-
|
|
142710
|
-
gl_FragColor = clr;
|
|
142711
|
-
}
|
|
142712
|
-
`;
|
|
142713
|
-
|
|
142714
|
-
const vertexShader$4 = /* glsl */ `
|
|
142715
|
-
attribute vec2 vertex_position;
|
|
142716
|
-
void main(void) {
|
|
142717
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
142718
|
-
}
|
|
142719
|
-
`;
|
|
142720
|
-
const fragmentShader$4 = /* glsl */ `
|
|
142721
|
-
uniform highp usampler2D transformA; // splat center x, y, z
|
|
142722
|
-
uniform highp usampler2D splatTransform; // transform palette index
|
|
142723
|
-
uniform sampler2D transformPalette; // palette of transforms
|
|
142724
|
-
uniform ivec2 splat_params; // splat texture width, num splats
|
|
142725
|
-
|
|
142726
|
-
void main(void) {
|
|
142727
|
-
// calculate output id
|
|
142728
|
-
ivec2 splatUV = ivec2(gl_FragCoord);
|
|
142729
|
-
|
|
142730
|
-
// skip if splat index is out of bounds
|
|
142731
|
-
if (splatUV.x + splatUV.y * splat_params.x >= splat_params.y) {
|
|
142732
|
-
discard;
|
|
142733
|
-
}
|
|
142734
|
-
|
|
142735
|
-
// read splat center
|
|
142736
|
-
vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
|
|
142737
|
-
|
|
142738
|
-
// apply optional per-splat transform
|
|
142739
|
-
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
142740
|
-
if (transformIndex > 0u) {
|
|
142741
|
-
// read transform matrix
|
|
142742
|
-
int u = int(transformIndex % 512u) * 3;
|
|
142743
|
-
int v = int(transformIndex / 512u);
|
|
142744
|
-
|
|
142745
|
-
mat3x4 t;
|
|
142746
|
-
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
142747
|
-
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
142748
|
-
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
142749
|
-
|
|
142750
|
-
center = vec4(center, 1.0) * t;
|
|
142751
|
-
}
|
|
142752
|
-
|
|
142753
|
-
gl_FragColor = vec4(center, 0.0);
|
|
142754
|
-
}
|
|
143169
|
+
const vertexShader$6 = /* glsl */ `
|
|
143170
|
+
attribute vec2 vertex_position;
|
|
143171
|
+
void main(void) {
|
|
143172
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
143173
|
+
}
|
|
143174
|
+
`;
|
|
143175
|
+
const fragmentShader$6 = /* glsl */ `
|
|
143176
|
+
uniform highp usampler2D transformA; // splat center x, y, z
|
|
143177
|
+
uniform highp usampler2D splatTransform; // transform palette index
|
|
143178
|
+
uniform sampler2D transformPalette; // palette of transforms
|
|
143179
|
+
uniform sampler2D splatState; // per-splat state
|
|
143180
|
+
uniform highp ivec3 splat_params; // texture width, texture height, num splats
|
|
143181
|
+
uniform highp uint mode; // 0: selected, 1: visible
|
|
143182
|
+
|
|
143183
|
+
// calculate min and max for a single column of splats
|
|
143184
|
+
void main(void) {
|
|
143185
|
+
|
|
143186
|
+
vec3 boundMin = vec3(1e6);
|
|
143187
|
+
vec3 boundMax = vec3(-1e6);
|
|
143188
|
+
|
|
143189
|
+
for (int id = 0; id < splat_params.y; id++) {
|
|
143190
|
+
// calculate splatUV
|
|
143191
|
+
ivec2 splatUV = ivec2(gl_FragCoord.x, id);
|
|
143192
|
+
|
|
143193
|
+
// skip out-of-range splats
|
|
143194
|
+
if ((splatUV.x + splatUV.y * splat_params.x) >= splat_params.z) {
|
|
143195
|
+
continue;
|
|
143196
|
+
}
|
|
143197
|
+
|
|
143198
|
+
// read splat state
|
|
143199
|
+
uint state = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
|
|
143200
|
+
|
|
143201
|
+
// skip deleted or locked splats
|
|
143202
|
+
if (((mode == 0u) && (state != 1u)) || ((mode == 1u) && ((state & 4u) != 0u))) {
|
|
143203
|
+
continue;
|
|
143204
|
+
}
|
|
143205
|
+
|
|
143206
|
+
// read splat center
|
|
143207
|
+
vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
|
|
143208
|
+
|
|
143209
|
+
// apply optional per-splat transform
|
|
143210
|
+
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
143211
|
+
if (transformIndex > 0u) {
|
|
143212
|
+
// read transform matrix
|
|
143213
|
+
int u = int(transformIndex % 512u) * 3;
|
|
143214
|
+
int v = int(transformIndex / 512u);
|
|
143215
|
+
|
|
143216
|
+
mat3x4 t;
|
|
143217
|
+
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
143218
|
+
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
143219
|
+
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
143220
|
+
|
|
143221
|
+
center = vec4(center, 1.0) * t;
|
|
143222
|
+
}
|
|
143223
|
+
|
|
143224
|
+
boundMin = min(boundMin, mix(center, boundMin, isinf(center)));
|
|
143225
|
+
boundMax = max(boundMax, mix(center, boundMax, isinf(center)));
|
|
143226
|
+
}
|
|
143227
|
+
|
|
143228
|
+
pcFragColor0 = vec4(boundMin, 0.0);
|
|
143229
|
+
pcFragColor1 = vec4(boundMax, 0.0);
|
|
143230
|
+
}
|
|
143231
|
+
`;
|
|
143232
|
+
|
|
143233
|
+
const vertexShader$5 = /* glsl */ `
|
|
143234
|
+
attribute vec2 vertex_position;
|
|
143235
|
+
void main(void) {
|
|
143236
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
143237
|
+
}
|
|
143238
|
+
`;
|
|
143239
|
+
const fragmentShader$5 = /* glsl */ `
|
|
143240
|
+
uniform highp usampler2D transformA; // splat center x, y, z
|
|
143241
|
+
uniform highp usampler2D splatTransform; // transform palette index
|
|
143242
|
+
uniform sampler2D transformPalette; // palette of transforms
|
|
143243
|
+
uniform uvec2 splat_params; // splat texture width, num splats
|
|
143244
|
+
|
|
143245
|
+
uniform mat4 matrix_model;
|
|
143246
|
+
uniform mat4 matrix_viewProjection;
|
|
143247
|
+
|
|
143248
|
+
uniform uvec2 output_params; // output width, height
|
|
143249
|
+
|
|
143250
|
+
// 0: mask, 1: rect, 2: sphere
|
|
143251
|
+
uniform int mode;
|
|
143252
|
+
|
|
143253
|
+
// mask params
|
|
143254
|
+
uniform sampler2D mask; // mask in alpha channel
|
|
143255
|
+
uniform vec2 mask_params; // mask width, height
|
|
143256
|
+
|
|
143257
|
+
// rect params
|
|
143258
|
+
uniform vec4 rect_params; // rect x, y, width, height
|
|
143259
|
+
|
|
143260
|
+
// sphere params
|
|
143261
|
+
uniform vec4 sphere_params; // sphere x, y, z, radius
|
|
143262
|
+
|
|
143263
|
+
// box params
|
|
143264
|
+
uniform vec4 box_params; // box x, y, z
|
|
143265
|
+
uniform vec4 aabb_params; // len x, y, z
|
|
143266
|
+
|
|
143267
|
+
void main(void) {
|
|
143268
|
+
// calculate output id
|
|
143269
|
+
uvec2 outputUV = uvec2(gl_FragCoord);
|
|
143270
|
+
uint outputId = (outputUV.x + outputUV.y * output_params.x) * 4u;
|
|
143271
|
+
|
|
143272
|
+
vec4 clr = vec4(0.0);
|
|
143273
|
+
|
|
143274
|
+
for (uint i = 0u; i < 4u; i++) {
|
|
143275
|
+
uint id = outputId + i;
|
|
143276
|
+
|
|
143277
|
+
if (id >= splat_params.y) {
|
|
143278
|
+
continue;
|
|
143279
|
+
}
|
|
143280
|
+
|
|
143281
|
+
// calculate splatUV
|
|
143282
|
+
ivec2 splatUV = ivec2(
|
|
143283
|
+
int(id % splat_params.x),
|
|
143284
|
+
int(id / splat_params.x)
|
|
143285
|
+
);
|
|
143286
|
+
|
|
143287
|
+
// read splat center
|
|
143288
|
+
vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
|
|
143289
|
+
|
|
143290
|
+
// apply optional per-splat transform
|
|
143291
|
+
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
143292
|
+
if (transformIndex > 0u) {
|
|
143293
|
+
// read transform matrix
|
|
143294
|
+
int u = int(transformIndex % 512u) * 3;
|
|
143295
|
+
int v = int(transformIndex / 512u);
|
|
143296
|
+
|
|
143297
|
+
mat3x4 t;
|
|
143298
|
+
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
143299
|
+
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
143300
|
+
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
143301
|
+
|
|
143302
|
+
center = vec4(center, 1.0) * t;
|
|
143303
|
+
}
|
|
143304
|
+
|
|
143305
|
+
// transform to clip space and discard if outside
|
|
143306
|
+
vec3 world = (matrix_model * vec4(center, 1.0)).xyz;
|
|
143307
|
+
vec4 clip = matrix_viewProjection * vec4(world, 1.0);
|
|
143308
|
+
vec3 ndc = clip.xyz / clip.w;
|
|
143309
|
+
|
|
143310
|
+
// skip offscreen fragments
|
|
143311
|
+
if (!any(greaterThan(abs(ndc), vec3(1.0)))) {
|
|
143312
|
+
if (mode == 0) {
|
|
143313
|
+
// select by mask
|
|
143314
|
+
ivec2 maskUV = ivec2((ndc.xy * vec2(0.5, -0.5) + 0.5) * mask_params);
|
|
143315
|
+
clr[i] = texelFetch(mask, maskUV, 0).a < 1.0 ? 0.0 : 1.0;
|
|
143316
|
+
} else if (mode == 1) {
|
|
143317
|
+
// select by rect
|
|
143318
|
+
clr[i] = all(greaterThan(ndc.xy * vec2(1.0, -1.0), rect_params.xy)) && all(lessThan(ndc.xy * vec2(1.0, -1.0), rect_params.zw)) ? 1.0 : 0.0;
|
|
143319
|
+
} else if (mode == 2) {
|
|
143320
|
+
// select by sphere
|
|
143321
|
+
clr[i] = length(world - sphere_params.xyz) < sphere_params.w ? 1.0 : 0.0;
|
|
143322
|
+
} else if (mode == 3) {
|
|
143323
|
+
// select by box
|
|
143324
|
+
vec3 relativePosition = world - box_params.xyz;
|
|
143325
|
+
bool isInsideCube = true;
|
|
143326
|
+
if (relativePosition.x < -aabb_params.x || relativePosition.x > aabb_params.x) {
|
|
143327
|
+
isInsideCube = false;
|
|
143328
|
+
}
|
|
143329
|
+
if (relativePosition.y < -aabb_params.y || relativePosition.y > aabb_params.y) {
|
|
143330
|
+
isInsideCube = false;
|
|
143331
|
+
}
|
|
143332
|
+
if (relativePosition.z < -aabb_params.z || relativePosition.z > aabb_params.z) {
|
|
143333
|
+
isInsideCube = false;
|
|
143334
|
+
}
|
|
143335
|
+
clr[i] = isInsideCube ? 1.0 : 0.0;
|
|
143336
|
+
}
|
|
143337
|
+
}
|
|
143338
|
+
}
|
|
143339
|
+
|
|
143340
|
+
gl_FragColor = clr;
|
|
143341
|
+
}
|
|
143342
|
+
`;
|
|
143343
|
+
|
|
143344
|
+
const vertexShader$4 = /* glsl */ `
|
|
143345
|
+
attribute vec2 vertex_position;
|
|
143346
|
+
void main(void) {
|
|
143347
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
143348
|
+
}
|
|
143349
|
+
`;
|
|
143350
|
+
const fragmentShader$4 = /* glsl */ `
|
|
143351
|
+
uniform highp usampler2D transformA; // splat center x, y, z
|
|
143352
|
+
uniform highp usampler2D splatTransform; // transform palette index
|
|
143353
|
+
uniform sampler2D transformPalette; // palette of transforms
|
|
143354
|
+
uniform ivec2 splat_params; // splat texture width, num splats
|
|
143355
|
+
|
|
143356
|
+
void main(void) {
|
|
143357
|
+
// calculate output id
|
|
143358
|
+
ivec2 splatUV = ivec2(gl_FragCoord);
|
|
143359
|
+
|
|
143360
|
+
// skip if splat index is out of bounds
|
|
143361
|
+
if (splatUV.x + splatUV.y * splat_params.x >= splat_params.y) {
|
|
143362
|
+
discard;
|
|
143363
|
+
}
|
|
143364
|
+
|
|
143365
|
+
// read splat center
|
|
143366
|
+
vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
|
|
143367
|
+
|
|
143368
|
+
// apply optional per-splat transform
|
|
143369
|
+
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
143370
|
+
if (transformIndex > 0u) {
|
|
143371
|
+
// read transform matrix
|
|
143372
|
+
int u = int(transformIndex % 512u) * 3;
|
|
143373
|
+
int v = int(transformIndex / 512u);
|
|
143374
|
+
|
|
143375
|
+
mat3x4 t;
|
|
143376
|
+
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
143377
|
+
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
143378
|
+
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
143379
|
+
|
|
143380
|
+
center = vec4(center, 1.0) * t;
|
|
143381
|
+
}
|
|
143382
|
+
|
|
143383
|
+
gl_FragColor = vec4(center, 0.0);
|
|
143384
|
+
}
|
|
142755
143385
|
`;
|
|
142756
143386
|
|
|
142757
143387
|
const v1 = new Vec3();
|
|
@@ -142788,18 +143418,18 @@ class DataProcessor {
|
|
|
142788
143418
|
attributes: {
|
|
142789
143419
|
vertex_position: SEMANTIC_POSITION
|
|
142790
143420
|
},
|
|
142791
|
-
vertexGLSL: `
|
|
142792
|
-
attribute vec2 vertex_position;
|
|
142793
|
-
void main(void) {
|
|
142794
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
142795
|
-
}
|
|
143421
|
+
vertexGLSL: `
|
|
143422
|
+
attribute vec2 vertex_position;
|
|
143423
|
+
void main(void) {
|
|
143424
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
143425
|
+
}
|
|
142796
143426
|
`,
|
|
142797
|
-
fragmentGLSL: `
|
|
142798
|
-
uniform sampler2D colorTex;
|
|
142799
|
-
void main(void) {
|
|
142800
|
-
ivec2 texel = ivec2(gl_FragCoord.xy);
|
|
142801
|
-
gl_FragColor = texelFetch(colorTex, texel, 0);
|
|
142802
|
-
}
|
|
143427
|
+
fragmentGLSL: `
|
|
143428
|
+
uniform sampler2D colorTex;
|
|
143429
|
+
void main(void) {
|
|
143430
|
+
ivec2 texel = ivec2(gl_FragCoord.xy);
|
|
143431
|
+
gl_FragColor = texelFetch(colorTex, texel, 0);
|
|
143432
|
+
}
|
|
142803
143433
|
`
|
|
142804
143434
|
});
|
|
142805
143435
|
// intersection test
|
|
@@ -144038,176 +144668,176 @@ class Camera extends Element$1 {
|
|
|
144038
144668
|
}
|
|
144039
144669
|
}
|
|
144040
144670
|
|
|
144041
|
-
const vertexShader$3 = /* glsl*/ `
|
|
144042
|
-
uniform vec3 near_origin;
|
|
144043
|
-
uniform vec3 near_x;
|
|
144044
|
-
uniform vec3 near_y;
|
|
144045
|
-
|
|
144046
|
-
uniform vec3 far_origin;
|
|
144047
|
-
uniform vec3 far_x;
|
|
144048
|
-
uniform vec3 far_y;
|
|
144049
|
-
|
|
144050
|
-
attribute vec2 vertex_position;
|
|
144051
|
-
|
|
144052
|
-
varying vec3 worldFar;
|
|
144053
|
-
varying vec3 worldNear;
|
|
144054
|
-
|
|
144055
|
-
void main(void) {
|
|
144056
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
144057
|
-
|
|
144058
|
-
vec2 p = vertex_position * 0.5 + 0.5;
|
|
144059
|
-
worldNear = near_origin + near_x * p.x + near_y * p.y;
|
|
144060
|
-
worldFar = far_origin + far_x * p.x + far_y * p.y;
|
|
144061
|
-
}
|
|
144062
|
-
`;
|
|
144063
|
-
const fragmentShader$3 = /* glsl*/ `
|
|
144064
|
-
uniform vec3 view_position;
|
|
144065
|
-
uniform mat4 matrix_viewProjection;
|
|
144066
|
-
uniform sampler2D blueNoiseTex32;
|
|
144067
|
-
|
|
144068
|
-
uniform int plane; // 0: x (yz), 1: y (xz), 2: z (xy)
|
|
144069
|
-
|
|
144070
|
-
vec4 planes[3] = vec4[3](
|
|
144071
|
-
vec4(1.0, 0.0, 0.0, 0.0),
|
|
144072
|
-
vec4(0.0, 1.0, 0.0, 0.0),
|
|
144073
|
-
vec4(0.0, 0.0, 1.0, 0.0)
|
|
144074
|
-
);
|
|
144075
|
-
|
|
144076
|
-
vec3 colors[3] = vec3[3](
|
|
144077
|
-
vec3(1.0, 0.2, 0.2),
|
|
144078
|
-
vec3(0.2, 1.0, 0.2),
|
|
144079
|
-
vec3(0.2, 0.2, 1.0)
|
|
144080
|
-
);
|
|
144081
|
-
|
|
144082
|
-
int axis0[3] = int[3](1, 0, 0);
|
|
144083
|
-
int axis1[3] = int[3](2, 2, 1);
|
|
144084
|
-
|
|
144085
|
-
varying vec3 worldNear;
|
|
144086
|
-
varying vec3 worldFar;
|
|
144087
|
-
|
|
144088
|
-
bool intersectPlane(inout float t, vec3 pos, vec3 dir, vec4 plane) {
|
|
144089
|
-
float d = dot(dir, plane.xyz);
|
|
144090
|
-
if (abs(d) < 1e-06) {
|
|
144091
|
-
return false;
|
|
144092
|
-
}
|
|
144093
|
-
|
|
144094
|
-
float n = -(dot(pos, plane.xyz) + plane.w) / d;
|
|
144095
|
-
if (n < 0.0) {
|
|
144096
|
-
return false;
|
|
144097
|
-
}
|
|
144098
|
-
|
|
144099
|
-
t = n;
|
|
144100
|
-
|
|
144101
|
-
return true;
|
|
144102
|
-
}
|
|
144103
|
-
|
|
144104
|
-
// https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#1e7c
|
|
144105
|
-
float pristineGrid(in vec2 uv, in vec2 ddx, in vec2 ddy, vec2 lineWidth) {
|
|
144106
|
-
vec2 uvDeriv = vec2(length(vec2(ddx.x, ddy.x)), length(vec2(ddx.y, ddy.y)));
|
|
144107
|
-
bvec2 invertLine = bvec2(lineWidth.x > 0.5, lineWidth.y > 0.5);
|
|
144108
|
-
vec2 targetWidth = vec2(
|
|
144109
|
-
invertLine.x ? 1.0 - lineWidth.x : lineWidth.x,
|
|
144110
|
-
invertLine.y ? 1.0 - lineWidth.y : lineWidth.y
|
|
144111
|
-
);
|
|
144112
|
-
vec2 drawWidth = clamp(targetWidth, uvDeriv, vec2(0.5));
|
|
144113
|
-
vec2 lineAA = uvDeriv * 1.5;
|
|
144114
|
-
vec2 gridUV = abs(fract(uv) * 2.0 - 1.0);
|
|
144115
|
-
gridUV.x = invertLine.x ? gridUV.x : 1.0 - gridUV.x;
|
|
144116
|
-
gridUV.y = invertLine.y ? gridUV.y : 1.0 - gridUV.y;
|
|
144117
|
-
vec2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);
|
|
144118
|
-
|
|
144119
|
-
grid2 *= clamp(targetWidth / drawWidth, 0.0, 1.0);
|
|
144120
|
-
grid2 = mix(grid2, targetWidth, clamp(uvDeriv * 2.0 - 1.0, 0.0, 1.0));
|
|
144121
|
-
grid2.x = invertLine.x ? 1.0 - grid2.x : grid2.x;
|
|
144122
|
-
grid2.y = invertLine.y ? 1.0 - grid2.y : grid2.y;
|
|
144123
|
-
|
|
144124
|
-
return mix(grid2.x, 1.0, grid2.y);
|
|
144125
|
-
}
|
|
144126
|
-
|
|
144127
|
-
float calcDepth(vec3 p) {
|
|
144128
|
-
vec4 v = matrix_viewProjection * vec4(p, 1.0);
|
|
144129
|
-
return (v.z / v.w) * 0.5 + 0.5;
|
|
144130
|
-
}
|
|
144131
|
-
|
|
144132
|
-
bool writeDepth(float alpha) {
|
|
144133
|
-
vec2 uv = fract(gl_FragCoord.xy / 32.0);
|
|
144134
|
-
float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
|
|
144135
|
-
return alpha > noise;
|
|
144136
|
-
}
|
|
144137
|
-
|
|
144138
|
-
void main(void) {
|
|
144139
|
-
vec3 p = worldNear;
|
|
144140
|
-
vec3 v = normalize(worldFar - worldNear);
|
|
144141
|
-
|
|
144142
|
-
// intersect ray with the world xz plane
|
|
144143
|
-
float t;
|
|
144144
|
-
if (!intersectPlane(t, p, v, planes[plane])) {
|
|
144145
|
-
discard;
|
|
144146
|
-
}
|
|
144147
|
-
|
|
144148
|
-
// calculate grid intersection
|
|
144149
|
-
vec3 worldPos = p + v * t;
|
|
144150
|
-
vec2 pos = plane == 0 ? worldPos.yz : (plane == 1 ? worldPos.xz : worldPos.xy);
|
|
144151
|
-
vec2 ddx = dFdx(pos);
|
|
144152
|
-
vec2 ddy = dFdy(pos);
|
|
144153
|
-
|
|
144154
|
-
float epsilon = 1.0 / 255.0;
|
|
144155
|
-
|
|
144156
|
-
// calculate fade
|
|
144157
|
-
float fade = 1.0 - smoothstep(400.0, 1000.0, length(worldPos - view_position));
|
|
144158
|
-
if (fade < epsilon) {
|
|
144159
|
-
discard;
|
|
144160
|
-
}
|
|
144161
|
-
|
|
144162
|
-
vec2 levelPos;
|
|
144163
|
-
float levelSize;
|
|
144164
|
-
float levelAlpha;
|
|
144165
|
-
|
|
144166
|
-
// 10m grid with colored main axes
|
|
144167
|
-
levelPos = pos * 0.1;
|
|
144168
|
-
levelSize = 2.0 / 1000.0;
|
|
144169
|
-
levelAlpha = pristineGrid(levelPos, ddx * 0.1, ddy * 0.1, vec2(levelSize)) * fade;
|
|
144170
|
-
if (levelAlpha > epsilon) {
|
|
144171
|
-
vec3 color;
|
|
144172
|
-
vec2 loc = abs(levelPos);
|
|
144173
|
-
if (loc.x < levelSize) {
|
|
144174
|
-
if (loc.y < levelSize) {
|
|
144175
|
-
color = vec3(1.0);
|
|
144176
|
-
} else {
|
|
144177
|
-
color = colors[axis1[plane]];
|
|
144178
|
-
}
|
|
144179
|
-
} else if (loc.y < levelSize) {
|
|
144180
|
-
color = colors[axis0[plane]];
|
|
144181
|
-
} else {
|
|
144182
|
-
color = vec3(0.9);
|
|
144183
|
-
}
|
|
144184
|
-
gl_FragColor = vec4(color, levelAlpha);
|
|
144185
|
-
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
|
|
144186
|
-
return;
|
|
144187
|
-
}
|
|
144188
|
-
|
|
144189
|
-
// 1m grid
|
|
144190
|
-
levelPos = pos;
|
|
144191
|
-
levelSize = 1.0 / 100.0;
|
|
144192
|
-
levelAlpha = pristineGrid(levelPos, ddx, ddy, vec2(levelSize)) * fade;
|
|
144193
|
-
if (levelAlpha > epsilon) {
|
|
144194
|
-
gl_FragColor = vec4(vec3(0.7), levelAlpha);
|
|
144195
|
-
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
|
|
144196
|
-
return;
|
|
144197
|
-
}
|
|
144198
|
-
|
|
144199
|
-
// 0.1m grid
|
|
144200
|
-
levelPos = pos * 10.0;
|
|
144201
|
-
levelSize = 1.0 / 100.0;
|
|
144202
|
-
levelAlpha = pristineGrid(levelPos, ddx * 10.0, ddy * 10.0, vec2(levelSize)) * fade;
|
|
144203
|
-
if (levelAlpha > epsilon) {
|
|
144204
|
-
gl_FragColor = vec4(vec3(0.7), levelAlpha);
|
|
144205
|
-
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
|
|
144206
|
-
return;
|
|
144207
|
-
}
|
|
144208
|
-
|
|
144209
|
-
discard;
|
|
144210
|
-
}
|
|
144671
|
+
const vertexShader$3 = /* glsl*/ `
|
|
144672
|
+
uniform vec3 near_origin;
|
|
144673
|
+
uniform vec3 near_x;
|
|
144674
|
+
uniform vec3 near_y;
|
|
144675
|
+
|
|
144676
|
+
uniform vec3 far_origin;
|
|
144677
|
+
uniform vec3 far_x;
|
|
144678
|
+
uniform vec3 far_y;
|
|
144679
|
+
|
|
144680
|
+
attribute vec2 vertex_position;
|
|
144681
|
+
|
|
144682
|
+
varying vec3 worldFar;
|
|
144683
|
+
varying vec3 worldNear;
|
|
144684
|
+
|
|
144685
|
+
void main(void) {
|
|
144686
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
144687
|
+
|
|
144688
|
+
vec2 p = vertex_position * 0.5 + 0.5;
|
|
144689
|
+
worldNear = near_origin + near_x * p.x + near_y * p.y;
|
|
144690
|
+
worldFar = far_origin + far_x * p.x + far_y * p.y;
|
|
144691
|
+
}
|
|
144692
|
+
`;
|
|
144693
|
+
const fragmentShader$3 = /* glsl*/ `
|
|
144694
|
+
uniform vec3 view_position;
|
|
144695
|
+
uniform mat4 matrix_viewProjection;
|
|
144696
|
+
uniform sampler2D blueNoiseTex32;
|
|
144697
|
+
|
|
144698
|
+
uniform int plane; // 0: x (yz), 1: y (xz), 2: z (xy)
|
|
144699
|
+
|
|
144700
|
+
vec4 planes[3] = vec4[3](
|
|
144701
|
+
vec4(1.0, 0.0, 0.0, 0.0),
|
|
144702
|
+
vec4(0.0, 1.0, 0.0, 0.0),
|
|
144703
|
+
vec4(0.0, 0.0, 1.0, 0.0)
|
|
144704
|
+
);
|
|
144705
|
+
|
|
144706
|
+
vec3 colors[3] = vec3[3](
|
|
144707
|
+
vec3(1.0, 0.2, 0.2),
|
|
144708
|
+
vec3(0.2, 1.0, 0.2),
|
|
144709
|
+
vec3(0.2, 0.2, 1.0)
|
|
144710
|
+
);
|
|
144711
|
+
|
|
144712
|
+
int axis0[3] = int[3](1, 0, 0);
|
|
144713
|
+
int axis1[3] = int[3](2, 2, 1);
|
|
144714
|
+
|
|
144715
|
+
varying vec3 worldNear;
|
|
144716
|
+
varying vec3 worldFar;
|
|
144717
|
+
|
|
144718
|
+
bool intersectPlane(inout float t, vec3 pos, vec3 dir, vec4 plane) {
|
|
144719
|
+
float d = dot(dir, plane.xyz);
|
|
144720
|
+
if (abs(d) < 1e-06) {
|
|
144721
|
+
return false;
|
|
144722
|
+
}
|
|
144723
|
+
|
|
144724
|
+
float n = -(dot(pos, plane.xyz) + plane.w) / d;
|
|
144725
|
+
if (n < 0.0) {
|
|
144726
|
+
return false;
|
|
144727
|
+
}
|
|
144728
|
+
|
|
144729
|
+
t = n;
|
|
144730
|
+
|
|
144731
|
+
return true;
|
|
144732
|
+
}
|
|
144733
|
+
|
|
144734
|
+
// https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#1e7c
|
|
144735
|
+
float pristineGrid(in vec2 uv, in vec2 ddx, in vec2 ddy, vec2 lineWidth) {
|
|
144736
|
+
vec2 uvDeriv = vec2(length(vec2(ddx.x, ddy.x)), length(vec2(ddx.y, ddy.y)));
|
|
144737
|
+
bvec2 invertLine = bvec2(lineWidth.x > 0.5, lineWidth.y > 0.5);
|
|
144738
|
+
vec2 targetWidth = vec2(
|
|
144739
|
+
invertLine.x ? 1.0 - lineWidth.x : lineWidth.x,
|
|
144740
|
+
invertLine.y ? 1.0 - lineWidth.y : lineWidth.y
|
|
144741
|
+
);
|
|
144742
|
+
vec2 drawWidth = clamp(targetWidth, uvDeriv, vec2(0.5));
|
|
144743
|
+
vec2 lineAA = uvDeriv * 1.5;
|
|
144744
|
+
vec2 gridUV = abs(fract(uv) * 2.0 - 1.0);
|
|
144745
|
+
gridUV.x = invertLine.x ? gridUV.x : 1.0 - gridUV.x;
|
|
144746
|
+
gridUV.y = invertLine.y ? gridUV.y : 1.0 - gridUV.y;
|
|
144747
|
+
vec2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);
|
|
144748
|
+
|
|
144749
|
+
grid2 *= clamp(targetWidth / drawWidth, 0.0, 1.0);
|
|
144750
|
+
grid2 = mix(grid2, targetWidth, clamp(uvDeriv * 2.0 - 1.0, 0.0, 1.0));
|
|
144751
|
+
grid2.x = invertLine.x ? 1.0 - grid2.x : grid2.x;
|
|
144752
|
+
grid2.y = invertLine.y ? 1.0 - grid2.y : grid2.y;
|
|
144753
|
+
|
|
144754
|
+
return mix(grid2.x, 1.0, grid2.y);
|
|
144755
|
+
}
|
|
144756
|
+
|
|
144757
|
+
float calcDepth(vec3 p) {
|
|
144758
|
+
vec4 v = matrix_viewProjection * vec4(p, 1.0);
|
|
144759
|
+
return (v.z / v.w) * 0.5 + 0.5;
|
|
144760
|
+
}
|
|
144761
|
+
|
|
144762
|
+
bool writeDepth(float alpha) {
|
|
144763
|
+
vec2 uv = fract(gl_FragCoord.xy / 32.0);
|
|
144764
|
+
float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
|
|
144765
|
+
return alpha > noise;
|
|
144766
|
+
}
|
|
144767
|
+
|
|
144768
|
+
void main(void) {
|
|
144769
|
+
vec3 p = worldNear;
|
|
144770
|
+
vec3 v = normalize(worldFar - worldNear);
|
|
144771
|
+
|
|
144772
|
+
// intersect ray with the world xz plane
|
|
144773
|
+
float t;
|
|
144774
|
+
if (!intersectPlane(t, p, v, planes[plane])) {
|
|
144775
|
+
discard;
|
|
144776
|
+
}
|
|
144777
|
+
|
|
144778
|
+
// calculate grid intersection
|
|
144779
|
+
vec3 worldPos = p + v * t;
|
|
144780
|
+
vec2 pos = plane == 0 ? worldPos.yz : (plane == 1 ? worldPos.xz : worldPos.xy);
|
|
144781
|
+
vec2 ddx = dFdx(pos);
|
|
144782
|
+
vec2 ddy = dFdy(pos);
|
|
144783
|
+
|
|
144784
|
+
float epsilon = 1.0 / 255.0;
|
|
144785
|
+
|
|
144786
|
+
// calculate fade
|
|
144787
|
+
float fade = 1.0 - smoothstep(400.0, 1000.0, length(worldPos - view_position));
|
|
144788
|
+
if (fade < epsilon) {
|
|
144789
|
+
discard;
|
|
144790
|
+
}
|
|
144791
|
+
|
|
144792
|
+
vec2 levelPos;
|
|
144793
|
+
float levelSize;
|
|
144794
|
+
float levelAlpha;
|
|
144795
|
+
|
|
144796
|
+
// 10m grid with colored main axes
|
|
144797
|
+
levelPos = pos * 0.1;
|
|
144798
|
+
levelSize = 2.0 / 1000.0;
|
|
144799
|
+
levelAlpha = pristineGrid(levelPos, ddx * 0.1, ddy * 0.1, vec2(levelSize)) * fade;
|
|
144800
|
+
if (levelAlpha > epsilon) {
|
|
144801
|
+
vec3 color;
|
|
144802
|
+
vec2 loc = abs(levelPos);
|
|
144803
|
+
if (loc.x < levelSize) {
|
|
144804
|
+
if (loc.y < levelSize) {
|
|
144805
|
+
color = vec3(1.0);
|
|
144806
|
+
} else {
|
|
144807
|
+
color = colors[axis1[plane]];
|
|
144808
|
+
}
|
|
144809
|
+
} else if (loc.y < levelSize) {
|
|
144810
|
+
color = colors[axis0[plane]];
|
|
144811
|
+
} else {
|
|
144812
|
+
color = vec3(0.9);
|
|
144813
|
+
}
|
|
144814
|
+
gl_FragColor = vec4(color, levelAlpha);
|
|
144815
|
+
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
|
|
144816
|
+
return;
|
|
144817
|
+
}
|
|
144818
|
+
|
|
144819
|
+
// 1m grid
|
|
144820
|
+
levelPos = pos;
|
|
144821
|
+
levelSize = 1.0 / 100.0;
|
|
144822
|
+
levelAlpha = pristineGrid(levelPos, ddx, ddy, vec2(levelSize)) * fade;
|
|
144823
|
+
if (levelAlpha > epsilon) {
|
|
144824
|
+
gl_FragColor = vec4(vec3(0.7), levelAlpha);
|
|
144825
|
+
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
|
|
144826
|
+
return;
|
|
144827
|
+
}
|
|
144828
|
+
|
|
144829
|
+
// 0.1m grid
|
|
144830
|
+
levelPos = pos * 10.0;
|
|
144831
|
+
levelSize = 1.0 / 100.0;
|
|
144832
|
+
levelAlpha = pristineGrid(levelPos, ddx * 10.0, ddy * 10.0, vec2(levelSize)) * fade;
|
|
144833
|
+
if (levelAlpha > epsilon) {
|
|
144834
|
+
gl_FragColor = vec4(vec3(0.7), levelAlpha);
|
|
144835
|
+
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
|
|
144836
|
+
return;
|
|
144837
|
+
}
|
|
144838
|
+
|
|
144839
|
+
discard;
|
|
144840
|
+
}
|
|
144211
144841
|
`;
|
|
144212
144842
|
|
|
144213
144843
|
const resolve = (scope, values) => {
|
|
@@ -144278,36 +144908,36 @@ class InfiniteGrid extends Element$1 {
|
|
|
144278
144908
|
}
|
|
144279
144909
|
}
|
|
144280
144910
|
|
|
144281
|
-
const vertexShader$2 = /* glsl*/ `
|
|
144282
|
-
attribute vec2 vertex_position;
|
|
144283
|
-
void main(void) {
|
|
144284
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
144285
|
-
}
|
|
144286
|
-
`;
|
|
144287
|
-
const fragmentShader$2 = /* glsl*/ `
|
|
144288
|
-
uniform sampler2D outlineTexture;
|
|
144289
|
-
uniform float alphaCutoff;
|
|
144290
|
-
uniform vec4 clr;
|
|
144291
|
-
|
|
144292
|
-
void main(void) {
|
|
144293
|
-
ivec2 texel = ivec2(gl_FragCoord.xy);
|
|
144294
|
-
|
|
144295
|
-
// skip solid pixels
|
|
144296
|
-
if (texelFetch(outlineTexture, texel, 0).a > alphaCutoff) {
|
|
144297
|
-
discard;
|
|
144298
|
-
}
|
|
144299
|
-
|
|
144300
|
-
for (int x = -2; x <= 2; x++) {
|
|
144301
|
-
for (int y = -2; y <= 2; y++) {
|
|
144302
|
-
if ((x != 0) && (y != 0) && (texelFetch(outlineTexture, texel + ivec2(x, y), 0).a > alphaCutoff)) {
|
|
144303
|
-
gl_FragColor = clr;
|
|
144304
|
-
return;
|
|
144305
|
-
}
|
|
144306
|
-
}
|
|
144307
|
-
}
|
|
144308
|
-
|
|
144309
|
-
discard;
|
|
144310
|
-
}
|
|
144911
|
+
const vertexShader$2 = /* glsl*/ `
|
|
144912
|
+
attribute vec2 vertex_position;
|
|
144913
|
+
void main(void) {
|
|
144914
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
144915
|
+
}
|
|
144916
|
+
`;
|
|
144917
|
+
const fragmentShader$2 = /* glsl*/ `
|
|
144918
|
+
uniform sampler2D outlineTexture;
|
|
144919
|
+
uniform float alphaCutoff;
|
|
144920
|
+
uniform vec4 clr;
|
|
144921
|
+
|
|
144922
|
+
void main(void) {
|
|
144923
|
+
ivec2 texel = ivec2(gl_FragCoord.xy);
|
|
144924
|
+
|
|
144925
|
+
// skip solid pixels
|
|
144926
|
+
if (texelFetch(outlineTexture, texel, 0).a > alphaCutoff) {
|
|
144927
|
+
discard;
|
|
144928
|
+
}
|
|
144929
|
+
|
|
144930
|
+
for (int x = -2; x <= 2; x++) {
|
|
144931
|
+
for (int y = -2; y <= 2; y++) {
|
|
144932
|
+
if ((x != 0) && (y != 0) && (texelFetch(outlineTexture, texel + ivec2(x, y), 0).a > alphaCutoff)) {
|
|
144933
|
+
gl_FragColor = clr;
|
|
144934
|
+
return;
|
|
144935
|
+
}
|
|
144936
|
+
}
|
|
144937
|
+
}
|
|
144938
|
+
|
|
144939
|
+
discard;
|
|
144940
|
+
}
|
|
144311
144941
|
`;
|
|
144312
144942
|
|
|
144313
144943
|
class Outline extends Element$1 {
|
|
@@ -144625,72 +145255,72 @@ class SceneState {
|
|
|
144625
145255
|
}
|
|
144626
145256
|
}
|
|
144627
145257
|
|
|
144628
|
-
const vertexShader$1 = /* glsl */ `
|
|
144629
|
-
attribute uint vertex_id;
|
|
144630
|
-
|
|
144631
|
-
uniform mat4 matrix_model;
|
|
144632
|
-
uniform mat4 matrix_viewProjection;
|
|
144633
|
-
|
|
144634
|
-
uniform sampler2D splatState;
|
|
144635
|
-
uniform highp usampler2D splatPosition;
|
|
144636
|
-
uniform highp usampler2D splatTransform; // per-splat index into transform palette
|
|
144637
|
-
uniform sampler2D transformPalette; // palette of transform matrices
|
|
144638
|
-
|
|
144639
|
-
uniform uvec2 texParams;
|
|
144640
|
-
|
|
144641
|
-
uniform float splatSize;
|
|
144642
|
-
uniform vec4 selectedClr;
|
|
144643
|
-
uniform vec4 unselectedClr;
|
|
144644
|
-
|
|
144645
|
-
varying vec4 varying_color;
|
|
144646
|
-
|
|
144647
|
-
// calculate the current splat index and uv
|
|
144648
|
-
ivec2 calcSplatUV(uint index, uint width) {
|
|
144649
|
-
return ivec2(int(index % width), int(index / width));
|
|
144650
|
-
}
|
|
144651
|
-
|
|
144652
|
-
void main(void) {
|
|
144653
|
-
ivec2 splatUV = calcSplatUV(vertex_id, texParams.x);
|
|
144654
|
-
uint splatState = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
|
|
144655
|
-
|
|
144656
|
-
if ((splatState & 6u) != 0u) {
|
|
144657
|
-
// deleted or locked (4 or 2)
|
|
144658
|
-
gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
|
|
144659
|
-
gl_PointSize = 0.0;
|
|
144660
|
-
} else {
|
|
144661
|
-
mat4 model = matrix_model;
|
|
144662
|
-
|
|
144663
|
-
// handle per-splat transform
|
|
144664
|
-
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
144665
|
-
if (transformIndex > 0u) {
|
|
144666
|
-
// read transform matrix
|
|
144667
|
-
int u = int(transformIndex % 512u) * 3;
|
|
144668
|
-
int v = int(transformIndex / 512u);
|
|
144669
|
-
|
|
144670
|
-
mat4 t;
|
|
144671
|
-
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
144672
|
-
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
144673
|
-
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
144674
|
-
t[3] = vec4(0.0, 0.0, 0.0, 1.0);
|
|
144675
|
-
|
|
144676
|
-
model = matrix_model * transpose(t);
|
|
144677
|
-
}
|
|
144678
|
-
|
|
144679
|
-
varying_color = (splatState == 1u) ? selectedClr : unselectedClr;
|
|
144680
|
-
|
|
144681
|
-
vec3 center = uintBitsToFloat(texelFetch(splatPosition, splatUV, 0).xyz);
|
|
144682
|
-
|
|
144683
|
-
gl_Position = matrix_viewProjection * model * vec4(center, 1.0);
|
|
144684
|
-
gl_PointSize = splatSize;
|
|
144685
|
-
}
|
|
144686
|
-
}
|
|
144687
|
-
`;
|
|
144688
|
-
const fragmentShader$1 = /* glsl */ `
|
|
144689
|
-
varying vec4 varying_color;
|
|
144690
|
-
|
|
144691
|
-
void main(void) {
|
|
144692
|
-
gl_FragColor = varying_color;
|
|
144693
|
-
}
|
|
145258
|
+
const vertexShader$1 = /* glsl */ `
|
|
145259
|
+
attribute uint vertex_id;
|
|
145260
|
+
|
|
145261
|
+
uniform mat4 matrix_model;
|
|
145262
|
+
uniform mat4 matrix_viewProjection;
|
|
145263
|
+
|
|
145264
|
+
uniform sampler2D splatState;
|
|
145265
|
+
uniform highp usampler2D splatPosition;
|
|
145266
|
+
uniform highp usampler2D splatTransform; // per-splat index into transform palette
|
|
145267
|
+
uniform sampler2D transformPalette; // palette of transform matrices
|
|
145268
|
+
|
|
145269
|
+
uniform uvec2 texParams;
|
|
145270
|
+
|
|
145271
|
+
uniform float splatSize;
|
|
145272
|
+
uniform vec4 selectedClr;
|
|
145273
|
+
uniform vec4 unselectedClr;
|
|
145274
|
+
|
|
145275
|
+
varying vec4 varying_color;
|
|
145276
|
+
|
|
145277
|
+
// calculate the current splat index and uv
|
|
145278
|
+
ivec2 calcSplatUV(uint index, uint width) {
|
|
145279
|
+
return ivec2(int(index % width), int(index / width));
|
|
145280
|
+
}
|
|
145281
|
+
|
|
145282
|
+
void main(void) {
|
|
145283
|
+
ivec2 splatUV = calcSplatUV(vertex_id, texParams.x);
|
|
145284
|
+
uint splatState = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
|
|
145285
|
+
|
|
145286
|
+
if ((splatState & 6u) != 0u) {
|
|
145287
|
+
// deleted or locked (4 or 2)
|
|
145288
|
+
gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
|
|
145289
|
+
gl_PointSize = 0.0;
|
|
145290
|
+
} else {
|
|
145291
|
+
mat4 model = matrix_model;
|
|
145292
|
+
|
|
145293
|
+
// handle per-splat transform
|
|
145294
|
+
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
145295
|
+
if (transformIndex > 0u) {
|
|
145296
|
+
// read transform matrix
|
|
145297
|
+
int u = int(transformIndex % 512u) * 3;
|
|
145298
|
+
int v = int(transformIndex / 512u);
|
|
145299
|
+
|
|
145300
|
+
mat4 t;
|
|
145301
|
+
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
145302
|
+
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
145303
|
+
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
145304
|
+
t[3] = vec4(0.0, 0.0, 0.0, 1.0);
|
|
145305
|
+
|
|
145306
|
+
model = matrix_model * transpose(t);
|
|
145307
|
+
}
|
|
145308
|
+
|
|
145309
|
+
varying_color = (splatState == 1u) ? selectedClr : unselectedClr;
|
|
145310
|
+
|
|
145311
|
+
vec3 center = uintBitsToFloat(texelFetch(splatPosition, splatUV, 0).xyz);
|
|
145312
|
+
|
|
145313
|
+
gl_Position = matrix_viewProjection * model * vec4(center, 1.0);
|
|
145314
|
+
gl_PointSize = splatSize;
|
|
145315
|
+
}
|
|
145316
|
+
}
|
|
145317
|
+
`;
|
|
145318
|
+
const fragmentShader$1 = /* glsl */ `
|
|
145319
|
+
varying vec4 varying_color;
|
|
145320
|
+
|
|
145321
|
+
void main(void) {
|
|
145322
|
+
gl_FragColor = varying_color;
|
|
145323
|
+
}
|
|
144694
145324
|
`;
|
|
144695
145325
|
|
|
144696
145326
|
class SplatOverlay extends Element$1 {
|
|
@@ -144779,19 +145409,19 @@ class SplatOverlay extends Element$1 {
|
|
|
144779
145409
|
}
|
|
144780
145410
|
}
|
|
144781
145411
|
|
|
144782
|
-
const vertexShader = /* glsl*/ `
|
|
144783
|
-
attribute vec2 vertex_position;
|
|
144784
|
-
void main(void) {
|
|
144785
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
144786
|
-
}
|
|
145412
|
+
const vertexShader = /* glsl*/ `
|
|
145413
|
+
attribute vec2 vertex_position;
|
|
145414
|
+
void main(void) {
|
|
145415
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
145416
|
+
}
|
|
144787
145417
|
`;
|
|
144788
|
-
const fragmentShader = /* glsl*/ `
|
|
144789
|
-
uniform sampler2D blitTexture;
|
|
144790
|
-
void main(void) {
|
|
144791
|
-
ivec2 texel = ivec2(gl_FragCoord.xy);
|
|
144792
|
-
|
|
144793
|
-
gl_FragColor = texelFetch(blitTexture, texel, 0);
|
|
144794
|
-
}
|
|
145418
|
+
const fragmentShader = /* glsl*/ `
|
|
145419
|
+
uniform sampler2D blitTexture;
|
|
145420
|
+
void main(void) {
|
|
145421
|
+
ivec2 texel = ivec2(gl_FragCoord.xy);
|
|
145422
|
+
|
|
145423
|
+
gl_FragColor = texelFetch(blitTexture, texel, 0);
|
|
145424
|
+
}
|
|
144795
145425
|
`;
|
|
144796
145426
|
|
|
144797
145427
|
class Underlay extends Element$1 {
|
|
@@ -146756,7 +147386,7 @@ class SupersplatAdapter {
|
|
|
146756
147386
|
const { config } = this.scene;
|
|
146757
147387
|
const state = this.viewerEventState;
|
|
146758
147388
|
// Colors and view settings from scene config
|
|
146759
|
-
const selectedClr = config.selectedClr ?? { r: 1, g:
|
|
147389
|
+
const selectedClr = config.selectedClr ?? { r: 1, g: 0.5, b: 0, a: 1 };
|
|
146760
147390
|
const unselectedClr = config.unselectedClr ?? { r: 0, g: 0, b: 1, a: 0.5 };
|
|
146761
147391
|
const lockedClr = config.lockedClr ?? { r: 0, g: 0, b: 0, a: 0.05 };
|
|
146762
147392
|
const bgClr = config.bgClr ?? { r: 0, g: 0, b: 0, a: 1 };
|
|
@@ -147068,6 +147698,7 @@ class SplatViewerCore {
|
|
|
147068
147698
|
};
|
|
147069
147699
|
this.enableStats = false;
|
|
147070
147700
|
this.autoFocus = true;
|
|
147701
|
+
this.previewMode = false;
|
|
147071
147702
|
this.isLoading = false;
|
|
147072
147703
|
this.hasModel = false;
|
|
147073
147704
|
this.error = null;
|
|
@@ -147120,6 +147751,11 @@ class SplatViewerCore {
|
|
|
147120
147751
|
this.canvas = options.canvas || null;
|
|
147121
147752
|
this.enableStats = options.enableStats || false;
|
|
147122
147753
|
this.autoFocus = options.autoFocus !== false;
|
|
147754
|
+
this.previewMode = options.previewMode || false;
|
|
147755
|
+
// In preview mode, always enable auto-focus
|
|
147756
|
+
if (this.previewMode) {
|
|
147757
|
+
this.autoFocus = true;
|
|
147758
|
+
}
|
|
147123
147759
|
this._navigationCubeConfig = options.navigationCube || null;
|
|
147124
147760
|
if (options.onStatsUpdate !== undefined) {
|
|
147125
147761
|
this._onStatsUpdate = options.onStatsUpdate;
|
|
@@ -147150,11 +147786,20 @@ class SplatViewerCore {
|
|
|
147150
147786
|
const emitEvent = (type, detail) => {
|
|
147151
147787
|
this.emit({ type, detail });
|
|
147152
147788
|
};
|
|
147153
|
-
this._supersplat = new SupersplatAdapter(this.canvas,
|
|
147789
|
+
this._supersplat = new SupersplatAdapter(this.canvas,
|
|
147790
|
+
// Disable navigation cube in preview mode
|
|
147791
|
+
this.previewMode ? null : this._navigationCubeConfig, emitEvent);
|
|
147154
147792
|
this._supersplatReady = this._supersplat.init();
|
|
147155
147793
|
this._supersplatReady
|
|
147156
147794
|
?.then(() => {
|
|
147157
|
-
|
|
147795
|
+
// Disable camera controls in preview mode
|
|
147796
|
+
if (this.previewMode && this._supersplat) {
|
|
147797
|
+
this._supersplat.setCameraControlsEnabled?.(false);
|
|
147798
|
+
}
|
|
147799
|
+
// Only set up fly camera if not in preview mode
|
|
147800
|
+
if (!this.previewMode) {
|
|
147801
|
+
this._setupFlyCameraForSupersplat();
|
|
147802
|
+
}
|
|
147158
147803
|
})
|
|
147159
147804
|
.catch(error => {
|
|
147160
147805
|
console.error('SplatViewerCore.init: Failed to set up fly camera for supersplat path', error);
|
|
@@ -147190,6 +147835,9 @@ class SplatViewerCore {
|
|
|
147190
147835
|
this.app = ctx.app;
|
|
147191
147836
|
this.entities.camera = ctx.camera;
|
|
147192
147837
|
const cameraAny = this.entities.camera;
|
|
147838
|
+
// SuperSplat's PCApp omits ScriptComponentSystem, so `addComponent('script')`
|
|
147839
|
+
// will not produce `camera.script.create()`. We keep the attempt (in case
|
|
147840
|
+
// the underlying app changes), but also support a controller-based fallback.
|
|
147193
147841
|
if (cameraAny &&
|
|
147194
147842
|
!cameraAny.script &&
|
|
147195
147843
|
typeof cameraAny.addComponent === 'function') {
|
|
@@ -147215,16 +147863,25 @@ class SplatViewerCore {
|
|
|
147215
147863
|
minHeight: DEFAULT_FLY_CAMERA_CONFIG.minHeight,
|
|
147216
147864
|
maxHeight: DEFAULT_FLY_CAMERA_CONFIG.maxHeight,
|
|
147217
147865
|
};
|
|
147218
|
-
|
|
147219
|
-
|
|
147220
|
-
|
|
147221
|
-
|
|
147222
|
-
|
|
147223
|
-
|
|
147224
|
-
|
|
147225
|
-
|
|
147226
|
-
|
|
147227
|
-
|
|
147866
|
+
// Prefer script-based fly when available; fallback to controller otherwise.
|
|
147867
|
+
const canCreateScript = typeof cameraAny?.script?.create === 'function';
|
|
147868
|
+
if (canCreateScript) {
|
|
147869
|
+
const created = cameraAny.script.create('flyCamera', {
|
|
147870
|
+
attributes: flyAttributes,
|
|
147871
|
+
});
|
|
147872
|
+
this._fly = created;
|
|
147873
|
+
if (this._fly) {
|
|
147874
|
+
;
|
|
147875
|
+
this._fly.emitFlyEvent = (type, detail) => {
|
|
147876
|
+
this.emit({ type: type, detail });
|
|
147877
|
+
};
|
|
147878
|
+
this._fly.deactivate?.();
|
|
147879
|
+
}
|
|
147880
|
+
}
|
|
147881
|
+
else {
|
|
147882
|
+
const controller = new FlyCameraController(ctx.app, cameraAny, (type, detail) => this.emit({ type: type, detail }), flyAttributes);
|
|
147883
|
+
controller.deactivate();
|
|
147884
|
+
this._fly = controller;
|
|
147228
147885
|
}
|
|
147229
147886
|
this._cameraMode = 'orbit';
|
|
147230
147887
|
}
|
|
@@ -149075,6 +149732,11 @@ class SplatViewerCore {
|
|
|
149075
149732
|
// Camera Mode / Fly Camera API
|
|
149076
149733
|
// ==========================================
|
|
149077
149734
|
setCameraMode(mode) {
|
|
149735
|
+
// Prevent camera mode changes in preview mode
|
|
149736
|
+
if (this.previewMode) {
|
|
149737
|
+
console.warn('SplatViewerCore.setCameraMode: Camera controls are disabled in preview mode');
|
|
149738
|
+
return;
|
|
149739
|
+
}
|
|
149078
149740
|
// supersplat-core path: manage mode switching explicitly (camera entity is updated by supersplat-core each frame)
|
|
149079
149741
|
if (this._supersplat) {
|
|
149080
149742
|
const prev = this._cameraMode;
|
|
@@ -149093,17 +149755,33 @@ class SplatViewerCore {
|
|
|
149093
149755
|
// Stop supersplat orbit updates + input
|
|
149094
149756
|
this._supersplat.setCameraControlsEnabled(false);
|
|
149095
149757
|
this._supersplat.setCameraManualControl(true);
|
|
149096
|
-
//
|
|
149097
|
-
|
|
149098
|
-
|
|
149099
|
-
if (
|
|
149100
|
-
;
|
|
149101
|
-
|
|
149102
|
-
|
|
149758
|
+
// Preserve camera position and rotation when switching to fly mode
|
|
149759
|
+
if (this._fly) {
|
|
149760
|
+
// For FlyCameraController (fallback path)
|
|
149761
|
+
if (typeof this._fly.syncFromEntity === 'function') {
|
|
149762
|
+
this._fly.syncFromEntity();
|
|
149763
|
+
}
|
|
149764
|
+
else {
|
|
149765
|
+
// For FlyCameraScript (legacy path)
|
|
149766
|
+
try {
|
|
149767
|
+
const pos = this.entities.camera?.getPosition?.();
|
|
149768
|
+
if (pos) {
|
|
149769
|
+
const posVec = pos.clone
|
|
149770
|
+
? pos.clone()
|
|
149771
|
+
: new Vec3(pos.x || 0, pos.y || 0, pos.z || 0);
|
|
149772
|
+
this.entities.camera?.setPosition?.(posVec);
|
|
149773
|
+
}
|
|
149774
|
+
const euler = this.entities.camera?.getEulerAngles?.();
|
|
149775
|
+
if (euler) {
|
|
149776
|
+
;
|
|
149777
|
+
this._fly._pitch = euler.x || 0;
|
|
149778
|
+
this._fly._yaw = euler.y || 0;
|
|
149779
|
+
}
|
|
149780
|
+
}
|
|
149781
|
+
catch {
|
|
149782
|
+
// ignore
|
|
149783
|
+
}
|
|
149103
149784
|
}
|
|
149104
|
-
}
|
|
149105
|
-
catch {
|
|
149106
|
-
// ignore
|
|
149107
149785
|
}
|
|
149108
149786
|
this._fly?.activate?.();
|
|
149109
149787
|
this._cameraMode = 'fly';
|
|
@@ -149220,9 +149898,48 @@ class SplatViewerCore {
|
|
|
149220
149898
|
this._cameraModeManager?.setFlyConfig(config);
|
|
149221
149899
|
}
|
|
149222
149900
|
getFlyCameraConfig() {
|
|
149901
|
+
// SuperSplat path: fly is either a script instance or controller fallback
|
|
149902
|
+
if (this._supersplat && this._fly) {
|
|
149903
|
+
try {
|
|
149904
|
+
const flyAny = this._fly;
|
|
149905
|
+
const cfg = {
|
|
149906
|
+
moveSpeed: flyAny.moveSpeed,
|
|
149907
|
+
fastSpeedMultiplier: flyAny.fastSpeedMultiplier,
|
|
149908
|
+
slowSpeedMultiplier: flyAny.slowSpeedMultiplier,
|
|
149909
|
+
lookSensitivity: flyAny.lookSensitivity,
|
|
149910
|
+
invertY: !!flyAny.invertY,
|
|
149911
|
+
keyBindings: { ...(flyAny.keyBindings || {}) },
|
|
149912
|
+
smoothing: flyAny.smoothing,
|
|
149913
|
+
friction: flyAny.friction,
|
|
149914
|
+
enableCollision: !!flyAny.enableCollision,
|
|
149915
|
+
minHeight: flyAny.minHeight ?? null,
|
|
149916
|
+
maxHeight: flyAny.maxHeight ?? null,
|
|
149917
|
+
};
|
|
149918
|
+
return cfg;
|
|
149919
|
+
}
|
|
149920
|
+
catch {
|
|
149921
|
+
return null;
|
|
149922
|
+
}
|
|
149923
|
+
}
|
|
149223
149924
|
return this._cameraModeManager?.getFlyConfig() || null;
|
|
149224
149925
|
}
|
|
149225
149926
|
getFlyCameraState() {
|
|
149927
|
+
// SuperSplat path: fly is either a script instance or controller fallback
|
|
149928
|
+
if (this._supersplat && this._fly?.getState) {
|
|
149929
|
+
try {
|
|
149930
|
+
const state = this._fly.getState();
|
|
149931
|
+
return {
|
|
149932
|
+
mode: this._cameraMode,
|
|
149933
|
+
position: state.position,
|
|
149934
|
+
rotation: state.rotation,
|
|
149935
|
+
velocity: state.velocity,
|
|
149936
|
+
isMoving: state.isMoving,
|
|
149937
|
+
};
|
|
149938
|
+
}
|
|
149939
|
+
catch {
|
|
149940
|
+
return null;
|
|
149941
|
+
}
|
|
149942
|
+
}
|
|
149226
149943
|
return this._cameraModeManager?.getFlyState() || null;
|
|
149227
149944
|
}
|
|
149228
149945
|
_setupScene() {
|
|
@@ -149274,8 +149991,8 @@ class SplatViewerCore {
|
|
|
149274
149991
|
panSensitivity: 1.0,
|
|
149275
149992
|
zoomSensitivity: 0.1,
|
|
149276
149993
|
};
|
|
149277
|
-
// Add navigation cube configuration if available
|
|
149278
|
-
if (this._navigationCubeConfig) {
|
|
149994
|
+
// Add navigation cube configuration if available (but not in preview mode)
|
|
149995
|
+
if (this._navigationCubeConfig && !this.previewMode) {
|
|
149279
149996
|
orbitAttributes.enableNavigationCube =
|
|
149280
149997
|
this._navigationCubeConfig.enabled || false;
|
|
149281
149998
|
this.entities.camera._navigationCubeConfig =
|
|
@@ -149290,48 +150007,57 @@ class SplatViewerCore {
|
|
|
149290
150007
|
detail: { type: interactionType },
|
|
149291
150008
|
});
|
|
149292
150009
|
};
|
|
150010
|
+
// Disable camera controls in preview mode
|
|
150011
|
+
if (this.previewMode && this._orbit) {
|
|
150012
|
+
const orbitAny = this._orbit;
|
|
150013
|
+
if (typeof orbitAny.setEnabled === 'function') {
|
|
150014
|
+
orbitAny.setEnabled(false);
|
|
150015
|
+
}
|
|
150016
|
+
}
|
|
149293
150017
|
this.entities.camera.setPosition(0, 0, 10);
|
|
149294
150018
|
this.entities.camera.lookAt(Vec3.ZERO);
|
|
149295
150019
|
// ==============================
|
|
149296
|
-
// Setup fly camera (disabled by default)
|
|
150020
|
+
// Setup fly camera (disabled by default, skipped in preview mode)
|
|
149297
150021
|
// ==============================
|
|
149298
|
-
|
|
149299
|
-
|
|
149300
|
-
|
|
149301
|
-
|
|
149302
|
-
|
|
149303
|
-
|
|
149304
|
-
|
|
149305
|
-
|
|
149306
|
-
|
|
149307
|
-
|
|
149308
|
-
|
|
149309
|
-
|
|
149310
|
-
|
|
149311
|
-
|
|
149312
|
-
|
|
149313
|
-
|
|
149314
|
-
this._fly = this.entities.camera.script.create('flyCamera', {
|
|
149315
|
-
attributes: flyAttributes,
|
|
149316
|
-
});
|
|
149317
|
-
// Wire event emission to core
|
|
149318
|
-
if (this._fly) {
|
|
149319
|
-
;
|
|
149320
|
-
this._fly.emitFlyEvent = (type, detail) => {
|
|
149321
|
-
this.emit({ type: type, detail });
|
|
150022
|
+
if (!this.previewMode) {
|
|
150023
|
+
try {
|
|
150024
|
+
registerFlyCameraScript();
|
|
150025
|
+
// Ensure script component exists (created above)
|
|
150026
|
+
const flyAttributes = {
|
|
150027
|
+
moveSpeed: DEFAULT_FLY_CAMERA_CONFIG.moveSpeed,
|
|
150028
|
+
fastSpeedMultiplier: DEFAULT_FLY_CAMERA_CONFIG.fastSpeedMultiplier,
|
|
150029
|
+
slowSpeedMultiplier: DEFAULT_FLY_CAMERA_CONFIG.slowSpeedMultiplier,
|
|
150030
|
+
lookSensitivity: DEFAULT_FLY_CAMERA_CONFIG.lookSensitivity,
|
|
150031
|
+
invertY: DEFAULT_FLY_CAMERA_CONFIG.invertY,
|
|
150032
|
+
keyBindings: DEFAULT_FLY_CAMERA_CONFIG.keyBindings,
|
|
150033
|
+
smoothing: DEFAULT_FLY_CAMERA_CONFIG.smoothing,
|
|
150034
|
+
friction: DEFAULT_FLY_CAMERA_CONFIG.friction,
|
|
150035
|
+
enableCollision: DEFAULT_FLY_CAMERA_CONFIG.enableCollision,
|
|
150036
|
+
minHeight: DEFAULT_FLY_CAMERA_CONFIG.minHeight,
|
|
150037
|
+
maxHeight: DEFAULT_FLY_CAMERA_CONFIG.maxHeight,
|
|
149322
150038
|
};
|
|
150039
|
+
this._fly = this.entities.camera.script.create('flyCamera', {
|
|
150040
|
+
attributes: flyAttributes,
|
|
150041
|
+
});
|
|
150042
|
+
// Wire event emission to core
|
|
150043
|
+
if (this._fly) {
|
|
150044
|
+
;
|
|
150045
|
+
this._fly.emitFlyEvent = (type, detail) => {
|
|
150046
|
+
this.emit({ type: type, detail });
|
|
150047
|
+
};
|
|
150048
|
+
}
|
|
150049
|
+
// Deactivate fly by default; orbit is the initial mode
|
|
150050
|
+
if (this._fly?.deactivate) {
|
|
150051
|
+
this._fly.deactivate();
|
|
150052
|
+
}
|
|
150053
|
+
// Initialize camera mode manager
|
|
150054
|
+
this._cameraModeManager = new CameraModeManager(this.app, this.entities.camera, this._orbit, this._fly, (eventType, detail) => {
|
|
150055
|
+
this.emit({ type: eventType, detail });
|
|
150056
|
+
}, 'orbit');
|
|
149323
150057
|
}
|
|
149324
|
-
|
|
149325
|
-
|
|
149326
|
-
this._fly.deactivate();
|
|
150058
|
+
catch (e) {
|
|
150059
|
+
console.warn('Failed to set up fly camera', e);
|
|
149327
150060
|
}
|
|
149328
|
-
// Initialize camera mode manager
|
|
149329
|
-
this._cameraModeManager = new CameraModeManager(this.app, this.entities.camera, this._orbit, this._fly, (eventType, detail) => {
|
|
149330
|
-
this.emit({ type: eventType, detail });
|
|
149331
|
-
}, 'orbit');
|
|
149332
|
-
}
|
|
149333
|
-
catch (e) {
|
|
149334
|
-
console.warn('Failed to set up fly camera', e);
|
|
149335
150061
|
}
|
|
149336
150062
|
}
|
|
149337
150063
|
_setupStats() {
|
|
@@ -149576,6 +150302,7 @@ class SplatViewerElement extends HTMLElement {
|
|
|
149576
150302
|
'enable-stats',
|
|
149577
150303
|
'auto-focus',
|
|
149578
150304
|
'max-splats',
|
|
150305
|
+
'preview-mode',
|
|
149579
150306
|
'camera-position',
|
|
149580
150307
|
'camera-target',
|
|
149581
150308
|
'orbit-sensitivity',
|
|
@@ -149606,6 +150333,9 @@ class SplatViewerElement extends HTMLElement {
|
|
|
149606
150333
|
get autoFocus() {
|
|
149607
150334
|
return this.hasAttribute('auto-focus');
|
|
149608
150335
|
}
|
|
150336
|
+
get previewMode() {
|
|
150337
|
+
return this.hasAttribute('preview-mode');
|
|
150338
|
+
}
|
|
149609
150339
|
get maxSplats() {
|
|
149610
150340
|
return this.getAttribute('max-splats');
|
|
149611
150341
|
}
|
|
@@ -149683,6 +150413,14 @@ class SplatViewerElement extends HTMLElement {
|
|
|
149683
150413
|
this.removeAttribute('auto-focus');
|
|
149684
150414
|
}
|
|
149685
150415
|
}
|
|
150416
|
+
set previewMode(value) {
|
|
150417
|
+
if (value) {
|
|
150418
|
+
this.setAttribute('preview-mode', '');
|
|
150419
|
+
}
|
|
150420
|
+
else {
|
|
150421
|
+
this.removeAttribute('preview-mode');
|
|
150422
|
+
}
|
|
150423
|
+
}
|
|
149686
150424
|
set maxSplats(value) {
|
|
149687
150425
|
if (value === null) {
|
|
149688
150426
|
this.removeAttribute('max-splats');
|
|
@@ -149864,6 +150602,7 @@ class SplatViewerElement extends HTMLElement {
|
|
|
149864
150602
|
return value.length > 0;
|
|
149865
150603
|
case 'enable-stats':
|
|
149866
150604
|
case 'auto-focus':
|
|
150605
|
+
case 'preview-mode':
|
|
149867
150606
|
case 'enable-navigation-cube':
|
|
149868
150607
|
// Boolean attributes - any value is valid
|
|
149869
150608
|
return true;
|
|
@@ -150810,7 +151549,7 @@ class SplatViewerElement extends HTMLElement {
|
|
|
150810
151549
|
if (!this._core) {
|
|
150811
151550
|
throw new Error('SplatViewerElement: Core not initialized. Call connectedCallback first.');
|
|
150812
151551
|
}
|
|
150813
|
-
return this._core.selectSplatsInSphere(center, radius, modelId, addToSelection);
|
|
151552
|
+
return this._core.selectSplatsInSphere(new Vec3(center.x, center.y, center.z), radius, modelId, addToSelection);
|
|
150814
151553
|
}
|
|
150815
151554
|
clearSplatSelection() {
|
|
150816
151555
|
if (!this._core) {
|