@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
|
@@ -105235,14 +105235,24 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105235
105235
|
activateFlyMode() {
|
|
105236
105236
|
if (!this.fly)
|
|
105237
105237
|
return;
|
|
105238
|
-
|
|
105239
|
-
this.fly.activate();
|
|
105240
|
-
// Align fly camera internal orientation with the current camera rotation so that
|
|
105241
|
-
// switching from orbit -> fly does not snap the view back to the initial direction.
|
|
105238
|
+
// Preserve camera position and rotation when switching to fly mode
|
|
105242
105239
|
try {
|
|
105240
|
+
// Preserve position
|
|
105241
|
+
const pos = this.camera.getPosition
|
|
105242
|
+
? this.camera.getPosition().clone()
|
|
105243
|
+
: this.camera.getLocalPosition
|
|
105244
|
+
? this.camera.getLocalPosition().clone()
|
|
105245
|
+
: null;
|
|
105246
|
+
if (pos && this.camera.setPosition) {
|
|
105247
|
+
;
|
|
105248
|
+
this.camera.setPosition(pos);
|
|
105249
|
+
}
|
|
105250
|
+
// Preserve rotation (convert Euler to pitch/yaw)
|
|
105243
105251
|
const euler = this.camera.getEulerAngles
|
|
105244
105252
|
? this.camera.getEulerAngles()
|
|
105245
|
-
:
|
|
105253
|
+
: this.camera.getLocalEulerAngles
|
|
105254
|
+
? this.camera.getLocalEulerAngles()
|
|
105255
|
+
: null;
|
|
105246
105256
|
if (euler) {
|
|
105247
105257
|
// These properties are part of the FlyCamera runtime state
|
|
105248
105258
|
;
|
|
@@ -105253,6 +105263,8 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105253
105263
|
catch {
|
|
105254
105264
|
// Best-effort sync; ignore if camera or script API differs
|
|
105255
105265
|
}
|
|
105266
|
+
if (typeof this.fly.activate === 'function')
|
|
105267
|
+
this.fly.activate();
|
|
105256
105268
|
}
|
|
105257
105269
|
deactivateFlyMode() {
|
|
105258
105270
|
if (!this.fly)
|
|
@@ -105262,6 +105274,373 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105262
105274
|
}
|
|
105263
105275
|
}
|
|
105264
105276
|
|
|
105277
|
+
/**
|
|
105278
|
+
* Fly camera controller for environments where PlayCanvas ScriptComponentSystem is not available
|
|
105279
|
+
* (e.g. supersplat-core's custom PCApp, which omits ScriptComponentSystem).
|
|
105280
|
+
*
|
|
105281
|
+
* This controller attaches DOM input listeners and updates the camera entity via `app.on('update')`.
|
|
105282
|
+
*/
|
|
105283
|
+
class FlyCameraController {
|
|
105284
|
+
constructor(app, entity, emitFlyEvent, config) {
|
|
105285
|
+
// Config
|
|
105286
|
+
this.moveSpeed = 5.0;
|
|
105287
|
+
this.fastSpeedMultiplier = 3.0;
|
|
105288
|
+
this.slowSpeedMultiplier = 0.3;
|
|
105289
|
+
this.lookSensitivity = 0.2;
|
|
105290
|
+
this.invertY = false;
|
|
105291
|
+
this.keyBindings = {
|
|
105292
|
+
forward: 'KeyW',
|
|
105293
|
+
backward: 'KeyS',
|
|
105294
|
+
left: 'KeyA',
|
|
105295
|
+
right: 'KeyD',
|
|
105296
|
+
up: 'KeyE',
|
|
105297
|
+
down: 'KeyQ',
|
|
105298
|
+
fastMove: 'ShiftLeft',
|
|
105299
|
+
slowMove: 'ControlLeft',
|
|
105300
|
+
};
|
|
105301
|
+
this.smoothing = 0.8;
|
|
105302
|
+
this.friction = 0.85;
|
|
105303
|
+
this.enableCollision = false;
|
|
105304
|
+
this.minHeight = null;
|
|
105305
|
+
this.maxHeight = null;
|
|
105306
|
+
this._isActive = true;
|
|
105307
|
+
this._isPointerLocked = false;
|
|
105308
|
+
this._isLooking = false;
|
|
105309
|
+
this._pressed = new Set();
|
|
105310
|
+
this._velocity = new Vec3(0, 0, 0);
|
|
105311
|
+
this._targetVelocity = new Vec3(0, 0, 0);
|
|
105312
|
+
this._pitch = 0;
|
|
105313
|
+
this._yaw = 0;
|
|
105314
|
+
this._lastMoveEmitTime = 0;
|
|
105315
|
+
this._lastLookEmitTime = 0;
|
|
105316
|
+
this._updateHandler = null;
|
|
105317
|
+
this.app = app;
|
|
105318
|
+
this.entity = entity;
|
|
105319
|
+
this.emitFlyEvent = emitFlyEvent;
|
|
105320
|
+
if (config) {
|
|
105321
|
+
this.setConfig(config);
|
|
105322
|
+
}
|
|
105323
|
+
// Sync initial yaw/pitch from entity orientation if available
|
|
105324
|
+
try {
|
|
105325
|
+
const euler = this.entity?.getEulerAngles?.();
|
|
105326
|
+
if (euler) {
|
|
105327
|
+
this._pitch = euler.x || 0;
|
|
105328
|
+
this._yaw = euler.y || 0;
|
|
105329
|
+
}
|
|
105330
|
+
}
|
|
105331
|
+
catch {
|
|
105332
|
+
// ignore
|
|
105333
|
+
}
|
|
105334
|
+
this._bindInputListeners();
|
|
105335
|
+
}
|
|
105336
|
+
_bindInputListeners() {
|
|
105337
|
+
// Keyboard (capture phase so we see keys even when other handlers run)
|
|
105338
|
+
this._onKeyDown = this._handleKeyDown.bind(this);
|
|
105339
|
+
this._onKeyUp = this._handleKeyUp.bind(this);
|
|
105340
|
+
document.addEventListener('keydown', this._onKeyDown, true);
|
|
105341
|
+
document.addEventListener('keyup', this._onKeyUp, true);
|
|
105342
|
+
// Look: pointer events primary, mouse fallback
|
|
105343
|
+
this._onMouseMove = this._handleMouseMove.bind(this);
|
|
105344
|
+
this._onPointerMove = this._handlePointerMove.bind(this);
|
|
105345
|
+
document.addEventListener('mousemove', this._onMouseMove);
|
|
105346
|
+
document.addEventListener('pointermove', this._onPointerMove, true);
|
|
105347
|
+
const canvas = this.app?.graphicsDevice?.canvas;
|
|
105348
|
+
this._onMouseDown = (e) => {
|
|
105349
|
+
if (e.button === 0 && this._isActive)
|
|
105350
|
+
this._isLooking = true;
|
|
105351
|
+
};
|
|
105352
|
+
this._onPointerDown = (e) => {
|
|
105353
|
+
if (e.button === 0 && this._isActive) {
|
|
105354
|
+
this._isLooking = true;
|
|
105355
|
+
}
|
|
105356
|
+
};
|
|
105357
|
+
canvas?.addEventListener('mousedown', this._onMouseDown, true);
|
|
105358
|
+
canvas?.addEventListener('pointerdown', this._onPointerDown, true);
|
|
105359
|
+
this._onMouseUp = (e) => {
|
|
105360
|
+
if (e.button === 0)
|
|
105361
|
+
this._isLooking = false;
|
|
105362
|
+
};
|
|
105363
|
+
this._onPointerUp = (e) => {
|
|
105364
|
+
if (e.button === 0) {
|
|
105365
|
+
this._isLooking = false;
|
|
105366
|
+
}
|
|
105367
|
+
};
|
|
105368
|
+
document.addEventListener('mouseup', this._onMouseUp, true);
|
|
105369
|
+
document.addEventListener('pointerup', this._onPointerUp, true);
|
|
105370
|
+
}
|
|
105371
|
+
_unbindInputListeners() {
|
|
105372
|
+
document.removeEventListener('keydown', this._onKeyDown, true);
|
|
105373
|
+
document.removeEventListener('keyup', this._onKeyUp, true);
|
|
105374
|
+
document.removeEventListener('mousemove', this._onMouseMove);
|
|
105375
|
+
document.removeEventListener('pointermove', this._onPointerMove, true);
|
|
105376
|
+
document.removeEventListener('mouseup', this._onMouseUp, true);
|
|
105377
|
+
document.removeEventListener('pointerup', this._onPointerUp, true);
|
|
105378
|
+
const canvas = this.app?.graphicsDevice?.canvas;
|
|
105379
|
+
if (canvas && this._onMouseDown) {
|
|
105380
|
+
canvas.removeEventListener('mousedown', this._onMouseDown, true);
|
|
105381
|
+
}
|
|
105382
|
+
if (canvas && this._onPointerDown) {
|
|
105383
|
+
canvas.removeEventListener('pointerdown', this._onPointerDown, true);
|
|
105384
|
+
}
|
|
105385
|
+
}
|
|
105386
|
+
/**
|
|
105387
|
+
* Sync position and rotation from the camera entity.
|
|
105388
|
+
* Called when switching from orbit to fly mode to preserve camera state.
|
|
105389
|
+
*/
|
|
105390
|
+
syncFromEntity() {
|
|
105391
|
+
try {
|
|
105392
|
+
// Preserve position
|
|
105393
|
+
const pos = this.entity?.getPosition?.() || this.entity?.getLocalPosition?.();
|
|
105394
|
+
if (pos) {
|
|
105395
|
+
const posVec = pos.clone ? pos.clone() : new Vec3(pos.x || 0, pos.y || 0, pos.z || 0);
|
|
105396
|
+
this.entity?.setPosition?.(posVec);
|
|
105397
|
+
this.entity?.setLocalPosition?.(posVec);
|
|
105398
|
+
}
|
|
105399
|
+
// Preserve rotation (convert Euler to pitch/yaw)
|
|
105400
|
+
const euler = this.entity?.getEulerAngles?.() || this.entity?.getLocalEulerAngles?.();
|
|
105401
|
+
if (euler) {
|
|
105402
|
+
// PlayCanvas Euler: x=pitch, y=yaw, z=roll
|
|
105403
|
+
this._pitch = euler.x || 0;
|
|
105404
|
+
this._yaw = euler.y || 0;
|
|
105405
|
+
// Apply rotation immediately so view doesn't snap
|
|
105406
|
+
if (this.entity?.setLocalEulerAngles) {
|
|
105407
|
+
this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
|
|
105408
|
+
}
|
|
105409
|
+
else if (this.entity?.setEulerAngles) {
|
|
105410
|
+
this.entity.setEulerAngles(this._pitch, this._yaw, 0);
|
|
105411
|
+
}
|
|
105412
|
+
}
|
|
105413
|
+
}
|
|
105414
|
+
catch {
|
|
105415
|
+
// ignore
|
|
105416
|
+
}
|
|
105417
|
+
}
|
|
105418
|
+
activate() {
|
|
105419
|
+
if (this._isActive)
|
|
105420
|
+
return;
|
|
105421
|
+
this._isActive = true;
|
|
105422
|
+
// Sync position and rotation from current camera state before activating
|
|
105423
|
+
this.syncFromEntity();
|
|
105424
|
+
if (!this._updateHandler) {
|
|
105425
|
+
this._updateHandler = (dt) => this.update(dt);
|
|
105426
|
+
this.app?.on?.('update', this._updateHandler);
|
|
105427
|
+
}
|
|
105428
|
+
}
|
|
105429
|
+
deactivate() {
|
|
105430
|
+
if (!this._isActive)
|
|
105431
|
+
return;
|
|
105432
|
+
this._isActive = false;
|
|
105433
|
+
this._isLooking = false;
|
|
105434
|
+
this._pressed.clear();
|
|
105435
|
+
if (this._updateHandler) {
|
|
105436
|
+
try {
|
|
105437
|
+
this.app?.off?.('update', this._updateHandler);
|
|
105438
|
+
}
|
|
105439
|
+
catch {
|
|
105440
|
+
// ignore
|
|
105441
|
+
}
|
|
105442
|
+
this._updateHandler = null;
|
|
105443
|
+
}
|
|
105444
|
+
}
|
|
105445
|
+
destroy() {
|
|
105446
|
+
try {
|
|
105447
|
+
this.deactivate();
|
|
105448
|
+
}
|
|
105449
|
+
finally {
|
|
105450
|
+
this._unbindInputListeners();
|
|
105451
|
+
}
|
|
105452
|
+
}
|
|
105453
|
+
setConfig(config) {
|
|
105454
|
+
if (config.moveSpeed !== undefined)
|
|
105455
|
+
this.moveSpeed = config.moveSpeed;
|
|
105456
|
+
if (config.fastSpeedMultiplier !== undefined)
|
|
105457
|
+
this.fastSpeedMultiplier = config.fastSpeedMultiplier;
|
|
105458
|
+
if (config.slowSpeedMultiplier !== undefined)
|
|
105459
|
+
this.slowSpeedMultiplier = config.slowSpeedMultiplier;
|
|
105460
|
+
if (config.lookSensitivity !== undefined)
|
|
105461
|
+
this.lookSensitivity = config.lookSensitivity;
|
|
105462
|
+
if (config.invertY !== undefined)
|
|
105463
|
+
this.invertY = config.invertY;
|
|
105464
|
+
if (config.keyBindings !== undefined)
|
|
105465
|
+
this.keyBindings = config.keyBindings;
|
|
105466
|
+
if (config.smoothing !== undefined)
|
|
105467
|
+
this.smoothing = config.smoothing;
|
|
105468
|
+
if (config.friction !== undefined)
|
|
105469
|
+
this.friction = config.friction;
|
|
105470
|
+
if (config.enableCollision !== undefined)
|
|
105471
|
+
this.enableCollision = config.enableCollision;
|
|
105472
|
+
if (config.minHeight !== undefined)
|
|
105473
|
+
this.minHeight = config.minHeight;
|
|
105474
|
+
if (config.maxHeight !== undefined)
|
|
105475
|
+
this.maxHeight = config.maxHeight;
|
|
105476
|
+
}
|
|
105477
|
+
getState() {
|
|
105478
|
+
const pos = this.entity?.getPosition?.();
|
|
105479
|
+
return {
|
|
105480
|
+
position: { x: pos?.x ?? 0, y: pos?.y ?? 0, z: pos?.z ?? 0 },
|
|
105481
|
+
rotation: { pitch: this._pitch, yaw: this._yaw },
|
|
105482
|
+
velocity: { x: this._velocity.x, y: this._velocity.y, z: this._velocity.z },
|
|
105483
|
+
isMoving: Math.abs(this._velocity.x) +
|
|
105484
|
+
Math.abs(this._velocity.y) +
|
|
105485
|
+
Math.abs(this._velocity.z) >
|
|
105486
|
+
1e-4,
|
|
105487
|
+
};
|
|
105488
|
+
}
|
|
105489
|
+
_handleKeyDown(e) {
|
|
105490
|
+
const keys = [];
|
|
105491
|
+
if (e.code)
|
|
105492
|
+
keys.push(e.code);
|
|
105493
|
+
if (e.key) {
|
|
105494
|
+
keys.push(e.key);
|
|
105495
|
+
if (e.key.length === 1) {
|
|
105496
|
+
keys.push(`Key${e.key.toUpperCase()}`);
|
|
105497
|
+
keys.push(e.key.toUpperCase());
|
|
105498
|
+
keys.push(e.key.toLowerCase());
|
|
105499
|
+
}
|
|
105500
|
+
}
|
|
105501
|
+
for (const k of keys)
|
|
105502
|
+
this._pressed.add(k);
|
|
105503
|
+
}
|
|
105504
|
+
_handleKeyUp(e) {
|
|
105505
|
+
const keys = [];
|
|
105506
|
+
if (e.code)
|
|
105507
|
+
keys.push(e.code);
|
|
105508
|
+
if (e.key) {
|
|
105509
|
+
keys.push(e.key);
|
|
105510
|
+
if (e.key.length === 1) {
|
|
105511
|
+
keys.push(`Key${e.key.toUpperCase()}`);
|
|
105512
|
+
keys.push(e.key.toUpperCase());
|
|
105513
|
+
keys.push(e.key.toLowerCase());
|
|
105514
|
+
}
|
|
105515
|
+
}
|
|
105516
|
+
for (const k of keys)
|
|
105517
|
+
this._pressed.delete(k);
|
|
105518
|
+
}
|
|
105519
|
+
_handleMouseMove(e) {
|
|
105520
|
+
if (!this._isLooking || !this._isActive)
|
|
105521
|
+
return;
|
|
105522
|
+
const dx = e.movementX * this.lookSensitivity;
|
|
105523
|
+
const dy = e.movementY * this.lookSensitivity * (this.invertY ? 1 : -1);
|
|
105524
|
+
this._yaw = (this._yaw - dx) % 360;
|
|
105525
|
+
this._pitch = Math.max(-89, Math.min(89, this._pitch + dy));
|
|
105526
|
+
}
|
|
105527
|
+
_handlePointerMove(e) {
|
|
105528
|
+
if (!this._isLooking || !this._isActive)
|
|
105529
|
+
return;
|
|
105530
|
+
const dx = (e.movementX || 0) * this.lookSensitivity;
|
|
105531
|
+
const dy = (e.movementY || 0) * this.lookSensitivity * (this.invertY ? 1 : -1);
|
|
105532
|
+
this._yaw = (this._yaw - dx) % 360;
|
|
105533
|
+
this._pitch = Math.max(-89, Math.min(89, this._pitch + dy));
|
|
105534
|
+
}
|
|
105535
|
+
_getEffectiveSpeed() {
|
|
105536
|
+
const fast = this._pressed.has(this.keyBindings.fastMove);
|
|
105537
|
+
const slow = this._pressed.has(this.keyBindings.slowMove);
|
|
105538
|
+
let s = this.moveSpeed;
|
|
105539
|
+
if (fast)
|
|
105540
|
+
s *= this.fastSpeedMultiplier;
|
|
105541
|
+
if (slow)
|
|
105542
|
+
s *= this.slowSpeedMultiplier;
|
|
105543
|
+
return s;
|
|
105544
|
+
}
|
|
105545
|
+
_updateVelocity() {
|
|
105546
|
+
const kb = this.keyBindings;
|
|
105547
|
+
const isPressed = (binding, fallbacks) => {
|
|
105548
|
+
const all = [binding, ...fallbacks].filter(Boolean);
|
|
105549
|
+
return all.some(k => this._pressed.has(k));
|
|
105550
|
+
};
|
|
105551
|
+
const forward = isPressed(kb.forward, ['KeyW', 'w', 'W']) ? 1 : 0;
|
|
105552
|
+
const backward = isPressed(kb.backward, ['KeyS', 's', 'S']) ? 1 : 0;
|
|
105553
|
+
const left = isPressed(kb.left, ['KeyA', 'a', 'A']) ? 1 : 0;
|
|
105554
|
+
const right = isPressed(kb.right, ['KeyD', 'd', 'D']) ? 1 : 0;
|
|
105555
|
+
const up = isPressed(kb.up, ['KeyE', 'e', 'E']) ? 1 : 0;
|
|
105556
|
+
const down = isPressed(kb.down, ['KeyQ', 'q', 'Q']) ? 1 : 0;
|
|
105557
|
+
const inputZ = forward - backward;
|
|
105558
|
+
const inputX = right - left;
|
|
105559
|
+
const inputY = up - down;
|
|
105560
|
+
const planarLen = Math.hypot(inputX, inputZ);
|
|
105561
|
+
const nx = planarLen > 0 ? inputX / planarLen : 0;
|
|
105562
|
+
const nz = planarLen > 0 ? inputZ / planarLen : 0;
|
|
105563
|
+
const speed = this._getEffectiveSpeed() * 2;
|
|
105564
|
+
const entity = this.entity;
|
|
105565
|
+
const fwd = entity?.forward && entity.forward.clone
|
|
105566
|
+
? entity.forward.clone()
|
|
105567
|
+
: entity?.forward
|
|
105568
|
+
? new Vec3(entity.forward.x, entity.forward.y, entity.forward.z)
|
|
105569
|
+
: new Vec3(0, 0, -1);
|
|
105570
|
+
const rightVec = entity?.right && entity.right.clone
|
|
105571
|
+
? entity.right.clone()
|
|
105572
|
+
: entity?.right
|
|
105573
|
+
? new Vec3(entity.right.x, entity.right.y, entity.right.z)
|
|
105574
|
+
: new Vec3(1, 0, 0);
|
|
105575
|
+
const upVec = entity?.up && entity.up.clone
|
|
105576
|
+
? entity.up.clone()
|
|
105577
|
+
: entity?.up
|
|
105578
|
+
? new Vec3(entity.up.x, entity.up.y, entity.up.z)
|
|
105579
|
+
: Vec3.UP.clone();
|
|
105580
|
+
const target = new Vec3(0, 0, 0);
|
|
105581
|
+
target.add(fwd.mulScalar(nz * speed));
|
|
105582
|
+
target.add(rightVec.mulScalar(nx * speed));
|
|
105583
|
+
target.add(upVec.mulScalar(inputY * speed));
|
|
105584
|
+
this._targetVelocity.copy(target);
|
|
105585
|
+
this._velocity.lerp(this._velocity, this._targetVelocity, Math.min(1, this.smoothing));
|
|
105586
|
+
if (nx === 0 && nz === 0 && inputY === 0) {
|
|
105587
|
+
this._velocity.mulScalar(this.friction);
|
|
105588
|
+
if (this._velocity.length() < 0.0001)
|
|
105589
|
+
this._velocity.set(0, 0, 0);
|
|
105590
|
+
}
|
|
105591
|
+
}
|
|
105592
|
+
_applyMovement(dt) {
|
|
105593
|
+
if (this._velocity.length() === 0)
|
|
105594
|
+
return;
|
|
105595
|
+
const pos = this.entity?.getPosition?.()?.clone?.() ?? new Vec3(0, 0, 0);
|
|
105596
|
+
pos.add(this._velocity.clone().mulScalar(dt));
|
|
105597
|
+
this.entity?.setPosition?.(pos);
|
|
105598
|
+
}
|
|
105599
|
+
_applyRotation() {
|
|
105600
|
+
if (this.entity?.setLocalEulerAngles) {
|
|
105601
|
+
this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
|
|
105602
|
+
}
|
|
105603
|
+
else if (this.entity?.setEulerAngles) {
|
|
105604
|
+
this.entity.setEulerAngles(this._pitch, this._yaw, 0);
|
|
105605
|
+
}
|
|
105606
|
+
}
|
|
105607
|
+
_applyConstraints() {
|
|
105608
|
+
if (!this.enableCollision &&
|
|
105609
|
+
this.minHeight == null &&
|
|
105610
|
+
this.maxHeight == null) {
|
|
105611
|
+
return;
|
|
105612
|
+
}
|
|
105613
|
+
const pos = this.entity?.getPosition?.()?.clone?.() ?? new Vec3(0, 0, 0);
|
|
105614
|
+
if (this.minHeight != null)
|
|
105615
|
+
pos.y = Math.max(pos.y, this.minHeight);
|
|
105616
|
+
if (this.maxHeight != null)
|
|
105617
|
+
pos.y = Math.min(pos.y, this.maxHeight);
|
|
105618
|
+
this.entity?.setPosition?.(pos);
|
|
105619
|
+
}
|
|
105620
|
+
update(dt) {
|
|
105621
|
+
if (!this._isActive)
|
|
105622
|
+
return;
|
|
105623
|
+
this._updateVelocity();
|
|
105624
|
+
this._applyMovement(dt);
|
|
105625
|
+
this._applyRotation();
|
|
105626
|
+
this._applyConstraints();
|
|
105627
|
+
// Emit throttled movement/look events (100ms)
|
|
105628
|
+
const now = performance.now();
|
|
105629
|
+
if (!this._lastMoveEmitTime || now - this._lastMoveEmitTime >= 100) {
|
|
105630
|
+
const pos = this.entity?.getPosition?.();
|
|
105631
|
+
this.emitFlyEvent?.('fly-camera-move', {
|
|
105632
|
+
position: { x: pos?.x ?? 0, y: pos?.y ?? 0, z: pos?.z ?? 0 },
|
|
105633
|
+
velocity: { x: this._velocity.x, y: this._velocity.y, z: this._velocity.z },
|
|
105634
|
+
});
|
|
105635
|
+
this._lastMoveEmitTime = now;
|
|
105636
|
+
}
|
|
105637
|
+
if (!this._lastLookEmitTime || now - this._lastLookEmitTime >= 100) {
|
|
105638
|
+
this.emitFlyEvent?.('fly-camera-look', { pitch: this._pitch, yaw: this._yaw });
|
|
105639
|
+
this._lastLookEmitTime = now;
|
|
105640
|
+
}
|
|
105641
|
+
}
|
|
105642
|
+
}
|
|
105643
|
+
|
|
105265
105644
|
// FlyCamera PlayCanvas script: first-person WASD movement with mouse-look
|
|
105266
105645
|
function registerFlyCameraScript() {
|
|
105267
105646
|
if (typeof pc === 'undefined') {
|
|
@@ -105347,10 +105726,18 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105347
105726
|
this._onKeyUp = this._handleKeyUp.bind(this);
|
|
105348
105727
|
document.addEventListener('keydown', this._onKeyDown, true);
|
|
105349
105728
|
document.addEventListener('keyup', this._onKeyUp, true);
|
|
105350
|
-
// Mouse move for look (while
|
|
105729
|
+
// Mouse/pointer move for look (while primary button held).
|
|
105730
|
+
//
|
|
105731
|
+
// Important: SuperSplat camera controls are pointer-event based and may call
|
|
105732
|
+
// preventDefault() on pointer events. When that happens, browsers often
|
|
105733
|
+
// suppress the corresponding legacy mouse events (mousedown/mousemove).
|
|
105734
|
+
// So we listen to *pointer* events as the primary path, with mouse as a
|
|
105735
|
+
// fallback for older environments.
|
|
105351
105736
|
this._onMouseMove = this._handleMouseMove.bind(this);
|
|
105737
|
+
this._onPointerMove = this._handlePointerMove.bind(this);
|
|
105352
105738
|
document.addEventListener('mousemove', this._onMouseMove);
|
|
105353
|
-
|
|
105739
|
+
document.addEventListener('pointermove', this._onPointerMove, true);
|
|
105740
|
+
// Button handling: click + hold to look, release to stop
|
|
105354
105741
|
const canvas = this.app.graphicsDevice.canvas;
|
|
105355
105742
|
this._onClickToLock = (e) => {
|
|
105356
105743
|
// Left button enables look while held (no pointer lock)
|
|
@@ -105358,13 +105745,17 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105358
105745
|
this._isLooking = true;
|
|
105359
105746
|
}
|
|
105360
105747
|
};
|
|
105361
|
-
canvas.addEventListener('mousedown', this._onClickToLock);
|
|
105748
|
+
canvas.addEventListener('mousedown', this._onClickToLock, true);
|
|
105749
|
+
this._onPointerDownToLook = this._handlePointerDown.bind(this);
|
|
105750
|
+
canvas.addEventListener('pointerdown', this._onPointerDownToLook, true);
|
|
105362
105751
|
this._onMouseUp = (e) => {
|
|
105363
105752
|
if (e.button === 0) {
|
|
105364
105753
|
this._isLooking = false;
|
|
105365
105754
|
}
|
|
105366
105755
|
};
|
|
105367
|
-
document.addEventListener('mouseup', this._onMouseUp);
|
|
105756
|
+
document.addEventListener('mouseup', this._onMouseUp, true);
|
|
105757
|
+
this._onPointerUp = this._handlePointerUp.bind(this);
|
|
105758
|
+
document.addEventListener('pointerup', this._onPointerUp, true);
|
|
105368
105759
|
};
|
|
105369
105760
|
FlyCamera.prototype.update = function (dt) {
|
|
105370
105761
|
if (!this._isActive)
|
|
@@ -105460,9 +105851,11 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105460
105851
|
this._onKeyDown = this._onKeyDown || this._handleKeyDown.bind(this);
|
|
105461
105852
|
this._onKeyUp = this._onKeyUp || this._handleKeyUp.bind(this);
|
|
105462
105853
|
this._onMouseMove = this._onMouseMove || this._handleMouseMove.bind(this);
|
|
105854
|
+
this._onPointerMove = this._onPointerMove || this._handlePointerMove.bind(this);
|
|
105463
105855
|
document.addEventListener('keydown', this._onKeyDown, true);
|
|
105464
105856
|
document.addEventListener('keyup', this._onKeyUp, true);
|
|
105465
105857
|
document.addEventListener('mousemove', this._onMouseMove);
|
|
105858
|
+
document.addEventListener('pointermove', this._onPointerMove, true);
|
|
105466
105859
|
const canvas = this.app.graphicsDevice.canvas;
|
|
105467
105860
|
this._onClickToLock =
|
|
105468
105861
|
this._onClickToLock ||
|
|
@@ -105471,7 +105864,10 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105471
105864
|
this._isLooking = true;
|
|
105472
105865
|
}
|
|
105473
105866
|
});
|
|
105474
|
-
canvas.addEventListener('mousedown', this._onClickToLock);
|
|
105867
|
+
canvas.addEventListener('mousedown', this._onClickToLock, true);
|
|
105868
|
+
this._onPointerDownToLook =
|
|
105869
|
+
this._onPointerDownToLook || this._handlePointerDown.bind(this);
|
|
105870
|
+
canvas.addEventListener('pointerdown', this._onPointerDownToLook, true);
|
|
105475
105871
|
this._onMouseUp =
|
|
105476
105872
|
this._onMouseUp ||
|
|
105477
105873
|
((e) => {
|
|
@@ -105479,22 +105875,36 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105479
105875
|
this._isLooking = false;
|
|
105480
105876
|
}
|
|
105481
105877
|
});
|
|
105482
|
-
document.addEventListener('mouseup', this._onMouseUp);
|
|
105878
|
+
document.addEventListener('mouseup', this._onMouseUp, true);
|
|
105879
|
+
this._onPointerUp = this._onPointerUp || this._handlePointerUp.bind(this);
|
|
105880
|
+
document.addEventListener('pointerup', this._onPointerUp, true);
|
|
105483
105881
|
};
|
|
105484
105882
|
FlyCamera.prototype.deactivate = function () {
|
|
105485
105883
|
if (!this._isActive)
|
|
105486
105884
|
return;
|
|
105487
105885
|
this._isActive = false;
|
|
105886
|
+
this._isLooking = false;
|
|
105887
|
+
try {
|
|
105888
|
+
this._pressed?.clear?.();
|
|
105889
|
+
}
|
|
105890
|
+
catch {
|
|
105891
|
+
// ignore
|
|
105892
|
+
}
|
|
105488
105893
|
// Exit pointer lock when deactivating
|
|
105489
105894
|
this._exitPointerLock();
|
|
105490
105895
|
// Remove listeners
|
|
105491
105896
|
document.removeEventListener('keydown', this._onKeyDown, true);
|
|
105492
105897
|
document.removeEventListener('keyup', this._onKeyUp, true);
|
|
105493
105898
|
document.removeEventListener('mousemove', this._onMouseMove);
|
|
105494
|
-
document.removeEventListener('
|
|
105899
|
+
document.removeEventListener('pointermove', this._onPointerMove, true);
|
|
105900
|
+
document.removeEventListener('mouseup', this._onMouseUp, true);
|
|
105901
|
+
document.removeEventListener('pointerup', this._onPointerUp, true);
|
|
105495
105902
|
const canvas = this.app?.graphicsDevice?.canvas;
|
|
105496
105903
|
if (canvas && this._onClickToLock) {
|
|
105497
|
-
canvas.removeEventListener('mousedown', this._onClickToLock);
|
|
105904
|
+
canvas.removeEventListener('mousedown', this._onClickToLock, true);
|
|
105905
|
+
}
|
|
105906
|
+
if (canvas && this._onPointerDownToLook) {
|
|
105907
|
+
canvas.removeEventListener('pointerdown', this._onPointerDownToLook, true);
|
|
105498
105908
|
}
|
|
105499
105909
|
};
|
|
105500
105910
|
FlyCamera.prototype.setConfig = function (config) {
|
|
@@ -105583,6 +105993,27 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105583
105993
|
this._yaw = (this._yaw - dx) % 360;
|
|
105584
105994
|
this._pitch = Math.max(-89, Math.min(89, this._pitch + dy));
|
|
105585
105995
|
};
|
|
105996
|
+
FlyCamera.prototype._handlePointerMove = function (e) {
|
|
105997
|
+
if (!this._isLooking || !this._isActive)
|
|
105998
|
+
return;
|
|
105999
|
+
const dx = (e.movementX || 0) * this.lookSensitivity;
|
|
106000
|
+
const dy = (e.movementY || 0) * this.lookSensitivity * (this.invertY ? 1 : -1);
|
|
106001
|
+
this._yaw = (this._yaw - dx) % 360;
|
|
106002
|
+
this._pitch = Math.max(-89, Math.min(89, this._pitch + dy));
|
|
106003
|
+
};
|
|
106004
|
+
FlyCamera.prototype._handlePointerDown = function (e) {
|
|
106005
|
+
if (!this._isActive)
|
|
106006
|
+
return;
|
|
106007
|
+
// Primary button enables look while held (no pointer lock)
|
|
106008
|
+
if (e.button === 0) {
|
|
106009
|
+
this._isLooking = true;
|
|
106010
|
+
}
|
|
106011
|
+
};
|
|
106012
|
+
FlyCamera.prototype._handlePointerUp = function (e) {
|
|
106013
|
+
if (e.button === 0) {
|
|
106014
|
+
this._isLooking = false;
|
|
106015
|
+
}
|
|
106016
|
+
};
|
|
105586
106017
|
FlyCamera.prototype._handlePointerLockChange = function () {
|
|
105587
106018
|
const canvas = this.app.graphicsDevice.canvas;
|
|
105588
106019
|
this._isPointerLocked = document.pointerLockElement === canvas;
|
|
@@ -105879,7 +106310,7 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105879
106310
|
scope.resolve(key).setValue(values[key]);
|
|
105880
106311
|
}
|
|
105881
106312
|
};
|
|
105882
|
-
const createBlueNoiseTexture = (device) => {
|
|
106313
|
+
const createBlueNoiseTexture$1 = (device) => {
|
|
105883
106314
|
const size = 32;
|
|
105884
106315
|
const texture = new Texture$1(device, {
|
|
105885
106316
|
width: size,
|
|
@@ -105958,7 +106389,7 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
105958
106389
|
throw new Error('InfiniteGrid: QuadRender is not available in this PlayCanvas version.');
|
|
105959
106390
|
}
|
|
105960
106391
|
this.quadRender = new QuadRender$1(this.shader);
|
|
105961
|
-
this.blueNoiseTexture = createBlueNoiseTexture(device);
|
|
106392
|
+
this.blueNoiseTexture = createBlueNoiseTexture$1(device);
|
|
105962
106393
|
this._createBlendState();
|
|
105963
106394
|
this._registerRenderHook();
|
|
105964
106395
|
}
|
|
@@ -139095,6 +139526,7 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
139095
139526
|
enableStats: false,
|
|
139096
139527
|
autoFocus: true,
|
|
139097
139528
|
maxSplats: 2000000,
|
|
139529
|
+
previewMode: false,
|
|
139098
139530
|
camera: {
|
|
139099
139531
|
position: { x: 0, y: 0, z: 10 },
|
|
139100
139532
|
target: { x: 0, y: 0, z: 0 },
|
|
@@ -139184,6 +139616,10 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
139184
139616
|
if (enableStats !== null) {
|
|
139185
139617
|
config.enableStats = parseBoolean(enableStats);
|
|
139186
139618
|
}
|
|
139619
|
+
const previewMode = element.getAttribute('preview-mode');
|
|
139620
|
+
if (previewMode !== null) {
|
|
139621
|
+
config.previewMode = parseBoolean(previewMode);
|
|
139622
|
+
}
|
|
139187
139623
|
// Parse number attributes
|
|
139188
139624
|
const maxSplats = element.getAttribute('max-splats');
|
|
139189
139625
|
if (maxSplats !== null) {
|
|
@@ -140324,51 +140760,161 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
|
|
|
140324
140760
|
return fallback.clone();
|
|
140325
140761
|
}
|
|
140326
140762
|
|
|
140327
|
-
const
|
|
140328
|
-
|
|
140329
|
-
|
|
140330
|
-
|
|
140331
|
-
|
|
140763
|
+
const cache = new WeakMap();
|
|
140764
|
+
function createBlueNoiseTexture(device) {
|
|
140765
|
+
const size = 32;
|
|
140766
|
+
const texture = new Texture$1(device, {
|
|
140767
|
+
width: size,
|
|
140768
|
+
height: size,
|
|
140769
|
+
format: PIXELFORMAT_R8_G8_B8_A8,
|
|
140770
|
+
mipmaps: false,
|
|
140771
|
+
});
|
|
140772
|
+
texture.addressU = ADDRESS_REPEAT;
|
|
140773
|
+
texture.addressV = ADDRESS_REPEAT;
|
|
140774
|
+
texture.minFilter = FILTER_NEAREST;
|
|
140775
|
+
texture.magFilter = FILTER_NEAREST;
|
|
140776
|
+
const pixels = texture.lock();
|
|
140777
|
+
const seed = 1337;
|
|
140778
|
+
let value = seed;
|
|
140779
|
+
const random = () => {
|
|
140780
|
+
value ^= value << 13;
|
|
140781
|
+
value ^= value >>> 17;
|
|
140782
|
+
value ^= value << 5;
|
|
140783
|
+
return ((value >>> 0) % 256) / 255;
|
|
140784
|
+
};
|
|
140785
|
+
for (let i = 0; i < size * size; i++) {
|
|
140786
|
+
const noise = Math.floor(random() * 255);
|
|
140787
|
+
const idx = i * 4;
|
|
140788
|
+
pixels[idx + 0] = noise;
|
|
140789
|
+
pixels[idx + 1] = noise;
|
|
140790
|
+
pixels[idx + 2] = noise;
|
|
140791
|
+
pixels[idx + 3] = 255;
|
|
140792
|
+
}
|
|
140793
|
+
texture.unlock();
|
|
140794
|
+
texture.name = 'supersplat-blue-noise';
|
|
140795
|
+
return texture;
|
|
140796
|
+
}
|
|
140797
|
+
function getBlueNoiseTex32(device) {
|
|
140798
|
+
const existing = cache.get(device);
|
|
140799
|
+
if (existing)
|
|
140800
|
+
return existing;
|
|
140801
|
+
const tex = createBlueNoiseTexture(device);
|
|
140802
|
+
cache.set(device, tex);
|
|
140803
|
+
return tex;
|
|
140804
|
+
}
|
|
140332
140805
|
|
|
140333
|
-
|
|
140334
|
-
|
|
140806
|
+
// SuperSplat-like selection box shader: screen-space ray/box intersection that draws a thick
|
|
140807
|
+
// white wire/grid pattern (not dependent on WebGL line width).
|
|
140808
|
+
const BOX_SELECT_VS = /* glsl */ `
|
|
140809
|
+
attribute vec3 vertex_position;
|
|
140335
140810
|
|
|
140336
|
-
|
|
140811
|
+
uniform mat4 matrix_model;
|
|
140812
|
+
uniform mat4 matrix_viewProjection;
|
|
140337
140813
|
|
|
140338
|
-
void main(
|
|
140339
|
-
|
|
140340
|
-
|
|
140341
|
-
}
|
|
140814
|
+
void main() {
|
|
140815
|
+
gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
|
|
140816
|
+
}
|
|
140342
140817
|
`;
|
|
140343
|
-
const
|
|
140344
|
-
|
|
140345
|
-
|
|
140346
|
-
|
|
140818
|
+
const BOX_SELECT_FS = /* glsl */ `
|
|
140819
|
+
// ray-box intersection in box space
|
|
140820
|
+
bool intersectBox(out float t0, out float t1, out int axis0, out int axis1, vec3 pos, vec3 dir, vec3 boxCen, vec3 boxLen)
|
|
140821
|
+
{
|
|
140822
|
+
bvec3 validDir = notEqual(dir, vec3(0.0));
|
|
140823
|
+
vec3 absDir = abs(dir);
|
|
140824
|
+
vec3 signDir = sign(dir);
|
|
140825
|
+
vec3 m = vec3(
|
|
140826
|
+
validDir.x ? 1.0 / absDir.x : 0.0,
|
|
140827
|
+
validDir.y ? 1.0 / absDir.y : 0.0,
|
|
140828
|
+
validDir.z ? 1.0 / absDir.z : 0.0
|
|
140829
|
+
) * signDir;
|
|
140347
140830
|
|
|
140348
|
-
|
|
140831
|
+
vec3 n = m * (pos - boxCen);
|
|
140832
|
+
vec3 k = abs(m) * boxLen;
|
|
140349
140833
|
|
|
140350
|
-
|
|
140351
|
-
|
|
140352
|
-
uniform float opacity;
|
|
140834
|
+
vec3 v0 = -n - k;
|
|
140835
|
+
vec3 v1 = -n + k;
|
|
140353
140836
|
|
|
140354
|
-
|
|
140837
|
+
// replace invalid axes with -inf and +inf so the tests below ignore them
|
|
140838
|
+
v0 = mix(vec3(-1.0 / 0.0000001), v0, validDir);
|
|
140839
|
+
v1 = mix(vec3(1.0 / 0.0000001), v1, validDir);
|
|
140355
140840
|
|
|
140356
|
-
|
|
140357
|
-
|
|
140358
|
-
float d = min(min(vUv.x, 1.0 - vUv.x), min(vUv.y, 1.0 - vUv.y));
|
|
140841
|
+
axis0 = (v0.x > v0.y) ? ((v0.x > v0.z) ? 0 : 2) : ((v0.y > v0.z) ? 1 : 2);
|
|
140842
|
+
axis1 = (v1.x < v1.y) ? ((v1.x < v1.z) ? 0 : 2) : ((v1.y < v1.z) ? 1 : 2);
|
|
140359
140843
|
|
|
140360
|
-
|
|
140361
|
-
|
|
140362
|
-
#ifdef GL_OES_standard_derivatives
|
|
140363
|
-
aa = max(aa, fwidth(d) * 1.5);
|
|
140364
|
-
#endif
|
|
140365
|
-
float a = 1.0 - smoothstep(lineWidth, lineWidth + aa, d);
|
|
140366
|
-
a *= opacity;
|
|
140844
|
+
t0 = v0[axis0];
|
|
140845
|
+
t1 = v1[axis1];
|
|
140367
140846
|
|
|
140368
|
-
|
|
140369
|
-
|
|
140370
|
-
}
|
|
140847
|
+
if (t0 > t1 || t1 < 0.0) {
|
|
140848
|
+
return false;
|
|
140849
|
+
}
|
|
140850
|
+
|
|
140851
|
+
return true;
|
|
140852
|
+
}
|
|
140853
|
+
|
|
140854
|
+
float calcDepth(in vec3 pos, in mat4 viewProjection) {
|
|
140855
|
+
vec4 v = viewProjection * vec4(pos, 1.0);
|
|
140856
|
+
return (v.z / v.w) * 0.5 + 0.5;
|
|
140857
|
+
}
|
|
140858
|
+
|
|
140859
|
+
uniform sampler2D blueNoiseTex32;
|
|
140860
|
+
uniform mat4 matrix_viewProjection;
|
|
140861
|
+
uniform vec3 boxCen;
|
|
140862
|
+
uniform vec3 boxLen;
|
|
140863
|
+
|
|
140864
|
+
uniform vec3 near_origin;
|
|
140865
|
+
uniform vec3 near_x;
|
|
140866
|
+
uniform vec3 near_y;
|
|
140867
|
+
|
|
140868
|
+
uniform vec3 far_origin;
|
|
140869
|
+
uniform vec3 far_x;
|
|
140870
|
+
uniform vec3 far_y;
|
|
140871
|
+
|
|
140872
|
+
uniform vec2 targetSize;
|
|
140873
|
+
uniform vec3 lineColor;
|
|
140874
|
+
|
|
140875
|
+
bool writeDepth(float alpha) {
|
|
140876
|
+
ivec2 uv = ivec2(gl_FragCoord.xy);
|
|
140877
|
+
ivec2 size = textureSize(blueNoiseTex32, 0);
|
|
140878
|
+
return alpha > texelFetch(blueNoiseTex32, uv % size, 0).y;
|
|
140879
|
+
}
|
|
140880
|
+
|
|
140881
|
+
bool strips(vec3 pos, int axis) {
|
|
140882
|
+
// Thickness tuned to match SuperSplat viewer "thick wire"
|
|
140883
|
+
bvec3 b = lessThan(fract(pos * 2.0 + vec3(0.015)), vec3(0.06));
|
|
140884
|
+
b[axis] = false;
|
|
140885
|
+
return any(b);
|
|
140886
|
+
}
|
|
140887
|
+
|
|
140888
|
+
void main() {
|
|
140889
|
+
vec2 clip = gl_FragCoord.xy / targetSize;
|
|
140890
|
+
vec3 worldNear = near_origin + near_x * clip.x + near_y * clip.y;
|
|
140891
|
+
vec3 worldFar = far_origin + far_x * clip.x + far_y * clip.y;
|
|
140892
|
+
vec3 rayDir = normalize(worldFar - worldNear);
|
|
140893
|
+
|
|
140894
|
+
float t0, t1;
|
|
140895
|
+
int axis0, axis1;
|
|
140896
|
+
if (!intersectBox(t0, t1, axis0, axis1, worldNear, rayDir, boxCen, boxLen)) {
|
|
140897
|
+
discard;
|
|
140898
|
+
}
|
|
140899
|
+
|
|
140900
|
+
vec3 frontPos = worldNear + rayDir * t0;
|
|
140901
|
+
bool front = t0 > 0.0 && strips(frontPos - boxCen, axis0);
|
|
140902
|
+
|
|
140903
|
+
vec3 backPos = worldNear + rayDir * t1;
|
|
140904
|
+
bool back = strips(backPos - boxCen, axis1);
|
|
140905
|
+
|
|
140906
|
+
if (front) {
|
|
140907
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
140908
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(frontPos, matrix_viewProjection) : 1.0;
|
|
140909
|
+
} else if (back) {
|
|
140910
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
140911
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(backPos, matrix_viewProjection) : 1.0;
|
|
140912
|
+
} else {
|
|
140913
|
+
discard;
|
|
140914
|
+
}
|
|
140915
|
+
}
|
|
140371
140916
|
`;
|
|
140917
|
+
const tmpPos$1 = new Vec3();
|
|
140372
140918
|
class BoxSelectionAPI {
|
|
140373
140919
|
constructor() {
|
|
140374
140920
|
this.boxes = new Map();
|
|
@@ -140405,6 +140951,7 @@ void main(void) {
|
|
|
140405
140951
|
this.ensureTranslateGizmo();
|
|
140406
140952
|
this.updateGizmoSize();
|
|
140407
140953
|
this.updateGizmoAttachment();
|
|
140954
|
+
this.updateBoxShaderUniforms();
|
|
140408
140955
|
}
|
|
140409
140956
|
createBox(options = {}) {
|
|
140410
140957
|
if (!this.app || !this.parent) {
|
|
@@ -140427,7 +140974,7 @@ void main(void) {
|
|
|
140427
140974
|
entity.render.castShadows = false;
|
|
140428
140975
|
entity.render.receiveShadows = false;
|
|
140429
140976
|
entity.render.enabled = visible;
|
|
140430
|
-
// Render as a thick wireframe (shader-based)
|
|
140977
|
+
// Render as a thick wireframe (shader-based) to match SuperSplat viewer.
|
|
140431
140978
|
this.parent.addChild(entity);
|
|
140432
140979
|
const record = {
|
|
140433
140980
|
id,
|
|
@@ -140514,8 +141061,8 @@ void main(void) {
|
|
|
140514
141061
|
return false;
|
|
140515
141062
|
}
|
|
140516
141063
|
record.color.set(r, g, b);
|
|
140517
|
-
const mat =
|
|
140518
|
-
record.
|
|
141064
|
+
const mat = record.entity.render?.material;
|
|
141065
|
+
mat?.setParameter?.('lineColor', [record.color.x, record.color.y, record.color.z]);
|
|
140519
141066
|
this.requestRender();
|
|
140520
141067
|
return true;
|
|
140521
141068
|
}
|
|
@@ -140710,19 +141257,34 @@ void main(void) {
|
|
|
140710
141257
|
}
|
|
140711
141258
|
buildWireframeMaterial(color) {
|
|
140712
141259
|
const material = new ShaderMaterial$1({
|
|
140713
|
-
uniqueName: '
|
|
140714
|
-
vertexGLSL:
|
|
140715
|
-
fragmentGLSL:
|
|
141260
|
+
uniqueName: 'boxSelectionSupersplatWire',
|
|
141261
|
+
vertexGLSL: BOX_SELECT_VS,
|
|
141262
|
+
fragmentGLSL: BOX_SELECT_FS,
|
|
140716
141263
|
});
|
|
140717
|
-
material.cull =
|
|
140718
|
-
material.depthWrite = false;
|
|
141264
|
+
material.cull = CULLFACE_FRONT;
|
|
140719
141265
|
material.blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE_MINUS_SRC_ALPHA);
|
|
140720
|
-
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
140721
|
-
material.setParameter('lineWidth', THICK_WIREFRAME_LINE_WIDTH_UV$1);
|
|
140722
|
-
material.setParameter('opacity', THICK_WIREFRAME_OPACITY$1);
|
|
140723
141266
|
material.update();
|
|
141267
|
+
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
140724
141268
|
return material;
|
|
140725
141269
|
}
|
|
141270
|
+
updateBoxShaderUniforms() {
|
|
141271
|
+
if (!this.app)
|
|
141272
|
+
return;
|
|
141273
|
+
const device = this.app.graphicsDevice;
|
|
141274
|
+
// Ensure required global uniforms for selection shaders.
|
|
141275
|
+
device.scope.resolve('targetSize').setValue([device.width, device.height]);
|
|
141276
|
+
device.scope.resolve('blueNoiseTex32').setValue(getBlueNoiseTex32(device));
|
|
141277
|
+
for (const record of this.boxes.values()) {
|
|
141278
|
+
const mat = record.entity.render?.material;
|
|
141279
|
+
if (!mat?.setParameter)
|
|
141280
|
+
continue;
|
|
141281
|
+
// World-space center
|
|
141282
|
+
record.entity.getWorldTransform().getTranslation(tmpPos$1);
|
|
141283
|
+
mat.setParameter('boxCen', [tmpPos$1.x, tmpPos$1.y, tmpPos$1.z]);
|
|
141284
|
+
// Half extents
|
|
141285
|
+
mat.setParameter('boxLen', [record.lenX * 0.5, record.lenY * 0.5, record.lenZ * 0.5]);
|
|
141286
|
+
}
|
|
141287
|
+
}
|
|
140726
141288
|
emitEvent(event, detail) {
|
|
140727
141289
|
if (this.onEvent) {
|
|
140728
141290
|
this.onEvent(event, detail);
|
|
@@ -140734,59 +141296,110 @@ void main(void) {
|
|
|
140734
141296
|
}
|
|
140735
141297
|
}
|
|
140736
141298
|
|
|
140737
|
-
|
|
140738
|
-
|
|
140739
|
-
const
|
|
140740
|
-
|
|
140741
|
-
const THICK_WIREFRAME_SPHERE_VS = /* glsl */ `
|
|
140742
|
-
attribute vec3 vertex_position;
|
|
140743
|
-
attribute vec2 vertex_texCoord0;
|
|
140744
|
-
|
|
140745
|
-
uniform mat4 matrix_model;
|
|
140746
|
-
uniform mat4 matrix_viewProjection;
|
|
141299
|
+
// SuperSplat-like selection sphere shader: screen-space ray/sphere intersection that draws a thick
|
|
141300
|
+
// white wire/grid pattern (not dependent on WebGL line width).
|
|
141301
|
+
const SPHERE_SELECT_VS = /* glsl */ `
|
|
141302
|
+
attribute vec3 vertex_position;
|
|
140747
141303
|
|
|
140748
|
-
|
|
141304
|
+
uniform mat4 matrix_model;
|
|
141305
|
+
uniform mat4 matrix_viewProjection;
|
|
140749
141306
|
|
|
140750
|
-
void main(
|
|
140751
|
-
|
|
140752
|
-
|
|
140753
|
-
}
|
|
141307
|
+
void main() {
|
|
141308
|
+
gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
|
|
141309
|
+
}
|
|
140754
141310
|
`;
|
|
140755
|
-
const
|
|
140756
|
-
|
|
140757
|
-
|
|
140758
|
-
|
|
141311
|
+
const SPHERE_SELECT_FS = /* glsl */ `
|
|
141312
|
+
bool intersectSphere(out float t0, out float t1, vec3 pos, vec3 dir, vec4 sphere) {
|
|
141313
|
+
vec3 L = sphere.xyz - pos;
|
|
141314
|
+
float tca = dot(L, dir);
|
|
140759
141315
|
|
|
140760
|
-
|
|
141316
|
+
float d2 = sphere.w * sphere.w - (dot(L, L) - tca * tca);
|
|
141317
|
+
if (d2 <= 0.0) {
|
|
141318
|
+
return false;
|
|
141319
|
+
}
|
|
140761
141320
|
|
|
140762
|
-
|
|
140763
|
-
|
|
140764
|
-
|
|
141321
|
+
float thc = sqrt(d2);
|
|
141322
|
+
t0 = tca - thc;
|
|
141323
|
+
t1 = tca + thc;
|
|
141324
|
+
if (t1 <= 0.0) {
|
|
141325
|
+
return false;
|
|
141326
|
+
}
|
|
140765
141327
|
|
|
140766
|
-
|
|
141328
|
+
return true;
|
|
141329
|
+
}
|
|
140767
141330
|
|
|
140768
|
-
float
|
|
140769
|
-
|
|
140770
|
-
|
|
140771
|
-
}
|
|
141331
|
+
float calcDepth(in vec3 pos, in mat4 viewProjection) {
|
|
141332
|
+
vec4 v = viewProjection * vec4(pos, 1.0);
|
|
141333
|
+
return (v.z / v.w) * 0.5 + 0.5;
|
|
141334
|
+
}
|
|
140772
141335
|
|
|
140773
|
-
|
|
140774
|
-
|
|
140775
|
-
|
|
140776
|
-
|
|
140777
|
-
|
|
140778
|
-
|
|
140779
|
-
float aa = 0.002;
|
|
140780
|
-
#ifdef GL_OES_standard_derivatives
|
|
140781
|
-
aa = max(aa, fwidth(d) * 1.5);
|
|
140782
|
-
#endif
|
|
140783
|
-
float a = 1.0 - smoothstep(lineWidth, lineWidth + aa, d);
|
|
140784
|
-
a *= opacity;
|
|
141336
|
+
vec2 calcAzimuthElev(in vec3 dir) {
|
|
141337
|
+
float azimuth = atan(dir.z, dir.x);
|
|
141338
|
+
float elev = asin(dir.y);
|
|
141339
|
+
return vec2(azimuth, elev) * 180.0 / 3.14159;
|
|
141340
|
+
}
|
|
140785
141341
|
|
|
140786
|
-
|
|
140787
|
-
|
|
140788
|
-
|
|
141342
|
+
uniform sampler2D blueNoiseTex32;
|
|
141343
|
+
uniform mat4 matrix_viewProjection;
|
|
141344
|
+
uniform vec4 sphere;
|
|
141345
|
+
uniform vec3 lineColor;
|
|
141346
|
+
|
|
141347
|
+
uniform vec3 near_origin;
|
|
141348
|
+
uniform vec3 near_x;
|
|
141349
|
+
uniform vec3 near_y;
|
|
141350
|
+
|
|
141351
|
+
uniform vec3 far_origin;
|
|
141352
|
+
uniform vec3 far_x;
|
|
141353
|
+
uniform vec3 far_y;
|
|
141354
|
+
|
|
141355
|
+
uniform vec2 targetSize;
|
|
141356
|
+
|
|
141357
|
+
bool writeDepth(float alpha) {
|
|
141358
|
+
vec2 uv = fract(gl_FragCoord.xy / 32.0);
|
|
141359
|
+
float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
|
|
141360
|
+
return alpha > noise;
|
|
141361
|
+
}
|
|
141362
|
+
|
|
141363
|
+
bool strips(vec3 lp) {
|
|
141364
|
+
vec2 ae = calcAzimuthElev(normalize(lp));
|
|
141365
|
+
|
|
141366
|
+
float spacing = 180.0 / (2.0 * 3.14159 * sphere.w);
|
|
141367
|
+
// Thickness tuned to match SuperSplat viewer "thick wire"
|
|
141368
|
+
float size = 0.06;
|
|
141369
|
+
return fract(ae.x / spacing) < size ||
|
|
141370
|
+
fract(ae.y / spacing) < size;
|
|
141371
|
+
}
|
|
141372
|
+
|
|
141373
|
+
void main() {
|
|
141374
|
+
vec2 clip = gl_FragCoord.xy / targetSize;
|
|
141375
|
+
vec3 worldNear = near_origin + near_x * clip.x + near_y * clip.y;
|
|
141376
|
+
vec3 worldFar = far_origin + far_x * clip.x + far_y * clip.y;
|
|
141377
|
+
|
|
141378
|
+
vec3 rayDir = normalize(worldFar - worldNear);
|
|
141379
|
+
|
|
141380
|
+
float t0, t1;
|
|
141381
|
+
if (!intersectSphere(t0, t1, worldNear, rayDir, sphere)) {
|
|
141382
|
+
discard;
|
|
141383
|
+
}
|
|
141384
|
+
|
|
141385
|
+
vec3 frontPos = worldNear + rayDir * t0;
|
|
141386
|
+
bool front = t0 > 0.0 && strips(frontPos - sphere.xyz);
|
|
141387
|
+
|
|
141388
|
+
vec3 backPos = worldNear + rayDir * t1;
|
|
141389
|
+
bool back = strips(backPos - sphere.xyz);
|
|
141390
|
+
|
|
141391
|
+
if (front) {
|
|
141392
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
141393
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(frontPos, matrix_viewProjection) : 1.0;
|
|
141394
|
+
} else if (back) {
|
|
141395
|
+
gl_FragColor = vec4(lineColor, 0.6);
|
|
141396
|
+
gl_FragDepth = writeDepth(0.6) ? calcDepth(backPos, matrix_viewProjection) : 1.0;
|
|
141397
|
+
} else {
|
|
141398
|
+
discard;
|
|
141399
|
+
}
|
|
141400
|
+
}
|
|
140789
141401
|
`;
|
|
141402
|
+
const tmpPos = new Vec3();
|
|
140790
141403
|
class SphereSelectionAPI {
|
|
140791
141404
|
constructor() {
|
|
140792
141405
|
this.spheres = new Map();
|
|
@@ -140822,6 +141435,7 @@ void main(void) {
|
|
|
140822
141435
|
this.ensureTranslateGizmo();
|
|
140823
141436
|
this.updateGizmoSize();
|
|
140824
141437
|
this.updateGizmoAttachment();
|
|
141438
|
+
this.updateSphereShaderUniforms();
|
|
140825
141439
|
}
|
|
140826
141440
|
createSphere(options = {}) {
|
|
140827
141441
|
if (!this.app || !this.parent) {
|
|
@@ -140834,7 +141448,8 @@ void main(void) {
|
|
|
140834
141448
|
const visible = options.visible ?? true;
|
|
140835
141449
|
const entity = new Entity(id);
|
|
140836
141450
|
entity.addComponent('render', {
|
|
140837
|
-
|
|
141451
|
+
// Use a box proxy like SuperSplat; shader ray-marches a true sphere.
|
|
141452
|
+
type: 'box',
|
|
140838
141453
|
material: this.buildWireframeMaterial(color),
|
|
140839
141454
|
});
|
|
140840
141455
|
entity.setLocalScale(radius * 2, radius * 2, radius * 2); // sphere diameter = radius * 2
|
|
@@ -140842,7 +141457,7 @@ void main(void) {
|
|
|
140842
141457
|
entity.render.castShadows = false;
|
|
140843
141458
|
entity.render.receiveShadows = false;
|
|
140844
141459
|
entity.render.enabled = visible;
|
|
140845
|
-
// Render as a thick wireframe (shader-based)
|
|
141460
|
+
// Render as a thick wireframe (shader-based) to match SuperSplat viewer.
|
|
140846
141461
|
this.parent.addChild(entity);
|
|
140847
141462
|
const record = {
|
|
140848
141463
|
id,
|
|
@@ -140925,8 +141540,12 @@ void main(void) {
|
|
|
140925
141540
|
return false;
|
|
140926
141541
|
}
|
|
140927
141542
|
record.color.set(r, g, b);
|
|
140928
|
-
const mat =
|
|
140929
|
-
|
|
141543
|
+
const mat = record.entity.render?.material;
|
|
141544
|
+
mat?.setParameter?.('lineColor', [
|
|
141545
|
+
record.color.x,
|
|
141546
|
+
record.color.y,
|
|
141547
|
+
record.color.z,
|
|
141548
|
+
]);
|
|
140930
141549
|
this.requestRender();
|
|
140931
141550
|
return true;
|
|
140932
141551
|
}
|
|
@@ -141124,19 +141743,30 @@ void main(void) {
|
|
|
141124
141743
|
}
|
|
141125
141744
|
buildWireframeMaterial(color) {
|
|
141126
141745
|
const material = new ShaderMaterial$1({
|
|
141127
|
-
uniqueName: '
|
|
141128
|
-
vertexGLSL:
|
|
141129
|
-
fragmentGLSL:
|
|
141746
|
+
uniqueName: 'sphereSelectionSupersplatWire',
|
|
141747
|
+
vertexGLSL: SPHERE_SELECT_VS,
|
|
141748
|
+
fragmentGLSL: SPHERE_SELECT_FS,
|
|
141130
141749
|
});
|
|
141131
|
-
material.cull =
|
|
141132
|
-
material.depthWrite = false;
|
|
141750
|
+
material.cull = CULLFACE_FRONT;
|
|
141133
141751
|
material.blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE_MINUS_SRC_ALPHA);
|
|
141134
|
-
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
141135
|
-
material.setParameter('lineWidth', THICK_WIREFRAME_LINE_WIDTH_UV);
|
|
141136
|
-
material.setParameter('opacity', THICK_WIREFRAME_OPACITY);
|
|
141137
141752
|
material.update();
|
|
141753
|
+
material.setParameter('lineColor', [color.x, color.y, color.z]);
|
|
141138
141754
|
return material;
|
|
141139
141755
|
}
|
|
141756
|
+
updateSphereShaderUniforms() {
|
|
141757
|
+
if (!this.app)
|
|
141758
|
+
return;
|
|
141759
|
+
const device = this.app.graphicsDevice;
|
|
141760
|
+
device.scope.resolve('targetSize').setValue([device.width, device.height]);
|
|
141761
|
+
device.scope.resolve('blueNoiseTex32').setValue(getBlueNoiseTex32(device));
|
|
141762
|
+
for (const record of this.spheres.values()) {
|
|
141763
|
+
const mat = record.entity.render?.material;
|
|
141764
|
+
if (!mat?.setParameter)
|
|
141765
|
+
continue;
|
|
141766
|
+
record.entity.getWorldTransform().getTranslation(tmpPos);
|
|
141767
|
+
mat.setParameter('sphere', [tmpPos.x, tmpPos.y, tmpPos.z, record.radius]);
|
|
141768
|
+
}
|
|
141769
|
+
}
|
|
141140
141770
|
emitEvent(event, detail) {
|
|
141141
141771
|
if (this.onEvent) {
|
|
141142
141772
|
this.onEvent(event, detail);
|
|
@@ -142543,222 +143173,222 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
142543
143173
|
}
|
|
142544
143174
|
}
|
|
142545
143175
|
|
|
142546
|
-
const vertexShader$6 = /* glsl */ `
|
|
142547
|
-
attribute vec2 vertex_position;
|
|
142548
|
-
void main(void) {
|
|
142549
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
142550
|
-
}
|
|
142551
|
-
`;
|
|
142552
|
-
const fragmentShader$6 = /* glsl */ `
|
|
142553
|
-
uniform highp usampler2D transformA; // splat center x, y, z
|
|
142554
|
-
uniform highp usampler2D splatTransform; // transform palette index
|
|
142555
|
-
uniform sampler2D transformPalette; // palette of transforms
|
|
142556
|
-
uniform sampler2D splatState; // per-splat state
|
|
142557
|
-
uniform highp ivec3 splat_params; // texture width, texture height, num splats
|
|
142558
|
-
uniform highp uint mode; // 0: selected, 1: visible
|
|
142559
|
-
|
|
142560
|
-
// calculate min and max for a single column of splats
|
|
142561
|
-
void main(void) {
|
|
142562
|
-
|
|
142563
|
-
vec3 boundMin = vec3(1e6);
|
|
142564
|
-
vec3 boundMax = vec3(-1e6);
|
|
142565
|
-
|
|
142566
|
-
for (int id = 0; id < splat_params.y; id++) {
|
|
142567
|
-
// calculate splatUV
|
|
142568
|
-
ivec2 splatUV = ivec2(gl_FragCoord.x, id);
|
|
142569
|
-
|
|
142570
|
-
// skip out-of-range splats
|
|
142571
|
-
if ((splatUV.x + splatUV.y * splat_params.x) >= splat_params.z) {
|
|
142572
|
-
continue;
|
|
142573
|
-
}
|
|
142574
|
-
|
|
142575
|
-
// read splat state
|
|
142576
|
-
uint state = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
|
|
142577
|
-
|
|
142578
|
-
// skip deleted or locked splats
|
|
142579
|
-
if (((mode == 0u) && (state != 1u)) || ((mode == 1u) && ((state & 4u) != 0u))) {
|
|
142580
|
-
continue;
|
|
142581
|
-
}
|
|
142582
|
-
|
|
142583
|
-
// read splat center
|
|
142584
|
-
vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
|
|
142585
|
-
|
|
142586
|
-
// apply optional per-splat transform
|
|
142587
|
-
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
142588
|
-
if (transformIndex > 0u) {
|
|
142589
|
-
// read transform matrix
|
|
142590
|
-
int u = int(transformIndex % 512u) * 3;
|
|
142591
|
-
int v = int(transformIndex / 512u);
|
|
142592
|
-
|
|
142593
|
-
mat3x4 t;
|
|
142594
|
-
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
142595
|
-
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
142596
|
-
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
142597
|
-
|
|
142598
|
-
center = vec4(center, 1.0) * t;
|
|
142599
|
-
}
|
|
142600
|
-
|
|
142601
|
-
boundMin = min(boundMin, mix(center, boundMin, isinf(center)));
|
|
142602
|
-
boundMax = max(boundMax, mix(center, boundMax, isinf(center)));
|
|
142603
|
-
}
|
|
142604
|
-
|
|
142605
|
-
pcFragColor0 = vec4(boundMin, 0.0);
|
|
142606
|
-
pcFragColor1 = vec4(boundMax, 0.0);
|
|
142607
|
-
}
|
|
142608
|
-
`;
|
|
142609
|
-
|
|
142610
|
-
const vertexShader$5 = /* glsl */ `
|
|
142611
|
-
attribute vec2 vertex_position;
|
|
142612
|
-
void main(void) {
|
|
142613
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
142614
|
-
}
|
|
142615
|
-
`;
|
|
142616
|
-
const fragmentShader$5 = /* glsl */ `
|
|
142617
|
-
uniform highp usampler2D transformA; // splat center x, y, z
|
|
142618
|
-
uniform highp usampler2D splatTransform; // transform palette index
|
|
142619
|
-
uniform sampler2D transformPalette; // palette of transforms
|
|
142620
|
-
uniform uvec2 splat_params; // splat texture width, num splats
|
|
142621
|
-
|
|
142622
|
-
uniform mat4 matrix_model;
|
|
142623
|
-
uniform mat4 matrix_viewProjection;
|
|
142624
|
-
|
|
142625
|
-
uniform uvec2 output_params; // output width, height
|
|
142626
|
-
|
|
142627
|
-
// 0: mask, 1: rect, 2: sphere
|
|
142628
|
-
uniform int mode;
|
|
142629
|
-
|
|
142630
|
-
// mask params
|
|
142631
|
-
uniform sampler2D mask; // mask in alpha channel
|
|
142632
|
-
uniform vec2 mask_params; // mask width, height
|
|
142633
|
-
|
|
142634
|
-
// rect params
|
|
142635
|
-
uniform vec4 rect_params; // rect x, y, width, height
|
|
142636
|
-
|
|
142637
|
-
// sphere params
|
|
142638
|
-
uniform vec4 sphere_params; // sphere x, y, z, radius
|
|
142639
|
-
|
|
142640
|
-
// box params
|
|
142641
|
-
uniform vec4 box_params; // box x, y, z
|
|
142642
|
-
uniform vec4 aabb_params; // len x, y, z
|
|
142643
|
-
|
|
142644
|
-
void main(void) {
|
|
142645
|
-
// calculate output id
|
|
142646
|
-
uvec2 outputUV = uvec2(gl_FragCoord);
|
|
142647
|
-
uint outputId = (outputUV.x + outputUV.y * output_params.x) * 4u;
|
|
142648
|
-
|
|
142649
|
-
vec4 clr = vec4(0.0);
|
|
142650
|
-
|
|
142651
|
-
for (uint i = 0u; i < 4u; i++) {
|
|
142652
|
-
uint id = outputId + i;
|
|
142653
|
-
|
|
142654
|
-
if (id >= splat_params.y) {
|
|
142655
|
-
continue;
|
|
142656
|
-
}
|
|
142657
|
-
|
|
142658
|
-
// calculate splatUV
|
|
142659
|
-
ivec2 splatUV = ivec2(
|
|
142660
|
-
int(id % splat_params.x),
|
|
142661
|
-
int(id / splat_params.x)
|
|
142662
|
-
);
|
|
142663
|
-
|
|
142664
|
-
// read splat center
|
|
142665
|
-
vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
|
|
142666
|
-
|
|
142667
|
-
// apply optional per-splat transform
|
|
142668
|
-
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
142669
|
-
if (transformIndex > 0u) {
|
|
142670
|
-
// read transform matrix
|
|
142671
|
-
int u = int(transformIndex % 512u) * 3;
|
|
142672
|
-
int v = int(transformIndex / 512u);
|
|
142673
|
-
|
|
142674
|
-
mat3x4 t;
|
|
142675
|
-
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
142676
|
-
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
142677
|
-
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
142678
|
-
|
|
142679
|
-
center = vec4(center, 1.0) * t;
|
|
142680
|
-
}
|
|
142681
|
-
|
|
142682
|
-
// transform to clip space and discard if outside
|
|
142683
|
-
vec3 world = (matrix_model * vec4(center, 1.0)).xyz;
|
|
142684
|
-
vec4 clip = matrix_viewProjection * vec4(world, 1.0);
|
|
142685
|
-
vec3 ndc = clip.xyz / clip.w;
|
|
142686
|
-
|
|
142687
|
-
// skip offscreen fragments
|
|
142688
|
-
if (!any(greaterThan(abs(ndc), vec3(1.0)))) {
|
|
142689
|
-
if (mode == 0) {
|
|
142690
|
-
// select by mask
|
|
142691
|
-
ivec2 maskUV = ivec2((ndc.xy * vec2(0.5, -0.5) + 0.5) * mask_params);
|
|
142692
|
-
clr[i] = texelFetch(mask, maskUV, 0).a < 1.0 ? 0.0 : 1.0;
|
|
142693
|
-
} else if (mode == 1) {
|
|
142694
|
-
// select by rect
|
|
142695
|
-
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;
|
|
142696
|
-
} else if (mode == 2) {
|
|
142697
|
-
// select by sphere
|
|
142698
|
-
clr[i] = length(world - sphere_params.xyz) < sphere_params.w ? 1.0 : 0.0;
|
|
142699
|
-
} else if (mode == 3) {
|
|
142700
|
-
// select by box
|
|
142701
|
-
vec3 relativePosition = world - box_params.xyz;
|
|
142702
|
-
bool isInsideCube = true;
|
|
142703
|
-
if (relativePosition.x < -aabb_params.x || relativePosition.x > aabb_params.x) {
|
|
142704
|
-
isInsideCube = false;
|
|
142705
|
-
}
|
|
142706
|
-
if (relativePosition.y < -aabb_params.y || relativePosition.y > aabb_params.y) {
|
|
142707
|
-
isInsideCube = false;
|
|
142708
|
-
}
|
|
142709
|
-
if (relativePosition.z < -aabb_params.z || relativePosition.z > aabb_params.z) {
|
|
142710
|
-
isInsideCube = false;
|
|
142711
|
-
}
|
|
142712
|
-
clr[i] = isInsideCube ? 1.0 : 0.0;
|
|
142713
|
-
}
|
|
142714
|
-
}
|
|
142715
|
-
}
|
|
142716
|
-
|
|
142717
|
-
gl_FragColor = clr;
|
|
142718
|
-
}
|
|
142719
|
-
`;
|
|
142720
|
-
|
|
142721
|
-
const vertexShader$4 = /* glsl */ `
|
|
142722
|
-
attribute vec2 vertex_position;
|
|
142723
|
-
void main(void) {
|
|
142724
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
142725
|
-
}
|
|
142726
|
-
`;
|
|
142727
|
-
const fragmentShader$4 = /* glsl */ `
|
|
142728
|
-
uniform highp usampler2D transformA; // splat center x, y, z
|
|
142729
|
-
uniform highp usampler2D splatTransform; // transform palette index
|
|
142730
|
-
uniform sampler2D transformPalette; // palette of transforms
|
|
142731
|
-
uniform ivec2 splat_params; // splat texture width, num splats
|
|
142732
|
-
|
|
142733
|
-
void main(void) {
|
|
142734
|
-
// calculate output id
|
|
142735
|
-
ivec2 splatUV = ivec2(gl_FragCoord);
|
|
142736
|
-
|
|
142737
|
-
// skip if splat index is out of bounds
|
|
142738
|
-
if (splatUV.x + splatUV.y * splat_params.x >= splat_params.y) {
|
|
142739
|
-
discard;
|
|
142740
|
-
}
|
|
142741
|
-
|
|
142742
|
-
// read splat center
|
|
142743
|
-
vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
|
|
142744
|
-
|
|
142745
|
-
// apply optional per-splat transform
|
|
142746
|
-
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
142747
|
-
if (transformIndex > 0u) {
|
|
142748
|
-
// read transform matrix
|
|
142749
|
-
int u = int(transformIndex % 512u) * 3;
|
|
142750
|
-
int v = int(transformIndex / 512u);
|
|
142751
|
-
|
|
142752
|
-
mat3x4 t;
|
|
142753
|
-
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
142754
|
-
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
142755
|
-
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
142756
|
-
|
|
142757
|
-
center = vec4(center, 1.0) * t;
|
|
142758
|
-
}
|
|
142759
|
-
|
|
142760
|
-
gl_FragColor = vec4(center, 0.0);
|
|
142761
|
-
}
|
|
143176
|
+
const vertexShader$6 = /* glsl */ `
|
|
143177
|
+
attribute vec2 vertex_position;
|
|
143178
|
+
void main(void) {
|
|
143179
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
143180
|
+
}
|
|
143181
|
+
`;
|
|
143182
|
+
const fragmentShader$6 = /* glsl */ `
|
|
143183
|
+
uniform highp usampler2D transformA; // splat center x, y, z
|
|
143184
|
+
uniform highp usampler2D splatTransform; // transform palette index
|
|
143185
|
+
uniform sampler2D transformPalette; // palette of transforms
|
|
143186
|
+
uniform sampler2D splatState; // per-splat state
|
|
143187
|
+
uniform highp ivec3 splat_params; // texture width, texture height, num splats
|
|
143188
|
+
uniform highp uint mode; // 0: selected, 1: visible
|
|
143189
|
+
|
|
143190
|
+
// calculate min and max for a single column of splats
|
|
143191
|
+
void main(void) {
|
|
143192
|
+
|
|
143193
|
+
vec3 boundMin = vec3(1e6);
|
|
143194
|
+
vec3 boundMax = vec3(-1e6);
|
|
143195
|
+
|
|
143196
|
+
for (int id = 0; id < splat_params.y; id++) {
|
|
143197
|
+
// calculate splatUV
|
|
143198
|
+
ivec2 splatUV = ivec2(gl_FragCoord.x, id);
|
|
143199
|
+
|
|
143200
|
+
// skip out-of-range splats
|
|
143201
|
+
if ((splatUV.x + splatUV.y * splat_params.x) >= splat_params.z) {
|
|
143202
|
+
continue;
|
|
143203
|
+
}
|
|
143204
|
+
|
|
143205
|
+
// read splat state
|
|
143206
|
+
uint state = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
|
|
143207
|
+
|
|
143208
|
+
// skip deleted or locked splats
|
|
143209
|
+
if (((mode == 0u) && (state != 1u)) || ((mode == 1u) && ((state & 4u) != 0u))) {
|
|
143210
|
+
continue;
|
|
143211
|
+
}
|
|
143212
|
+
|
|
143213
|
+
// read splat center
|
|
143214
|
+
vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
|
|
143215
|
+
|
|
143216
|
+
// apply optional per-splat transform
|
|
143217
|
+
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
143218
|
+
if (transformIndex > 0u) {
|
|
143219
|
+
// read transform matrix
|
|
143220
|
+
int u = int(transformIndex % 512u) * 3;
|
|
143221
|
+
int v = int(transformIndex / 512u);
|
|
143222
|
+
|
|
143223
|
+
mat3x4 t;
|
|
143224
|
+
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
143225
|
+
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
143226
|
+
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
143227
|
+
|
|
143228
|
+
center = vec4(center, 1.0) * t;
|
|
143229
|
+
}
|
|
143230
|
+
|
|
143231
|
+
boundMin = min(boundMin, mix(center, boundMin, isinf(center)));
|
|
143232
|
+
boundMax = max(boundMax, mix(center, boundMax, isinf(center)));
|
|
143233
|
+
}
|
|
143234
|
+
|
|
143235
|
+
pcFragColor0 = vec4(boundMin, 0.0);
|
|
143236
|
+
pcFragColor1 = vec4(boundMax, 0.0);
|
|
143237
|
+
}
|
|
143238
|
+
`;
|
|
143239
|
+
|
|
143240
|
+
const vertexShader$5 = /* glsl */ `
|
|
143241
|
+
attribute vec2 vertex_position;
|
|
143242
|
+
void main(void) {
|
|
143243
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
143244
|
+
}
|
|
143245
|
+
`;
|
|
143246
|
+
const fragmentShader$5 = /* glsl */ `
|
|
143247
|
+
uniform highp usampler2D transformA; // splat center x, y, z
|
|
143248
|
+
uniform highp usampler2D splatTransform; // transform palette index
|
|
143249
|
+
uniform sampler2D transformPalette; // palette of transforms
|
|
143250
|
+
uniform uvec2 splat_params; // splat texture width, num splats
|
|
143251
|
+
|
|
143252
|
+
uniform mat4 matrix_model;
|
|
143253
|
+
uniform mat4 matrix_viewProjection;
|
|
143254
|
+
|
|
143255
|
+
uniform uvec2 output_params; // output width, height
|
|
143256
|
+
|
|
143257
|
+
// 0: mask, 1: rect, 2: sphere
|
|
143258
|
+
uniform int mode;
|
|
143259
|
+
|
|
143260
|
+
// mask params
|
|
143261
|
+
uniform sampler2D mask; // mask in alpha channel
|
|
143262
|
+
uniform vec2 mask_params; // mask width, height
|
|
143263
|
+
|
|
143264
|
+
// rect params
|
|
143265
|
+
uniform vec4 rect_params; // rect x, y, width, height
|
|
143266
|
+
|
|
143267
|
+
// sphere params
|
|
143268
|
+
uniform vec4 sphere_params; // sphere x, y, z, radius
|
|
143269
|
+
|
|
143270
|
+
// box params
|
|
143271
|
+
uniform vec4 box_params; // box x, y, z
|
|
143272
|
+
uniform vec4 aabb_params; // len x, y, z
|
|
143273
|
+
|
|
143274
|
+
void main(void) {
|
|
143275
|
+
// calculate output id
|
|
143276
|
+
uvec2 outputUV = uvec2(gl_FragCoord);
|
|
143277
|
+
uint outputId = (outputUV.x + outputUV.y * output_params.x) * 4u;
|
|
143278
|
+
|
|
143279
|
+
vec4 clr = vec4(0.0);
|
|
143280
|
+
|
|
143281
|
+
for (uint i = 0u; i < 4u; i++) {
|
|
143282
|
+
uint id = outputId + i;
|
|
143283
|
+
|
|
143284
|
+
if (id >= splat_params.y) {
|
|
143285
|
+
continue;
|
|
143286
|
+
}
|
|
143287
|
+
|
|
143288
|
+
// calculate splatUV
|
|
143289
|
+
ivec2 splatUV = ivec2(
|
|
143290
|
+
int(id % splat_params.x),
|
|
143291
|
+
int(id / splat_params.x)
|
|
143292
|
+
);
|
|
143293
|
+
|
|
143294
|
+
// read splat center
|
|
143295
|
+
vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
|
|
143296
|
+
|
|
143297
|
+
// apply optional per-splat transform
|
|
143298
|
+
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
143299
|
+
if (transformIndex > 0u) {
|
|
143300
|
+
// read transform matrix
|
|
143301
|
+
int u = int(transformIndex % 512u) * 3;
|
|
143302
|
+
int v = int(transformIndex / 512u);
|
|
143303
|
+
|
|
143304
|
+
mat3x4 t;
|
|
143305
|
+
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
143306
|
+
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
143307
|
+
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
143308
|
+
|
|
143309
|
+
center = vec4(center, 1.0) * t;
|
|
143310
|
+
}
|
|
143311
|
+
|
|
143312
|
+
// transform to clip space and discard if outside
|
|
143313
|
+
vec3 world = (matrix_model * vec4(center, 1.0)).xyz;
|
|
143314
|
+
vec4 clip = matrix_viewProjection * vec4(world, 1.0);
|
|
143315
|
+
vec3 ndc = clip.xyz / clip.w;
|
|
143316
|
+
|
|
143317
|
+
// skip offscreen fragments
|
|
143318
|
+
if (!any(greaterThan(abs(ndc), vec3(1.0)))) {
|
|
143319
|
+
if (mode == 0) {
|
|
143320
|
+
// select by mask
|
|
143321
|
+
ivec2 maskUV = ivec2((ndc.xy * vec2(0.5, -0.5) + 0.5) * mask_params);
|
|
143322
|
+
clr[i] = texelFetch(mask, maskUV, 0).a < 1.0 ? 0.0 : 1.0;
|
|
143323
|
+
} else if (mode == 1) {
|
|
143324
|
+
// select by rect
|
|
143325
|
+
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;
|
|
143326
|
+
} else if (mode == 2) {
|
|
143327
|
+
// select by sphere
|
|
143328
|
+
clr[i] = length(world - sphere_params.xyz) < sphere_params.w ? 1.0 : 0.0;
|
|
143329
|
+
} else if (mode == 3) {
|
|
143330
|
+
// select by box
|
|
143331
|
+
vec3 relativePosition = world - box_params.xyz;
|
|
143332
|
+
bool isInsideCube = true;
|
|
143333
|
+
if (relativePosition.x < -aabb_params.x || relativePosition.x > aabb_params.x) {
|
|
143334
|
+
isInsideCube = false;
|
|
143335
|
+
}
|
|
143336
|
+
if (relativePosition.y < -aabb_params.y || relativePosition.y > aabb_params.y) {
|
|
143337
|
+
isInsideCube = false;
|
|
143338
|
+
}
|
|
143339
|
+
if (relativePosition.z < -aabb_params.z || relativePosition.z > aabb_params.z) {
|
|
143340
|
+
isInsideCube = false;
|
|
143341
|
+
}
|
|
143342
|
+
clr[i] = isInsideCube ? 1.0 : 0.0;
|
|
143343
|
+
}
|
|
143344
|
+
}
|
|
143345
|
+
}
|
|
143346
|
+
|
|
143347
|
+
gl_FragColor = clr;
|
|
143348
|
+
}
|
|
143349
|
+
`;
|
|
143350
|
+
|
|
143351
|
+
const vertexShader$4 = /* glsl */ `
|
|
143352
|
+
attribute vec2 vertex_position;
|
|
143353
|
+
void main(void) {
|
|
143354
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
143355
|
+
}
|
|
143356
|
+
`;
|
|
143357
|
+
const fragmentShader$4 = /* glsl */ `
|
|
143358
|
+
uniform highp usampler2D transformA; // splat center x, y, z
|
|
143359
|
+
uniform highp usampler2D splatTransform; // transform palette index
|
|
143360
|
+
uniform sampler2D transformPalette; // palette of transforms
|
|
143361
|
+
uniform ivec2 splat_params; // splat texture width, num splats
|
|
143362
|
+
|
|
143363
|
+
void main(void) {
|
|
143364
|
+
// calculate output id
|
|
143365
|
+
ivec2 splatUV = ivec2(gl_FragCoord);
|
|
143366
|
+
|
|
143367
|
+
// skip if splat index is out of bounds
|
|
143368
|
+
if (splatUV.x + splatUV.y * splat_params.x >= splat_params.y) {
|
|
143369
|
+
discard;
|
|
143370
|
+
}
|
|
143371
|
+
|
|
143372
|
+
// read splat center
|
|
143373
|
+
vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
|
|
143374
|
+
|
|
143375
|
+
// apply optional per-splat transform
|
|
143376
|
+
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
143377
|
+
if (transformIndex > 0u) {
|
|
143378
|
+
// read transform matrix
|
|
143379
|
+
int u = int(transformIndex % 512u) * 3;
|
|
143380
|
+
int v = int(transformIndex / 512u);
|
|
143381
|
+
|
|
143382
|
+
mat3x4 t;
|
|
143383
|
+
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
143384
|
+
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
143385
|
+
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
143386
|
+
|
|
143387
|
+
center = vec4(center, 1.0) * t;
|
|
143388
|
+
}
|
|
143389
|
+
|
|
143390
|
+
gl_FragColor = vec4(center, 0.0);
|
|
143391
|
+
}
|
|
142762
143392
|
`;
|
|
142763
143393
|
|
|
142764
143394
|
const v1 = new Vec3();
|
|
@@ -142795,18 +143425,18 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
142795
143425
|
attributes: {
|
|
142796
143426
|
vertex_position: SEMANTIC_POSITION
|
|
142797
143427
|
},
|
|
142798
|
-
vertexGLSL: `
|
|
142799
|
-
attribute vec2 vertex_position;
|
|
142800
|
-
void main(void) {
|
|
142801
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
142802
|
-
}
|
|
143428
|
+
vertexGLSL: `
|
|
143429
|
+
attribute vec2 vertex_position;
|
|
143430
|
+
void main(void) {
|
|
143431
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
143432
|
+
}
|
|
142803
143433
|
`,
|
|
142804
|
-
fragmentGLSL: `
|
|
142805
|
-
uniform sampler2D colorTex;
|
|
142806
|
-
void main(void) {
|
|
142807
|
-
ivec2 texel = ivec2(gl_FragCoord.xy);
|
|
142808
|
-
gl_FragColor = texelFetch(colorTex, texel, 0);
|
|
142809
|
-
}
|
|
143434
|
+
fragmentGLSL: `
|
|
143435
|
+
uniform sampler2D colorTex;
|
|
143436
|
+
void main(void) {
|
|
143437
|
+
ivec2 texel = ivec2(gl_FragCoord.xy);
|
|
143438
|
+
gl_FragColor = texelFetch(colorTex, texel, 0);
|
|
143439
|
+
}
|
|
142810
143440
|
`
|
|
142811
143441
|
});
|
|
142812
143442
|
// intersection test
|
|
@@ -144045,176 +144675,176 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
144045
144675
|
}
|
|
144046
144676
|
}
|
|
144047
144677
|
|
|
144048
|
-
const vertexShader$3 = /* glsl*/ `
|
|
144049
|
-
uniform vec3 near_origin;
|
|
144050
|
-
uniform vec3 near_x;
|
|
144051
|
-
uniform vec3 near_y;
|
|
144052
|
-
|
|
144053
|
-
uniform vec3 far_origin;
|
|
144054
|
-
uniform vec3 far_x;
|
|
144055
|
-
uniform vec3 far_y;
|
|
144056
|
-
|
|
144057
|
-
attribute vec2 vertex_position;
|
|
144058
|
-
|
|
144059
|
-
varying vec3 worldFar;
|
|
144060
|
-
varying vec3 worldNear;
|
|
144061
|
-
|
|
144062
|
-
void main(void) {
|
|
144063
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
144064
|
-
|
|
144065
|
-
vec2 p = vertex_position * 0.5 + 0.5;
|
|
144066
|
-
worldNear = near_origin + near_x * p.x + near_y * p.y;
|
|
144067
|
-
worldFar = far_origin + far_x * p.x + far_y * p.y;
|
|
144068
|
-
}
|
|
144069
|
-
`;
|
|
144070
|
-
const fragmentShader$3 = /* glsl*/ `
|
|
144071
|
-
uniform vec3 view_position;
|
|
144072
|
-
uniform mat4 matrix_viewProjection;
|
|
144073
|
-
uniform sampler2D blueNoiseTex32;
|
|
144074
|
-
|
|
144075
|
-
uniform int plane; // 0: x (yz), 1: y (xz), 2: z (xy)
|
|
144076
|
-
|
|
144077
|
-
vec4 planes[3] = vec4[3](
|
|
144078
|
-
vec4(1.0, 0.0, 0.0, 0.0),
|
|
144079
|
-
vec4(0.0, 1.0, 0.0, 0.0),
|
|
144080
|
-
vec4(0.0, 0.0, 1.0, 0.0)
|
|
144081
|
-
);
|
|
144082
|
-
|
|
144083
|
-
vec3 colors[3] = vec3[3](
|
|
144084
|
-
vec3(1.0, 0.2, 0.2),
|
|
144085
|
-
vec3(0.2, 1.0, 0.2),
|
|
144086
|
-
vec3(0.2, 0.2, 1.0)
|
|
144087
|
-
);
|
|
144088
|
-
|
|
144089
|
-
int axis0[3] = int[3](1, 0, 0);
|
|
144090
|
-
int axis1[3] = int[3](2, 2, 1);
|
|
144091
|
-
|
|
144092
|
-
varying vec3 worldNear;
|
|
144093
|
-
varying vec3 worldFar;
|
|
144094
|
-
|
|
144095
|
-
bool intersectPlane(inout float t, vec3 pos, vec3 dir, vec4 plane) {
|
|
144096
|
-
float d = dot(dir, plane.xyz);
|
|
144097
|
-
if (abs(d) < 1e-06) {
|
|
144098
|
-
return false;
|
|
144099
|
-
}
|
|
144100
|
-
|
|
144101
|
-
float n = -(dot(pos, plane.xyz) + plane.w) / d;
|
|
144102
|
-
if (n < 0.0) {
|
|
144103
|
-
return false;
|
|
144104
|
-
}
|
|
144105
|
-
|
|
144106
|
-
t = n;
|
|
144107
|
-
|
|
144108
|
-
return true;
|
|
144109
|
-
}
|
|
144110
|
-
|
|
144111
|
-
// https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#1e7c
|
|
144112
|
-
float pristineGrid(in vec2 uv, in vec2 ddx, in vec2 ddy, vec2 lineWidth) {
|
|
144113
|
-
vec2 uvDeriv = vec2(length(vec2(ddx.x, ddy.x)), length(vec2(ddx.y, ddy.y)));
|
|
144114
|
-
bvec2 invertLine = bvec2(lineWidth.x > 0.5, lineWidth.y > 0.5);
|
|
144115
|
-
vec2 targetWidth = vec2(
|
|
144116
|
-
invertLine.x ? 1.0 - lineWidth.x : lineWidth.x,
|
|
144117
|
-
invertLine.y ? 1.0 - lineWidth.y : lineWidth.y
|
|
144118
|
-
);
|
|
144119
|
-
vec2 drawWidth = clamp(targetWidth, uvDeriv, vec2(0.5));
|
|
144120
|
-
vec2 lineAA = uvDeriv * 1.5;
|
|
144121
|
-
vec2 gridUV = abs(fract(uv) * 2.0 - 1.0);
|
|
144122
|
-
gridUV.x = invertLine.x ? gridUV.x : 1.0 - gridUV.x;
|
|
144123
|
-
gridUV.y = invertLine.y ? gridUV.y : 1.0 - gridUV.y;
|
|
144124
|
-
vec2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);
|
|
144125
|
-
|
|
144126
|
-
grid2 *= clamp(targetWidth / drawWidth, 0.0, 1.0);
|
|
144127
|
-
grid2 = mix(grid2, targetWidth, clamp(uvDeriv * 2.0 - 1.0, 0.0, 1.0));
|
|
144128
|
-
grid2.x = invertLine.x ? 1.0 - grid2.x : grid2.x;
|
|
144129
|
-
grid2.y = invertLine.y ? 1.0 - grid2.y : grid2.y;
|
|
144130
|
-
|
|
144131
|
-
return mix(grid2.x, 1.0, grid2.y);
|
|
144132
|
-
}
|
|
144133
|
-
|
|
144134
|
-
float calcDepth(vec3 p) {
|
|
144135
|
-
vec4 v = matrix_viewProjection * vec4(p, 1.0);
|
|
144136
|
-
return (v.z / v.w) * 0.5 + 0.5;
|
|
144137
|
-
}
|
|
144138
|
-
|
|
144139
|
-
bool writeDepth(float alpha) {
|
|
144140
|
-
vec2 uv = fract(gl_FragCoord.xy / 32.0);
|
|
144141
|
-
float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
|
|
144142
|
-
return alpha > noise;
|
|
144143
|
-
}
|
|
144144
|
-
|
|
144145
|
-
void main(void) {
|
|
144146
|
-
vec3 p = worldNear;
|
|
144147
|
-
vec3 v = normalize(worldFar - worldNear);
|
|
144148
|
-
|
|
144149
|
-
// intersect ray with the world xz plane
|
|
144150
|
-
float t;
|
|
144151
|
-
if (!intersectPlane(t, p, v, planes[plane])) {
|
|
144152
|
-
discard;
|
|
144153
|
-
}
|
|
144154
|
-
|
|
144155
|
-
// calculate grid intersection
|
|
144156
|
-
vec3 worldPos = p + v * t;
|
|
144157
|
-
vec2 pos = plane == 0 ? worldPos.yz : (plane == 1 ? worldPos.xz : worldPos.xy);
|
|
144158
|
-
vec2 ddx = dFdx(pos);
|
|
144159
|
-
vec2 ddy = dFdy(pos);
|
|
144160
|
-
|
|
144161
|
-
float epsilon = 1.0 / 255.0;
|
|
144162
|
-
|
|
144163
|
-
// calculate fade
|
|
144164
|
-
float fade = 1.0 - smoothstep(400.0, 1000.0, length(worldPos - view_position));
|
|
144165
|
-
if (fade < epsilon) {
|
|
144166
|
-
discard;
|
|
144167
|
-
}
|
|
144168
|
-
|
|
144169
|
-
vec2 levelPos;
|
|
144170
|
-
float levelSize;
|
|
144171
|
-
float levelAlpha;
|
|
144172
|
-
|
|
144173
|
-
// 10m grid with colored main axes
|
|
144174
|
-
levelPos = pos * 0.1;
|
|
144175
|
-
levelSize = 2.0 / 1000.0;
|
|
144176
|
-
levelAlpha = pristineGrid(levelPos, ddx * 0.1, ddy * 0.1, vec2(levelSize)) * fade;
|
|
144177
|
-
if (levelAlpha > epsilon) {
|
|
144178
|
-
vec3 color;
|
|
144179
|
-
vec2 loc = abs(levelPos);
|
|
144180
|
-
if (loc.x < levelSize) {
|
|
144181
|
-
if (loc.y < levelSize) {
|
|
144182
|
-
color = vec3(1.0);
|
|
144183
|
-
} else {
|
|
144184
|
-
color = colors[axis1[plane]];
|
|
144185
|
-
}
|
|
144186
|
-
} else if (loc.y < levelSize) {
|
|
144187
|
-
color = colors[axis0[plane]];
|
|
144188
|
-
} else {
|
|
144189
|
-
color = vec3(0.9);
|
|
144190
|
-
}
|
|
144191
|
-
gl_FragColor = vec4(color, levelAlpha);
|
|
144192
|
-
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
|
|
144193
|
-
return;
|
|
144194
|
-
}
|
|
144195
|
-
|
|
144196
|
-
// 1m grid
|
|
144197
|
-
levelPos = pos;
|
|
144198
|
-
levelSize = 1.0 / 100.0;
|
|
144199
|
-
levelAlpha = pristineGrid(levelPos, ddx, ddy, vec2(levelSize)) * fade;
|
|
144200
|
-
if (levelAlpha > epsilon) {
|
|
144201
|
-
gl_FragColor = vec4(vec3(0.7), levelAlpha);
|
|
144202
|
-
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
|
|
144203
|
-
return;
|
|
144204
|
-
}
|
|
144205
|
-
|
|
144206
|
-
// 0.1m grid
|
|
144207
|
-
levelPos = pos * 10.0;
|
|
144208
|
-
levelSize = 1.0 / 100.0;
|
|
144209
|
-
levelAlpha = pristineGrid(levelPos, ddx * 10.0, ddy * 10.0, vec2(levelSize)) * fade;
|
|
144210
|
-
if (levelAlpha > epsilon) {
|
|
144211
|
-
gl_FragColor = vec4(vec3(0.7), levelAlpha);
|
|
144212
|
-
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
|
|
144213
|
-
return;
|
|
144214
|
-
}
|
|
144215
|
-
|
|
144216
|
-
discard;
|
|
144217
|
-
}
|
|
144678
|
+
const vertexShader$3 = /* glsl*/ `
|
|
144679
|
+
uniform vec3 near_origin;
|
|
144680
|
+
uniform vec3 near_x;
|
|
144681
|
+
uniform vec3 near_y;
|
|
144682
|
+
|
|
144683
|
+
uniform vec3 far_origin;
|
|
144684
|
+
uniform vec3 far_x;
|
|
144685
|
+
uniform vec3 far_y;
|
|
144686
|
+
|
|
144687
|
+
attribute vec2 vertex_position;
|
|
144688
|
+
|
|
144689
|
+
varying vec3 worldFar;
|
|
144690
|
+
varying vec3 worldNear;
|
|
144691
|
+
|
|
144692
|
+
void main(void) {
|
|
144693
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
144694
|
+
|
|
144695
|
+
vec2 p = vertex_position * 0.5 + 0.5;
|
|
144696
|
+
worldNear = near_origin + near_x * p.x + near_y * p.y;
|
|
144697
|
+
worldFar = far_origin + far_x * p.x + far_y * p.y;
|
|
144698
|
+
}
|
|
144699
|
+
`;
|
|
144700
|
+
const fragmentShader$3 = /* glsl*/ `
|
|
144701
|
+
uniform vec3 view_position;
|
|
144702
|
+
uniform mat4 matrix_viewProjection;
|
|
144703
|
+
uniform sampler2D blueNoiseTex32;
|
|
144704
|
+
|
|
144705
|
+
uniform int plane; // 0: x (yz), 1: y (xz), 2: z (xy)
|
|
144706
|
+
|
|
144707
|
+
vec4 planes[3] = vec4[3](
|
|
144708
|
+
vec4(1.0, 0.0, 0.0, 0.0),
|
|
144709
|
+
vec4(0.0, 1.0, 0.0, 0.0),
|
|
144710
|
+
vec4(0.0, 0.0, 1.0, 0.0)
|
|
144711
|
+
);
|
|
144712
|
+
|
|
144713
|
+
vec3 colors[3] = vec3[3](
|
|
144714
|
+
vec3(1.0, 0.2, 0.2),
|
|
144715
|
+
vec3(0.2, 1.0, 0.2),
|
|
144716
|
+
vec3(0.2, 0.2, 1.0)
|
|
144717
|
+
);
|
|
144718
|
+
|
|
144719
|
+
int axis0[3] = int[3](1, 0, 0);
|
|
144720
|
+
int axis1[3] = int[3](2, 2, 1);
|
|
144721
|
+
|
|
144722
|
+
varying vec3 worldNear;
|
|
144723
|
+
varying vec3 worldFar;
|
|
144724
|
+
|
|
144725
|
+
bool intersectPlane(inout float t, vec3 pos, vec3 dir, vec4 plane) {
|
|
144726
|
+
float d = dot(dir, plane.xyz);
|
|
144727
|
+
if (abs(d) < 1e-06) {
|
|
144728
|
+
return false;
|
|
144729
|
+
}
|
|
144730
|
+
|
|
144731
|
+
float n = -(dot(pos, plane.xyz) + plane.w) / d;
|
|
144732
|
+
if (n < 0.0) {
|
|
144733
|
+
return false;
|
|
144734
|
+
}
|
|
144735
|
+
|
|
144736
|
+
t = n;
|
|
144737
|
+
|
|
144738
|
+
return true;
|
|
144739
|
+
}
|
|
144740
|
+
|
|
144741
|
+
// https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#1e7c
|
|
144742
|
+
float pristineGrid(in vec2 uv, in vec2 ddx, in vec2 ddy, vec2 lineWidth) {
|
|
144743
|
+
vec2 uvDeriv = vec2(length(vec2(ddx.x, ddy.x)), length(vec2(ddx.y, ddy.y)));
|
|
144744
|
+
bvec2 invertLine = bvec2(lineWidth.x > 0.5, lineWidth.y > 0.5);
|
|
144745
|
+
vec2 targetWidth = vec2(
|
|
144746
|
+
invertLine.x ? 1.0 - lineWidth.x : lineWidth.x,
|
|
144747
|
+
invertLine.y ? 1.0 - lineWidth.y : lineWidth.y
|
|
144748
|
+
);
|
|
144749
|
+
vec2 drawWidth = clamp(targetWidth, uvDeriv, vec2(0.5));
|
|
144750
|
+
vec2 lineAA = uvDeriv * 1.5;
|
|
144751
|
+
vec2 gridUV = abs(fract(uv) * 2.0 - 1.0);
|
|
144752
|
+
gridUV.x = invertLine.x ? gridUV.x : 1.0 - gridUV.x;
|
|
144753
|
+
gridUV.y = invertLine.y ? gridUV.y : 1.0 - gridUV.y;
|
|
144754
|
+
vec2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);
|
|
144755
|
+
|
|
144756
|
+
grid2 *= clamp(targetWidth / drawWidth, 0.0, 1.0);
|
|
144757
|
+
grid2 = mix(grid2, targetWidth, clamp(uvDeriv * 2.0 - 1.0, 0.0, 1.0));
|
|
144758
|
+
grid2.x = invertLine.x ? 1.0 - grid2.x : grid2.x;
|
|
144759
|
+
grid2.y = invertLine.y ? 1.0 - grid2.y : grid2.y;
|
|
144760
|
+
|
|
144761
|
+
return mix(grid2.x, 1.0, grid2.y);
|
|
144762
|
+
}
|
|
144763
|
+
|
|
144764
|
+
float calcDepth(vec3 p) {
|
|
144765
|
+
vec4 v = matrix_viewProjection * vec4(p, 1.0);
|
|
144766
|
+
return (v.z / v.w) * 0.5 + 0.5;
|
|
144767
|
+
}
|
|
144768
|
+
|
|
144769
|
+
bool writeDepth(float alpha) {
|
|
144770
|
+
vec2 uv = fract(gl_FragCoord.xy / 32.0);
|
|
144771
|
+
float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
|
|
144772
|
+
return alpha > noise;
|
|
144773
|
+
}
|
|
144774
|
+
|
|
144775
|
+
void main(void) {
|
|
144776
|
+
vec3 p = worldNear;
|
|
144777
|
+
vec3 v = normalize(worldFar - worldNear);
|
|
144778
|
+
|
|
144779
|
+
// intersect ray with the world xz plane
|
|
144780
|
+
float t;
|
|
144781
|
+
if (!intersectPlane(t, p, v, planes[plane])) {
|
|
144782
|
+
discard;
|
|
144783
|
+
}
|
|
144784
|
+
|
|
144785
|
+
// calculate grid intersection
|
|
144786
|
+
vec3 worldPos = p + v * t;
|
|
144787
|
+
vec2 pos = plane == 0 ? worldPos.yz : (plane == 1 ? worldPos.xz : worldPos.xy);
|
|
144788
|
+
vec2 ddx = dFdx(pos);
|
|
144789
|
+
vec2 ddy = dFdy(pos);
|
|
144790
|
+
|
|
144791
|
+
float epsilon = 1.0 / 255.0;
|
|
144792
|
+
|
|
144793
|
+
// calculate fade
|
|
144794
|
+
float fade = 1.0 - smoothstep(400.0, 1000.0, length(worldPos - view_position));
|
|
144795
|
+
if (fade < epsilon) {
|
|
144796
|
+
discard;
|
|
144797
|
+
}
|
|
144798
|
+
|
|
144799
|
+
vec2 levelPos;
|
|
144800
|
+
float levelSize;
|
|
144801
|
+
float levelAlpha;
|
|
144802
|
+
|
|
144803
|
+
// 10m grid with colored main axes
|
|
144804
|
+
levelPos = pos * 0.1;
|
|
144805
|
+
levelSize = 2.0 / 1000.0;
|
|
144806
|
+
levelAlpha = pristineGrid(levelPos, ddx * 0.1, ddy * 0.1, vec2(levelSize)) * fade;
|
|
144807
|
+
if (levelAlpha > epsilon) {
|
|
144808
|
+
vec3 color;
|
|
144809
|
+
vec2 loc = abs(levelPos);
|
|
144810
|
+
if (loc.x < levelSize) {
|
|
144811
|
+
if (loc.y < levelSize) {
|
|
144812
|
+
color = vec3(1.0);
|
|
144813
|
+
} else {
|
|
144814
|
+
color = colors[axis1[plane]];
|
|
144815
|
+
}
|
|
144816
|
+
} else if (loc.y < levelSize) {
|
|
144817
|
+
color = colors[axis0[plane]];
|
|
144818
|
+
} else {
|
|
144819
|
+
color = vec3(0.9);
|
|
144820
|
+
}
|
|
144821
|
+
gl_FragColor = vec4(color, levelAlpha);
|
|
144822
|
+
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
|
|
144823
|
+
return;
|
|
144824
|
+
}
|
|
144825
|
+
|
|
144826
|
+
// 1m grid
|
|
144827
|
+
levelPos = pos;
|
|
144828
|
+
levelSize = 1.0 / 100.0;
|
|
144829
|
+
levelAlpha = pristineGrid(levelPos, ddx, ddy, vec2(levelSize)) * fade;
|
|
144830
|
+
if (levelAlpha > epsilon) {
|
|
144831
|
+
gl_FragColor = vec4(vec3(0.7), levelAlpha);
|
|
144832
|
+
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
|
|
144833
|
+
return;
|
|
144834
|
+
}
|
|
144835
|
+
|
|
144836
|
+
// 0.1m grid
|
|
144837
|
+
levelPos = pos * 10.0;
|
|
144838
|
+
levelSize = 1.0 / 100.0;
|
|
144839
|
+
levelAlpha = pristineGrid(levelPos, ddx * 10.0, ddy * 10.0, vec2(levelSize)) * fade;
|
|
144840
|
+
if (levelAlpha > epsilon) {
|
|
144841
|
+
gl_FragColor = vec4(vec3(0.7), levelAlpha);
|
|
144842
|
+
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
|
|
144843
|
+
return;
|
|
144844
|
+
}
|
|
144845
|
+
|
|
144846
|
+
discard;
|
|
144847
|
+
}
|
|
144218
144848
|
`;
|
|
144219
144849
|
|
|
144220
144850
|
const resolve = (scope, values) => {
|
|
@@ -144285,36 +144915,36 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
144285
144915
|
}
|
|
144286
144916
|
}
|
|
144287
144917
|
|
|
144288
|
-
const vertexShader$2 = /* glsl*/ `
|
|
144289
|
-
attribute vec2 vertex_position;
|
|
144290
|
-
void main(void) {
|
|
144291
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
144292
|
-
}
|
|
144293
|
-
`;
|
|
144294
|
-
const fragmentShader$2 = /* glsl*/ `
|
|
144295
|
-
uniform sampler2D outlineTexture;
|
|
144296
|
-
uniform float alphaCutoff;
|
|
144297
|
-
uniform vec4 clr;
|
|
144298
|
-
|
|
144299
|
-
void main(void) {
|
|
144300
|
-
ivec2 texel = ivec2(gl_FragCoord.xy);
|
|
144301
|
-
|
|
144302
|
-
// skip solid pixels
|
|
144303
|
-
if (texelFetch(outlineTexture, texel, 0).a > alphaCutoff) {
|
|
144304
|
-
discard;
|
|
144305
|
-
}
|
|
144306
|
-
|
|
144307
|
-
for (int x = -2; x <= 2; x++) {
|
|
144308
|
-
for (int y = -2; y <= 2; y++) {
|
|
144309
|
-
if ((x != 0) && (y != 0) && (texelFetch(outlineTexture, texel + ivec2(x, y), 0).a > alphaCutoff)) {
|
|
144310
|
-
gl_FragColor = clr;
|
|
144311
|
-
return;
|
|
144312
|
-
}
|
|
144313
|
-
}
|
|
144314
|
-
}
|
|
144315
|
-
|
|
144316
|
-
discard;
|
|
144317
|
-
}
|
|
144918
|
+
const vertexShader$2 = /* glsl*/ `
|
|
144919
|
+
attribute vec2 vertex_position;
|
|
144920
|
+
void main(void) {
|
|
144921
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
144922
|
+
}
|
|
144923
|
+
`;
|
|
144924
|
+
const fragmentShader$2 = /* glsl*/ `
|
|
144925
|
+
uniform sampler2D outlineTexture;
|
|
144926
|
+
uniform float alphaCutoff;
|
|
144927
|
+
uniform vec4 clr;
|
|
144928
|
+
|
|
144929
|
+
void main(void) {
|
|
144930
|
+
ivec2 texel = ivec2(gl_FragCoord.xy);
|
|
144931
|
+
|
|
144932
|
+
// skip solid pixels
|
|
144933
|
+
if (texelFetch(outlineTexture, texel, 0).a > alphaCutoff) {
|
|
144934
|
+
discard;
|
|
144935
|
+
}
|
|
144936
|
+
|
|
144937
|
+
for (int x = -2; x <= 2; x++) {
|
|
144938
|
+
for (int y = -2; y <= 2; y++) {
|
|
144939
|
+
if ((x != 0) && (y != 0) && (texelFetch(outlineTexture, texel + ivec2(x, y), 0).a > alphaCutoff)) {
|
|
144940
|
+
gl_FragColor = clr;
|
|
144941
|
+
return;
|
|
144942
|
+
}
|
|
144943
|
+
}
|
|
144944
|
+
}
|
|
144945
|
+
|
|
144946
|
+
discard;
|
|
144947
|
+
}
|
|
144318
144948
|
`;
|
|
144319
144949
|
|
|
144320
144950
|
class Outline extends Element$1 {
|
|
@@ -144632,72 +145262,72 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
144632
145262
|
}
|
|
144633
145263
|
}
|
|
144634
145264
|
|
|
144635
|
-
const vertexShader$1 = /* glsl */ `
|
|
144636
|
-
attribute uint vertex_id;
|
|
144637
|
-
|
|
144638
|
-
uniform mat4 matrix_model;
|
|
144639
|
-
uniform mat4 matrix_viewProjection;
|
|
144640
|
-
|
|
144641
|
-
uniform sampler2D splatState;
|
|
144642
|
-
uniform highp usampler2D splatPosition;
|
|
144643
|
-
uniform highp usampler2D splatTransform; // per-splat index into transform palette
|
|
144644
|
-
uniform sampler2D transformPalette; // palette of transform matrices
|
|
144645
|
-
|
|
144646
|
-
uniform uvec2 texParams;
|
|
144647
|
-
|
|
144648
|
-
uniform float splatSize;
|
|
144649
|
-
uniform vec4 selectedClr;
|
|
144650
|
-
uniform vec4 unselectedClr;
|
|
144651
|
-
|
|
144652
|
-
varying vec4 varying_color;
|
|
144653
|
-
|
|
144654
|
-
// calculate the current splat index and uv
|
|
144655
|
-
ivec2 calcSplatUV(uint index, uint width) {
|
|
144656
|
-
return ivec2(int(index % width), int(index / width));
|
|
144657
|
-
}
|
|
144658
|
-
|
|
144659
|
-
void main(void) {
|
|
144660
|
-
ivec2 splatUV = calcSplatUV(vertex_id, texParams.x);
|
|
144661
|
-
uint splatState = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
|
|
144662
|
-
|
|
144663
|
-
if ((splatState & 6u) != 0u) {
|
|
144664
|
-
// deleted or locked (4 or 2)
|
|
144665
|
-
gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
|
|
144666
|
-
gl_PointSize = 0.0;
|
|
144667
|
-
} else {
|
|
144668
|
-
mat4 model = matrix_model;
|
|
144669
|
-
|
|
144670
|
-
// handle per-splat transform
|
|
144671
|
-
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
144672
|
-
if (transformIndex > 0u) {
|
|
144673
|
-
// read transform matrix
|
|
144674
|
-
int u = int(transformIndex % 512u) * 3;
|
|
144675
|
-
int v = int(transformIndex / 512u);
|
|
144676
|
-
|
|
144677
|
-
mat4 t;
|
|
144678
|
-
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
144679
|
-
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
144680
|
-
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
144681
|
-
t[3] = vec4(0.0, 0.0, 0.0, 1.0);
|
|
144682
|
-
|
|
144683
|
-
model = matrix_model * transpose(t);
|
|
144684
|
-
}
|
|
144685
|
-
|
|
144686
|
-
varying_color = (splatState == 1u) ? selectedClr : unselectedClr;
|
|
144687
|
-
|
|
144688
|
-
vec3 center = uintBitsToFloat(texelFetch(splatPosition, splatUV, 0).xyz);
|
|
144689
|
-
|
|
144690
|
-
gl_Position = matrix_viewProjection * model * vec4(center, 1.0);
|
|
144691
|
-
gl_PointSize = splatSize;
|
|
144692
|
-
}
|
|
144693
|
-
}
|
|
144694
|
-
`;
|
|
144695
|
-
const fragmentShader$1 = /* glsl */ `
|
|
144696
|
-
varying vec4 varying_color;
|
|
144697
|
-
|
|
144698
|
-
void main(void) {
|
|
144699
|
-
gl_FragColor = varying_color;
|
|
144700
|
-
}
|
|
145265
|
+
const vertexShader$1 = /* glsl */ `
|
|
145266
|
+
attribute uint vertex_id;
|
|
145267
|
+
|
|
145268
|
+
uniform mat4 matrix_model;
|
|
145269
|
+
uniform mat4 matrix_viewProjection;
|
|
145270
|
+
|
|
145271
|
+
uniform sampler2D splatState;
|
|
145272
|
+
uniform highp usampler2D splatPosition;
|
|
145273
|
+
uniform highp usampler2D splatTransform; // per-splat index into transform palette
|
|
145274
|
+
uniform sampler2D transformPalette; // palette of transform matrices
|
|
145275
|
+
|
|
145276
|
+
uniform uvec2 texParams;
|
|
145277
|
+
|
|
145278
|
+
uniform float splatSize;
|
|
145279
|
+
uniform vec4 selectedClr;
|
|
145280
|
+
uniform vec4 unselectedClr;
|
|
145281
|
+
|
|
145282
|
+
varying vec4 varying_color;
|
|
145283
|
+
|
|
145284
|
+
// calculate the current splat index and uv
|
|
145285
|
+
ivec2 calcSplatUV(uint index, uint width) {
|
|
145286
|
+
return ivec2(int(index % width), int(index / width));
|
|
145287
|
+
}
|
|
145288
|
+
|
|
145289
|
+
void main(void) {
|
|
145290
|
+
ivec2 splatUV = calcSplatUV(vertex_id, texParams.x);
|
|
145291
|
+
uint splatState = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
|
|
145292
|
+
|
|
145293
|
+
if ((splatState & 6u) != 0u) {
|
|
145294
|
+
// deleted or locked (4 or 2)
|
|
145295
|
+
gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
|
|
145296
|
+
gl_PointSize = 0.0;
|
|
145297
|
+
} else {
|
|
145298
|
+
mat4 model = matrix_model;
|
|
145299
|
+
|
|
145300
|
+
// handle per-splat transform
|
|
145301
|
+
uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
|
|
145302
|
+
if (transformIndex > 0u) {
|
|
145303
|
+
// read transform matrix
|
|
145304
|
+
int u = int(transformIndex % 512u) * 3;
|
|
145305
|
+
int v = int(transformIndex / 512u);
|
|
145306
|
+
|
|
145307
|
+
mat4 t;
|
|
145308
|
+
t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
|
|
145309
|
+
t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
|
|
145310
|
+
t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
|
|
145311
|
+
t[3] = vec4(0.0, 0.0, 0.0, 1.0);
|
|
145312
|
+
|
|
145313
|
+
model = matrix_model * transpose(t);
|
|
145314
|
+
}
|
|
145315
|
+
|
|
145316
|
+
varying_color = (splatState == 1u) ? selectedClr : unselectedClr;
|
|
145317
|
+
|
|
145318
|
+
vec3 center = uintBitsToFloat(texelFetch(splatPosition, splatUV, 0).xyz);
|
|
145319
|
+
|
|
145320
|
+
gl_Position = matrix_viewProjection * model * vec4(center, 1.0);
|
|
145321
|
+
gl_PointSize = splatSize;
|
|
145322
|
+
}
|
|
145323
|
+
}
|
|
145324
|
+
`;
|
|
145325
|
+
const fragmentShader$1 = /* glsl */ `
|
|
145326
|
+
varying vec4 varying_color;
|
|
145327
|
+
|
|
145328
|
+
void main(void) {
|
|
145329
|
+
gl_FragColor = varying_color;
|
|
145330
|
+
}
|
|
144701
145331
|
`;
|
|
144702
145332
|
|
|
144703
145333
|
class SplatOverlay extends Element$1 {
|
|
@@ -144786,19 +145416,19 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
144786
145416
|
}
|
|
144787
145417
|
}
|
|
144788
145418
|
|
|
144789
|
-
const vertexShader = /* glsl*/ `
|
|
144790
|
-
attribute vec2 vertex_position;
|
|
144791
|
-
void main(void) {
|
|
144792
|
-
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
144793
|
-
}
|
|
145419
|
+
const vertexShader = /* glsl*/ `
|
|
145420
|
+
attribute vec2 vertex_position;
|
|
145421
|
+
void main(void) {
|
|
145422
|
+
gl_Position = vec4(vertex_position, 0.0, 1.0);
|
|
145423
|
+
}
|
|
144794
145424
|
`;
|
|
144795
|
-
const fragmentShader = /* glsl*/ `
|
|
144796
|
-
uniform sampler2D blitTexture;
|
|
144797
|
-
void main(void) {
|
|
144798
|
-
ivec2 texel = ivec2(gl_FragCoord.xy);
|
|
144799
|
-
|
|
144800
|
-
gl_FragColor = texelFetch(blitTexture, texel, 0);
|
|
144801
|
-
}
|
|
145425
|
+
const fragmentShader = /* glsl*/ `
|
|
145426
|
+
uniform sampler2D blitTexture;
|
|
145427
|
+
void main(void) {
|
|
145428
|
+
ivec2 texel = ivec2(gl_FragCoord.xy);
|
|
145429
|
+
|
|
145430
|
+
gl_FragColor = texelFetch(blitTexture, texel, 0);
|
|
145431
|
+
}
|
|
144802
145432
|
`;
|
|
144803
145433
|
|
|
144804
145434
|
class Underlay extends Element$1 {
|
|
@@ -146763,7 +147393,7 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
146763
147393
|
const { config } = this.scene;
|
|
146764
147394
|
const state = this.viewerEventState;
|
|
146765
147395
|
// Colors and view settings from scene config
|
|
146766
|
-
const selectedClr = config.selectedClr ?? { r: 1, g:
|
|
147396
|
+
const selectedClr = config.selectedClr ?? { r: 1, g: 0.5, b: 0, a: 1 };
|
|
146767
147397
|
const unselectedClr = config.unselectedClr ?? { r: 0, g: 0, b: 1, a: 0.5 };
|
|
146768
147398
|
const lockedClr = config.lockedClr ?? { r: 0, g: 0, b: 0, a: 0.05 };
|
|
146769
147399
|
const bgClr = config.bgClr ?? { r: 0, g: 0, b: 0, a: 1 };
|
|
@@ -147075,6 +147705,7 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
147075
147705
|
};
|
|
147076
147706
|
this.enableStats = false;
|
|
147077
147707
|
this.autoFocus = true;
|
|
147708
|
+
this.previewMode = false;
|
|
147078
147709
|
this.isLoading = false;
|
|
147079
147710
|
this.hasModel = false;
|
|
147080
147711
|
this.error = null;
|
|
@@ -147127,6 +147758,11 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
147127
147758
|
this.canvas = options.canvas || null;
|
|
147128
147759
|
this.enableStats = options.enableStats || false;
|
|
147129
147760
|
this.autoFocus = options.autoFocus !== false;
|
|
147761
|
+
this.previewMode = options.previewMode || false;
|
|
147762
|
+
// In preview mode, always enable auto-focus
|
|
147763
|
+
if (this.previewMode) {
|
|
147764
|
+
this.autoFocus = true;
|
|
147765
|
+
}
|
|
147130
147766
|
this._navigationCubeConfig = options.navigationCube || null;
|
|
147131
147767
|
if (options.onStatsUpdate !== undefined) {
|
|
147132
147768
|
this._onStatsUpdate = options.onStatsUpdate;
|
|
@@ -147157,11 +147793,20 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
147157
147793
|
const emitEvent = (type, detail) => {
|
|
147158
147794
|
this.emit({ type, detail });
|
|
147159
147795
|
};
|
|
147160
|
-
this._supersplat = new SupersplatAdapter(this.canvas,
|
|
147796
|
+
this._supersplat = new SupersplatAdapter(this.canvas,
|
|
147797
|
+
// Disable navigation cube in preview mode
|
|
147798
|
+
this.previewMode ? null : this._navigationCubeConfig, emitEvent);
|
|
147161
147799
|
this._supersplatReady = this._supersplat.init();
|
|
147162
147800
|
this._supersplatReady
|
|
147163
147801
|
?.then(() => {
|
|
147164
|
-
|
|
147802
|
+
// Disable camera controls in preview mode
|
|
147803
|
+
if (this.previewMode && this._supersplat) {
|
|
147804
|
+
this._supersplat.setCameraControlsEnabled?.(false);
|
|
147805
|
+
}
|
|
147806
|
+
// Only set up fly camera if not in preview mode
|
|
147807
|
+
if (!this.previewMode) {
|
|
147808
|
+
this._setupFlyCameraForSupersplat();
|
|
147809
|
+
}
|
|
147165
147810
|
})
|
|
147166
147811
|
.catch(error => {
|
|
147167
147812
|
console.error('SplatViewerCore.init: Failed to set up fly camera for supersplat path', error);
|
|
@@ -147197,6 +147842,9 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
147197
147842
|
this.app = ctx.app;
|
|
147198
147843
|
this.entities.camera = ctx.camera;
|
|
147199
147844
|
const cameraAny = this.entities.camera;
|
|
147845
|
+
// SuperSplat's PCApp omits ScriptComponentSystem, so `addComponent('script')`
|
|
147846
|
+
// will not produce `camera.script.create()`. We keep the attempt (in case
|
|
147847
|
+
// the underlying app changes), but also support a controller-based fallback.
|
|
147200
147848
|
if (cameraAny &&
|
|
147201
147849
|
!cameraAny.script &&
|
|
147202
147850
|
typeof cameraAny.addComponent === 'function') {
|
|
@@ -147222,16 +147870,25 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
147222
147870
|
minHeight: DEFAULT_FLY_CAMERA_CONFIG.minHeight,
|
|
147223
147871
|
maxHeight: DEFAULT_FLY_CAMERA_CONFIG.maxHeight,
|
|
147224
147872
|
};
|
|
147225
|
-
|
|
147226
|
-
|
|
147227
|
-
|
|
147228
|
-
|
|
147229
|
-
|
|
147230
|
-
|
|
147231
|
-
|
|
147232
|
-
|
|
147233
|
-
|
|
147234
|
-
|
|
147873
|
+
// Prefer script-based fly when available; fallback to controller otherwise.
|
|
147874
|
+
const canCreateScript = typeof cameraAny?.script?.create === 'function';
|
|
147875
|
+
if (canCreateScript) {
|
|
147876
|
+
const created = cameraAny.script.create('flyCamera', {
|
|
147877
|
+
attributes: flyAttributes,
|
|
147878
|
+
});
|
|
147879
|
+
this._fly = created;
|
|
147880
|
+
if (this._fly) {
|
|
147881
|
+
;
|
|
147882
|
+
this._fly.emitFlyEvent = (type, detail) => {
|
|
147883
|
+
this.emit({ type: type, detail });
|
|
147884
|
+
};
|
|
147885
|
+
this._fly.deactivate?.();
|
|
147886
|
+
}
|
|
147887
|
+
}
|
|
147888
|
+
else {
|
|
147889
|
+
const controller = new FlyCameraController(ctx.app, cameraAny, (type, detail) => this.emit({ type: type, detail }), flyAttributes);
|
|
147890
|
+
controller.deactivate();
|
|
147891
|
+
this._fly = controller;
|
|
147235
147892
|
}
|
|
147236
147893
|
this._cameraMode = 'orbit';
|
|
147237
147894
|
}
|
|
@@ -149082,6 +149739,11 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
149082
149739
|
// Camera Mode / Fly Camera API
|
|
149083
149740
|
// ==========================================
|
|
149084
149741
|
setCameraMode(mode) {
|
|
149742
|
+
// Prevent camera mode changes in preview mode
|
|
149743
|
+
if (this.previewMode) {
|
|
149744
|
+
console.warn('SplatViewerCore.setCameraMode: Camera controls are disabled in preview mode');
|
|
149745
|
+
return;
|
|
149746
|
+
}
|
|
149085
149747
|
// supersplat-core path: manage mode switching explicitly (camera entity is updated by supersplat-core each frame)
|
|
149086
149748
|
if (this._supersplat) {
|
|
149087
149749
|
const prev = this._cameraMode;
|
|
@@ -149100,17 +149762,33 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
149100
149762
|
// Stop supersplat orbit updates + input
|
|
149101
149763
|
this._supersplat.setCameraControlsEnabled(false);
|
|
149102
149764
|
this._supersplat.setCameraManualControl(true);
|
|
149103
|
-
//
|
|
149104
|
-
|
|
149105
|
-
|
|
149106
|
-
if (
|
|
149107
|
-
;
|
|
149108
|
-
|
|
149109
|
-
|
|
149765
|
+
// Preserve camera position and rotation when switching to fly mode
|
|
149766
|
+
if (this._fly) {
|
|
149767
|
+
// For FlyCameraController (fallback path)
|
|
149768
|
+
if (typeof this._fly.syncFromEntity === 'function') {
|
|
149769
|
+
this._fly.syncFromEntity();
|
|
149770
|
+
}
|
|
149771
|
+
else {
|
|
149772
|
+
// For FlyCameraScript (legacy path)
|
|
149773
|
+
try {
|
|
149774
|
+
const pos = this.entities.camera?.getPosition?.();
|
|
149775
|
+
if (pos) {
|
|
149776
|
+
const posVec = pos.clone
|
|
149777
|
+
? pos.clone()
|
|
149778
|
+
: new Vec3(pos.x || 0, pos.y || 0, pos.z || 0);
|
|
149779
|
+
this.entities.camera?.setPosition?.(posVec);
|
|
149780
|
+
}
|
|
149781
|
+
const euler = this.entities.camera?.getEulerAngles?.();
|
|
149782
|
+
if (euler) {
|
|
149783
|
+
;
|
|
149784
|
+
this._fly._pitch = euler.x || 0;
|
|
149785
|
+
this._fly._yaw = euler.y || 0;
|
|
149786
|
+
}
|
|
149787
|
+
}
|
|
149788
|
+
catch {
|
|
149789
|
+
// ignore
|
|
149790
|
+
}
|
|
149110
149791
|
}
|
|
149111
|
-
}
|
|
149112
|
-
catch {
|
|
149113
|
-
// ignore
|
|
149114
149792
|
}
|
|
149115
149793
|
this._fly?.activate?.();
|
|
149116
149794
|
this._cameraMode = 'fly';
|
|
@@ -149227,9 +149905,48 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
149227
149905
|
this._cameraModeManager?.setFlyConfig(config);
|
|
149228
149906
|
}
|
|
149229
149907
|
getFlyCameraConfig() {
|
|
149908
|
+
// SuperSplat path: fly is either a script instance or controller fallback
|
|
149909
|
+
if (this._supersplat && this._fly) {
|
|
149910
|
+
try {
|
|
149911
|
+
const flyAny = this._fly;
|
|
149912
|
+
const cfg = {
|
|
149913
|
+
moveSpeed: flyAny.moveSpeed,
|
|
149914
|
+
fastSpeedMultiplier: flyAny.fastSpeedMultiplier,
|
|
149915
|
+
slowSpeedMultiplier: flyAny.slowSpeedMultiplier,
|
|
149916
|
+
lookSensitivity: flyAny.lookSensitivity,
|
|
149917
|
+
invertY: !!flyAny.invertY,
|
|
149918
|
+
keyBindings: { ...(flyAny.keyBindings || {}) },
|
|
149919
|
+
smoothing: flyAny.smoothing,
|
|
149920
|
+
friction: flyAny.friction,
|
|
149921
|
+
enableCollision: !!flyAny.enableCollision,
|
|
149922
|
+
minHeight: flyAny.minHeight ?? null,
|
|
149923
|
+
maxHeight: flyAny.maxHeight ?? null,
|
|
149924
|
+
};
|
|
149925
|
+
return cfg;
|
|
149926
|
+
}
|
|
149927
|
+
catch {
|
|
149928
|
+
return null;
|
|
149929
|
+
}
|
|
149930
|
+
}
|
|
149230
149931
|
return this._cameraModeManager?.getFlyConfig() || null;
|
|
149231
149932
|
}
|
|
149232
149933
|
getFlyCameraState() {
|
|
149934
|
+
// SuperSplat path: fly is either a script instance or controller fallback
|
|
149935
|
+
if (this._supersplat && this._fly?.getState) {
|
|
149936
|
+
try {
|
|
149937
|
+
const state = this._fly.getState();
|
|
149938
|
+
return {
|
|
149939
|
+
mode: this._cameraMode,
|
|
149940
|
+
position: state.position,
|
|
149941
|
+
rotation: state.rotation,
|
|
149942
|
+
velocity: state.velocity,
|
|
149943
|
+
isMoving: state.isMoving,
|
|
149944
|
+
};
|
|
149945
|
+
}
|
|
149946
|
+
catch {
|
|
149947
|
+
return null;
|
|
149948
|
+
}
|
|
149949
|
+
}
|
|
149233
149950
|
return this._cameraModeManager?.getFlyState() || null;
|
|
149234
149951
|
}
|
|
149235
149952
|
_setupScene() {
|
|
@@ -149281,8 +149998,8 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
149281
149998
|
panSensitivity: 1.0,
|
|
149282
149999
|
zoomSensitivity: 0.1,
|
|
149283
150000
|
};
|
|
149284
|
-
// Add navigation cube configuration if available
|
|
149285
|
-
if (this._navigationCubeConfig) {
|
|
150001
|
+
// Add navigation cube configuration if available (but not in preview mode)
|
|
150002
|
+
if (this._navigationCubeConfig && !this.previewMode) {
|
|
149286
150003
|
orbitAttributes.enableNavigationCube =
|
|
149287
150004
|
this._navigationCubeConfig.enabled || false;
|
|
149288
150005
|
this.entities.camera._navigationCubeConfig =
|
|
@@ -149297,48 +150014,57 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
149297
150014
|
detail: { type: interactionType },
|
|
149298
150015
|
});
|
|
149299
150016
|
};
|
|
150017
|
+
// Disable camera controls in preview mode
|
|
150018
|
+
if (this.previewMode && this._orbit) {
|
|
150019
|
+
const orbitAny = this._orbit;
|
|
150020
|
+
if (typeof orbitAny.setEnabled === 'function') {
|
|
150021
|
+
orbitAny.setEnabled(false);
|
|
150022
|
+
}
|
|
150023
|
+
}
|
|
149300
150024
|
this.entities.camera.setPosition(0, 0, 10);
|
|
149301
150025
|
this.entities.camera.lookAt(Vec3.ZERO);
|
|
149302
150026
|
// ==============================
|
|
149303
|
-
// Setup fly camera (disabled by default)
|
|
150027
|
+
// Setup fly camera (disabled by default, skipped in preview mode)
|
|
149304
150028
|
// ==============================
|
|
149305
|
-
|
|
149306
|
-
|
|
149307
|
-
|
|
149308
|
-
|
|
149309
|
-
|
|
149310
|
-
|
|
149311
|
-
|
|
149312
|
-
|
|
149313
|
-
|
|
149314
|
-
|
|
149315
|
-
|
|
149316
|
-
|
|
149317
|
-
|
|
149318
|
-
|
|
149319
|
-
|
|
149320
|
-
|
|
149321
|
-
this._fly = this.entities.camera.script.create('flyCamera', {
|
|
149322
|
-
attributes: flyAttributes,
|
|
149323
|
-
});
|
|
149324
|
-
// Wire event emission to core
|
|
149325
|
-
if (this._fly) {
|
|
149326
|
-
;
|
|
149327
|
-
this._fly.emitFlyEvent = (type, detail) => {
|
|
149328
|
-
this.emit({ type: type, detail });
|
|
150029
|
+
if (!this.previewMode) {
|
|
150030
|
+
try {
|
|
150031
|
+
registerFlyCameraScript();
|
|
150032
|
+
// Ensure script component exists (created above)
|
|
150033
|
+
const flyAttributes = {
|
|
150034
|
+
moveSpeed: DEFAULT_FLY_CAMERA_CONFIG.moveSpeed,
|
|
150035
|
+
fastSpeedMultiplier: DEFAULT_FLY_CAMERA_CONFIG.fastSpeedMultiplier,
|
|
150036
|
+
slowSpeedMultiplier: DEFAULT_FLY_CAMERA_CONFIG.slowSpeedMultiplier,
|
|
150037
|
+
lookSensitivity: DEFAULT_FLY_CAMERA_CONFIG.lookSensitivity,
|
|
150038
|
+
invertY: DEFAULT_FLY_CAMERA_CONFIG.invertY,
|
|
150039
|
+
keyBindings: DEFAULT_FLY_CAMERA_CONFIG.keyBindings,
|
|
150040
|
+
smoothing: DEFAULT_FLY_CAMERA_CONFIG.smoothing,
|
|
150041
|
+
friction: DEFAULT_FLY_CAMERA_CONFIG.friction,
|
|
150042
|
+
enableCollision: DEFAULT_FLY_CAMERA_CONFIG.enableCollision,
|
|
150043
|
+
minHeight: DEFAULT_FLY_CAMERA_CONFIG.minHeight,
|
|
150044
|
+
maxHeight: DEFAULT_FLY_CAMERA_CONFIG.maxHeight,
|
|
149329
150045
|
};
|
|
150046
|
+
this._fly = this.entities.camera.script.create('flyCamera', {
|
|
150047
|
+
attributes: flyAttributes,
|
|
150048
|
+
});
|
|
150049
|
+
// Wire event emission to core
|
|
150050
|
+
if (this._fly) {
|
|
150051
|
+
;
|
|
150052
|
+
this._fly.emitFlyEvent = (type, detail) => {
|
|
150053
|
+
this.emit({ type: type, detail });
|
|
150054
|
+
};
|
|
150055
|
+
}
|
|
150056
|
+
// Deactivate fly by default; orbit is the initial mode
|
|
150057
|
+
if (this._fly?.deactivate) {
|
|
150058
|
+
this._fly.deactivate();
|
|
150059
|
+
}
|
|
150060
|
+
// Initialize camera mode manager
|
|
150061
|
+
this._cameraModeManager = new CameraModeManager(this.app, this.entities.camera, this._orbit, this._fly, (eventType, detail) => {
|
|
150062
|
+
this.emit({ type: eventType, detail });
|
|
150063
|
+
}, 'orbit');
|
|
149330
150064
|
}
|
|
149331
|
-
|
|
149332
|
-
|
|
149333
|
-
this._fly.deactivate();
|
|
150065
|
+
catch (e) {
|
|
150066
|
+
console.warn('Failed to set up fly camera', e);
|
|
149334
150067
|
}
|
|
149335
|
-
// Initialize camera mode manager
|
|
149336
|
-
this._cameraModeManager = new CameraModeManager(this.app, this.entities.camera, this._orbit, this._fly, (eventType, detail) => {
|
|
149337
|
-
this.emit({ type: eventType, detail });
|
|
149338
|
-
}, 'orbit');
|
|
149339
|
-
}
|
|
149340
|
-
catch (e) {
|
|
149341
|
-
console.warn('Failed to set up fly camera', e);
|
|
149342
150068
|
}
|
|
149343
150069
|
}
|
|
149344
150070
|
_setupStats() {
|
|
@@ -149583,6 +150309,7 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
149583
150309
|
'enable-stats',
|
|
149584
150310
|
'auto-focus',
|
|
149585
150311
|
'max-splats',
|
|
150312
|
+
'preview-mode',
|
|
149586
150313
|
'camera-position',
|
|
149587
150314
|
'camera-target',
|
|
149588
150315
|
'orbit-sensitivity',
|
|
@@ -149613,6 +150340,9 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
149613
150340
|
get autoFocus() {
|
|
149614
150341
|
return this.hasAttribute('auto-focus');
|
|
149615
150342
|
}
|
|
150343
|
+
get previewMode() {
|
|
150344
|
+
return this.hasAttribute('preview-mode');
|
|
150345
|
+
}
|
|
149616
150346
|
get maxSplats() {
|
|
149617
150347
|
return this.getAttribute('max-splats');
|
|
149618
150348
|
}
|
|
@@ -149690,6 +150420,14 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
149690
150420
|
this.removeAttribute('auto-focus');
|
|
149691
150421
|
}
|
|
149692
150422
|
}
|
|
150423
|
+
set previewMode(value) {
|
|
150424
|
+
if (value) {
|
|
150425
|
+
this.setAttribute('preview-mode', '');
|
|
150426
|
+
}
|
|
150427
|
+
else {
|
|
150428
|
+
this.removeAttribute('preview-mode');
|
|
150429
|
+
}
|
|
150430
|
+
}
|
|
149693
150431
|
set maxSplats(value) {
|
|
149694
150432
|
if (value === null) {
|
|
149695
150433
|
this.removeAttribute('max-splats');
|
|
@@ -149871,6 +150609,7 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
149871
150609
|
return value.length > 0;
|
|
149872
150610
|
case 'enable-stats':
|
|
149873
150611
|
case 'auto-focus':
|
|
150612
|
+
case 'preview-mode':
|
|
149874
150613
|
case 'enable-navigation-cube':
|
|
149875
150614
|
// Boolean attributes - any value is valid
|
|
149876
150615
|
return true;
|
|
@@ -150817,7 +151556,7 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
|
|
|
150817
151556
|
if (!this._core) {
|
|
150818
151557
|
throw new Error('SplatViewerElement: Core not initialized. Call connectedCallback first.');
|
|
150819
151558
|
}
|
|
150820
|
-
return this._core.selectSplatsInSphere(center, radius, modelId, addToSelection);
|
|
151559
|
+
return this._core.selectSplatsInSphere(new Vec3(center.x, center.y, center.z), radius, modelId, addToSelection);
|
|
150821
151560
|
}
|
|
150822
151561
|
clearSplatSelection() {
|
|
150823
151562
|
if (!this._core) {
|