diff --git a/src/RenderMode.js b/src/RenderMode.js new file mode 100644 index 00000000..ac0b0a94 --- /dev/null +++ b/src/RenderMode.js @@ -0,0 +1,5 @@ +export const RenderMode = { + Always: 0, + OnChange: 1, + Never: 2 +}; diff --git a/src/SceneHelper.js b/src/SceneHelper.js index 986acb58..c9a4f0cf 100644 --- a/src/SceneHelper.js +++ b/src/SceneHelper.js @@ -129,6 +129,10 @@ export class SceneHelper { this.meshCursor.visible = visible; } + getMeschCursorVisibility() { + return this.meshCursor.visible; + } + setMeshCursorPosition(position) { this.meshCursor.position.copy(position); } diff --git a/src/SplatMesh.js b/src/SplatMesh.js index 63066730..0d071ea9 100644 --- a/src/SplatMesh.js +++ b/src/SplatMesh.js @@ -72,6 +72,7 @@ export class SplatMesh extends THREE.Mesh { this.maxRadius = 0; this.visibleRegionRadius = 0; this.visibleRegionFadeStartRadius = 0; + this.visibleRegionChanging = false; this.disposed = false; } @@ -921,6 +922,7 @@ export class SplatMesh extends THREE.Mesh { this.material.uniforms.currentTime.value = performance.now(); this.material.uniforms.fadeInComplete.value = fadeInComplete; this.material.uniformsNeedUpdate = true; + this.visibleRegionChanging = !fadeInComplete; } /** diff --git a/src/Viewer.js b/src/Viewer.js index b7e608d8..ae2fbfa2 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -20,13 +20,14 @@ import { VRButton } from './webxr/VRButton.js'; import { ARButton } from './webxr/ARButton.js'; import { delayedExecute } from './Util.js'; import { LoaderStatus } from './loaders/LoaderStatus.js'; - +import { RenderMode } from './RenderMode.js'; const THREE_CAMERA_FOV = 50; const MINIMUM_DISTANCE_TO_NEW_FOCAL_POINT = .75; const MIN_SPLAT_COUNT_TO_SHOW_SPLAT_TREE_LOADING_SPINNER = 1500000; const FOCUS_MARKER_FADE_IN_SPEED = 10.0; const FOCUS_MARKER_FADE_OUT_SPEED = 2.5; +const CONSECUTIVE_RENDERED_FRAMES_FOR_FPS_CALCULATION = 60; /** * Viewer: Manages the rendering of splat scenes. Manages an instance of SplatMesh as well as a web worker @@ -121,6 +122,8 @@ export class Viewer { this.gpuAcceleratedSort = false; } + this.renderMode = options.renderMode || RenderMode.Always; + this.controls = null; this.showMeshCursor = false; @@ -147,6 +150,7 @@ export class Viewer { this.currentFPS = 0; this.lastSortTime = 0; + this.consecutiveRenderFrames = 0; this.previousCameraTarget = new THREE.Vector3(); this.nextCameraTarget = new THREE.Vector3(); @@ -283,6 +287,10 @@ export class Viewer { } } + setRenderMode(renderMode) { + this.renderMode = renderMode; + } + onKeyDown = function() { const forward = new THREE.Vector3(); @@ -941,6 +949,7 @@ export class Viewer { this.sortPromiseResolver(); this.sortPromise = null; this.sortPromiseResolver = null; + this.forceRenderNextFrame(); if (sortCount === 0) { this.runAfterFirstSort.forEach((func) => { func(); @@ -1088,13 +1097,60 @@ export class Viewer { this.requestFrameId = requestAnimationFrame(this.selfDrivenUpdateFunc); } this.update(); - this.render(); + if (this.shouldRender()) { + this.render(); + this.consecutiveRenderFrames++; + } else { + this.consecutiveRenderFrames = 0; + } + this.renderNextFrame = false; } + forceRenderNextFrame() { + this.renderNextFrame = true; + } + + shouldRender = function() { + + let renderCount = 0; + const lastCameraPosition = new THREE.Vector3(); + const lastCameraOrientation = new THREE.Quaternion(); + const changeEpsilon = 0.0001; + + return function() { + let shouldRender = false; + let cameraChanged = false; + if (this.camera) { + const cp = this.camera.position; + const co = this.camera.quaternion; + cameraChanged = Math.abs(cp.x - lastCameraPosition.x) > changeEpsilon || + Math.abs(cp.y - lastCameraPosition.y) > changeEpsilon || + Math.abs(cp.z - lastCameraPosition.z) > changeEpsilon || + Math.abs(co.x - lastCameraOrientation.x) > changeEpsilon || + Math.abs(co.y - lastCameraOrientation.y) > changeEpsilon || + Math.abs(co.z - lastCameraOrientation.z) > changeEpsilon || + Math.abs(co.w - lastCameraOrientation.w) > changeEpsilon; + } + + shouldRender = this.renderMode !== RenderMode.Never && (renderCount === 0 || this.splatMesh.visibleRegionChanging || + cameraChanged || this.renderMode === RenderMode.Always || this.dynamicMode === true || this.renderNextFrame); + + if (this.camera) { + lastCameraPosition.copy(this.camera.position); + lastCameraOrientation.copy(this.camera.quaternion); + } + + renderCount++; + return shouldRender; + }; + + }(); + render = function() { return function() { if (!this.initialized || !this.splatRenderingInitialized) return; + const hasRenderables = (threeScene) => { for (let child of threeScene.children) { if (child.visible) return true; @@ -1141,14 +1197,18 @@ export class Viewer { let frameCount = 0; return function() { - const currentTime = getCurrentTime(); - const calcDelta = currentTime - lastCalcTime; - if (calcDelta >= 1.0) { - this.currentFPS = frameCount; - frameCount = 0; - lastCalcTime = currentTime; + if (this.consecutiveRenderFrames > CONSECUTIVE_RENDERED_FRAMES_FOR_FPS_CALCULATION) { + const currentTime = getCurrentTime(); + const calcDelta = currentTime - lastCalcTime; + if (calcDelta >= 1.0) { + this.currentFPS = frameCount; + frameCount = 0; + lastCalcTime = currentTime; + } else { + frameCount++; + } } else { - frameCount++; + this.currentFPS = null; } }; @@ -1227,6 +1287,7 @@ export class Viewer { this.sceneHelper.setFocusMarkerOpacity(newFocusMarkerOpacity); this.sceneHelper.updateFocusMarker(this.nextCameraTarget, this.camera, renderDimensions); wasTransitioning = true; + this.forceRenderNextFrame(); } else { let currentFocusMarkerOpacity; if (wasTransitioning) currentFocusMarkerOpacity = 1.0; @@ -1237,6 +1298,7 @@ export class Viewer { this.sceneHelper.setFocusMarkerOpacity(newFocusMarkerOpacity); if (newFocusMarkerOpacity === 0.0) this.sceneHelper.setFocusMarkerVisibility(false); } + if (currentFocusMarkerOpacity > 0.0) this.forceRenderNextFrame(); wasTransitioning = false; } }; @@ -1250,6 +1312,7 @@ export class Viewer { return function() { if (this.showMeshCursor) { + this.forceRenderNextFrame(); this.getRenderDimensions(renderDimensions); outHits.length = 0; this.raycaster.setFromCameraAndScreenPosition(this.camera, this.mousePosition, renderDimensions); @@ -1261,6 +1324,7 @@ export class Viewer { this.sceneHelper.setMeshCursorVisibility(false); } } else { + if (this.sceneHelper.getMeschCursorVisibility()) this.forceRenderNextFrame(); this.sceneHelper.setMeshCursorVisibility(false); } }; @@ -1279,7 +1343,7 @@ 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, splatCount, + this.camera.up, meshCursorPosition, this.currentFPS || 'N/A', splatCount, this.splatRenderCount, splatRenderCountPct, this.lastSortTime); }; diff --git a/src/index.js b/src/index.js index 0dc576dc..64b29353 100644 --- a/src/index.js +++ b/src/index.js @@ -13,6 +13,7 @@ import { OrbitControls } from './OrbitControls.js'; import { AbortablePromise } from './AbortablePromise.js'; import { SceneFormat } from './loaders/SceneFormat.js'; import { WebXRMode } from './webxr/WebXRMode.js'; +import { RenderMode } from './RenderMode.js'; export { PlyParser, @@ -29,5 +30,6 @@ export { OrbitControls, AbortablePromise, SceneFormat, - WebXRMode + WebXRMode, + RenderMode };