From 24ce2b30865a4809bc1a8a4833a1b909d65c7a82 Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Mon, 25 Mar 2024 12:50:02 -0700 Subject: [PATCH 01/14] Enable orthographic projection when using built-in camera --- src/SplatMesh.js | 38 +++++++++++++++----- src/Viewer.js | 90 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 107 insertions(+), 21 deletions(-) diff --git a/src/SplatMesh.js b/src/SplatMesh.js index 0b863659..0b7e4b5e 100644 --- a/src/SplatMesh.js +++ b/src/SplatMesh.js @@ -125,6 +125,8 @@ export class SplatMesh extends THREE.Mesh { vertexShaderSource += ` uniform vec2 focal; + uniform float orthoZoom; + uniform int orthographicMode; uniform float inverseFocalAdjustment; uniform vec2 viewport; uniform vec2 basisViewport; @@ -211,12 +213,19 @@ export class SplatMesh extends THREE.Mesh { // require a non-linear component (perspective division) which would yield a non-gaussian result. (This assumes // the current projection is a perspective projection). - float s = 1.0 / (viewCenter.z * viewCenter.z); - mat3 J = mat3( - focal.x / viewCenter.z, 0., -(focal.x * viewCenter.x) * s, - 0., focal.y / viewCenter.z, -(focal.y * viewCenter.y) * s, - 0., 0., 0. - ); + mat3 J; + if (orthographicMode == 1) { + J = transpose(mat3(orthoZoom, 0.0, 0.0, + 0.0, orthoZoom, 0.0, + 0.0, 0.0, 0.0)); + } else { + float s = 1.0 / (viewCenter.z * viewCenter.z); + J = mat3( + focal.x / viewCenter.z, 0., -(focal.x * viewCenter.x) * s, + 0., focal.y / viewCenter.z, -(focal.y * viewCenter.y) * s, + 0., 0., 0. + ); + } // Concatenate the projection approximation with the model-view transformation mat3 W = transpose(mat3(transformModelViewMatrix)); @@ -305,7 +314,9 @@ export class SplatMesh extends THREE.Mesh { vec2 ndcOffset = vec2(vPosition.x * basisVector1 + vPosition.y * basisVector2) * basisViewport * 2.0 * inverseFocalAdjustment; - gl_Position = vec4(ndcCenter.xy + ndcOffset, ndcCenter.z, 1.0); + + vec4 quadPos = vec4(ndcCenter.xy + ndcOffset, ndcCenter.z, 1.0); + gl_Position = quadPos; // Scale the position data we send to the fragment shader vPosition *= sqrt8; @@ -349,6 +360,10 @@ export class SplatMesh extends THREE.Mesh { 'type': 'i', 'value': 0 }, + 'orthographicMode': { + 'type': 'i', + 'value': 0 + }, 'visibleRegionFadeStartRadius': { 'type': 'f', 'value': 0.0 @@ -377,6 +392,10 @@ export class SplatMesh extends THREE.Mesh { 'type': 'v2', 'value': new THREE.Vector2() }, + 'orthoZoom': { + 'type': 'f', + 'value': 1.0 + }, 'inverseFocalAdjustment': { 'type': 'f', 'value': 1.0 @@ -1006,7 +1025,8 @@ export class SplatMesh extends THREE.Mesh { const viewport = new THREE.Vector2(); - return function(renderDimensions, cameraFocalLengthX, cameraFocalLengthY, inverseFocalAdjustment) { + return function(renderDimensions, cameraFocalLengthX, cameraFocalLengthY, + orthographicMode, orthographicZoom, inverseFocalAdjustment) { const splatCount = this.getSplatCount(); if (splatCount > 0) { viewport.set(renderDimensions.x * this.devicePixelRatio, @@ -1014,6 +1034,8 @@ export class SplatMesh extends THREE.Mesh { this.material.uniforms.viewport.value.copy(viewport); this.material.uniforms.basisViewport.value.set(1.0 / viewport.x, 1.0 / viewport.y); this.material.uniforms.focal.value.set(cameraFocalLengthX, cameraFocalLengthY); + this.material.uniforms.orthographicMode.value = orthographicMode ? 1 : 0; + this.material.uniforms.orthoZoom.value = orthographicZoom; this.material.uniforms.inverseFocalAdjustment.value = inverseFocalAdjustment; if (this.dynamicMode) { for (let i = 0; i < this.scenes.length; i++) { diff --git a/src/Viewer.js b/src/Viewer.js index c6e252be..4b6e4f49 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -168,6 +168,8 @@ export class Viewer { this.infoPanel = null; + this.orthographicMode = false; + this.currentFPS = 0; this.lastSortTime = 0; this.consecutiveRenderFrames = 0; @@ -226,7 +228,10 @@ export class Viewer { this.getRenderDimensions(renderDimensions); if (!this.usingExternalCamera) { - this.camera = new THREE.PerspectiveCamera(THREE_CAMERA_FOV, renderDimensions.x / renderDimensions.y, 0.1, 500); + this.perspectiveCamera = new THREE.PerspectiveCamera(THREE_CAMERA_FOV, renderDimensions.x / renderDimensions.y, 0.1, 1000); + this.orthographicCamera = new THREE.OrthographicCamera(renderDimensions.x / -2, renderDimensions.x / 2, + renderDimensions.y / 2, renderDimensions.y / -2, 0.1, 1000 ); + this.camera = this.orthographicMode ? this.orthographicCamera : this.perspectiveCamera; this.camera.position.copy(this.initialCameraPosition); this.camera.up.copy(this.cameraUp).normalize(); this.camera.lookAt(this.initialCameraLookAt); @@ -270,14 +275,18 @@ export class Viewer { this.sceneHelper.setupControlPlane(); if (this.useBuiltInControls && this.webXRMode === WebXRMode.None) { - this.controls = new OrbitControls(this.camera, this.renderer.domElement); - this.controls.listenToKeyEvents(window); - this.controls.rotateSpeed = 0.5; - this.controls.maxPolarAngle = Math.PI * .75; - this.controls.minPolarAngle = 0.1; - this.controls.enableDamping = true; - this.controls.dampingFactor = 0.05; - this.controls.target.copy(this.initialCameraLookAt); + this.perspectiveControls = new OrbitControls(this.perspectiveCamera, this.renderer.domElement); + this.orthographicControls = new OrbitControls(this.orthographicCamera, this.renderer.domElement); + for (let controls of [this.perspectiveControls, this.orthographicControls]) { + controls.listenToKeyEvents(window); + controls.rotateSpeed = 0.5; + controls.maxPolarAngle = Math.PI * .75; + controls.minPolarAngle = 0.1; + controls.enableDamping = true; + controls.dampingFactor = 0.05; + controls.target.copy(this.initialCameraLookAt); + } + this.controls = this.orthographicMode ? this.orthographicControls : this.perspectiveControls; this.mouseMoveListener = this.onMouseMove.bind(this); this.renderer.domElement.addEventListener('pointermove', this.mouseMoveListener, false); this.mouseDownListener = this.onMouseDown.bind(this); @@ -352,6 +361,9 @@ export class Viewer { this.infoPanel.hide(); } break; + case 'KeyO': + this.setOrthographicMode(!this.orthographicMode); + break; } }; @@ -423,6 +435,46 @@ export class Viewer { } } + setOrthographicMode(orthographicMode) { + if (orthographicMode === this.orthographicMode) return; + this.orthographicMode = orthographicMode; + const fromCamera = this.camera; + const toCamera = this.orthographicMode ? this.orthographicCamera : this.perspectiveCamera; + toCamera.position.copy(fromCamera.position); + toCamera.up.copy(fromCamera.up); + toCamera.rotation.copy(fromCamera.rotation); + toCamera.quaternion.copy(fromCamera.quaternion); + toCamera.matrix.copy(fromCamera.matrix); + this.camera = toCamera; + + if (this.controls) { + const fromControls = this.controls; + const toControls = this.orthographicMode ? this.orthographicControls : this.perspectiveControls; + toControls.target.copy(fromControls.target); + const tempVector = new THREE.Vector3(); + if (this.orthographicMode) { + const toLookAtDistance = tempVector.copy(fromControls.target).sub(fromCamera.position).length(); + toCamera.zoom = 1 / (toLookAtDistance * .001); + } else { + Viewer.setCameraPositionFromZoom(toCamera, fromCamera, toControls); + } + this.controls = toControls; + this.camera.lookAt(this.controls.target); + } + } + + static setCameraPositionFromZoom = function() { + + const tempVector = new THREE.Vector3(); + + return function(targetCamera, zoomedCamera, controls) { + const toLookAtDistance = 1 / (zoomedCamera.zoom * 0.001); + tempVector.copy(controls.target).sub(targetCamera.position).normalize().multiplyScalar(toLookAtDistance).negate(); + targetCamera.position.copy(controls.target).add(tempVector); + }; + + }(); + updateSplatMesh = function() { const renderDimensions = new THREE.Vector2(); @@ -441,8 +493,8 @@ export class Viewer { const focalAdjustment = this.focalAdjustment; const inverseFocalAdjustment = 1.0 / focalAdjustment; - this.splatMesh.updateUniforms(renderDimensions, focalLengthX * focalAdjustment, - focalLengthY * focalAdjustment, inverseFocalAdjustment); + this.splatMesh.updateUniforms(renderDimensions, focalLengthX * focalAdjustment, focalLengthY * focalAdjustment, + this.orthographicMode, this.camera.zoom, inverseFocalAdjustment); } }; @@ -1203,7 +1255,12 @@ export class Viewer { update(renderer, camera) { if (this.dropInMode) this.updateForDropInMode(renderer, camera); if (!this.initialized || !this.splatRenderingInitialized) return; - if (this.controls) this.controls.update(); + if (this.controls) { + this.controls.update(); + if (this.orthographicMode && !this.usingExternalCamera) { + Viewer.setCameraPositionFromZoom(this.camera, this.camera, this.controls); + } + } this.splatMesh.updateVisibleRegionFadeDistance(this.sceneRevealMode); this.updateSplatSort(); this.updateForRendererSizeChanges(); @@ -1255,7 +1312,14 @@ export class Viewer { this.renderer.getSize(currentRendererSize); if (currentRendererSize.x !== lastRendererSize.x || currentRendererSize.y !== lastRendererSize.y) { if (!this.usingExternalCamera) { - this.camera.aspect = currentRendererSize.x / currentRendererSize.y; + if (this.orthographicMode) { + this.camera.left = -currentRendererSize.x / 2.0; + this.camera.right = currentRendererSize.x / 2.0; + this.camera.top = currentRendererSize.y / 2.0; + this.camera.bottom = -currentRendererSize.y / 2.0; + } else { + this.camera.aspect = currentRendererSize.x / currentRendererSize.y; + } this.camera.updateProjectionMatrix(); } lastRendererSize.copy(currentRendererSize); From 04e68745dccf037fb845ff1304b68e0a7edad21d Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Thu, 28 Mar 2024 11:17:52 -0700 Subject: [PATCH 02/14] Update focus marker to work with orthographic camera --- src/SceneHelper.js | 6 +++++- src/Viewer.js | 25 +++++++++++++------------ src/raycaster/Raycaster.js | 2 +- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/SceneHelper.js b/src/SceneHelper.js index c9a4f0cf..c2c483f9 100644 --- a/src/SceneHelper.js +++ b/src/SceneHelper.js @@ -165,13 +165,17 @@ export class SceneHelper { const tempPosition = new THREE.Vector3(); const tempMatrix = new THREE.Matrix4(); + const toCamera = new THREE.Vector3(); return function(position, camera, viewport) { tempMatrix.copy(camera.matrixWorld).invert(); tempPosition.copy(position).applyMatrix4(tempMatrix); tempPosition.normalize().multiplyScalar(10); tempPosition.applyMatrix4(camera.matrixWorld); - this.focusMarker.position.copy(tempPosition); + toCamera.copy(camera.position).sub(position); + const toCameraDistance = toCamera.length(); + this.focusMarker.position.copy(position); + this.focusMarker.scale.set(toCameraDistance, toCameraDistance, toCameraDistance); this.focusMarker.material.uniforms.realFocusPosition.value.copy(position); this.focusMarker.material.uniforms.viewport.value.copy(viewport); this.focusMarker.material.uniformsNeedUpdate = true; diff --git a/src/Viewer.js b/src/Viewer.js index 4b6e4f49..48e47315 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -168,7 +168,7 @@ export class Viewer { this.infoPanel = null; - this.orthographicMode = false; + this.startInOrthographicMode = false; this.currentFPS = 0; this.lastSortTime = 0; @@ -231,7 +231,7 @@ export class Viewer { this.perspectiveCamera = new THREE.PerspectiveCamera(THREE_CAMERA_FOV, renderDimensions.x / renderDimensions.y, 0.1, 1000); this.orthographicCamera = new THREE.OrthographicCamera(renderDimensions.x / -2, renderDimensions.x / 2, renderDimensions.y / 2, renderDimensions.y / -2, 0.1, 1000 ); - this.camera = this.orthographicMode ? this.orthographicCamera : this.perspectiveCamera; + this.camera = this.startInOrthographicMode ? this.orthographicCamera : this.perspectiveCamera; this.camera.position.copy(this.initialCameraPosition); this.camera.up.copy(this.cameraUp).normalize(); this.camera.lookAt(this.initialCameraLookAt); @@ -286,7 +286,7 @@ export class Viewer { controls.dampingFactor = 0.05; controls.target.copy(this.initialCameraLookAt); } - this.controls = this.orthographicMode ? this.orthographicControls : this.perspectiveControls; + this.controls = this.camera.isOrthographicCamera ? this.orthographicControls : this.perspectiveControls; this.mouseMoveListener = this.onMouseMove.bind(this); this.renderer.domElement.addEventListener('pointermove', this.mouseMoveListener, false); this.mouseDownListener = this.onMouseDown.bind(this); @@ -362,7 +362,9 @@ export class Viewer { } break; case 'KeyO': - this.setOrthographicMode(!this.orthographicMode); + if (!this.usingExternalCamera) { + this.setOrthographicMode(!this.camera.isOrthographicCamera); + } break; } }; @@ -436,10 +438,9 @@ export class Viewer { } setOrthographicMode(orthographicMode) { - if (orthographicMode === this.orthographicMode) return; - this.orthographicMode = orthographicMode; + if (orthographicMode === this.camera.isOrthographicCamera) return; const fromCamera = this.camera; - const toCamera = this.orthographicMode ? this.orthographicCamera : this.perspectiveCamera; + const toCamera = orthographicMode ? this.orthographicCamera : this.perspectiveCamera; toCamera.position.copy(fromCamera.position); toCamera.up.copy(fromCamera.up); toCamera.rotation.copy(fromCamera.rotation); @@ -449,10 +450,10 @@ export class Viewer { if (this.controls) { const fromControls = this.controls; - const toControls = this.orthographicMode ? this.orthographicControls : this.perspectiveControls; + const toControls = orthographicMode ? this.orthographicControls : this.perspectiveControls; toControls.target.copy(fromControls.target); const tempVector = new THREE.Vector3(); - if (this.orthographicMode) { + if (orthographicMode) { const toLookAtDistance = tempVector.copy(fromControls.target).sub(fromCamera.position).length(); toCamera.zoom = 1 / (toLookAtDistance * .001); } else { @@ -494,7 +495,7 @@ export class Viewer { const inverseFocalAdjustment = 1.0 / focalAdjustment; this.splatMesh.updateUniforms(renderDimensions, focalLengthX * focalAdjustment, focalLengthY * focalAdjustment, - this.orthographicMode, this.camera.zoom, inverseFocalAdjustment); + this.camera.isOrthographicCamera, this.camera.zoom || 1.0, inverseFocalAdjustment); } }; @@ -1257,7 +1258,7 @@ export class Viewer { if (!this.initialized || !this.splatRenderingInitialized) return; if (this.controls) { this.controls.update(); - if (this.orthographicMode && !this.usingExternalCamera) { + if (this.camera.isOrthographicCamera && !this.usingExternalCamera) { Viewer.setCameraPositionFromZoom(this.camera, this.camera, this.controls); } } @@ -1312,7 +1313,7 @@ export class Viewer { this.renderer.getSize(currentRendererSize); if (currentRendererSize.x !== lastRendererSize.x || currentRendererSize.y !== lastRendererSize.y) { if (!this.usingExternalCamera) { - if (this.orthographicMode) { + if (this.camera.isOrthographicCamera) { this.camera.left = -currentRendererSize.x / 2.0; this.camera.right = currentRendererSize.x / 2.0; this.camera.top = currentRendererSize.y / 2.0; diff --git a/src/raycaster/Raycaster.js b/src/raycaster/Raycaster.js index 08120888..c7d22273 100644 --- a/src/raycaster/Raycaster.js +++ b/src/raycaster/Raycaster.js @@ -21,7 +21,7 @@ export class Raycaster { this.ray.direction.set(ndcCoords.x, ndcCoords.y, 0.5 ).unproject(camera).sub(this.ray.origin).normalize(); this.camera = camera; } else if (camera.isOrthographicCamera) { - this.ray.origin.set(screenPosition.x, screenPosition.y, + this.ray.origin.set(ndcCoords.x, ndcCoords.y, (camera.near + camera.far) / (camera.near - camera.far)).unproject(camera); this.ray.direction.set(0, 0, -1).transformDirection(camera.matrixWorld); this.camera = camera; From 1dfafc43d07de372af97ff3dee8ecaf0739214b2 Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Thu, 28 Mar 2024 14:43:58 -0700 Subject: [PATCH 03/14] Display camera mode in the info panel UI --- src/Viewer.js | 5 +++-- src/ui/InfoPanel.js | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Viewer.js b/src/Viewer.js index 48e47315..822ef95d 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -1440,8 +1440,9 @@ export class Viewer { const meshCursorPosition = this.showMeshCursor ? this.sceneHelper.meshCursor.position : null; const splatRenderCountPct = this.splatRenderCount / splatCount * 100; this.infoPanel.update(renderDimensions, this.camera.position, cameraLookAtPosition, - this.camera.up, meshCursorPosition, this.currentFPS || 'N/A', splatCount, - this.splatRenderCount, splatRenderCountPct, this.lastSortTime, this.focalAdjustment); + this.camera.up, this.camera.isOrthographicCamera, meshCursorPosition, + this.currentFPS || 'N/A', splatCount, this.splatRenderCount, splatRenderCountPct, + this.lastSortTime, this.focalAdjustment); }; }(); diff --git a/src/ui/InfoPanel.js b/src/ui/InfoPanel.js index d995389d..62ff98aa 100644 --- a/src/ui/InfoPanel.js +++ b/src/ui/InfoPanel.js @@ -10,6 +10,7 @@ export class InfoPanel { ['Camera position', 'cameraPosition'], ['Camera look-at', 'cameraLookAt'], ['Camera up', 'cameraUp'], + ['Camera mode', 'orthographicCamera'], ['Cursor position', 'cursorPosition'], ['FPS', 'fps'], ['Rendering:', 'renderSplatCount'], @@ -98,8 +99,9 @@ export class InfoPanel { this.visible = false; } - update = function(renderDimensions, cameraPosition, cameraLookAtPosition, cameraUp, - meshCursorPosition, currentFPS, splatCount, splatRenderCount, splatRenderCountPct, lastSortTime, focalAdjustment) { + update = function(renderDimensions, cameraPosition, cameraLookAtPosition, cameraUp, orthographicCamera, + meshCursorPosition, currentFPS, splatCount, splatRenderCount, + splatRenderCountPct, lastSortTime, focalAdjustment) { const cameraPosString = `${cameraPosition.x.toFixed(5)}, ${cameraPosition.y.toFixed(5)}, ${cameraPosition.z.toFixed(5)}`; if (this.infoCells.cameraPosition.innerHTML !== cameraPosString) { @@ -119,6 +121,8 @@ export class InfoPanel { this.infoCells.cameraUp.innerHTML = cameraUpString; } + this.infoCells.orthographicCamera.innerHTML = orthographicCamera ? 'Orthographic' : 'Perspective'; + if (meshCursorPosition) { const cursPos = meshCursorPosition; const cursorPosString = `${cursPos.x.toFixed(5)}, ${cursPos.y.toFixed(5)}, ${cursPos.z.toFixed(5)}`; From 8989c8a6345c5f452688f53d9ee0727d6c762112 Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Thu, 28 Mar 2024 17:52:44 -0700 Subject: [PATCH 04/14] Fix bug with external camera --- README.md | 4 ++-- src/Viewer.js | 33 ++++++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d9ec2980..ae4b7a92 100644 --- a/README.md +++ b/README.md @@ -262,8 +262,8 @@ const viewer = new GaussianSplats3D.Viewer({ 'webXRMode': GaussianSplats3D.WebXRMode.None, 'renderMode': GaussianSplats3D.RenderMode.OnChange, 'sceneRevealMode': GaussianSplats3D.SceneRevealMode.Instant, - `antialiased`: false, - `focalAdjustment`: 1.0 + 'antialiased': false, + 'focalAdjustment': 1.0 }); viewer.addSplatScene('') .then(() => { diff --git a/src/Viewer.js b/src/Viewer.js index 822ef95d..1c0196df 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -145,6 +145,11 @@ export class Viewer { this.gpuAcceleratedSort, this.integerBasedSort, antialiased, this.maxScreenSpaceSplatSize); this.controls = null; + this.perspectiveControls = null; + this.orthographicControls = null; + + this.orthographicCamera = null; + this.perspectiveCamera = null; this.showMeshCursor = false; this.showControlPlane = false; @@ -275,16 +280,26 @@ export class Viewer { this.sceneHelper.setupControlPlane(); if (this.useBuiltInControls && this.webXRMode === WebXRMode.None) { - this.perspectiveControls = new OrbitControls(this.perspectiveCamera, this.renderer.domElement); - this.orthographicControls = new OrbitControls(this.orthographicCamera, this.renderer.domElement); + if (!this.usingExternalCamera) { + this.perspectiveControls = new OrbitControls(this.perspectiveCamera, this.renderer.domElement); + this.orthographicControls = new OrbitControls(this.orthographicCamera, this.renderer.domElement); + } else { + if (this.camera.isOrthographicCamera) { + this.orthographicControls = new OrbitControls(this.camera, this.renderer.domElement); + } else { + this.perspectiveControls = new OrbitControls(this.camera, this.renderer.domElement); + } + } for (let controls of [this.perspectiveControls, this.orthographicControls]) { - controls.listenToKeyEvents(window); - controls.rotateSpeed = 0.5; - controls.maxPolarAngle = Math.PI * .75; - controls.minPolarAngle = 0.1; - controls.enableDamping = true; - controls.dampingFactor = 0.05; - controls.target.copy(this.initialCameraLookAt); + if (controls) { + controls.listenToKeyEvents(window); + controls.rotateSpeed = 0.5; + controls.maxPolarAngle = Math.PI * .75; + controls.minPolarAngle = 0.1; + controls.enableDamping = true; + controls.dampingFactor = 0.05; + controls.target.copy(this.initialCameraLookAt); + } } this.controls = this.camera.isOrthographicCamera ? this.orthographicControls : this.perspectiveControls; this.mouseMoveListener = this.onMouseMove.bind(this); From 827f2478b159560290c7a76d8ebc718e972ceace Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Sun, 31 Mar 2024 11:11:42 -0700 Subject: [PATCH 05/14] Ensure proper aspect ratio when switching camera types --- src/OrbitControls.js | 5 +++++ src/Viewer.js | 35 +++++++++++++++++++++++++---------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/OrbitControls.js b/src/OrbitControls.js index de214ce3..31ed235f 100644 --- a/src/OrbitControls.js +++ b/src/OrbitControls.js @@ -437,6 +437,11 @@ class OrbitControls extends EventDispatcher { }; + this.clearDampedRotation = function() { + sphericalDelta.theta = 0.0; + sphericalDelta.phi = 0.0; + }; + // // internals // diff --git a/src/Viewer.js b/src/Viewer.js index 1c0196df..253b07d8 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -467,10 +467,10 @@ export class Viewer { const fromControls = this.controls; const toControls = orthographicMode ? this.orthographicControls : this.perspectiveControls; toControls.target.copy(fromControls.target); - const tempVector = new THREE.Vector3(); + toControls.clearDampedRotation(); + fromControls.clearDampedRotation(); if (orthographicMode) { - const toLookAtDistance = tempVector.copy(fromControls.target).sub(fromCamera.position).length(); - toCamera.zoom = 1 / (toLookAtDistance * .001); + Viewer.setCameraZoomFromPosition(toCamera, fromCamera, fromControls); } else { Viewer.setCameraPositionFromZoom(toCamera, fromCamera, toControls); } @@ -483,10 +483,22 @@ export class Viewer { const tempVector = new THREE.Vector3(); - return function(targetCamera, zoomedCamera, controls) { + return function(positionCamera, zoomedCamera, controls) { const toLookAtDistance = 1 / (zoomedCamera.zoom * 0.001); - tempVector.copy(controls.target).sub(targetCamera.position).normalize().multiplyScalar(toLookAtDistance).negate(); - targetCamera.position.copy(controls.target).add(tempVector); + tempVector.copy(controls.target).sub(positionCamera.position).normalize().multiplyScalar(toLookAtDistance).negate(); + positionCamera.position.copy(controls.target).add(tempVector); + }; + + }(); + + + static setCameraZoomFromPosition = function() { + + const tempVector = new THREE.Vector3(); + + return function(zoomCamera, positionZamera, controls) { + const toLookAtDistance = tempVector.copy(controls.target).sub(positionZamera.position).length(); + zoomCamera.zoom = 1 / (toLookAtDistance * .001); }; }(); @@ -1323,11 +1335,13 @@ export class Viewer { const lastRendererSize = new THREE.Vector2(); const currentRendererSize = new THREE.Vector2(); + let lastCameraOrthographic; return function() { - this.renderer.getSize(currentRendererSize); - if (currentRendererSize.x !== lastRendererSize.x || currentRendererSize.y !== lastRendererSize.y) { - if (!this.usingExternalCamera) { + if (!this.usingExternalCamera) { + this.renderer.getSize(currentRendererSize); + if (lastCameraOrthographic === undefined || lastCameraOrthographic !== this.camera.isOrthographicCamera || + currentRendererSize.x !== lastRendererSize.x || currentRendererSize.y !== lastRendererSize.y) { if (this.camera.isOrthographicCamera) { this.camera.left = -currentRendererSize.x / 2.0; this.camera.right = currentRendererSize.x / 2.0; @@ -1337,8 +1351,9 @@ export class Viewer { this.camera.aspect = currentRendererSize.x / currentRendererSize.y; } this.camera.updateProjectionMatrix(); + lastRendererSize.copy(currentRendererSize); + lastCameraOrthographic = this.camera.isOrthographicCamera; } - lastRendererSize.copy(currentRendererSize); } }; From fe8de859836bcc26837912bbf6bef89e868fa69e Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Sun, 31 Mar 2024 19:39:45 -0700 Subject: [PATCH 06/14] Add method to set splat scale --- src/SplatMesh.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/SplatMesh.js b/src/SplatMesh.js index 0b7e4b5e..dc5585cc 100644 --- a/src/SplatMesh.js +++ b/src/SplatMesh.js @@ -138,6 +138,7 @@ export class SplatMesh extends THREE.Mesh { uniform float currentTime; uniform int fadeInComplete; uniform vec3 sceneCenter; + uniform float splatScale; varying vec4 vColor; varying vec2 vUv; @@ -295,8 +296,8 @@ export class SplatMesh extends THREE.Mesh { vec2 eigenVector2 = vec2(eigenVector1.y, -eigenVector1.x); // We use sqrt(8) standard deviations instead of 3 to eliminate more of the splat with a very low opacity. - vec2 basisVector1 = eigenVector1 * min(sqrt8 * sqrt(eigenValue1), ${parseInt(maxScreenSpaceSplatSize)}.0); - vec2 basisVector2 = eigenVector2 * min(sqrt8 * sqrt(eigenValue2), ${parseInt(maxScreenSpaceSplatSize)}.0); + vec2 basisVector1 = eigenVector1 * splatScale * min(sqrt8 * sqrt(eigenValue1), ${parseInt(maxScreenSpaceSplatSize)}.0); + vec2 basisVector2 = eigenVector2 * splatScale * min(sqrt8 * sqrt(eigenValue2), ${parseInt(maxScreenSpaceSplatSize)}.0); if (fadeInComplete == 0) { float opacityAdjust = 1.0; @@ -419,6 +420,10 @@ export class SplatMesh extends THREE.Mesh { 'centersColorsTextureSize': { 'type': 'v2', 'value': new THREE.Vector2(1024, 1024) + }, + 'splatScale': { + 'type': 'f', + 'value': 1.0 } }; @@ -1048,6 +1053,11 @@ export class SplatMesh extends THREE.Mesh { }(); + setSplatScale(scale = 1) { + this.material.uniforms.splatScale.value = scale; + this.material.uniformsNeedUpdate = true; + } + getSplatDataTextures() { return this.splatDataTextures; } From 192307e26a223d6de1656cd8181a3b74d7088351 Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Tue, 2 Apr 2024 13:06:57 -0700 Subject: [PATCH 07/14] Adhere to Viewer.renderer.autoClear settings --- src/Viewer.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Viewer.js b/src/Viewer.js index 253b07d8..f4ed6bb7 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -1269,10 +1269,14 @@ export class Viewer { } return false; }; + const savedAuoClear = this.renderer.autoClear; - this.renderer.autoClear = false; - if (hasRenderables(this.threeScene)) this.renderer.render(this.threeScene, this.camera); + if (hasRenderables(this.threeScene)) { + this.renderer.render(this.threeScene, this.camera); + this.renderer.autoClear = false; + } this.renderer.render(this.splatMesh, this.camera); + this.renderer.autoClear = false; if (this.sceneHelper.getFocusMarkerOpacity() > 0.0) this.renderer.render(this.sceneHelper.focusMarker, this.camera); if (this.showControlPlane) this.renderer.render(this.sceneHelper.controlPlane, this.camera); this.renderer.autoClear = savedAuoClear; From 5f572d32e01801967aa95316670fe3897a2f0393 Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Tue, 2 Apr 2024 18:36:59 -0700 Subject: [PATCH 08/14] Properly deal with device pixel ratio > 1.0 --- src/Viewer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Viewer.js b/src/Viewer.js index f4ed6bb7..02e759a7 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -518,7 +518,8 @@ export class Viewer { const focalLengthY = this.camera.projectionMatrix.elements[5] * 0.5 * this.devicePixelRatio * renderDimensions.y; - const focalAdjustment = this.focalAdjustment; + const focalMultiplier = this.camera.isOrthographicCamera ? (1.0 / this.devicePixelRatio) : 1.0; + const focalAdjustment = this.focalAdjustment * focalMultiplier; const inverseFocalAdjustment = 1.0 / focalAdjustment; this.splatMesh.updateUniforms(renderDimensions, focalLengthX * focalAdjustment, focalLengthY * focalAdjustment, From 6a9335d8e2ce353f92d7b8d31cd0f244e49a24ab Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Wed, 3 Apr 2024 16:22:59 -0700 Subject: [PATCH 09/14] Add point-cloud mode --- README.md | 10 +++++++++- src/SplatMesh.js | 41 ++++++++++++++++++++++++++++++++++++----- src/Viewer.js | 20 ++++++++++++++++++-- src/ui/InfoPanel.js | 9 ++++++--- 4 files changed, 69 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ae4b7a92..d3540c5e 100644 --- a/README.md +++ b/README.md @@ -59,12 +59,20 @@ Keyboard - Ratio of rendered splats to total splats - Last splat sort duration -- `P` Toggles a debug object that shows the orientation of the camera controls. It includes a green arrow representing the camera's orbital axis and a white square representing the plane at which the camera's elevation angle is 0. +- `U` Toggles a debug object that shows the orientation of the camera controls. It includes a green arrow representing the camera's orbital axis and a white square representing the plane at which the camera's elevation angle is 0. - `Left arrow` Rotate the camera's up vector counter-clockwise - `Right arrow` Rotate the camera's up vector clockwise +- `P` Toggle point-cloud mode, where each splat is rendered as a filled circle + +- `=` Increase splat scale + +- `-` Decrease splat scale + +- `O` Toggle orthographic mode +
## Building from source and running locally diff --git a/src/SplatMesh.js b/src/SplatMesh.js index dc5585cc..29c61277 100644 --- a/src/SplatMesh.js +++ b/src/SplatMesh.js @@ -90,6 +90,9 @@ export class SplatMesh extends THREE.Mesh { this.visibleRegionFadeStartRadius = 0; this.visibleRegionChanging = false; + this.splatScale = 1.0; + this.pointCloudMode = false; + this.disposed = false; } @@ -100,9 +103,12 @@ export class SplatMesh extends THREE.Mesh { * @param {boolean} antialiased If true, calculate compensation factor to deal with gaussians being rendered at a significantly * different resolution than that of their training * @param {number} maxScreenSpaceSplatSize The maximum clip space splat size + * @param {number} splatScale Value by which all splats are scaled in screen-space (default is 1.0) + * @param {number} pointCloudMode Render all splats as screen-space circles * @return {THREE.ShaderMaterial} */ - static buildMaterial(dynamicMode = false, antialiased = false, maxScreenSpaceSplatSize = 2048) { + static buildMaterial(dynamicMode = false, antialiased = false, + maxScreenSpaceSplatSize = 2048, splatScale = 1.0, pointCloudMode = false) { // Contains the code to project 3D covariance to 2D and from there calculate the quad (using the eigen vectors of the // 2D covariance) that is ultimately rasterized @@ -127,6 +133,7 @@ export class SplatMesh extends THREE.Mesh { uniform vec2 focal; uniform float orthoZoom; uniform int orthographicMode; + uniform int pointCloudMode; uniform float inverseFocalAdjustment; uniform vec2 viewport; uniform vec2 basisViewport; @@ -289,6 +296,10 @@ export class SplatMesh extends THREE.Mesh { float eigenValue1 = traceOver2 + term2; float eigenValue2 = traceOver2 - term2; + if (pointCloudMode == 1) { + eigenValue1 = eigenValue2 = 0.2; + } + if (eigenValue2 <= 0.0) return; vec2 eigenVector1 = normalize(vec2(b, eigenValue1 - a)); @@ -423,7 +434,11 @@ export class SplatMesh extends THREE.Mesh { }, 'splatScale': { 'type': 'f', - 'value': 1.0 + 'value': splatScale + }, + 'pointCloudMode': { + 'type': 'i', + 'value': pointCloudMode ? 1 : 0 } }; @@ -665,7 +680,8 @@ export class SplatMesh extends THREE.Mesh { this.lastBuildMaxSplatCount = 0; this.disposeMeshData(); this.geometry = SplatMesh.buildGeomtery(maxSplatCount); - this.material = SplatMesh.buildMaterial(this.dynamicMode, this.antialiased, this.maxScreenSpaceSplatSize); + this.material = SplatMesh.buildMaterial(this.dynamicMode, this.antialiased, + this.maxScreenSpaceSplatSize, this.splatScale, this.pointCloudMode); const indexMaps = SplatMesh.buildSplatIndexMaps(splatBuffers); this.globalSplatIndexToLocalSplatIndexMap = indexMaps.localSplatIndexMap; this.globalSplatIndexToSceneIndexMap = indexMaps.sceneIndexMap; @@ -1053,11 +1069,26 @@ export class SplatMesh extends THREE.Mesh { }(); - setSplatScale(scale = 1) { - this.material.uniforms.splatScale.value = scale; + setSplatScale(splatScale = 1) { + this.splatScale = splatScale; + this.material.uniforms.splatScale.value = splatScale; this.material.uniformsNeedUpdate = true; } + getSplatScale() { + return this.splatScale; + } + + setPointCloudMode(pointCloudMode) { + this.pointCloudMode = pointCloudMode; + this.material.uniforms.pointCloudMode.value = pointCloudMode ? 1 : 0; + this.material.uniformsNeedUpdate = true; + } + + getPointCloudMode() { + return this.pointCloudMode; + } + getSplatDataTextures() { return this.splatDataTextures; } diff --git a/src/Viewer.js b/src/Viewer.js index 02e759a7..fdb560bf 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -365,7 +365,7 @@ export class Viewer { case 'KeyC': this.showMeshCursor = !this.showMeshCursor; break; - case 'KeyP': + case 'KeyU': this.showControlPlane = !this.showControlPlane; break; case 'KeyI': @@ -381,6 +381,21 @@ export class Viewer { this.setOrthographicMode(!this.camera.isOrthographicCamera); } break; + case 'KeyP': + if (!this.usingExternalCamera) { + this.splatMesh.setPointCloudMode(!this.splatMesh.getPointCloudMode()); + } + break; + case 'Equal': + if (!this.usingExternalCamera) { + this.splatMesh.setSplatScale(this.splatMesh.getSplatScale() + 0.05); + } + break; + case 'Minus': + if (!this.usingExternalCamera) { + this.splatMesh.setSplatScale(Math.max(this.splatMesh.getSplatScale() - 0.05, 0.0)); + } + break; } }; @@ -1477,7 +1492,8 @@ export class Viewer { this.infoPanel.update(renderDimensions, this.camera.position, cameraLookAtPosition, this.camera.up, this.camera.isOrthographicCamera, meshCursorPosition, this.currentFPS || 'N/A', splatCount, this.splatRenderCount, splatRenderCountPct, - this.lastSortTime, this.focalAdjustment); + this.lastSortTime, this.focalAdjustment, this.splatMesh.getSplatScale(), + this.splatMesh.getPointCloudMode()); }; }(); diff --git a/src/ui/InfoPanel.js b/src/ui/InfoPanel.js index 62ff98aa..4c367c54 100644 --- a/src/ui/InfoPanel.js +++ b/src/ui/InfoPanel.js @@ -16,7 +16,9 @@ export class InfoPanel { ['Rendering:', 'renderSplatCount'], ['Sort time', 'sortTime'], ['Render window', 'renderWindow'], - ['Focal adjustment', 'focalAdjustment'] + ['Focal adjustment', 'focalAdjustment'], + ['Splat scale', 'splatScale'], + ['Point cloud mode', 'pointCloudMode'] ]; this.infoPanelContainer = document.createElement('div'); @@ -101,7 +103,7 @@ export class InfoPanel { update = function(renderDimensions, cameraPosition, cameraLookAtPosition, cameraUp, orthographicCamera, meshCursorPosition, currentFPS, splatCount, splatRenderCount, - splatRenderCountPct, lastSortTime, focalAdjustment) { + splatRenderCountPct, lastSortTime, focalAdjustment, splatScale, pointCloudMode) { const cameraPosString = `${cameraPosition.x.toFixed(5)}, ${cameraPosition.y.toFixed(5)}, ${cameraPosition.z.toFixed(5)}`; if (this.infoCells.cameraPosition.innerHTML !== cameraPosString) { @@ -138,8 +140,9 @@ export class InfoPanel { `${splatRenderCount} splats out of ${splatCount} (${splatRenderCountPct.toFixed(2)}%)`; this.infoCells.sortTime.innerHTML = `${lastSortTime.toFixed(3)} ms`; - this.infoCells.focalAdjustment.innerHTML = `${focalAdjustment.toFixed(3)}`; + this.infoCells.splatScale.innerHTML = `${splatScale.toFixed(3)}`; + this.infoCells.pointCloudMode.innerHTML = `${pointCloudMode}`; }; setContainer(container) { From 82a8dd583b66e0c35af5877a8716f53fb6e96732 Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Wed, 3 Apr 2024 16:32:49 -0700 Subject: [PATCH 10/14] pointCloudMode -> pointCloudModeEnabled --- src/SplatMesh.js | 26 +++++++++++++------------- src/Viewer.js | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/SplatMesh.js b/src/SplatMesh.js index 29c61277..ed229371 100644 --- a/src/SplatMesh.js +++ b/src/SplatMesh.js @@ -91,7 +91,7 @@ export class SplatMesh extends THREE.Mesh { this.visibleRegionChanging = false; this.splatScale = 1.0; - this.pointCloudMode = false; + this.pointCloudModeEnabled = false; this.disposed = false; } @@ -104,11 +104,11 @@ export class SplatMesh extends THREE.Mesh { * different resolution than that of their training * @param {number} maxScreenSpaceSplatSize The maximum clip space splat size * @param {number} splatScale Value by which all splats are scaled in screen-space (default is 1.0) - * @param {number} pointCloudMode Render all splats as screen-space circles + * @param {number} pointCloudModeEnabled Render all splats as screen-space circles * @return {THREE.ShaderMaterial} */ static buildMaterial(dynamicMode = false, antialiased = false, - maxScreenSpaceSplatSize = 2048, splatScale = 1.0, pointCloudMode = false) { + maxScreenSpaceSplatSize = 2048, splatScale = 1.0, pointCloudModeEnabled = false) { // Contains the code to project 3D covariance to 2D and from there calculate the quad (using the eigen vectors of the // 2D covariance) that is ultimately rasterized @@ -133,7 +133,7 @@ export class SplatMesh extends THREE.Mesh { uniform vec2 focal; uniform float orthoZoom; uniform int orthographicMode; - uniform int pointCloudMode; + uniform int pointCloudModeEnabled; uniform float inverseFocalAdjustment; uniform vec2 viewport; uniform vec2 basisViewport; @@ -296,7 +296,7 @@ export class SplatMesh extends THREE.Mesh { float eigenValue1 = traceOver2 + term2; float eigenValue2 = traceOver2 - term2; - if (pointCloudMode == 1) { + if (pointCloudModeEnabled == 1) { eigenValue1 = eigenValue2 = 0.2; } @@ -436,9 +436,9 @@ export class SplatMesh extends THREE.Mesh { 'type': 'f', 'value': splatScale }, - 'pointCloudMode': { + 'pointCloudModeEnabled': { 'type': 'i', - 'value': pointCloudMode ? 1 : 0 + 'value': pointCloudModeEnabled ? 1 : 0 } }; @@ -681,7 +681,7 @@ export class SplatMesh extends THREE.Mesh { this.disposeMeshData(); this.geometry = SplatMesh.buildGeomtery(maxSplatCount); this.material = SplatMesh.buildMaterial(this.dynamicMode, this.antialiased, - this.maxScreenSpaceSplatSize, this.splatScale, this.pointCloudMode); + this.maxScreenSpaceSplatSize, this.splatScale, this.pointCloudModeEnabled); const indexMaps = SplatMesh.buildSplatIndexMaps(splatBuffers); this.globalSplatIndexToLocalSplatIndexMap = indexMaps.localSplatIndexMap; this.globalSplatIndexToSceneIndexMap = indexMaps.sceneIndexMap; @@ -1079,14 +1079,14 @@ export class SplatMesh extends THREE.Mesh { return this.splatScale; } - setPointCloudMode(pointCloudMode) { - this.pointCloudMode = pointCloudMode; - this.material.uniforms.pointCloudMode.value = pointCloudMode ? 1 : 0; + setPointCloudModeEnabled(enabled) { + this.pointCloudModeEnabled = enabled; + this.material.uniforms.pointCloudModeEnabled.value = enabled ? 1 : 0; this.material.uniformsNeedUpdate = true; } - getPointCloudMode() { - return this.pointCloudMode; + getPointCloudModeEnabled() { + return this.pointCloudModeEnabled; } getSplatDataTextures() { diff --git a/src/Viewer.js b/src/Viewer.js index fdb560bf..98c3bf06 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -383,7 +383,7 @@ export class Viewer { break; case 'KeyP': if (!this.usingExternalCamera) { - this.splatMesh.setPointCloudMode(!this.splatMesh.getPointCloudMode()); + this.splatMesh.setPointCloudModeEnabled(!this.splatMesh.getPointCloudModeEnabled()); } break; case 'Equal': From a226dbac5d6575ded044f0af2bc513d4e8e0113b Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Thu, 4 Apr 2024 08:40:02 -0700 Subject: [PATCH 11/14] Added initial version of Viewer.removeSplatScene() --- src/SplatMesh.js | 101 ++++++++++++++++++++++------- src/Util.js | 2 +- src/Viewer.js | 128 ++++++++++++++++++++++++++++++------- src/splattree/SplatTree.js | 49 ++++++++------ 4 files changed, 212 insertions(+), 68 deletions(-) diff --git a/src/SplatMesh.js b/src/SplatMesh.js index ed229371..2a18a70c 100644 --- a/src/SplatMesh.js +++ b/src/SplatMesh.js @@ -55,6 +55,7 @@ export class SplatMesh extends THREE.Mesh { this.scenes = []; // Special octree tailored to SplatMesh instances this.splatTree = null; + this.baseSplatTree = null; // Textures in which splat data will be stored for rendering this.splatDataTextures = {}; this.distancesTransformFeedback = { @@ -93,7 +94,7 @@ export class SplatMesh extends THREE.Mesh { this.splatScale = 1.0; this.pointCloudModeEnabled = false; - this.disposed = false; + this.lastRenderer = null; } /** @@ -572,7 +573,6 @@ export class SplatMesh extends THREE.Mesh { /** * Build an instance of SplatTree (a specialized octree) for the given splat mesh. - * @param {SplatMesh} splatMesh SplatMesh instance for which the splat tree will be built * @param {Array} minAlphas Array of minimum splat slphas for each scene * @param {function} onSplatTreeIndexesUpload Function to be called when the upload of splat centers to the splat tree * builder worker starts and finishes. @@ -580,28 +580,32 @@ export class SplatMesh extends THREE.Mesh { * the format produced by the splat tree builder worker starts and ends. * @return {SplatTree} */ - static buildSplatTree = function(splatMesh, minAlphas = [], onSplatTreeIndexesUpload, onSplatTreeConstruction) { + buildSplatTree = function(minAlphas = [], onSplatTreeIndexesUpload, onSplatTreeConstruction) { return new Promise((resolve) => { + this.disposeSplatTree(); // TODO: expose SplatTree constructor parameters (maximumDepth and maxCentersPerNode) so that they can // be configured on a per-scene basis - const splatTree = new SplatTree(8, 1000); + this.baseSplatTree = new SplatTree(8, 1000); console.time('SplatTree build'); const splatColor = new THREE.Vector4(); - splatTree.processSplatMesh(splatMesh, (splatIndex) => { - splatMesh.getSplatColor(splatIndex, splatColor); - const sceneIndex = splatMesh.getSceneIndexForSplat(splatIndex); + this.baseSplatTree.processSplatMesh(this, (splatIndex) => { + this.getSplatColor(splatIndex, splatColor); + const sceneIndex = this.getSceneIndexForSplat(splatIndex); const minAlpha = minAlphas[sceneIndex] || 1; return splatColor.w >= minAlpha; }, onSplatTreeIndexesUpload, onSplatTreeConstruction) .then(() => { console.timeEnd('SplatTree build'); + this.splatTree = this.baseSplatTree; + this.baseSplatTree = null; + let leavesWithVertices = 0; let avgSplatCount = 0; let maxSplatCount = 0; let nodeCount = 0; - splatTree.visitLeaves((node) => { + this.splatTree.visitLeaves((node) => { const nodeSplatCount = node.data.indexes.length; if (nodeSplatCount > 0) { avgSplatCount += nodeSplatCount; @@ -610,12 +614,12 @@ export class SplatMesh extends THREE.Mesh { leavesWithVertices++; } }); - console.log(`SplatTree leaves: ${splatTree.countLeaves()}`); + console.log(`SplatTree leaves: ${this.splatTree.countLeaves()}`); console.log(`SplatTree leaves with splats:${leavesWithVertices}`); avgSplatCount = avgSplatCount / nodeCount; console.log(`Avg splat count per node: ${avgSplatCount}`); - console.log(`Total splat count: ${splatMesh.getSplatCount()}`); - resolve(splatTree); + console.log(`Total splat count: ${this.getSplatCount()}`); + resolve(); }); }); }; @@ -646,6 +650,7 @@ export class SplatMesh extends THREE.Mesh { build(splatBuffers, sceneOptions, keepSceneTransforms = true, finalBuild = false, onSplatTreeIndexesUpload, onSplatTreeConstruction) { + this.sceneOptions = sceneOptions; this.finalBuild = finalBuild; const maxSplatCount = SplatMesh.getTotalMaxSplatCountForSplatBuffers(splatBuffers); @@ -667,6 +672,7 @@ export class SplatMesh extends THREE.Mesh { this.scenes[0].splatBuffer !== this.lastBuildScenes[0].splatBuffer) { isUpdateBuild = false; } + if (!isUpdateBuild) { isUpdateBuild = false; this.boundingBox = new THREE.Box3(); @@ -698,11 +704,9 @@ export class SplatMesh extends THREE.Mesh { this.lastBuildSceneCount = this.scenes.length; if (finalBuild) { - this.disposeSplatTree(); - SplatMesh.buildSplatTree(this, sceneOptions.map(options => options.splatAlphaRemovalThreshold || 1), - onSplatTreeIndexesUpload, onSplatTreeConstruction) - .then((splatTree) => { - this.splatTree = splatTree; + this.buildSplatTree(sceneOptions.map(options => options.splatAlphaRemovalThreshold || 1), + onSplatTreeIndexesUpload, onSplatTreeConstruction) + .then(() => { if (this.onSplatTreeReadyCallback) this.onSplatTreeReadyCallback(this.splatTree); }); } @@ -716,9 +720,52 @@ export class SplatMesh extends THREE.Mesh { this.disposeTextures(); this.disposeSplatTree(); if (this.enableDistancesComputationOnGPU) { + if (this.computeDistancesOnGPUSyncTimeout) { + clearTimeout(this.computeDistancesOnGPUSyncTimeout); + this.computeDistancesOnGPUSyncTimeout = null; + } this.disposeDistancesComputationGPUResources(); } - this.disposed = true; + this.scenes = []; + this.distancesTransformFeedback = { + 'id': null, + 'vertexShader': null, + 'fragmentShader': null, + 'program': null, + 'centersBuffer': null, + 'transformIndexesBuffer': null, + 'outDistancesBuffer': null, + 'centersLoc': -1, + 'modelViewProjLoc': -1, + 'transformIndexesLoc': -1, + 'transformsLocs': [] + }; + this.renderer = null; + + this.globalSplatIndexToLocalSplatIndexMap = []; + this.globalSplatIndexToSceneIndexMap = []; + + this.lastBuildSplatCount = 0; + this.lastBuildScenes = []; + this.lastBuildMaxSplatCount = 0; + this.lastBuildSceneCount = 0; + this.firstRenderTime = -1; + this.finalBuild = false; + + this.webGLUtils = null; + + this.boundingBox = new THREE.Box3(); + this.calculatedSceneCenter = new THREE.Vector3(); + this.maxSplatDistanceFromSceneCenter = 0; + this.visibleRegionBufferRadius = 0; + this.visibleRegionRadius = 0; + this.visibleRegionFadeStartRadius = 0; + this.visibleRegionChanging = false; + + this.splatScale = 1.0; + this.pointCloudModeEnabled = false; + + this.lastRenderer = null; } /** @@ -749,7 +796,10 @@ export class SplatMesh extends THREE.Mesh { } disposeSplatTree() { + if (this.splatTree) this.splatTree.dispose(); this.splatTree = null; + if (this.baseSplatTree) this.baseSplatTree.dispose(); + this.baseSplatTree = null; } getSplatTree() { @@ -1192,7 +1242,6 @@ export class SplatMesh extends THREE.Mesh { setupDistancesComputationTransformFeedback = function() { - let currentRenderer; let currentMaxSplatCount; return function() { @@ -1200,7 +1249,7 @@ export class SplatMesh extends THREE.Mesh { if (!this.renderer) return; - const rebuildGPUObjects = (currentRenderer !== this.renderer); + const rebuildGPUObjects = (this.lastRenderer !== this.renderer); const rebuildBuffers = currentMaxSplatCount !== maxSplatCount; if (!rebuildGPUObjects && !rebuildBuffers) return; @@ -1293,6 +1342,7 @@ export class SplatMesh extends THREE.Mesh { const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING); const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM); + const currentProgramDeleted = currentProgram ? gl.getProgramParameter(currentProgram, gl.DELETE_STATUS) : false; if (rebuildGPUObjects) { this.distancesTransformFeedback.vao = gl.createVertexArray(); @@ -1373,10 +1423,10 @@ export class SplatMesh extends THREE.Mesh { gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, this.distancesTransformFeedback.id); gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.distancesTransformFeedback.outDistancesBuffer); - if (currentProgram) gl.useProgram(currentProgram); + if (currentProgram && currentProgramDeleted !== true) gl.useProgram(currentProgram); if (currentVao) gl.bindVertexArray(currentVao); - currentRenderer = this.renderer; + this.lastRenderer = this.renderer; currentMaxSplatCount = maxSplatCount; }; @@ -1482,6 +1532,7 @@ export class SplatMesh extends THREE.Mesh { const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING); const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM); + const currentProgramDeleted = currentProgram ? gl.getProgramParameter(currentProgram, gl.DELETE_STATUS) : false; gl.bindVertexArray(this.distancesTransformFeedback.vao); gl.useProgram(this.distancesTransformFeedback.program); @@ -1549,10 +1600,12 @@ export class SplatMesh extends THREE.Mesh { const status = gl.clientWaitSync(sync, bitflags, timeout); switch (status) { case gl.TIMEOUT_EXPIRED: - return setTimeout(checkSync); + this.computeDistancesOnGPUSyncTimeout = setTimeout(checkSync); + return this.computeDistancesOnGPUSyncTimeout; case gl.WAIT_FAILED: throw new Error('should never get here'); default: + this.computeDistancesOnGPUSyncTimeout = null; gl.deleteSync(sync); const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING); gl.bindVertexArray(this.distancesTransformFeedback.vao); @@ -1567,10 +1620,10 @@ export class SplatMesh extends THREE.Mesh { resolve(); } }; - setTimeout(checkSync); + this.computeDistancesOnGPUSyncTimeout = setTimeout(checkSync); }); - if (currentProgram) gl.useProgram(currentProgram); + if (currentProgram && currentProgramDeleted !== true) gl.useProgram(currentProgram); if (currentVao) gl.bindVertexArray(currentVao); return promise; diff --git a/src/Util.js b/src/Util.js index 7ec9f184..0c1b4ab8 100644 --- a/src/Util.js +++ b/src/Util.js @@ -142,6 +142,6 @@ export const delayedExecute = (func) => { return new Promise((resolve) => { window.setTimeout(() => { resolve(func()); - }, 1); + }, 50); }); }; diff --git a/src/Viewer.js b/src/Viewer.js index 98c3bf06..4ae4762e 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -167,7 +167,8 @@ export class Viewer { this.runAfterFirstSort = []; this.selfDrivenModeRunning = false; - this.splatRenderingInitialized = false; + this.splatRenderReady = false; + this.splatMeshUpdating = false; this.raycaster = new Raycaster(); @@ -936,7 +937,7 @@ export class Viewer { if (this.isDisposingOrDisposed()) return Promise.resolve(); - this.splatRenderingInitialized = false; + this.splatRenderReady = false; loadCount++; const finish = (resolver) => { @@ -948,7 +949,7 @@ export class Viewer { this.loadingSpinner.removeTask(splatProcessingTaskId); splatProcessingTaskId = null; } - this.splatRenderingInitialized = true; + this.splatRenderReady = true; } // If we aren't calculating the splat distances from the center on the GPU, the sorting worker needs splat centers and @@ -961,7 +962,7 @@ export class Viewer { 'transformIndexes': transformIndexes.buffer }); } - this.forceSort = true; + this.sortNeededForSceneChange = true; resolver(); }; @@ -996,12 +997,6 @@ export class Viewer { }(); - disposeSortWorker() { - if (this.sortWorker) this.sortWorker.terminate(); - this.sortWorker = null; - this.sortRunning = false; - } - /** * Add one or more instances of SplatBuffer to the SplatMesh instance managed by the viewer. This function is additive; all splat * buffers contained by the viewer's splat mesh before calling this function will be preserved. @@ -1075,7 +1070,6 @@ export class Viewer { } this.lastSortTime = e.data.sortTime; this.sortPromiseResolver(); - this.sortPromise = null; this.sortPromiseResolver = null; this.forceRenderNextFrame(); if (sortCount === 0) { @@ -1119,6 +1113,90 @@ export class Viewer { }); } + disposeSortWorker() { + if (this.sortWorker) this.sortWorker.terminate(); + this.sortWorker = null; + this.sortRunning = false; + } + + removeSplatScene(index, showLoadingUI = true) { + if (this.splatMeshUpdating || this.isDisposingOrDisposed()) return Promise.resolve(); + return new Promise((resolve, reject) => { + this.splatMeshUpdating = true; + let revmovalTaskId; + + if (showLoadingUI) { + this.loadingSpinner.show(); + revmovalTaskId = this.loadingSpinner.addTask('Removing splat scene...'); + } + + const checkAndHideLoadingUI = () => { + if (showLoadingUI) { + this.loadingSpinner.hide(); + this.loadingSpinner.removeTask(revmovalTaskId); + } + }; + + const onDone = () => { + checkAndHideLoadingUI(); + this.splatMeshUpdating = false; + resolve(); + }; + + const checkForEarlyExit = () => { + if(this.isDisposingOrDisposed()) { + onDone(); + return true; + } + return false; + }; + + delayedExecute(() => { + if (checkForEarlyExit()) return; + const newSplatBuffers = []; + const newSceneOptions = []; + const newSceneTransformComponents = []; + const newVisibleRegionFadeStartRadius = this.splatMesh.visibleRegionFadeStartRadius; + for (let i = 0; i < this.splatMesh.scenes.length; i++) { + if (i !== index) { + const scene = this.splatMesh.scenes[i]; + newSplatBuffers.push(scene.splatBuffer); + newSceneOptions.push(this.splatMesh.sceneOptions[i]); + newSceneTransformComponents.push({ + 'position': scene.position.clone(), + 'quaternion': scene.quaternion.clone(), + 'scale': scene.scale.clone() + }); + } + } + this.splatMesh.dispose(); + + this.addSplatBuffers(newSplatBuffers, newSceneOptions, true, false, true) + .then(() => { + if (checkForEarlyExit()) return; + checkAndHideLoadingUI(); + this.splatMesh.visibleRegionFadeStartRadius = newVisibleRegionFadeStartRadius; + this.splatMesh.scenes.forEach((scene, index) => { + scene.position.copy(newSceneTransformComponents[index].position); + scene.quaternion.copy(newSceneTransformComponents[index].quaternion); + scene.scale.copy(newSceneTransformComponents[index].scale); + }); + this.splatMesh.updateTransforms(); + this.updateSplatSort(true) + .then(() => { + if (checkForEarlyExit()) return; + this.sortPromise.then(() => { + onDone(); + }); + }); + }) + .catch((e) => { + reject(e); + }); + }); + }); + } + /** * Start self-driven mode */ @@ -1193,7 +1271,7 @@ export class Viewer { this.camera = null; this.threeScene = null; - this.splatRenderingInitialized = false; + this.splatRenderReady = false; this.initialized = false; if (this.renderer) { if (!this.usingExternalRenderer) { @@ -1277,7 +1355,7 @@ export class Viewer { render = function() { return function() { - if (!this.initialized || !this.splatRenderingInitialized) return; + if (!this.initialized || !this.splatRenderReady || this.splatMeshUpdating) return; const hasRenderables = (threeScene) => { for (let child of threeScene.children) { @@ -1302,7 +1380,7 @@ export class Viewer { update(renderer, camera) { if (this.dropInMode) this.updateForDropInMode(renderer, camera); - if (!this.initialized || !this.splatRenderingInitialized) return; + if (!this.initialized || !this.splatRenderReady || this.splatMeshUpdating) return; if (this.controls) { this.controls.update(); if (this.camera.isOrthographicCamera && !this.usingExternalCamera) { @@ -1493,7 +1571,7 @@ export class Viewer { this.camera.up, this.camera.isOrthographicCamera, meshCursorPosition, this.currentFPS || 'N/A', splatCount, this.splatRenderCount, splatRenderCountPct, this.lastSortTime, this.focalAdjustment, this.splatMesh.getSplatScale(), - this.splatMesh.getPointCloudMode()); + this.splatMesh.getPointCloudModeEnabled()); }; }(); @@ -1532,9 +1610,8 @@ export class Viewer { } ]; - return async function() { + return async function(force = false) { if (this.sortRunning) return; - if (!this.initialized || !this.splatRenderingInitialized) return; let angleDiff = 0; let positionDiff = 0; @@ -1545,10 +1622,13 @@ export class Viewer { angleDiff = sortViewDir.dot(lastSortViewDir); positionDiff = sortViewOffset.copy(this.camera.position).sub(lastSortViewPos).length(); - if (!this.forceSort && !this.splatMesh.dynamicMode && queuedSorts.length === 0) { - if (angleDiff <= 0.99) needsRefreshForRotation = true; - if (positionDiff >= 1.0) needsRefreshForPosition = true; - if (!needsRefreshForRotation && !needsRefreshForPosition) return; + if (!force) { + if (!this.initialized || !this.splatRenderReady || this.splatMeshUpdating) return; + if (!this.sortNeededForSceneChange && !this.splatMesh.dynamicMode && queuedSorts.length === 0) { + if (angleDiff <= 0.99) needsRefreshForRotation = true; + if (positionDiff >= 1.0) needsRefreshForPosition = true; + if (!needsRefreshForRotation && !needsRefreshForPosition) return; + } } this.sortRunning = true; @@ -1566,6 +1646,10 @@ export class Viewer { await this.splatMesh.computeDistancesOnGPU(mvpMatrix, this.sortWorkerPrecomputedDistances); } + if (!force) { + if (!this.initialized || !this.splatRenderReady || this.splatMeshUpdating) return; + } + if (this.splatMesh.dynamicMode || shouldSortAll) { queuedSorts.push(this.splatRenderCount); } else { @@ -1613,7 +1697,7 @@ export class Viewer { lastSortViewDir.copy(sortViewDir); } - this.forceSort = false; + this.sortNeededForSceneChange = false; }; }(); diff --git a/src/splattree/SplatTree.js b/src/splattree/SplatTree.js index a4151339..c008656a 100644 --- a/src/splattree/SplatTree.js +++ b/src/splattree/SplatTree.js @@ -109,6 +109,7 @@ function createSplatTreeWorker(self) { this.addedIndexes = {}; this.nodesWithIndexes = []; this.splatMesh = null; + this.disposed = false; } } @@ -311,15 +312,26 @@ export class SplatTree { this.splatMesh = null; } + + dispose() { + this.diposeSplatTreeWorker(); + this.disposed = true; + } + + diposeSplatTreeWorker() { + if (splatTreeWorker) splatTreeWorker.terminate(); + splatTreeWorker = null; + }; + /** * Construct this instance of SplatTree from an instance of SplatMesh. * * @param {SplatMesh} splatMesh The instance of SplatMesh from which to construct this splat tree. * @param {function} filterFunc Optional function to filter out unwanted splats. * @param {function} onIndexesUpload Function to be called when the upload of splat centers to the splat tree - * builder worker starts and finishes. + * builder worker starts and finishes. * @param {function} onSplatTreeConstruction Function to be called when the conversion of the local splat tree from - * the format produced by the splat tree builder worker starts and ends. + * the format produced by the splat tree builder worker starts and ends. * @return {undefined} */ processSplatMesh = function(splatMesh, filterFunc = () => true, onIndexesUpload, onSplatTreeConstruction) { @@ -347,27 +359,22 @@ export class SplatTree { return sceneCenters; }; - const diposeSplatTreeWorker = () => { - splatTreeWorker.terminate(); - splatTreeWorker = null; - }; - - const checkForEarlyExit = (resolve) => { - if (splatMesh.disposed) { - diposeSplatTreeWorker(); - resolve(); - return true; - } - return false; - }; - return new Promise((resolve) => { + const checkForEarlyExit = () => { + if (this.disposed) { + this.diposeSplatTreeWorker(); + resolve(); + return true; + } + return false; + }; + if (onIndexesUpload) onIndexesUpload(false); delayedExecute(() => { - if (checkForEarlyExit(resolve)) return; + if (checkForEarlyExit()) return; const allCenters = []; if (splatMesh.dynamicMode) { @@ -386,7 +393,7 @@ export class SplatTree { splatTreeWorker.onmessage = (e) => { - if (checkForEarlyExit(resolve)) return; + if (checkForEarlyExit()) return; if (e.data.subTrees) { @@ -394,13 +401,13 @@ export class SplatTree { delayedExecute(() => { - if (checkForEarlyExit(resolve)) return; + if (checkForEarlyExit()) return; for (let workerSubTree of e.data.subTrees) { const convertedSubTree = SplatSubTree.convertWorkerSubTree(workerSubTree, splatMesh); this.subTrees.push(convertedSubTree); } - diposeSplatTreeWorker(); + this.diposeSplatTreeWorker(); if (onSplatTreeConstruction) onSplatTreeConstruction(true); @@ -413,7 +420,7 @@ export class SplatTree { }; delayedExecute(() => { - if (checkForEarlyExit(resolve)) return; + if (checkForEarlyExit()) return; if (onIndexesUpload) onIndexesUpload(true); const transferBuffers = allCenters.map((array) => array.buffer); workerProcessCenters(allCenters, transferBuffers, this.maxDepth, this.maxCentersPerNode); From 8404ab85c607002fd55dc82f61f906aa33636040 Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Thu, 4 Apr 2024 17:10:00 -0700 Subject: [PATCH 12/14] Add fast/slow parameter to delayedExecute() --- src/SplatMesh.js | 102 ++++++++++++++++-------------- src/Util.js | 4 +- src/Viewer.js | 130 ++++++++++++++++++++------------------- src/worker/SortWorker.js | 2 +- 4 files changed, 125 insertions(+), 113 deletions(-) diff --git a/src/SplatMesh.js b/src/SplatMesh.js index 2a18a70c..882940ff 100644 --- a/src/SplatMesh.js +++ b/src/SplatMesh.js @@ -94,6 +94,7 @@ export class SplatMesh extends THREE.Mesh { this.splatScale = 1.0; this.pointCloudModeEnabled = false; + this.disposed = false; this.lastRenderer = null; } @@ -596,30 +597,34 @@ export class SplatMesh extends THREE.Mesh { }, onSplatTreeIndexesUpload, onSplatTreeConstruction) .then(() => { console.timeEnd('SplatTree build'); + if (this.disposed) { + resolve(); + } else { - this.splatTree = this.baseSplatTree; - this.baseSplatTree = null; - - let leavesWithVertices = 0; - let avgSplatCount = 0; - let maxSplatCount = 0; - let nodeCount = 0; - - this.splatTree.visitLeaves((node) => { - const nodeSplatCount = node.data.indexes.length; - if (nodeSplatCount > 0) { - avgSplatCount += nodeSplatCount; - maxSplatCount = Math.max(maxSplatCount, nodeSplatCount); - nodeCount++; - leavesWithVertices++; - } - }); - console.log(`SplatTree leaves: ${this.splatTree.countLeaves()}`); - console.log(`SplatTree leaves with splats:${leavesWithVertices}`); - avgSplatCount = avgSplatCount / nodeCount; - console.log(`Avg splat count per node: ${avgSplatCount}`); - console.log(`Total splat count: ${this.getSplatCount()}`); - resolve(); + this.splatTree = this.baseSplatTree; + this.baseSplatTree = null; + + let leavesWithVertices = 0; + let avgSplatCount = 0; + let maxSplatCount = 0; + let nodeCount = 0; + + this.splatTree.visitLeaves((node) => { + const nodeSplatCount = node.data.indexes.length; + if (nodeSplatCount > 0) { + avgSplatCount += nodeSplatCount; + maxSplatCount = Math.max(maxSplatCount, nodeSplatCount); + nodeCount++; + leavesWithVertices++; + } + }); + console.log(`SplatTree leaves: ${this.splatTree.countLeaves()}`); + console.log(`SplatTree leaves with splats:${leavesWithVertices}`); + avgSplatCount = avgSplatCount / nodeCount; + console.log(`Avg splat count per node: ${avgSplatCount}`); + console.log(`Total splat count: ${this.getSplatCount()}`); + resolve(); + } }); }); }; @@ -765,6 +770,7 @@ export class SplatMesh extends THREE.Mesh { this.splatScale = 1.0; this.pointCloudModeEnabled = false; + this.disposed = true; this.lastRenderer = null; } @@ -1595,29 +1601,33 @@ export class SplatMesh extends THREE.Mesh { const promise = new Promise((resolve) => { const checkSync = () => { - const timeout = 0; - const bitflags = 0; - const status = gl.clientWaitSync(sync, bitflags, timeout); - switch (status) { - case gl.TIMEOUT_EXPIRED: - this.computeDistancesOnGPUSyncTimeout = setTimeout(checkSync); - return this.computeDistancesOnGPUSyncTimeout; - case gl.WAIT_FAILED: - throw new Error('should never get here'); - default: - this.computeDistancesOnGPUSyncTimeout = null; - gl.deleteSync(sync); - const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING); - gl.bindVertexArray(this.distancesTransformFeedback.vao); - gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.outDistancesBuffer); - gl.getBufferSubData(gl.ARRAY_BUFFER, 0, outComputedDistances); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - - if (currentVao) gl.bindVertexArray(currentVao); - - // console.timeEnd("gpu_compute_distances"); - - resolve(); + if (this.disposed) { + resolve(); + } else { + const timeout = 0; + const bitflags = 0; + const status = gl.clientWaitSync(sync, bitflags, timeout); + switch (status) { + case gl.TIMEOUT_EXPIRED: + this.computeDistancesOnGPUSyncTimeout = setTimeout(checkSync); + return this.computeDistancesOnGPUSyncTimeout; + case gl.WAIT_FAILED: + throw new Error('should never get here'); + default: + this.computeDistancesOnGPUSyncTimeout = null; + gl.deleteSync(sync); + const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING); + gl.bindVertexArray(this.distancesTransformFeedback.vao); + gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.outDistancesBuffer); + gl.getBufferSubData(gl.ARRAY_BUFFER, 0, outComputedDistances); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + if (currentVao) gl.bindVertexArray(currentVao); + + // console.timeEnd("gpu_compute_distances"); + + resolve(); + } } }; this.computeDistancesOnGPUSyncTimeout = setTimeout(checkSync); diff --git a/src/Util.js b/src/Util.js index 0c1b4ab8..917170f4 100644 --- a/src/Util.js +++ b/src/Util.js @@ -138,10 +138,10 @@ export const disposeAllMeshes = (object3D) => { } }; -export const delayedExecute = (func) => { +export const delayedExecute = (func, fast) => { return new Promise((resolve) => { window.setTimeout(() => { resolve(func()); - }, 50); + }, fast ? 1 : 50); }); }; diff --git a/src/Viewer.js b/src/Viewer.js index 4ae4762e..c930790a 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -109,14 +109,14 @@ export class Viewer { // scene may change. This prevents optimizations that depend on a static scene from being made. Additionally, if 'dynamicScene' is // true it tells the splat mesh to not apply scene tranforms to splat data that is returned by functions like // SplatMesh.getSplatCenter() by default. - const dynamicScene = !!options.dynamicScene; + this.dynamicScene = !!options.dynamicScene; // When true, will perform additional steps during rendering to address artifacts caused by the rendering of gaussians at a // substantially different resolution than that at which they were rendered during training. This will only work correctly // for models that were trained using a process that utilizes this compensation calculation. For more details: // https://github.com/nerfstudio-project/gsplat/pull/117 // https://github.com/graphdeco-inria/gaussian-splatting/issues/294#issuecomment-1772688093 - const antialiased = options.antialiased || false; + this.antialiased = options.antialiased || false; this.webXRMode = options.webXRMode || WebXRMode.None; @@ -141,8 +141,7 @@ export class Viewer { // Specify the maximum screen-space splat size, can help deal with large splats that get too unwieldy this.maxScreenSpaceSplatSize = options.maxScreenSpaceSplatSize || 2048; - this.splatMesh = new SplatMesh(dynamicScene, this.halfPrecisionCovariancesOnGPU, this.devicePixelRatio, - this.gpuAcceleratedSort, this.integerBasedSort, antialiased, this.maxScreenSpaceSplatSize); + this.createSplatMesh(); this.controls = null; this.perspectiveControls = null; @@ -168,7 +167,6 @@ export class Viewer { this.selfDrivenModeRunning = false; this.splatRenderReady = false; - this.splatMeshUpdating = false; this.raycaster = new Raycaster(); @@ -214,6 +212,12 @@ export class Viewer { if (!this.dropInMode) this.init(); } + createSplatMesh() { + this.splatMesh = new SplatMesh(this.dynamicScene, this.halfPrecisionCovariancesOnGPU, this.devicePixelRatio, + this.gpuAcceleratedSort, this.integerBasedSort, this.antialiased, this.maxScreenSpaceSplatSize); + + } + init() { if (this.initialized) return; @@ -929,27 +933,20 @@ export class Viewer { */ addSplatBuffers = function() { - let loadCount = 0; - let splatProcessingTaskId = null; - return function(splatBuffers, splatBufferOptions = [], finalBuild = true, showLoadingUI = true, showLoadingSpinnerForSplatTreeBuild = true) { if (this.isDisposingOrDisposed()) return Promise.resolve(); this.splatRenderReady = false; - loadCount++; + let splatProcessingTaskId = null; const finish = (resolver) => { if (this.isDisposingOrDisposed()) return; - loadCount--; - if (loadCount === 0) { - if (splatProcessingTaskId !== null) { - this.loadingSpinner.removeTask(splatProcessingTaskId); - splatProcessingTaskId = null; - } - this.splatRenderReady = true; + if (splatProcessingTaskId !== null) { + this.loadingSpinner.removeTask(splatProcessingTaskId); + splatProcessingTaskId = null; } // If we aren't calculating the splat distances from the center on the GPU, the sorting worker needs splat centers and @@ -962,6 +959,8 @@ export class Viewer { 'transformIndexes': transformIndexes.buffer }); } + + this.splatRenderReady = true; this.sortNeededForSceneChange = true; resolver(); }; @@ -988,7 +987,7 @@ export class Viewer { finish(resolve); } } - }); + }, true); }); }; @@ -1100,14 +1099,15 @@ export class Viewer { } for (let i = 0; i < splatCount; i++) this.sortWorkerIndexesToSort[i] = i; this.sortWorker.maxSplatCount = maxSplatCount; - resolve(); - } else if (e.data.sortSetupComplete) { + console.log('Sorting web worker ready.'); const splatDataTextures = this.splatMesh.getSplatDataTextures(); const covariancesTextureSize = splatDataTextures.covariances.size; const centersColorsTextureSize = splatDataTextures.centerColors.size; console.log('Covariances texture size: ' + covariancesTextureSize.x + ' x ' + covariancesTextureSize.y); console.log('Centers/colors texture size: ' + centersColorsTextureSize.x + ' x ' + centersColorsTextureSize.y); + + resolve(); } }; }); @@ -1120,9 +1120,8 @@ export class Viewer { } removeSplatScene(index, showLoadingUI = true) { - if (this.splatMeshUpdating || this.isDisposingOrDisposed()) return Promise.resolve(); + if (this.isDisposingOrDisposed()) return Promise.resolve(); return new Promise((resolve, reject) => { - this.splatMeshUpdating = true; let revmovalTaskId; if (showLoadingUI) { @@ -1139,12 +1138,11 @@ export class Viewer { const onDone = () => { checkAndHideLoadingUI(); - this.splatMeshUpdating = false; resolve(); }; const checkForEarlyExit = () => { - if(this.isDisposingOrDisposed()) { + if (this.isDisposingOrDisposed()) { onDone(); return true; } @@ -1152,46 +1150,55 @@ export class Viewer { }; delayedExecute(() => { - if (checkForEarlyExit()) return; - const newSplatBuffers = []; - const newSceneOptions = []; - const newSceneTransformComponents = []; - const newVisibleRegionFadeStartRadius = this.splatMesh.visibleRegionFadeStartRadius; - for (let i = 0; i < this.splatMesh.scenes.length; i++) { - if (i !== index) { - const scene = this.splatMesh.scenes[i]; - newSplatBuffers.push(scene.splatBuffer); - newSceneOptions.push(this.splatMesh.sceneOptions[i]); - newSceneTransformComponents.push({ - 'position': scene.position.clone(), - 'quaternion': scene.quaternion.clone(), - 'scale': scene.scale.clone() - }); + this.sortPromise.then(() => { + if (checkForEarlyExit()) return; + const newSplatBuffers = []; + const newSceneOptions = []; + const newSceneTransformComponents = []; + const newVisibleRegionFadeStartRadius = this.splatMesh.visibleRegionFadeStartRadius; + for (let i = 0; i < this.splatMesh.scenes.length; i++) { + if (i !== index) { + const scene = this.splatMesh.scenes[i]; + newSplatBuffers.push(scene.splatBuffer); + newSceneOptions.push(this.splatMesh.sceneOptions[i]); + newSceneTransformComponents.push({ + 'position': scene.position.clone(), + 'quaternion': scene.quaternion.clone(), + 'scale': scene.scale.clone() + }); + } } - } - this.splatMesh.dispose(); + this.splatMesh.dispose(); + this.createSplatMesh(); - this.addSplatBuffers(newSplatBuffers, newSceneOptions, true, false, true) - .then(() => { - if (checkForEarlyExit()) return; - checkAndHideLoadingUI(); - this.splatMesh.visibleRegionFadeStartRadius = newVisibleRegionFadeStartRadius; - this.splatMesh.scenes.forEach((scene, index) => { - scene.position.copy(newSceneTransformComponents[index].position); - scene.quaternion.copy(newSceneTransformComponents[index].quaternion); - scene.scale.copy(newSceneTransformComponents[index].scale); - }); - this.splatMesh.updateTransforms(); - this.updateSplatSort(true) + this.addSplatBuffers(newSplatBuffers, newSceneOptions, true, false, true) .then(() => { if (checkForEarlyExit()) return; - this.sortPromise.then(() => { - onDone(); + checkAndHideLoadingUI(); + this.splatMesh.visibleRegionFadeStartRadius = newVisibleRegionFadeStartRadius; + this.splatMesh.scenes.forEach((scene, index) => { + scene.position.copy(newSceneTransformComponents[index].position); + scene.quaternion.copy(newSceneTransformComponents[index].quaternion); + scene.scale.copy(newSceneTransformComponents[index].scale); + }); + this.splatMesh.updateTransforms(); + + this.splatRenderReady = false; + this.updateSplatSort(true) + .then(() => { + if (checkForEarlyExit()) { + this.splatRenderReady = true; + return; + } + this.sortPromise.then(() => { + this.splatRenderReady = true; + onDone(); + }); }); + }) + .catch((e) => { + reject(e); }); - }) - .catch((e) => { - reject(e); }); }); }); @@ -1355,7 +1362,7 @@ export class Viewer { render = function() { return function() { - if (!this.initialized || !this.splatRenderReady || this.splatMeshUpdating) return; + if (!this.initialized || !this.splatRenderReady) return; const hasRenderables = (threeScene) => { for (let child of threeScene.children) { @@ -1380,7 +1387,7 @@ export class Viewer { update(renderer, camera) { if (this.dropInMode) this.updateForDropInMode(renderer, camera); - if (!this.initialized || !this.splatRenderReady || this.splatMeshUpdating) return; + if (!this.initialized || !this.splatRenderReady) return; if (this.controls) { this.controls.update(); if (this.camera.isOrthographicCamera && !this.usingExternalCamera) { @@ -1623,7 +1630,6 @@ export class Viewer { positionDiff = sortViewOffset.copy(this.camera.position).sub(lastSortViewPos).length(); if (!force) { - if (!this.initialized || !this.splatRenderReady || this.splatMeshUpdating) return; if (!this.sortNeededForSceneChange && !this.splatMesh.dynamicMode && queuedSorts.length === 0) { if (angleDiff <= 0.99) needsRefreshForRotation = true; if (positionDiff >= 1.0) needsRefreshForPosition = true; @@ -1646,10 +1652,6 @@ export class Viewer { await this.splatMesh.computeDistancesOnGPU(mvpMatrix, this.sortWorkerPrecomputedDistances); } - if (!force) { - if (!this.initialized || !this.splatRenderReady || this.splatMeshUpdating) return; - } - if (this.splatMesh.dynamicMode || shouldSortAll) { queuedSorts.push(this.splatRenderCount); } else { diff --git a/src/worker/SortWorker.js b/src/worker/SortWorker.js index 011c8b44..05918bfb 100644 --- a/src/worker/SortWorker.js +++ b/src/worker/SortWorker.js @@ -88,7 +88,7 @@ function sortWorker(self) { new Uint32Array(wasmMemory, transformIndexesOffset, splatCount).set(new Uint32Array(transformIndexes)); } self.postMessage({ - 'sortSetupComplete': true, + 'centerDataSet': true, }); } else if (e.data.sort) { const renderCount = e.data.sort.splatRenderCount || 0; From a1206ffd71cd7cf368b885b1bbe102011cebc91d Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Thu, 4 Apr 2024 22:14:10 -0700 Subject: [PATCH 13/14] More efficient update of sort worker center & transform index data --- src/SplatMesh.js | 60 +++++++++++++++++++++++++++++++++------- src/Viewer.js | 53 ++++++++++++++++++----------------- src/worker/SortWorker.js | 9 ++++-- 3 files changed, 84 insertions(+), 38 deletions(-) diff --git a/src/SplatMesh.js b/src/SplatMesh.js index 882940ff..2abccc61 100644 --- a/src/SplatMesh.js +++ b/src/SplatMesh.js @@ -587,7 +587,7 @@ export class SplatMesh extends THREE.Mesh { // TODO: expose SplatTree constructor parameters (maximumDepth and maxCentersPerNode) so that they can // be configured on a per-scene basis this.baseSplatTree = new SplatTree(8, 1000); - console.time('SplatTree build'); + const buildStartTime = performance.now(); const splatColor = new THREE.Vector4(); this.baseSplatTree.processSplatMesh(this, (splatIndex) => { this.getSplatColor(splatIndex, splatColor); @@ -596,7 +596,8 @@ export class SplatMesh extends THREE.Mesh { return splatColor.w >= minAlpha; }, onSplatTreeIndexesUpload, onSplatTreeConstruction) .then(() => { - console.timeEnd('SplatTree build'); + const buildTime = performance.now() - buildStartTime; + console.log('SplatTree build: ' + buildTime + ' ms'); if (this.disposed) { resolve(); } else { @@ -651,6 +652,7 @@ export class SplatMesh extends THREE.Mesh { * builder worker starts and finishes. * @param {function} onSplatTreeConstruction Function to be called when the conversion of the local splat tree from * the format produced by the splat tree builder worker starts and ends. + * @return {object} Object containing info about the splats that are updated */ build(splatBuffers, sceneOptions, keepSceneTransforms = true, finalBuild = false, onSplatTreeIndexesUpload, onSplatTreeConstruction) { @@ -704,6 +706,19 @@ export class SplatMesh extends THREE.Mesh { for (let i = 0; i < this.scenes.length; i++) { this.lastBuildScenes[i] = this.scenes[i]; } + + const buildResults = { + 'from': this.lastBuildSplatCount, + 'to': this.getSplatCount() - 1, + 'count': this.getSplatCount() - this.lastBuildSplatCount + }; + if (!this.enableDistancesComputationOnGPU) { + buildResults.centers = this.integerBasedDistancesComputation ? + this.getIntegerCenters(true, isUpdateBuild) : + this.getFloatCenters(true, isUpdateBuild); + buildResults.transformIndexes = this.getTransformIndexes(isUpdateBuild); + } + this.lastBuildSplatCount = this.getSplatCount(); this.lastBuildMaxSplatCount = this.getMaxSplatCount(); this.lastBuildSceneCount = this.scenes.length; @@ -715,6 +730,8 @@ export class SplatMesh extends THREE.Mesh { if (this.onSplatTreeReadyCallback) this.onSplatTreeReadyCallback(this.splatTree); }); } + + return buildResults; } /** @@ -823,7 +840,7 @@ export class SplatMesh extends THREE.Mesh { this.uploadSplatDataToTextures(isUpdateBuild); if (this.enableDistancesComputationOnGPU) { this.updateGPUCentersBufferForDistancesComputation(isUpdateBuild); - this.updateGPUTransformIndexesBufferForDistancesComputation(); + this.updateGPUTransformIndexesBufferForDistancesComputation(isUpdateBuild); } } @@ -1465,7 +1482,7 @@ export class SplatMesh extends THREE.Mesh { if (isUpdateBuild) { gl.bufferSubData(gl.ARRAY_BUFFER, subBufferOffset, srcCenters); } else { - const maxArray = new ArrayType(this.getMaxSplatCount() * 16); + const maxArray = new ArrayType(this.getMaxSplatCount() * attributeBytesPerCenter); maxArray.set(srcCenters); gl.bufferData(gl.ARRAY_BUFFER, maxArray, gl.STATIC_DRAW); } @@ -1477,8 +1494,9 @@ export class SplatMesh extends THREE.Mesh { /** * Refresh GPU buffers used for pre-computing splat distances with centers data from the scenes for this mesh. + * @param {boolean} isUpdateBuild Specify whether or not to only update for splats that have been added since the last build. */ - updateGPUTransformIndexesBufferForDistancesComputation() { + updateGPUTransformIndexesBufferForDistancesComputation(isUpdateBuild) { if (!this.renderer || !this.dynamicMode) return; @@ -1487,8 +1505,18 @@ export class SplatMesh extends THREE.Mesh { const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING); gl.bindVertexArray(this.distancesTransformFeedback.vao); + const subBufferOffset = isUpdateBuild ? this.lastBuildSplatCount * 4 : 0; + const transformIndexes = this.getTransformIndexes(isUpdateBuild); + gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.transformIndexesBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.getTransformIndexes(), gl.STATIC_DRAW); + + if (isUpdateBuild) { + gl.bufferSubData(gl.ARRAY_BUFFER, subBufferOffset, transformIndexes); + } else { + const maxArray = new Uint32Array(this.getMaxSplatCount() * 4); + maxArray.set(transformIndexes); + gl.bufferData(gl.ARRAY_BUFFER, maxArray, gl.STATIC_DRAW); + } gl.bindBuffer(gl.ARRAY_BUFFER, null); if (currentVao) gl.bindVertexArray(currentVao); @@ -1496,11 +1524,24 @@ export class SplatMesh extends THREE.Mesh { /** * Get a typed array containing a mapping from global splat indexes to their scene index. + * @param {boolean} isUpdateBuild Specify whether or not to only update for splats that have been added since the last build. * @return {Uint32Array} */ - getTransformIndexes() { - const transformIndexes = new Uint32Array(this.globalSplatIndexToSceneIndexMap.length); - transformIndexes.set(this.globalSplatIndexToSceneIndexMap); + getTransformIndexes(isUpdateBuild) { + + let transformIndexes; + if (isUpdateBuild) { + const splatCount = this.getSplatCount(); + const fillCount = splatCount - this.lastBuildSplatCount; + transformIndexes = new Uint32Array(fillCount); + for (let i = this.lastBuildSplatCount; i < splatCount; i++) { + transformIndexes[i] = this.globalSplatIndexToSceneIndexMap[i]; + } + } else { + transformIndexes = new Uint32Array(this.globalSplatIndexToSceneIndexMap.length); + transformIndexes.set(this.globalSplatIndexToSceneIndexMap); + } + return transformIndexes; } @@ -1730,7 +1771,6 @@ export class SplatMesh extends THREE.Mesh { return intCenters; } - /** * Returns an array of splat centers, transformed as appropriate, optionally padded. * @param {number} padFour Enforce alignement of 4 by inserting a 1 after every 3 values diff --git a/src/Viewer.js b/src/Viewer.js index c930790a..919266cd 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -215,7 +215,7 @@ export class Viewer { createSplatMesh() { this.splatMesh = new SplatMesh(this.dynamicScene, this.halfPrecisionCovariancesOnGPU, this.devicePixelRatio, this.gpuAcceleratedSort, this.integerBasedSort, this.antialiased, this.maxScreenSpaceSplatSize); - + this.splatMesh.frustumCulled = false; } init() { @@ -941,7 +941,7 @@ export class Viewer { this.splatRenderReady = false; let splatProcessingTaskId = null; - const finish = (resolver) => { + const finish = (buildResults, resolver) => { if (this.isDisposingOrDisposed()) return; if (splatProcessingTaskId !== null) { @@ -952,11 +952,14 @@ export class Viewer { // If we aren't calculating the splat distances from the center on the GPU, the sorting worker needs splat centers and // transform indexes so that it can calculate those distance values. if (!this.gpuAcceleratedSort) { - const centers = this.integerBasedSort ? this.splatMesh.getIntegerCenters(true) : this.splatMesh.getFloatCenters(true); - const transformIndexes = this.splatMesh.getTransformIndexes(); this.sortWorker.postMessage({ - 'centers': centers.buffer, - 'transformIndexes': transformIndexes.buffer + 'centers': buildResults.centers.buffer, + 'transformIndexes': buildResults.transformIndexes.buffer, + 'range': { + 'from': buildResults.from, + 'to': buildResults.to, + 'count': buildResults.count + } }); } @@ -974,17 +977,18 @@ export class Viewer { if (this.isDisposingOrDisposed()) { resolve(); } else { - this.addSplatBuffersToMesh(splatBuffers, splatBufferOptions, finalBuild, showLoadingSpinnerForSplatTreeBuild); + const buildResults = this.addSplatBuffersToMesh(splatBuffers, splatBufferOptions, + finalBuild, showLoadingSpinnerForSplatTreeBuild); const maxSplatCount = this.splatMesh.getMaxSplatCount(); if (this.sortWorker && this.sortWorker.maxSplatCount !== maxSplatCount) { this.disposeSortWorker(); } if (!this.sortWorker) { this.setupSortWorker(this.splatMesh).then(() => { - finish(resolve); + finish(buildResults, resolve); }); } else { - finish(resolve); + finish(buildResults, resolve); } } }, true); @@ -1014,6 +1018,7 @@ export class Viewer { * @param {boolean} finalBuild Will the splat mesh be in its final state after this build? * @param {boolean} showLoadingSpinnerForSplatTreeBuild Whether or not to show the loading spinner during * construction of the splat tree. + * @return {object} Object containing info about the splats that are updated */ addSplatBuffersToMesh(splatBuffers, splatBufferOptions, finalBuild = true, showLoadingSpinnerForSplatTreeBuild = false) { if (this.isDisposingOrDisposed()) return; @@ -1033,15 +1038,13 @@ export class Viewer { } } }; - const onSplatTreeConstructed = (finished) => { + const onSplatTreeReady = (finished) => { if (this.isDisposingOrDisposed()) return; if (finished && splatOptimizingTaskId) { this.loadingSpinner.removeTask(splatOptimizingTaskId); } }; - this.splatMesh.build(allSplatBuffers, allSplatBufferOptions, true, finalBuild, - onSplatTreeIndexesUpload, onSplatTreeConstructed); - this.splatMesh.frustumCulled = false; + return this.splatMesh.build(allSplatBuffers, allSplatBufferOptions, true, finalBuild, onSplatTreeIndexesUpload, onSplatTreeReady); } /** @@ -1152,16 +1155,16 @@ export class Viewer { delayedExecute(() => { this.sortPromise.then(() => { if (checkForEarlyExit()) return; - const newSplatBuffers = []; - const newSceneOptions = []; - const newSceneTransformComponents = []; - const newVisibleRegionFadeStartRadius = this.splatMesh.visibleRegionFadeStartRadius; + const savedSplatBuffers = []; + const savedSceneOptions = []; + const savedSceneTransformComponents = []; + const savedVisibleRegionFadeStartRadius = this.splatMesh.visibleRegionFadeStartRadius; for (let i = 0; i < this.splatMesh.scenes.length; i++) { if (i !== index) { const scene = this.splatMesh.scenes[i]; - newSplatBuffers.push(scene.splatBuffer); - newSceneOptions.push(this.splatMesh.sceneOptions[i]); - newSceneTransformComponents.push({ + savedSplatBuffers.push(scene.splatBuffer); + savedSceneOptions.push(this.splatMesh.sceneOptions[i]); + savedSceneTransformComponents.push({ 'position': scene.position.clone(), 'quaternion': scene.quaternion.clone(), 'scale': scene.scale.clone() @@ -1171,15 +1174,15 @@ export class Viewer { this.splatMesh.dispose(); this.createSplatMesh(); - this.addSplatBuffers(newSplatBuffers, newSceneOptions, true, false, true) + this.addSplatBuffers(savedSplatBuffers, savedSceneOptions, true, false, true) .then(() => { if (checkForEarlyExit()) return; checkAndHideLoadingUI(); - this.splatMesh.visibleRegionFadeStartRadius = newVisibleRegionFadeStartRadius; + this.splatMesh.visibleRegionFadeStartRadius = savedVisibleRegionFadeStartRadius; this.splatMesh.scenes.forEach((scene, index) => { - scene.position.copy(newSceneTransformComponents[index].position); - scene.quaternion.copy(newSceneTransformComponents[index].quaternion); - scene.scale.copy(newSceneTransformComponents[index].scale); + scene.position.copy(savedSceneTransformComponents[index].position); + scene.quaternion.copy(savedSceneTransformComponents[index].quaternion); + scene.scale.copy(savedSceneTransformComponents[index].scale); }); this.splatMesh.updateTransforms(); diff --git a/src/worker/SortWorker.js b/src/worker/SortWorker.js index 05918bfb..1e030153 100644 --- a/src/worker/SortWorker.js +++ b/src/worker/SortWorker.js @@ -80,12 +80,15 @@ function sortWorker(self) { centers = e.data.centers; transformIndexes = e.data.transformIndexes; if (integerBasedSort) { - new Int32Array(wasmMemory, centersOffset, splatCount * 4).set(new Int32Array(centers)); + new Int32Array(wasmMemory, centersOffset + e.data.range.from * Constants.BytesPerInt * 4, + e.data.range.count * 4).set(new Int32Array(centers)); } else { - new Float32Array(wasmMemory, centersOffset, splatCount * 4).set(new Float32Array(centers)); + new Float32Array(wasmMemory, centersOffset + e.data.range.from * Constants.BytesPerFloat * 4, + e.data.range.count * 4).set(new Float32Array(centers)); } if (dynamicMode) { - new Uint32Array(wasmMemory, transformIndexesOffset, splatCount).set(new Uint32Array(transformIndexes)); + new Uint32Array(wasmMemory, transformIndexesOffset + e.data.range.from * 4, + e.data.range.count).set(new Uint32Array(transformIndexes)); } self.postMessage({ 'centerDataSet': true, From b5ae580b4b119564d8ef70eea9c91867f2c0d154 Mon Sep 17 00:00:00 2001 From: Mark Kellogg Date: Fri, 5 Apr 2024 11:33:13 -0700 Subject: [PATCH 14/14] Update version number & make gpuAcceleratedSort=false by default --- package-lock.json | 4 ++-- package.json | 2 +- src/Viewer.js | 6 +----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf7e41a8..66e754f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@mkkellogg/gaussian-splats-3d", - "version": "0.3.6", + "version": "0.3.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@mkkellogg/gaussian-splats-3d", - "version": "0.3.6", + "version": "0.3.7", "license": "MIT", "devDependencies": { "@babel/core": "7.22.0", diff --git a/package.json b/package.json index 14d1f4de..b4f78848 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "git", "url": "https://github.com/mkkellogg/GaussianSplats3D" }, - "version": "0.3.6", + "version": "0.3.7", "description": "Three.js-based 3D Gaussian splat viewer", "module": "build/gaussian-splats-3d.module.js", "main": "build/gaussian-splats-3d.umd.cjs", diff --git a/src/Viewer.js b/src/Viewer.js index 919266cd..8871b497 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -83,11 +83,7 @@ export class Viewer { // If 'gpuAcceleratedSort' is true, a partially GPU-accelerated approach to sorting splats will be used. // Currently this means pre-computing splat distances from the camera on the GPU - this.gpuAcceleratedSort = options.gpuAcceleratedSort; - if (this.gpuAcceleratedSort !== true && this.gpuAcceleratedSort !== false) { - if (this.isMobile()) this.gpuAcceleratedSort = false; - else this.gpuAcceleratedSort = true; - } + this.gpuAcceleratedSort = options.gpuAcceleratedSort || false; // if 'integerBasedSort' is true, the integer version of splat centers as well as other values used to calculate // splat distances are used instead of the float version. This speeds up computation, but introduces the possibility of