From bbb2a908ebd069312d8e06ebda94ad03b769113f Mon Sep 17 00:00:00 2001 From: lindsay Date: Fri, 7 Jul 2023 15:26:28 +0200 Subject: [PATCH] Refactorings --- .../AngleMeasurementsControl.js | 4 +- .../DistanceMeasurementsControl.js | 177 ++- src/plugins/SectionPlanesPlugin/Control.js | 2 +- src/viewer/Viewer.js | 1 - .../scene/CameraControl/lib/CameraUpdater.js | 3 - .../lib/controllers/PickController.js | 156 +- .../lib/handlers/MousePickHandler.js | 55 +- .../ViewFrustumCullingManager.js | 45 + .../frustumCulling/ViewFrustumCullingMesh.js | 9 + .../frustumCulling/ViewFrustumCullingModel.js | 9 + .../frustumCulling/ViewFrustumCullingNode.js | 12 + .../frustumCulling/ViewFrustumCullingState.js | 427 ++++++ .../cluster-helper.js | 123 +- src/viewer/scene/frustumCulling/rbush3d.js | 848 +++++++++++ .../xeokit-cluster.js | 0 .../LodCullingManager.js | 381 ++--- .../DataTextureSceneModel.js | 1028 ++++++++++++- .../lib/DataTextureSceneModelMesh.js | 16 +- .../lib/DataTextureSceneModelNode.js | 32 +- .../lib/layers/BindableDataTexture.js | 93 ++ .../lib/layers/DataTextureBuffer.js | 27 + .../lib/layers/DataTextureGenerator.js | 573 +++++++ .../lib/layers/DataTextureState.js | 1345 ++--------------- .../TrianglesDataTextureLayer.js | 198 +-- .../ViewFrustumCullingManager.js | 749 --------- .../calculateUniquePositions.js | 102 +- .../dataTextureRamStats.js | 56 + .../layers/trianglesDataTexture/rbush3d.js | 814 ---------- .../trianglesDataTexture/rebucketPositions.js | 436 ++---- .../models/VBOSceneModel/VBOSceneModel.js | 16 +- .../lib/VBOSceneModelGeometry.js | 14 - .../TrianglesBatchingLayer.js | 150 -- .../TrianglesInstancingLayer.js | 151 -- src/viewer/scene/scene/Scene.js | 3 +- src/viewer/scene/webgl/Renderer.js | 56 +- 35 files changed, 3872 insertions(+), 4239 deletions(-) create mode 100644 src/viewer/scene/frustumCulling/ViewFrustumCullingManager.js create mode 100644 src/viewer/scene/frustumCulling/ViewFrustumCullingMesh.js create mode 100644 src/viewer/scene/frustumCulling/ViewFrustumCullingModel.js create mode 100644 src/viewer/scene/frustumCulling/ViewFrustumCullingNode.js create mode 100644 src/viewer/scene/frustumCulling/ViewFrustumCullingState.js rename src/viewer/scene/{models/DataTextureSceneModel/lib/layers/trianglesDataTexture => frustumCulling}/cluster-helper.js (69%) create mode 100644 src/viewer/scene/frustumCulling/rbush3d.js rename src/viewer/scene/{models/DataTextureSceneModel/lib/layers/trianglesDataTexture => frustumCulling}/xeokit-cluster.js (100%) rename src/viewer/scene/{models/DataTextureSceneModel/lib/layers/trianglesDataTexture => lodCulling}/LodCullingManager.js (68%) create mode 100644 src/viewer/scene/models/DataTextureSceneModel/lib/layers/BindableDataTexture.js create mode 100644 src/viewer/scene/models/DataTextureSceneModel/lib/layers/DataTextureBuffer.js create mode 100644 src/viewer/scene/models/DataTextureSceneModel/lib/layers/DataTextureGenerator.js delete mode 100644 src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/ViewFrustumCullingManager.js create mode 100644 src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/dataTextureRamStats.js delete mode 100644 src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/rbush3d.js diff --git a/src/plugins/AngleMeasurementsPlugin/AngleMeasurementsControl.js b/src/plugins/AngleMeasurementsPlugin/AngleMeasurementsControl.js index 39246c8fe..aaac2330d 100644 --- a/src/plugins/AngleMeasurementsPlugin/AngleMeasurementsControl.js +++ b/src/plugins/AngleMeasurementsPlugin/AngleMeasurementsControl.js @@ -88,7 +88,7 @@ class AngleMeasurementsControl extends Component { const touchEndCanvasPos = math.vec2(); const touchStartWorldPos = math.vec3(); - this._onMouseHoverSurface = cameraControl.on("hoverSurface", event => { + this._onMouseHoverSurface = cameraControl.on("hoverOverEntitySurface", event => { isMouseHoveringEntity = true; mouseHoverEntity = event.entity; mouseWorldPos.set(event.worldPos); @@ -222,7 +222,7 @@ class AngleMeasurementsControl extends Component { } }); - this._onHoverNothing = cameraControl.on("hoverOff", event => { + this._onHoverNothing = cameraControl.on("hoverNothing", event => { isMouseHoveringEntity = false; if (this._currentAngleMeasurement) { diff --git a/src/plugins/DistanceMeasurementsPlugin/DistanceMeasurementsControl.js b/src/plugins/DistanceMeasurementsPlugin/DistanceMeasurementsControl.js index 5ba04c367..d08582133 100644 --- a/src/plugins/DistanceMeasurementsPlugin/DistanceMeasurementsControl.js +++ b/src/plugins/DistanceMeasurementsPlugin/DistanceMeasurementsControl.js @@ -152,11 +152,14 @@ class DistanceMeasurementsControl extends Component { const input = scene.input; const startDot = this._touchStartDot; - const pickSurfacePrecisionEnabled = scene.pickSurfacePrecisionEnabled; - let mouseHoverEntity = null; - const mouseWorldPos = math.vec3(); - const mouseCanvasPos = math.vec2(); + + const surfaceWorldPos = math.vec3(); + const surfaceCanvasPos = math.vec2(); + + const snappedWorldPos = math.vec3(); + const snappedCanvasPos = math.vec2(); + let lastMouseCanvasX; let lastMouseCanvasY; @@ -171,13 +174,17 @@ class DistanceMeasurementsControl extends Component { const touchEndCanvasPos = math.vec2(); const touchStartWorldPos = math.vec3(); + let snapped = false; + this._onMouseHoverSurface = cameraControl.on("hoverOverEntitySurface", event => { + snapped = false; + // This gets fired for both mouse and touch input, but we don't care when handling touch mouseHoverEntity = true; - mouseWorldPos.set(event.worldPos); - mouseCanvasPos.set(event.canvasPos); + surfaceWorldPos.set(event.worldPos); + surfaceCanvasPos.set(event.canvasPos); if (touchState === FIRST_TOUCH_EXPECTED) { this.markerDiv.style.marginLeft = `${event.canvasPos[0] - 5}px`; @@ -201,18 +208,21 @@ class DistanceMeasurementsControl extends Component { this._currentDistanceMeasurementByMouse.yAxisVisible = this._currentDistanceMeasurementByMouseInittouchState.yAxisVisible && this.plugin.defaultYAxisVisible; this._currentDistanceMeasurementByMouse.zAxisVisible = this._currentDistanceMeasurementByMouseInittouchState.zAxisVisible && this.plugin.defaultZAxisVisible; this._currentDistanceMeasurementByMouse.targetVisible = this._currentDistanceMeasurementByMouseInittouchState.targetVisible; - // this._currentDistanceMeasurementByMouse.target.entity = mouseHoverEntity; - this._currentDistanceMeasurementByMouse.target.worldPos = mouseWorldPos; + this._currentDistanceMeasurementByMouse.target.worldPos = surfaceWorldPos.slice(); } }); this._onMouseHoverSurface = cameraControl.on("pickedVertex", event => { + console.log("pickedVertex"); + + snapped = true; + // This gets fired for both mouse and touch input, but we don't care when handling touch mouseHoverEntity = true; - mouseWorldPos.set(event.snappedWorldPos); - mouseCanvasPos.set(event.snappedCanvasPos); + snappedWorldPos.set(event.snappedWorldPos); + snappedCanvasPos.set(event.snappedCanvasPos); if (touchState === FIRST_TOUCH_EXPECTED) { this.markerDiv.style.marginLeft = `${event.snappedCanvasPos[0] - 5}px`; @@ -223,8 +233,8 @@ class DistanceMeasurementsControl extends Component { } // } else { // if (event.worldPos !== null && event.canvasPos !== null) { - // mouseWorldPos.set(event.worldPos); - // mouseCanvasPos.set(event.canvasPos); + // surfaceWorldPos.set(event.worldPos); + // surfaceCanvasPos.set(event.canvasPos); // // if (touchState === FIRST_TOUCH_EXPECTED) { // this.markerDiv.style.marginLeft = `${event.canvasPos[0]-5}px`; @@ -250,25 +260,28 @@ class DistanceMeasurementsControl extends Component { this._currentDistanceMeasurementByMouse.yAxisVisible = this._currentDistanceMeasurementByMouseInittouchState.yAxisVisible && this.plugin.defaultYAxisVisible; this._currentDistanceMeasurementByMouse.zAxisVisible = this._currentDistanceMeasurementByMouseInittouchState.zAxisVisible && this.plugin.defaultZAxisVisible; this._currentDistanceMeasurementByMouse.targetVisible = this._currentDistanceMeasurementByMouseInittouchState.targetVisible; - // this._currentDistanceMeasurementByMouse.target.entity = mouseHoverEntity; - this._currentDistanceMeasurementByMouse.target.worldPos = mouseWorldPos; + this._currentDistanceMeasurementByMouse.target.worldPos = surfaceWorldPos; } }); this._onInputMouseDown = input.on("mousedown", (coords) => { + console.log("mousedown"); lastMouseCanvasX = coords[0]; lastMouseCanvasY = coords[1]; }); this._onInputMouseUp = input.on("mouseup", (coords) => { + console.log("mouseup snapped = " + snapped); if (coords[0] > lastMouseCanvasX + mouseCanvasClickTolerance || coords[0] < lastMouseCanvasX - mouseCanvasClickTolerance || coords[1] > lastMouseCanvasY + mouseCanvasClickTolerance || coords[1] < lastMouseCanvasY - mouseCanvasClickTolerance) { return; } + const worldPos = snapped ? snappedWorldPos.slice() : surfaceWorldPos.slice(); if (this._currentDistanceMeasurementByMouse) { if (mouseHoverEntity) { + this._currentDistanceMeasurementByMouse.target.worldPos = worldPos; this._currentDistanceMeasurementByMouse.clickable = true; this.fire("measurementEnd", this._currentDistanceMeasurementByMouse); this._currentDistanceMeasurementByMouse = null; @@ -282,10 +295,10 @@ class DistanceMeasurementsControl extends Component { this._currentDistanceMeasurementByMouse = plugin.createMeasurement({ id: math.createUUID(), origin: { - worldPos: mouseWorldPos + worldPos: worldPos.slice() }, target: { - worldPos: mouseWorldPos + worldPos: worldPos.slice() }, approximate: true }); @@ -304,8 +317,9 @@ class DistanceMeasurementsControl extends Component { }); this._onMouseHoverOff = cameraControl.on("hoverNothing", event => { + console.log("hoverNothing"); mouseHoverEntity = null; - + snapped = false; this.markerDiv.style.marginLeft = `-100px`; this.markerDiv.style.marginTop = `-100px`; @@ -318,72 +332,73 @@ class DistanceMeasurementsControl extends Component { }); this._onPickedNothing = cameraControl.on("pickedNothing", event => { - if (this._currentDistanceMeasurementByMouse) { - this.fire("measurementCancel", this._currentDistanceMeasurementByMouse); - this._currentDistanceMeasurementByMouse.destroy(); - this._currentDistanceMeasurementByMouse = null; - } - startDot.setVisible(false); - touchState = FIRST_TOUCH_EXPECTED; + console.log("pickedNothing"); + // if (this._currentDistanceMeasurementByMouse) { + // this.fire("measurementCancel", this._currentDistanceMeasurementByMouse); + // this._currentDistanceMeasurementByMouse.destroy(); + // this._currentDistanceMeasurementByMouse = null; + // } + // startDot.setVisible(false); + // touchState = FIRST_TOUCH_EXPECTED; }); - canvas.addEventListener("touchstart", this._onCanvasTouchStart = (event) => { - const touches = event.touches; - const changedTouches = event.changedTouches; - if (touches.length === 1 && changedTouches.length === 1) { - getCanvasPosFromEvent(touches[0], touchStartCanvasPos); - } - }, {passive: true}); - - canvas.addEventListener("touchend", this._onCanvasTouchEnd = (event) => { - const touches = event.touches; - const changedTouches = event.changedTouches; - if (touches.length === 0 && changedTouches.length === 1) { - getCanvasPosFromEvent(changedTouches[0], touchEndCanvasPos); - if (touchEndCanvasPos[0] > touchStartCanvasPos[0] + touchCanvasClickTolerance || - touchEndCanvasPos[0] < touchStartCanvasPos[0] - touchCanvasClickTolerance || - touchEndCanvasPos[1] > touchStartCanvasPos[1] + touchCanvasClickTolerance || - touchEndCanvasPos[1] < touchStartCanvasPos[1] - touchCanvasClickTolerance) { - return; // User is repositioning the camera or model - } - const pickResult = scene.pick({ - canvasPos: touchEndCanvasPos, - pickSurface: true, - pickSurfacePrecision: pickSurfacePrecisionEnabled - }); - if (pickResult && pickResult.worldPos) { - switch (touchState) { - case FIRST_TOUCH_EXPECTED: - startDot.setVisible(true); - this._touchStartMarker.worldPos = pickResult.worldPos; - touchStartWorldPos.set(pickResult.worldPos); - touchState = SECOND_TOUCH_EXPECTED; - break; - case SECOND_TOUCH_EXPECTED: - startDot.setVisible(false); - this._touchStartMarker.worldPos = pickResult.worldPos; - const measurement = plugin.createMeasurement({ - id: math.createUUID(), - origin: { - worldPos: touchStartWorldPos - }, - target: { - worldPos: pickResult.worldPos - }, - approximate: (!pickSurfacePrecisionEnabled) - }); - measurement.clickable = true; - touchState = FIRST_TOUCH_EXPECTED; - this.fire("measurementEnd", measurement); - break; - } - } else { - startDot.setVisible(false); - touchState = FIRST_TOUCH_EXPECTED; - } - } - // event.stopPropagation(); - }, {passive: true}); + // canvas.addEventListener("touchstart", this._onCanvasTouchStart = (event) => { + // const touches = event.touches; + // const changedTouches = event.changedTouches; + // if (touches.length === 1 && changedTouches.length === 1) { + // getCanvasPosFromEvent(touches[0], touchStartCanvasPos); + // } + // }, {passive: true}); + // + // canvas.addEventListener("touchend", this._onCanvasTouchEnd = (event) => { + // const touches = event.touches; + // const changedTouches = event.changedTouches; + // if (touches.length === 0 && changedTouches.length === 1) { + // getCanvasPosFromEvent(changedTouches[0], touchEndCanvasPos); + // if (touchEndCanvasPos[0] > touchStartCanvasPos[0] + touchCanvasClickTolerance || + // touchEndCanvasPos[0] < touchStartCanvasPos[0] - touchCanvasClickTolerance || + // touchEndCanvasPos[1] > touchStartCanvasPos[1] + touchCanvasClickTolerance || + // touchEndCanvasPos[1] < touchStartCanvasPos[1] - touchCanvasClickTolerance) { + // return; // User is repositioning the camera or model + // } + // const pickResult = scene.pick({ + // canvasPos: touchEndCanvasPos, + // pickSurface: true, + // pickSurfacePrecision: pickSurfacePrecisionEnabled + // }); + // if (pickResult && pickResult.worldPos) { + // switch (touchState) { + // case FIRST_TOUCH_EXPECTED: + // startDot.setVisible(true); + // this._touchStartMarker.worldPos = pickResult.worldPos; + // touchStartWorldPos.set(pickResult.worldPos); + // touchState = SECOND_TOUCH_EXPECTED; + // break; + // case SECOND_TOUCH_EXPECTED: + // startDot.setVisible(false); + // this._touchStartMarker.worldPos = pickResult.worldPos; + // const measurement = plugin.createMeasurement({ + // id: math.createUUID(), + // origin: { + // worldPos: touchStartWorldPos + // }, + // target: { + // worldPos: pickResult.worldPos + // }, + // approximate: (!pickSurfacePrecisionEnabled) + // }); + // measurement.clickable = true; + // touchState = FIRST_TOUCH_EXPECTED; + // this.fire("measurementEnd", measurement); + // break; + // } + // } else { + // startDot.setVisible(false); + // touchState = FIRST_TOUCH_EXPECTED; + // } + // } + // // event.stopPropagation(); + // }, {passive: true}); this._active = true; } diff --git a/src/plugins/SectionPlanesPlugin/Control.js b/src/plugins/SectionPlanesPlugin/Control.js index f387b6d13..95ffdfef0 100644 --- a/src/plugins/SectionPlanesPlugin/Control.js +++ b/src/plugins/SectionPlanesPlugin/Control.js @@ -1182,7 +1182,7 @@ class Control { grabbed = true; }); - this._onCameraControlHoverLeave = this._viewer.cameraControl.on("hoverOut", (hit) => { + this._onCameraControlHoverLeave = this._viewer.cameraControl.on("hoverOutEntity", (hit) => { if (!this._visible) { return; } diff --git a/src/viewer/Viewer.js b/src/viewer/Viewer.js index 53fd69bb1..afa142762 100644 --- a/src/viewer/Viewer.js +++ b/src/viewer/Viewer.js @@ -106,7 +106,6 @@ class Viewer { saoEnabled: cfg.saoEnabled, alphaDepthMask: (cfg.alphaDepthMask !== false), entityOffsetsEnabled: (!!cfg.entityOffsetsEnabled), - pickSurfacePrecisionEnabled: (!!cfg.pickSurfacePrecisionEnabled), logarithmicDepthBufferEnabled: (!!cfg.logarithmicDepthBufferEnabled), pbrEnabled: (!!cfg.pbrEnabled), colorTextureEnabled: (cfg.colorTextureEnabled !== false) diff --git a/src/viewer/scene/CameraControl/lib/CameraUpdater.js b/src/viewer/scene/CameraControl/lib/CameraUpdater.js index 89ba5c201..afb386fac 100644 --- a/src/viewer/scene/CameraControl/lib/CameraUpdater.js +++ b/src/viewer/scene/CameraControl/lib/CameraUpdater.js @@ -297,13 +297,10 @@ class CameraUpdater { updates.dollyDelta *= configs.dollyInertia; } - pickController.fireEvents(); - document.body.style.cursor = cursorType; }); } - destroy() { this._scene.off(this._onTick); } diff --git a/src/viewer/scene/CameraControl/lib/controllers/PickController.js b/src/viewer/scene/CameraControl/lib/controllers/PickController.js index e3a65cf8e..c0970973a 100644 --- a/src/viewer/scene/CameraControl/lib/controllers/PickController.js +++ b/src/viewer/scene/CameraControl/lib/controllers/PickController.js @@ -213,9 +213,11 @@ class PickController { this.pickVertexResult.snappedCanvasPos && this.pickVertexResult.snappedWorldPos) { // Vertex pick succeeded, so schedule event this.pickedVertex = true; + this.pickedEntitySurface = false; this._needFireEvents = true; } if (!this.pickedVertex) { // Vertex pick failed, attempt surface pick as fallback + this.schedulePickEntitySurface = false; this.pickEntitySurfaceResult = this._scene.pick({ pickSurface: true, pickSurfaceNormal: false, @@ -225,7 +227,6 @@ class PickController { this.pickEntitySurfaceResult.entity && this.pickEntitySurfaceResult.worldPos) { // Entity surface pick succeeded, so schedule event this.pickedEntitySurface = true; - this.schedulePickEntitySurface = false; this._needFireEvents = true; } else { this.pickedNothing = true; @@ -263,79 +264,86 @@ class PickController { this.schedulePickVertex = false; this.schedulePickEdge = false; } - - fireEvents() { - if (!this._needFireEvents) { - return; - } - if (this.pickedNothing) { - this._cameraControl.fire("pickedNothing", { - canvasPos: this.pickedNothingResult.canvasPos - }, true); - this._cameraControl.fire("hoverNothing", { - canvasPos: this.pickedNothingResult.canvasPos - }, true); - } else { - let hoverLeaveEntityFired = false; - let hoverEnterEntityFired = false; - if (this.pickedEntity) { - this._cameraControl.fire("pickedEntity", this.pickEntityResult, true); - this._cameraControl.fire("hoverOverEntity", { - entity: this.pickEntityResult.entity.id, - canvasPos: this.pickEntityResult.canvasPos - }, true); - if (this._lastPickedEntityId !== null && this._lastPickedEntityId !== this.pickEntityResult.entity.id) { // Hover off old Entity - this._cameraControl.fire("hoverLeaveEntity", { - entity: this._scene.objects[this._lastPickedEntityId], - canvasPos: this.pickEntityResult.canvasPos - }, true); - hoverLeaveEntityFired = true; - this._lastPickedEntityId = this.pickEntityResult.entity.id; - } - if (this._lastPickedEntityId !== this.pickEntityResult.entity.id) { // Hover onto new Entity - this._cameraControl.fire("hoverEnterEntity", { - entity: this.pickEntityResult.entity, - canvasPos: this.pickEntityResult.canvasPos - }, true); - hoverEnterEntityFired = true; - } - this._lastPickedEntityId = this.pickEntityResult.entity.id; - } - if (this.pickedEntitySurface) { - this._cameraControl.fire("pickedEntitySurface", this.pickEntitySurfaceResult, true); - this._cameraControl.fire("hoverOverEntitySurface", { - entity: this.pickEntitySurfaceResult.entity.id, - worldPos: this.pickEntitySurfaceResult.worldPos, - canvasPos: this.pickEntitySurfaceResult.canvasPos - }, true); - if (this._lastPickedEntitySurfaceId !== null) { - if (hoverLeaveEntityFired) { - this._cameraControl.fire("hoverLeaveEntity", { - entity: this._scene.objects[this._lastPickedEntitySurfaceId], - canvasPos: this.pickEntitySurfaceResult.canvasPos - }, true); - } - this._lastPickedEntitySurfaceId = null; - } - if (this._lastPickedEntitySurfaceId !== this.pickEntitySurfaceResult.entity.id) { // Hover onto new Entity - if (hoverEnterEntityFired) { - this._cameraControl.fire("hoverEnterEntity", { - entity: this.pickEntitySurfaceResult.entity, - canvasPos: this.pickEntitySurfaceResult.canvasPos - }, true); - } - } - this._lastPickedEntitySurfaceId = this.pickEntitySurfaceResult.entity.id; - } - if (this.pickedVertex) { - this._cameraControl.fire("pickedVertex", this.pickVertexResult, true); - } - if (this.pickedEdge) { - this._cameraControl.fire("pickedEdge", this.pickEdgeResult, true); - } - } - this._needFireEvents = false; - } + // + // fireEvents() { + // + // if (!this._needFireEvents) { + // return; + // } + // + // ///////////////////////////////////////////////////////// + // return; + // //////////////////////////////////////////////////////// + // + // + // if (this.pickedNothing) { + // this._cameraControl.fire("pickedNothing", { + // canvasPos: this.pickedNothingResult.canvasPos + // }, true); + // this._cameraControl.fire("hoverNothing", { + // canvasPos: this.pickedNothingResult.canvasPos + // }, true); + // } else { + // let hoverLeaveEntityFired = false; + // let hoverEnterEntityFired = false; + // if (this.pickedEntity) { + // this._cameraControl.fire("pickedEntity", this.pickEntityResult, true); + // this._cameraControl.fire("hoverOverEntity", { + // entity: this.pickEntityResult.entity.id, + // canvasPos: this.pickEntityResult.canvasPos + // }, true); + // if (this._lastPickedEntityId !== null && this._lastPickedEntityId !== this.pickEntityResult.entity.id) { // Hover off old Entity + // this._cameraControl.fire("hoverLeaveEntity", { + // entity: this._scene.objects[this._lastPickedEntityId], + // canvasPos: this.pickEntityResult.canvasPos + // }, true); + // hoverLeaveEntityFired = true; + // this._lastPickedEntityId = this.pickEntityResult.entity.id; + // } + // if (this._lastPickedEntityId !== this.pickEntityResult.entity.id) { // Hover onto new Entity + // this._cameraControl.fire("hoverEnterEntity", { + // entity: this.pickEntityResult.entity, + // canvasPos: this.pickEntityResult.canvasPos + // }, true); + // hoverEnterEntityFired = true; + // } + // this._lastPickedEntityId = this.pickEntityResult.entity.id; + // } + // // if (this.pickedEntitySurface && !this.pickedVertex) { + // // this._cameraControl.fire("pickedEntitySurface", this.pickEntitySurfaceResult, true); + // // this._cameraControl.fire("hoverOverEntitySurface", { + // // entity: this.pickEntitySurfaceResult.entity.id, + // // worldPos: this.pickEntitySurfaceResult.worldPos, + // // canvasPos: this.pickEntitySurfaceResult.canvasPos + // // }, true); + // // if (this._lastPickedEntitySurfaceId !== null) { + // // if (hoverLeaveEntityFired) { + // // this._cameraControl.fire("hoverLeaveEntity", { + // // entity: this._scene.objects[this._lastPickedEntitySurfaceId], + // // canvasPos: this.pickEntitySurfaceResult.canvasPos + // // }, true); + // // } + // // this._lastPickedEntitySurfaceId = null; + // // } + // // if (this._lastPickedEntitySurfaceId !== this.pickEntitySurfaceResult.entity.id) { // Hover onto new Entity + // // if (hoverEnterEntityFired) { + // // this._cameraControl.fire("hoverEnterEntity", { + // // entity: this.pickEntitySurfaceResult.entity, + // // canvasPos: this.pickEntitySurfaceResult.canvasPos + // // }, true); + // // } + // // } + // // this._lastPickedEntitySurfaceId = this.pickEntitySurfaceResult.entity.id; + // // } + // if (this.pickedVertex) { + // this._cameraControl.fire("pickedVertex", this.pickVertexResult, true); + // } + // if (this.pickedEdge) { + // this._cameraControl.fire("pickedEdge", this.pickEdgeResult, true); + // } + // } + // this._needFireEvents = false; + // } destroy() { } diff --git a/src/viewer/scene/CameraControl/lib/handlers/MousePickHandler.js b/src/viewer/scene/CameraControl/lib/handlers/MousePickHandler.js index 2a9df5a53..c0d3599e2 100644 --- a/src/viewer/scene/CameraControl/lib/handlers/MousePickHandler.js +++ b/src/viewer/scene/CameraControl/lib/handlers/MousePickHandler.js @@ -74,30 +74,37 @@ class MousePickHandler { pickController.update(); - // if (pickController.pickedEntitySurface) { - // const pickedEntityId = pickController.pickEntitySurfaceResult.entity.id; - // if (this._lastPickedEntityId !== pickedEntityId) { - // if (this._lastPickedEntityId !== undefined) { - // cameraControl.fire("hoverLeaveEntity", { // Hovered off an entity - // entity: scene.objects[this._lastPickedEntityId] - // }, true); - // } - // cameraControl.fire("hoverEnterEntity", pickController.pickEntitySurfaceResult, true); // Hovering over a new entity - // this._lastPickedEntityId = pickedEntityId; - // } - // cameraControl.fire("hoverOverEntity", pickController.pickEntitySurfaceResult, true); - // cameraControl.fire("hoverOverEntitySurface", pickController.pickEntitySurfaceResult, true); - // } else { - // if (this._lastPickedEntityId !== undefined) { - // cameraControl.fire("hoverLeaveEntity", { // Hovered off an entity - // entity: scene.objects[this._lastPickedEntityId] - // }, true); - // this._lastPickedEntityId = undefined; - // } - // cameraControl.fire("hoverNothing", { // Not hovering on any entity - // canvasPos: pickController.pickCursorPos - // }, true); - // } + if (pickController.pickedEntitySurface) { + const pickedEntityId = pickController.pickEntitySurfaceResult.entity.id; + if (this._lastPickedEntityId !== pickedEntityId) { + if (this._lastPickedEntityId !== undefined) { + cameraControl.fire("hoverLeaveEntity", { // Hovered off an entity + entity: scene.objects[this._lastPickedEntityId] + }, true); + } + cameraControl.fire("hoverEnterEntity", pickController.pickEntitySurfaceResult, true); // Hovering over a new entity + this._lastPickedEntityId = pickedEntityId; + } + cameraControl.fire("hoverOverEntity", pickController.pickEntitySurfaceResult, true); + cameraControl.fire("hoverOverEntitySurface", pickController.pickEntitySurfaceResult, true); + } else { + if (this._lastPickedEntityId !== undefined) { + cameraControl.fire("hoverLeaveEntity", { // Hovered off an entity + entity: scene.objects[this._lastPickedEntityId] + }, true); + this._lastPickedEntityId = undefined; + } + cameraControl.fire("hoverNothing", { // Not hovering on any entity + canvasPos: pickController.pickCursorPos + }, true); + } + + if (pickController.pickedVertex) { + cameraControl.fire("pickedVertex", pickController.pickVertexResult, true); + } + if (pickController.pickedEdge) { + cameraControl.fire("pickedEdge", pickController.pickEdgeResult, true); + } } }); diff --git a/src/viewer/scene/frustumCulling/ViewFrustumCullingManager.js b/src/viewer/scene/frustumCulling/ViewFrustumCullingManager.js new file mode 100644 index 000000000..d769b454f --- /dev/null +++ b/src/viewer/scene/frustumCulling/ViewFrustumCullingManager.js @@ -0,0 +1,45 @@ +import {ViewFrustumCullingState} from "./ViewFrustumCullingState"; + +export class ViewFrustumCullingManager { + + constructor(model) { + this.model = model; + this.entities = []; + this.meshes = []; + this.finalized = false; + } + + addEntity(entity) { + if (this.finalized) { + throw "Already finalized"; + } + this.entities.push(entity); + } + + addMesh(mesh) { + if (this.finalized) { + throw "Already finalized"; + } + this.meshes.push(mesh); + } + + finalize(fnForceFinalizeLayer) { + if (this.finalized) { + throw "Already finalized"; + } + this.finalized = true; + this.vfcState = new ViewFrustumCullingState(); + this.vfcState.initializeVfcState(this.entities, this.meshes); + this.vfcState.finalize(this.model, fnForceFinalizeLayer); + this.model.scene.on("rendering", () => this.applyViewFrustumCulling.call(this)); + } + + applyViewFrustumCulling() { + if (!(this.finalized)) { + throw "Not finalized"; + } + this.vfcState.applyViewFrustumCulling(this.model); + } +} + + diff --git a/src/viewer/scene/frustumCulling/ViewFrustumCullingMesh.js b/src/viewer/scene/frustumCulling/ViewFrustumCullingMesh.js new file mode 100644 index 000000000..dcd88e5ed --- /dev/null +++ b/src/viewer/scene/frustumCulling/ViewFrustumCullingMesh.js @@ -0,0 +1,9 @@ +/** + * @abstract + */ +export class ViewFrustumCullingNode { + + get numtriangles() { + } +} + diff --git a/src/viewer/scene/frustumCulling/ViewFrustumCullingModel.js b/src/viewer/scene/frustumCulling/ViewFrustumCullingModel.js new file mode 100644 index 000000000..f8a559bab --- /dev/null +++ b/src/viewer/scene/frustumCulling/ViewFrustumCullingModel.js @@ -0,0 +1,9 @@ +/** + * @abstract + */ +export class ViewFrustumCullingModel { + + get worldMatrix() { + } + +} \ No newline at end of file diff --git a/src/viewer/scene/frustumCulling/ViewFrustumCullingNode.js b/src/viewer/scene/frustumCulling/ViewFrustumCullingNode.js new file mode 100644 index 000000000..fee16673d --- /dev/null +++ b/src/viewer/scene/frustumCulling/ViewFrustumCullingNode.js @@ -0,0 +1,12 @@ +/** + * @abstract + */ +export class ViewFrustumCullingNode { + + get culledVCF() { + } + + set culledVFC(culledVFC) { + } +} + diff --git a/src/viewer/scene/frustumCulling/ViewFrustumCullingState.js b/src/viewer/scene/frustumCulling/ViewFrustumCullingState.js new file mode 100644 index 000000000..7e7355984 --- /dev/null +++ b/src/viewer/scene/frustumCulling/ViewFrustumCullingState.js @@ -0,0 +1,427 @@ +import {clusterizeV2} from "./cluster-helper"; +import {math} from "../math"; + +// For JSDoc autocompletion +//import {ViewFrustumCullingModel} from "../../../ViewFrustumCullingModel.js" +import {RBush3D} from "./rbush3d.js"; +//import {ViewFrustumCullingNode} from "../../ViewFrustumCullingNode.js"; + +let tempVec3 = math.vec3(); + +/** + * Number of bits per-dimension in the 2-dimensional LUT fast atan table + */ +const ATAN2_LUT_BITS = 9; +const ATAN2_FACTOR = 1 << (ATAN2_LUT_BITS - 1); + +/** + * Constant for quick conversion of radians to degrees + */ +const _180_DIV_MATH_PI = 180 / Math.PI; +const atan2LUT = new Float32Array((1 << ATAN2_LUT_BITS) * (1 << ATAN2_LUT_BITS)); + +// Initialize the Look Up Table +for (let i = -ATAN2_FACTOR; i < ATAN2_FACTOR; i++) { + for (let j = -ATAN2_FACTOR; j < ATAN2_FACTOR; j++) { + const index = ((i + ATAN2_FACTOR) << ATAN2_LUT_BITS) + (j + ATAN2_FACTOR); + const max = Math.max(Math.abs(i), Math.abs(j)); + atan2LUT [index] = Math.atan2(i / max, j / max); + } +} + +/** + * Fast ````Math.atan2```` implementation based in Look Up Tables. + * + * @param {number} x + * @param {number} y + * + * @returns {number} + */ +function fastAtan2(x, y) { + const max_factor = ATAN2_FACTOR / Math.max(Math.abs(x), Math.abs(y)); + const xx = Math.round(x * max_factor) + (ATAN2_FACTOR - 1); + const yy = Math.round(y * max_factor) + (ATAN2_FACTOR - 1); + return atan2LUT [(xx << ATAN2_LUT_BITS) + yy]; +} + +const VISIBILITY_CHECK_ALL_D = (1 << 0); +const VISIBILITY_CHECK_NONE_D = (1 << 1); +const VISIBILITY_CHECK_D_LESS = (1 << 2); +const VISIBILITY_CHECK_D_MORE = (1 << 3); + +const VISIBILITY_CHECK_ALL_H = (1 << 4); +const VISIBILITY_CHECK_NONE_H = (1 << 5); +const VISIBILITY_CHECK_H_LESS = (1 << 6); +const VISIBILITY_CHECK_H_MORE = (1 << 7); + +const VISIBILITY_CHECK_ALL_V = (1 << 8); +const VISIBILITY_CHECK_NONE_V = (1 << 9); +const VISIBILITY_CHECK_V_LESS = (1 << 10); +const VISIBILITY_CHECK_V_MORE = (1 << 11); + +const VISIBILITY_CHECK_ENVOLVES_D = (1 << 12); +const VISIBILITY_CHECK_ENVOLVES_H = (1 << 13); +const VISIBILITY_CHECK_ENVOLVES_V = (1 << 14); + +/** + * Data structure containing pre-initialized `View Frustum Culling` data. + * + * Will be used by the rest of `View Frustum Culling` related code. + */ +export class ViewFrustumCullingState { + + constructor() { + + /** + * The pre-computed AABB tree that will be used for efficient View Frustum Culling. + * + * @type {RBush3D} + * @private + */ + this._aabbTree = null; + + /** + * @type {Array<{mesh: object, clusterNumber: number}>} + * @private + */ + this._orderedMeshList = []; + + /** + * @type {Array} + * @private + */ + this._orderedEntityList = []; + + /** + * @private + */ + this._frustumProps = { + dirty: true, + wMultiply: 1.0, + hMultiply: 1.0, + }; + + /** + * @private + */ + this._cullFrame = 0; + + /** + * @type {boolean} + * @private + */ + this.finalized = false; + } + + /** + * + * @param {Array} entityList + * @param {Array} meshList + */ + initializeVfcState(entityList, meshList) { + if (this.finalized) { + throw "Already finalized"; + } + const clusterResult = clusterizeV2(entityList, meshList); + this._aabbTree = clusterResult.rTreeBasedAabbTree; + for (let i = 0, len = clusterResult.orderedClusteredIndexes.length; i < len; i++) { + const entityIndex = clusterResult.orderedClusteredIndexes[i]; + const clusterNumber = clusterResult.entityIdToClusterIdMapping[entityIndex]; + const entity = entityList[entityIndex]; + const newMeshIds = []; + for (let j = 0, len2 = entity.meshIds.length; j < len2; j++) { + const meshIndex = entity.meshIds[j]; + meshList[meshIndex].id = this._orderedMeshList.length; + newMeshIds.push(this._orderedMeshList.length); + this._orderedMeshList.push({clusterNumber: clusterNumber, mesh: meshList[meshIndex]}); + } + entity.meshIds = newMeshIds; + this._orderedEntityList.push(entity); + } + for (let i = 0, len = clusterResult.instancedIndexes.length; i < len; i++) { + const entityIndex = clusterResult.instancedIndexes[i]; + let entity = entityList[entityIndex]; + const newMeshIds = []; + for (let j = 0, len2 = entity.meshIds.length; j < len2; j++) { + const meshIndex = entity.meshIds[j]; + meshList[meshIndex].id = this._orderedMeshList.length; + newMeshIds.push(this._orderedMeshList.length); + this._orderedMeshList.push({clusterNumber: 99999, mesh: meshList[meshIndex]}); + } + entity.meshIds = newMeshIds; + this._orderedEntityList.push(entity); + } + } + + /** + * @param {ViewFrustumCullingModel} model + * @param {*} fnForceFinalizeLayer + */ + finalize(model, fnForceFinalizeLayer) { + if (this.finalized) { + throw "Already finalized"; + } + let lastClusterNumber = -1; + for (let i = 0, len = this._orderedMeshList.length; i < len; i++) { + const {clusterNumber, mesh} = this._orderedMeshList [i]; + if (lastClusterNumber !== -1 && lastClusterNumber !== clusterNumber) { + fnForceFinalizeLayer.call(model); + } + model.createMesh(mesh); + lastClusterNumber = clusterNumber; + } + // fnForceFinalizeLayer (); + for (let i = 0, len = this._orderedEntityList.length; i < len; i++) { + model.createEntity(this._orderedEntityList[i]) + } + // Free memory + this._orderedMeshList = []; + this._orderedEntityList = []; + this.finalized = true; + } + + /** + * @param {ViewFrustumCullingModel} model + */ + applyViewFrustumCulling(model) { + if (!this.finalized) { + throw "Not finalized"; + } + if (!this._aabbTree) { + return; + } + if (!this._canvasElement) { + /** + * @type {HTMLCanvasElement} + * @private + */ + this._canvasElement = model.scene.canvas.canvas; + } + if (!this._camera) { + this._camera = model.scene.camera; + } + this._ensureFrustumPropsUpdated(model); + this._initializeCullingDataIfNeeded(model); + const visibleNodes = this._searchVisibleNodesWithFrustumCulling(); + // console.log (`visibleNodes: ${visibleNodes.length} / ${this._internalNodesList.length}`); + this._cullFrame++; + this._markVisibleFrameOfVisibleNodes(visibleNodes, this._cullFrame); + this._cullNonVisibleNodes(model, this._cullFrame); + + // console.log (`${numIntersectionChecks} intersection checks`); + } + + _initializeCullingDataIfNeeded(model) { + if (this._internalNodesList) { + return; + } + if (!this._aabbTree) { + return; + } + const allAabbNodes = this._aabbTree.all(); + let maxEntityId = 0; + allAabbNodes.forEach(aabbbNode => { + maxEntityId = Math.max(maxEntityId, aabbbNode.entity.id) + }); + const internalNodesList = new Array(maxEntityId + 1); + allAabbNodes.forEach(aabbbNode => { + internalNodesList [aabbbNode.entity.id] = model._nodes[aabbbNode.entity.xeokitId]; + }); + + /** + * @type {Array} + * @private + */ + this._internalNodesList = internalNodesList; + this._lastVisibleFrameOfNodes = new Array(internalNodesList.length); + this._lastVisibleFrameOfNodes.fill(0); + } + + _searchVisibleNodesWithFrustumCulling() { + return this._aabbTree.searchCustom((bbox, isLeaf) => this._aabbIntersectsCameraFrustum(bbox, isLeaf), (bbox) => this._aabbContainedInCameraFrustum(bbox)) + } + + _markVisibleFrameOfVisibleNodes(visibleNodes, cullFrame) { + const lastVisibleFrameOfNodes = this._lastVisibleFrameOfNodes; + for (let i = 0, len = visibleNodes.length; i < len; i++) { + lastVisibleFrameOfNodes [visibleNodes[i].entity.id] = cullFrame; + } + } + + _cullNonVisibleNodes(model, cullFrame) { + const internalNodesList = this._internalNodesList; + const lastVisibleFrameOfNodes = this._lastVisibleFrameOfNodes; + for (let i = 0, len = internalNodesList.length; i < len; i++) { + if (internalNodesList[i]) { + internalNodesList[i].culledVFC = lastVisibleFrameOfNodes[i] !== cullFrame; + } + } + } + + /** + * Returns all 8 coordinates of an AABB. + * + * @param {Array} bbox An AABB + * + * @private + */ + _getPointsForBBox(bbox) { + const points = []; + for (let i = 0; i < 8; i++) { + points.push([(i & 1) ? bbox.maxX : bbox.minX, (i & 2) ? bbox.maxY : bbox.minY, (i & 4) ? bbox.maxZ : bbox.minZ,]); + } + return points; + } + + /** + * @param {*} bbox + * @param {*} isLeaf + * @returns + * + * @private + */ + _aabbIntersectsCameraFrustum(bbox, isLeaf) { + if (isLeaf) { + return true; + } + if (this._camera.projection === "ortho") { // TODO: manage ortho views + this._frustumProps.dirty = false; + return true; + } + // numIntersectionChecks++; + const check = this._aabbIntersectsCameraFrustum_internal(bbox); + const interD = !(check & VISIBILITY_CHECK_ALL_D) && !(check & VISIBILITY_CHECK_NONE_D); + const interH = !(check & VISIBILITY_CHECK_ALL_H) && !(check & VISIBILITY_CHECK_NONE_H); + const interV = !(check & VISIBILITY_CHECK_ALL_V) && !(check & VISIBILITY_CHECK_NONE_V); + if (((check & VISIBILITY_CHECK_ENVOLVES_D) || interD || (check & VISIBILITY_CHECK_ALL_D)) && + ((check & VISIBILITY_CHECK_ENVOLVES_H) || interH || (check & VISIBILITY_CHECK_ALL_H)) && + ((check & VISIBILITY_CHECK_ENVOLVES_V) || interV || (check & VISIBILITY_CHECK_ALL_V))) { + return true; + } + return false; + } + + /** + * @param {*} bbox + * @returns + * + * @private + */ + _aabbContainedInCameraFrustum(bbox) { + if (this._camera.projection === "ortho") { // TODO: manage ortho views + this._frustumProps.dirty = false; + return true; + } + const check = bbox._check; + return (check & VISIBILITY_CHECK_ALL_D) && (check & VISIBILITY_CHECK_ALL_H) && (check & VISIBILITY_CHECK_ALL_V); + } + + /** + * @param {ViewFrustumCullingModel} model + * + * @private + */ + _ensureFrustumPropsUpdated(model) { + const min = Math.min(this._canvasElement.width, this._canvasElement.height); // Assuming "min" for fovAxis + this._frustumProps.wMultiply = this._canvasElement.width / min; + this._frustumProps.hMultiply = this._canvasElement.height / min; + const aspect = this._canvasElement.width / this._canvasElement.height; + let fov = this._camera.perspective.fov; + if (aspect < 1) { + fov = fov / aspect; + } + fov = Math.min(fov, 120); + this._frustumProps.fov = fov; + // if (!this._frustumProps.dirty) + // { + // return; + // } + // Adjust camera eye/look to take into account the `model.worldMatrix`: + // - the entities' AABBs don't take it into account + // - and they can't, since `model.worldMatrix` is dynamic + // So, instead of transformating the positions of the r*tree's AABBs, + // apply the inverse transform to the camera eye/look, since the culling + // result is equivalent. + const invWorldMatrix = math.inverseMat4(model.worldMatrix, math.mat4()); + const modelCamEye = math.transformVec3(this._camera.eye, invWorldMatrix, [0, 0, 0]); + const modelCamLook = math.transformVec3(this._camera.look, invWorldMatrix, [0, 0, 0]); + this._frustumProps.forward = math.normalizeVec3(math.subVec3(modelCamLook, modelCamEye, [0, 0, 0]), [0, 0, 0]); + this._frustumProps.up = math.normalizeVec3(this._camera.up, [0, 0, 0]); + this._frustumProps.right = math.normalizeVec3(math.cross3Vec3(this._frustumProps.forward, this._frustumProps.up, [0, 0, 0]), [0, 0, 0]); + this._frustumProps.eye = modelCamEye.slice(); + this._frustumProps.CAM_FACTOR_1 = this._frustumProps.fov / 2 * this._frustumProps.wMultiply / _180_DIV_MATH_PI; + this._frustumProps.CAM_FACTOR_2 = this._frustumProps.fov / 2 * this._frustumProps.hMultiply / _180_DIV_MATH_PI; + // this._frustumProps.dirty = false; + } + + /** + * @param {*} bbox + * @returns + * + * @private + */ + _aabbIntersectsCameraFrustum_internal(bbox) { + const bboxPoints = bbox._points || this._getPointsForBBox(bbox); + bbox._points = bboxPoints; + let retVal = + VISIBILITY_CHECK_ALL_D | VISIBILITY_CHECK_NONE_D | + VISIBILITY_CHECK_ALL_H | VISIBILITY_CHECK_NONE_H | + VISIBILITY_CHECK_ALL_V | VISIBILITY_CHECK_NONE_V; + for (let i = 0, len = bboxPoints.length; i < len; i++) { + // if ((!(retVal & VISIBILITY_CHECK_ALL_D) && !(retVal & VISIBILITY_CHECK_NONE_D)) || + // (!(retVal & VISIBILITY_CHECK_ALL_H) && !(retVal & VISIBILITY_CHECK_NONE_H)) || + // (!(retVal & VISIBILITY_CHECK_ALL_V) && !(retVal & VISIBILITY_CHECK_NONE_V))) + // { + // break; + // } + const bboxPoint = bboxPoints [i]; + const pointRelToCam = tempVec3; + pointRelToCam[0] = bboxPoint[0] - this._frustumProps.eye[0]; + pointRelToCam[1] = bboxPoint[1] - this._frustumProps.eye[1]; + pointRelToCam[2] = bboxPoint[2] - this._frustumProps.eye[2]; + const forwardComponent = math.dotVec3(pointRelToCam, this._frustumProps.forward); + if (forwardComponent < 0) { + retVal |= VISIBILITY_CHECK_D_LESS; + retVal &= ~VISIBILITY_CHECK_ALL_D; + } else { + retVal |= VISIBILITY_CHECK_D_MORE; + retVal &= ~VISIBILITY_CHECK_NONE_D; + } + const rightComponent = math.dotVec3(pointRelToCam, this._frustumProps.right); + const rightAngle = fastAtan2(rightComponent, forwardComponent); + if (Math.abs(rightAngle) > this._frustumProps.CAM_FACTOR_1) { + if (rightAngle < 0) { + retVal |= VISIBILITY_CHECK_H_LESS; + } else { + retVal |= VISIBILITY_CHECK_H_MORE; + } + retVal &= ~VISIBILITY_CHECK_ALL_H; + } else { + retVal &= ~VISIBILITY_CHECK_NONE_H; + } + const upComponent = math.dotVec3(pointRelToCam, this._frustumProps.up); + const upAngle = fastAtan2(upComponent, forwardComponent); + if (Math.abs(upAngle) > this._frustumProps.CAM_FACTOR_2) { + if (upAngle < 0) { + retVal |= VISIBILITY_CHECK_V_LESS; + } else { + retVal |= VISIBILITY_CHECK_V_MORE; + } + retVal &= ~VISIBILITY_CHECK_ALL_V; + } else { + retVal &= ~VISIBILITY_CHECK_NONE_V; + } + } + if ((retVal & VISIBILITY_CHECK_D_LESS) && (retVal & VISIBILITY_CHECK_D_MORE)) { + retVal |= VISIBILITY_CHECK_ENVOLVES_D; + } + if ((retVal & VISIBILITY_CHECK_H_LESS) && (retVal & VISIBILITY_CHECK_H_MORE)) { + retVal |= VISIBILITY_CHECK_ENVOLVES_H; + } + if ((retVal & VISIBILITY_CHECK_V_LESS) && (retVal & VISIBILITY_CHECK_V_MORE)) { + retVal |= VISIBILITY_CHECK_ENVOLVES_V; + } + bbox._check = retVal; + return retVal; + } +} \ No newline at end of file diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/cluster-helper.js b/src/viewer/scene/frustumCulling/cluster-helper.js similarity index 69% rename from src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/cluster-helper.js rename to src/viewer/scene/frustumCulling/cluster-helper.js index 730d73a96..d5ac79fec 100644 --- a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/cluster-helper.js +++ b/src/viewer/scene/frustumCulling/cluster-helper.js @@ -3,50 +3,13 @@ * @license MIT */ -import {math} from "../../../../../math/math.js"; -import {geometryCompressionUtils} from "../../../../../math/geometryCompressionUtils.js"; +import {math} from "../math/math.js"; +import {geometryCompressionUtils} from "../math/geometryCompressionUtils.js"; import {RBush3D} from "./rbush3d.js"; import {makeClusters} from "./xeokit-cluster.js" -function generateAABB (aabbsForIndexes) { - const aabbsToLoad = []; - - for (let i = 0, len = aabbsForIndexes.length; i < len; i++) { - const item = aabbsForIndexes [i]; - - if (!item) { - continue; - } - - aabbsToLoad.push ({ - minX: item.aabb [0], - minY: item.aabb [1], - minZ: item.aabb [2], - maxX: item.aabb [3], - maxY: item.aabb [4], - maxZ: item.aabb [5], - entity: { - id: i, - xeokitId: item.entityId, - meshes: [ - { - numTriangles: item.numTriangles, - } - ] - }, - numTriangles: item.numTriangles, - }); - } - - const aabbTree = new RBush3D (4); - - aabbTree.load (aabbsToLoad); - - return aabbTree; -} - -function clusterizeV2 (entities, meshes) { +export function clusterizeV2(entities, meshes) { // const meshesById = {}; // meshes.forEach(mesh => meshesById[mesh.id] = mesh); @@ -57,39 +20,37 @@ function clusterizeV2 (entities, meshes) { // const entityMeshes = inflatedData.entityMeshes; // const entityMeshIds = inflatedData.entityMeshIds; // const entityUsesInstancing = inflatedData.entityUsesInstancing; - + // const numMeshes = meshPositions.length; // const numEntities = entityMeshes.length; const numMeshes = meshes.length; const numEntities = entities.length; + const aabbsForIndexes = []; + const instancedIndexes = []; - const _aabbsForIndexes = []; - const _instancedIndexes = []; - - function entityUsesInstancing (entity) { + const entityUsesInstancing = (entity) => { for (let i = 0, len = entity.meshIds.length; i < len; i++) { if (meshes[entity.meshIds[i]].geometryId) { return true; } } - return false; - }; + } for (let i = 0; i < numEntities; i++) { const entity = entities[i]; // TODO - if (entityUsesInstancing (entity)) { - _instancedIndexes.push (i); + if (entityUsesInstancing(entity)) { + instancedIndexes.push(i); continue; } const aabbEntity = math.collapseAABB3(); let numTriangles = 0 - entity.meshIds.forEach (meshId => { + entity.meshIds.forEach(meshId => { const mesh = meshes[meshId]; const bounds = geometryCompressionUtils.getPositionsBounds(mesh.positions); @@ -108,40 +69,64 @@ function clusterizeV2 (entities, meshes) { math.expandAABB3Point3(aabbEntity, min); math.expandAABB3Point3(aabbEntity, max); - numTriangles += Math.round (mesh.indices.length / 3); + numTriangles += Math.round(mesh.indices.length / 3); }); - _aabbsForIndexes [i] = { + aabbsForIndexes [i] = { aabb: aabbEntity, numTriangles, entityId: entity.id, }; } - let _orderedEntityIds = []; - let _entityIdToClusterIdMapping = {}; + let orderedEntityIds = []; + let entityIdToClusterIdMapping = {}; let rTreeBasedAabbTree; - if (Object.keys(_aabbsForIndexes).length > 0) - { - rTreeBasedAabbTree = generateAABB (_aabbsForIndexes); - - let generateClustersResult = makeClusters ({ + if (Object.keys(aabbsForIndexes).length > 0) { + rTreeBasedAabbTree = generateAABB(aabbsForIndexes); + let generateClustersResult = makeClusters({ aabbTree: rTreeBasedAabbTree, }); - - _orderedEntityIds = generateClustersResult.orderedEntityIds; - _entityIdToClusterIdMapping = generateClustersResult.clusteringResult.entityIdToClusterIdMapping; + orderedEntityIds = generateClustersResult.orderedEntityIds; + entityIdToClusterIdMapping = generateClustersResult.clusteringResult.entityIdToClusterIdMapping; } - // console.log (generateClustersResult); - - return { - orderedClusteredIndexes: _orderedEntityIds, - entityIdToClusterIdMapping: _entityIdToClusterIdMapping, - instancedIndexes: _instancedIndexes, + return { + orderedClusteredIndexes: orderedEntityIds, + entityIdToClusterIdMapping: entityIdToClusterIdMapping, + instancedIndexes: instancedIndexes, rTreeBasedAabbTree }; } -export {clusterizeV2} \ No newline at end of file +function generateAABB(aabbsForIndexes) { + const aabbsToLoad = []; + for (let i = 0, len = aabbsForIndexes.length; i < len; i++) { + const item = aabbsForIndexes [i]; + if (!item) { + continue; + } + aabbsToLoad.push({ + minX: item.aabb [0], + minY: item.aabb [1], + minZ: item.aabb [2], + maxX: item.aabb [3], + maxY: item.aabb [4], + maxZ: item.aabb [5], + entity: { + id: i, + xeokitId: item.entityId, + meshes: [ + { + numTriangles: item.numTriangles, + } + ] + }, + numTriangles: item.numTriangles, + }); + } + const aabbTree = new RBush3D(4); + aabbTree.load(aabbsToLoad); + return aabbTree; +} diff --git a/src/viewer/scene/frustumCulling/rbush3d.js b/src/viewer/scene/frustumCulling/rbush3d.js new file mode 100644 index 000000000..c744554f9 --- /dev/null +++ b/src/viewer/scene/frustumCulling/rbush3d.js @@ -0,0 +1,848 @@ +var module = {}; +var exports = {}; + +(function (f) { + if (typeof exports === "object" && typeof module !== "undefined") { + module.exports = f() + } else if (typeof define === "function" && define.amd) { + define([], f) + } else { + var g; + if (typeof window !== "undefined") { + g = window + } else if (typeof global !== "undefined") { + g = global + } else if (typeof self !== "undefined") { + g = self + } else { + g = this + } + g.RBush3D = f() + } +})(function () { + var define, module, exports; + return (function () { + function r(e, n, t) { + function o(i, f) { + if (!n[i]) { + if (!e[i]) { + var c = "function" == typeof require && require; + if (!f && c) return c(i, !0); + if (u) return u(i, !0); + var a = new Error("Cannot find module '" + i + "'"); + throw a.code = "MODULE_NOT_FOUND", a + } + var p = n[i] = {exports: {}}; + e[i][0].call(p.exports, function (r) { + var n = e[i][1][r]; + return o(n || r) + }, p, p.exports, r, e, n, t) + } + return n[i].exports + } + + for (var u = "function" == typeof require && require, i = 0; i < t.length; i++) o(t[i]); + return o + } + + return r + })()({ + 1: [function (require, module, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var quickselect = require('quickselect'); + var nodePool = []; + var freeNode = function (node) { + return nodePool.push(node); + }; + var freeAllNode = function (node) { + if (node) { + freeNode(node); + if (!isLeaf(node)) { + node.children.forEach(freeAllNode); + } + } + }; + var allowNode = function (children) { + var node = nodePool.pop(); + if (node) { + node.children = children; + node.height = 1; + node.leaf = true; + node.minX = Infinity; + node.minY = Infinity; + node.minZ = Infinity; + node.maxX = -Infinity; + node.maxY = -Infinity; + node.maxZ = -Infinity; + } else { + node = { + children: children, + height: 1, + leaf: true, + minX: Infinity, + minY: Infinity, + minZ: Infinity, + maxX: -Infinity, + maxY: -Infinity, + maxZ: -Infinity, + }; + } + return node; + }; + var distNodePool = []; + var freeDistNode = function (node) { + return distNodePool.push(node); + }; + var allowDistNode = function (dist, node) { + var heapNode = distNodePool.pop(); + if (heapNode) { + heapNode.dist = dist; + heapNode.node = node; + } else { + heapNode = {dist: dist, node: node}; + } + return heapNode; + }; + var isLeaf = function (node) { + return node.leaf; + }; + var isLeafChild = function (node, child) { + return node.leaf; + }; + var findItem = function (item, items, equalsFn) { + if (!equalsFn) + return items.indexOf(item); + for (var i = 0; i < items.length; i++) { + if (equalsFn(item, items[i])) + return i; + } + return -1; + }; + var calcBBox = function (node) { + distBBox(node, 0, node.children.length, node); + }; + var distBBox = function (node, k, p, destNode) { + var dNode = destNode; + if (dNode) { + dNode.minX = Infinity; + dNode.minY = Infinity; + dNode.minZ = Infinity; + dNode.maxX = -Infinity; + dNode.maxY = -Infinity; + dNode.maxZ = -Infinity; + } else { + dNode = allowNode([]); + } + for (var i = k, child = void 0; i < p; i++) { + child = node.children[i]; + extend(dNode, child); + } + return dNode; + }; + var extend = function (a, b) { + a.minX = Math.min(a.minX, b.minX); + a.minY = Math.min(a.minY, b.minY); + a.minZ = Math.min(a.minZ, b.minZ); + a.maxX = Math.max(a.maxX, b.maxX); + a.maxY = Math.max(a.maxY, b.maxY); + a.maxZ = Math.max(a.maxZ, b.maxZ); + return a; + }; + var bboxVolume = function (a) { + return (a.maxX - a.minX) * + (a.maxY - a.minY) * + (a.maxZ - a.minZ); + }; + var bboxMargin = function (a) { + return (a.maxX - a.minX) + + (a.maxY - a.minY) + + (a.maxZ - a.minZ); + }; + var enlargedVolume = function (a, b) { + var minX = Math.min(a.minX, b.minX), minY = Math.min(a.minY, b.minY), minZ = Math.min(a.minZ, b.minZ), + maxX = Math.max(a.maxX, b.maxX), maxY = Math.max(a.maxY, b.maxY), maxZ = Math.max(a.maxZ, b.maxZ); + return (maxX - minX) * + (maxY - minY) * + (maxZ - minZ); + }; + var intersectionVolume = function (a, b) { + var minX = Math.max(a.minX, b.minX), minY = Math.max(a.minY, b.minY), minZ = Math.max(a.minZ, b.minZ), + maxX = Math.min(a.maxX, b.maxX), maxY = Math.min(a.maxY, b.maxY), maxZ = Math.min(a.maxZ, b.maxZ); + return Math.max(0, maxX - minX) * + Math.max(0, maxY - minY) * + Math.max(0, maxZ - minZ); + }; + var contains = function (a, b) { + return a.minX <= b.minX && + a.minY <= b.minY && + a.minZ <= b.minZ && + b.maxX <= a.maxX && + b.maxY <= a.maxY && + b.maxZ <= a.maxZ; + }; + exports.intersects = function (a, b) { + return b.minX <= a.maxX && + b.minY <= a.maxY && + b.minZ <= a.maxZ && + b.maxX >= a.minX && + b.maxY >= a.minY && + b.maxZ >= a.minZ; + }; + exports.boxRayIntersects = function (box, ox, oy, oz, idx, idy, idz) { + var tx0 = (box.minX - ox) * idx; + var tx1 = (box.maxX - ox) * idx; + var ty0 = (box.minY - oy) * idy; + var ty1 = (box.maxY - oy) * idy; + var tz0 = (box.minZ - oz) * idz; + var tz1 = (box.maxZ - oz) * idz; + var z0 = Math.min(tz0, tz1); + var z1 = Math.max(tz0, tz1); + var y0 = Math.min(ty0, ty1); + var y1 = Math.max(ty0, ty1); + var x0 = Math.min(tx0, tx1); + var x1 = Math.max(tx0, tx1); + var tmin = Math.max(0, x0, y0, z0); + var tmax = Math.min(x1, y1, z1); + return tmax >= tmin ? tmin : Infinity; + }; + var multiSelect = function (arr, left, right, n, compare) { + var stack = [left, right]; + var mid; + while (stack.length) { + right = stack.pop(); + left = stack.pop(); + if (right - left <= n) + continue; + mid = left + Math.ceil((right - left) / n / 2) * n; + quickselect(arr, mid, left, right, compare); + stack.push(left, mid, mid, right); + } + }; + var compareMinX = function (a, b) { + return a.minX - b.minX; + }; + var compareMinY = function (a, b) { + return a.minY - b.minY; + }; + var compareMinZ = function (a, b) { + return a.minZ - b.minZ; + }; + var RBush3D = (function () { + function RBush3D(maxEntries) { + if (maxEntries === void 0) { + maxEntries = 16; + } + this.maxEntries = Math.max(maxEntries, 8); + this.minEntries = Math.max(4, Math.ceil(this.maxEntries * 0.4)); + this.clear(); + } + + RBush3D.alloc = function () { + return this.pool.pop() || new this(); + }; + RBush3D.free = function (rbush) { + rbush.clear(); + this.pool.push(rbush); + }; + // Start of chipmunk + RBush3D.prototype.searchCustom = function (customIntersects, customContains) { + var node = this.data; + var result = []; + if (!customIntersects(node, isLeafChild(node, child))) + return result; + var nodesToSearch = []; + while (node) { + for (var i = 0, len = node.children.length; i < len; i++) { + var child = node.children[i]; + if (customIntersects(child, isLeafChild(node, child))) { + if (isLeafChild(node, child)) + result.push(child); + else if (customContains(child)) + this._all(child, result); + else + nodesToSearch.push(child); + } + } + node = nodesToSearch.pop(); + } + return result; + }; + RBush3D.prototype.analyzeTriangles = function (node) { + if (node === undefined) { + node = this.data; + } + + var totalTriangles = 0; + + if (isLeaf(node)) { + for (var i = 0, len = node.children.length; i < len; i++) { + totalTriangles += node.children [i].numTriangles; + } + } else { + for (var i = 0, len = node.children.length; i < len; i++) { + totalTriangles += this.analyzeTriangles(node.children [i]); + } + } + + return node.totalTriangles = totalTriangles; + }; + RBush3D.prototype.groupNodesByNumTrianglesThreshold = function (numTrianglesThreshold, node) { + if (node === undefined) { + this.analyzeTriangles(); + node = this.data; + } + + var totalTriangles = 0; + + var items = []; + + for (var i = 0, len = node.children.length; i < len; i++) { + var child = node.children[i]; + items.push({ + item: child, + triangles: child.totalTriangles || child.numTriangles, + }); + } + + items.sort(function (a, b) { + return -(a.item.triangles - b.item.triangles); + }); + + var retVal = []; + var retValItem = []; + var retValItemTris = 0; + + var c = 0; + + for (var i = 0, len = items.length; i < len; i++) { + var item = items[i]; + + if ((retValItemTris + item.triangles) < numTrianglesThreshold) { + retValItem.push(item); + retValItemTris += item.triangles; + continue; + } + + if (retValItem.length) { + retVal.push(retValItem); + retValItem = []; + retValItemTris = 0; + } + + if ((retValItemTris + item.triangles) < numTrianglesThreshold) { + i--; + continue; + } + + if (isLeafChild(node, item.item)) { + retVal.push([ + item, + ]); + } else { + var tmp = this.groupNodesByNumTrianglesThreshold( + numTrianglesThreshold, + item.item + ); + + var tmp2 = []; + var accum2 = 0; + + for (var j = 0, len2 = tmp.length; j < len2 - 1; j++) { + retVal.push(tmp [i]); + } + + if (tmp.length > 1) { + tmp = tmp [tmp.length - 1]; + + for (var j = 0, len2 = tmp.length; j < len2; j++) { + accum2 = accum2 + tmp [j].triangles; + + if (accum2 < numTrianglesThreshold) { + tmp2.push(tmp[j]); + } else { + if (accum2 == 0) { + tmp2.push(tmp[j]); + } + + retVal.push(tmp2); + + accum2 = 0; + tmp2 = []; + } + } + + tmp2.forEach(function (subItem) { + retValItem.push(subItem); + retValItemTris += subItem.triangles; + }); + } + } + } + + if (retValItem.length) { + retVal.push(retValItem); + } + + return retVal; + } + // End of chipmunk + RBush3D.prototype.search = function (bbox) { + var node = this.data; + var result = []; + if (!exports.intersects(bbox, node)) + return result; + var nodesToSearch = []; + while (node) { + for (var i = 0, len = node.children.length; i < len; i++) { + var child = node.children[i]; + if (exports.intersects(bbox, child)) { + if (isLeafChild(node, child)) + result.push(child); + else if (contains(bbox, child)) + this._all(child, result); + else + nodesToSearch.push(child); + } + } + node = nodesToSearch.pop(); + } + return result; + }; + RBush3D.prototype.collides = function (bbox) { + var node = this.data; + if (!exports.intersects(bbox, node)) + return false; + var nodesToSearch = []; + while (node) { + for (var i = 0, len = node.children.length; i < len; i++) { + var child = node.children[i]; + if (exports.intersects(bbox, child)) { + if (isLeafChild(node, child) || contains(bbox, child)) + return true; + nodesToSearch.push(child); + } + } + node = nodesToSearch.pop(); + } + return false; + }; + RBush3D.prototype.raycastInv = function (ox, oy, oz, idx, idy, idz, maxLen) { + if (maxLen === void 0) { + maxLen = Infinity; + } + var node = this.data; + if (idx === Infinity && idy === Infinity && idz === Infinity) + return allowDistNode(Infinity, undefined); + if (exports.boxRayIntersects(node, ox, oy, oz, idx, idy, idz) === Infinity) + return allowDistNode(Infinity, undefined); + var heap = [allowDistNode(0, node)]; + var swap = function (a, b) { + var t = heap[a]; + heap[a] = heap[b]; + heap[b] = t; + }; + var pop = function () { + var top = heap[0]; + var newLen = heap.length - 1; + heap[0] = heap[newLen]; + heap.length = newLen; + var idx = 0; + while (true) { + var left = (idx << 1) | 1; + if (left >= newLen) + break; + var right = left + 1; + if (right < newLen && heap[right].dist < heap[left].dist) { + left = right; + } + if (heap[idx].dist < heap[left].dist) + break; + swap(idx, left); + idx = left; + } + freeDistNode(top); + return top.node; + }; + var push = function (dist, node) { + var idx = heap.length; + heap.push(allowDistNode(dist, node)); + while (idx > 0) { + var p = (idx - 1) >> 1; + if (heap[p].dist <= heap[idx].dist) + break; + swap(idx, p); + idx = p; + } + }; + var dist = maxLen; + var result; + while (heap.length && heap[0].dist < dist) { + node = pop(); + for (var i = 0, len = node.children.length; i < len; i++) { + var child = node.children[i]; + var d = exports.boxRayIntersects(child, ox, oy, oz, idx, idy, idz); + if (!isLeafChild(node, child)) { + push(d, child); + } else if (d < dist) { + if (d === 0) { + return allowDistNode(d, child); + } + dist = d; + result = child; + } + } + } + return allowDistNode(dist < maxLen ? dist : Infinity, result); + }; + RBush3D.prototype.raycast = function (ox, oy, oz, dx, dy, dz, maxLen) { + if (maxLen === void 0) { + maxLen = Infinity; + } + return this.raycastInv(ox, oy, oz, 1 / dx, 1 / dy, 1 / dz, maxLen); + }; + RBush3D.prototype.all = function () { + return this._all(this.data, []); + }; + RBush3D.prototype.load = function (data) { + if (!(data && data.length)) + return this; + if (data.length < this.minEntries) { + for (var i = 0, len = data.length; i < len; i++) { + this.insert(data[i]); + } + return this; + } + var node = this.build(data.slice(), 0, data.length - 1, 0); + if (!this.data.children.length) { + this.data = node; + } else if (this.data.height === node.height) { + this.splitRoot(this.data, node); + } else { + if (this.data.height < node.height) { + var tmpNode = this.data; + this.data = node; + node = tmpNode; + } + this._insert(node, this.data.height - node.height - 1, true); + } + return this; + }; + RBush3D.prototype.insert = function (item) { + if (item) + this._insert(item, this.data.height - 1); + return this; + }; + RBush3D.prototype.clear = function () { + if (this.data) { + freeAllNode(this.data); + } + this.data = allowNode([]); + return this; + }; + RBush3D.prototype.remove = function (item, equalsFn) { + if (!item) + return this; + var node = this.data; + var i = 0; + var goingUp = false; + var index; + var parent; + var path = []; + var indexes = []; + while (node || path.length) { + if (!node) { + node = path.pop(); + i = indexes.pop(); + parent = path[path.length - 1]; + goingUp = true; + } + if (isLeaf(node)) { + index = findItem(item, node.children, equalsFn); + if (index !== -1) { + node.children.splice(index, 1); + path.push(node); + this.condense(path); + return this; + } + } + if (!goingUp && !isLeaf(node) && contains(node, item)) { + path.push(node); + indexes.push(i); + i = 0; + parent = node; + node = node.children[0]; + } else if (parent) { + i++; + node = parent.children[i]; + goingUp = false; + } else { + node = undefined; + } + } + return this; + }; + RBush3D.prototype.toJSON = function () { + return this.data; + }; + RBush3D.prototype.fromJSON = function (data) { + freeAllNode(this.data); + this.data = data; + return this; + }; + RBush3D.prototype.build = function (items, left, right, height) { + var N = right - left + 1; + var M = this.maxEntries; + var node; + if (N <= M) { + node = allowNode(items.slice(left, right + 1)); + calcBBox(node); + return node; + } + if (!height) { + height = Math.ceil(Math.log(N) / Math.log(M)); + M = Math.ceil(N / Math.pow(M, height - 1)); + } + node = allowNode([]); + node.leaf = false; + node.height = height; + var N3 = Math.ceil(N / M), N2 = N3 * Math.ceil(Math.pow(M, 2 / 3)), + N1 = N3 * Math.ceil(Math.pow(M, 1 / 3)); + multiSelect(items, left, right, N1, compareMinX); + for (var i = left; i <= right; i += N1) { + var right2 = Math.min(i + N1 - 1, right); + multiSelect(items, i, right2, N2, compareMinY); + for (var j = i; j <= right2; j += N2) { + var right3 = Math.min(j + N2 - 1, right2); + multiSelect(items, j, right3, N3, compareMinZ); + for (var k = j; k <= right3; k += N3) { + var right4 = Math.min(k + N3 - 1, right3); + node.children.push(this.build(items, k, right4, height - 1)); + } + } + } + calcBBox(node); + return node; + }; + RBush3D.prototype._all = function (node, result) { + var nodesToSearch = []; + while (node) { + if (isLeaf(node)) + result.push.apply(result, node.children); + else + nodesToSearch.push.apply(nodesToSearch, node.children); + node = nodesToSearch.pop(); + } + return result; + }; + RBush3D.prototype.chooseSubtree = function (bbox, node, level, path) { + var minVolume; + var minEnlargement; + var targetNode; + while (true) { + path.push(node); + if (isLeaf(node) || path.length - 1 === level) + break; + minVolume = minEnlargement = Infinity; + for (var i = 0, len = node.children.length; i < len; i++) { + var child = node.children[i]; + var volume = bboxVolume(child); + var enlargement = enlargedVolume(bbox, child) - volume; + if (enlargement < minEnlargement) { + minEnlargement = enlargement; + minVolume = volume < minVolume ? volume : minVolume; + targetNode = child; + } else if (enlargement === minEnlargement) { + if (volume < minVolume) { + minVolume = volume; + targetNode = child; + } + } + } + node = targetNode || node.children[0]; + } + return node; + }; + RBush3D.prototype.split = function (insertPath, level) { + var node = insertPath[level]; + var M = node.children.length; + var m = this.minEntries; + this.chooseSplitAxis(node, m, M); + var splitIndex = this.chooseSplitIndex(node, m, M); + var newNode = allowNode(node.children.splice(splitIndex, node.children.length - splitIndex)); + newNode.height = node.height; + newNode.leaf = node.leaf; + calcBBox(node); + calcBBox(newNode); + if (level) + insertPath[level - 1].children.push(newNode); + else + this.splitRoot(node, newNode); + }; + RBush3D.prototype.splitRoot = function (node, newNode) { + this.data = allowNode([node, newNode]); + this.data.height = node.height + 1; + this.data.leaf = false; + calcBBox(this.data); + }; + RBush3D.prototype.chooseSplitIndex = function (node, m, M) { + var minOverlap = Infinity; + var minVolume = Infinity; + var index; + for (var i = m; i <= M - m; i++) { + var bbox1 = distBBox(node, 0, i); + var bbox2 = distBBox(node, i, M); + var overlap = intersectionVolume(bbox1, bbox2); + var volume = bboxVolume(bbox1) + bboxVolume(bbox2); + if (overlap < minOverlap) { + minOverlap = overlap; + index = i; + minVolume = volume < minVolume ? volume : minVolume; + } else if (overlap === minOverlap) { + if (volume < minVolume) { + minVolume = volume; + index = i; + } + } + } + return index; + }; + RBush3D.prototype.chooseSplitAxis = function (node, m, M) { + var xMargin = this.allDistMargin(node, m, M, compareMinX); + var yMargin = this.allDistMargin(node, m, M, compareMinY); + var zMargin = this.allDistMargin(node, m, M, compareMinZ); + if (xMargin < yMargin && xMargin < zMargin) { + node.children.sort(compareMinX); + } else if (yMargin < xMargin && yMargin < zMargin) { + node.children.sort(compareMinY); + } + }; + RBush3D.prototype.allDistMargin = function (node, m, M, compare) { + node.children.sort(compare); + var leftBBox = distBBox(node, 0, m); + var rightBBox = distBBox(node, M - m, M); + var margin = bboxMargin(leftBBox) + bboxMargin(rightBBox); + for (var i = m; i < M - m; i++) { + var child = node.children[i]; + extend(leftBBox, child); + margin += bboxMargin(leftBBox); + } + for (var i = M - m - 1; i >= m; i--) { + var child = node.children[i]; + extend(rightBBox, child); + margin += bboxMargin(rightBBox); + } + return margin; + }; + RBush3D.prototype.adjustParentBBoxes = function (bbox, path, level) { + for (var i = level; i >= 0; i--) { + extend(path[i], bbox); + } + }; + RBush3D.prototype.condense = function (path) { + for (var i = path.length - 1, siblings = void 0; i >= 0; i--) { + if (path[i].children.length === 0) { + if (i > 0) { + siblings = path[i - 1].children; + siblings.splice(siblings.indexOf(path[i]), 1); + freeNode(path[i]); + } else { + this.clear(); + } + } else { + calcBBox(path[i]); + } + } + }; + RBush3D.prototype._insert = function (item, level, isNode) { + var insertPath = []; + var node = this.chooseSubtree(item, this.data, level, insertPath); + node.children.push(item); + extend(node, item); + while (level >= 0) { + if (insertPath[level].children.length > this.maxEntries) { + this.split(insertPath, level); + level--; + } else + break; + } + this.adjustParentBBoxes(item, insertPath, level); + }; + RBush3D.pool = []; + return RBush3D; + }()); + exports.RBush3D = RBush3D; + + }, {"quickselect": 2}], 2: [function (require, module, exports) { + (function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.quickselect = factory()); + }(this, (function () { + 'use strict'; + + function quickselect(arr, k, left, right, compare) { + quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare); + } + + function quickselectStep(arr, k, left, right, compare) { + + while (right > left) { + if (right - left > 600) { + var n = right - left + 1; + var m = k - left + 1; + var z = Math.log(n); + var s = 0.5 * Math.exp(2 * z / 3); + var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); + var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); + var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); + quickselectStep(arr, k, newLeft, newRight, compare); + } + + var t = arr[k]; + var i = left; + var j = right; + + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); + + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } + + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); + } + + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } + } + + function swap(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + function defaultCompare(a, b) { + return a < b ? -1 : a > b ? 1 : 0; + } + + return quickselect; + + }))); + + }, {}] + }, {}, [1])(1) +}); + +var tmp0 = module.exports.RBush3D; + +export {tmp0 as RBush3D}; diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/xeokit-cluster.js b/src/viewer/scene/frustumCulling/xeokit-cluster.js similarity index 100% rename from src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/xeokit-cluster.js rename to src/viewer/scene/frustumCulling/xeokit-cluster.js diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/LodCullingManager.js b/src/viewer/scene/lodCulling/LodCullingManager.js similarity index 68% rename from src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/LodCullingManager.js rename to src/viewer/scene/lodCulling/LodCullingManager.js index a96d06cf3..a25c515a0 100644 --- a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/LodCullingManager.js +++ b/src/viewer/scene/lodCulling/LodCullingManager.js @@ -1,8 +1,8 @@ -import { DataTextureSceneModel } from "../../../DataTextureSceneModel.js" - -// For JSDoc autocompletion -import { DataTextureSceneModelNode } from "../../DataTextureSceneModelNode.js" -import { Scene } from "../../../../../scene/Scene.js" +// import {DataTextureSceneModel} from "../../../DataTextureSceneModel.js" +// +// // For JSDoc autocompletion +// import {DataTextureSceneModelNode} from "../../DataTextureSceneModelNode.js" +// import {Scene} from "../scene/Scene.js" /** * Wheter the FPS tracker was already installed. @@ -11,22 +11,124 @@ let _attachedFPSTracker = false; /** * The list of ````LodCullingManager````'s subscribed to FPS tracking. - * + * * @type {Array } */ const _fpsTrackingManagers = []; +export class LodCullingManager { + /** + * @param {SceneModel} model + * @param {Array} lodLevels + * @param {number} targetFps + */ + constructor(model, lodLevels, targetFps) { + /** + * @type {SceneModel} + */ + this.model = model; + /** + * @private + */ + this.lodState = new LodState(lodLevels, targetFps); + // console.time("initializeLodState"); + this.lodState.initializeLodState(model); + // console.timeEnd("initializeLodState"); + attachFPSTracker(this.model.scene, this); + } + + /** + * Cull any objects belonging to the current `LOD` level, and increase the `LOD` level. + * + * @private + */ + _increaseLODLevelIndex() { + const lodState = this.lodState; + if (lodState.lodLevelIndex === lodState.triangleLODLevels.length) { + return false; + } + const nodesInLOD = lodState.nodesInLOD [lodState.triangleLODLevels[lodState.lodLevelIndex]] || []; + for (let i = 0, len = nodesInLOD.length; i < len; i++) { + nodesInLOD[i].culledLOD = true; + } + lodState.lodLevelIndex++; + return true; + } + + /** + * Un-cull any objects belonging to the current `LOD` level, and decrease the `LOD` level. + * + * @private + */ + _decreaseLODLevelIndex() { + const lodState = this.lodState; + if (lodState.lodLevelIndex === 0) { + return false; + } + const nodesInLOD = lodState.nodesInLOD [lodState.triangleLODLevels[lodState.lodLevelIndex - 1]] || []; + for (let i = 0, len = nodesInLOD.length; i < len; i++) { + nodesInLOD[i].culledLOD = false; + } + lodState.lodLevelIndex--; + return true; + } + + /** + * Apply LOD culling. + * + * Will update LOD level, if needed, based in... + * - current FPS + * - target FPS + * + * ... and then will cull/uncull the needed objects according to the LOD level. + * + * @param {number} currentFPS The current FPS (frames per second) + * @returns {boolean} Whether the LOD level was changed. This is, if some object was culled/unculled + */ + applyLodCulling(currentFPS) { + let lodState = this.lodState; + let retVal = false; + if (currentFPS < lodState.targetFps) { + if (++lodState.consecutiveFramesWithoutTargetFps > 0) { + lodState.consecutiveFramesWithoutTargetFps = 0; + retVal = this._increaseLODLevelIndex(); + } + } else if (currentFPS > (lodState.targetFps + 4)) { + if (++lodState.consecutiveFramesWithTargetFps > 1) { + lodState.consecutiveFramesWithTargetFps = 0; + retVal = this._decreaseLODLevelIndex(); + } + } + if (retVal) { + // console.log("LOD level = " + lodState.lodLevelIndex); + } + return retVal; + } + + resetLodCulling() { + let retVal = false; + let decreasedLevel = false; + do { + retVal |= (decreasedLevel = this._decreaseLODLevelIndex()); + } while (decreasedLevel); + if (retVal) { + //console.log("LOD reset"); + } + return retVal; + } +} + /** - * - * @param {Scene} scene + * + * @param {Scene} scene * @param {LodCullingManager} cullingManager */ -function attachFPSTracker (scene, cullingManager) { +function attachFPSTracker(scene, cullingManager) { if (!_attachedFPSTracker) { _attachedFPSTracker = true; const MAX_NUM_TICKS = 4; - let tickTimeArray = new Array (MAX_NUM_TICKS); + let tickTimeArray = new Array(MAX_NUM_TICKS); let numTick = 0; let currentFPS = -1; @@ -34,22 +136,17 @@ function attachFPSTracker (scene, cullingManager) { let preRenderTime = Date.now(); let deltaTime = 0; - // Apply LOD-culling before rendering the scene - scene.on("rendering", function () { - if (currentFPS == -1) - { + scene.on("rendering", () => { // Apply LOD-culling before rendering the scene + if (currentFPS === -1) { return; } - - // Call LOD-culling tasks - for (let i = 0, len = _fpsTrackingManagers.length; i < len; i++) - { - _fpsTrackingManagers[i].applyLodCulling (currentFPS); + for (let i = 0, len = _fpsTrackingManagers.length; i < len; i++) { + _fpsTrackingManagers[i].applyLodCulling(currentFPS); } }); // Once the scene has dispached the GL draw* commands, the rendering will - // happen in asynchornous mode. + // happen in asynchronous mode. // A way to measure the frame-rate, is the time that passes: // - since all render commands are sent to the GPU @@ -65,31 +162,29 @@ function attachFPSTracker (scene, cullingManager) { // This mechanism here is not ideal but at least makes sure to track the // frame-rate in such a way that is directly proportional to the time spent // drawing geometry on the GPU. And this makes the metric quite good for the - // prupose of the LOD mechanism! - scene.on("rendered", function () { - preRenderTime = Date.now (); + // purpose of the LOD mechanism! - window.requestAnimationFrame(function () { - numTick++; + scene.on("rendered", () => { + preRenderTime = Date.now(); + window.requestAnimationFrame(() => { + numTick++; const newTime = Date.now(); deltaTime = newTime - preRenderTime; - + preRenderTime = newTime; - + tickTimeArray[numTick % MAX_NUM_TICKS] = deltaTime; - + let sumTickTimes = 0; - - if (numTick > MAX_NUM_TICKS) - { - for (let i = 0; i < MAX_NUM_TICKS; i++) - { + + if (numTick > MAX_NUM_TICKS) { + for (let i = 0; i < MAX_NUM_TICKS; i++) { sumTickTimes += tickTimeArray[i]; } - + currentFPS = MAX_NUM_TICKS / sumTickTimes * 1000; - } + } }); }); @@ -98,44 +193,42 @@ function attachFPSTracker (scene, cullingManager) { { let sceneTick = 0; - let lastTickCameraMoved = sceneTick ; + let lastTickCameraMoved = sceneTick; - scene.camera.on ("matrix", function () { - lastTickCameraMoved = sceneTick ; + scene.camera.on("matrix", () => { + lastTickCameraMoved = sceneTick; }); - scene.on ("tick", function () { - if ((sceneTick - lastTickCameraMoved) > 3) - { - // Call LOD-culling tasks - for (let i = 0, len = _fpsTrackingManagers.length; i < len; i++) - { - _fpsTrackingManagers[i].resetLodCulling (); + scene.on("tick", () => { + if ((sceneTick - lastTickCameraMoved) > 3) { + for (let i = 0, len = _fpsTrackingManagers.length; i < len; i++) { // Call LOD-culling tasks + _fpsTrackingManagers[i].resetLodCulling(); } } - sceneTick++; }); } } - _fpsTrackingManagers.push (cullingManager); + _fpsTrackingManagers.push(cullingManager); } /** * Data structure containing pre-initialized `LOD` data. - * + * * Will be used by the rest of `LOD` related code. */ - class LodState { +class LodState { + /** * @param {Array} lodLevels The triangle counts for the LOD levels, for example ```[ 2000, 600, 150, 80, 20 ]``` * @param {number} targetFps The target FPS (_Frames Per Second_) for the dynamic culling of objects in the different LOD levels. */ - constructor (lodLevels, targetFps) { + constructor(lodLevels, targetFps) { + /** * An array ordered DESC with the number of triangles allowed in each LOD bucket. - * + * * @type {Array} */ this.triangleLODLevels = lodLevels; @@ -144,7 +237,7 @@ function attachFPSTracker (scene, cullingManager) { * A computed dictionary for `triangle-number-buckets` where: * - key: the number of triangles allowed for the objects in the bucket. * - value: all PerformanceNodes that have the number of triangles or more. - * + * * @type {Map>} */ this.nodesInLOD = {}; @@ -153,7 +246,7 @@ function attachFPSTracker (scene, cullingManager) { * A computed dictionary for `triangle-number-buckets` where: * - key: the number of triangles allowed for the objects in the bucket. * - value: the sum of triangles counts for all PeformanceNodes in the bucket. - * + * * @type {Map} */ this.triangleCountInLOD = {}; @@ -161,12 +254,12 @@ function attachFPSTracker (scene, cullingManager) { /** * The target FPS for the `LOD` mechanism: * - if real FPS are below this number, the next `LOD` level will be applied. - * + * * - if real FPS are... * - above this number plus a margin * - and for some consecutive frames * ... then the previous `LOD` level will be applied. - * + * * @type {number} */ this.targetFps = targetFps; @@ -178,32 +271,31 @@ function attachFPSTracker (scene, cullingManager) { /** * Current `LOD` level. Starts at 0. - * + * * @type {number} */ this.lodLevelIndex = 0; /** * Number of consecutive frames in current `LOD` level where FPS was above `targetFps` - * + * * @type {number} */ this.consecutiveFramesWithTargetFps = 0; /** * Number of consecutive frames in current `LOD` level where FPS was below `targetFps` - * + * * @type {number} */ this.consecutiveFramesWithoutTargetFps = 0; } /** - * @param {DataTextureSceneModel} model + * @param {SceneModel} model */ - initializeLodState (model) { - if (model._nodeList.length == 0) - { + initializeLodState(model) { + if (model._nodeList.length === 0) { return; } @@ -211,35 +303,30 @@ function attachFPSTracker (scene, cullingManager) { // const LOD_RESTORE_TIME = 600; // const LOD_TARGET_FPS = 20; const nodeList = model._nodeList; - + let nodesInLOD = {}; let triangleCountInLOD = {}; - for (let i = 0, len = nodeList.length; i < len; i++) - { + for (let i = 0, len = nodeList.length; i < len; i++) { const node = nodeList[i]; let lodLevel, len; - for (lodLevel = 0, len = this.triangleLODLevels.length; lodLevel < len; lodLevel++) - { - if (node.numTriangles >= this.triangleLODLevels [lodLevel]) - { + for (lodLevel = 0, len = this.triangleLODLevels.length; lodLevel < len; lodLevel++) { + if (node.numTriangles >= this.triangleLODLevels [lodLevel]) { break; } } var lodPolys = this.triangleLODLevels [lodLevel] || 0; - if (!(lodPolys in nodesInLOD)) - { + if (!(lodPolys in nodesInLOD)) { nodesInLOD [lodPolys] = []; } - nodesInLOD [lodPolys].push (node); + nodesInLOD [lodPolys].push(node); - if (!(lodPolys in triangleCountInLOD)) - { + if (!(lodPolys in triangleCountInLOD)) { triangleCountInLOD [lodPolys] = 0; } @@ -250,149 +337,3 @@ function attachFPSTracker (scene, cullingManager) { this.triangleCountInLOD = triangleCountInLOD; } } - -class LodCullingManager { - /** - * @param {DataTextureSceneModel} model - * @param {Array} lodLevels - * @param {number} targetFps - */ - constructor (model, lodLevels, targetFps) { - /** - * @type {DataTextureSceneModel} - */ - this.model = model; - - /** - * @private - */ - this.lodState = new LodState ( - lodLevels, - targetFps - ); - - console.time ("initializeLodState"); - - this.lodState.initializeLodState (model); - - console.timeEnd ("initializeLodState"); - - attachFPSTracker (this.model.scene, this); - } - - /** - * Cull any objects belonging to the current `LOD` level, and increase the `LOD` level. - * - * @private - */ - _increaseLODLevelIndex () - { - const lodState = this.lodState; - - if (lodState.lodLevelIndex == lodState.triangleLODLevels.length) - { - return false; - } - - const nodesInLOD = lodState.nodesInLOD [lodState.triangleLODLevels[lodState.lodLevelIndex]] || []; - - for (let i = 0, len = nodesInLOD.length; i < len; i++) - { - nodesInLOD[i].culledLOD = true; - } - - lodState.lodLevelIndex++; - - return true; - } - - /** - * Un-cull any objects belonging to the current `LOD` level, and decrease the `LOD` level. - * - * @private - */ - _decreaseLODLevelIndex () - { - const lodState = this.lodState; - - if (lodState.lodLevelIndex == 0) - { - return false; - } - - const nodesInLOD = lodState.nodesInLOD [lodState.triangleLODLevels[lodState.lodLevelIndex - 1]] || []; - - for (let i = 0, len = nodesInLOD.length; i < len; i++) - { - nodesInLOD[i].culledLOD = false; - } - - lodState.lodLevelIndex--; - - return true; - } - - /** - * Apply LOD culling. - * - * Will update LOD level, if needed, based in... - * - current FPS - * - target FPS - * - * ... and then will cull/uncull the needed objects according to the LOD level. - * - * @param {number} currentFPS The current FPS (frames per second) - * @returns {boolean} Whether the LOD level was changed. This is, if some object was culled/unculled - */ - applyLodCulling (currentFPS) - { - let lodState = this.lodState; - const model = this.model; - - let retVal = false; - - if (currentFPS < lodState.targetFps) - { - if (++lodState.consecutiveFramesWithoutTargetFps > 0) - { - lodState.consecutiveFramesWithoutTargetFps = 0; - retVal = this._increaseLODLevelIndex(); - } - } - else if (currentFPS > (lodState.targetFps + 4)) - { - if (++lodState.consecutiveFramesWithTargetFps > 1) - { - lodState.consecutiveFramesWithTargetFps = 0; - retVal = this._decreaseLODLevelIndex(); - } - } - - if (retVal) { - console.log ("LOD level = " + lodState.lodLevelIndex); - } - - return retVal; - } - - resetLodCulling () - { - const model = this.model; - - let retVal = false; - - let decreasedLevel = false; - - do { - retVal |= (decreasedLevel = this._decreaseLODLevelIndex()); - } while (decreasedLevel); - - if (retVal) { - console.log ("LOD resetted"); - } - - return retVal; - } -} - -export { LodCullingManager } \ No newline at end of file diff --git a/src/viewer/scene/models/DataTextureSceneModel/DataTextureSceneModel.js b/src/viewer/scene/models/DataTextureSceneModel/DataTextureSceneModel.js index cb56d23b7..df9891408 100644 --- a/src/viewer/scene/models/DataTextureSceneModel/DataTextureSceneModel.js +++ b/src/viewer/scene/models/DataTextureSceneModel/DataTextureSceneModel.js @@ -8,12 +8,14 @@ import {ENTITY_FLAGS} from './lib/ENTITY_FLAGS.js'; import {utils} from "../../utils.js"; import {RenderFlags} from "../../webgl/RenderFlags.js"; -import {LodCullingManager} from "./lib/layers/trianglesDataTexture/LodCullingManager.js"; -import {ViewFrustumCullingManager} from "./lib/layers/trianglesDataTexture/ViewFrustumCullingManager.js"; +// import {LodCullingManager} from "./lib/layers/trianglesDataTexture/LodCullingManager.js"; +//import {ViewFrustumCullingManager} from "./lib/layers/trianglesDataTexture/ViewFrustumCullingManager.js"; import {buildEdgeIndices} from "../../math/buildEdgeIndices"; import {rebucketPositions} from "./lib/layers/trianglesDataTexture/rebucketPositions"; import {uniquifyPositions} from "./lib/layers/trianglesDataTexture/calculateUniquePositions"; import {quantizePositions} from "./lib/compression"; +import {LodCullingManager} from "../../lodCulling/LodCullingManager"; +import {ViewFrustumCullingManager} from "../../frustumCulling/ViewFrustumCullingManager"; const tempVec3a = math.vec3(); const tempMat4 = math.mat4(); @@ -26,11 +28,913 @@ const defaultQuaternion = math.identityQuaternion(); /** * @desc A high-performance data-texture-based model representation for efficient rendering and low memory usage. * + * Data textures offer a highly efficient method for storing model representations on the GPU. They are much more efficient than the standard vertex buffer object (VBO) + * technique used in almost every other graphics library. + * + * By using data textures to store geometry and materials, a xeokit shader can randomly access data from anywhere within the texture. In contrast, with VBOs, shaders + * can only access the primitive they are currently rendering. This allows shaders to fetch contextual information about the primitive being rendered, enabling implementation of many useful algorithms on the GPU, such as LoD, visibility culling, and more. + * + * Data textures also allow us to use a lot of "pointer indirection" within the textures, combining and reusing duplicate geometry elements and materials. This dramatically + * minimizes the GPU memory footprint with large models. + * + * # Examples + * + * * [DataTextureSceneModel using geometry batching](/examples/#sceneRepresentation_DataTextureSceneModel_batching) + * * [DataTextureSceneModel using geometry batching and RTC coordinates](/examples/#sceneRepresentation_DataTextureSceneModel_batching_origin) + * * [DataTextureSceneModel using geometry instancing](/examples/#sceneRepresentation_DataTextureSceneModel_instancing) + * * [DataTextureSceneModel using geometry instancing and RTC coordinates](/examples/#sceneRepresentation_DataTextureSceneModel_instancing_origin) + * + *
+ * + * It should be noted that while the terms "batching" and "instancing" are used in the DataTextureModel API, their usage is primarily superficial. They are + * employed to ensure the (mostly) seamless implementation of the {@link SceneModel} interface, which is also implemented by {@link VBOSceneModel}. However, internally, the + * DataTextureModel adopts its own pointer-based reuse strategy to handle both geometry modes, as previously mentioned. + * + * # Overview + * + * While xeokit's standard [scene graph](https://github.com/xeokit/xeokit-sdk/wiki/Scene-Graphs) is great for gizmos and medium-sized models, it doesn't scale up to millions of objects in terms of memory and rendering efficiency. + * + * For huge models, we have the ````DataTextureSceneModel```` representation, which is optimized to pack large amounts of geometry into memory and render it efficiently using WebGL. + * + * ````DataTextureSceneModel```` is the default model representation loaded by (at least) {@link GLTFLoaderPlugin}, {@link XKTLoaderPlugin} and {@link WebIFCLoaderPlugin}. + * + * In this tutorial you'll learn how to use ````DataTextureSceneModel```` to create high-detail content programmatically. Ordinarily you'd be learning about ````DataTextureSceneModel```` if you were writing your own model loader plugins. + * + * # Contents + * + * - [DataTextureSceneModel](#performancemodel) + * - [GPU-Resident Geometry](#gpu-resident-geometry) + * - [Picking](#picking) + * - [Example 1: Geometry Instancing](#example-1--geometry-instancing) + * - [Finalizing a DataTextureSceneModel](#finalizing-a-performancemodel) + * - [Finding Entities](#finding-entities) + * - [Example 2: Geometry Batching](#example-2--geometry-batching) + * - [Classifying with Metadata](#classifying-with-metadata) + * - [Querying Metadata](#querying-metadata) + * - [Metadata Structure](#metadata-structure) + * - [RTC Coordinates](#rtc-coordinates-for-double-precision) + * - [Example 3: RTC Coordinates with Geometry Instancing](#example-2--rtc-coordinates-with-geometry-instancing) + * - [Example 4: RTC Coordinates with Geometry Batching](#example-2--rtc-coordinates-with-geometry-batching) + * + * ## DataTextureSceneModel + * + * ````DataTextureSceneModel```` uses two rendering techniques internally: + * + * 1. ***Geometry batching*** for unique geometries, combining those into a single WebGL geometry buffer, to render in one draw call, and + * 2. ***geometry instancing*** for geometries that are shared by multiple meshes, rendering all instances of each shared geometry in one draw call. + * + *
+ * These techniques come with certain limitations: + * + * * Non-realistic rendering - while scene graphs can use xeokit's full set of material workflows, ````DataTextureSceneModel```` uses simple Lambertian shading without textures. + * * Static transforms - transforms within a ````DataTextureSceneModel```` are static and cannot be dynamically translated, rotated and scaled the way {@link Node}s and {@link Mesh}es in scene graphs can. + * * Immutable model representation - while scene graph {@link Node}s and + * {@link Mesh}es can be dynamically plugged together, ````DataTextureSceneModel```` is immutable, + * since it packs its geometries into textures. + * + * ````DataTextureSceneModel````'s API allows us to exploit batching and instancing, while exposing its elements as + * abstract {@link Entity} types. + * + * {@link Entity} is the abstract base class for + * the various xeokit components that represent models, objects, or anonymous visible elements. An Entity has a unique ID and can be + * individually shown, hidden, selected, highlighted, ghosted, culled, picked and clipped, and has its own World-space boundary. + * + * * A ````DataTextureSceneModel```` is an {@link Entity} that represents a model. + * * A ````DataTextureSceneModel```` represents each of its objects with an {@link Entity}. + * * Each {@link Entity} has one or more meshes that define its shape. + * * Each mesh has either its own unique geometry, or shares a geometry with other meshes. + * + * ## GPU-Resident Geometry + * + * For a low memory footprint, ````DataTextureSceneModel```` stores its geometries in GPU memory only, compressed (quantized) as integers. Unfortunately, GPU-resident geometry is + * not readable by JavaScript. + * + * + * ## Example 1: Geometry Instancing + * + * In the example below, we'll use a ````DataTextureSceneModel```` + * to build a simple table model using geometry instancing. + * + * We'll start by adding a reusable box-shaped geometry to our ````DataTextureSceneModel````. + * + * Then, for each object in our model we'll add an {@link Entity} + * that has a mesh that instances our box geometry, transforming and coloring the instance. + * + * [![](http://xeokit.io/img/docs/sceneGraph.png)](/examples/#sceneRepresentation_DataTextureSceneModel_instancing) + * + * [[Run this example](/examples/#sceneRepresentation_DataTextureSceneModel_instancing)] + * + * ````javascript + * import {Viewer, DataTextureSceneModel} from "xeokit-sdk.es.js"; + * + * const viewer = new Viewer({ + * canvasId: "myCanvas", + * transparent: true + * }); + * + * viewer.scene.camera.eye = [-21.80, 4.01, 6.56]; + * viewer.scene.camera.look = [0, -5.75, 0]; + * viewer.scene.camera.up = [0.37, 0.91, -0.11]; + * + * // Build a DataTextureSceneModel representing a table + * // with four legs, using geometry instancing + * + * const dataTextureSceneModel = new DataTextureSceneModel(viewer.scene, { + * id: "table", + * isModel: true, // <--- Registers DataTextureSceneModel in viewer.scene.models + * position: [0, 0, 0], + * scale: [1, 1, 1], + * rotation: [0, 0, 0] + * }); + * + * // Create a reusable geometry within the DataTextureSceneModel + * // We'll instance this geometry by five meshes + * + * dataTextureSceneModel.createGeometry({ + * + * id: "myBoxGeometry", + * + * // The primitive type - allowed values are "points", "lines" and "triangles". + * // See the OpenGL/WebGL specification docs + * // for how the coordinate arrays are supposed to be laid out. + * primitive: "triangles", + * + * // The vertices - eight for our cube, each + * // one spanning three array elements for X,Y and Z + * positions: [ + * 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, // v0-v1-v2-v3 front + * 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, // v0-v3-v4-v1 right + * 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, // v0-v1-v6-v1 top + * -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, // v1-v6-v7-v2 left + * -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, // v7-v4-v3-v2 bottom + * 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1 // v4-v7-v6-v1 back + * ], + * + * // Normal vectors, one for each vertex + * normals: [ + * 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // v0-v1-v2-v3 front + * 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right + * 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // v0-v5-v6-v1 top + * -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // v1-v6-v7-v2 left + * 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, // v7-v4-v3-v2 bottom + * 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1 // v4-v7-v6-v5 back + * ], + * + * // Indices - these organise the positions and and normals + * // into geometric primitives in accordance with the "primitive" parameter, + * // in this case a set of three indices for each triangle. + * // + * // Note that each triangle is specified in counter-clockwise winding order. + * // + * indices: [ + * 0, 1, 2, 0, 2, 3, // front + * 4, 5, 6, 4, 6, 7, // right + * 8, 9, 10, 8, 10, 11, // top + * 12, 13, 14, 12, 14, 15, // left + * 16, 17, 18, 16, 18, 19, // bottom + * 20, 21, 22, 20, 22, 23 + * ] + * }); + * + * // Red table leg + * + * dataTextureSceneModel.createMesh({ + * id: "redLegMesh", + * geometryId: "myBoxGeometry", + * position: [-4, -6, -4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [1, 0.3, 0.3], + * origin: cfg.origin + * }); + * + * dataTextureSceneModel.createEntity({ + * id: "redLeg", + * meshIds: ["redLegMesh"], + * isObject: true // <---- Registers Entity by ID on viewer.scene.objects + * }); + * + * // Green table leg + * + * dataTextureSceneModel.createMesh({ + * id: "greenLegMesh", + * geometryId: "myBoxGeometry", + * position: [4, -6, -4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [0.3, 1.0, 0.3], + * origin: cfg.origin + * }); + * + * dataTextureSceneModel.createEntity({ + * id: "greenLeg", + * meshIds: ["greenLegMesh"], + * isObject: true // <---- Registers Entity by ID on viewer.scene.objects + * }); + * + * // Blue table leg + * + * dataTextureSceneModel.createMesh({ + * id: "blueLegMesh", + * geometryId: "myBoxGeometry", + * position: [4, -6, 4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [0.3, 0.3, 1.0], + * origin: cfg.origin + * }); + * + * dataTextureSceneModel.createEntity({ + * id: "blueLeg", + * meshIds: ["blueLegMesh"], + * isObject: true // <---- Registers Entity by ID on viewer.scene.objects + * }); + * + * // Yellow table leg + * + * dataTextureSceneModel.createMesh({ + * id: "yellowLegMesh", + * geometryId: "myBoxGeometry", + * position: [-4, -6, 4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [1.0, 1.0, 0.0], + * origin: cfg.origin + * }); + * + * dataTextureSceneModel.createEntity({ + * id: "yellowLeg", + * meshIds: ["yellowLegMesh"], + * isObject: true // <---- Registers Entity by ID on viewer.scene.objects + * }); + * + * // Purple table top + * + * dataTextureSceneModel.createMesh({ + * id: "purpleTableTopMesh", + * geometryId: "myBoxGeometry", + * position: [0, -3, 0], + * scale: [6, 0.5, 6], + * rotation: [0, 0, 0], + * color: [1.0, 0.3, 1.0], + * origin: cfg.origin + * }); + * + * dataTextureSceneModel.createEntity({ + * id: "purpleTableTop", + * meshIds: ["purpleTableTopMesh"], + * isObject: true // <---- Registers Entity by ID on viewer.scene.objects + * }); + * ```` + * + * ## Finalizing a DataTextureSceneModel + * + * Before we can view and interact with our ````DataTextureSceneModel````, we need to **finalize** it. Internally, this causes the ````DataTextureSceneModel```` to + * build the data textures that support our geometries and materials. + * + * Once finalized, we can't add anything more to our ````DataTextureSceneModel````. + * + * ```` javascript + * dataTextureSceneModel.finalize(); + * ```` + * + * ## Finding Entities + * + * As mentioned earlier, {@link Entity} is + * the abstract base class for components that represent models, objects, or just + * anonymous visible elements. + * + * Since we created configured our ````DataTextureSceneModel```` with ````isModel: true````, + * we're able to find it as an Entity by ID in ````viewer.scene.models````. Likewise, since + * we configured each of its Entities with ````isObject: true````, we're able to + * find them in ````viewer.scene.objects````. + * + * + * ````javascript + * // Get the whole table model Entity + * const table = viewer.scene.models["table"]; + * + * // Get some leg object Entities + * const redLeg = viewer.scene.objects["redLeg"]; + * const greenLeg = viewer.scene.objects["greenLeg"]; + * const blueLeg = viewer.scene.objects["blueLeg"]; + * ```` + * + * ## Example 2: Geometry Batching + * + * Let's once more use a ````DataTextureSceneModel```` + * to build the simple table model, this time using geometry batching. + * + * [![](http://xeokit.io/img/docs/sceneGraph.png)](/examples/#sceneRepresentation_DataTextureSceneModel_batching) + * + * * [[Run this example](/examples/#sceneRepresentation_DataTextureSceneModel_batching)] + * + * ````javascript + * import {Viewer, DataTextureSceneModel} from "xeokit-sdk.es.js"; + * + * const viewer = new Viewer({ + * canvasId: "myCanvas", + * transparent: true + * }); + * + * viewer.scene.camera.eye = [-21.80, 4.01, 6.56]; + * viewer.scene.camera.look = [0, -5.75, 0]; + * viewer.scene.camera.up = [0.37, 0.91, -0.11]; + * + * // Create a DataTextureSceneModel representing a table with four legs, using geometry batching + * const dataTextureSceneModel = new DataTextureSceneModel(viewer.scene, { + * id: "table", + * isModel: true, // <--- Registers DataTextureSceneModel in viewer.scene.models + * position: [0, 0, 0], + * scale: [1, 1, 1], + * rotation: [0, 0, 0] + * }); + * + * // Red table leg + * + * dataTextureSceneModel.createMesh({ + * id: "redLegMesh", + * + * // Geometry arrays are same as for the earlier batching example + * primitive: "triangles", + * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], + * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], + * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ], + * position: [-4, -6, -4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [1, 0.3, 0.3], + * origin: cfg.origin + * }); + * + * dataTextureSceneModel.createEntity({ + * id: "redLeg", + * meshIds: ["redLegMesh"], + * isObject: true // <---- Registers Entity by ID on viewer.scene.objects + * }); + * + * // Green table leg + * + * dataTextureSceneModel.createMesh({ + * id: "greenLegMesh", + * primitive: "triangles", + * primitive: "triangles", + * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], + * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], + * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ], + * position: [4, -6, -4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [0.3, 1.0, 0.3], + * origin: cfg.origin + * }); + * + * dataTextureSceneModel.createEntity({ + * id: "greenLeg", + * meshIds: ["greenLegMesh"], + * isObject: true // <---- Registers Entity by ID on viewer.scene.objects + * }); + * + * // Blue table leg + * + * dataTextureSceneModel.createMesh({ + * id: "blueLegMesh", + * primitive: "triangles", + * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], + * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], + * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ], + * position: [4, -6, 4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [0.3, 0.3, 1.0], + * origin: cfg.origin + * }); + * + * dataTextureSceneModel.createEntity({ + * id: "blueLeg", + * meshIds: ["blueLegMesh"], + * isObject: true // <---- Registers Entity by ID on viewer.scene.objects + * }); + * + * // Yellow table leg object + * + * dataTextureSceneModel.createMesh({ + * id: "yellowLegMesh", + * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], + * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], + * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ], + * position: [-4, -6, 4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [1.0, 1.0, 0.0], + * origin: cfg.origin + * }); + * + * dataTextureSceneModel.createEntity({ + * id: "yellowLeg", + * meshIds: ["yellowLegMesh"], + * isObject: true // <---- Registers Entity by ID on viewer.scene.objects + * }); + * + * // Purple table top + * + * dataTextureSceneModel.createMesh({ + * id: "purpleTableTopMesh", + * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], + * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], + * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ], + * position: [0, -3, 0], + * scale: [6, 0.5, 6], + * rotation: [0, 0, 0], + * color: [1.0, 0.3, 1.0], + * origin: cfg.origin + * }); + * + * dataTextureSceneModel.createEntity({ + * id: "purpleTableTop", + * meshIds: ["purpleTableTopMesh"], + * isObject: true // <---- Registers Entity by ID on viewer.scene.objects + * }); + * + * // Finalize the DataTextureSceneModel. + * + * dataTextureSceneModel.finalize(); + * + * // Find BigModelNodes by their model and object IDs + * + * // Get the whole table model + * const table = viewer.scene.models["table"]; + * + * // Get some leg objects + * const redLeg = viewer.scene.objects["redLeg"]; + * const greenLeg = viewer.scene.objects["greenLeg"]; + * const blueLeg = viewer.scene.objects["blueLeg"]; + * ```` + * + * ## Classifying with Metadata + * + * In the previous examples, we used ````DataTextureSceneModel```` to build + * two versions of the same table model, to demonstrate geometry batching and geometry instancing. + * + * We'll now classify our {@link Entity}s with metadata. This metadata + * will work the same for both our examples, since they create the exact same structure of {@link Entity}s + * to represent their models and objects. The abstract Entity type is, after all, intended to provide an abstract interface through which differently-implemented scene content can be accessed uniformly. + * + * To create the metadata, we'll create a {@link MetaModel} for our model, + * with a {@link MetaObject} for each of it's objects. The MetaModel and MetaObjects + * get the same IDs as the {@link Entity}s that represent their model and objects within our scene. + * + * ```` javascript + * const furnitureMetaModel = viewer.metaScene.createMetaModel("furniture", { // Creates a MetaModel in the MetaScene + * + * "projectId": "myTableProject", + * "revisionId": "V1.0", + * + * "metaObjects": [ + * { // Creates a MetaObject in the MetaModel + * "id": "table", + * "name": "Table", // Same ID as an object Entity + * "type": "furniture", // Arbitrary type, could be IFC type + * "properties": { // Arbitrary properties, could be IfcPropertySet + * "cost": "200" + * } + * }, + * { + * "id": "redLeg", + * "name": "Red table Leg", + * "type": "leg", + * "parent": "table", // References first MetaObject as parent + * "properties": { + * "material": "wood" + * } + * }, + * { + * "id": "greenLeg", // Node with corresponding id does not need to exist + * "name": "Green table leg", // and MetaObject does not need to exist for Node with an id + * "type": "leg", + * "parent": "table", + * "properties": { + * "material": "wood" + * } + * }, + * { + * "id": "blueLeg", + * "name": "Blue table leg", + * "type": "leg", + * "parent": "table", + * "properties": { + * "material": "wood" + * } + * }, + * { + * "id": "yellowLeg", + * "name": "Yellow table leg", + * "type": "leg", + * "parent": "table", + * "properties": { + * "material": "wood" + * } + * }, + * { + * "id": "tableTop", + * "name": "Purple table top", + * "type": "surface", + * "parent": "table", + * "properties": { + * "material": "formica", + * "width": "60", + * "depth": "60", + * "thickness": "5" + * } + * } + * ] + * }); + * ```` + * + * ## Querying Metadata + * + * Having created and classified our model (either the instancing or batching example), we can now find the {@link MetaModel} + * and {@link MetaObject}s using the IDs of their + * corresponding {@link Entity}s. + * + * ````JavaScript + * const furnitureMetaModel = scene.metaScene.metaModels["furniture"]; + * + * const redLegMetaObject = scene.metaScene.metaObjects["redLeg"]; + * ```` + * + * In the snippet below, we'll log metadata on each {@link Entity} we click on: + * + * ````JavaScript + * viewer.scene.input.on("mouseclicked", function (coords) { + * + * const hit = viewer.scene.pick({ + * canvasPos: coords + * }); + * + * if (hit) { + * const entity = hit.entity; + * const metaObject = viewer.metaScene.metaObjects[entity.id]; + * if (metaObject) { + * console.log(JSON.stringify(metaObject.getJSON(), null, "\t")); + * } + * } + * }); + * ```` + * + * ## Metadata Structure + * + * The {@link MetaModel} + * organizes its {@link MetaObject}s in + * a tree that describes their structural composition: + * + * ````JavaScript + * // Get metadata on the root object + * const tableMetaObject = furnitureMetaModel.rootMetaObject; + * + * // Get metadata on the leg objects + * const redLegMetaObject = tableMetaObject.children[0]; + * const greenLegMetaObject = tableMetaObject.children[1]; + * const blueLegMetaObject = tableMetaObject.children[2]; + * const yellowLegMetaObject = tableMetaObject.children[3]; + * ```` + * + * Given an {@link Entity}, we can find the object or model of which it is a part, or the objects that comprise it. We can also generate UI + * components from the metadata, such as the tree view demonstrated in [this demo](/examples/#BIMOffline_glTF_OTCConferenceCenter). + * + * This hierarchy allows us to express the hierarchical structure of a model while representing it in + * various ways in the 3D scene (such as with ````DataTextureSceneModel````, which + * has a non-hierarchical scene representation). + * + * Note also that a {@link MetaObject} does not need to have a corresponding + * {@link Entity} and vice-versa. + * + * # RTC Coordinates for Double Precision + * + * ````DataTextureSceneModel```` can emulate 64-bit precision on GPUs using relative-to-center (RTC) coordinates. + * + * Consider a model that contains many small objects, but with such large spatial extents that 32 bits of GPU precision (accurate to ~7 digits) will not be sufficient to render all of the the objects without jittering. + * + * To prevent jittering, we could spatially subdivide the objects into "tiles". Each tile would have a center position, and the positions of the objects within the tile would be relative to that center ("RTC coordinates"). + * + * While the center positions of the tiles would be 64-bit values, the object positions only need to be 32-bit. + * + * Internally, when rendering an object with RTC coordinates, xeokit first temporarily translates the camera viewing matrix by the object's tile's RTC center, on the CPU, using 64-bit math. + * + * Then xeokit loads the viewing matrix into its WebGL shaders, where math happens at 32-bit precision. Within the shaders, the matrix is effectively down-cast to 32-bit precision, and the object's 32-bit vertex positions are transformed by the matrix. + * + * We see no jittering, because with RTC a detectable loss of GPU accuracy only starts happening to objects as they become very distant from the camera viewpoint, at which point they are too small to be discernible anyway. + * + * ## RTC Coordinates with Geometry Instancing + * + * To use RTC with ````DataTextureSceneModel```` geometry instancing, we specify an RTC center for the geometry via its ````origin```` parameter. Then ````DataTextureSceneModel```` assumes that all meshes that instance that geometry are within the same RTC coordinate system, ie. the meshes ````position```` and ````rotation```` properties are assumed to be relative to the geometry's ````origin````. + * + * For simplicity, our example's meshes all instance the same geometry. Therefore, our example model has only one RTC center. + * + * Note that the axis-aligned World-space boundary (AABB) of our model is ````[ -6, -9, -6, 1000000006, -2.5, 1000000006]````. + * + * [![](http://xeokit.io/img/docs/sceneGraph.png)](/examples/#sceneRepresentation_DataTextureSceneModel_batching) + * + * * [[Run this example](/examples/#sceneRepresentation_DataTextureSceneModel_instancing_origin)] + * + * ````javascript + * const origin = [100000000, 0, 100000000]; + * + * dataTextureSceneModel.createGeometry({ + * id: "box", + * primitive: "triangles", + * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], + * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], + * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ], + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "leg1", + * geometryId: "box", + * position: [-4, -6, -4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [1, 0.3, 0.3], + * origin: origin + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["leg1"], + * isObject: true + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "leg2", + * geometryId: "box", + * position: [4, -6, -4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [0.3, 1.0, 0.3], + * origin: origin + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["leg2"], + * isObject: true + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "leg3", + * geometryId: "box", + * position: [4, -6, 4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [0.3, 0.3, 1.0], + * origin: origin + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["leg3"], + * isObject: true + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "leg4", + * geometryId: "box", + * position: [-4, -6, 4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [1.0, 1.0, 0.0], + * origin: origin + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["leg4"], + * isObject: true + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "top", + * geometryId: "box", + * position: [0, -3, 0], + * scale: [6, 0.5, 6], + * rotation: [0, 0, 0], + * color: [1.0, 0.3, 1.0], + * origin: origin + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["top"], + * isObject: true + * }); + * ```` + * + * ## RTC Coordinates with Geometry Batching + * + * To use RTC with ````DataTextureSceneModel```` geometry batching, we specify an RTC center (````origin````) for each mesh. For performance, we try to have as many meshes share the same value for ````origin```` as possible. Each mesh's ````positions````, ````position```` and ````rotation```` properties are assumed to be relative to ````origin````. + * + * For simplicity, the meshes in our example all share the same RTC center. + * + * The axis-aligned World-space boundary (AABB) of our model is ````[ -6, -9, -6, 1000000006, -2.5, 1000000006]````. + * + * [![](http://xeokit.io/img/docs/sceneGraph.png)](/examples/#sceneRepresentation_DataTextureSceneModel_batching) + * + * * [[Run this example](/examples/#sceneRepresentation_DataTextureSceneModel_batching_origin)] + * + * ````javascript + * const origin = [100000000, 0, 100000000]; + * + * dataTextureSceneModel.createMesh({ + * id: "leg1", + * origin: origin, // This mesh's positions and transforms are relative to the RTC center + * primitive: "triangles", + * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], + * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], + * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ], + * position: [-4, -6, -4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [1, 0.3, 0.3] + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["leg1"], + * isObject: true + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "leg2", + * origin: origin, // This mesh's positions and transforms are relative to the RTC center + * primitive: "triangles", + * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], + * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], + * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ], + * position: [4, -6, -4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [0.3, 1.0, 0.3] + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["leg2"], + * isObject: true + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "leg3", + * origin: origin, // This mesh's positions and transforms are relative to the RTC center + * primitive: "triangles", + * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], + * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], + * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ], + * position: [4, -6, 4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [0.3, 0.3, 1.0] + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["leg3"], + * isObject: true + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "leg4", + * origin: origin, // This mesh's positions and transforms are relative to the RTC center + * primitive: "triangles", + * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], + * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], + * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ], + * position: [-4, -6, 4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [1.0, 1.0, 0.0] + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["leg4"], + * isObject: true + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "top", + * origin: origin, // This mesh's positions and transforms are relative to the RTC center + * primitive: "triangles", + * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], + * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], + * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ], + * position: [0, -3, 0], + * scale: [6, 0.5, 6], + * rotation: [0, 0, 0], + * color: [1.0, 0.3, 1.0] + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["top"], + * isObject: true + * }); + * ```` + * + * ## Positioning at World-space coordinates + * + * To position a DataTextureSceneModel at given double-precision World coordinates, we can + * configure the ````origin```` of the DataTextureSceneModel itself. The ````origin```` is a double-precision + * 3D World-space position at which the DataTextureSceneModel will be located. + * + * Note that ````position```` is a single-precision offset relative to ````origin````. + * + * ````javascript + * const origin = [100000000, 0, 100000000]; + * + * const dataTextureSceneModel = new DataTextureSceneModel(viewer.scene, { + * id: "table", + * isModel: true, + * origin: origin, // Everything in this DataTextureSceneModel is relative to this RTC center + * position: [0, 0, 0], + * scale: [1, 1, 1], + * rotation: [0, 0, 0] + * }); + * + * dataTextureSceneModel.createGeometry({ + * id: "box", + * primitive: "triangles", + * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], + * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], + * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ], + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "leg1", + * geometryId: "box", + * position: [-4, -6, -4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [1, 0.3, 0.3] + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["leg1"], + * isObject: true + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "leg2", + * geometryId: "box", + * position: [4, -6, -4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [0.3, 1.0, 0.3] + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["leg2"], + * isObject: true + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "leg3", + * geometryId: "box", + * position: [4, -6, 4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [0.3, 0.3, 1.0] + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["leg3"], + * isObject: true + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "leg4", + * geometryId: "box", + * position: [-4, -6, 4], + * scale: [1, 3, 1], + * rotation: [0, 0, 0], + * color: [1.0, 1.0, 0.0] + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["leg4"], + * isObject: true + * }); + * + * dataTextureSceneModel.createMesh({ + * id: "top", + * geometryId: "box", + * position: [0, -3, 0], + * scale: [6, 0.5, 6], + * rotation: [0, 0, 0], + * color: [1.0, 0.3, 1.0] + * }); + * + * dataTextureSceneModel.createEntity({ + * meshIds: ["top"], + * isObject: true + * }); + * ```` * * @implements {Drawable} * @implements {Entity} + * @implements {SceneModel} + * @implements {ViewFrustumCullingModel} */ -class DataTextureSceneModel extends Component { +export class DataTextureSceneModel extends Component { /** * @constructor @@ -54,13 +958,13 @@ class DataTextureSceneModel extends Component { * @param {Boolean} [cfg.edges=false] Indicates if the DataTextureSceneModel's edges are initially emphasized. * @param {Number[]} [cfg.colorize=[1.0,1.0,1.0]] DataTextureSceneModel's initial RGB colorize color, multiplies by the rendered fragment colors. * @param {Number} [cfg.opacity=1.0] DataTextureSceneModel's initial opacity factor, multiplies by the rendered fragment alpha. - * @param {Number} [cfg.backfaces=false] When we set this ````true````, then we force rendering of backfaces for this DataTextureModel. When + * @param {Number} [cfg.backfaces=false] When we set this ````true````, then we force rendering of backfaces for this DataTextureSceneModel. When * we leave this ````false````, then we allow the Viewer to decide when to render backfaces. In that case, the * Viewer will hide backfaces on watertight meshes, show backfaces on open meshes, and always show backfaces on meshes when we slice them open with {@link SectionPlane}s. - * @param {Boolean} [cfg.saoEnabled=true] Indicates if Scalable Ambient Obscurance (SAO) will apply to this DataTextureModel. SAO is configured by the Scene's {@link SAO} component. + * @param {Boolean} [cfg.saoEnabled=true] Indicates if Scalable Ambient Obscurance (SAO) will apply to this DataTextureSceneModel. SAO is configured by the Scene's {@link SAO} component. * @param {Number} [cfg.edgeThreshold=10] When xraying, highlighting, selecting or edging, this is the threshold angle between normals of adjacent triangles, below which their shared wireframe edge is not drawn. - * @param {Number} [cfg.targetLodFps] Optional target LoD FPS. When provided, will enable LoD culling for this DataTextureModel, with the given target FPS. - * @param {Boolean} [cfg.enableViewFrustumCulling=false] When true, will enable view frustum culling for the objects within this DataTextureModel. + * @param {Number} [cfg.targetLodFps] Optional target LoD FPS. When provided, will enable LoD culling for this DataTextureSceneModel, with the given target FPS. + * @param {Boolean} [cfg.enableViewFrustumCulling=false] When true, will enable view frustum culling for the objects within this DataTextureSceneModel. * @param {Boolean} [cfg.disableVertexWelding] Disable vertex welding when loading geometry into the GPU. Default is ```false```. * @param {Boolean} [cfg.disableIndexRebucketing] Disable index rebucketing when loading geometry into the GPU. Default is ```false```. */ @@ -101,6 +1005,8 @@ class DataTextureSceneModel extends Component { this._aabb = math.collapseAABB3(); this._aabbDirty = false; + this._currentLayers = {}; + this._layers = {}; /** * @type {Array} @@ -112,11 +1018,6 @@ class DataTextureSceneModel extends Component { */ this._nodeList = []; - /** - * @type {TrianglesDataTextureLayer} - */ - this._currentDataTextureLayer = null; - this._instancingGeometries = {}; this._preparedInstancingGeometries = {}; @@ -199,7 +1100,6 @@ class DataTextureSceneModel extends Component { /** @private */ this._numTriangles = 0; - this._edgeThreshold = cfg.edgeThreshold || 10; this.visible = cfg.visible; @@ -953,8 +1853,6 @@ class DataTextureSceneModel extends Component { * @param {Number[]} [cfg.indices] Array of indices. Not required for `points` primitives. * @param {Number[]} [cfg.edgeIndices] Array of edge line indices. Used only for Required for 'triangles' primitives. These are automatically generated internally if not supplied, using the ````edgeThreshold```` given to the ````DataTextureSceneModel```` constructor. * @param {Number[]} [cfg.positionsDecodeMatrix] A 4x4 matrix for decompressing ````positionsCompresse````. - * @param {Number[]} [cfg.origin] Optional geometry origin, relative to {@link DataTextureSceneModel#origin}. When this is given, then every mesh created with {@link DataTextureSceneModel#createMesh} that uses this geometry will - * be transformed relative to this origin. */ createGeometry(cfg) { const geometryId = cfg.id; @@ -985,8 +1883,6 @@ class DataTextureSceneModel extends Component { math.expandAABB3Points3(aabb, cfg.positions); cfg.positionsCompressed = quantizePositions(cfg.positions, aabb, cfg.positionsDecodeMatrix) } - const cfgOrigin = cfg.origin || cfg.rtcCenter; - const origin = (cfgOrigin) ? math.addVec3(this._origin, cfgOrigin, tempVec3a) : this._origin; switch (primitive) { case "triangles": case "solid": @@ -995,7 +1891,7 @@ class DataTextureSceneModel extends Component { this.error(`Config missing: indices - required for primitive type "${primitive}"`); return; } - const geometryCfg = utils.apply({origin, layerIndex: 0,}, cfg); + const geometryCfg = utils.apply({layerIndex: 0}, cfg); if (!geometryCfg.edgeIndices) { geometryCfg.edgeIndices = buildEdgeIndices(cfg.positionsCompressed, cfg.indices, null, 5.0); } @@ -1062,7 +1958,7 @@ class DataTextureSceneModel extends Component { return; } if (cfg.primitive !== "points" && !cfg.indices) { - this.error(`Config missing: indices - required for ${cfg.primitive} primitives`); + this.error(`Config missing: indices - required with ${cfg.primitive} primitives`); return; } if (cfg.positionsCompressed && !cfg.positionsDecodeMatrix) { @@ -1081,45 +1977,28 @@ class DataTextureSceneModel extends Component { preparedGeometryCfg = this._preparedInstancingGeometries[geometryId]; cfg.geometryId = geometryId; } - const origin = cfg.origin; + const origin = (cfg.origin) ? math.addVec3(this._origin, cfg.origin, tempVec3a) : this._origin; cfg.color = cfg.color ? cfg.color.slice() : [255, 255, 255]; if (this._vfcManager && !this._vfcManager.finalized) { this._vfcManager.addMesh(cfg); // To be created in #finalize() return; } - let layer = this._currentDataTextureLayer; - if (null !== layer && !layer.canCreatePortion(preparedGeometryCfg, null)) { - layer.finalize(); - delete this._currentDataTextureLayer; - layer = null; - } - if (!layer) { - layer = new TrianglesDataTextureLayer(this, {layerIndex: 0, origin}); // layerIndex is set in #finalize() - this._layerList.push(layer); - this._currentDataTextureLayer = layer; - } - + let layer = this._getLayer(origin, preparedGeometryCfg); const color = (cfg.color) ? new Uint8Array([Math.floor(cfg.color[0] * 255), Math.floor(cfg.color[1] * 255), Math.floor(cfg.color[2] * 255)]) : [255, 255, 255]; const opacity = (cfg.opacity !== undefined && cfg.opacity !== null) ? Math.floor(cfg.opacity * 255) : 255; const metallic = (cfg.metallic !== undefined && cfg.metallic !== null) ? Math.floor(cfg.metallic * 255) : 0; const roughness = (cfg.roughness !== undefined && cfg.roughness !== null) ? Math.floor(cfg.roughness * 255) : 255; - const mesh = new DataTextureSceneModelMesh(this, id, color, opacity); - const pickId = mesh.pickId; const a = pickId >> 24 & 0xFF; const b = pickId >> 16 & 0xFF; const g = pickId >> 8 & 0xFF; const r = pickId & 0xFF; - const pickColor = new Uint8Array([r, g, b, a]); // Quantized pick color const aabb = math.collapseAABB3(); - preparedGeometryCfg.solid = preparedGeometryCfg.primitive === "solid"; - let meshMatrix; - let worldMatrix = this._worldMatrixNonIdentity ? this._worldMatrix : null; - + const worldMatrix = this._worldMatrixNonIdentity ? this._worldMatrix : null; if (cfg.matrix) { meshMatrix = cfg.matrix; } else { @@ -1129,7 +2008,6 @@ class DataTextureSceneModel extends Component { math.eulerToQuaternion(rotation, "XYZ", defaultQuaternion); meshMatrix = math.composeMat4(position, defaultQuaternion, scale, math.mat4()); } - const portionId = layer.createPortion(preparedGeometryCfg, { origin, geometryId, @@ -1142,14 +2020,9 @@ class DataTextureSceneModel extends Component { aabb, pickColor }); - math.expandAABB3(this._aabb, aabb); - this._numTriangles += preparedGeometryCfg.indices.length / 3; mesh.numTriangles = preparedGeometryCfg.indices.length / 3; - - mesh.origin = preparedGeometryCfg.origin; - math.expandAABB3(this._aabb, aabb); this.numGeometries++; mesh.origin = origin; // noinspection JSConstantReassignment @@ -1158,10 +2031,28 @@ class DataTextureSceneModel extends Component { mesh._portionId = portionId; // noinspection JSConstantReassignment mesh.aabb = aabb; - this._meshes[id] = mesh; } + _getLayer(origin, preparedGeometryCfg) { + const layerId = `${origin[0]}.${origin[1]}.${origin[2]}`; + let layer = this._currentLayers[layerId]; + if (layer) { + if (!layer.canCreatePortion(preparedGeometryCfg, null)) { + layer.finalize(); + delete this._currentLayers[layerId]; + layer = null; + } else { + return layer; + } + } + layer = new TrianglesDataTextureLayer(this, {layerIndex: 0, origin}); // layerIndex is set in #finalize() + this._currentLayers[layerId] = layer; + this._layers[layerId] = layer; + this._layerList.push(layer); + return layer; + } + /** * Creates an {@link Entity} within this DataTextureSceneModel, giving it one or more meshes previously created with {@link DataTextureSceneModel#createMesh}. * @@ -1170,7 +2061,6 @@ class DataTextureSceneModel extends Component { * @param {Object} cfg Entity configuration. * @param {String} cfg.id Optional ID for the new Entity. Must not clash with any existing components within the {@link Scene}. * @param {String[]} cfg.meshIds IDs of one or more meshes created previously with {@link DataTextureSceneModel@createMesh}. - * @param {Boolean} [cfg.isObject] Set ````true```` if the {@link Entity} represents an object, in which case it will be registered by {@link Entity#id} in {@link Scene#objects} and can also have a corresponding {@link MetaObject} with matching {@link MetaObject#id}, registered by that ID in {@link MetaScene#metaObjects}. * @param {Boolean} [cfg.visible=true] Indicates if the Entity is initially visible. * @param {Boolean} [cfg.culled=false] Indicates if the Entity is initially culled from view. @@ -1190,7 +2080,7 @@ class DataTextureSceneModel extends Component { if (id === undefined) { id = math.createUUID(); } else if (this.scene.components[id]) { - this.error("Scene already has a Component with this ID: " + id + " - will assign random ID"); + this.error("Scene already has a Component with this ID: " + id + " - will assign random ID to new Entity"); id = math.createUUID(); } const meshIds = cfg.meshIds; @@ -1273,16 +2163,16 @@ class DataTextureSceneModel extends Component { } if (this._vfcManager) { this._vfcManager.finalize(() => { - if (!this._currentDataTextureLayer) { - return; + for (let layerId in this._currentLayers) { + this._currentLayers[layerId].finalize(); + delete this._currentLayers[layerId]; } - this._currentDataTextureLayer.finalize(); - delete this._currentDataTextureLayer; - this._currentDataTextureLayer = null; }); - } - if (this._currentDataTextureLayer) { - this._currentDataTextureLayer.finalize(); + } else { + for (let layerId in this._currentLayers) { + this._currentLayers[layerId].finalize(); + delete this._currentLayers[layerId]; + } } for (let i = 0, len = this._nodeList.length; i < len; i++) { const node = this._nodeList[i]; @@ -1359,7 +2249,9 @@ class DataTextureSceneModel extends Component { } } - /** @private */ + /** + * @private + */ _getActiveSectionPlanesForLayer(layer) { const renderFlags = this.renderFlags; const sectionPlanes = this.scene._sectionPlanesState.sectionPlanes; @@ -1381,23 +2273,17 @@ class DataTextureSceneModel extends Component { /** @private */ _updateRenderFlags() { - if (this.numVisibleLayerPortions === 0) { return; } - if (this.numCulledLayerPortions === this.numPortions) { return; } - const renderFlags = this.renderFlags; - renderFlags.colorOpaque = (this.numTransparentLayerPortions < this.numPortions); - if (this.numTransparentLayerPortions > 0) { renderFlags.colorTransparent = true; } - if (this.numXRayedLayerPortions > 0) { const xrayMaterial = this.scene.xrayMaterial._state; if (xrayMaterial.fill) { @@ -1415,7 +2301,6 @@ class DataTextureSceneModel extends Component { } } } - if (this.numEdgesLayerPortions > 0) { const edgeMaterial = this.scene.edgeMaterial._state; if (edgeMaterial.edges) { @@ -1425,7 +2310,6 @@ class DataTextureSceneModel extends Component { } } } - if (this.numSelectedLayerPortions > 0) { const selectedMaterial = this.scene.selectedMaterial._state; if (selectedMaterial.fill) { @@ -1443,7 +2327,6 @@ class DataTextureSceneModel extends Component { } } } - if (this.numHighlightedLayerPortions > 0) { const highlightMaterial = this.scene.highlightMaterial._state; if (highlightMaterial.fill) { @@ -1573,9 +2456,7 @@ class DataTextureSceneModel extends Component { } } - /** - * @private - */ + /** @private */ drawOcclusion(frameCtx) { if (this.numVisibleLayerPortions === 0) { return; @@ -1587,9 +2468,7 @@ class DataTextureSceneModel extends Component { } } - /** - * @private - */ + /** @private */ drawShadow(frameCtx) { if (this.numVisibleLayerPortions === 0) { return; @@ -1690,7 +2569,6 @@ class DataTextureSceneModel extends Component { } } - /** * This function applies two steps to the provided mesh geometry data: * @@ -1710,6 +2588,8 @@ class DataTextureSceneModel extends Component { * * @param {object} geometryCfg The mesh information containing `.positions`, `.indices`, `.edgeIndices` arrays. * + * @param enableVertexWelding + * @param enableIndexRebucketing * @returns {object} The mesh information enrichened with `.preparedBuckets` key. */ function prepareMeshGeometry(geometryCfg, enableVertexWelding, enableIndexRebucketing) { @@ -1753,5 +2633,3 @@ function prepareMeshGeometry(geometryCfg, enableVertexWelding, enableIndexRebuck return geometryCfg; } - -export {DataTextureSceneModel}; \ No newline at end of file diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/DataTextureSceneModelMesh.js b/src/viewer/scene/models/DataTextureSceneModel/lib/DataTextureSceneModelMesh.js index 0d68bc5fa..3db5dd2a2 100644 --- a/src/viewer/scene/models/DataTextureSceneModel/lib/DataTextureSceneModelMesh.js +++ b/src/viewer/scene/models/DataTextureSceneModel/lib/DataTextureSceneModelMesh.js @@ -3,18 +3,19 @@ import {math} from "../../../math/math.js"; /** * @private * @implements Pickable + * @implements ViewFrustumCullingMesh */ class DataTextureSceneModelMesh { constructor(model, id, color, opacity, layer = null, portionId = 0) { /** - * The VBOSceneModel that contains this PerformanceModelMesh. + * The DataTextureSceneModel that contains this PerformanceModelMesh. * - * A PerformanceModelMesh always belongs to exactly one VBOSceneModel. + * A PerformanceModelMesh always belongs to exactly one DataTextureSceneModel. * * @property model - * @type {VBOSceneModel} + * @type {DataTextureSceneModel} * @final */ this.model = model; @@ -25,7 +26,7 @@ class DataTextureSceneModelMesh { * A PerformanceModelMesh always belongs to exactly one DataTextureSceneModelNode. * * @property object - * @type {VBOSceneModelNode} + * @type {DataTextureSceneModelNode} * @final */ this.object = null; @@ -36,7 +37,7 @@ class DataTextureSceneModelMesh { * A PerformanceModelMesh always belongs to exactly one DataTextureSceneModelNode. * * @property object - * @type {VBOSceneModelNode} + * @type {DataTextureSceneModelNode} * @final */ this.parent = null; @@ -68,14 +69,11 @@ class DataTextureSceneModelMesh { * @type {Float64Array} */ this.aabb = math.AABB3(); - this._layer = layer; this._portionId = portionId; - this._color = [color[0], color[1], color[2], opacity]; // [0..255] this._colorize = [color[0], color[1], color[2], opacity]; // [0..255] this._colorizing = false; - this._transparent = (opacity < 255); this.numTriangles = 0; @@ -259,7 +257,7 @@ class DataTextureSceneModelMesh { /** * @private - * @returns {VBOSceneModelNode} + * @returns {DataTextureSceneModelNode} */ delegatePickedEntity() { return this.parent; diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/DataTextureSceneModelNode.js b/src/viewer/scene/models/DataTextureSceneModel/lib/DataTextureSceneModelNode.js index 701e8a42e..8a93d184c 100644 --- a/src/viewer/scene/models/DataTextureSceneModel/lib/DataTextureSceneModelNode.js +++ b/src/viewer/scene/models/DataTextureSceneModel/lib/DataTextureSceneModelNode.js @@ -6,6 +6,7 @@ const tempIntRGB = new Uint16Array([0, 0, 0]); /** * @private + * @implements ViewFrustumCullingNode */ class DataTextureSceneModelNode { @@ -34,16 +35,16 @@ class DataTextureSceneModelNode { this.model = model; /** - * The PerformanceModelMesh instances contained by this DataTextureSceneModelNode + * The DataTextureSceneModelMesh instances contained by this DataTextureSceneModelNode * @property meshes - * @type {{Array of PerformanceModelMesh}} + * @type {{Array of DataTextureSceneModelMesh}} * @final */ this.meshes = meshes; this._numTriangles = 0; - for (var i = 0, len = this.meshes.length; i < len; i++) { // TODO: tidier way? Refactor? + for (let i = 0, len = this.meshes.length; i < len; i++) { // TODO: tidier way? Refactor? const mesh = this.meshes[i]; mesh.parent = this; this._numTriangles += mesh.numTriangles; @@ -77,7 +78,6 @@ class DataTextureSceneModelNode { this._culledVFC = false; this._culledLOD = false; - if (this._isObject) { model.scene._registerObject(this); } @@ -208,7 +208,7 @@ class DataTextureSceneModelNode { } else { this._flags = this._flags & ~ENTITY_FLAGS.HIGHLIGHTED; } - for (var i = 0, len = this.meshes.length; i < len; i++) { + for (let i = 0, len = this.meshes.length; i < len; i++) { this.meshes[i]._setHighlighted(this._flags); } if (this._isObject) { @@ -316,7 +316,7 @@ class DataTextureSceneModelNode { } else { this._flags = this._flags & ~ENTITY_FLAGS.EDGES; } - for (var i = 0, len = this.meshes.length; i < len; i++) { + for (let i = 0, len = this.meshes.length; i < len; i++) { this.meshes[i]._setEdges(this._flags); } this.model.glRedraw(); @@ -328,7 +328,7 @@ class DataTextureSceneModelNode { set culledVFC(culled) { this._culledVFC = culled; - this.internalSetCulled (); + this._setCulled(); } get culledLOD() { @@ -337,7 +337,7 @@ class DataTextureSceneModelNode { set culledLOD(culled) { this._culledLOD = culled; - this.internalSetCulled (); + this._setCulled(); } /** @@ -361,13 +361,11 @@ class DataTextureSceneModelNode { */ set culled(culled) { this._culled = culled; - this.internalSetCulled (); + this._setCulled(); } - internalSetCulled() - { - let culled = !!(this._culled) || !!(this._culledLOD) || !!(this._culledVFC); - + _setCulled() { + const culled = !!(this._culled) || !!(this._culledLOD) || !!(this._culledVFC); if (!!(this._flags & ENTITY_FLAGS.CULLED) === culled) { return; // Redundant update } @@ -376,7 +374,7 @@ class DataTextureSceneModelNode { } else { this._flags = this._flags & ~ENTITY_FLAGS.CULLED; } - for (var i = 0, len = this.meshes.length; i < len; i++) { + for (let i = 0, len = this.meshes.length; i < len; i++) { this.meshes[i]._setCulled(this._flags); } this.model.glRedraw(); @@ -409,7 +407,7 @@ class DataTextureSceneModelNode { } else { this._flags = this._flags & ~ENTITY_FLAGS.CLIPPABLE; } - for (var i = 0, len = this.meshes.length; i < len; i++) { + for (let i = 0, len = this.meshes.length; i < len; i++) { this.meshes[i]._setClippable(this._flags); } this.model.glRedraw(); @@ -438,7 +436,7 @@ class DataTextureSceneModelNode { } else { this._flags = this._flags & ~ENTITY_FLAGS.COLLIDABLE; } - for (var i = 0, len = this.meshes.length; i < len; i++) { + for (let i = 0, len = this.meshes.length; i < len; i++) { this.meshes[i]._setCollidable(this._flags); } } @@ -470,7 +468,7 @@ class DataTextureSceneModelNode { } else { this._flags = this._flags & ~ENTITY_FLAGS.PICKABLE; } - for (var i = 0, len = this.meshes.length; i < len; i++) { + for (let i = 0, len = this.meshes.length; i < len; i++) { this.meshes[i]._setPickable(this._flags); } } diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/BindableDataTexture.js b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/BindableDataTexture.js new file mode 100644 index 000000000..74d90b5f1 --- /dev/null +++ b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/BindableDataTexture.js @@ -0,0 +1,93 @@ +export class BindableDataTexture { + /** + * + * @param {WebGL2RenderingContext} gl + * @param {WebGLTexture} texture + * @param {int} textureWidth + * @param {int} textureHeight + * @param {TypedArray} textureData + */ + constructor(gl, texture, textureWidth, textureHeight, textureData = null) { + + /** + * The WebGL context. + * + * @type WebGL2RenderingContext + * @private + */ + this._gl = gl; + + /** + * The WebGLTexture handle. + * + * @type {WebGLTexture} + * @private + */ + this._texture = texture; + + /** + * The texture width. + * + * @type {number} + * @private + */ + this._textureWidth = textureWidth; + + /** + * The texture height. + * + * @type {number} + * @private + */ + this._textureHeight = textureHeight; + + /** + * (nullable) When the texture data array is kept in the JS side, it will be stored here. + * + * @type {TypedArray} + * @private + */ + this._textureData = textureData; + } + + /** + * Convenience method to be used by the renderers to bind the texture before draw calls. + * + * @param {Program} glProgram + * @param {string} shaderName The name of the shader attribute + * @param {number} glTextureUnit The WebGL texture unit + * + * @returns {bool} + */ + bindTexture(glProgram, shaderName, glTextureUnit) { + return glProgram.bindTexture(shaderName, this, glTextureUnit); + } + + /** + * + * Used internally by the `program` passed to `bindTexture` in order to bind the texture to an active `texture-unit`. + * + * @param {number} unit The WebGL texture unit + * + * @returns {bool} + * @private + */ + bind(unit) { + this._gl.activeTexture(this._gl["TEXTURE" + unit]); + this._gl.bindTexture(this._gl.TEXTURE_2D, this._texture); + return true; + } + + /** + * Used internally by the `program` passed to `bindTexture` in order to bind the texture to an active `texture-unit`. + * + * @param {number} unit The WebGL texture unit + * @private + */ + unbind(unit) { + // This `unbind` method is ignored at the moment to allow avoiding to rebind same texture already bound to a texture unit. + + // this._gl.activeTexture(this.state.gl["TEXTURE" + unit]); + // this._gl.bindTexture(this.state.gl.TEXTURE_2D, null); + } +} \ No newline at end of file diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/DataTextureBuffer.js b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/DataTextureBuffer.js new file mode 100644 index 000000000..26d67f752 --- /dev/null +++ b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/DataTextureBuffer.js @@ -0,0 +1,27 @@ +export class DataTextureBuffer { + constructor() { + this.positions = []; + this.indices8Bits = []; + this.indices16Bits = []; + this.indices32Bits = []; + this.edgeIndices8Bits = []; + this.edgeIndices16Bits = []; + this.edgeIndices32Bits = []; + this.edgeIndices = []; + this._objectDataColors = []; + this._objectDataPickColors = []; + this._vertexBasesForObject = []; + this._indexBaseOffsetsForObject = []; + this._edgeIndexBaseOffsetsForObject = []; + this._objectDataPositionsMatrices = []; + this._objectDataInstanceGeometryMatrices = []; + this._objectDataInstanceNormalsMatrices = []; + this._portionIdForIndices8Bits = []; + this._portionIdForIndices16Bits = []; + this._portionIdForIndices32Bits = []; + this._portionIdForEdges8Bits = []; + this._portionIdForEdges16Bits = []; + this._portionIdForEdges32Bits = []; + this._portionIdFanOut = []; + } +} \ No newline at end of file diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/DataTextureGenerator.js b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/DataTextureGenerator.js new file mode 100644 index 000000000..26982c7a1 --- /dev/null +++ b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/DataTextureGenerator.js @@ -0,0 +1,573 @@ +import {createRTCViewMat, math} from "../../../../math"; +import {BindableDataTexture} from "./BindableDataTexture"; +import {dataTextureRamStats} from "./trianglesDataTexture/dataTextureRamStats"; + +/** + * @private + */ +export class DataTextureGenerator { + /** + * Enables the currently binded ````WebGLTexture```` to be used as a data texture. + * + * @param {WebGL2RenderingContext} gl + * + * @private + */ + disableBindedTextureFiltering(gl) { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + } + + /** + * Generate and return a `camera data texture`. + * + * The texture will automatically update its contents before each render when the camera matrix is dirty, + * and to do so will use the following events: + * + * - `scene.rendering` event will be used to know that the camera texture should be updated + * - `camera.matrix` event will be used to know that the camera matices changed + * + * @param {WebGL2RenderingContext} gl + * @param {Camera} camera + * @param {Scene} scene + * @param {null|number[3]} origin + * @param followCameraUpdate + * @returns {BindableDataTexture} + */ + generateCameraDataTexture(gl, camera, scene, origin, followCameraUpdate = true) { + const textureWidth = 4; + const textureHeight = 3; // space for 3 matrices + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, textureWidth, textureHeight); + this.disableBindedTextureFiltering(gl); + gl.bindTexture(gl.TEXTURE_2D, null); + const cameraTexture = new BindableDataTexture(gl, texture, textureWidth, textureHeight); + let cameraDirty = true; + cameraTexture._updateViewMatrix = (viewMatrix, projMatrix) => { + gl.bindTexture(gl.TEXTURE_2D, cameraTexture._texture); + // Camera's "view matrix" + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, // 1st matrix: camera view matrix + 4, + 1, + gl.RGBA, + gl.FLOAT, + new Float32Array((origin) ? createRTCViewMat(viewMatrix, origin) : viewMatrix) + ); + + // Camera's "view normal matrix" + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 1, // 2nd matrix: camera view normal matrix + 4, + 1, + gl.RGBA, + gl.FLOAT, + new Float32Array(camera.viewNormalMatrix) + ); + + // Camera's "project matrix" + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 2, // 3rd matrix: camera project matrix + 4, + 1, + gl.RGBA, + gl.FLOAT, + new Float32Array(projMatrix) + ); + }; + + if (followCameraUpdate) { + const onCameraMatrix = () => { + if (!cameraDirty) { + return; + } + cameraDirty = false; + cameraTexture._updateViewMatrix(camera.viewMatrix, camera.project.matrix); + }; + camera.on("matrix", () => cameraDirty = true); + scene.on("rendering", onCameraMatrix); + onCameraMatrix(); + } + return cameraTexture; + } + + /** + * Generate and return a `model data texture`. + * + * @param {WebGL2RenderingContext} gl + * @param {PerformanceModel} model + * + * @returns {BindableDataTexture} + */ + generatePeformanceModelDataTexture(gl, model) { + const textureWidth = 4; + const textureHeight = 2; // space for 2 matrices + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, textureWidth, textureHeight); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, // x-offset + 0, // y-offset (model world matrix) + 4, // data width (4x4 values) + 1, // data height (1 matrix) + gl.RGBA, + gl.FLOAT, + new Float32Array(model.worldMatrix) + ); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, // x-offset + 1, // y-offset (model normal matrix) + 4, // data width (4x4 values) + 1, // data height (1 matrix) + gl.RGBA, + gl.FLOAT, + new Float32Array(model.worldNormalMatrix) + ); + this.disableBindedTextureFiltering(gl); + gl.bindTexture(gl.TEXTURE_2D, null); + return new BindableDataTexture(gl, texture, textureWidth, textureHeight); + } + + /** + * This will generate an RGBA texture for: + * - colors + * - pickColors + * - flags + * - flags2 + * - vertex bases + * - vertex base offsets + * + * The texture will have: + * - 4 RGBA columns per row: for each object (pick) color and flags(2) + * - N rows where N is the number of objects + * + * @param {WebGL2RenderingContext} gl + * @param {ArrayLike>} colors Array of colors for all objects in the layer + * @param {ArrayLike>} pickColors Array of pickColors for all objects in the layer + * @param {ArrayLike} vertexBases Array of position-index-bases foteh all objects in the layer + * @param {ArrayLike} indexBaseOffsets For triangles: array of offests between the (gl_VertexID / 3) and the position where the indices start in the texture layer + * @param {ArrayLike} edgeIndexBaseOffsets For edges: Array of offests between the (gl_VertexID / 2) and the position where the edge indices start in the texture layer + * @param {ArrayLike} solid Array is-solid flag for all objects in the layer + * + * @returns {BindableDataTexture} + */ + generateTextureForColorsAndFlags(gl, colors, pickColors, vertexBases, indexBaseOffsets, edgeIndexBaseOffsets, solid) { + const numPortions = colors.length; + + // The number of rows in the texture is the number of + // objects in the layer. + + this.numPortions = numPortions; + + const textureWidth = 512 * 8; + const textureHeight = Math.ceil(numPortions / (textureWidth / 8)); + + if (textureHeight === 0) { + throw "texture height===0"; + } + + // 8 columns per texture row: + // - col0: (RGBA) object color RGBA + // - col1: (packed Uint32 as RGBA) object pick color + // - col2: (packed 4 bytes as RGBA) object flags + // - col3: (packed 4 bytes as RGBA) object flags2 + // - col4: (packed Uint32 bytes as RGBA) vertex base + // - col5: (packed Uint32 bytes as RGBA) index base offset + // - col6: (packed Uint32 bytes as RGBA) edge index base offset + // - col7: (packed 4 bytes as RGBA) is-solid flag for objects + + const texArray = new Uint8Array(4 * textureWidth * textureHeight); + + dataTextureRamStats.sizeDataColorsAndFlags += texArray.byteLength; + dataTextureRamStats.numberOfTextures++; + + for (let i = 0; i < numPortions; i++) { + // object color + texArray.set(colors [i], i * 32 + 0); + texArray.set(pickColors [i], i * 32 + 4); // object pick color + texArray.set([0, 0, 0, 0], i * 32 + 8); // object flags + texArray.set([0, 0, 0, 0], i * 32 + 12); // object flags2 + + // vertex base + texArray.set([ + (vertexBases[i] >> 24) & 255, + (vertexBases[i] >> 16) & 255, + (vertexBases[i] >> 8) & 255, + (vertexBases[i]) & 255, + ], + i * 32 + 16 + ); + + // triangles index base offset + texArray.set( + [ + (indexBaseOffsets[i] >> 24) & 255, + (indexBaseOffsets[i] >> 16) & 255, + (indexBaseOffsets[i] >> 8) & 255, + (indexBaseOffsets[i]) & 255, + ], + i * 32 + 20 + ); + + // edge index base offset + texArray.set( + [ + (edgeIndexBaseOffsets[i] >> 24) & 255, + (edgeIndexBaseOffsets[i] >> 16) & 255, + (edgeIndexBaseOffsets[i] >> 8) & 255, + (edgeIndexBaseOffsets[i]) & 255, + ], + i * 32 + 24 + ); + + // is-solid flag + texArray.set([solid[i] ? 1 : 0, 0, 0, 0], i * 32 + 28); + } + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8UI, textureWidth, textureHeight); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, texArray, 0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.bindTexture(gl.TEXTURE_2D, null); + return new BindableDataTexture(gl, texture, textureWidth, textureHeight, texArray); + } + + /** + * This will generate a texture for all object offsets. + * + * @param {WebGL2RenderingContext} gl + * @param {int[]} offsets Array of int[3], one XYZ offset array for each object + * + * @returns {BindableDataTexture} + */ + generateTextureForObjectOffsets(gl, numOffsets) { + const textureWidth = 512; + const textureHeight = Math.ceil(numOffsets / textureWidth); + if (textureHeight === 0) { + throw "texture height===0"; + } + const texArray = new Float32Array(3 * textureWidth * textureHeight).fill(0); + dataTextureRamStats.sizeDataTextureOffsets += texArray.byteLength; + dataTextureRamStats.numberOfTextures++; + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB32F, textureWidth, textureHeight); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB, gl.FLOAT, texArray, 0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.bindTexture(gl.TEXTURE_2D, null); + return new BindableDataTexture(gl, texture, textureWidth, textureHeight, texArray); + } + + /** + * This will generate a texture for all positions decode matrices in the layer. + * + * The texture will have: + * - 4 RGBA columns per row (each column will contain 4 packed half-float (16 bits) components). + * Thus, each row will contain 16 packed half-floats corresponding to a complete positions decode matrix) + * - N rows where N is the number of objects + * + * @param {WebGL2RenderingContext} gl + * @param {ArrayLike} positionDecodeMatrices Array of positions decode matrices for all objects in the layer + * @param {ArrayLike} instanceMatrices Array of geometry instancing matrices for all objects in the layer. Null if the objects are not instanced. + * + * @returns {BindableDataTexture} + */ + generateTextureForPositionsDecodeMatrices(gl, positionDecodeMatrices, instanceMatrices) { + const numMatrices = positionDecodeMatrices.length; + if (numMatrices === 0) { + throw "num decode+entity matrices===0"; + } + // in one row we can fit 512 matrices + const textureWidth = 512 * 4; + const textureHeight = Math.ceil(numMatrices / (textureWidth / 4)); + const texArray = new Float32Array(4 * textureWidth * textureHeight); + dataTextureRamStats.sizeDataPositionDecodeMatrices += texArray.byteLength; + dataTextureRamStats.numberOfTextures++; + const tmpMatrix = math.mat4(); + for (let i = 0; i < positionDecodeMatrices.length; i++) { // 4x4 values + texArray.set(math.mulMat4(instanceMatrices[i], positionDecodeMatrices[i], tmpMatrix), i * 16); + } + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, textureWidth, textureHeight); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGBA, gl.FLOAT, texArray, 0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.bindTexture(gl.TEXTURE_2D, null); + return new BindableDataTexture(gl, texture, textureWidth, textureHeight); + } + + /** + * @param {WebGL2RenderingContext} gl + * @param {ArrayLike} indices + * + * @returns {BindableDataTexture} + */ + generateTextureFor8BitIndices(gl, indices) { + if (indices.length === 0) { + return {texture: null, textureHeight: 0,}; + } + const textureWidth = 4096; + const textureHeight = Math.ceil(indices.length / 3 / textureWidth); + if (textureHeight === 0) { + throw "texture height===0"; + } + const texArraySize = textureWidth * textureHeight * 3; + const texArray = new Uint8Array(texArraySize); + dataTextureRamStats.sizeDataTextureIndices += texArray.byteLength; + dataTextureRamStats.numberOfTextures++; + texArray.fill(0); + texArray.set(indices, 0) + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB8UI, textureWidth, textureHeight); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB_INTEGER, gl.UNSIGNED_BYTE, texArray, 0); + this.disableBindedTextureFiltering(gl); + gl.bindTexture(gl.TEXTURE_2D, null); + return new BindableDataTexture(gl, texture, textureWidth, textureHeight); + } + + /** + * @param {WebGL2RenderingContext} gl + * @param {ArrayLike} indices + * + * @returns {BindableDataTexture} + */ + generateTextureFor16BitIndices(gl, indices) { + if (indices.length === 0) { + return {texture: null, textureHeight: 0,}; + } + const textureWidth = 4096; + const textureHeight = Math.ceil(indices.length / 3 / textureWidth); + if (textureHeight === 0) { + throw "texture height===0"; + } + const texArraySize = textureWidth * textureHeight * 3; + const texArray = new Uint16Array(texArraySize); + dataTextureRamStats.sizeDataTextureIndices += texArray.byteLength; + dataTextureRamStats.numberOfTextures++; + texArray.fill(0); + texArray.set(indices, 0) + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB16UI, textureWidth, textureHeight); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB_INTEGER, gl.UNSIGNED_SHORT, texArray, 0); + this.disableBindedTextureFiltering(gl); + gl.bindTexture(gl.TEXTURE_2D, null); + return new BindableDataTexture(gl, texture, textureWidth, textureHeight); + } + + /** + * @param {WebGL2RenderingContext} gl + * @param {ArrayLike} indices + * + * @returns {BindableDataTexture} + */ + generateTextureFor32BitIndices(gl, indices) { + if (indices.length === 0) { + return {texture: null, textureHeight: 0}; + } + const textureWidth = 4096; + const textureHeight = Math.ceil(indices.length / 3 / textureWidth); + if (textureHeight === 0) { + throw "texture height===0"; + } + const texArraySize = textureWidth * textureHeight * 3; + const texArray = new Uint32Array(texArraySize); + dataTextureRamStats.sizeDataTextureIndices += texArray.byteLength; + dataTextureRamStats.numberOfTextures++; + texArray.fill(0); + texArray.set(indices, 0) + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB32UI, textureWidth, textureHeight); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB_INTEGER, gl.UNSIGNED_INT, texArray, 0); + this.disableBindedTextureFiltering(gl); + gl.bindTexture(gl.TEXTURE_2D, null); + return new BindableDataTexture(gl, texture, textureWidth, textureHeight); + } + + /** + * @param {WebGL2RenderingContext} gl + * @param {ArrayLike} edgeIndices + * + * @returns {BindableDataTexture} + */ + generateTextureFor8BitsEdgeIndices(gl, edgeIndices) { + if (edgeIndices.length === 0) { + return {texture: null, textureHeight: 0}; + } + const textureWidth = 4096; + const textureHeight = Math.ceil(edgeIndices.length / 2 / textureWidth); + if (textureHeight === 0) { + throw "texture height===0"; + } + const texArraySize = textureWidth * textureHeight * 2; + const texArray = new Uint8Array(texArraySize); + dataTextureRamStats.sizeDataTextureEdgeIndices += texArray.byteLength; + dataTextureRamStats.numberOfTextures++; + texArray.fill(0); + texArray.set(edgeIndices, 0) + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RG8UI, textureWidth, textureHeight); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RG_INTEGER, gl.UNSIGNED_BYTE, texArray, 0); + this.disableBindedTextureFiltering(gl); + gl.bindTexture(gl.TEXTURE_2D, null); + return new BindableDataTexture(gl, texture, textureWidth, textureHeight); + } + + /** + * @param {WebGL2RenderingContext} gl + * @param {ArrayLike} edgeIndices + * + * @returns {BindableDataTexture} + */ + generateTextureFor16BitsEdgeIndices(gl, edgeIndices) { + if (edgeIndices.length === 0) { + return {texture: null, textureHeight: 0}; + } + const textureWidth = 4096; + const textureHeight = Math.ceil(edgeIndices.length / 2 / textureWidth); + if (textureHeight === 0) { + throw "texture height===0"; + } + const texArraySize = textureWidth * textureHeight * 2; + const texArray = new Uint16Array(texArraySize); + dataTextureRamStats.sizeDataTextureEdgeIndices += texArray.byteLength; + dataTextureRamStats.numberOfTextures++; + texArray.fill(0); + texArray.set(edgeIndices, 0) + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RG16UI, textureWidth, textureHeight); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RG_INTEGER, gl.UNSIGNED_SHORT, texArray, 0); + this.disableBindedTextureFiltering(gl); + gl.bindTexture(gl.TEXTURE_2D, null); + return new BindableDataTexture(gl, texture, textureWidth, textureHeight); + } + + /** + * @param {WebGL2RenderingContext} gl + * @param {ArrayLike} edgeIndices + * + * @returns {BindableDataTexture} + */ + generateTextureFor32BitsEdgeIndices(gl, edgeIndices) { + if (edgeIndices.length === 0) { + return {texture: null, textureHeight: 0,}; + } + const textureWidth = 4096; + const textureHeight = Math.ceil(edgeIndices.length / 2 / textureWidth); + if (textureHeight === 0) { + throw "texture height===0"; + } + const texArraySize = textureWidth * textureHeight * 2; + const texArray = new Uint32Array(texArraySize); + dataTextureRamStats.sizeDataTextureEdgeIndices += texArray.byteLength; + dataTextureRamStats.numberOfTextures++; + texArray.fill(0); + texArray.set(edgeIndices, 0) + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RG32UI, textureWidth, textureHeight); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RG_INTEGER, gl.UNSIGNED_INT, texArray, 0); + this.disableBindedTextureFiltering(gl); + gl.bindTexture(gl.TEXTURE_2D, null); + return new BindableDataTexture(gl, texture, textureWidth, textureHeight); + } + + /** + * @param {WebGL2RenderingContext} gl + * @param {ArrayLike} positions Array of (uniquified) quantized positions in the layer + * + * This will generate a texture for positions in the layer. + * + * The texture will have: + * - 1024 columns, where each pixel will be a 16-bit-per-component RGB texture, corresponding to the XYZ of the position + * - a number of rows R where R*1024 is just >= than the number of vertices (positions / 3) + * + * @returns {BindableDataTexture} + */ + generateTextureForPositions(gl, positions) { + const numVertices = positions.length / 3; + const textureWidth = 4096; + const textureHeight = Math.ceil(numVertices / textureWidth); + if (textureHeight === 0) { + throw "texture height===0"; + } + const texArraySize = textureWidth * textureHeight * 3; + const texArray = new Uint16Array(texArraySize); + dataTextureRamStats.sizeDataTexturePositions += texArray.byteLength; + dataTextureRamStats.numberOfTextures++; + texArray.fill(0); + texArray.set(positions, 0); + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB16UI, textureWidth, textureHeight); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB_INTEGER, gl.UNSIGNED_SHORT, texArray, 0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.bindTexture(gl.TEXTURE_2D, null); + return new BindableDataTexture(gl, texture, textureWidth, textureHeight); + } + + /** + * @param {WebGL2RenderingContext} gl + * @param {ArrayLike} portionIdsArray + * + * @returns {BindableDataTexture} + */ + generateTextureForPackedPortionIds(gl, portionIdsArray) { + if (portionIdsArray.length === 0) { + return {texture: null, textureHeight: 0,}; + } + const lenArray = portionIdsArray.length; + const textureWidth = 4096; + const textureHeight = Math.ceil(lenArray / textureWidth); + if (textureHeight === 0) { + throw "texture height===0"; + } + const texArraySize = textureWidth * textureHeight; + const texArray = new Uint16Array(texArraySize); + texArray.set(portionIdsArray, 0); + dataTextureRamStats.sizeDataTexturePortionIds += texArray.byteLength; + dataTextureRamStats.numberOfTextures++; + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R16UI, textureWidth, textureHeight); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RED_INTEGER, gl.UNSIGNED_SHORT, texArray, 0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.bindTexture(gl.TEXTURE_2D, null); + return new BindableDataTexture(gl, texture, textureWidth, textureHeight); + } +} \ No newline at end of file diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/DataTextureState.js b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/DataTextureState.js index b08ad7c82..abb3c66a4 100644 --- a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/DataTextureState.js +++ b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/DataTextureState.js @@ -1,201 +1,17 @@ -import { createRTCViewMat } from "../../../../math/rtcCoords.js"; - // Imports used to complete the JSDocs arguments to methods -import { Program } from "../../../../webgl/Program.js" -import { Camera } from "../../../../camera/Camera.js" -import { Scene } from "../../../../scene/Scene.js" -import { math } from "../../../../math/math.js"; - -const identityMatrix = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; - -const dataTextureRamStats = { - sizeDataColorsAndFlags: 0, - sizeDataPositionDecodeMatrices: 0, - sizeDataTextureOffsets: 0, - sizeDataTexturePositions: 0, - sizeDataTextureIndices: 0, - sizeDataTextureEdgeIndices: 0, - sizeDataTexturePortionIds: 0, - numberOfGeometries: 0, - numberOfPortions: 0, - numberOfLayers: 0, - numberOfTextures: 0, - totalPolygons: 0, - totalPolygons8Bits: 0, - totalPolygons16Bits: 0, - totalPolygons32Bits: 0, - totalEdges: 0, - totalEdges8Bits: 0, - totalEdges16Bits: 0, - totalEdges32Bits: 0, - cannotCreatePortion: { - because10BitsObjectId: 0, - becauseTextureSize: 0, - }, - overheadSizeAlignementIndices: 0, - overheadSizeAlignementEdgeIndices: 0, -}; - -window.printDataTextureRamStats = function () { - console.log (JSON.stringify(dataTextureRamStats, null, 4)); - - let totalRamSize = 0; +import {Program} from "../../../../webgl/Program.js" - Object.keys(dataTextureRamStats).forEach (key => { - if (key.startsWith ("size")) { - totalRamSize+=dataTextureRamStats[key]; - } - }); +const identityMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; - console.log (`Total size ${totalRamSize} bytes (${(totalRamSize/1000/1000).toFixed(2)} MB)`); - console.log (`Avg bytes / triangle: ${(totalRamSize / dataTextureRamStats.totalPolygons).toFixed(2)}`); - let percentualRamStats = {}; - Object.keys(dataTextureRamStats).forEach (key => { - if (key.startsWith ("size")) { - percentualRamStats[key] = - `${(dataTextureRamStats[key] / totalRamSize * 100).toFixed(2)} % of total`; - } - }); - - console.log (JSON.stringify({percentualRamUsage: percentualRamStats}, null, 4)); -}; - -class BindableDataTexture -{ - /** - * - * @param {WebGL2RenderingContext} gl - * @param {WebGLTexture} texture - * @param {int} textureWidth - * @param {int} textureHeight - * @param {TypedArray} textureData - */ - constructor(gl, texture, textureWidth, textureHeight, textureData = null) - { - /** - * The WebGL context. - * - * @type WebGL2RenderingContext - * @private - */ - this._gl = gl; - - /** - * The WebGLTexture handle. - * - * @type {WebGLTexture} - * @private - */ - this._texture = texture; - - /** - * The texture width. - * - * @type {number} - * @private - */ - this._textureWidth = textureWidth; - - /** - * The texture height. - * - * @type {number} - * @private - */ - this._textureHeight = textureHeight; - - /** - * (nullable) When the texture data array is kept in the JS side, it will be stored here. - * - * @type {TypedArray} - * @private - */ - this._textureData = textureData; - } - - /** - * Convenience method to be used by the renderers to bind the texture before draw calls. - * - * @param {Program} glProgram - * @param {string} shaderName The name of the shader attribute - * @param {number} glTextureUnit The WebGL texture unit - * - * @returns {bool} - */ - bindTexture (glProgram, shaderName, glTextureUnit) { - return glProgram.bindTexture (shaderName, this, glTextureUnit); - } - - /** - * - * Used internally by the `program` passed to `bindTexture` in order to bind the texture to an active `texture-unit`. - * - * @param {number} unit The WebGL texture unit - * - * @returns {bool} - * @private - */ - bind (unit) { - this._gl.activeTexture(this._gl["TEXTURE" + unit]); - this._gl.bindTexture(this._gl.TEXTURE_2D, this._texture); - return true; - } - - /** - * Used internally by the `program` passed to `bindTexture` in order to bind the texture to an active `texture-unit`. - * - * @param {number} unit The WebGL texture unit - * @private - */ - unbind (unit) { - // This `unbind` method is ignored at the moment to allow avoiding to rebind same texture already bound to a texture unit. - - // this._gl.activeTexture(this.state.gl["TEXTURE" + unit]); - // this._gl.bindTexture(this.state.gl.TEXTURE_2D, null); - } -} - -class DataTextureBuffer -{ - constructor () - { - this.positions = []; - this.indices8Bits = []; - this.indices16Bits = []; - this.indices32Bits = []; - this.edgeIndices8Bits = []; - this.edgeIndices16Bits = []; - this.edgeIndices32Bits = []; - this.edgeIndices = []; - this._objectDataColors = []; - this._objectDataPickColors = []; - this._vertexBasesForObject = []; - this._indexBaseOffsetsForObject = []; - this._edgeIndexBaseOffsetsForObject = []; - this._objectDataPositionsMatrices = []; - this._objectDataInstanceGeometryMatrices = []; - this._objectDataInstanceNormalsMatrices = []; - this._portionIdForIndices8Bits = []; - this._portionIdForIndices16Bits = []; - this._portionIdForIndices32Bits = []; - this._portionIdForEdges8Bits = []; - this._portionIdForEdges16Bits = []; - this._portionIdForEdges32Bits = []; - this._portionIdFanOut = []; - } -} - -class DataTextureState -{ - constructor () - { +export class DataTextureState { + constructor() { /** * Texture that holds colors/pickColors/flags/flags2 per-object: * - columns: one concept per column => color / pick-color / ... * - row: the object Id - * + * * @type BindableDataTexture */ this.texturePerObjectIdColorsAndFlags = null; @@ -204,7 +20,7 @@ class DataTextureState * Texture that holds the XYZ offsets per-object: * - columns: just 1 column with the XYZ-offset * - row: the object Id - * + * * @type BindableDataTexture */ this.texturePerObjectIdOffsets = null; @@ -213,119 +29,119 @@ class DataTextureState * Texture that holds the positionsDecodeMatrix per-object: * - columns: each column is one column of the matrix * - row: the object Id - * + * * @type BindableDataTexture */ this.texturePerObjectIdPositionsDecodeMatrix = null; /** * Texture that holds all the `different-vertices` used by the layer. - * + * * @type BindableDataTexture - */ + */ this.texturePerVertexIdCoordinates = null; /** * Texture that holds the PortionId that corresponds to a given polygon-id. - * + * * Variant of the texture for 8-bit based polygon-ids. - * + * * @type BindableDataTexture */ this.texturePerPolygonIdPortionIds8Bits = null; /** * Texture that holds the PortionId that corresponds to a given polygon-id. - * + * * Variant of the texture for 16-bit based polygon-ids. - * + * * @type BindableDataTexture */ this.texturePerPolygonIdPortionIds16Bits = null; /** * Texture that holds the PortionId that corresponds to a given polygon-id. - * + * * Variant of the texture for 32-bit based polygon-ids. - * + * * @type BindableDataTexture */ this.texturePerPolygonIdPortionIds32Bits = null; /** * Texture that holds the PortionId that corresponds to a given edge-id. - * + * * Variant of the texture for 8-bit based polygon-ids. - * + * * @type BindableDataTexture */ this.texturePerEdgeIdPortionIds8Bits = null; /** * Texture that holds the PortionId that corresponds to a given edge-id. - * + * * Variant of the texture for 16-bit based polygon-ids. - * + * * @type BindableDataTexture */ this.texturePerEdgeIdPortionIds16Bits = null; /** * Texture that holds the PortionId that corresponds to a given edge-id. - * + * * Variant of the texture for 32-bit based polygon-ids. - * + * * @type BindableDataTexture */ this.texturePerEdgeIdPortionIds32Bits = null; /** * Texture that holds the unique-vertex-indices for 8-bit based indices. - * + * * @type BindableDataTexture - */ + */ this.texturePerPolygonIdIndices8Bits = null; /** * Texture that holds the unique-vertex-indices for 16-bit based indices. - * + * * @type BindableDataTexture - */ + */ this.texturePerPolygonIdIndices16Bits = null; /** * Texture that holds the unique-vertex-indices for 32-bit based indices. - * + * * @type BindableDataTexture - */ + */ this.texturePerPolygonIdIndices32Bits = null; /** * Texture that holds the unique-vertex-indices for 8-bit based edge indices. - * + * * @type BindableDataTexture - */ + */ this.texturePerPolygonIdEdgeIndices8Bits = null; /** * Texture that holds the unique-vertex-indices for 16-bit based edge indices. - * + * * @type BindableDataTexture - */ + */ this.texturePerPolygonIdEdgeIndices16Bits = null; /** * Texture that holds the unique-vertex-indices for 32-bit based edge indices. - * + * * @type BindableDataTexture - */ + */ this.texturePerPolygonIdEdgeIndices32Bits = null; /** * Texture that holds the camera matrices * - columns: each column in the texture is a camera matrix column. * - row: each row is a different camera matrix. - * + * * @type BindableDataTexture */ this.textureCameraMatrices = null; @@ -334,7 +150,7 @@ class DataTextureState * Texture that holds the camera matrices, specific to ray-picking * - columns: each column in the texture is a camera matrix column. * - row: each row is a different camera matrix. - * + * * @type BindableDataTexture */ this.texturePickCameraMatrices = null; @@ -343,14 +159,13 @@ class DataTextureState * Texture that holds the model matrices * - columns: each column in the texture is a model matrix column. * - row: each row is a different model matrix. - * + * * @type BindableDataTexture */ this.textureModelMatrices = null; } - finalize() - { + finalize() { this.indicesPerBitnessTextures = { 8: this.texturePerPolygonIdIndices8Bits, 16: this.texturePerPolygonIdIndices16Bits, @@ -377,15 +192,15 @@ class DataTextureState } /** - * - * @param {Program} glProgram - * @param {string} objectMatricesTextureShaderName - * @param {string} vertexTextureShaderName - * @param {string} objectAttributesTextureShaderName - * @param {string} cameraMatricesShaderName - * @param {string} modelMatricesShaderName + * + * @param {Program} glProgram + * @param {string} objectMatricesTextureShaderName + * @param {string} vertexTextureShaderName + * @param {string} objectAttributesTextureShaderName + * @param {string} cameraMatricesShaderName + * @param {string} modelMatricesShaderName */ - bindCommonTextures ( + bindCommonTextures( glProgram, objectMatricesTextureShaderName, vertexTextureShaderName, @@ -394,1096 +209,106 @@ class DataTextureState modelMatricesShaderName, objectOffsetsShaderName ) { - this.texturePerObjectIdPositionsDecodeMatrix.bindTexture ( + this.texturePerObjectIdPositionsDecodeMatrix.bindTexture( glProgram, objectMatricesTextureShaderName, 1 // webgl texture unit ); - this.texturePerVertexIdCoordinates.bindTexture ( + this.texturePerVertexIdCoordinates.bindTexture( glProgram, - vertexTextureShaderName, + vertexTextureShaderName, 2 // webgl texture unit ); - - this.texturePerObjectIdColorsAndFlags.bindTexture ( + + this.texturePerObjectIdColorsAndFlags.bindTexture( glProgram, - objectAttributesTextureShaderName, + objectAttributesTextureShaderName, 3 // webgl texture unit ); - this.textureCameraMatrices.bindTexture ( + this.textureCameraMatrices.bindTexture( glProgram, - cameraMatricesShaderName, + cameraMatricesShaderName, 4 // webgl texture unit ); - this.textureModelMatrices.bindTexture ( + this.textureModelMatrices.bindTexture( glProgram, - modelMatricesShaderName, + modelMatricesShaderName, 5 // webgl texture unit ); - this.texturePerObjectIdOffsets.bindTexture ( + this.texturePerObjectIdOffsets.bindTexture( glProgram, - objectOffsetsShaderName, + objectOffsetsShaderName, 6 // webgl texture unit ); } /** - * - * @param {Program} glProgram - * @param {string} cameraMatricesShaderName + * + * @param {Program} glProgram + * @param {string} cameraMatricesShaderName */ - bindPickCameraTexture (glProgram, cameraMatricesShaderName) - { - this.texturePickCameraMatrices.bindTexture ( + bindPickCameraTexture(glProgram, cameraMatricesShaderName) { + this.texturePickCameraMatrices.bindTexture( glProgram, - cameraMatricesShaderName, + cameraMatricesShaderName, 4 // webgl texture unit ); } /** - * - * @param {Program} glProgram - * @param {string} portionIdsShaderName - * @param {string} polygonIndicesShaderName - * @param {8|16|32} textureBitness + * + * @param {Program} glProgram + * @param {string} portionIdsShaderName + * @param {string} polygonIndicesShaderName + * @param {8|16|32} textureBitness */ - bindTriangleIndicesTextures ( + bindTriangleIndicesTextures( glProgram, portionIdsShaderName, polygonIndicesShaderName, textureBitness, ) { - this.indicesPortionIdsPerBitnessTextures[textureBitness].bindTexture ( + this.indicesPortionIdsPerBitnessTextures[textureBitness].bindTexture( glProgram, - portionIdsShaderName, + portionIdsShaderName, 7 // webgl texture unit - ); + ); - this.indicesPerBitnessTextures[textureBitness].bindTexture ( + this.indicesPerBitnessTextures[textureBitness].bindTexture( glProgram, - polygonIndicesShaderName, + polygonIndicesShaderName, 8 // webgl texture unit ); } /** - * - * @param {Program} glProgram - * @param {string} edgePortionIdsShaderName - * @param {string} edgeIndicesShaderName - * @param {8|16|32} textureBitness + * + * @param {Program} glProgram + * @param {string} edgePortionIdsShaderName + * @param {string} edgeIndicesShaderName + * @param {8|16|32} textureBitness */ - bindEdgeIndicesTextures ( + bindEdgeIndicesTextures( glProgram, edgePortionIdsShaderName, edgeIndicesShaderName, textureBitness, ) { - this.edgeIndicesPortionIdsPerBitnessTextures[textureBitness].bindTexture ( + this.edgeIndicesPortionIdsPerBitnessTextures[textureBitness].bindTexture( glProgram, - edgePortionIdsShaderName, + edgePortionIdsShaderName, 7 // webgl texture unit - ); + ); - this.edgeIndicesPerBitnessTextures[textureBitness].bindTexture ( + this.edgeIndicesPerBitnessTextures[textureBitness].bindTexture( glProgram, - edgeIndicesShaderName, + edgeIndicesShaderName, 8 // webgl texture unit ); } } -class DataTextureGenerator -{ - /** - * Enables the currently binded ````WebGLTexture```` to be used as a data texture. - * - * @param {WebGL2RenderingContext} gl - * - * @private - */ - disableBindedTextureFiltering (gl) - { - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - } - - /** - * Generate and return a `camera data texture`. - * - * The texture will automatically update its contents before each render when the camera matrix is dirty, - * and to do so will use the following events: - * - * - `scene.rendering` event will be used to know that the camera texture should be updated - * - `camera.matrix` event will be used to know that the camera matices changed - * - * @param {WebGL2RenderingContext} gl - * @param {Camera} camera - * @param {Scene} scene - * @param {null|number[3]} origin - * - * @returns {BindableDataTexture} - */ - generateCameraDataTexture (gl, camera, scene, origin, followCameraUpdate = true) - { - const textureWidth = 4; - const textureHeight = 3; // space for 3 matrices - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, textureWidth, textureHeight); - - this.disableBindedTextureFiltering (gl); - - gl.bindTexture (gl.TEXTURE_2D, null); - - const cameraTexture = new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight - ); - - let cameraDirty = true; - - cameraTexture._updateViewMatrix = (viewMatrix, projMatrix) => { - gl.bindTexture (gl.TEXTURE_2D, cameraTexture._texture); - - // Camera's "view matrix" - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 0, // 1st matrix: camera view matrix - 4, - 1, - gl.RGBA, - gl.FLOAT, - new Float32Array ((origin) ? createRTCViewMat(viewMatrix, origin) : viewMatrix) - ); - - // Camera's "view normal matrix" - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 1, // 2nd matrix: camera view normal matrix - 4, - 1, - gl.RGBA, - gl.FLOAT, - new Float32Array (camera.viewNormalMatrix) - ); - - // Camera's "project matrix" - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 2, // 3rd matrix: camera project matrix - 4, - 1, - gl.RGBA, - gl.FLOAT, - new Float32Array (projMatrix) - ); - }; - - if (followCameraUpdate) - { - const onCameraMatrix = () => { - if (!cameraDirty) { - return; - } - - cameraDirty = false; - - cameraTexture._updateViewMatrix (camera.viewMatrix, camera.project.matrix); - }; - - camera.on ("matrix", () => cameraDirty = true); - - scene.on ("rendering", onCameraMatrix); - - onCameraMatrix (); - } - - return cameraTexture; - } - - /** - * Generate and return a `model data texture`. - * - * @param {WebGL2RenderingContext} gl - * @param {PerformanceModel} model - * - * @returns {BindableDataTexture} - */ - generatePeformanceModelDataTexture (gl, model) - { - const textureWidth = 4; - const textureHeight = 2; // space for 2 matrices - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, textureWidth, textureHeight); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, // x-offset - 0, // y-offset (model world matrix) - 4, // data width (4x4 values) - 1, // data height (1 matrix) - gl.RGBA, - gl.FLOAT, - new Float32Array (model.worldMatrix) - ); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, // x-offset - 1, // y-offset (model normal matrix) - 4, // data width (4x4 values) - 1, // data height (1 matrix) - gl.RGBA, - gl.FLOAT, - new Float32Array (model.worldNormalMatrix) - ); - - this.disableBindedTextureFiltering (gl); - - gl.bindTexture (gl.TEXTURE_2D, null); - - return new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight - ); - } - - /** - * This will generate an RGBA texture for: - * - colors - * - pickColors - * - flags - * - flags2 - * - vertex bases - * - vertex base offsets - * - * The texture will have: - * - 4 RGBA columns per row: for each object (pick) color and flags(2) - * - N rows where N is the number of objects - * - * @param {WebGL2RenderingContext} gl - * @param {ArrayLike>} colors Array of colors for all objects in the layer - * @param {ArrayLike>} pickColors Array of pickColors for all objects in the layer - * @param {ArrayLike} vertexBases Array of position-index-bases foteh all objects in the layer - * @param {ArrayLike} indexBaseOffsets For triangles: array of offests between the (gl_VertexID / 3) and the position where the indices start in the texture layer - * @param {ArrayLike} edgeIndexBaseOffsets For edges: Array of offests between the (gl_VertexID / 2) and the position where the edge indices start in the texture layer - * @param {ArrayLike} solid Array is-solid flag for all objects in the layer - * - * @returns {BindableDataTexture} - */ - generateTextureForColorsAndFlags (gl, colors, pickColors, vertexBases, indexBaseOffsets, edgeIndexBaseOffsets, solid) { - const numPortions = colors.length; - - // The number of rows in the texture is the number of - // objects in the layer. - - this.numPortions = numPortions; - - const textureWidth = 512 * 8; - const textureHeight = Math.ceil (numPortions / (textureWidth / 8)); - - if (textureHeight == 0) - { - throw "texture height == 0"; - } - - // 8 columns per texture row: - // - col0: (RGBA) object color RGBA - // - col1: (packed Uint32 as RGBA) object pick color - // - col2: (packed 4 bytes as RGBA) object flags - // - col3: (packed 4 bytes as RGBA) object flags2 - // - col4: (packed Uint32 bytes as RGBA) vertex base - // - col5: (packed Uint32 bytes as RGBA) index base offset - // - col6: (packed Uint32 bytes as RGBA) edge index base offset - // - col7: (packed 4 bytes as RGBA) is-solid flag for objects - - const texArray = new Uint8Array (4 * textureWidth * textureHeight); - - dataTextureRamStats.sizeDataColorsAndFlags += texArray.byteLength; - dataTextureRamStats.numberOfTextures++; - - for (let i = 0; i < numPortions; i++) - { - // object color - texArray.set ( - colors [i], - i * 32 + 0 - ); - - // object pick color - texArray.set ( - pickColors [i], - i * 32 + 4 - ); - - // object flags - texArray.set ( - [ - 0, 0, 0, 0 - ], - i * 32 + 8 - ); - - // object flags2 - texArray.set ( - [ - 0, 0, 0, 0 - ], - i * 32 + 12 - ); - - // vertex base - texArray.set ( - [ - (vertexBases[i] >> 24) & 255, - (vertexBases[i] >> 16) & 255, - (vertexBases[i] >> 8) & 255, - (vertexBases[i]) & 255, - ], - i * 32 + 16 - ); - - // triangles index base offset - texArray.set ( - [ - (indexBaseOffsets[i] >> 24) & 255, - (indexBaseOffsets[i] >> 16) & 255, - (indexBaseOffsets[i] >> 8) & 255, - (indexBaseOffsets[i]) & 255, - ], - i * 32 + 20 - ); - - // edge index base offset - texArray.set ( - [ - (edgeIndexBaseOffsets[i] >> 24) & 255, - (edgeIndexBaseOffsets[i] >> 16) & 255, - (edgeIndexBaseOffsets[i] >> 8) & 255, - (edgeIndexBaseOffsets[i]) & 255, - ], - i * 32 + 24 - ); - - // is-solid flag - texArray.set ( - [ - solid[i] ? 1 : 0, - 0, - 0, - 0, - ], - i * 32 + 28 - ); - } - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8UI, textureWidth, textureHeight); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 0, - textureWidth, - textureHeight, - gl.RGBA_INTEGER, - gl.UNSIGNED_BYTE, - texArray, - 0 - ); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE); - - gl.bindTexture(gl.TEXTURE_2D, null); - - return new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight, - texArray - ); - } - - /** - * This will generate a texture for all object offsets. - * - * @param {WebGL2RenderingContext} gl - * @param {int[]} offsets Array of int[3], one XYZ offset array for each object - * - * @returns {BindableDataTexture} - */ - generateTextureForObjectOffsets (gl, numOffsets) { - const textureWidth = 512; - const textureHeight = Math.ceil (numOffsets / textureWidth); - - if (textureHeight == 0) - { - throw "texture height == 0"; - } - - var texArray = new Float32Array(3 * textureWidth * textureHeight).fill(0); - - dataTextureRamStats.sizeDataTextureOffsets += texArray.byteLength; - dataTextureRamStats.numberOfTextures++; - - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB32F, textureWidth, textureHeight); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 0, - textureWidth, - textureHeight, - gl.RGB, - gl.FLOAT, - texArray, - 0 - ); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE); - - gl.bindTexture(gl.TEXTURE_2D, null); - - return new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight, - texArray - ); - } - /** - * This will generate a texture for all positions decode matrices in the layer. - * - * The texture will have: - * - 4 RGBA columns per row (each column will contain 4 packed half-float (16 bits) components). - * Thus, each row will contain 16 packed half-floats corresponding to a complete positions decode matrix) - * - N rows where N is the number of objects - * - * @param {WebGL2RenderingContext} gl - * @param {ArrayLike} positionDecodeMatrices Array of positions decode matrices for all objects in the layer - * @param {ArrayLike} instanceMatrices Array of geometry instancing matrices for all objects in the layer. Null if the objects are not instanced. - * - * @returns {BindableDataTexture} - */ - generateTextureForPositionsDecodeMatrices (gl, positionDecodeMatrices, instanceMatrices) { - const numMatrices = positionDecodeMatrices.length; - - if (numMatrices == 0) - { - throw "num decode+entity matrices == 0"; - } - - // in one row we can fit 512 matrices - const textureWidth = 512 * 4; - const textureHeight = Math.ceil (numMatrices / (textureWidth / 4)); - - var texArray = new Float32Array(4 * textureWidth * textureHeight); - - dataTextureRamStats.sizeDataPositionDecodeMatrices += texArray.byteLength; - dataTextureRamStats.numberOfTextures++; - - const tmpMatrix = math.mat4(); - - for (var i = 0; i < positionDecodeMatrices.length; i++) - { - // 4x4 values - texArray.set ( - math.mulMat4( - instanceMatrices[i], - positionDecodeMatrices[i], - tmpMatrix - ), - i * 16 - ); - } - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, textureWidth, textureHeight); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 0, - textureWidth, - textureHeight, - gl.RGBA, - gl.FLOAT, - texArray, - 0 - ); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE); - - gl.bindTexture(gl.TEXTURE_2D, null); - - return new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight - ); - } - - /** - * @param {WebGL2RenderingContext} gl - * @param {ArrayLike} indices - * - * @returns {BindableDataTexture} - */ - generateTextureFor8BitIndices (gl, indices) { - if (indices.length == 0) { - return { - texture: null, - textureHeight: 0, - }; - } - - const textureWidth = 4096; - const textureHeight = Math.ceil (indices.length / 3 / textureWidth); - - if (textureHeight == 0) - { - throw "texture height == 0"; - } - - const texArraySize = textureWidth * textureHeight * 3; - const texArray = new Uint8Array (texArraySize); - - dataTextureRamStats.sizeDataTextureIndices += texArray.byteLength; - dataTextureRamStats.numberOfTextures++; - - texArray.fill(0); - texArray.set(indices, 0) - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB8UI, textureWidth, textureHeight); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 0, - textureWidth, - textureHeight, - gl.RGB_INTEGER, - gl.UNSIGNED_BYTE, - texArray, - 0 - ); - - this.disableBindedTextureFiltering (gl); - - gl.bindTexture(gl.TEXTURE_2D, null); - - return new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight - ); - } - - /** - * @param {WebGL2RenderingContext} gl - * @param {ArrayLike} indices - * - * @returns {BindableDataTexture} - */ - generateTextureFor16BitIndices (gl, indices) { - if (indices.length == 0) { - return { - texture: null, - textureHeight: 0, - }; - } - const textureWidth = 4096; - const textureHeight = Math.ceil (indices.length / 3 / textureWidth); - - if (textureHeight == 0) - { - throw "texture height == 0"; - } - - const texArraySize = textureWidth * textureHeight * 3; - const texArray = new Uint16Array (texArraySize); - - dataTextureRamStats.sizeDataTextureIndices += texArray.byteLength; - dataTextureRamStats.numberOfTextures++; - - texArray.fill(0); - texArray.set(indices, 0) - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB16UI, textureWidth, textureHeight); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 0, - textureWidth, - textureHeight, - gl.RGB_INTEGER, - gl.UNSIGNED_SHORT, - texArray, - 0 - ); - - this.disableBindedTextureFiltering (gl); - - gl.bindTexture(gl.TEXTURE_2D, null); - - return new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight - ); - } - - /** - * @param {WebGL2RenderingContext} gl - * @param {ArrayLike} indices - * - * @returns {BindableDataTexture} - */ - generateTextureFor32BitIndices (gl, indices) { - if (indices.length == 0) { - return { - texture: null, - textureHeight: 0, - }; - } - - const textureWidth = 4096; - const textureHeight = Math.ceil (indices.length / 3 / textureWidth); - - if (textureHeight == 0) - { - throw "texture height == 0"; - } - - const texArraySize = textureWidth * textureHeight * 3; - const texArray = new Uint32Array (texArraySize); - - dataTextureRamStats.sizeDataTextureIndices += texArray.byteLength; - dataTextureRamStats.numberOfTextures++; - - texArray.fill(0); - texArray.set(indices, 0) - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB32UI, textureWidth, textureHeight); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 0, - textureWidth, - textureHeight, - gl.RGB_INTEGER, - gl.UNSIGNED_INT, - texArray, - 0 - ); - - this.disableBindedTextureFiltering (gl); - - gl.bindTexture(gl.TEXTURE_2D, null); - - return new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight - ); - } - - /** - * @param {WebGL2RenderingContext} gl - * @param {ArrayLike} edgeIndices - * - * @returns {BindableDataTexture} - */ - generateTextureFor8BitsEdgeIndices (gl, edgeIndices) { - if (edgeIndices.length == 0) { - return { - texture: null, - textureHeight: 0, - }; - } - - const textureWidth = 4096; - const textureHeight = Math.ceil (edgeIndices.length / 2 / textureWidth); - - if (textureHeight == 0) - { - throw "texture height == 0"; - } - - const texArraySize = textureWidth * textureHeight * 2; - const texArray = new Uint8Array (texArraySize); - - dataTextureRamStats.sizeDataTextureEdgeIndices += texArray.byteLength; - dataTextureRamStats.numberOfTextures++; - - texArray.fill(0); - texArray.set(edgeIndices, 0) - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RG8UI, textureWidth, textureHeight); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 0, - textureWidth, - textureHeight, - gl.RG_INTEGER, - gl.UNSIGNED_BYTE, - texArray, - 0 - ); - - this.disableBindedTextureFiltering (gl); - - gl.bindTexture(gl.TEXTURE_2D, null); - - return new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight - ); - } - - /** - * @param {WebGL2RenderingContext} gl - * @param {ArrayLike} edgeIndices - * - * @returns {BindableDataTexture} - */ - generateTextureFor16BitsEdgeIndices (gl, edgeIndices) { - if (edgeIndices.length == 0) { - return { - texture: null, - textureHeight: 0, - }; - } - - const textureWidth = 4096; - const textureHeight = Math.ceil (edgeIndices.length / 2 / textureWidth); - - if (textureHeight == 0) - { - throw "texture height == 0"; - } - - const texArraySize = textureWidth * textureHeight * 2; - const texArray = new Uint16Array (texArraySize); - - dataTextureRamStats.sizeDataTextureEdgeIndices += texArray.byteLength; - dataTextureRamStats.numberOfTextures++; - - texArray.fill(0); - texArray.set(edgeIndices, 0) - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RG16UI, textureWidth, textureHeight); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 0, - textureWidth, - textureHeight, - gl.RG_INTEGER, - gl.UNSIGNED_SHORT, - texArray, - 0 - ); - - this.disableBindedTextureFiltering (gl); - - gl.bindTexture(gl.TEXTURE_2D, null); - - return new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight - ); - } - - /** - * @param {WebGL2RenderingContext} gl - * @param {ArrayLike} edgeIndices - * - * @returns {BindableDataTexture} - */ - generateTextureFor32BitsEdgeIndices (gl, edgeIndices) { - if (edgeIndices.length == 0) { - return { - texture: null, - textureHeight: 0, - }; - } - - const textureWidth = 4096; - const textureHeight = Math.ceil (edgeIndices.length / 2 / textureWidth); - - if (textureHeight == 0) - { - throw "texture height == 0"; - } - - const texArraySize = textureWidth * textureHeight * 2; - const texArray = new Uint32Array (texArraySize); - - dataTextureRamStats.sizeDataTextureEdgeIndices += texArray.byteLength; - dataTextureRamStats.numberOfTextures++; - - texArray.fill(0); - texArray.set(edgeIndices, 0) - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RG32UI, textureWidth, textureHeight); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 0, - textureWidth, - textureHeight, - gl.RG_INTEGER, - gl.UNSIGNED_INT, - texArray, - 0 - ); - - this.disableBindedTextureFiltering (gl); - - gl.bindTexture(gl.TEXTURE_2D, null); - - return new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight - ); - } - - /** - * @param {WebGL2RenderingContext} gl - * @param {ArrayLike} positions Array of (uniquified) quantized positions in the layer - * - * This will generate a texture for positions in the layer. - * - * The texture will have: - * - 1024 columns, where each pixel will be a 16-bit-per-component RGB texture, corresponding to the XYZ of the position - * - a number of rows R where R*1024 is just >= than the number of vertices (positions / 3) - * - * @returns {BindableDataTexture} - */ - generateTextureForPositions (gl, positions) { - const numVertices = positions.length / 3; - const textureWidth = 4096; - const textureHeight = Math.ceil (numVertices / textureWidth); - - if (textureHeight == 0) - { - throw "texture height == 0"; - } - - const texArraySize = textureWidth * textureHeight * 3; - const texArray = new Uint16Array (texArraySize); - - dataTextureRamStats.sizeDataTexturePositions += texArray.byteLength; - dataTextureRamStats.numberOfTextures++; - - texArray.fill(0); - - texArray.set (positions, 0); - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB16UI, textureWidth, textureHeight); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 0, - textureWidth, - textureHeight, - gl.RGB_INTEGER, - gl.UNSIGNED_SHORT, - texArray, - 0 - ); - - gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE); - - gl.bindTexture(gl.TEXTURE_2D, null); - - return new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight - ); - } - - /** - * @param {WebGL2RenderingContext} gl - * @param {ArrayLike} portionIdsArray - * - * @returns {BindableDataTexture} - */ - generateTextureForPackedPortionIds (gl, portionIdsArray) { - if (portionIdsArray.length == 0) { - return { - texture: null, - textureHeight: 0, - }; - } - const lenArray = portionIdsArray.length; - const textureWidth = 4096; - const textureHeight = Math.ceil (lenArray / textureWidth); - - if (textureHeight == 0) - { - throw "texture height == 0"; - } - - const texArraySize = textureWidth * textureHeight; - const texArray = new Uint16Array (texArraySize); - - texArray.set ( - portionIdsArray, - 0 - ); - - dataTextureRamStats.sizeDataTexturePortionIds += texArray.byteLength; - dataTextureRamStats.numberOfTextures++; - - const texture = gl.createTexture(); - - gl.bindTexture (gl.TEXTURE_2D, texture); - - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R16UI, textureWidth, textureHeight); - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - 0, - 0, - textureWidth, - textureHeight, - gl.RED_INTEGER, - gl.UNSIGNED_SHORT, - texArray, - 0 - ); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE); - - gl.bindTexture(gl.TEXTURE_2D, null); - - return new BindableDataTexture( - gl, - texture, - textureWidth, - textureHeight - ); - } -} - -export { - dataTextureRamStats, - DataTextureState, - DataTextureBuffer, - DataTextureGenerator, -} \ No newline at end of file diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/TrianglesDataTextureLayer.js b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/TrianglesDataTextureLayer.js index f5d4c6e1a..fcea179f5 100644 --- a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/TrianglesDataTextureLayer.js +++ b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/TrianglesDataTextureLayer.js @@ -6,9 +6,11 @@ import {RenderState} from "../../../../../webgl/RenderState.js"; import {geometryCompressionUtils} from "../../../../../math/geometryCompressionUtils.js"; import {getDataTextureRenderers} from "./TrianglesDataTextureRenderers.js"; import {TrianglesDataTextureBuffer} from "./TrianglesDataTextureBuffer.js"; -import {DataTextureGenerator, dataTextureRamStats, DataTextureState} from "../DataTextureState.js" +import {DataTextureState} from "../DataTextureState.js" import {DataTextureSceneModel} from '../../../DataTextureSceneModel.js'; +import {DataTextureGenerator} from "../DataTextureGenerator"; +import {dataTextureRamStats} from "./dataTextureRamStats"; /** * 12-bits allowed for object ids. @@ -40,8 +42,6 @@ const INDICES_EDGE_INDICES_ALIGNEMENT_SIZE = 8; const MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE = 10; const identityMatrix = math.identityMat4(); -const tempMat4 = math.mat4(); -const tempMat4b = math.mat4(); const tempVec4a = math.vec4([0, 0, 0, 1]); const tempVec4b = math.vec4([0, 0, 0, 1]); const tempVec4c = math.vec4([0, 0, 0, 1]); @@ -51,14 +51,6 @@ const tempUint8Array4 = new Uint8Array(4); const tempFloat32Array3 = new Float32Array(3); -const tempVec3a = math.vec3(); -const tempVec3b = math.vec3(); -const tempVec3c = math.vec3(); -const tempVec3d = math.vec3(); -const tempVec3e = math.vec3(); -const tempVec3f = math.vec3(); -const tempVec3g = math.vec3(); - let _numberOfLayers = 0; /** @@ -103,7 +95,7 @@ class TrianglesDataTextureLayer { this.dataTextureGenerator = new DataTextureGenerator(); this._state = new RenderState({ - origin: math.vec3(), + origin: math.vec3(cfg.origin), metallicRoughnessBuf: null, positionsDecodeMatrix: math.mat4(), textureState: this._dataTextureState, @@ -193,11 +185,7 @@ class TrianglesDataTextureLayer { (instancingGeometryId + "#0") in this._instancedGeometrySubPortionData; if (!alreadyHasPortionGeometry) { - const maxIndicesOfAnyBits = Math.max( - state.numIndices8Bits, - state.numIndices16Bits, - state.numIndices32Bits, - ); + const maxIndicesOfAnyBits = Math.max(state.numIndices8Bits, state.numIndices16Bits, state.numIndices32Bits,); let numVertices = 0; let numIndices = geometryCfg.indices.length / 3; @@ -215,12 +203,6 @@ class TrianglesDataTextureLayer { (state.numVertices + numVertices) <= MAX_DATA_TEXTURE_HEIGHT * 4096 && (maxIndicesOfAnyBits + numIndices) <= MAX_DATA_TEXTURE_HEIGHT * 4096; } - - // if (!retVal) - // { - // console.log ("Cannot create portion!"); - // } - return retVal; } @@ -229,15 +211,15 @@ class TrianglesDataTextureLayer { * * Gives the portion the specified geometry, color and matrix. * - * @param cfg.positions Flat float Local-space positions array. - * @param [cfg.normals] Flat float normals array. - * @param [cfg.colors] Flat float colors array. - * @param cfg.indices Flat int indices array. - * @param [cfg.edgeIndices] Flat int edges indices array. - * @param cfg.color Quantized RGB color [0..255,0..255,0..255,0..255] - * @param cfg.metallic Metalness factor [0..255] - * @param cfg.roughness Roughness factor [0..255] - * @param cfg.opacity Opacity [0..255] + * @param geometryCfg.positions Flat float Local-space positions array. + * @param [geometryCfg.normals] Flat float normals array. + * @param [geometryCfg.colors] Flat float colors array. + * @param geometryCfg.indices Flat int indices array. + * @param [geometryCfg.edgeIndices] Flat int edges indices array. + * @param geometryCfg.color Quantized RGB color [0..255,0..255,0..255,0..255] + * @param geometryCfg.metallic Metalness factor [0..255] + * @param geometryCfg.roughness Roughness factor [0..255] + * @param geometryCfg.opacity Opacity [0..255] * @param [cfg.meshMatrix] Flat float 4x4 matrix * @param [cfg.worldMatrix] Flat float 4x4 matrix * @param cfg.worldAABB Flat float AABB World-space AABB @@ -248,51 +230,38 @@ class TrianglesDataTextureLayer { if (this._finalized) { throw "Already finalized"; } - const instancing = objectCfg !== null; - - let portionId = this._subPortionIdMapping.length; - + const portionId = this._subPortionIdMapping.length; const portionIdFanout = []; this._subPortionIdMapping.push(portionIdFanout); - const objectAABB = instancing ? objectCfg.aabb : geometryCfg.aabb; - geometryCfg.preparedBuckets.forEach((bucketGeometry, bucketIndex) => { let geometrySubPortionData; - if (instancing) { const key = geometryCfg.id + "#" + bucketIndex; - if (!(key in this._instancedGeometrySubPortionData)) { this._instancedGeometrySubPortionData[key] = this.createSubPortionGeometry(bucketGeometry); } - geometrySubPortionData = this._instancedGeometrySubPortionData[key]; } else { geometrySubPortionData = this.createSubPortionGeometry(bucketGeometry); } - const aabb = math.collapseAABB3(); - const subPortionId = this.createSubPortionObject( instancing ? objectCfg : geometryCfg, geometrySubPortionData, bucketGeometry.positions, geometryCfg.positionsDecodeMatrix, - instancing ? objectCfg.origin : geometryCfg.origin, + objectCfg.origin, + // math.addVec3( instancing ? objectCfg.origin : geometryCfg.origin, this._state.origin, math.vec3()), aabb, instancing, geometryCfg.solid ); - math.expandAABB3(objectAABB, aabb); - portionIdFanout.push(subPortionId); }); - this.model.numPortions++; - return portionId; } @@ -536,15 +505,15 @@ class TrianglesDataTextureLayer { // Adjust the world AABB with the object `origin` - if (origin) { - this._state.origin = origin; - worldAABB[0] += origin[0]; - worldAABB[1] += origin[1]; - worldAABB[2] += origin[2]; - worldAABB[3] += origin[0]; - worldAABB[4] += origin[1]; - worldAABB[5] += origin[2]; - } + // if (this._state.origin) { + // const origin = this._state.origin; + worldAABB[0] += origin[0]; + worldAABB[1] += origin[1]; + worldAABB[2] += origin[2]; + worldAABB[3] += origin[0]; + worldAABB[4] += origin[1]; + worldAABB[5] += origin[2]; + // } math.expandAABB3(this.aabb, worldAABB); @@ -677,6 +646,10 @@ class TrianglesDataTextureLayer { return subPortionId; } + // updatePickCameratexture(pickViewMatrix, pickCameraMatrix) { + // this._dataTextureState.texturePickCameraMatrices._updateViewMatrix(pickViewMatrix, pickCameraMatrix); + // } + /** * Builds data textures from the appended geometries and loads them into the GPU. * @@ -826,30 +799,26 @@ class TrianglesDataTextureLayer { textureState.textureModelMatrices = this.model._modelMatricesTexture; // Camera textures - if (!this.model.cameraTexture) { - this.model.cameraTexture = this.dataTextureGenerator.generateCameraDataTexture( - this.model.scene.canvas.gl, - this.model.scene.camera, - this.model.scene, - this._state.origin.slice() - ); - } - textureState.textureCameraMatrices = this.model.cameraTexture; + textureState.cameraTexture = this.dataTextureGenerator.generateCameraDataTexture( + this.model.scene.canvas.gl, + this.model.scene.camera, + this.model.scene, + this._state.origin.slice() + ); + + textureState.textureCameraMatrices = textureState.cameraTexture; - if (!this.model.pickCameraTexture) { - this.model.pickCameraTexture = this.dataTextureGenerator.generateCameraDataTexture( - this.model.scene.canvas.gl, - this.model.scene.camera, - this.model.scene, - this._state.origin.slice(), - false - ); - } + textureState.pickCameraTexture = this.dataTextureGenerator.generateCameraDataTexture( + this.model.scene.canvas.gl, + this.model.scene.camera, + this.model.scene, + this._state.origin.slice(), + false + ); - textureState.texturePickCameraMatrices = this.model.pickCameraTexture; + textureState.texturePickCameraMatrices = textureState.pickCameraTexture; - // Mark the data-texture-state as finalized textureState.finalize(); // Free up memory @@ -860,13 +829,11 @@ class TrianglesDataTextureLayer { } attachToRenderingEvent() { - const self = this; - - this.model.scene.on("rendering", function () { - if (self._deferredSetFlagsDirty) { - self.commitDeferredFlags(); + this.model.scene.on("rendering", () =>{ + if (this._deferredSetFlagsDirty) { + this.commitDeferredFlags(); } - self.numUpdatesInFrame = 0; + this.numUpdatesInFrame = 0; }); } @@ -1036,18 +1003,13 @@ class TrianglesDataTextureLayer { */ commitDeferredFlags() { this._deferredSetFlagsActive = false; - if (!this._deferredSetFlagsDirty) { return; } - this._deferredSetFlagsDirty = false; - const gl = this.model.scene.canvas.gl; const textureState = this._dataTextureState; - gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectIdColorsAndFlags._texture); - gl.texSubImage2D( gl.TEXTURE_2D, 0, // level @@ -1059,9 +1021,7 @@ class TrianglesDataTextureLayer { gl.UNSIGNED_BYTE, textureState.texturePerObjectIdColorsAndFlags._textureData ); - gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectIdOffsets._texture); - gl.texSubImage2D( gl.TEXTURE_2D, 0, // level @@ -1079,7 +1039,6 @@ class TrianglesDataTextureLayer { if (!this._finalized) { throw "Not finalized"; } - if (flags & ENTITY_FLAGS.CULLED) { this._numCulledLayerPortions += this._subPortionIdMapping[portionId].length; this.model.numCulledLayerPortions++; @@ -1112,7 +1071,6 @@ class TrianglesDataTextureLayer { setColor(portionId, color) { const subPortionMapping = this._subPortionIdMapping[portionId]; - for (let i = 0, len = subPortionMapping.length; i < len; i++) { this._subPortionSetColor(subPortionMapping[i], color); } @@ -1125,33 +1083,23 @@ class TrianglesDataTextureLayer { if (!this._finalized) { throw "Not finalized"; } - // Color const textureState = this._dataTextureState; const gl = this.model.scene.canvas.gl; - tempUint8Array4 [0] = color[0]; tempUint8Array4 [1] = color[1]; tempUint8Array4 [2] = color[2]; tempUint8Array4 [3] = color[3]; - // object colors - textureState.texturePerObjectIdColorsAndFlags._textureData.set( - tempUint8Array4, - portionId * 32 - ); - + textureState.texturePerObjectIdColorsAndFlags._textureData.set(tempUint8Array4, portionId * 32); if (this._deferredSetFlagsActive) { this._deferredSetFlagsDirty = true; return; } - if (++this.numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE) { this.beginDeferredFlags(); } - gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectIdColorsAndFlags._texture); - gl.texSubImage2D( gl.TEXTURE_2D, 0, // level @@ -1163,7 +1111,6 @@ class TrianglesDataTextureLayer { gl.UNSIGNED_BYTE, tempUint8Array4 ); - // gl.bindTexture (gl.TEXTURE_2D, null); } @@ -1180,7 +1127,6 @@ class TrianglesDataTextureLayer { _setFlags(portionId, flags, transparent, deferred = false) { const subPortionMapping = this._subPortionIdMapping[portionId]; - for (let i = 0, len = subPortionMapping.length; i < len; i++) { this._subPortionSetFlags(subPortionMapping[i], flags, transparent); } @@ -1254,32 +1200,22 @@ class TrianglesDataTextureLayer { // Pick let f3 = (visible && !culled && pickable) ? RENDER_PASSES.PICK : RENDER_PASSES.NOT_RENDERED; - const textureState = this._dataTextureState; const gl = this.model.scene.canvas.gl; - tempUint8Array4 [0] = f0; tempUint8Array4 [1] = f1; tempUint8Array4 [2] = f2; tempUint8Array4 [3] = f3; - // object flags - textureState.texturePerObjectIdColorsAndFlags._textureData.set( - tempUint8Array4, - portionId * 32 + 8 - ); - + textureState.texturePerObjectIdColorsAndFlags._textureData.set(tempUint8Array4, portionId * 32 + 8); if (this._deferredSetFlagsActive) { this._deferredSetFlagsDirty = true; return; } - if (++this.numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE) { this.beginDeferredFlags(); } - gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectIdColorsAndFlags._texture); - gl.texSubImage2D( gl.TEXTURE_2D, 0, // level @@ -1291,7 +1227,6 @@ class TrianglesDataTextureLayer { gl.UNSIGNED_BYTE, tempUint8Array4 ); - // gl.bindTexture (gl.TEXTURE_2D, null); } @@ -1300,7 +1235,6 @@ class TrianglesDataTextureLayer { _setFlags2(portionId, flags, deferred = false) { const subPortionMapping = this._subPortionIdMapping[portionId]; - for (let i = 0, len = subPortionMapping.length; i < len; i++) { this._subPortionSetFlags2(subPortionMapping[i], flags); } @@ -1310,34 +1244,23 @@ class TrianglesDataTextureLayer { if (!this._finalized) { throw "Not finalized"; } - const clippable = !!(flags & ENTITY_FLAGS.CLIPPABLE) ? 255 : 0; - const textureState = this._dataTextureState; const gl = this.model.scene.canvas.gl; - tempUint8Array4 [0] = clippable; tempUint8Array4 [1] = 0; tempUint8Array4 [2] = 1; tempUint8Array4 [3] = 2; - // object flags2 - textureState.texturePerObjectIdColorsAndFlags._textureData.set( - tempUint8Array4, - portionId * 32 + 12 - ); - + textureState.texturePerObjectIdColorsAndFlags._textureData.set(tempUint8Array4, portionId * 32 + 12); if (this._deferredSetFlagsActive) { this._deferredSetFlagsDirty = true; return; } - if (++this.numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE) { this.beginDeferredFlags(); } - gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectIdColorsAndFlags._texture); - gl.texSubImage2D( gl.TEXTURE_2D, 0, // level @@ -1349,7 +1272,6 @@ class TrianglesDataTextureLayer { gl.UNSIGNED_BYTE, tempUint8Array4 ); - // gl.bindTexture (gl.TEXTURE_2D, null); } @@ -1359,7 +1281,6 @@ class TrianglesDataTextureLayer { setOffset(portionId, offset) { const subPortionMapping = this._subPortionIdMapping[portionId]; - for (let i = 0, len = subPortionMapping.length; i < len; i++) { this._subPortionSetOffset(subPortionMapping[i], offset); } @@ -1373,31 +1294,21 @@ class TrianglesDataTextureLayer { // this.model.error("Entity#offset not enabled for this Viewer"); // See Viewer entityOffsetsEnabled // return; // } - const textureState = this._dataTextureState; const gl = this.model.scene.canvas.gl; - tempFloat32Array3 [0] = offset[0]; tempFloat32Array3 [1] = offset[1]; tempFloat32Array3 [2] = offset[2]; - // object offset - textureState.texturePerObjectIdOffsets._textureData.set( - tempFloat32Array3, - portionId * 3 - ); - + textureState.texturePerObjectIdOffsets._textureData.set(tempFloat32Array3, portionId * 3); if (this._deferredSetFlagsActive) { this._deferredSetFlagsDirty = true; return; } - if (++this.numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE) { this.beginDeferredFlags(); } - gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectIdOffsets._texture); - gl.texSubImage2D( gl.TEXTURE_2D, 0, // level @@ -1409,7 +1320,6 @@ class TrianglesDataTextureLayer { gl.FLOAT, tempFloat32Array3 ); - // gl.bindTexture (gl.TEXTURE_2D, null); } diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/ViewFrustumCullingManager.js b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/ViewFrustumCullingManager.js deleted file mode 100644 index 9bd31b7dd..000000000 --- a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/ViewFrustumCullingManager.js +++ /dev/null @@ -1,749 +0,0 @@ -import { clusterizeV2 } from "./cluster-helper.js"; -import { math } from "../../../../../math/math.js"; - -// For JSDoc autocompletion -import { DataTextureSceneModel } from "../../../DataTextureSceneModel.js" -import { RBush3D } from "./rbush3d.js"; -import { DataTextureSceneModelNode } from "../../DataTextureSceneModelNode.js"; - -let tempVec3 = math.vec3 (); - -/** - * Number of bits per-dimension in the 2-dimensional LUT fast atan table - */ -const ATAN2_LUT_BITS = 9; - -const ATAN2_FACTOR = 1 << (ATAN2_LUT_BITS - 1); - -/** - * Constant for quick conversion of radians to degrees - */ -const _180_DIV_MATH_PI = 180 / Math.PI; - -const atan2LUT = new Float32Array ((1 << ATAN2_LUT_BITS) * (1 << ATAN2_LUT_BITS)); - -// Initialize the Look Up Table -for (let i = -ATAN2_FACTOR; i < ATAN2_FACTOR; i++) -{ - for (let j = -ATAN2_FACTOR; j < ATAN2_FACTOR; j++) - { - const index = ((i+ATAN2_FACTOR) << ATAN2_LUT_BITS) + (j+ATAN2_FACTOR); - - const max = Math.max ( - Math.abs (i), - Math.abs (j) - ); - - atan2LUT [index] = Math.atan2 ( - i/max, - j/max - ); - } -} - -/** - * Fast ````Math.atan2```` implementation based in Look Up Tables. - * - * @param {number} x - * @param {number} y - * - * @returns {number} - */ -function fastAtan2(x, y) -{ - const max_factor = ATAN2_FACTOR / Math.max ( - Math.abs (x), - Math.abs (y) - ); - - const xx = Math.round ( - x * max_factor - ) + (ATAN2_FACTOR - 1); - - const yy = Math.round ( - y * max_factor - ) + (ATAN2_FACTOR - 1); - - return atan2LUT [(xx << ATAN2_LUT_BITS) + yy]; -} - -const VISIBILITY_CHECK_ALL_D = (1 << 0); -const VISIBILITY_CHECK_NONE_D = (1 << 1); -const VISIBILITY_CHECK_D_LESS = (1 << 2); -const VISIBILITY_CHECK_D_MORE = (1 << 3); - -const VISIBILITY_CHECK_ALL_H = (1 << 4); -const VISIBILITY_CHECK_NONE_H = (1 << 5); -const VISIBILITY_CHECK_H_LESS = (1 << 6); -const VISIBILITY_CHECK_H_MORE = (1 << 7); - -const VISIBILITY_CHECK_ALL_V = (1 << 8); -const VISIBILITY_CHECK_NONE_V = (1 << 9); -const VISIBILITY_CHECK_V_LESS = (1 << 10); -const VISIBILITY_CHECK_V_MORE = (1 << 11); - -const VISIBILITY_CHECK_ENVOLVES_D = (1 << 12); -const VISIBILITY_CHECK_ENVOLVES_H = (1 << 13); -const VISIBILITY_CHECK_ENVOLVES_V = (1 << 14); - -/** - * Data structure containing pre-initialized `View Frustum Culling` data. - * - * Will be used by the rest of `View Frustum Culling` related code. - */ - class ViewFrustumCullingState { - constructor () { - /** - * The pre-computed AABB tree that will be used for efficient View Frustum Culling. - * - * @type {RBush3D} - * @private - */ - this._aabbTree = null; - - /** - * @type {Array<{mesh: object, clusterNumber: number}>} - * @private - */ - this._orderedMeshList = []; - - /** - * @type {Array} - * @private - */ - this._orderedEntityList = []; - - /** - * @private - */ - this._frustumProps = { - dirty: true, - wMultiply: 1.0, - hMultiply: 1.0, - }; - - /** - * @private - */ - this._cullFrame = 0; - - /** - * @type {boolean} - * @private - */ - this.finalized = false; - } - - /** - * - * @param {Array} entityList - * @param {Array} meshList - */ - initializeVfcState (entityList, meshList) { - if (this.finalized) { - throw "Already finalized"; - } - - const clusterResult = clusterizeV2 (entityList, meshList); - - this._aabbTree = clusterResult.rTreeBasedAabbTree; - - for (let i = 0, len = clusterResult.orderedClusteredIndexes.length; i < len; i++) - { - const entityIndex = clusterResult.orderedClusteredIndexes[i]; - - const clusterNumber = clusterResult.entityIdToClusterIdMapping[entityIndex]; - - const entity = entityList[entityIndex]; - - const newMeshIds = []; - - for (let j = 0, len2 = entity.meshIds.length; j < len2; j++) - { - const meshIndex = entity.meshIds[j]; - - meshList[meshIndex].id = this._orderedMeshList.length; - - newMeshIds.push (this._orderedMeshList.length); - - this._orderedMeshList.push ({ - clusterNumber: clusterNumber, - mesh: meshList[meshIndex] - }); - } - - entity.meshIds = newMeshIds; - - this._orderedEntityList.push ( - entity - ); - } - - for (let i = 0, len = clusterResult.instancedIndexes.length; i < len; i++) { - const entityIndex = clusterResult.instancedIndexes[i]; - - let entity = entityList[entityIndex]; - - const newMeshIds = []; - - for (let j = 0, len2 = entity.meshIds.length; j < len2; j++) - { - const meshIndex = entity.meshIds[j]; - - meshList[meshIndex].id = this._orderedMeshList.length; - - newMeshIds.push (this._orderedMeshList.length); - - this._orderedMeshList.push ({ - clusterNumber: 99999, - mesh: meshList[meshIndex] - }); - } - - entity.meshIds = newMeshIds; - - this._orderedEntityList.push ( - entity - ); - } - } - - /** - * @param {DataTextureSceneModel} model - * @param {*} fnForceFinalizeLayer - */ - finalize (model, fnForceFinalizeLayer) { - if (this.finalized) { - throw "Already finalized"; - } - - let lastClusterNumber = -1; - - for (let i = 0, len = this._orderedMeshList.length; i < len; i++) { - const { clusterNumber, mesh } = this._orderedMeshList [i]; - - if (lastClusterNumber != -1 && lastClusterNumber != clusterNumber) { - fnForceFinalizeLayer.call (model); - } - - model.createMesh (mesh); - - lastClusterNumber = clusterNumber; - } - - // fnForceFinalizeLayer (); - - for (let i = 0, len = this._orderedEntityList.length; i < len; i++) { - model.createEntity (this._orderedEntityList[i]) - } - - // Free memory - this._orderedMeshList = []; - this._orderedEntityList = []; - - this.finalized = true; - } - - /** - * @param {DataTextureSceneModel} model - */ - applyViewFrustumCulling (model) { - if (!this.finalized) { - throw "Not finalized"; - } - - if (!this._aabbTree) { - return; - } - - if (!this._canvasElement) { - /** - * @type {HTMLCanvasElement} - * @private - */ - this._canvasElement = model.scene.canvas.canvas; - } - - if (!this._camera) { - this._camera = model.scene.camera; - } - - this._ensureFrustumPropsUpdated (model); - - this._initializeCullingDataIfNeeded (model); - - const visibleNodes = this._searchVisibleNodesWithFrustumCulling (); - - // console.log (`visibleNodes: ${visibleNodes.length} / ${this._internalNodesList.length}`); - - this._cullFrame++; - - this._markVisibleFrameOfVisibleNodes ( - visibleNodes, - this._cullFrame - ); - - this._cullNonVisibleNodes ( - model, - this._cullFrame - ); - - // console.log (`${numIntersectionChecks} intersection checks`); - } - - _initializeCullingDataIfNeeded (model) { - if (this._internalNodesList) - { - return; - } - - if (!this._aabbTree) { - return; - } - - const allAabbNodes = this._aabbTree.all(); - - let maxEntityId = 0; - - allAabbNodes.forEach (aabbbNode => { - maxEntityId = Math.max ( - maxEntityId, - aabbbNode.entity.id - ) - }); - - const internalNodesList = new Array(maxEntityId + 1); - - allAabbNodes.forEach (aabbbNode => { - internalNodesList [ - aabbbNode.entity.id - ] = model._nodes[aabbbNode.entity.xeokitId]; - }); - - /** - * @type {Array} - * @private - */ - this._internalNodesList = internalNodesList; - - /** - * @private - */ - this._lastVisibleFrameOfNodes = new Array (internalNodesList.length); - - this._lastVisibleFrameOfNodes.fill(0); - } - - _searchVisibleNodesWithFrustumCulling() { - return this._aabbTree.searchCustom( - (bbox, isLeaf) => this._aabbIntersectsCameraFrustum (bbox, isLeaf), - (bbox) => this._aabbContainedInCameraFrustum (bbox) - ) - } - - _markVisibleFrameOfVisibleNodes (visibleNodes, cullFrame) { - const lastVisibleFrameOfNodes = this._lastVisibleFrameOfNodes; - - for (let i = 0, len = visibleNodes.length; i < len; i++) - { - lastVisibleFrameOfNodes [visibleNodes[i].entity.id] = cullFrame; - } - } - - _cullNonVisibleNodes (model, cullFrame) { - const internalNodesList = this._internalNodesList; - const lastVisibleFrameOfNodes = this._lastVisibleFrameOfNodes; - - for (let i = 0, len = internalNodesList.length; i < len; i++) - { - if (internalNodesList[i]) { - internalNodesList[i].culledVFC = lastVisibleFrameOfNodes[i] !== cullFrame; - } - } - } - - /** - * Returns all 8 coordinates of an AABB. - * - * @param {Array} bbox An AABB - * - * @private - */ - _getPointsForBBox (bbox) { - var retVal = []; - - for (var i = 0; i < 8; i++) - { - retVal.push ([ - (i & 1) ? bbox.maxX : bbox.minX, - (i & 2) ? bbox.maxY : bbox.minY, - (i & 4) ? bbox.maxZ : bbox.minZ, - ]); - } - - return retVal; - } - - /** - * @param {*} bbox - * @param {*} isLeaf - * @returns - * - * @private - */ - _aabbIntersectsCameraFrustum (bbox, isLeaf) - { - if (isLeaf) { - return true; - } - - if (this._camera.projection == "ortho") { - // TODO: manage ortho views - this._frustumProps.dirty = false; - return true; - } - - // numIntersectionChecks++; - - var check = this._aabbIntersectsCameraFrustum_internal (bbox); - - var interD = !(check & VISIBILITY_CHECK_ALL_D) && !(check & VISIBILITY_CHECK_NONE_D); - var interH = !(check & VISIBILITY_CHECK_ALL_H) && !(check & VISIBILITY_CHECK_NONE_H); - var interV = !(check & VISIBILITY_CHECK_ALL_V) && !(check & VISIBILITY_CHECK_NONE_V); - - if (((check & VISIBILITY_CHECK_ENVOLVES_D) || interD || (check & VISIBILITY_CHECK_ALL_D)) && - ((check & VISIBILITY_CHECK_ENVOLVES_H) || interH || (check & VISIBILITY_CHECK_ALL_H)) && - ((check & VISIBILITY_CHECK_ENVOLVES_V) || interV || (check & VISIBILITY_CHECK_ALL_V))) - { - return true; - } - - return false; - } - - /** - * @param {*} bbox - * @returns - * - * @private - */ - _aabbContainedInCameraFrustum (bbox) - { - if (this._camera.projection == "ortho") { - // TODO: manage ortho views - this._frustumProps.dirty = false; - return true; - } - - var check = bbox._check; - - return (check & VISIBILITY_CHECK_ALL_D) && - (check & VISIBILITY_CHECK_ALL_H) && - (check & VISIBILITY_CHECK_ALL_V); - } - - /** - * @param {DataTextureSceneModel} model - * - * @private - */ - _ensureFrustumPropsUpdated (model) - { - // Assuming "min" for fovAxis - const min = Math.min ( - this._canvasElement.width, - this._canvasElement.height - ); - - this._frustumProps.wMultiply = this._canvasElement.width / min; - this._frustumProps.hMultiply = this._canvasElement.height / min; - - const aspect = this._canvasElement.width / this._canvasElement.height; - - let fov = this._camera.perspective.fov; - - if (aspect < 1) - { - fov = fov / aspect; - } - - fov = Math.min (fov, 120); - - this._frustumProps.fov = fov; - - // if (!this._frustumProps.dirty) - // { - // return; - // } - - // Adjust camera eye/look to take into account the `model.worldMatrix`: - // - the entities' AABBs don't take it into account - // - and they can't, since `model.worldMatrix` is dynamic - // So, instead of transformating the positions of the r*tree's AABBs, - // apply the inverse transform to the camera eye/look, since the culling - // result is equivalent. - const invWorldMatrix = math.inverseMat4( - model.worldMatrix, - math.mat4() - ); - - const modelCamEye = math.transformVec3( - this._camera.eye, - invWorldMatrix, - [ 0, 0, 0 ] - ); - - const modelCamLook = math.transformVec3( - this._camera.look, - invWorldMatrix, - [ 0, 0, 0 ] - ); - - this._frustumProps.forward = math.normalizeVec3 ( - math.subVec3 ( - modelCamLook, - modelCamEye, - [ 0, 0, 0] - ), - [ 0, 0, 0] - ); - - this._frustumProps.up = math.normalizeVec3( - this._camera.up, - [ 0, 0, 0 ] - ); - - this._frustumProps.right = math.normalizeVec3 ( - math.cross3Vec3 ( - this._frustumProps.forward, - this._frustumProps.up, - [ 0, 0, 0] - ), - [ 0, 0, 0 ] - ); - - this._frustumProps.eye = modelCamEye.slice (); - - this._frustumProps.CAM_FACTOR_1 = this._frustumProps.fov / 2 * this._frustumProps.wMultiply / _180_DIV_MATH_PI; - this._frustumProps.CAM_FACTOR_2 = this._frustumProps.fov / 2 * this._frustumProps.hMultiply / _180_DIV_MATH_PI; - - // this._frustumProps.dirty = false; - } - - /** - * @param {*} bbox - * @returns - * - * @private - */ - _aabbIntersectsCameraFrustum_internal (bbox) - { - var bboxPoints = bbox._points || this._getPointsForBBox (bbox); - - bbox._points = bboxPoints; - - var retVal = - VISIBILITY_CHECK_ALL_D | VISIBILITY_CHECK_NONE_D | - VISIBILITY_CHECK_ALL_H | VISIBILITY_CHECK_NONE_H | - VISIBILITY_CHECK_ALL_V | VISIBILITY_CHECK_NONE_V; - - if (window._debug) - { - window._debug = false; - - debugger; - } - - for (var i = 0, len = bboxPoints.length; i < len; i++) { - // if ((!(retVal & VISIBILITY_CHECK_ALL_D) && !(retVal & VISIBILITY_CHECK_NONE_D)) || - // (!(retVal & VISIBILITY_CHECK_ALL_H) && !(retVal & VISIBILITY_CHECK_NONE_H)) || - // (!(retVal & VISIBILITY_CHECK_ALL_V) && !(retVal & VISIBILITY_CHECK_NONE_V))) - // { - // break; - // } - - var bboxPoint = bboxPoints [i]; - - var pointRelToCam = tempVec3; - - pointRelToCam[0] = bboxPoint[0] - this._frustumProps.eye[0]; - pointRelToCam[1] = bboxPoint[1] - this._frustumProps.eye[1]; - pointRelToCam[2] = bboxPoint[2] - this._frustumProps.eye[2]; - - var forwardComponent = math.dotVec3 ( - pointRelToCam, - this._frustumProps.forward - ); - - if (forwardComponent < 0) - { - retVal |= VISIBILITY_CHECK_D_LESS; - retVal &= ~VISIBILITY_CHECK_ALL_D; - } - else - { - retVal |= VISIBILITY_CHECK_D_MORE; - retVal &= ~VISIBILITY_CHECK_NONE_D; - } - - var rightComponent = math.dotVec3 ( - pointRelToCam, - this._frustumProps.right - ); - - var rightAngle = fastAtan2 ( - rightComponent, - forwardComponent - ); - - if (Math.abs (rightAngle) > this._frustumProps.CAM_FACTOR_1) - { - if (rightAngle < 0) - retVal |= VISIBILITY_CHECK_H_LESS; - else - retVal |= VISIBILITY_CHECK_H_MORE; - - retVal &= ~VISIBILITY_CHECK_ALL_H; - } - else - { - retVal &= ~VISIBILITY_CHECK_NONE_H; - } - - var upComponent = math.dotVec3 ( - pointRelToCam, - this._frustumProps.up - ); - - var upAngle = fastAtan2 ( - upComponent, - forwardComponent - ); - - if (Math.abs (upAngle) > this._frustumProps.CAM_FACTOR_2) - { - if (upAngle < 0) - retVal |= VISIBILITY_CHECK_V_LESS; - else - retVal |= VISIBILITY_CHECK_V_MORE; - - retVal &= ~VISIBILITY_CHECK_ALL_V; - } - else - { - retVal &= ~VISIBILITY_CHECK_NONE_V; - } - } - - // console.log (retVal); - - if ((retVal & VISIBILITY_CHECK_D_LESS) && (retVal & VISIBILITY_CHECK_D_MORE)) - { - retVal |= VISIBILITY_CHECK_ENVOLVES_D; - } - - if ((retVal & VISIBILITY_CHECK_H_LESS) && (retVal & VISIBILITY_CHECK_H_MORE)) - { - retVal |= VISIBILITY_CHECK_ENVOLVES_H; - } - - if ((retVal & VISIBILITY_CHECK_V_LESS) && (retVal & VISIBILITY_CHECK_V_MORE)) - { - retVal |= VISIBILITY_CHECK_ENVOLVES_V; - } - - bbox._check = retVal; - - // console.log (retVal); - - return retVal; - } -} - -class ViewFrustumCullingManager { - /** - * @param {DataTextureSceneModel} model - */ - constructor (model) { - /** - * @private - */ - this.model = model; - - /** - * @private - */ - this.entities = []; - - /** - * @private - */ - this.meshes = []; - - this.finalized = false; - } - - /** - */ - addEntity (entity) { - if (this.finalized) { - throw "Already finalized"; - } - - this.entities.push (entity); - } - - /** - */ - addMesh (mesh) { - if (this.finalized) { - throw "Already finalized"; - } - - this.meshes.push (mesh); - } - - finalize (fnForceFinalizeLayer) { - if (this.finalized) { - throw "Already finalized"; - } - - this.finalized = true; - - /** - * @private - */ - this.vfcState = new ViewFrustumCullingState (); - - console.time ("initializeVfcState"); - - this.vfcState.initializeVfcState (this.entities, this.meshes); - - console.timeEnd ("initializeVfcState"); - - console.time ("finalizeVfcState"); - - this.vfcState.finalize (this.model, fnForceFinalizeLayer); - - console.timeEnd ("finalizeVfcState"); - - const self = this; - - const cb = () => this.applyViewFrustumCulling.call (self); - - this.model.scene.on ("rendering", cb); - } - - /** - * @private - */ - applyViewFrustumCulling () { - if (!(this.finalized)) { - throw "Not finalized"; - } - - this.vfcState.applyViewFrustumCulling (this.model); - } -} - -export { ViewFrustumCullingManager } \ No newline at end of file diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/calculateUniquePositions.js b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/calculateUniquePositions.js index f84e1b0cf..307058bb1 100644 --- a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/calculateUniquePositions.js +++ b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/calculateUniquePositions.js @@ -1,43 +1,35 @@ /** * @author https://github.com/tmarti, with support from https://tribia.com/ * @license MIT - * + * * This file takes a geometry given by { positions, indices }, and returns * equivalent { positions, indices } arrays but which only contain unique * positions. - * + * * The time is O(N logN) with the number of positions due to a pre-sorting * step, but is much more GC-friendly and actually faster than the classic O(N) * approach based in keeping a hash-based LUT to identify unique positions. */ - let comparePositions = null; +let comparePositions = null; -function compareVertex (a, b) { +function compareVertex(a, b) { let res; - for (let i = 0; i < 3; i++) { - if (0!= (res = comparePositions[a*3+i] - comparePositions[b*3+i])) - { + if (0 != (res = comparePositions[a * 3 + i] - comparePositions[b * 3 + i])) { return res; } } - return 0; -}; +} let seqInit = null; -function setMaxNumberOfPositions (maxPositions) -{ - if (seqInit !== null && seqInit.length >= maxPositions) - { +function setMaxNumberOfPositions(maxPositions) { + if (seqInit !== null && seqInit.length >= maxPositions) { return; } - seqInit = new Uint32Array(maxPositions); - - for (let i = 0; i < maxPositions; i++) - { + for (let i = 0; i < maxPositions; i++) { seqInit[i] = i; } } @@ -46,42 +38,41 @@ function setMaxNumberOfPositions (maxPositions) * This function obtains unique positions in the provided object * .positions array and calculates an index mapping, which is then * applied to the provided object .indices and .edgeindices. - * + * * The input object items are not modified, and instead new set * of positions, indices and edgeIndices with the applied optimization * are returned. - * + * * The algorithm, instead of being based in a hash-like LUT for * identifying unique positions, is based in pre-sorting the input * positions... - * + * * (it's possible to define a _"consistent ordering"_ for the positions * as positions are quantized and thus not suffer from float number * comparison artifacts) - * + * * ... so same positions are adjacent in the sorted array, and then * it's easy to scan linearly the sorted array. During the linear run, * we will know that we found a different position because the comparison * function will return != 0 between current and previous element. - * + * * During this linear traversal of the array, a `unique counter` is used * in order to calculate the mapping between original indices and unique * indices. - * + * * @param {*} mesh The input mesh to process, with `positions`, `indices` and `edgeIndices` keys. - * + * * @returns An array with 3 elements: 0 => the uniquified positions; 1 and 2 => the remapped edges and edgeIndices arrays */ -function uniquifyPositions(mesh) -{ - let _positions = mesh.positionsCompressed; - let _indices = mesh.indices; - let _edgeIndices = mesh.edgeIndices; +export function uniquifyPositions(mesh) { + const _positions = mesh.positionsCompressed; + const _indices = mesh.indices; + const _edgeIndices = mesh.edgeIndices; setMaxNumberOfPositions(_positions.length / 3); - let seq = seqInit.slice (0, _positions.length / 3); - let remappings = seqInit.slice (0, _positions.length / 3); + const seq = seqInit.slice(0, _positions.length / 3); + const remappings = seqInit.slice(0, _positions.length / 3); comparePositions = _positions; @@ -91,66 +82,45 @@ function uniquifyPositions(mesh) remappings[seq[0]] = 0; - for (let i = 1, len = seq.length; i < len; i++) - { - if (0 != compareVertex(seq[i], seq[i-1])) - { + for (let i = 1, len = seq.length; i < len; i++) { + if (0 !== compareVertex(seq[i], seq[i - 1])) { uniqueIdx++; } - remappings[seq[i]] = uniqueIdx; } const numUniquePositions = uniqueIdx + 1; - - const newPositions = new Uint16Array (numUniquePositions * 3); + const newPositions = new Uint16Array(numUniquePositions * 3); uniqueIdx = 0 newPositions [uniqueIdx * 3 + 0] = _positions [seq[0] * 3 + 0]; newPositions [uniqueIdx * 3 + 1] = _positions [seq[0] * 3 + 1]; newPositions [uniqueIdx * 3 + 2] = _positions [seq[0] * 3 + 2]; - - for (let i = 1, len = seq.length; i < len; i++) - { - if (0 != compareVertex(seq[i], seq[i-1])) - { - uniqueIdx++; + for (let i = 1, len = seq.length; i < len; i++) { + if (0 !== compareVertex(seq[i], seq[i - 1])) { + uniqueIdx++; newPositions [uniqueIdx * 3 + 0] = _positions [seq[i] * 3 + 0]; newPositions [uniqueIdx * 3 + 1] = _positions [seq[i] * 3 + 1]; newPositions [uniqueIdx * 3 + 2] = _positions [seq[i] * 3 + 2]; } - remappings[seq[i]] = uniqueIdx; } comparePositions = null; - let newIndices = new Uint32Array (_indices.length); + const newIndices = new Uint32Array(_indices.length); - for (let i = 0, len = _indices.length; i < len; i++) - { - newIndices[i] = remappings [ - _indices[i] - ]; + for (let i = 0, len = _indices.length; i < len; i++) { + newIndices[i] = remappings [_indices[i]]; } - let newEdgeIndices = new Uint32Array (_edgeIndices.length); + const newEdgeIndices = new Uint32Array(_edgeIndices.length); - for (let i = 0, len = _edgeIndices.length; i < len; i++) - { - newEdgeIndices[i] = remappings [ - _edgeIndices[i] - ]; + for (let i = 0, len = _edgeIndices.length; i < len; i++) { + newEdgeIndices[i] = remappings [_edgeIndices[i]]; } - return [ - newPositions, - newIndices, - newEdgeIndices - ]; -} - - -export { uniquifyPositions } \ No newline at end of file + return [newPositions, newIndices, newEdgeIndices]; +} \ No newline at end of file diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/dataTextureRamStats.js b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/dataTextureRamStats.js new file mode 100644 index 000000000..87c034f26 --- /dev/null +++ b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/dataTextureRamStats.js @@ -0,0 +1,56 @@ +const dataTextureRamStats = { + sizeDataColorsAndFlags: 0, + sizeDataPositionDecodeMatrices: 0, + sizeDataTextureOffsets: 0, + sizeDataTexturePositions: 0, + sizeDataTextureIndices: 0, + sizeDataTextureEdgeIndices: 0, + sizeDataTexturePortionIds: 0, + numberOfGeometries: 0, + numberOfPortions: 0, + numberOfLayers: 0, + numberOfTextures: 0, + totalPolygons: 0, + totalPolygons8Bits: 0, + totalPolygons16Bits: 0, + totalPolygons32Bits: 0, + totalEdges: 0, + totalEdges8Bits: 0, + totalEdges16Bits: 0, + totalEdges32Bits: 0, + cannotCreatePortion: { + because10BitsObjectId: 0, + becauseTextureSize: 0, + }, + overheadSizeAlignementIndices: 0, + overheadSizeAlignementEdgeIndices: 0, +}; + +window.printDataTextureRamStats = function () { + + console.log(JSON.stringify(dataTextureRamStats, null, 4)); + + let totalRamSize = 0; + + Object.keys(dataTextureRamStats).forEach(key => { + if (key.startsWith("size")) { + totalRamSize += dataTextureRamStats[key]; + } + }); + + console.log(`Total size ${totalRamSize} bytes (${(totalRamSize / 1000 / 1000).toFixed(2)} MB)`); + console.log(`Avg bytes / triangle: ${(totalRamSize / dataTextureRamStats.totalPolygons).toFixed(2)}`); + + let percentualRamStats = {}; + + Object.keys(dataTextureRamStats).forEach(key => { + if (key.startsWith("size")) { + percentualRamStats[key] = + `${(dataTextureRamStats[key] / totalRamSize * 100).toFixed(2)} % of total`; + } + }); + + console.log(JSON.stringify({percentualRamUsage: percentualRamStats}, null, 4)); +}; + +export {dataTextureRamStats}; \ No newline at end of file diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/rbush3d.js b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/rbush3d.js deleted file mode 100644 index 5d04f00bd..000000000 --- a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/rbush3d.js +++ /dev/null @@ -1,814 +0,0 @@ -var module = {}; -var exports = {}; - -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.RBush3D = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i= a.minX && - b.maxY >= a.minY && - b.maxZ >= a.minZ; -}; -exports.boxRayIntersects = function (box, ox, oy, oz, idx, idy, idz) { - var tx0 = (box.minX - ox) * idx; - var tx1 = (box.maxX - ox) * idx; - var ty0 = (box.minY - oy) * idy; - var ty1 = (box.maxY - oy) * idy; - var tz0 = (box.minZ - oz) * idz; - var tz1 = (box.maxZ - oz) * idz; - var z0 = Math.min(tz0, tz1); - var z1 = Math.max(tz0, tz1); - var y0 = Math.min(ty0, ty1); - var y1 = Math.max(ty0, ty1); - var x0 = Math.min(tx0, tx1); - var x1 = Math.max(tx0, tx1); - var tmin = Math.max(0, x0, y0, z0); - var tmax = Math.min(x1, y1, z1); - return tmax >= tmin ? tmin : Infinity; -}; -var multiSelect = function (arr, left, right, n, compare) { - var stack = [left, right]; - var mid; - while (stack.length) { - right = stack.pop(); - left = stack.pop(); - if (right - left <= n) - continue; - mid = left + Math.ceil((right - left) / n / 2) * n; - quickselect(arr, mid, left, right, compare); - stack.push(left, mid, mid, right); - } -}; -var compareMinX = function (a, b) { return a.minX - b.minX; }; -var compareMinY = function (a, b) { return a.minY - b.minY; }; -var compareMinZ = function (a, b) { return a.minZ - b.minZ; }; -var RBush3D = (function () { - function RBush3D(maxEntries) { - if (maxEntries === void 0) { maxEntries = 16; } - this.maxEntries = Math.max(maxEntries, 8); - this.minEntries = Math.max(4, Math.ceil(this.maxEntries * 0.4)); - this.clear(); - } - RBush3D.alloc = function () { - return this.pool.pop() || new this(); - }; - RBush3D.free = function (rbush) { - rbush.clear(); - this.pool.push(rbush); - }; - // Start of chipmunk - RBush3D.prototype.searchCustom = function (customIntersects, customContains) { - var node = this.data; - var result = []; - if (!customIntersects(node, isLeafChild(node, child))) - return result; - var nodesToSearch = []; - while (node) { - for (var i = 0, len = node.children.length; i < len; i++) { - var child = node.children[i]; - if (customIntersects(child, isLeafChild(node, child))) { - if (isLeafChild(node, child)) - result.push(child); - else if (customContains(child)) - this._all(child, result); - else - nodesToSearch.push(child); - } - } - node = nodesToSearch.pop(); - } - return result; - }; - RBush3D.prototype.analyzeTriangles = function (node) { - if (node === undefined) - { - node = this.data; - } - - var totalTriangles = 0; - - if (isLeaf(node)) - { - for (var i = 0, len = node.children.length; i < len; i++) { - totalTriangles += node.children [i].numTriangles; - } - } - else - { - for (var i = 0, len = node.children.length; i < len; i++) { - totalTriangles += this.analyzeTriangles (node.children [i]); - } - } - - return node.totalTriangles = totalTriangles; - }; - RBush3D.prototype.groupNodesByNumTrianglesThreshold = function (numTrianglesThreshold, node) - { - if (node === undefined) - { - this.analyzeTriangles (); - node = this.data; - } - - var totalTriangles = 0; - - var items = []; - - for (var i = 0, len = node.children.length; i < len; i++) { - var child = node.children[i]; - items.push ({ - item: child, - triangles: child.totalTriangles || child.numTriangles, - }); - } - - items.sort (function (a, b) { - return -(a.item.triangles - b.item.triangles); - }); - - var retVal = []; - var retValItem = []; - var retValItemTris = 0; - - var c = 0; - - for (var i = 0, len = items.length; i < len; i++) { - var item = items[i]; - - if ((retValItemTris + item.triangles) < numTrianglesThreshold) - { - retValItem.push (item); - retValItemTris += item.triangles; - continue; - } - - if (retValItem.length) - { - retVal.push (retValItem); - retValItem = []; - retValItemTris = 0; - } - - if ((retValItemTris + item.triangles) < numTrianglesThreshold) - { - i--; - continue; - } - - if (isLeafChild(node, item.item)) - { - retVal.push ([ - item, - ]); - } - else - { - var tmp = this.groupNodesByNumTrianglesThreshold ( - numTrianglesThreshold, - item.item - ); - - var tmp2 = []; - var accum2 = 0; - - for (var j = 0, len2 = tmp.length; j < len2 - 1; j++) - { - retVal.push (tmp [i]); - } - - if (tmp.length > 1) - { - tmp = tmp [tmp.length - 1]; - - for (var j = 0, len2 = tmp.length; j < len2; j++) - { - accum2 = accum2 + tmp [j].triangles; - - if (accum2 < numTrianglesThreshold) - { - tmp2.push (tmp[j]); - } - else - { - if (accum2 == 0) - { - tmp2.push (tmp[j]); - } - - retVal.push (tmp2); - - accum2 = 0; - tmp2 = []; - } - } - - tmp2.forEach (function (subItem) { - retValItem.push (subItem); - retValItemTris += subItem.triangles; - }); - } - } - } - - if (retValItem.length) - { - retVal.push (retValItem); - } - - return retVal; - } - // End of chipmunk - RBush3D.prototype.search = function (bbox) { - var node = this.data; - var result = []; - if (!exports.intersects(bbox, node)) - return result; - var nodesToSearch = []; - while (node) { - for (var i = 0, len = node.children.length; i < len; i++) { - var child = node.children[i]; - if (exports.intersects(bbox, child)) { - if (isLeafChild(node, child)) - result.push(child); - else if (contains(bbox, child)) - this._all(child, result); - else - nodesToSearch.push(child); - } - } - node = nodesToSearch.pop(); - } - return result; - }; - RBush3D.prototype.collides = function (bbox) { - var node = this.data; - if (!exports.intersects(bbox, node)) - return false; - var nodesToSearch = []; - while (node) { - for (var i = 0, len = node.children.length; i < len; i++) { - var child = node.children[i]; - if (exports.intersects(bbox, child)) { - if (isLeafChild(node, child) || contains(bbox, child)) - return true; - nodesToSearch.push(child); - } - } - node = nodesToSearch.pop(); - } - return false; - }; - RBush3D.prototype.raycastInv = function (ox, oy, oz, idx, idy, idz, maxLen) { - if (maxLen === void 0) { maxLen = Infinity; } - var node = this.data; - if (idx === Infinity && idy === Infinity && idz === Infinity) - return allowDistNode(Infinity, undefined); - if (exports.boxRayIntersects(node, ox, oy, oz, idx, idy, idz) === Infinity) - return allowDistNode(Infinity, undefined); - var heap = [allowDistNode(0, node)]; - var swap = function (a, b) { - var t = heap[a]; - heap[a] = heap[b]; - heap[b] = t; - }; - var pop = function () { - var top = heap[0]; - var newLen = heap.length - 1; - heap[0] = heap[newLen]; - heap.length = newLen; - var idx = 0; - while (true) { - var left = (idx << 1) | 1; - if (left >= newLen) - break; - var right = left + 1; - if (right < newLen && heap[right].dist < heap[left].dist) { - left = right; - } - if (heap[idx].dist < heap[left].dist) - break; - swap(idx, left); - idx = left; - } - freeDistNode(top); - return top.node; - }; - var push = function (dist, node) { - var idx = heap.length; - heap.push(allowDistNode(dist, node)); - while (idx > 0) { - var p = (idx - 1) >> 1; - if (heap[p].dist <= heap[idx].dist) - break; - swap(idx, p); - idx = p; - } - }; - var dist = maxLen; - var result; - while (heap.length && heap[0].dist < dist) { - node = pop(); - for (var i = 0, len = node.children.length; i < len; i++) { - var child = node.children[i]; - var d = exports.boxRayIntersects(child, ox, oy, oz, idx, idy, idz); - if (!isLeafChild(node, child)) { - push(d, child); - } - else if (d < dist) { - if (d === 0) { - return allowDistNode(d, child); - } - dist = d; - result = child; - } - } - } - return allowDistNode(dist < maxLen ? dist : Infinity, result); - }; - RBush3D.prototype.raycast = function (ox, oy, oz, dx, dy, dz, maxLen) { - if (maxLen === void 0) { maxLen = Infinity; } - return this.raycastInv(ox, oy, oz, 1 / dx, 1 / dy, 1 / dz, maxLen); - }; - RBush3D.prototype.all = function () { - return this._all(this.data, []); - }; - RBush3D.prototype.load = function (data) { - if (!(data && data.length)) - return this; - if (data.length < this.minEntries) { - for (var i = 0, len = data.length; i < len; i++) { - this.insert(data[i]); - } - return this; - } - var node = this.build(data.slice(), 0, data.length - 1, 0); - if (!this.data.children.length) { - this.data = node; - } - else if (this.data.height === node.height) { - this.splitRoot(this.data, node); - } - else { - if (this.data.height < node.height) { - var tmpNode = this.data; - this.data = node; - node = tmpNode; - } - this._insert(node, this.data.height - node.height - 1, true); - } - return this; - }; - RBush3D.prototype.insert = function (item) { - if (item) - this._insert(item, this.data.height - 1); - return this; - }; - RBush3D.prototype.clear = function () { - if (this.data) { - freeAllNode(this.data); - } - this.data = allowNode([]); - return this; - }; - RBush3D.prototype.remove = function (item, equalsFn) { - if (!item) - return this; - var node = this.data; - var i = 0; - var goingUp = false; - var index; - var parent; - var path = []; - var indexes = []; - while (node || path.length) { - if (!node) { - node = path.pop(); - i = indexes.pop(); - parent = path[path.length - 1]; - goingUp = true; - } - if (isLeaf(node)) { - index = findItem(item, node.children, equalsFn); - if (index !== -1) { - node.children.splice(index, 1); - path.push(node); - this.condense(path); - return this; - } - } - if (!goingUp && !isLeaf(node) && contains(node, item)) { - path.push(node); - indexes.push(i); - i = 0; - parent = node; - node = node.children[0]; - } - else if (parent) { - i++; - node = parent.children[i]; - goingUp = false; - } - else { - node = undefined; - } - } - return this; - }; - RBush3D.prototype.toJSON = function () { - return this.data; - }; - RBush3D.prototype.fromJSON = function (data) { - freeAllNode(this.data); - this.data = data; - return this; - }; - RBush3D.prototype.build = function (items, left, right, height) { - var N = right - left + 1; - var M = this.maxEntries; - var node; - if (N <= M) { - node = allowNode(items.slice(left, right + 1)); - calcBBox(node); - return node; - } - if (!height) { - height = Math.ceil(Math.log(N) / Math.log(M)); - M = Math.ceil(N / Math.pow(M, height - 1)); - } - node = allowNode([]); - node.leaf = false; - node.height = height; - var N3 = Math.ceil(N / M), N2 = N3 * Math.ceil(Math.pow(M, 2 / 3)), N1 = N3 * Math.ceil(Math.pow(M, 1 / 3)); - multiSelect(items, left, right, N1, compareMinX); - for (var i = left; i <= right; i += N1) { - var right2 = Math.min(i + N1 - 1, right); - multiSelect(items, i, right2, N2, compareMinY); - for (var j = i; j <= right2; j += N2) { - var right3 = Math.min(j + N2 - 1, right2); - multiSelect(items, j, right3, N3, compareMinZ); - for (var k = j; k <= right3; k += N3) { - var right4 = Math.min(k + N3 - 1, right3); - node.children.push(this.build(items, k, right4, height - 1)); - } - } - } - calcBBox(node); - return node; - }; - RBush3D.prototype._all = function (node, result) { - var nodesToSearch = []; - while (node) { - if (isLeaf(node)) - result.push.apply(result, node.children); - else - nodesToSearch.push.apply(nodesToSearch, node.children); - node = nodesToSearch.pop(); - } - return result; - }; - RBush3D.prototype.chooseSubtree = function (bbox, node, level, path) { - var minVolume; - var minEnlargement; - var targetNode; - while (true) { - path.push(node); - if (isLeaf(node) || path.length - 1 === level) - break; - minVolume = minEnlargement = Infinity; - for (var i = 0, len = node.children.length; i < len; i++) { - var child = node.children[i]; - var volume = bboxVolume(child); - var enlargement = enlargedVolume(bbox, child) - volume; - if (enlargement < minEnlargement) { - minEnlargement = enlargement; - minVolume = volume < minVolume ? volume : minVolume; - targetNode = child; - } - else if (enlargement === minEnlargement) { - if (volume < minVolume) { - minVolume = volume; - targetNode = child; - } - } - } - node = targetNode || node.children[0]; - } - return node; - }; - RBush3D.prototype.split = function (insertPath, level) { - var node = insertPath[level]; - var M = node.children.length; - var m = this.minEntries; - this.chooseSplitAxis(node, m, M); - var splitIndex = this.chooseSplitIndex(node, m, M); - var newNode = allowNode(node.children.splice(splitIndex, node.children.length - splitIndex)); - newNode.height = node.height; - newNode.leaf = node.leaf; - calcBBox(node); - calcBBox(newNode); - if (level) - insertPath[level - 1].children.push(newNode); - else - this.splitRoot(node, newNode); - }; - RBush3D.prototype.splitRoot = function (node, newNode) { - this.data = allowNode([node, newNode]); - this.data.height = node.height + 1; - this.data.leaf = false; - calcBBox(this.data); - }; - RBush3D.prototype.chooseSplitIndex = function (node, m, M) { - var minOverlap = Infinity; - var minVolume = Infinity; - var index; - for (var i = m; i <= M - m; i++) { - var bbox1 = distBBox(node, 0, i); - var bbox2 = distBBox(node, i, M); - var overlap = intersectionVolume(bbox1, bbox2); - var volume = bboxVolume(bbox1) + bboxVolume(bbox2); - if (overlap < minOverlap) { - minOverlap = overlap; - index = i; - minVolume = volume < minVolume ? volume : minVolume; - } - else if (overlap === minOverlap) { - if (volume < minVolume) { - minVolume = volume; - index = i; - } - } - } - return index; - }; - RBush3D.prototype.chooseSplitAxis = function (node, m, M) { - var xMargin = this.allDistMargin(node, m, M, compareMinX); - var yMargin = this.allDistMargin(node, m, M, compareMinY); - var zMargin = this.allDistMargin(node, m, M, compareMinZ); - if (xMargin < yMargin && xMargin < zMargin) { - node.children.sort(compareMinX); - } - else if (yMargin < xMargin && yMargin < zMargin) { - node.children.sort(compareMinY); - } - }; - RBush3D.prototype.allDistMargin = function (node, m, M, compare) { - node.children.sort(compare); - var leftBBox = distBBox(node, 0, m); - var rightBBox = distBBox(node, M - m, M); - var margin = bboxMargin(leftBBox) + bboxMargin(rightBBox); - for (var i = m; i < M - m; i++) { - var child = node.children[i]; - extend(leftBBox, child); - margin += bboxMargin(leftBBox); - } - for (var i = M - m - 1; i >= m; i--) { - var child = node.children[i]; - extend(rightBBox, child); - margin += bboxMargin(rightBBox); - } - return margin; - }; - RBush3D.prototype.adjustParentBBoxes = function (bbox, path, level) { - for (var i = level; i >= 0; i--) { - extend(path[i], bbox); - } - }; - RBush3D.prototype.condense = function (path) { - for (var i = path.length - 1, siblings = void 0; i >= 0; i--) { - if (path[i].children.length === 0) { - if (i > 0) { - siblings = path[i - 1].children; - siblings.splice(siblings.indexOf(path[i]), 1); - freeNode(path[i]); - } - else { - this.clear(); - } - } - else { - calcBBox(path[i]); - } - } - }; - RBush3D.prototype._insert = function (item, level, isNode) { - var insertPath = []; - var node = this.chooseSubtree(item, this.data, level, insertPath); - node.children.push(item); - extend(node, item); - while (level >= 0) { - if (insertPath[level].children.length > this.maxEntries) { - this.split(insertPath, level); - level--; - } - else - break; - } - this.adjustParentBBoxes(item, insertPath, level); - }; - RBush3D.pool = []; - return RBush3D; -}()); -exports.RBush3D = RBush3D; - -},{"quickselect":2}],2:[function(require,module,exports){ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.quickselect = factory()); -}(this, (function () { 'use strict'; - -function quickselect(arr, k, left, right, compare) { - quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare); -} - -function quickselectStep(arr, k, left, right, compare) { - - while (right > left) { - if (right - left > 600) { - var n = right - left + 1; - var m = k - left + 1; - var z = Math.log(n); - var s = 0.5 * Math.exp(2 * z / 3); - var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); - var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); - var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); - quickselectStep(arr, k, newLeft, newRight, compare); - } - - var t = arr[k]; - var i = left; - var j = right; - - swap(arr, left, k); - if (compare(arr[right], t) > 0) swap(arr, left, right); - - while (i < j) { - swap(arr, i, j); - i++; - j--; - while (compare(arr[i], t) < 0) i++; - while (compare(arr[j], t) > 0) j--; - } - - if (compare(arr[left], t) === 0) swap(arr, left, j); - else { - j++; - swap(arr, j, right); - } - - if (j <= k) left = j + 1; - if (k <= j) right = j - 1; - } -} - -function swap(arr, i, j) { - var tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; -} - -function defaultCompare(a, b) { - return a < b ? -1 : a > b ? 1 : 0; -} - -return quickselect; - -}))); - -},{}]},{},[1])(1) -}); - -var tmp0 = module.exports.RBush3D; - -export {tmp0 as RBush3D}; diff --git a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/rebucketPositions.js b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/rebucketPositions.js index 28afc4bcc..9a65692c9 100644 --- a/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/rebucketPositions.js +++ b/src/viewer/scene/models/DataTextureSceneModel/lib/layers/trianglesDataTexture/rebucketPositions.js @@ -7,172 +7,115 @@ const MAX_RE_BUCKET_FAN_OUT = 8; let bucketsForIndices = null; -function compareBuckets (a, b) { - let aa = a*3; - let bb = b*3; - +function compareBuckets(a, b) { + const aa = a * 3; + const bb = b * 3; let aa1, aa2, aa3, bb1, bb2, bb3; - - const minBucketA = Math.min ( + const minBucketA = Math.min( aa1 = bucketsForIndices[aa], - aa2 = bucketsForIndices[aa+1], - aa3 = bucketsForIndices[aa+2] + aa2 = bucketsForIndices[aa + 1], + aa3 = bucketsForIndices[aa + 2] ); - - const minBucketB = Math.min ( + const minBucketB = Math.min( bb1 = bucketsForIndices[bb], - bb2 = bucketsForIndices[bb+1], - bb3 = bucketsForIndices[bb+2] + bb2 = bucketsForIndices[bb + 1], + bb3 = bucketsForIndices[bb + 2] ); - - if (minBucketA != minBucketB) - { + if (minBucketA !== minBucketB) { return minBucketA - minBucketB; } - - const maxBucketA = Math.max ( - aa1, - aa2, - aa3, - ); - - const maxBucketB = Math.max ( - bb1, - bb2, - bb3, - ); - - if (maxBucketA != maxBucketB) - { + const maxBucketA = Math.max(aa1, aa2, aa3); + const maxBucketB = Math.max(bb1, bb2, bb3,); + if (maxBucketA !== maxBucketB) { return maxBucketA - maxBucketB; } - return 0; -}; - -function preSortIndices(indices, bitsPerBucket) -{ - let seq = new Int32Array (indices.length / 3); +} - for (let i = 0, len = seq.length; i < len; i++) - { +function preSortIndices(indices, bitsPerBucket) { + const seq = new Int32Array(indices.length / 3); + for (let i = 0, len = seq.length; i < len; i++) { seq[i] = i; } - - bucketsForIndices = new Int32Array (indices.length); - - for (let i = 0, len = indices.length; i < len; i++) - { + bucketsForIndices = new Int32Array(indices.length); + for (let i = 0, len = indices.length; i < len; i++) { bucketsForIndices[i] = indices[i] >> bitsPerBucket; } - seq.sort(compareBuckets); - const sortedIndices = new Int32Array(indices.length); - - for (let i = 0, len = seq.length; i < len; i++) - { - sortedIndices[i*3+0] = indices[seq[i]*3+0]; - sortedIndices[i*3+1] = indices[seq[i]*3+1]; - sortedIndices[i*3+2] = indices[seq[i]*3+2]; + for (let i = 0, len = seq.length; i < len; i++) { + sortedIndices[i * 3 + 0] = indices[seq[i] * 3 + 0]; + sortedIndices[i * 3 + 1] = indices[seq[i] * 3 + 1]; + sortedIndices[i * 3 + 2] = indices[seq[i] * 3 + 2]; } - return sortedIndices; } let compareEdgeIndices = null; -function compareIndices(a, b) -{ - let retVal = compareEdgeIndices[a*2] - compareEdgeIndices[b*2]; - - if (retVal != 0) - { +function compareIndices(a, b) { + let retVal = compareEdgeIndices[a * 2] - compareEdgeIndices[b * 2]; + if (retVal !== 0) { return retVal; } - - return compareEdgeIndices[a*2+1] - compareEdgeIndices[b*2+1]; + return compareEdgeIndices[a * 2 + 1] - compareEdgeIndices[b * 2 + 1]; } -function preSortEdgeIndices(edgeIndices) -{ - if ((edgeIndices || []).length == 0) - { +function preSortEdgeIndices(edgeIndices) { + if ((edgeIndices || []).length === 0) { return []; } - - let seq = new Int32Array (edgeIndices.length / 2); - - for (let i = 0, len = seq.length; i < len; i++) - { + let seq = new Int32Array(edgeIndices.length / 2); + for (let i = 0, len = seq.length; i < len; i++) { seq[i] = i; } - - for (let i = 0, j = 0, len = edgeIndices.length; i < len; i+=2) - { - if (edgeIndices[i] > edgeIndices[i+1]) { + for (let i = 0, j = 0, len = edgeIndices.length; i < len; i += 2) { + if (edgeIndices[i] > edgeIndices[i + 1]) { let tmp = edgeIndices[i]; - edgeIndices[i] = edgeIndices[i+1]; - edgeIndices[i+1] = tmp; + edgeIndices[i] = edgeIndices[i + 1]; + edgeIndices[i + 1] = tmp; } } - - compareEdgeIndices = new Int32Array (edgeIndices); - + compareEdgeIndices = new Int32Array(edgeIndices); seq.sort(compareIndices); - const sortedEdgeIndices = new Int32Array(edgeIndices.length); - - for (let i = 0, len = seq.length; i < len; i++) - { - sortedEdgeIndices[i*2+0] = edgeIndices[seq[i]*2+0]; - sortedEdgeIndices[i*2+1] = edgeIndices[seq[i]*2+1]; + for (let i = 0, len = seq.length; i < len; i++) { + sortedEdgeIndices[i * 2 + 0] = edgeIndices[seq[i] * 2 + 0]; + sortedEdgeIndices[i * 2 + 1] = edgeIndices[seq[i] * 2 + 1]; } - return sortedEdgeIndices; } - -function rebucketPositions(mesh, bitsPerBucket, checkResult = false) -{ - const positions = (mesh.positions || []); - const indices = preSortIndices (mesh.indices || [], bitsPerBucket); - - const edgeIndices = preSortEdgeIndices (mesh.edgeIndices || []); +function rebucketPositions(mesh, bitsPerBucket, checkResult = false) { + const positions = (mesh.positions || []); + const indices = preSortIndices(mesh.indices || [], bitsPerBucket); + const edgeIndices = preSortEdgeIndices(mesh.edgeIndices || []); - /** - * Code adapted from https://stackoverflow.com/questions/22697936/binary-search-in-javascript - */ - function edgeSearch(el0, el1) { - if (el0 > el1) - { + function edgeSearch(el0, el1) { // Code adapted from https://stackoverflow.com/questions/22697936/binary-search-in-javascript + if (el0 > el1) { let tmp = el0; el0 = el1; el1 = tmp; } - function compare_fn (a, b) { - if (a != el0) - { + function compare_fn(a, b) { + if (a !== el0) { return el0 - a; } - - if (b != el1) - { + if (b !== el1) { return el1 - b; } - return 0; - }; + } - var m = 0; - var n = (edgeIndices.length >> 1) - 1; + let m = 0; + let n = (edgeIndices.length >> 1) - 1; while (m <= n) { - var k = (n + m) >> 1; - var cmp = compare_fn(edgeIndices[k*2], edgeIndices[k*2+1]); + const k = (n + m) >> 1; + const cmp = compare_fn(edgeIndices[k * 2], edgeIndices[k * 2 + 1]); if (cmp > 0) { m = k + 1; - } else if(cmp < 0) { + } else if (cmp < 0) { n = k - 1; } else { return k; @@ -181,21 +124,13 @@ function rebucketPositions(mesh, bitsPerBucket, checkResult = false) return -m - 1; } - // console.log (edgeIndices); - - // throw (e); - - // console.log (`${mesh.edgeIndices.length / 2} edge indices`); - // console.log (`${edgeIndices.length / 2} edge indices sorted`); - - const alreadyOutputEdgeIndices = new Int32Array(edgeIndices.length/2); + const alreadyOutputEdgeIndices = new Int32Array(edgeIndices.length / 2); alreadyOutputEdgeIndices.fill(0); const numPositions = positions.length / 3; - if (numPositions > ((1 << bitsPerBucket) * MAX_RE_BUCKET_FAN_OUT)) - { - return [ mesh ]; + if (numPositions > ((1 << bitsPerBucket) * MAX_RE_BUCKET_FAN_OUT)) { + return [mesh]; } const bucketIndicesRemap = new Int32Array(numPositions); @@ -203,11 +138,10 @@ function rebucketPositions(mesh, bitsPerBucket, checkResult = false) const buckets = []; - function addEmptyBucket () - { + function addEmptyBucket() { bucketIndicesRemap.fill(-1); - let newBucket = { + const newBucket = { positions: [], indices: [], edgeIndices: [], @@ -216,67 +150,63 @@ function rebucketPositions(mesh, bitsPerBucket, checkResult = false) bucketNumber: buckets.length, }; - buckets.push (newBucket); + buckets.push(newBucket); return newBucket; } - let currentBucket = addEmptyBucket (); + let currentBucket = addEmptyBucket(); // let currentBucket = 0; - + let retVal = 0; - for (let i = 0, len = indices.length; i < len; i+=3) - { + for (let i = 0, len = indices.length; i < len; i += 3) { let additonalPositionsInBucket = 0; const ii0 = indices[i]; - const ii1 = indices[i+1]; - const ii2 = indices[i+2]; + const ii1 = indices[i + 1]; + const ii2 = indices[i + 2]; - if (bucketIndicesRemap[ii0] == -1) { + if (bucketIndicesRemap[ii0] === -1) { additonalPositionsInBucket++; } - if (bucketIndicesRemap[ii1] == -1) { + if (bucketIndicesRemap[ii1] === -1) { additonalPositionsInBucket++; } - if (bucketIndicesRemap[ii2] == -1) { + if (bucketIndicesRemap[ii2] === -1) { additonalPositionsInBucket++; } - if ((additonalPositionsInBucket + currentBucket.numPositions) > currentBucket.maxNumPositions) - { + if ((additonalPositionsInBucket + currentBucket.numPositions) > currentBucket.maxNumPositions) { currentBucket = addEmptyBucket(); } - if (currentBucket.bucketNumber > MAX_RE_BUCKET_FAN_OUT) - { - return [ mesh ]; + if (currentBucket.bucketNumber > MAX_RE_BUCKET_FAN_OUT) { + return [mesh]; } - - if (bucketIndicesRemap[ii0] == -1) - { + + if (bucketIndicesRemap[ii0] === -1) { bucketIndicesRemap[ii0] = currentBucket.numPositions++; - currentBucket.positions.push (positions[ii0*3]); - currentBucket.positions.push (positions[ii0*3+1]); - currentBucket.positions.push (positions[ii0*3+2]); + currentBucket.positions.push(positions[ii0 * 3]); + currentBucket.positions.push(positions[ii0 * 3 + 1]); + currentBucket.positions.push(positions[ii0 * 3 + 2]); } - - if (bucketIndicesRemap[ii1] == -1) { + + if (bucketIndicesRemap[ii1] === -1) { bucketIndicesRemap[ii1] = currentBucket.numPositions++; - currentBucket.positions.push (positions[ii1*3]); - currentBucket.positions.push (positions[ii1*3+1]); - currentBucket.positions.push (positions[ii1*3+2]); + currentBucket.positions.push(positions[ii1 * 3]); + currentBucket.positions.push(positions[ii1 * 3 + 1]); + currentBucket.positions.push(positions[ii1 * 3 + 2]); } - if (bucketIndicesRemap[ii2] == -1) { + if (bucketIndicesRemap[ii2] === -1) { bucketIndicesRemap[ii2] = currentBucket.numPositions++; - currentBucket.positions.push (positions[ii2*3]); - currentBucket.positions.push (positions[ii2*3+1]); - currentBucket.positions.push (positions[ii2*3+2]); + currentBucket.positions.push(positions[ii2 * 3]); + currentBucket.positions.push(positions[ii2 * 3 + 1]); + currentBucket.positions.push(positions[ii2 * 3 + 2]); } currentBucket.indices.push(bucketIndicesRemap[ii0]); @@ -285,165 +215,133 @@ function rebucketPositions(mesh, bitsPerBucket, checkResult = false) // Check possible edge1 let edgeIndex; - - if ((edgeIndex = edgeSearch(ii0, ii1)) >= 0) - { - if (alreadyOutputEdgeIndices[edgeIndex] == 0) - { + + if ((edgeIndex = edgeSearch(ii0, ii1)) >= 0) { + if (alreadyOutputEdgeIndices[edgeIndex] === 0) { alreadyOutputEdgeIndices[edgeIndex] = 1; - currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex*2]]); - currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex*2+1]]); + currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2]]); + currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2 + 1]]); } } - if ((edgeIndex = edgeSearch(ii0, ii2)) >= 0) - { - if (alreadyOutputEdgeIndices[edgeIndex] == 0) - { + if ((edgeIndex = edgeSearch(ii0, ii2)) >= 0) { + if (alreadyOutputEdgeIndices[edgeIndex] === 0) { alreadyOutputEdgeIndices[edgeIndex] = 1; - currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex*2]]); - currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex*2+1]]); + currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2]]); + currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2 + 1]]); } } - if ((edgeIndex = edgeSearch(ii1, ii2)) >= 0) - { - if (alreadyOutputEdgeIndices[edgeIndex] == 0) - { + if ((edgeIndex = edgeSearch(ii1, ii2)) >= 0) { + if (alreadyOutputEdgeIndices[edgeIndex] === 0) { alreadyOutputEdgeIndices[edgeIndex] = 1; - currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex*2]]); - currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex*2+1]]); + currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2]]); + currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2 + 1]]); } } } - + const prevBytesPerIndex = bitsPerBucket / 8 * 2; const newBytesPerIndex = bitsPerBucket / 8; const originalSize = positions.length * 2 + (indices.length + edgeIndices.length) * prevBytesPerIndex; let newSize = 0; - let newPositions = - positions.length / 3; + let newPositions = -positions.length / 3; - buckets.forEach (bucket => { + buckets.forEach(bucket => { newSize += bucket.positions.length * 2 + (bucket.indices.length + bucket.edgeIndices.length) * newBytesPerIndex; newPositions += bucket.positions.length / 3; }); - - if (newSize > originalSize) - { - return [ mesh ]; + if (newSize > originalSize) { + return [mesh]; } - - // console.log ("added positions " + newPositions + ", buckets: " + buckets.length); - - if (checkResult) - { - doCheckResult (buckets, mesh); + if (checkResult) { + doCheckResult(buckets, mesh); } - - // return [ mesh ]; - return buckets; - } - -function unbucket (buckets) -{ - let positions = []; - let indices = []; - let edgeIndices = []; +} +function unbucket(buckets) { + const positions = []; + const indices = []; + const edgeIndices = []; let positionsBase = 0; - - buckets.forEach (bucket => { - bucket.positions.forEach (coord => { - positions.push (coord); + buckets.forEach(bucket => { + bucket.positions.forEach(coord => { + positions.push(coord); }); - - bucket.indices.forEach (index => { - indices.push (index + positionsBase); + bucket.indices.forEach(index => { + indices.push(index + positionsBase); }); - - bucket.edgeIndices.forEach (edgeIndex => { - edgeIndices.push (edgeIndex + positionsBase); + bucket.edgeIndices.forEach(edgeIndex => { + edgeIndices.push(edgeIndex + positionsBase); }); - positionsBase += positions.length / 3; }); - - return { - positions, - indices, - edgeIndices - }; + return {positions, indices, edgeIndices}; } - function doCheckResult (buckets, mesh) - { - const meshDict = {}; - const edgesDict = {}; - - let edgeIndicesCount = 0; - - buckets.forEach (bucket => { - let indices = bucket.indices; - let edgeIndices = bucket.edgeIndices; - let positions = bucket.positions; - - for (var i = 0, len = indices.length; i < len; i+=3) - { - var key = positions[indices[i]*3] + "_" + positions[indices[i]*3+1] + "_" + positions[indices[i]*3+2] + "/" + - positions[indices[i+1]*3] + "_" + positions[indices[i+1]*3+1] + "_" + positions[indices[i+1]*3+2] + "/" + - positions[indices[i+2]*3] + "_" + positions[indices[i+2]*3+1] + "_" + positions[indices[i+2]*3+2]; - - meshDict[key] = true; - } - - edgeIndicesCount += bucket.edgeIndices.length / 2; - - for (var i = 0, len = edgeIndices.length; i < len; i+=2) - { - var key = positions[edgeIndices[i]*3] + "_" + positions[edgeIndices[i]*3+1] + "_" + positions[edgeIndices[i]*3+2] + "/" + - positions[edgeIndices[i+1]*3] + "_" + positions[edgeIndices[i+1]*3+1] + "_" + positions[edgeIndices[i+1]*3+2] + "/"; - - edgesDict[key] = true; - } - }); - - { - let indices = mesh.indices; - let edgeIndices = mesh.edgeIndices; - let positions = mesh.positions; - - for (var i = 0, len = indices.length; i < len; i+=3) - { - var key = positions[indices[i]*3] + "_" + positions[indices[i]*3+1] + "_" + positions[indices[i]*3+2] + "/" + - positions[indices[i+1]*3] + "_" + positions[indices[i+1]*3+1] + "_" + positions[indices[i+1]*3+2] + "/" + - positions[indices[i+2]*3] + "_" + positions[indices[i+2]*3+1] + "_" + positions[indices[i+2]*3+2]; - - if (!(key in meshDict)) { - console.log ("Not found " + key); - throw "Ohhhh!"; - } - } - +function doCheckResult(buckets, mesh) { + const meshDict = {}; + const edgesDict = {}; + + let edgeIndicesCount = 0; + + buckets.forEach(bucket => { + const indices = bucket.indices; + const edgeIndices = bucket.edgeIndices; + const positions = bucket.positions; + + for (let i = 0, len = indices.length; i < len; i += 3) { + const key = positions[indices[i] * 3] + "_" + positions[indices[i] * 3 + 1] + "_" + positions[indices[i] * 3 + 2] + "/" + + positions[indices[i + 1] * 3] + "_" + positions[indices[i + 1] * 3 + 1] + "_" + positions[indices[i + 1] * 3 + 2] + "/" + + positions[indices[i + 2] * 3] + "_" + positions[indices[i + 2] * 3 + 1] + "_" + positions[indices[i + 2] * 3 + 2]; + meshDict[key] = true; + } + + edgeIndicesCount += bucket.edgeIndices.length / 2; + + for (let i = 0, len = edgeIndices.length; i < len; i += 2) { + const key = positions[edgeIndices[i] * 3] + "_" + positions[edgeIndices[i] * 3 + 1] + "_" + positions[edgeIndices[i] * 3 + 2] + "/" + + positions[edgeIndices[i + 1] * 3] + "_" + positions[edgeIndices[i + 1] * 3 + 1] + "_" + positions[edgeIndices[i + 1] * 3 + 2] + "/"; + edgesDict[key] = true; + } + }); + + { + const indices = mesh.indices; + const edgeIndices = mesh.edgeIndices; + const positions = mesh.positions; + + for (let i = 0, len = indices.length; i < len; i += 3) { + const key = positions[indices[i] * 3] + "_" + positions[indices[i] * 3 + 1] + "_" + positions[indices[i] * 3 + 2] + "/" + + positions[indices[i + 1] * 3] + "_" + positions[indices[i + 1] * 3 + 1] + "_" + positions[indices[i + 1] * 3 + 2] + "/" + + positions[indices[i + 2] * 3] + "_" + positions[indices[i + 2] * 3 + 1] + "_" + positions[indices[i + 2] * 3 + 2]; + + if (!(key in meshDict)) { + console.log("Not found " + key); + throw "Ohhhh!"; + } + } + // for (var i = 0, len = edgeIndices.length; i < len; i+=2) // { // var key = positions[edgeIndices[i]*3] + "_" + positions[edgeIndices[i]*3+1] + "_" + positions[edgeIndices[i]*3+2] + "/" + // positions[edgeIndices[i+1]*3] + "_" + positions[edgeIndices[i+1]*3+1] + "_" + positions[edgeIndices[i+1]*3+2] + "/"; - + // if (!(key in edgesDict)) { // var key2 = edgeIndices[i] + "_" + edgeIndices[i+1]; - + // console.log (" - Not found " + key); // console.log (" - Not found " + key2); // // throw "Ohhhh2!"; // } // } - } - } - - export { rebucketPositions } \ No newline at end of file + } +} + +export {rebucketPositions} \ No newline at end of file diff --git a/src/viewer/scene/models/VBOSceneModel/VBOSceneModel.js b/src/viewer/scene/models/VBOSceneModel/VBOSceneModel.js index fdeb9c74f..ebed5bdb5 100644 --- a/src/viewer/scene/models/VBOSceneModel/VBOSceneModel.js +++ b/src/viewer/scene/models/VBOSceneModel/VBOSceneModel.js @@ -643,7 +643,6 @@ const defaultTextureSetId = "defaultTextureSet"; * * vboSceneModel.createGeometry({ * id: "box", - * origin: origin, // This geometry's positions, and the transforms of all meshes that instance the geometry, are relative to the RTC center * primitive: "triangles", * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ], * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ], @@ -656,7 +655,8 @@ const defaultTextureSetId = "defaultTextureSet"; * position: [-4, -6, -4], * scale: [1, 3, 1], * rotation: [0, 0, 0], - * color: [1, 0.3, 0.3] + * color: [1, 0.3, 0.3], + * origin: origin * }); * * vboSceneModel.createEntity({ @@ -670,7 +670,8 @@ const defaultTextureSetId = "defaultTextureSet"; * position: [4, -6, -4], * scale: [1, 3, 1], * rotation: [0, 0, 0], - * color: [0.3, 1.0, 0.3] + * color: [0.3, 1.0, 0.3], + * origin: origin * }); * * vboSceneModel.createEntity({ @@ -684,7 +685,8 @@ const defaultTextureSetId = "defaultTextureSet"; * position: [4, -6, 4], * scale: [1, 3, 1], * rotation: [0, 0, 0], - * color: [0.3, 0.3, 1.0] + * color: [0.3, 0.3, 1.0], + * origin: origin * }); * * vboSceneModel.createEntity({ @@ -698,7 +700,8 @@ const defaultTextureSetId = "defaultTextureSet"; * position: [-4, -6, 4], * scale: [1, 3, 1], * rotation: [0, 0, 0], - * color: [1.0, 1.0, 0.0] + * color: [1.0, 1.0, 0.0], + * origin: origin * }); * * vboSceneModel.createEntity({ @@ -712,7 +715,8 @@ const defaultTextureSetId = "defaultTextureSet"; * position: [0, -3, 0], * scale: [6, 0.5, 6], * rotation: [0, 0, 0], - * color: [1.0, 0.3, 1.0] + * color: [1.0, 0.3, 1.0], + * origin: origin * }); * * vboSceneModel.createEntity({ diff --git a/src/viewer/scene/models/VBOSceneModel/lib/VBOSceneModelGeometry.js b/src/viewer/scene/models/VBOSceneModel/lib/VBOSceneModelGeometry.js index e3ffb5abc..4f69c0273 100644 --- a/src/viewer/scene/models/VBOSceneModel/lib/VBOSceneModelGeometry.js +++ b/src/viewer/scene/models/VBOSceneModel/lib/VBOSceneModelGeometry.js @@ -30,10 +30,6 @@ export class VBOSceneModelGeometry { */ constructor(id, model, cfg) { - /////////////////////////////////////////////////////// - /////////////////////////////////////////////////////// - // TODO: optional origin param, or create from positions automatically if required - then offset from mesh origin in createMesh - /** * ID of this VBOSceneModelGeometry, unique within the VBOSceneModel. * @@ -73,7 +69,6 @@ export class VBOSceneModelGeometry { this.uvBuf = null; this.colorsBuf = null; - const pickSurfacePrecisionEnabled = model.scene.pickSurfacePrecisionEnabled; const gl = model.scene.canvas.gl; if (cfg.positionsCompressed && cfg.positionsCompressed.length > 0) { @@ -84,9 +79,6 @@ export class VBOSceneModelGeometry { math.expandAABB3Points3(localAABB, cfg.positionsCompressed); geometryCompressionUtils.decompressAABB(localAABB, this.positionsDecodeMatrix); math.AABB3ToOBB3(localAABB, this.obb); - if (pickSurfacePrecisionEnabled) { - this.quantizedPositions = cfg.positionsCompressed; - } } else if (cfg.positions && cfg.positions.length > 0) { const lenPositions = cfg.positions.length; @@ -96,9 +88,6 @@ export class VBOSceneModelGeometry { const quantizedPositions = quantizePositions(cfg.positions, localAABB, this.positionsDecodeMatrix); let normalized = false; this.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, quantizedPositions, lenPositions, 3, gl.STATIC_DRAW, normalized); - if (pickSurfacePrecisionEnabled) { - this.quantizedPositions = quantizedPositions; - } } if (cfg.normalsCompressed && cfg.normalsCompressed.length > 0) { @@ -141,9 +130,6 @@ export class VBOSceneModelGeometry { if (cfg.indices && cfg.indices.length > 0) { this.indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(cfg.indices), cfg.indices.length, 1, gl.STATIC_DRAW); - if (pickSurfacePrecisionEnabled) { - this.indices = cfg.indices; - } this.numIndices = cfg.indices.length; } diff --git a/src/viewer/scene/models/VBOSceneModel/lib/layers/trianglesBatching/TrianglesBatchingLayer.js b/src/viewer/scene/models/VBOSceneModel/lib/layers/trianglesBatching/TrianglesBatchingLayer.js index 9bc8833ae..8b1bc5730 100644 --- a/src/viewer/scene/models/VBOSceneModel/lib/layers/trianglesBatching/TrianglesBatchingLayer.js +++ b/src/viewer/scene/models/VBOSceneModel/lib/layers/trianglesBatching/TrianglesBatchingLayer.js @@ -411,16 +411,6 @@ class TrianglesBatchingLayer { numVerts: numVerts }; - if (scene.pickSurfacePrecisionEnabled) { - // Quantized in-memory positions are initialized in finalize() - if (indices) { - portion.indices = indices; - } - if (scene.entityOffsetsEnabled) { - portion.offset = new Float32Array(3); - } - } - this._portions.push(portion); this._numPortions++; @@ -454,15 +444,6 @@ class TrianglesBatchingLayer { : quantizePositions(buffer.positions, this._modelAABB, state.positionsDecodeMatrix); // BOTTLENECK state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, quantizedPositions, quantizedPositions.length, 3, gl.STATIC_DRAW); - - if (this.model.scene.pickSurfacePrecisionEnabled) { - for (let i = 0, numPortions = this._portions.length; i < numPortions; i++) { - const portion = this._portions[i]; - const start = portion.vertsBaseIndex * 3; - const end = start + (portion.numVerts * 3); - portion.quantizedPositions = quantizedPositions.slice(start, end); - } - } } if (buffer.normals.length > 0) { @@ -884,43 +865,8 @@ class TrianglesBatchingLayer { if (this._state.offsetsBuf) { this._state.offsetsBuf.setData(tempArray, firstOffset, lenOffsets); } - if (this.model.scene.pickSurfacePrecisionEnabled) { - portion.offset[0] = offset[0]; - portion.offset[1] = offset[1]; - portion.offset[2] = offset[2]; - } } - getEachVertex(portionId, callback) { - if (!this.model.scene.pickSurfacePrecisionEnabled) { - return; - } - const state = this._state; - const portion = this._portions[portionId]; - if (!portion) { - this.model.error("portion not found: " + portionId); - return; - } - const positions = portion.quantizedPositions; - const origin = state.origin; - const offset = portion.offset; - const offsetX = origin[0] + offset[0]; - const offsetY = origin[1] + offset[1]; - const offsetZ = origin[2] + offset[2]; - const worldPos = tempVec4a; - for (let i = 0, len = positions.length; i < len; i += 3) { - worldPos[0] = positions[i]; - worldPos[1] = positions[i + 1]; - worldPos[2] = positions[i + 2]; - worldPos[3] = 1.0; - math.decompressPosition(worldPos, state.positionsDecodeMatrix); - math.transformPoint4(this.model.worldMatrix, worldPos); - worldPos[0] += offsetX; - worldPos[1] += offsetY; - worldPos[2] += offsetZ; - callback(worldPos); - } - } // ---------------------- COLOR RENDERING ----------------------------------- @@ -1194,102 +1140,6 @@ class TrianglesBatchingLayer { //------------------------------------------------------------------------------------------------ - precisionRayPickSurface(portionId, worldRayOrigin, worldRayDir, worldSurfacePos, worldNormal) { - - if (!this.model.scene.pickSurfacePrecisionEnabled) { - return false; - } - - const state = this._state; - const portion = this._portions[portionId]; - - if (!portion) { - this.model.error("portion not found: " + portionId); - return false; - } - - const positions = portion.quantizedPositions; - const indices = portion.indices; - const origin = state.origin; - const offset = portion.offset; - - const rtcRayOrigin = tempVec3a; - const rtcRayDir = tempVec3b; - - rtcRayOrigin.set(origin ? math.subVec3(worldRayOrigin, origin, tempVec3c) : worldRayOrigin); // World -> RTC - rtcRayDir.set(worldRayDir); - - if (offset) { - math.subVec3(rtcRayOrigin, offset); - } - - math.transformRay(this.model.worldNormalMatrix, rtcRayOrigin, rtcRayDir, rtcRayOrigin, rtcRayDir); // RTC -> local - - const a = tempVec3d; - const b = tempVec3e; - const c = tempVec3f; - - let gotIntersect = false; - let closestDist = 0; - const closestIntersectPos = tempVec3g; - - for (let i = 0, len = indices.length; i < len; i += 3) { - - const ia = indices[i] * 3; - const ib = indices[i + 1] * 3; - const ic = indices[i + 2] * 3; - - a[0] = positions[ia]; - a[1] = positions[ia + 1]; - a[2] = positions[ia + 2]; - - b[0] = positions[ib]; - b[1] = positions[ib + 1]; - b[2] = positions[ib + 2]; - - c[0] = positions[ic]; - c[1] = positions[ic + 1]; - c[2] = positions[ic + 2]; - - math.decompressPosition(a, state.positionsDecodeMatrix); - math.decompressPosition(b, state.positionsDecodeMatrix); - math.decompressPosition(c, state.positionsDecodeMatrix); - - if (math.rayTriangleIntersect(rtcRayOrigin, rtcRayDir, a, b, c, closestIntersectPos)) { - - math.transformPoint3(this.model.worldMatrix, closestIntersectPos, closestIntersectPos); - - if (offset) { - math.addVec3(closestIntersectPos, offset); - } - - if (origin) { - math.addVec3(closestIntersectPos, origin); - } - - const dist = Math.abs(math.lenVec3(math.subVec3(closestIntersectPos, worldRayOrigin, []))); - - if (!gotIntersect || dist > closestDist) { - closestDist = dist; - worldSurfacePos.set(closestIntersectPos); - if (worldNormal) { // Not that wasteful to eagerly compute - unlikely to hit >2 surfaces on most geometry - math.triangleNormal(a, b, c, worldNormal); - } - gotIntersect = true; - } - } - } - - if (gotIntersect && worldNormal) { - math.transformVec3(this.model.worldNormalMatrix, worldNormal, worldNormal); - math.normalizeVec3(worldNormal); - } - - return gotIntersect; - } - - // --------- - destroy() { const state = this._state; if (state.positionsBuf) { diff --git a/src/viewer/scene/models/VBOSceneModel/lib/layers/trianglesInstancing/TrianglesInstancingLayer.js b/src/viewer/scene/models/VBOSceneModel/lib/layers/trianglesInstancing/TrianglesInstancingLayer.js index cde41e21e..e99bda6f4 100644 --- a/src/viewer/scene/models/VBOSceneModel/lib/layers/trianglesInstancing/TrianglesInstancingLayer.js +++ b/src/viewer/scene/models/VBOSceneModel/lib/layers/trianglesInstancing/TrianglesInstancingLayer.js @@ -258,12 +258,6 @@ class TrianglesInstancingLayer { const portion = {}; - if (this.model.scene.pickSurfacePrecisionEnabled) { - portion.matrix = meshMatrix.slice(); - portion.inverseMatrix = null; // Lazy-computed in precisionRayPickSurface - portion.normalMatrix = null; // Lazy-computed in precisionRayPickSurface - } - this._portions.push(portion); this._numPortions++; @@ -658,41 +652,6 @@ class TrianglesInstancingLayer { } } - getEachVertex(portionId, callback) { - if (!this.model.scene.pickSurfacePrecisionEnabled) { - return false; - } - const state = this._state; - const geometry = state.geometry; - const portion = this._portions[portionId]; - if (!portion) { - this.model.error("portion not found: " + portionId); - return; - } - const positions = geometry.quantizedPositions; - const origin = state.origin; - const offset = portion.offset; - const offsetX = origin[0] + offset[0]; - const offsetY = origin[1] + offset[1]; - const offsetZ = origin[2] + offset[2]; - const worldPos = tempVec4a; - const portionMatrix = portion.matrix; - const modelMatrix = this.model.worldMatrix; - const positionsDecodeMatrix = geometry.positionsDecodeMatrix; - for (let i = 0, len = positions.length; i < len; i += 3) { - worldPos[0] = positions[i]; - worldPos[1] = positions[i + 1]; - worldPos[2] = positions[i + 2]; - math.decompressPosition(worldPos, positionsDecodeMatrix); - math.transformPoint3(portionMatrix, worldPos); - math.transformPoint3(modelMatrix, worldPos); - worldPos[0] += offsetX; - worldPos[1] += offsetY; - worldPos[2] += offsetZ; - callback(worldPos); - } - } - // ---------------------- COLOR RENDERING ----------------------------------- drawColorOpaque(renderFlags, frameCtx) { @@ -955,116 +914,6 @@ class TrianglesInstancingLayer { //----------------------------------------------------------------------------------------- - precisionRayPickSurface(portionId, worldRayOrigin, worldRayDir, worldSurfacePos, worldNormal) { - - if (!this.model.scene.pickSurfacePrecisionEnabled) { - return false; - } - - const geometry = this._state.geometry; - const state = this._state; - const portion = this._portions[portionId]; - - if (!portion) { - this.model.error("portion not found: " + portionId); - return false; - } - - if (!portion.inverseMatrix) { - portion.inverseMatrix = math.inverseMat4(portion.matrix, math.mat4()); - } - - if (worldNormal && !portion.normalMatrix) { - portion.normalMatrix = math.transposeMat4(portion.inverseMatrix, math.mat4()); - } - - const quantizedPositions = geometry.quantizedPositions; - const indices = geometry.indices; - const origin = state.origin; - const offset = portion.offset; - - const rtcRayOrigin = tempVec3a; - const rtcRayDir = tempVec3b; - - rtcRayOrigin.set(origin ? math.subVec3(worldRayOrigin, origin, tempVec3c) : worldRayOrigin); // World -> RTC - rtcRayDir.set(worldRayDir); - - if (offset) { - math.subVec3(rtcRayOrigin, offset); - } - - math.transformRay(this.model.worldNormalMatrix, rtcRayOrigin, rtcRayDir, rtcRayOrigin, rtcRayDir); - - math.transformRay(portion.inverseMatrix, rtcRayOrigin, rtcRayDir, rtcRayOrigin, rtcRayDir); - - const a = tempVec3d; - const b = tempVec3e; - const c = tempVec3f; - - let gotIntersect = false; - let closestDist = 0; - const closestIntersectPos = tempVec3g; - - for (let i = 0, len = indices.length; i < len; i += 3) { - - const ia = indices[i + 0] * 3; - const ib = indices[i + 1] * 3; - const ic = indices[i + 2] * 3; - - a[0] = quantizedPositions[ia]; - a[1] = quantizedPositions[ia + 1]; - a[2] = quantizedPositions[ia + 2]; - - b[0] = quantizedPositions[ib]; - b[1] = quantizedPositions[ib + 1]; - b[2] = quantizedPositions[ib + 2]; - - c[0] = quantizedPositions[ic]; - c[1] = quantizedPositions[ic + 1]; - c[2] = quantizedPositions[ic + 2]; - - const {positionsDecodeMatrix} = state.geometry; - - math.decompressPosition(a, positionsDecodeMatrix); - math.decompressPosition(b, positionsDecodeMatrix); - math.decompressPosition(c, positionsDecodeMatrix); - - if (math.rayTriangleIntersect(rtcRayOrigin, rtcRayDir, a, b, c, closestIntersectPos)) { - - math.transformPoint3(portion.matrix, closestIntersectPos, closestIntersectPos); - - math.transformPoint3(this.model.worldMatrix, closestIntersectPos, closestIntersectPos); - - if (offset) { - math.addVec3(closestIntersectPos, offset); - } - - if (origin) { - math.addVec3(closestIntersectPos, origin); - } - - const dist = Math.abs(math.lenVec3(math.subVec3(closestIntersectPos, worldRayOrigin, []))); - - if (!gotIntersect || dist > closestDist) { - closestDist = dist; - worldSurfacePos.set(closestIntersectPos); - if (worldNormal) { // Not that wasteful to eagerly compute - unlikely to hit >2 surfaces on most geometry - math.triangleNormal(a, b, c, worldNormal); - } - gotIntersect = true; - } - } - } - - if (gotIntersect && worldNormal) { - math.transformVec3(portion.normalMatrix, worldNormal, worldNormal); - math.transformVec3(this.model.worldNormalMatrix, worldNormal, worldNormal); - math.normalizeVec3(worldNormal); - } - - return gotIntersect; - } - destroy() { const state = this._state; if (state.colorsBuf) { diff --git a/src/viewer/scene/scene/Scene.js b/src/viewer/scene/scene/Scene.js index 20ff9ecc3..67a5dfa58 100644 --- a/src/viewer/scene/scene/Scene.js +++ b/src/viewer/scene/scene/Scene.js @@ -820,7 +820,6 @@ class Scene extends Component { this.gammaFactor = cfg.gammaFactor; this._entityOffsetsEnabled = !!cfg.entityOffsetsEnabled; - this._pickSurfacePrecisionEnabled = !!cfg.pickSurfacePrecisionEnabled; this._logarithmicDepthBufferEnabled = !!cfg.logarithmicDepthBufferEnabled; this._pbrEnabled = !!cfg.pbrEnabled; @@ -1223,7 +1222,7 @@ class Scene extends Component { * @returns {Boolean} True if precision picking is enabled. */ get pickSurfacePrecisionEnabled() { - return this._pickSurfacePrecisionEnabled; + return false; // Removed } /** diff --git a/src/viewer/scene/webgl/Renderer.js b/src/viewer/scene/webgl/Renderer.js index dbed4b8a5..8f8e6fbf7 100644 --- a/src/viewer/scene/webgl/Renderer.js +++ b/src/viewer/scene/webgl/Renderer.js @@ -981,6 +981,11 @@ const Renderer = function (scene, options) { } if (null !== pickViewMatrix) { + + //====================================================================================== + // Fix me + //========================================================================================== + // data-textures: update the pick-camera-matrices of all DataTextureSceneModel's for (let type in drawableTypeInfo) { if (drawableTypeInfo.hasOwnProperty(type)) { @@ -1020,60 +1025,35 @@ const Renderer = function (scene, options) { if (params.pickSurface) { - if (params.pickSurfacePrecision && scene.pickSurfacePrecisionEnabled) { - - // JavaScript-based ray-picking - slow and precise + // GPU-based ray-picking - if (params.canvasPos) { - math.canvasPosToWorldRay(scene.canvas.canvas, pickViewMatrix, pickProjMatrix, canvasPos, worldRayOrigin, worldRayDir); - } + if (pickable.canPickTriangle && pickable.canPickTriangle()) { - if (pickable.precisionRayPickSurface(worldRayOrigin, worldRayDir, worldSurfacePos, worldSurfaceNormal)) { + gpuPickTriangle(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult); - pickResult.worldPos = worldSurfacePos; + pickable.pickTriangleSurface(pickViewMatrix, pickProjMatrix, pickResult); - if (params.pickSurfaceNormal !== false) { - pickResult.worldNormal = worldSurfaceNormal; - } - - pickResult.pickSurfacePrecision = true; - } + pickResult.pickSurfacePrecision = false; } else { - // GPU-based ray-picking - fast and imprecise + if (pickable.canPickWorldPos && pickable.canPickWorldPos()) { - if (pickable.canPickTriangle && pickable.canPickTriangle()) { + nearAndFar[0] = scene.camera.project.near; + nearAndFar[1] = scene.camera.project.far; - gpuPickTriangle(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult); + gpuPickWorldPos(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, nearAndFar, pickResult); - pickable.pickTriangleSurface(pickViewMatrix, pickProjMatrix, pickResult); + if (params.pickSurfaceNormal !== false) { + gpuPickWorldNormal(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult); + } pickResult.pickSurfacePrecision = false; - - } else { - - if (pickable.canPickWorldPos && pickable.canPickWorldPos()) { - - nearAndFar[0] = scene.camera.project.near; - nearAndFar[1] = scene.camera.project.far; - - gpuPickWorldPos(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, nearAndFar, pickResult); - - if (params.pickSurfaceNormal !== false) { - gpuPickWorldNormal(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult); - } - - pickResult.pickSurfacePrecision = false; - } } } } - pickBuffer.unbind(); - pickResult.entity = pickedEntity; - return pickResult; }; })(); @@ -1493,7 +1473,7 @@ const Renderer = function (scene, options) { return { worldPos, snappedWorldPos, - snappedCanvasPos, + snappedCanvasPos }; };