From 4aef572a445f244e8e4dc0ffa2f94ce2b8e0cce3 Mon Sep 17 00:00:00 2001 From: Alexandru Popovici Date: Fri, 21 Apr 2023 13:12:15 +0300 Subject: [PATCH] Multi Viewer (#1518) * Fixed an issue with HDRIs not generating proper PMREMs over multiple viewer instances * WIP on makign the world tree multiple instanced * The WorldTree is no longer static. Each viewer has it's own instance and it hands it over to whoever needs it. * Fixed an issue with filtering and the new non-static tree. Also removed the 'root' key from the NodeData structure since it's not needed * Added an guard when building batches for situations where all render views have invalid geometries. This generally means there is somethign wrong with the stream itself * multi-viewer css fixes --------- Co-authored-by: Dimitrie Stefanescu --- packages/viewer-sandbox/index.html | 25 +- packages/viewer-sandbox/src/Sandbox.ts | 209 ++++---- packages/viewer-sandbox/src/main-multi.ts | 261 +++++++++ packages/viewer-sandbox/src/main.ts | 506 ++++++++---------- packages/viewer-sandbox/src/style.css | 11 + packages/viewer/src/IViewer.ts | 4 + packages/viewer/src/modules/Assets.ts | 16 +- packages/viewer/src/modules/DebugViewer.ts | 5 - packages/viewer/src/modules/SectionBox.js | 5 - .../viewer/src/modules/SpeckleRenderer.ts | 96 ++-- packages/viewer/src/modules/Viewer.ts | 45 +- .../viewer/src/modules/ViewerObjectLoader.ts | 7 +- packages/viewer/src/modules/batching/Batch.ts | 4 +- .../viewer/src/modules/batching/Batcher.ts | 115 ++-- .../viewer/src/modules/batching/LineBatch.ts | 7 +- .../viewer/src/modules/batching/MeshBatch.ts | 8 +- .../viewer/src/modules/batching/PointBatch.ts | 7 +- .../viewer/src/modules/converter/Converter.ts | 43 +- .../src/modules/filtering/FilteringManager.ts | 41 +- .../src/modules/filtering/PropertyManager.ts | 11 +- packages/viewer/src/modules/tree/DataTree.ts | 10 +- .../viewer/src/modules/tree/RenderTree.ts | 34 +- packages/viewer/src/modules/tree/WorldTree.ts | 66 +-- 23 files changed, 888 insertions(+), 648 deletions(-) create mode 100644 packages/viewer-sandbox/src/main-multi.ts diff --git a/packages/viewer-sandbox/index.html b/packages/viewer-sandbox/index.html index fd0deee75..12bd5395e 100644 --- a/packages/viewer-sandbox/index.html +++ b/packages/viewer-sandbox/index.html @@ -8,7 +8,30 @@ -
+
+
+
+
+ diff --git a/packages/viewer-sandbox/src/Sandbox.ts b/packages/viewer-sandbox/src/Sandbox.ts index bc0ec0dc0..3d2cee1ce 100644 --- a/packages/viewer-sandbox/src/Sandbox.ts +++ b/packages/viewer-sandbox/src/Sandbox.ts @@ -1,4 +1,4 @@ -import { Box3, Viewer, WorldTree } from '@speckle/viewer' +import { Box3, WorldTree } from '@speckle/viewer' import { Vector3 } from '@speckle/viewer' import { CanonicalView, @@ -23,11 +23,11 @@ export default class Sandbox { private objectControls private batchesFolder - public static urlParams = { + public urlParams = { url: 'https://latest.speckle.dev/streams/c43ac05d04/commits/ec724cfbeb' } - public static sceneParams = { + public sceneParams = { worldSize: { x: 0, y: 0, z: 0 }, worldOrigin: { x: 0, y: 0, z: 0 }, pixelThreshold: 0.5, @@ -35,7 +35,7 @@ export default class Sandbox { tonemapping: 4 //'ACESFilmicToneMapping' } - public static pipelineParams = { + public pipelineParams = { pipelineOutput: 8, accumulationFrames: 16, dynamicAoEnabled: true, @@ -61,7 +61,7 @@ export default class Sandbox { } } - public static lightParams: SunLightConfiguration = { + public lightParams: SunLightConfiguration = { enabled: true, castShadow: true, intensity: 5, @@ -73,7 +73,7 @@ export default class Sandbox { shadowcatcher: true } - public static batchesParams = { + public batchesParams = { showBvh: false, totalBvhSize: 0, explode: 0, @@ -81,11 +81,11 @@ export default class Sandbox { culling: true } - public static filterParams = { + public filterParams = { filterBy: 'Volume' } - public static shadowCatcherParams = { + public shadowCatcherParams = { textureSize: 512, weights: { x: 1, y: 1, z: 0, w: 1 }, blurRadius: 16, @@ -94,12 +94,17 @@ export default class Sandbox { sigmoidStrength: 2 } - public constructor(viewer: DebugViewer, selectionList: SelectionEvent[]) { + public constructor( + container: HTMLElement, + viewer: DebugViewer, + selectionList: SelectionEvent[] + ) { this.viewer = viewer this.selectionList = selectionList this.pane = new Pane({ title: 'Speckle Sandbox', expanded: true }) - this.pane['containerElem_'].style = - 'position:fixed; top: 5px; right: 5px; width: 300px;' + // Mad HTML/CSS skills + container.appendChild(this.pane['containerElem_']) + this.pane['containerElem_'].style = 'pointer-events:auto;' this.tabs = this.pane.addTab({ pages: [ @@ -116,20 +121,20 @@ export default class Sandbox { this.addViewControls() this.addBatches() this.properties = this.viewer.getObjectProperties() - Sandbox.batchesParams.totalBvhSize = this.getBVHSize() + this.batchesParams.totalBvhSize = this.getBVHSize() this.refresh() }) viewer.on(ViewerEvent.UnloadComplete, (url: string) => { this.removeViewControls() this.addViewControls() this.properties = this.viewer.getObjectProperties() - Viewer.World.reduceWorld(WorldTree.getRenderTree(url).treeBounds) + viewer.World.reduceWorld(WorldTree.getRenderTree(url).treeBounds) }) viewer.on(ViewerEvent.UnloadAllComplete, (url: string) => { this.removeViewControls() this.addViewControls() this.properties = this.viewer.getObjectProperties() - Viewer.World.resetWorld() + viewer.World.resetWorld() url }) viewer.on(ViewerEvent.ObjectClicked, (selectionEvent: SelectionEvent) => { @@ -284,7 +289,7 @@ export default class Sandbox { } public makeGenericUI() { - this.tabs.pages[0].addInput(Sandbox.urlParams, 'url', { + this.tabs.pages[0].addInput(this.urlParams, 'url', { title: 'url' }) @@ -293,7 +298,7 @@ export default class Sandbox { }) loadButton.on('click', () => { - this.loadUrl(Sandbox.urlParams.url) + this.loadUrl(this.urlParams.url) }) const clearButton = this.tabs.pages[0].addButton({ @@ -343,13 +348,19 @@ export default class Sandbox { }) const dark = localStorage.getItem('dark') === 'dark' if (dark) { + console.log(document.getElementById('multi-root')) + document.getElementById('multi-root')?.classList.toggle('background-dark') document.getElementById('renderer')?.classList.toggle('background-dark') } darkModeToggle.on('click', () => { - const dark = document - .getElementById('renderer') - ?.classList.toggle('background-dark') + let dark = false + if (document.getElementById('renderer')) + dark = document.getElementById('renderer')?.classList.toggle('background-dark') + else + dark = document + .getElementById('multi-root') + ?.classList.toggle('background-dark') localStorage.setItem('dark', dark ? `dark` : `light`) }) @@ -397,31 +408,31 @@ export default class Sandbox { title: 'World', expanded: true }) - worldFolder.addInput(Sandbox.sceneParams.worldSize, 'x', { + worldFolder.addInput(this.sceneParams.worldSize, 'x', { disabled: true, label: 'Size-x', step: 0.00000001 }) - worldFolder.addInput(Sandbox.sceneParams.worldSize, 'y', { + worldFolder.addInput(this.sceneParams.worldSize, 'y', { disabled: true, label: 'Size-y', step: 0.00000001 }) - worldFolder.addInput(Sandbox.sceneParams.worldSize, 'z', { + worldFolder.addInput(this.sceneParams.worldSize, 'z', { disabled: true, label: 'Size-z', step: 0.00000001 }) worldFolder.addSeparator() - worldFolder.addInput(Sandbox.sceneParams.worldOrigin, 'x', { + worldFolder.addInput(this.sceneParams.worldOrigin, 'x', { disabled: true, label: 'Origin-x' }) - worldFolder.addInput(Sandbox.sceneParams.worldOrigin, 'y', { + worldFolder.addInput(this.sceneParams.worldOrigin, 'y', { disabled: true, label: 'Origin-y' }) - worldFolder.addInput(Sandbox.sceneParams.worldOrigin, 'z', { + worldFolder.addInput(this.sceneParams.worldOrigin, 'z', { disabled: true, label: 'Origin-z' }) @@ -433,13 +444,13 @@ export default class Sandbox { }) postFolder - .addInput(Sandbox.sceneParams, 'exposure', { + .addInput(this.sceneParams, 'exposure', { min: 0, max: 1 }) .on('change', () => { this.viewer.getRenderer().renderer.toneMappingExposure = - Sandbox.sceneParams.exposure + this.sceneParams.exposure this.viewer.requestRender() }) @@ -468,14 +479,14 @@ export default class Sandbox { }) postFolder - .addInput(Sandbox.sceneParams, 'tonemapping', { + .addInput(this.sceneParams, 'tonemapping', { options: { Linear: 1, ACES: 4 } }) .on('change', () => { - this.viewer.getRenderer().renderer.toneMapping = Sandbox.sceneParams.tonemapping + this.viewer.getRenderer().renderer.toneMapping = this.sceneParams.tonemapping this.viewer.requestRender() }) @@ -484,7 +495,7 @@ export default class Sandbox { expanded: true }) pipelineFolder - .addInput(Sandbox.pipelineParams, 'pipelineOutput', { + .addInput(this.pipelineParams, 'pipelineOutput', { options: { DEPTH_RGBA: 0, DEPTH: 1, @@ -498,18 +509,18 @@ export default class Sandbox { } }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) pipelineFolder - .addInput(Sandbox.pipelineParams, 'accumulationFrames', { + .addInput(this.pipelineParams, 'accumulationFrames', { min: 1, max: 128, step: 1 }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) @@ -519,33 +530,33 @@ export default class Sandbox { }) dynamicAoFolder - .addInput(Sandbox.pipelineParams.dynamicAoParams, 'intensity', { min: 0, max: 5 }) + .addInput(this.pipelineParams.dynamicAoParams, 'intensity', { min: 0, max: 5 }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) dynamicAoFolder - .addInput(Sandbox.pipelineParams.dynamicAoParams, 'kernelRadius', { + .addInput(this.pipelineParams.dynamicAoParams, 'kernelRadius', { min: 0, max: 500 }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) dynamicAoFolder - .addInput(Sandbox.pipelineParams.dynamicAoParams, 'bias', { + .addInput(this.pipelineParams.dynamicAoParams, 'bias', { min: -1, max: 1 }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) dynamicAoFolder - .addInput(Sandbox.pipelineParams.dynamicAoParams, 'normalsType', { + .addInput(this.pipelineParams.dynamicAoParams, 'normalsType', { options: { DEFAULT: 0, ADVANCED: 1, @@ -553,35 +564,35 @@ export default class Sandbox { } }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) dynamicAoFolder - .addInput(Sandbox.pipelineParams.dynamicAoParams, 'blurEnabled', {}) + .addInput(this.pipelineParams.dynamicAoParams, 'blurEnabled', {}) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) dynamicAoFolder - .addInput(Sandbox.pipelineParams.dynamicAoParams, 'blurRadius', { + .addInput(this.pipelineParams.dynamicAoParams, 'blurRadius', { min: 0, max: 10 }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) dynamicAoFolder - .addInput(Sandbox.pipelineParams.dynamicAoParams, 'blurDepthCutoff', { + .addInput(this.pipelineParams.dynamicAoParams, 'blurDepthCutoff', { min: 0, max: 1, step: 0.00001 }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) @@ -596,13 +607,13 @@ export default class Sandbox { // this.viewer.requestRender() // }) staticAoFolder - .addInput(Sandbox.pipelineParams.staticAoParams, 'intensity', { + .addInput(this.pipelineParams.staticAoParams, 'intensity', { min: 0, max: 5, step: 0.01 }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) // staticAoFolder @@ -627,34 +638,34 @@ export default class Sandbox { // this.viewer.requestRender() // }) staticAoFolder - .addInput(Sandbox.pipelineParams.staticAoParams, 'kernelRadius', { + .addInput(this.pipelineParams.staticAoParams, 'kernelRadius', { min: 0, max: 1000 }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) staticAoFolder - .addInput(Sandbox.pipelineParams.staticAoParams, 'bias', { + .addInput(this.pipelineParams.staticAoParams, 'bias', { min: -1, max: 1, step: 0.0001 }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) staticAoFolder - .addInput(Sandbox.pipelineParams.staticAoParams, 'kernelSize', { + .addInput(this.pipelineParams.staticAoParams, 'kernelSize', { min: 1, max: 128, step: 1 }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.getRenderer().pipelineOptions = this.pipelineParams this.viewer.requestRender() }) @@ -667,62 +678,62 @@ export default class Sandbox { expanded: true }) directLightFolder - .addInput(Sandbox.lightParams, 'enabled', { + .addInput(this.lightParams, 'enabled', { label: 'Sun Enabled' }) .on('change', () => { - this.viewer.setLightConfiguration(Sandbox.lightParams) + this.viewer.setLightConfiguration(this.lightParams) }) directLightFolder - .addInput(Sandbox.lightParams, 'castShadow', { + .addInput(this.lightParams, 'castShadow', { label: 'Sun Shadows' }) .on('change', () => { - this.viewer.setLightConfiguration(Sandbox.lightParams) + this.viewer.setLightConfiguration(this.lightParams) }) directLightFolder - .addInput(Sandbox.lightParams, 'intensity', { + .addInput(this.lightParams, 'intensity', { label: 'Sun Intensity', min: 0, max: 10 }) .on('change', () => { - this.viewer.setLightConfiguration(Sandbox.lightParams) + this.viewer.setLightConfiguration(this.lightParams) }) directLightFolder - .addInput(Sandbox.lightParams, 'color', { + .addInput(this.lightParams, 'color', { view: 'color', label: 'Sun Color' }) .on('change', () => { - this.viewer.setLightConfiguration(Sandbox.lightParams) + this.viewer.setLightConfiguration(this.lightParams) }) directLightFolder - .addInput(Sandbox.lightParams, 'elevation', { + .addInput(this.lightParams, 'elevation', { label: 'Sun Elevation', min: 0, max: Math.PI }) .on('change', () => { - this.viewer.setLightConfiguration(Sandbox.lightParams) + this.viewer.setLightConfiguration(this.lightParams) }) directLightFolder - .addInput(Sandbox.lightParams, 'azimuth', { + .addInput(this.lightParams, 'azimuth', { label: 'Sun Azimuth', min: -Math.PI * 0.5, max: Math.PI * 0.5 }) .on('change', () => { - this.viewer.setLightConfiguration(Sandbox.lightParams) + this.viewer.setLightConfiguration(this.lightParams) }) directLightFolder - .addInput(Sandbox.lightParams, 'radius', { + .addInput(this.lightParams, 'radius', { label: 'Sun Radius', min: 0, max: 1000 }) .on('change', () => { - this.viewer.setLightConfiguration(Sandbox.lightParams) + this.viewer.setLightConfiguration(this.lightParams) }) directLightFolder @@ -757,14 +768,14 @@ export default class Sandbox { }) indirectLightsFolder - .addInput(Sandbox.lightParams, 'indirectLightIntensity', { + .addInput(this.lightParams, 'indirectLightIntensity', { label: 'Probe Intensity', min: 0, max: 10 }) .on('change', (value) => { value - this.viewer.setLightConfiguration(Sandbox.lightParams) + this.viewer.setLightConfiguration(this.lightParams) }) const shadowcatcherFolder = this.tabs.pages[1].addFolder({ @@ -773,14 +784,14 @@ export default class Sandbox { }) shadowcatcherFolder - .addInput(Sandbox.lightParams, 'shadowcatcher', { label: 'Enabled' }) + .addInput(this.lightParams, 'shadowcatcher', { label: 'Enabled' }) .on('change', (value) => { value - this.viewer.setLightConfiguration(Sandbox.lightParams) + this.viewer.setLightConfiguration(this.lightParams) }) shadowcatcherFolder - .addInput(Sandbox.shadowCatcherParams, 'textureSize', { + .addInput(this.shadowCatcherParams, 'textureSize', { label: 'Texture Size', min: 1, max: 1024, @@ -788,12 +799,11 @@ export default class Sandbox { }) .on('change', (value) => { value - this.viewer.getRenderer().shadowcatcher.configuration = - Sandbox.shadowCatcherParams + this.viewer.getRenderer().shadowcatcher.configuration = this.shadowCatcherParams this.viewer.getRenderer().updateShadowCatcher() }) shadowcatcherFolder - .addInput(Sandbox.shadowCatcherParams, 'weights', { + .addInput(this.shadowCatcherParams, 'weights', { label: 'weights', x: { min: 0, max: 100 }, y: { min: 0, max: 100 }, @@ -802,12 +812,11 @@ export default class Sandbox { }) .on('change', (value) => { value - this.viewer.getRenderer().shadowcatcher.configuration = - Sandbox.shadowCatcherParams + this.viewer.getRenderer().shadowcatcher.configuration = this.shadowCatcherParams this.viewer.getRenderer().updateShadowCatcher() }) shadowcatcherFolder - .addInput(Sandbox.shadowCatcherParams, 'blurRadius', { + .addInput(this.shadowCatcherParams, 'blurRadius', { label: 'Blur Radius', min: 1, max: 128, @@ -815,12 +824,11 @@ export default class Sandbox { }) .on('change', (value) => { value - this.viewer.getRenderer().shadowcatcher.configuration = - Sandbox.shadowCatcherParams + this.viewer.getRenderer().shadowcatcher.configuration = this.shadowCatcherParams this.viewer.getRenderer().updateShadowCatcher() }) shadowcatcherFolder - .addInput(Sandbox.shadowCatcherParams, 'stdDeviation', { + .addInput(this.shadowCatcherParams, 'stdDeviation', { label: 'Blur Std Deviation', min: 1, max: 128, @@ -828,12 +836,11 @@ export default class Sandbox { }) .on('change', (value) => { value - this.viewer.getRenderer().shadowcatcher.configuration = - Sandbox.shadowCatcherParams + this.viewer.getRenderer().shadowcatcher.configuration = this.shadowCatcherParams this.viewer.getRenderer().updateShadowCatcher() }) shadowcatcherFolder - .addInput(Sandbox.shadowCatcherParams, 'sigmoidRange', { + .addInput(this.shadowCatcherParams, 'sigmoidRange', { label: 'Sigmoid Range', min: -10, max: 10, @@ -841,12 +848,11 @@ export default class Sandbox { }) .on('change', (value) => { value - this.viewer.getRenderer().shadowcatcher.configuration = - Sandbox.shadowCatcherParams + this.viewer.getRenderer().shadowcatcher.configuration = this.shadowCatcherParams this.viewer.getRenderer().updateShadowCatcher() }) shadowcatcherFolder - .addInput(Sandbox.shadowCatcherParams, 'sigmoidStrength', { + .addInput(this.shadowCatcherParams, 'sigmoidStrength', { label: 'Sigmoid Strength', min: -10, max: 10, @@ -854,8 +860,7 @@ export default class Sandbox { }) .on('change', (value) => { value - this.viewer.getRenderer().shadowcatcher.configuration = - Sandbox.shadowCatcherParams + this.viewer.getRenderer().shadowcatcher.configuration = this.shadowCatcherParams this.viewer.getRenderer().updateShadowCatcher() }) } @@ -866,7 +871,7 @@ export default class Sandbox { expanded: true }) - filteringFolder.addInput(Sandbox.filterParams, 'filterBy', { + filteringFolder.addInput(this.filterParams, 'filterBy', { options: { Volume: 'parameters.HOST_VOLUME_COMPUTED.value', Area: 'parameters.HOST_AREA_COMPUTED.value', @@ -883,7 +888,7 @@ export default class Sandbox { }) .on('click', () => { const data = this.properties.find((value) => { - return value.key === Sandbox.filterParams.filterBy + return value.key === this.filterParams.filterBy }) as PropertyInfo this.viewer.setColorFilter(data) this.pane.refresh() @@ -909,19 +914,19 @@ export default class Sandbox { }) container - .addInput(Sandbox.batchesParams, 'showBvh', { + .addInput(this.batchesParams, 'showBvh', { label: 'Show BVH' }) .on('change', (ev) => { this.viewer.getRenderer().showBVH = ev.value this.viewer.requestRender() }) - container.addInput(Sandbox.batchesParams, 'totalBvhSize', { + container.addInput(this.batchesParams, 'totalBvhSize', { label: 'BVH Size(MB)', disabled: true }) container - .addInput(Sandbox.batchesParams, 'explode', { + .addInput(this.batchesParams, 'explode', { label: 'Explode', min: 0, max: 0.25, @@ -929,13 +934,10 @@ export default class Sandbox { }) .on('change', (value) => { value - this.viewer.explode( - Sandbox.batchesParams.explode, - Sandbox.batchesParams.explodeRange - ) + this.viewer.explode(this.batchesParams.explode, this.batchesParams.explodeRange) }) container - .addInput(Sandbox.batchesParams, 'explodeRange', { + .addInput(this.batchesParams, 'explodeRange', { label: 'Explode Range', min: 1, max: 1000, @@ -943,10 +945,7 @@ export default class Sandbox { }) .on('change', (value) => { value - this.viewer.explode( - Sandbox.batchesParams.explode, - Sandbox.batchesParams.explodeRange - ) + this.viewer.explode(this.batchesParams.explode, this.batchesParams.explodeRange) }) // container // .addInput(Sandbox.batchesParams, 'culling', { diff --git a/packages/viewer-sandbox/src/main-multi.ts b/packages/viewer-sandbox/src/main-multi.ts new file mode 100644 index 000000000..3f2435bca --- /dev/null +++ b/packages/viewer-sandbox/src/main-multi.ts @@ -0,0 +1,261 @@ +import { + DefaultViewerParams, + SelectionEvent, + ViewerEvent, + DebugViewer, + Viewer +} from '@speckle/viewer' + +import './style.css' +import Sandbox from './Sandbox' + +// const container0 = document.querySelector('#renderer0') +// if (!container0) { +// throw new Error("Couldn't find #app container!") +// } + +// const container1 = document.querySelector('#renderer1') +// if (!container1) { +// throw new Error("Couldn't find #app container!") +// } + +const createViewer = async (containerName: string, stream: string) => { + const container = document.querySelector(containerName) + const controlsContainer = document.querySelector( + `${containerName}-controls` + ) + if (!container) { + throw new Error("Couldn't find #app container!") + } + if (!controlsContainer) { + throw new Error("Couldn't find #app controls container!") + } + + // Viewer setup + const params = DefaultViewerParams + params.showStats = true + params.verbose = true + + const multiSelectList: SelectionEvent[] = [] + const viewer: Viewer = new DebugViewer(container, params) + await viewer.init() + + const sandbox = new Sandbox(controlsContainer, viewer as DebugViewer, multiSelectList) + + window.addEventListener('load', () => { + viewer.resize() + }) + + viewer.on( + ViewerEvent.LoadProgress, + (a: { progress: number; id: string; url: string }) => { + if (a.progress >= 1) { + viewer.resize() + } + } + ) + + viewer.on(ViewerEvent.LoadComplete, () => { + Object.assign(sandbox.sceneParams.worldSize, viewer.World.worldSize) + Object.assign(sandbox.sceneParams.worldOrigin, viewer.World.worldOrigin) + sandbox.refresh() + }) + + viewer.on(ViewerEvent.UnloadComplete, () => { + Object.assign(sandbox.sceneParams.worldSize, viewer.World.worldSize) + Object.assign(sandbox.sceneParams.worldOrigin, viewer.World.worldOrigin) + sandbox.refresh() + }) + viewer.on(ViewerEvent.UnloadAllComplete, () => { + Object.assign(sandbox.sceneParams.worldSize, viewer.World.worldSize) + Object.assign(sandbox.sceneParams.worldOrigin, viewer.World.worldOrigin) + sandbox.refresh() + }) + + viewer.on(ViewerEvent.ObjectClicked, async (selectionInfo: SelectionEvent) => { + if (!selectionInfo) { + multiSelectList.length = 0 + await viewer.resetSelection() + viewer.setSectionBox() + return + } + if (!selectionInfo.multiple) multiSelectList.length = 0 + + const guids = multiSelectList.map((val) => val.hits[0].guid) + if ( + (selectionInfo.multiple && !guids.includes(selectionInfo.hits[0].guid)) || + multiSelectList.length === 0 + ) { + multiSelectList.push(selectionInfo) + } + + const ids = multiSelectList.map((val) => val.hits[0].object.id) + console.log(selectionInfo) + await viewer.selectObjects(ids as string[]) + }) + + viewer.on(ViewerEvent.ObjectDoubleClicked, async (selectionInfo: SelectionEvent) => { + if (!selectionInfo) { + viewer.zoom() + return + } + + viewer.zoom([selectionInfo.hits[0].object.id as string]) + }) + + sandbox.makeGenericUI() + sandbox.makeSceneUI() + sandbox.makeFilteringUI() + sandbox.makeBatchesUI() + + await sandbox.loadUrl(stream) +} + +createViewer( + '#renderer0', + 'https://latest.speckle.dev/streams/0c6ad366c4/commits/aa1c393aec' +) +createViewer('#renderer1', 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8') + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const getStream = () => { + return ( + // prettier-ignore + // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D' + // Revit sample house (good for bim-like stuff with many display meshes) + + 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' + // 'Super' heavy revit shit + // 'https://speckle.xyz/streams/e6f9156405/commits/0694d53bb5' + // IFC building (good for a tree based structure) + // 'https://latest.speckle.dev/streams/92b620fb17/commits/2ebd336223' + // IFC story, a subtree of the above + // 'https://latest.speckle.dev/streams/92b620fb17/objects/8247bbc53865b0e0cb5ee4e252e66216' + // Small scale lines + // 'https://speckle.xyz/streams/638d3b1f83/commits/6025e2b546?c=%5B2.18058,-0.20814,9.67642,3.85491,5.05364,0,0,1%5D' + // 'https://latest.speckle.dev/streams/3ed8357f29/commits/d10f2af1ce' + // 'https://latest.speckle.dev/streams/444bfbd6e4/commits/e22f696b08' + // 'https://latest.speckle.dev/streams/92b620fb17/commits/af6098915b?c=%5B0.02144,-0.0377,0.05554,0.00566,0.00236,0,0,1%5D' + // AutoCAD + // 'https://latest.speckle.dev/streams/3ed8357f29/commits/d10f2af1ce' + //Blizzard world + // 'https://latest.speckle.dev/streams/0c6ad366c4/commits/aa1c393aec' + //Car + // 'https://latest.speckle.dev/streams/17d2e25a97/commits/6b6cf3d43e' + // Jonathon's + // 'https://latest.speckle.dev/streams/501258ee5f/commits/f885570011' + // Alex's cube + // 'https://latest.speckle.dev/streams/46e3e0e1ec/commits/a6392c19d6?c=%5B6.85874,2.9754,0.79022,0,0,0,0,1%5D' + // Groups of groups + // 'https://speckle.xyz/streams/1ce562e99a/commits/6fa28a5a0f' + // Arc flowers + // 'https://latest.speckle.dev/streams/9e6c4343ba/commits/037e382aa2' + // Car lines + // 'https://speckle.xyz/streams/638d3b1f83/commits/6025e2b546?c=%5B2.18058,-0.20814,9.67642,3.85491,5.05364,0,0,1%5D' + // Arc and lines + // ' https://speckle.xyz/streams/99abc74dd4/commits/b32fdcf171?c=%5B198440.6051,6522070.21462,19199.49584,176653.24219,6523663.5,0,0,1%5D' + // AUTOCAD test stream + // 'https://latest.speckle.dev/streams/3ed8357f29/commits/b49bfc73ea' + // REVIT test stream + // 'https://latest.speckle.dev/streams/c544db35f5/commits/7c29374369' + // Arcs + // 'https://latest.speckle.dev/streams/0c6ad366c4/commits/912d83412e' + // Freezers + // 'https://speckle.xyz/streams/f0532359ac/commits/98678e2a3d?c=%5B2455.15367,2689.87156,4366.68444,205.422,-149.41199,148.749,0,1%5D' + //Gergo's house + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/78bdd8eb76' + // Point cloud + // 'https://latest.speckle.dev/streams/2d19273d31/commits/9ceb423feb' + // 'https://latest.speckle.dev/streams/7707df6cae/commits/02bdf09092' + // 'https://latest.speckle.dev/streams/ca0378725b/commits/fbae00db5a' + // Luis sphere + // 'https://speckle.xyz/streams/b85d53c3b4/commits/b47f21b707' + // Crankshaft + // 'https://speckle.xyz/streams/c239718aff/commits/b3a8cfb97d' + // Building AO params + // 'https://latest.speckle.dev/streams/0dd74866d0/commits/317e210afa' + // Murder Cube + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/7f0c4d2fc1/' + // Classroom + // 'https://speckle.xyz/streams/0208ffb67b/commits/a980292728' + // 'https://latest.speckle.dev/streams/4658eb53b9/commits/328bd99997' + // 'https://latest.speckle.dev/streams/83e18d886f/commits/532bd6be3e' + // 'https://latest.speckle.dev/streams/1c2b3db9fb/commits/f12861736e' + // 'https://latest.speckle.dev/streams/1c2b3db9fb/commits/1015d417ea' + // Jedd's views + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/e6632fe057' + // 'https://latest.speckle.dev/streams/7d051a6449/commits/7632757a33' + // 'https://latest.speckle.dev/streams/4658eb53b9/commits/d8ec9cccf7' + // MEPs (whatever they are) + // 'https://latest.speckle.dev/streams/85bc4f61c6/commits/8575fe2978' + // Alex cubes + // 'https://latest.speckle.dev/streams/4658eb53b9/commits/d8ec9cccf7' + // Tekla + // 'https://latest.speckle.dev/streams/caec6d6676/commits/588c731104' + // Purple market square + // 'https://latest.speckle.dev/streams/4ed51ed832/commits/5a313ac116' + // Sum building + // 'https://latest.speckle.dev/streams/92b620fb17/commits/4ea2759162' + // Boat + // 'https://latest.speckle.dev/streams/92b620fb17/commits/ba5df427db' + // 'https://latest.speckle.dev/streams/92b620fb17/commits/c9ebe49824' + // Dim's dome + // 'https://latest.speckle.dev/streams/92b620fb17/commits/158d4e8bec' + // Engines 'n Shit + // 'https://latest.speckle.dev/streams/92b620fb17/commits/80b25e6e6c' + // Dim's tower + // 'https://latest.speckle.dev/streams/92b620fb17/commits/7fd3ec04c0' + //COD + // 'https://latest.speckle.dev/streams/d3c83b47bf/commits/5f76b7ef3d?overlay=34577a1a92,571d460754,4c39b56c32,a62dd3a5da&c=%5B2046.38919,1074.97765,125.18054,2088.91862,1025.71927,94.66317,0,1%5D' + // 'https://latest.speckle.dev/streams/4658eb53b9/commits/0feb23d263' + // Jonathon's not loading + // 'https://speckle.xyz/streams/ca99defd4b/commits/589b265c99' + // Jonathon's 3070 + // 'https://speckle.xyz/streams/7ce9010d71/commits/d29e56fe75' + // Filter issue + // 'https://speckle.xyz/streams/f95d8deb90/commits/30f31becb6' + // Transparent + // 'https://latest.speckle.dev/streams/b5cc4e967c/objects/20343e0e8d469613a9d407499a6c38b1' + // dark + // 'https://latest.speckle.dev/streams/b5cc4e967c/commits/efdf3e2728?c=%5B-59.16128,-41.76491,-4.77376,-4.08052,-12.63558,-4.77376,0,1%5D' + // 'https://latest.speckle.dev/streams/92b620fb17/commits/b4366a7086?filter=%7B%7D&c=%5B-31.02357,37.60008,96.58899,11.01564,7.40652,66.0411,0,1%5D)' + // double + // 'https://latest.speckle.dev/streams/92b620fb17/commits/b4366a7086?overlay=c009dbe144&filter=%7B%7D&c=%5B-104.70053,-98.80617,67.44669,6.53096,1.8739,38.584,0,1%5D' + // 'https://latest.speckle.dev/streams/c43ac05d04/commits/ec724cfbeb', + // 'https://latest.speckle.dev/streams/efd2c6a31d/commits/4b495e1901' + // 'https://latest.speckle.dev/streams/efd2c6a31d/commits/4b495e1901' + // tekla 2 + // 'https://speckle.xyz/streams/be4813ccd2/commits/da85000921?c=%5B-1.12295,-2.60901,6.12402,4.77979,0.555,3.63346,0,1%5D' + // 'https://latest.speckle.dev/streams/85bc4f61c6/commits/bb7b718a1a' + + // large meshes + // 'https://speckle.xyz/streams/48e6e33aa6/commits/2cf892f1b0' + // large lines + // 'https://latest.speckle.dev/streams/444bfbd6e4/commits/8f297ad0cd' + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/6b1b1195c4' + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/cef1e7527b' + // large lines + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/49dad07ae2' + // Instances Rhino + // 'https://latest.speckle.dev/streams/f92e060177/commits/1fff853107' + // Instances Revit + // 'https://latest.speckle.dev/streams/f92e060177/commits/92858681b7' + // 'https://latest.speckle.dev/streams/f92e060177/commits/655771674e' + // 'https://latest.speckle.dev/streams/f92e060177/commits/00dbbf4509' + // 'https://latest.speckle.dev/streams/f92e060177/commits/46fd255010' + // 'https://latest.speckle.dev/streams/f92e060177/commits/038a587267' + // 'https://latest.speckle.dev/streams/3f895e614f/commits/8a3e424997' + // Big curves + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/49dad07ae2' + // 'https://speckle.xyz/streams/7ce9010d71/commits/afda4ffdf8' + // Jonathon's lines + // 'https://speckle.xyz/streams/7ce9010d71/commits/8cd9e7e4fc' + // 'https://speckle.xyz/streams/7ce9010d71/objects/f46f95746975591c18b0b854dab5b570 ' + // 'https://speckle.xyz/streams/813b728084/commits/e2f5ac9775' + // Overlayhs + // 'https://latest.speckle.dev/streams/85b9f0b9f5/commits/cdfbf3e036?overlay=71f61af444,00fe449457,53a6692b79' + //'Rafinery' + // 'https://speckle.xyz/streams/b7cac6a6df/commits/2e42381302' + // 'https://speckle.xyz/streams/7ce9010d71/commits/b8bbfd0c05?c=%5B-4.50925,11.1348,5.38124,-0.23829,0.68512,-0.09006,0,1%5D' + ) +} diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 4dcfbae4f..6ace0dfcd 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -9,279 +9,245 @@ import { import './style.css' import Sandbox from './Sandbox' -const container = document.querySelector('#renderer') -if (!container) { - throw new Error("Couldn't find #app container!") +const createViewer = async (containerName: string, stream: string) => { + const container = document.querySelector(containerName) + + const controlsContainer = document.querySelector( + `${containerName}-controls` + ) + if (!container) { + throw new Error("Couldn't find #app container!") + } + if (!controlsContainer) { + throw new Error("Couldn't find #app controls container!") + } + + // Viewer setup + const params = DefaultViewerParams + params.showStats = true + params.verbose = true + + const multiSelectList: SelectionEvent[] = [] + const viewer: Viewer = new DebugViewer(container, params) + await viewer.init() + + const sandbox = new Sandbox(controlsContainer, viewer as DebugViewer, multiSelectList) + + window.addEventListener('load', () => { + viewer.resize() + }) + + viewer.on( + ViewerEvent.LoadProgress, + (a: { progress: number; id: string; url: string }) => { + if (a.progress >= 1) { + viewer.resize() + } + } + ) + + viewer.on(ViewerEvent.LoadComplete, () => { + Object.assign(sandbox.sceneParams.worldSize, viewer.World.worldSize) + Object.assign(sandbox.sceneParams.worldOrigin, viewer.World.worldOrigin) + sandbox.refresh() + }) + + viewer.on(ViewerEvent.UnloadComplete, () => { + Object.assign(sandbox.sceneParams.worldSize, viewer.World.worldSize) + Object.assign(sandbox.sceneParams.worldOrigin, viewer.World.worldOrigin) + sandbox.refresh() + }) + viewer.on(ViewerEvent.UnloadAllComplete, () => { + Object.assign(sandbox.sceneParams.worldSize, viewer.World.worldSize) + Object.assign(sandbox.sceneParams.worldOrigin, viewer.World.worldOrigin) + sandbox.refresh() + }) + + viewer.on(ViewerEvent.ObjectClicked, async (selectionInfo: SelectionEvent) => { + if (!selectionInfo) { + multiSelectList.length = 0 + await viewer.resetSelection() + viewer.setSectionBox() + return + } + if (!selectionInfo.multiple) multiSelectList.length = 0 + + const guids = multiSelectList.map((val) => val.hits[0].guid) + if ( + (selectionInfo.multiple && !guids.includes(selectionInfo.hits[0].guid)) || + multiSelectList.length === 0 + ) { + multiSelectList.push(selectionInfo) + } + + const ids = multiSelectList.map((val) => val.hits[0].object.id) + console.log(selectionInfo) + await viewer.selectObjects(ids as string[]) + }) + + viewer.on(ViewerEvent.ObjectDoubleClicked, async (selectionInfo: SelectionEvent) => { + if (!selectionInfo) { + viewer.zoom() + return + } + + viewer.zoom([selectionInfo.hits[0].object.id as string]) + }) + + sandbox.makeGenericUI() + sandbox.makeSceneUI() + sandbox.makeFilteringUI() + sandbox.makeBatchesUI() + + await sandbox.loadUrl(stream) } -// Viewer setup -const params = DefaultViewerParams -params.showStats = true -params.verbose = true +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const getStream = () => { + return ( + // prettier-ignore + // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D' + // Revit sample house (good for bim-like stuff with many display meshes) -const multiSelectList: SelectionEvent[] = [] -const viewer: Viewer = new DebugViewer(container, params) -await viewer.init() + 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' + // 'Super' heavy revit shit + // 'https://speckle.xyz/streams/e6f9156405/commits/0694d53bb5' + // IFC building (good for a tree based structure) + // 'https://latest.speckle.dev/streams/92b620fb17/commits/2ebd336223' + // IFC story, a subtree of the above + // 'https://latest.speckle.dev/streams/92b620fb17/objects/8247bbc53865b0e0cb5ee4e252e66216' + // Small scale lines + // 'https://speckle.xyz/streams/638d3b1f83/commits/6025e2b546?c=%5B2.18058,-0.20814,9.67642,3.85491,5.05364,0,0,1%5D' + // 'https://latest.speckle.dev/streams/3ed8357f29/commits/d10f2af1ce' + // 'https://latest.speckle.dev/streams/444bfbd6e4/commits/e22f696b08' + // 'https://latest.speckle.dev/streams/92b620fb17/commits/af6098915b?c=%5B0.02144,-0.0377,0.05554,0.00566,0.00236,0,0,1%5D' + // AutoCAD + // 'https://latest.speckle.dev/streams/3ed8357f29/commits/d10f2af1ce' + //Blizzard world + // 'https://latest.speckle.dev/streams/0c6ad366c4/commits/aa1c393aec' + //Car + // 'https://latest.speckle.dev/streams/17d2e25a97/commits/6b6cf3d43e' + // Jonathon's + // 'https://latest.speckle.dev/streams/501258ee5f/commits/f885570011' + // Alex's cube + // 'https://latest.speckle.dev/streams/46e3e0e1ec/commits/a6392c19d6?c=%5B6.85874,2.9754,0.79022,0,0,0,0,1%5D' + // Groups of groups + // 'https://speckle.xyz/streams/1ce562e99a/commits/6fa28a5a0f' + // Arc flowers + // 'https://latest.speckle.dev/streams/9e6c4343ba/commits/037e382aa2' + // Car lines + // 'https://speckle.xyz/streams/638d3b1f83/commits/6025e2b546?c=%5B2.18058,-0.20814,9.67642,3.85491,5.05364,0,0,1%5D' + // Arc and lines + // ' https://speckle.xyz/streams/99abc74dd4/commits/b32fdcf171?c=%5B198440.6051,6522070.21462,19199.49584,176653.24219,6523663.5,0,0,1%5D' + // AUTOCAD test stream + // 'https://latest.speckle.dev/streams/3ed8357f29/commits/b49bfc73ea' + // REVIT test stream + // 'https://latest.speckle.dev/streams/c544db35f5/commits/7c29374369' + // Arcs + // 'https://latest.speckle.dev/streams/0c6ad366c4/commits/912d83412e' + // Freezers + // 'https://speckle.xyz/streams/f0532359ac/commits/98678e2a3d?c=%5B2455.15367,2689.87156,4366.68444,205.422,-149.41199,148.749,0,1%5D' + //Gergo's house + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/78bdd8eb76' + // Point cloud + // 'https://latest.speckle.dev/streams/2d19273d31/commits/9ceb423feb' + // 'https://latest.speckle.dev/streams/7707df6cae/commits/02bdf09092' + // 'https://latest.speckle.dev/streams/ca0378725b/commits/fbae00db5a' + // Luis sphere + // 'https://speckle.xyz/streams/b85d53c3b4/commits/b47f21b707' + // Crankshaft + // 'https://speckle.xyz/streams/c239718aff/commits/b3a8cfb97d' + // Building AO params + // 'https://latest.speckle.dev/streams/0dd74866d0/commits/317e210afa' + // Murder Cube + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/7f0c4d2fc1/' + // Classroom + // 'https://speckle.xyz/streams/0208ffb67b/commits/a980292728' + // 'https://latest.speckle.dev/streams/4658eb53b9/commits/328bd99997' + // 'https://latest.speckle.dev/streams/83e18d886f/commits/532bd6be3e' + // 'https://latest.speckle.dev/streams/1c2b3db9fb/commits/f12861736e' + // 'https://latest.speckle.dev/streams/1c2b3db9fb/commits/1015d417ea' + // Jedd's views + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/e6632fe057' + // 'https://latest.speckle.dev/streams/7d051a6449/commits/7632757a33' + // 'https://latest.speckle.dev/streams/4658eb53b9/commits/d8ec9cccf7' + // MEPs (whatever they are) + // 'https://latest.speckle.dev/streams/85bc4f61c6/commits/8575fe2978' + // Alex cubes + // 'https://latest.speckle.dev/streams/4658eb53b9/commits/d8ec9cccf7' + // Tekla + // 'https://latest.speckle.dev/streams/caec6d6676/commits/588c731104' + // Purple market square + // 'https://latest.speckle.dev/streams/4ed51ed832/commits/5a313ac116' + // Sum building + // 'https://latest.speckle.dev/streams/92b620fb17/commits/4ea2759162' + // Boat + // 'https://latest.speckle.dev/streams/92b620fb17/commits/ba5df427db' + // 'https://latest.speckle.dev/streams/92b620fb17/commits/c9ebe49824' + // Dim's dome + // 'https://latest.speckle.dev/streams/92b620fb17/commits/158d4e8bec' + // Engines 'n Shit + // 'https://latest.speckle.dev/streams/92b620fb17/commits/80b25e6e6c' + // Dim's tower + // 'https://latest.speckle.dev/streams/92b620fb17/commits/7fd3ec04c0' + //COD + // 'https://latest.speckle.dev/streams/d3c83b47bf/commits/5f76b7ef3d?overlay=34577a1a92,571d460754,4c39b56c32,a62dd3a5da&c=%5B2046.38919,1074.97765,125.18054,2088.91862,1025.71927,94.66317,0,1%5D' + // 'https://latest.speckle.dev/streams/4658eb53b9/commits/0feb23d263' + // Jonathon's not loading + // 'https://speckle.xyz/streams/ca99defd4b/commits/589b265c99' + // Jonathon's 3070 + // 'https://speckle.xyz/streams/7ce9010d71/commits/d29e56fe75' + // Filter issue + // 'https://speckle.xyz/streams/f95d8deb90/commits/30f31becb6' + // Transparent + // 'https://latest.speckle.dev/streams/b5cc4e967c/objects/20343e0e8d469613a9d407499a6c38b1' + // dark + // 'https://latest.speckle.dev/streams/b5cc4e967c/commits/efdf3e2728?c=%5B-59.16128,-41.76491,-4.77376,-4.08052,-12.63558,-4.77376,0,1%5D' + // 'https://latest.speckle.dev/streams/92b620fb17/commits/b4366a7086?filter=%7B%7D&c=%5B-31.02357,37.60008,96.58899,11.01564,7.40652,66.0411,0,1%5D)' + // double + // 'https://latest.speckle.dev/streams/92b620fb17/commits/b4366a7086?overlay=c009dbe144&filter=%7B%7D&c=%5B-104.70053,-98.80617,67.44669,6.53096,1.8739,38.584,0,1%5D' + // 'https://latest.speckle.dev/streams/c43ac05d04/commits/ec724cfbeb', + // 'https://latest.speckle.dev/streams/efd2c6a31d/commits/4b495e1901' + // 'https://latest.speckle.dev/streams/efd2c6a31d/commits/4b495e1901' + // tekla 2 + // 'https://speckle.xyz/streams/be4813ccd2/commits/da85000921?c=%5B-1.12295,-2.60901,6.12402,4.77979,0.555,3.63346,0,1%5D' + // 'https://latest.speckle.dev/streams/85bc4f61c6/commits/bb7b718a1a' -const sandbox = new Sandbox(viewer as DebugViewer, multiSelectList) + // large meshes + // 'https://speckle.xyz/streams/48e6e33aa6/commits/2cf892f1b0' + // large lines + // 'https://latest.speckle.dev/streams/444bfbd6e4/commits/8f297ad0cd' + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/6b1b1195c4' + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/cef1e7527b' + // large lines + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/49dad07ae2' + // Instances Rhino + // 'https://latest.speckle.dev/streams/f92e060177/commits/1fff853107' + // Instances Revit + // 'https://latest.speckle.dev/streams/f92e060177/commits/92858681b7' + // 'https://latest.speckle.dev/streams/f92e060177/commits/655771674e' + // 'https://latest.speckle.dev/streams/f92e060177/commits/00dbbf4509' + // 'https://latest.speckle.dev/streams/f92e060177/commits/46fd255010' + // 'https://latest.speckle.dev/streams/f92e060177/commits/038a587267' + // 'https://latest.speckle.dev/streams/3f895e614f/commits/8a3e424997' + // Big curves + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/49dad07ae2' + // 'https://speckle.xyz/streams/7ce9010d71/commits/afda4ffdf8' + // Jonathon's lines + // 'https://speckle.xyz/streams/7ce9010d71/commits/8cd9e7e4fc' + // 'https://speckle.xyz/streams/7ce9010d71/objects/f46f95746975591c18b0b854dab5b570 ' + // 'https://speckle.xyz/streams/813b728084/commits/e2f5ac9775' + // Overlayhs + // 'https://latest.speckle.dev/streams/85b9f0b9f5/commits/cdfbf3e036?overlay=71f61af444,00fe449457,53a6692b79' + //'Rafinery' + // 'https://speckle.xyz/streams/b7cac6a6df/commits/2e42381302' + // 'https://speckle.xyz/streams/7ce9010d71/commits/b8bbfd0c05?c=%5B-4.50925,11.1348,5.38124,-0.23829,0.68512,-0.09006,0,1%5D' + ) +} -window.addEventListener('load', () => { - viewer.resize() -}) +const container0 = document.querySelector('#renderer') +if (!container0) { + throw new Error("Couldn't find app container!") +} -/** QUERY TEST */ -// container.addEventListener('mousemove', async (ev) => { -// const point = viewer.Utils.screenToNDC(ev.clientX, ev.clientY) -// const res: QueryResult = viewer.query({ -// id: 'test', -// point, -// operation: 'Pick' -// }) -// if (!res) return -// const hitPoint = { -// x: res.objects[0].point.x, -// y: res.objects[0].point.y, -// z: res.objects[0].point.z -// } -// await viewer.selectObjects([res.objects[0].object.id]) -// const resProj: QueryResult = viewer.query({ -// id: 'test', -// point: hitPoint, -// operation: 'Project' -// }) -// // console.log(viewer.Utils.NDCToScreen(res_p.x, res_p.y)) -// const resUnProj: QueryResult = viewer.query({ -// id: 'test', -// point: { x: resProj.x, y: resProj.y, z: resProj.z }, -// operation: 'Unproject' -// }) -// console.log( -// hitPoint.x - resUnProj.x, -// hitPoint.y - resUnProj.y, -// hitPoint.z - resUnProj.z -// ) -// }) - -viewer.on( - ViewerEvent.LoadProgress, - (a: { progress: number; id: string; url: string }) => { - if (a.progress >= 1) { - viewer.resize() - } - } -) - -// const updt = () => { -// const resOcc = viewer.query({ -// id: 'testX', -// point: { x: -2.2779617121296436, y: -1.9397099063369891, z: 7.411126386421243 }, -// tolerance: 0.001, -// operation: 'Occlusion' -// }) -// if (resOcc) console.log(resOcc.objects === null) -// requestAnimationFrame(updt) -// } - -// requestAnimationFrame(updt) - -viewer.on(ViewerEvent.LoadComplete, () => { - Object.assign(Sandbox.sceneParams.worldSize, Viewer.World.worldSize) - Object.assign(Sandbox.sceneParams.worldOrigin, Viewer.World.worldOrigin) - sandbox.refresh() -}) -viewer.on(ViewerEvent.UnloadComplete, () => { - Object.assign(Sandbox.sceneParams.worldSize, Viewer.World.worldSize) - Object.assign(Sandbox.sceneParams.worldOrigin, Viewer.World.worldOrigin) - sandbox.refresh() -}) -viewer.on(ViewerEvent.UnloadAllComplete, () => { - Object.assign(Sandbox.sceneParams.worldSize, Viewer.World.worldSize) - Object.assign(Sandbox.sceneParams.worldOrigin, Viewer.World.worldOrigin) - sandbox.refresh() -}) - -viewer.on(ViewerEvent.ObjectClicked, async (selectionInfo: SelectionEvent) => { - if (!selectionInfo) { - multiSelectList.length = 0 - await viewer.resetSelection() - viewer.setSectionBox() - return - } - if (!selectionInfo.multiple) multiSelectList.length = 0 - - const guids = multiSelectList.map((val) => val.hits[0].guid) - if ( - (selectionInfo.multiple && !guids.includes(selectionInfo.hits[0].guid)) || - multiSelectList.length === 0 - ) { - multiSelectList.push(selectionInfo) - } - - const ids = multiSelectList.map((val) => val.hits[0].object.id) - console.log(selectionInfo) - await viewer.selectObjects(ids as string[]) -}) - -viewer.on(ViewerEvent.ObjectDoubleClicked, async (selectionInfo: SelectionEvent) => { - if (!selectionInfo) { - viewer.zoom() - return - } - - viewer.zoom([selectionInfo.hits[0].object.id as string]) -}) - -sandbox.makeGenericUI() -sandbox.makeSceneUI() -sandbox.makeFilteringUI() -sandbox.makeBatchesUI() -// Load demo object -// setTimeout(async () => { -// const objUrl = ( -// await UrlHelper.getResourceUrls( -// 'https://speckle.xyz/streams/e6f9156405/commits/0694d53bb5' -// ) -// )[0] -// viewer.cancelLoad(objUrl) -// }, 1500) -await sandbox.loadUrl( - // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D' - // Revit sample house (good for bim-like stuff with many display meshes) - // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' - // 'Super' heavy revit shit - // 'https://speckle.xyz/streams/e6f9156405/commits/0694d53bb5' - // IFC building (good for a tree based structure) - // 'https://latest.speckle.dev/streams/92b620fb17/commits/2ebd336223' - // IFC story, a subtree of the above - // 'https://latest.speckle.dev/streams/92b620fb17/objects/8247bbc53865b0e0cb5ee4e252e66216' - // Small scale lines - // 'https://speckle.xyz/streams/638d3b1f83/commits/6025e2b546?c=%5B2.18058,-0.20814,9.67642,3.85491,5.05364,0,0,1%5D' - // 'https://latest.speckle.dev/streams/3ed8357f29/commits/d10f2af1ce' - // 'https://latest.speckle.dev/streams/444bfbd6e4/commits/e22f696b08' - // 'https://latest.speckle.dev/streams/92b620fb17/commits/af6098915b?c=%5B0.02144,-0.0377,0.05554,0.00566,0.00236,0,0,1%5D' - // AutoCAD - // 'https://latest.speckle.dev/streams/3ed8357f29/commits/d10f2af1ce' - //Blizzard world - // 'https://latest.speckle.dev/streams/0c6ad366c4/commits/aa1c393aec' - //Car - // 'https://latest.speckle.dev/streams/17d2e25a97/commits/6b6cf3d43e' - // Jonathon's - // 'https://latest.speckle.dev/streams/501258ee5f/commits/f885570011' - // Alex's cube - // 'https://latest.speckle.dev/streams/46e3e0e1ec/commits/a6392c19d6?c=%5B6.85874,2.9754,0.79022,0,0,0,0,1%5D' - // Groups of groups - // 'https://speckle.xyz/streams/1ce562e99a/commits/6fa28a5a0f' - // Arc flowers - // 'https://latest.speckle.dev/streams/9e6c4343ba/commits/037e382aa2' - // Car lines - // 'https://speckle.xyz/streams/638d3b1f83/commits/6025e2b546?c=%5B2.18058,-0.20814,9.67642,3.85491,5.05364,0,0,1%5D' - // Arc and lines - // ' https://speckle.xyz/streams/99abc74dd4/commits/b32fdcf171?c=%5B198440.6051,6522070.21462,19199.49584,176653.24219,6523663.5,0,0,1%5D' - // AUTOCAD test stream - // 'https://latest.speckle.dev/streams/3ed8357f29/commits/b49bfc73ea' - // REVIT test stream - // 'https://latest.speckle.dev/streams/c544db35f5/commits/7c29374369' - // Arcs - // 'https://latest.speckle.dev/streams/0c6ad366c4/commits/912d83412e' - // Freezers - // 'https://speckle.xyz/streams/f0532359ac/commits/98678e2a3d?c=%5B2455.15367,2689.87156,4366.68444,205.422,-149.41199,148.749,0,1%5D' - //Gergo's house - // 'https://latest.speckle.dev/streams/c1faab5c62/commits/78bdd8eb76' - // Point cloud - // 'https://latest.speckle.dev/streams/2d19273d31/commits/9ceb423feb' - // 'https://latest.speckle.dev/streams/7707df6cae/commits/02bdf09092' - // 'https://latest.speckle.dev/streams/ca0378725b/commits/fbae00db5a' - // Luis sphere - // 'https://speckle.xyz/streams/b85d53c3b4/commits/b47f21b707' - // Crankshaft - // 'https://speckle.xyz/streams/c239718aff/commits/b3a8cfb97d' - // Building AO params - // 'https://latest.speckle.dev/streams/0dd74866d0/commits/317e210afa' - // Murder Cube - // 'https://latest.speckle.dev/streams/c1faab5c62/commits/7f0c4d2fc1/' - // Classroom - // 'https://speckle.xyz/streams/0208ffb67b/commits/a980292728' - // 'https://latest.speckle.dev/streams/4658eb53b9/commits/328bd99997' - // 'https://latest.speckle.dev/streams/83e18d886f/commits/532bd6be3e' - // 'https://latest.speckle.dev/streams/1c2b3db9fb/commits/f12861736e' - // 'https://latest.speckle.dev/streams/1c2b3db9fb/commits/1015d417ea' - // Jedd's views - // 'https://latest.speckle.dev/streams/c1faab5c62/commits/e6632fe057' - // 'https://latest.speckle.dev/streams/7d051a6449/commits/7632757a33' - // 'https://latest.speckle.dev/streams/4658eb53b9/commits/d8ec9cccf7' - // MEPs (whatever they are) - // 'https://latest.speckle.dev/streams/85bc4f61c6/commits/8575fe2978' - // Alex cubes - // 'https://latest.speckle.dev/streams/4658eb53b9/commits/d8ec9cccf7' - // Tekla - // 'https://latest.speckle.dev/streams/caec6d6676/commits/588c731104' - // Purple market square - // 'https://latest.speckle.dev/streams/4ed51ed832/commits/5a313ac116' - // Sum building - // 'https://latest.speckle.dev/streams/92b620fb17/commits/4ea2759162' - // Boat - // 'https://latest.speckle.dev/streams/92b620fb17/commits/ba5df427db' - // 'https://latest.speckle.dev/streams/92b620fb17/commits/c9ebe49824' - // Dim's dome - // 'https://latest.speckle.dev/streams/92b620fb17/commits/158d4e8bec' - // Engines 'n Shit - // 'https://latest.speckle.dev/streams/92b620fb17/commits/80b25e6e6c' - // Dim's tower - // 'https://latest.speckle.dev/streams/92b620fb17/commits/7fd3ec04c0' - //COD - // 'https://latest.speckle.dev/streams/d3c83b47bf/commits/5f76b7ef3d?overlay=34577a1a92,571d460754,4c39b56c32,a62dd3a5da&c=%5B2046.38919,1074.97765,125.18054,2088.91862,1025.71927,94.66317,0,1%5D' - // 'https://latest.speckle.dev/streams/4658eb53b9/commits/0feb23d263' - // Jonathon's not loading - // 'https://speckle.xyz/streams/ca99defd4b/commits/589b265c99' - // Jonathon's 3070 - // 'https://speckle.xyz/streams/7ce9010d71/commits/d29e56fe75' - // Filter issue - // 'https://speckle.xyz/streams/f95d8deb90/commits/30f31becb6' - // Transparent - // 'https://latest.speckle.dev/streams/b5cc4e967c/objects/20343e0e8d469613a9d407499a6c38b1' - // dark - // 'https://latest.speckle.dev/streams/b5cc4e967c/commits/efdf3e2728?c=%5B-59.16128,-41.76491,-4.77376,-4.08052,-12.63558,-4.77376,0,1%5D' - // 'https://latest.speckle.dev/streams/92b620fb17/commits/b4366a7086?filter=%7B%7D&c=%5B-31.02357,37.60008,96.58899,11.01564,7.40652,66.0411,0,1%5D)' - // double - // 'https://latest.speckle.dev/streams/92b620fb17/commits/b4366a7086?overlay=c009dbe144&filter=%7B%7D&c=%5B-104.70053,-98.80617,67.44669,6.53096,1.8739,38.584,0,1%5D' - // 'https://latest.speckle.dev/streams/c43ac05d04/commits/ec724cfbeb', - // 'https://latest.speckle.dev/streams/efd2c6a31d/commits/4b495e1901' - // 'https://latest.speckle.dev/streams/efd2c6a31d/commits/4b495e1901' - // tekla 2 - // 'https://speckle.xyz/streams/be4813ccd2/commits/da85000921?c=%5B-1.12295,-2.60901,6.12402,4.77979,0.555,3.63346,0,1%5D' - // 'https://latest.speckle.dev/streams/85bc4f61c6/commits/bb7b718a1a' - - // large meshes - // 'https://speckle.xyz/streams/48e6e33aa6/commits/2cf892f1b0' - // large lines - // 'https://latest.speckle.dev/streams/444bfbd6e4/commits/8f297ad0cd' - // 'https://latest.speckle.dev/streams/c1faab5c62/commits/6b1b1195c4' - // 'https://latest.speckle.dev/streams/c1faab5c62/commits/cef1e7527b' - // large lines - // 'https://latest.speckle.dev/streams/c1faab5c62/commits/49dad07ae2' - // Instances Rhino - // 'https://latest.speckle.dev/streams/f92e060177/commits/1fff853107' - // Instances Revit - // 'https://latest.speckle.dev/streams/f92e060177/commits/92858681b7' - // 'https://latest.speckle.dev/streams/f92e060177/commits/655771674e' - // 'https://latest.speckle.dev/streams/f92e060177/commits/00dbbf4509' - // 'https://latest.speckle.dev/streams/f92e060177/commits/46fd255010' - // 'https://latest.speckle.dev/streams/f92e060177/commits/038a587267' - // 'https://latest.speckle.dev/streams/3f895e614f/commits/8a3e424997' - // Big curves - // 'https://latest.speckle.dev/streams/c1faab5c62/commits/49dad07ae2' - // 'https://speckle.xyz/streams/7ce9010d71/commits/afda4ffdf8' - // Jonathon's lines - // 'https://speckle.xyz/streams/7ce9010d71/commits/8cd9e7e4fc' - // 'https://speckle.xyz/streams/7ce9010d71/objects/f46f95746975591c18b0b854dab5b570 ' - // 'https://speckle.xyz/streams/813b728084/commits/e2f5ac9775' - // Overlayhs - // 'https://latest.speckle.dev/streams/85b9f0b9f5/commits/cdfbf3e036?overlay=71f61af444,00fe449457,53a6692b79' - //'Rafinery' - // 'https://speckle.xyz/streams/b7cac6a6df/commits/2e42381302' - // 'https://speckle.xyz/streams/7ce9010d71/commits/b8bbfd0c05?c=%5B-4.50925,11.1348,5.38124,-0.23829,0.68512,-0.09006,0,1%5D' - // 'https://latest.speckle.dev/streams/caec6d6676/commits/588c731104' - // 'https://latest.speckle.dev/streams/f92e060177/commits/1fff853107' - 'https://latest.speckle.dev/streams/c43ac05d04/commits/ec724cfbeb' - // 'https://latest.speckle.dev/streams/bbdf474833/commits/83d0eb7e06 ' -) +createViewer('#renderer', getStream()) diff --git a/packages/viewer-sandbox/src/style.css b/packages/viewer-sandbox/src/style.css index 5effb11b8..02762a158 100644 --- a/packages/viewer-sandbox/src/style.css +++ b/packages/viewer-sandbox/src/style.css @@ -23,6 +23,17 @@ width: 100%; } +#renderer0-controls:first-child { + pointer-events: auto; +} +#renderer1-controls:first-child { + pointer-events: auto; +} + +canvas { + position: absolute; +} + .button { border: 0; line-height: 1.5; diff --git a/packages/viewer/src/IViewer.ts b/packages/viewer/src/IViewer.ts index 7bf406a71..b1ab199a3 100644 --- a/packages/viewer/src/IViewer.ts +++ b/packages/viewer/src/IViewer.ts @@ -6,6 +6,8 @@ import { PropertyInfo } from './modules/filtering/PropertyManager' import { Query, QueryArgsResultMap, QueryResult } from './modules/queries/Query' import { DataTree } from './modules/tree/DataTree' import { Utils } from './modules/Utils' +import { WorldTree } from './modules/tree/WorldTree' +import { World } from './modules/World' export interface ViewerParams { showStats: boolean @@ -213,9 +215,11 @@ export interface IViewer { /** Data ops */ getDataTree(): DataTree + getWorldTree(): WorldTree query(query: T): QueryArgsResultMap[T['operation']] queryAsync(query: Query): Promise get Utils(): Utils + get World(): World getObjects(id: string): BatchObject[] explode(time: number, range: number) diff --git a/packages/viewer/src/modules/Assets.ts b/packages/viewer/src/modules/Assets.ts index 71d054320..0599b73c9 100644 --- a/packages/viewer/src/modules/Assets.ts +++ b/packages/viewer/src/modules/Assets.ts @@ -13,12 +13,6 @@ import Logger from 'js-logger' export class Assets { private static _cache: { [name: string]: Texture } = {} - private static pmremGenerator: PMREMGenerator - - public constructor(renderer: WebGLRenderer) { - Assets.pmremGenerator = new PMREMGenerator(renderer) - Assets.pmremGenerator.compileEquirectangularShader() - } private static getLoader(src: string, assetType: AssetType): TextureLoader { if (assetType === undefined) assetType = src.split('.').pop() as AssetType @@ -36,7 +30,10 @@ export class Assets { } } - public static getEnvironment(asset: Asset | string): Promise { + public static getEnvironment( + asset: Asset | string, + renderer: WebGLRenderer + ): Promise { let srcUrl: string = null let assetType: AssetType = undefined if ((asset).src) { @@ -55,9 +52,12 @@ export class Assets { loader.load( srcUrl, (texture) => { - const pmremRT = this.pmremGenerator.fromEquirectangular(texture) + const generator = new PMREMGenerator(renderer) + generator.compileEquirectangularShader() + const pmremRT = generator.fromEquirectangular(texture) this._cache[srcUrl] = pmremRT.texture texture.dispose() + generator.dispose() resolve(this._cache[srcUrl]) }, undefined, diff --git a/packages/viewer/src/modules/DebugViewer.ts b/packages/viewer/src/modules/DebugViewer.ts index e2c39324e..e3a66c231 100644 --- a/packages/viewer/src/modules/DebugViewer.ts +++ b/packages/viewer/src/modules/DebugViewer.ts @@ -1,4 +1,3 @@ -import { WorldTree } from './tree/WorldTree' import { Viewer } from './Viewer' export class DebugViewer extends Viewer { @@ -9,8 +8,4 @@ export class DebugViewer extends Viewer { requestRenderShadowmap() { this.getRenderer().updateDirectLights() } - - getWorldTree() { - return WorldTree.getInstance() - } } diff --git a/packages/viewer/src/modules/SectionBox.js b/packages/viewer/src/modules/SectionBox.js index 15133b242..76a485bf9 100644 --- a/packages/viewer/src/modules/SectionBox.js +++ b/packages/viewer/src/modules/SectionBox.js @@ -376,11 +376,6 @@ export default class SectionBox extends EventEmitter { this.controls.showZ = true } - // setBoxFromObjects(objectIds: string[], offset = 0.05) { - // WorldTree.getInstance().walk() => Solved - // this.setBox(...) - // } - setBox(targetBox, offset = 0.05) { let box diff --git a/packages/viewer/src/modules/SpeckleRenderer.ts b/packages/viewer/src/modules/SpeckleRenderer.ts index b80bc4920..146a92aea 100644 --- a/packages/viewer/src/modules/SpeckleRenderer.ts +++ b/packages/viewer/src/modules/SpeckleRenderer.ts @@ -33,7 +33,7 @@ import SpeckleDepthMaterial from './materials/SpeckleDepthMaterial' import SpeckleStandardMaterial from './materials/SpeckleStandardMaterial' import { NodeRenderView } from './tree/NodeRenderView' import { Viewer } from './Viewer' -import { TreeNode, WorldTree } from './tree/WorldTree' +import { TreeNode } from './tree/WorldTree' import { CanonicalView, DefaultLightConfiguration, @@ -482,7 +482,10 @@ export default class SpeckleRenderer { } public addRenderTree(subtreeId: string) { - this.batcher.makeBatches(subtreeId, SpeckleTypeAllRenderables) + this.batcher.makeBatches( + this.viewer.getWorldTree().getRenderTree(subtreeId), + SpeckleTypeAllRenderables + ) const subtreeGroup = new Group() subtreeGroup.name = subtreeId subtreeGroup.layers.set(ObjectLayers.STREAM_CONTENT) @@ -510,12 +513,14 @@ export default class SpeckleRenderer { this.rootGroup.add(subtreeGroup) const generator = this.batcher.makeBatchesAsync( - subtreeId, + this.viewer.getWorldTree().getRenderTree(subtreeId), SpeckleTypeAllRenderables, undefined, priority ) for await (const batch of generator) { + if (!batch) continue + this.addBatch(batch, subtreeGroup) this.zoom() if (batch.geometryType === GeometryType.MESH) { @@ -572,6 +577,7 @@ export default class SpeckleRenderer { parent.add(bvhHelper) } } + this.viewer.World.expandWorld(batch.bounds) } public removeRenderTree(subtreeId: string) { @@ -829,7 +835,7 @@ export default class SpeckleRenderer { const queryResult = [] for (let k = 0; k < rvs.length; k++) { const hitId = rvs[k].renderData.id - const hitNode = WorldTree.getInstance().findId(hitId) + const hitNode = this.viewer.getWorldTree().findId(hitId) let parentNode = hitNode while (!parentNode.model.atomic && parentNode.parent) { parentNode = parentNode.parent @@ -952,11 +958,16 @@ export default class SpeckleRenderer { let box = new Box3() const rvs: NodeRenderView[] = [] if (objectIds.length > 0) { - WorldTree.getInstance().walk((node: TreeNode) => { + this.viewer.getWorldTree().walk((node: TreeNode) => { if (!node.model.atomic) return true if (!node.model.raw) return true if (objectIds.indexOf(node.model.raw.id) !== -1) { - rvs.push(...WorldTree.getRenderTree().getRenderViewsForNode(node, node)) + rvs.push( + ...this.viewer + .getWorldTree() + .getRenderTree() + .getRenderViewsForNode(node, node) + ) } return true }) @@ -1241,41 +1252,41 @@ export default class SpeckleRenderer { } /** DEBUG */ - public onObjectClickDebug(e) { - const results: Array = this._intersections.intersect( - this._scene, - this.viewer.cameraHandler.activeCam.camera, - e, - true, - this.viewer.sectionBox.getCurrentBox() - ) - if (!results) { - this.batcher.resetBatchesDrawRanges() - return - } - const result = results[0] - // console.warn(result) - const rv = this.batcher.getRenderView( - result.object.uuid, - result.faceIndex !== undefined ? result.faceIndex : result.index - ) - const hitId = rv.renderData.id + // public onObjectClickDebug(e) { + // const results: Array = this._intersections.intersect( + // this._scene, + // this.viewer.cameraHandler.activeCam.camera, + // e, + // true, + // this.viewer.sectionBox.getCurrentBox() + // ) + // if (!results) { + // this.batcher.resetBatchesDrawRanges() + // return + // } + // const result = results[0] + // // console.warn(result) + // const rv = this.batcher.getRenderView( + // result.object.uuid, + // result.faceIndex !== undefined ? result.faceIndex : result.index + // ) + // const hitId = rv.renderData.id - // const hitNode = WorldTree.getInstance().findId(hitId) - // console.log(hitNode) + // // const hitNode = WorldTree.getInstance(this.viewer.viewerGuid).findId(hitId) + // // console.log(hitNode) - this.batcher.resetBatchesDrawRanges() + // this.batcher.resetBatchesDrawRanges() - this.batcher.isolateRenderViewBatch(hitId) - if (this.SHOW_BVH) { - this.allObjects.traverse((obj) => { - if (obj.name.includes('_bvh')) { - obj.visible = false - } - }) - this.scene.getObjectByName(result.object.id + '_bvh').visible = true - } - } + // this.batcher.isolateRenderViewBatch(hitId) + // if (this.SHOW_BVH) { + // this.allObjects.traverse((obj) => { + // if (obj.name.includes('_bvh')) { + // obj.visible = false + // } + // }) + // this.scene.getObjectByName(result.object.id + '_bvh').visible = true + // } + // } public debugShowBatches() { this.batcher.resetBatchesDrawRanges() @@ -1318,7 +1329,7 @@ export default class SpeckleRenderer { const objects = batches[k].mesh.batchObjects for (let i = 0; i < objects.length; i++) { const center = objects[i].renderView.aabb.getCenter(new Vector3()) - const dir = center.sub(Viewer.World.worldOrigin) + const dir = center.sub(this.viewer.World.worldOrigin) dir.normalize().multiplyScalar(time * range) objects[i].transformTRS(dir, undefined, undefined, undefined) } @@ -1330,8 +1341,11 @@ export default class SpeckleRenderer { } public getObjects(id: string): BatchObject[] { - const node = WorldTree.getInstance().findId(id) - const rvs = WorldTree.getRenderTree().getRenderViewsForNode(node, node) + const node = this.viewer.getWorldTree().findId(id) + const rvs = this.viewer + .getWorldTree() + .getRenderTree() + .getRenderViewsForNode(node, node) const batches = this.batcher.getBatches(undefined, GeometryType.MESH) as MeshBatch[] const meshes = batches.map((batch: MeshBatch) => batch.mesh) const objects = meshes.flatMap((mesh) => mesh.batchObjects) diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index 4c04f9171..4eb2a20b4 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -42,10 +42,12 @@ export class Viewer extends EventEmitter implements IViewer { private startupParams: ViewerParams /** Viewer components */ - private static world: World = new World() + private tree: WorldTree = new WorldTree() + private world: World = new World() public static Assets: Assets protected speckleRenderer: SpeckleRenderer private filteringManager: FilteringManager + private propertyManager: PropertyManager /** Legacy viewer components (will revisit soon) */ public sectionBox: SectionBox public cameraHandler: CameraHandler @@ -58,7 +60,7 @@ export class Viewer extends EventEmitter implements IViewer { /** various utils/helpers */ private utils: Utils /** Gets the World object. Currently it's used for info mostly */ - public static get World(): World { + public get World(): World { return this.world } @@ -84,7 +86,8 @@ export class Viewer extends EventEmitter implements IViewer { this.container = container || document.getElementById('renderer') if (params.showStats) { this.stats = Stats() - this.container.appendChild(this.stats.dom) + this.container.prepend(this.stats.dom) + this.stats.dom.style.position = 'relative' // Mad CSS skills } this.loaders = {} this.startupParams = params @@ -97,8 +100,9 @@ export class Viewer extends EventEmitter implements IViewer { this.speckleRenderer.create(this.container) window.addEventListener('resize', this.resize.bind(this), false) - new Assets(this.speckleRenderer.renderer) - this.filteringManager = new FilteringManager(this.speckleRenderer) + new Assets() + this.filteringManager = new FilteringManager(this.speckleRenderer, this.tree) + this.propertyManager = new PropertyManager() // eslint-disable-next-line @typescript-eslint/no-explicit-any // ;(window as any)._V = this // For debugging! ಠ_ಠ @@ -183,7 +187,10 @@ export class Viewer extends EventEmitter implements IViewer { public async init(): Promise { if (this.startupParams.environmentSrc) { - Assets.getEnvironment(this.startupParams.environmentSrc) + Assets.getEnvironment( + this.startupParams.environmentSrc, + this.speckleRenderer.renderer + ) .then((value: Texture) => { this.speckleRenderer.indirectIBL = value }) @@ -202,7 +209,7 @@ export class Viewer extends EventEmitter implements IViewer { resourceURL: string = null, bypassCache = true ): PropertyInfo[] { - return PropertyManager.getProperties(resourceURL, bypassCache) + return this.propertyManager.getProperties(this.tree, resourceURL, bypassCache) } public selectObjects(objectIds: string[]): Promise { @@ -325,7 +332,11 @@ export class Viewer extends EventEmitter implements IViewer { } public getDataTree(): DataTree { - return WorldTree.getDataTree() + return this.tree.getDataTree() + } + + public getWorldTree(): WorldTree { + return this.tree } public query(query: T): QueryArgsResultMap[T['operation']] { @@ -378,7 +389,7 @@ export class Viewer extends EventEmitter implements IViewer { } public getViews(): SpeckleView[] { - return WorldTree.getInstance() + return this.tree .findAll((node: TreeNode) => { return node.model.renderView?.speckleType === SpeckleType.View3D }) @@ -442,7 +453,7 @@ export class Viewer extends EventEmitter implements IViewer { await this.downloadObject(url, token, enableCaching) let t0 = performance.now() - WorldTree.getRenderTree(url).buildRenderTree() + this.tree.getRenderTree(url).buildRenderTree() Logger.log('SYNC Tree build time -> ', performance.now() - t0) t0 = performance.now() @@ -465,7 +476,7 @@ export class Viewer extends EventEmitter implements IViewer { await this.downloadObject(url, token, enableCaching) let t0 = performance.now() - const treeBuilt = await WorldTree.getRenderTree(url).buildRenderTreeAsync(priority) + const treeBuilt = await this.tree.getRenderTree(url).buildRenderTreeAsync(priority) Logger.log('ASYNC Tree build time -> ', performance.now() - t0) if (treeBuilt) { @@ -481,7 +492,7 @@ export class Viewer extends EventEmitter implements IViewer { public async cancelLoad(url: string, unload = false) { this.loaders[url].cancelLoad() - WorldTree.getRenderTree(url).cancelBuild(url) + this.tree.getRenderTree(url).cancelBuild(url) this.speckleRenderer.cancelRenderTree(url) if (unload) { await this.unloadObject(url) @@ -495,8 +506,8 @@ export class Viewer extends EventEmitter implements IViewer { (this as EventEmitter).emit(ViewerEvent.Busy, true) delete this.loaders[url] this.speckleRenderer.removeRenderTree(url) - WorldTree.getRenderTree(url).purge() - WorldTree.getInstance().purge(url) + this.tree.getRenderTree(url).purge() + this.tree.purge(url) } finally { if (--this.inProgressOperations === 0) { ;(this as EventEmitter).emit(ViewerEvent.Busy, false) @@ -514,12 +525,12 @@ export class Viewer extends EventEmitter implements IViewer { delete this.loaders[key] } this.filteringManager.reset() - WorldTree.getInstance().root.children.forEach((node) => { + this.tree.root.children.forEach((node) => { this.speckleRenderer.removeRenderTree(node.model.id) - WorldTree.getRenderTree().purge() + this.tree.getRenderTree().purge() }) - WorldTree.getInstance().purge() + this.tree.purge() } finally { if (--this.inProgressOperations === 0) { ;(this as EventEmitter).emit(ViewerEvent.Busy, false) diff --git a/packages/viewer/src/modules/ViewerObjectLoader.ts b/packages/viewer/src/modules/ViewerObjectLoader.ts index f94811584..07f8e1ffc 100644 --- a/packages/viewer/src/modules/ViewerObjectLoader.ts +++ b/packages/viewer/src/modules/ViewerObjectLoader.ts @@ -3,6 +3,7 @@ import { ViewerEvent } from '../IViewer' import Converter from './converter/Converter' import EventEmitter from './EventEmitter' import Logger from 'js-logger' +import { Viewer } from './Viewer' /** * Helper wrapper around the ObjectLoader class, with some built in assumptions. */ @@ -22,8 +23,8 @@ export default class ViewerObjectLoader { return this._objectUrl } - constructor(parentEmitter: EventEmitter, objectUrl, authToken, enableCaching) { - this.emiter = parentEmitter + constructor(parent: Viewer, objectUrl, authToken, enableCaching) { + this.emiter = parent this._objectUrl = objectUrl this.token = null try { @@ -64,7 +65,7 @@ export default class ViewerObjectLoader { options: { enableCaching, customLogger: (Logger as any).log } }) - this.converter = new Converter(this.loader) + this.converter = new Converter(this.loader, parent.getWorldTree()) this.cancel = false } diff --git a/packages/viewer/src/modules/batching/Batch.ts b/packages/viewer/src/modules/batching/Batch.ts index 8577be8a3..a3fbf4e51 100644 --- a/packages/viewer/src/modules/batching/Batch.ts +++ b/packages/viewer/src/modules/batching/Batch.ts @@ -1,4 +1,4 @@ -import { Material, Object3D, WebGLRenderer } from 'three' +import { Box3, Material, Object3D, WebGLRenderer } from 'three' import { MaterialOptions } from '../materials/Materials' import { NodeRenderView } from '../tree/NodeRenderView' @@ -17,6 +17,8 @@ export interface Batch { renderObject: Object3D geometryType: GeometryType + get bounds(): Box3 + getCount(): number setBatchMaterial(material: Material): void setVisibleRange(...range: BatchUpdateRange[]) diff --git a/packages/viewer/src/modules/batching/Batcher.ts b/packages/viewer/src/modules/batching/Batcher.ts index 57f6961e8..9d25a52ef 100644 --- a/packages/viewer/src/modules/batching/Batcher.ts +++ b/packages/viewer/src/modules/batching/Batcher.ts @@ -1,7 +1,6 @@ import { generateUUID } from 'three/src/math/MathUtils' import MeshBatch from './MeshBatch' import { SpeckleType } from '../converter/GeometryConverter' -import { WorldTree } from '../tree/WorldTree' import LineBatch from './LineBatch' import Materials from '../materials/Materials' import { NodeRenderView } from '../tree/NodeRenderView' @@ -18,6 +17,7 @@ import { Material, Mesh, WebGLRenderer } from 'three' import { FilterMaterial, FilterMaterialType } from '../filtering/FilteringManager' import Logger from 'js-logger' import { World } from '../World' +import { RenderTree } from '../tree/RenderTree' import SpeckleMesh from '../objects/SpeckleMesh' export enum TransformStorage { @@ -44,17 +44,15 @@ export default class Batcher { } public makeBatches( - subtreeId: string, + renderTree: RenderTree, speckleType: SpeckleType[], batchType?: GeometryType ) { - const renderViews = WorldTree.getRenderTree(subtreeId) - .getAtomicRenderViews(...speckleType) - .sort((a, b) => { - if (a.renderMaterialHash === 0) return -1 - if (b.renderMaterialHash === 0) return 1 - return a.renderMaterialHash - b.renderMaterialHash - }) + const renderViews = renderTree.getAtomicRenderViews(...speckleType).sort((a, b) => { + if (a.renderMaterialHash === 0) return -1 + if (b.renderMaterialHash === 0) return 1 + return a.renderMaterialHash - b.renderMaterialHash + }) const materialHashes = [ ...Array.from(new Set(renderViews.map((value) => value.renderMaterialHash))) ] @@ -78,7 +76,7 @@ export default class Batcher { for (let k = 0; k < batches.length; k++) { const restrictedRvs = batches[k] const batch = this.buildBatch( - subtreeId, + renderTree, restrictedRvs, materialHashes[i], batchType @@ -89,20 +87,18 @@ export default class Batcher { } public async *makeBatchesAsync( - subtreeId: string, + renderTree: RenderTree, speckleType: SpeckleType[], batchType?: GeometryType, priority?: number ) { const pause = World.getPause(priority) - const renderViews = WorldTree.getRenderTree(subtreeId) - .getAtomicRenderViews(...speckleType) - .sort((a, b) => { - if (a.renderMaterialHash === 0) return -1 - if (b.renderMaterialHash === 0) return 1 - return a.renderMaterialHash - b.renderMaterialHash - }) + const renderViews = renderTree.getAtomicRenderViews(...speckleType).sort((a, b) => { + if (a.renderMaterialHash === 0) return -1 + if (b.renderMaterialHash === 0) return 1 + return a.renderMaterialHash - b.renderMaterialHash + }) const materialHashes = [ ...Array.from(new Set(renderViews.map((value) => value.renderMaterialHash))) ] @@ -129,7 +125,7 @@ export default class Batcher { for (let k = 0; k < batches.length; k++) { const restrictedRvs = batches[k] const batch = this.buildBatch( - subtreeId, + renderTree, restrictedRvs, materialHashes[i], batchType @@ -203,13 +199,27 @@ export default class Batcher { } private buildBatch( - subtreeId: string, + renderTree: RenderTree, renderViews: NodeRenderView[], materialHash: number, batchType?: GeometryType ): Batch { - const geometryType = - batchType !== undefined ? batchType : renderViews[0].geometryType + let batch = renderViews.filter((value) => value.renderMaterialHash === materialHash) + /** Prune any meshes with no geometry data */ + batch = batch.filter((value) => value.validGeometry) + + if (!batch.length) { + /** This is for the case when all renderviews have invalid geometries, and it generally + * means there is something wrong with the stream + */ + Logger.warn( + 'All renderviews have invalid geometries. Skipping batch!', + renderViews + ) + return null + } + + const geometryType = batchType !== undefined ? batchType : batch[0].geometryType let matRef = null if (geometryType === GeometryType.MESH) { @@ -230,21 +240,21 @@ export default class Batcher { case GeometryType.MESH: geometryBatch = new MeshBatch( batchID, - subtreeId, - renderViews, + renderTree.id, + batch, this.floatTextures ? TransformStorage.VERTEX_TEXTURE : TransformStorage.UNIFORM_ARRAY ) break case GeometryType.LINE: - geometryBatch = new LineBatch(batchID, subtreeId, renderViews) + geometryBatch = new LineBatch(batchID, renderTree.id, batch) break case GeometryType.POINT: - geometryBatch = new PointBatch(batchID, subtreeId, renderViews) + geometryBatch = new PointBatch(batchID, renderTree.id, batch) break case GeometryType.POINT_CLOUD: - geometryBatch = new PointBatch(batchID, subtreeId, renderViews) + geometryBatch = new PointBatch(batchID, renderTree.id, batch) break } @@ -469,56 +479,9 @@ export default class Batcher { /** * Used for debuggin only */ - public isolateRenderView(id: string) { - const rvs = WorldTree.getRenderTree().getRenderViewsForNodeId(id) - const batchIds = [...Array.from(new Set(rvs.map((value) => value.batchId)))] - for (const k in this.batches) { - if (!batchIds.includes(k)) { - this.batches[k].setDrawRanges({ - offset: 0, - count: Infinity, - material: this.materials.getFilterMaterial( - this.batches[k].renderViews[0], - FilterMaterialType.GHOST - ) - }) - this.batches[k].setVisibleRange(HideAllBatchUpdateRange) - } else { - const drawRanges = [] - for (let i = 0; i < this.batches[k].renderViews.length; i++) { - if (!rvs.includes(this.batches[k].renderViews[i])) { - drawRanges.push({ - offset: this.batches[k].renderViews[i].batchStart, - count: this.batches[k].renderViews[i].batchCount, - material: this.materials.getFilterMaterial( - this.batches[k].renderViews[i], - FilterMaterialType.GHOST - ) - }) - } else { - drawRanges.push({ - offset: this.batches[k].renderViews[i].batchStart, - count: this.batches[k].renderViews[i].batchCount, - material: this.materials.getFilterMaterial( - this.batches[k].renderViews[i], - FilterMaterialType.SELECT - ) - }) - } - } - if (drawRanges.length > 0) { - this.batches[k].setDrawRanges(...drawRanges) - this.batches[k].autoFillDrawRanges() - } - } - } - } - /** - * Used for debuggin only - */ - public async isolateRenderViewBatch(id: string) { - const rv = WorldTree.getRenderTree().getRenderViewForNodeId(id) + public async isolateRenderViewBatch(id: string, renderTree: RenderTree) { + const rv = renderTree.getRenderViewForNodeId(id) for (const k in this.batches) { if (k !== rv.batchId) { this.batches[k].setDrawRanges({ diff --git a/packages/viewer/src/modules/batching/LineBatch.ts b/packages/viewer/src/modules/batching/LineBatch.ts index 809a3b63a..68dc7fd35 100644 --- a/packages/viewer/src/modules/batching/LineBatch.ts +++ b/packages/viewer/src/modules/batching/LineBatch.ts @@ -15,7 +15,6 @@ import { Geometry } from '../converter/Geometry' import SpeckleLineMaterial from '../materials/SpeckleLineMaterial' import { ObjectLayers } from '../SpeckleRenderer' import { NodeRenderView } from '../tree/NodeRenderView' -import { Viewer } from '../Viewer' import { AllBatchUpdateRange, Batch, @@ -34,6 +33,11 @@ export default class LineBatch implements Batch { public colorBuffer: InstancedInterleavedBuffer private static readonly vector4Buffer: Vector4 = new Vector4() + public get bounds() { + if (!this.geometry.boundingBox) this.geometry.computeBoundingBox() + return this.geometry.boundingBox + } + public constructor(id: string, subtreeId: string, renderViews: NodeRenderView[]) { this.id = id this.subtreeId = subtreeId @@ -221,7 +225,6 @@ export default class LineBatch implements Batch { private makeLineGeometry(position: Float64Array) { this.geometry = this.makeLineGeometryTriangle(new Float32Array(position)) Geometry.updateRTEGeometry(this.geometry, position) - Viewer.World.expandWorld(this.geometry.boundingBox) } private makeLineGeometryTriangle(position: Float32Array): LineSegmentsGeometry { diff --git a/packages/viewer/src/modules/batching/MeshBatch.ts b/packages/viewer/src/modules/batching/MeshBatch.ts index bf579b1e2..3ad1049b5 100644 --- a/packages/viewer/src/modules/batching/MeshBatch.ts +++ b/packages/viewer/src/modules/batching/MeshBatch.ts @@ -1,4 +1,5 @@ import { + Box3, BufferAttribute, BufferGeometry, DynamicDrawUsage, @@ -13,7 +14,6 @@ import { Geometry } from '../converter/Geometry' import SpeckleStandardColoredMaterial from '../materials/SpeckleStandardColoredMaterial' import SpeckleMesh from '../objects/SpeckleMesh' import { NodeRenderView } from '../tree/NodeRenderView' -import { Viewer } from '../Viewer' import { AllBatchUpdateRange, Batch, @@ -41,6 +41,10 @@ export default class MeshBatch implements Batch { private indexBuffer1: BufferAttribute private indexBufferIndex = 0 + public get bounds(): Box3 { + return this.mesh.BVH.getBoundingBox(new Box3()) + } + public constructor( id: string, subtreeId: string, @@ -548,8 +552,6 @@ export default class MeshBatch implements Batch { this.geometry.computeBoundingSphere() this.geometry.computeBoundingBox() - Viewer.World.expandWorld(this.geometry.boundingBox) - Geometry.updateRTEGeometry(this.geometry, position) return this.geometry diff --git a/packages/viewer/src/modules/batching/PointBatch.ts b/packages/viewer/src/modules/batching/PointBatch.ts index 6c4e5ceb1..cb5bc9d28 100644 --- a/packages/viewer/src/modules/batching/PointBatch.ts +++ b/packages/viewer/src/modules/batching/PointBatch.ts @@ -8,7 +8,6 @@ import { } from 'three' import { Geometry } from '../converter/Geometry' import { NodeRenderView } from '../tree/NodeRenderView' -import { Viewer } from '../Viewer' import { AllBatchUpdateRange, Batch, @@ -28,6 +27,11 @@ export default class PointBatch implements Batch { public batchMaterial: Material public mesh: Points + public get bounds() { + if (!this.geometry.boundingBox) this.geometry.computeBoundingBox() + return this.geometry.boundingBox + } + public constructor(id: string, subtreeId: string, renderViews: NodeRenderView[]) { this.id = id this.subtreeId = subtreeId @@ -321,7 +325,6 @@ export default class PointBatch implements Batch { this.geometry.computeBoundingSphere() this.geometry.computeBoundingBox() - Viewer.World.expandWorld(this.geometry.boundingBox) Geometry.updateRTEGeometry(this.geometry, position) return this.geometry diff --git a/packages/viewer/src/modules/converter/Converter.ts b/packages/viewer/src/modules/converter/Converter.ts index 9d24d2b5f..de971a403 100644 --- a/packages/viewer/src/modules/converter/Converter.ts +++ b/packages/viewer/src/modules/converter/Converter.ts @@ -16,7 +16,7 @@ export default class Coverter { private activePromises: number private maxChildrenPromises: number private spoofIDs = true - private isRoot = true + private tree: WorldTree private readonly NodeConverterMapping: { [name: string]: ConverterNodeDelegate @@ -38,7 +38,7 @@ export default class Coverter { RevitInstance: this.RevitInstanceToNode.bind(this) } - constructor(objectLoader: unknown) { + constructor(objectLoader: unknown, tree: WorldTree) { if (!objectLoader) { Logger.warn( 'Converter initialized without a corresponding object loader. Any objects that include references will throw errors.' @@ -46,11 +46,10 @@ export default class Coverter { } this.objectLoader = objectLoader - this.lastAsyncPause = Date.now() this.activePromises = 0 this.maxChildrenPromises = 200 - WorldTree.getInstance() + this.tree = tree } public async asyncPause() { @@ -99,20 +98,18 @@ export default class Coverter { return } - const childNode: TreeNode = WorldTree.getInstance().parse({ + const childNode: TreeNode = this.tree.parse({ id: !node ? objectURL : this.getNodeId(obj), raw: Object.assign({}, obj), atomic: true, - root: this.isRoot, children: [] }) - this.isRoot = false if (node === null) { - WorldTree.getInstance().addSubtree(childNode) + this.tree.addSubtree(childNode) // console.warn(`Added root node with id ${obj.id}`) } else { - WorldTree.getInstance().addNode(childNode, node) + this.tree.addNode(childNode, node) // console.warn(`Added child node with id ${obj.id} to parent node ${node.model.id}`) } @@ -148,14 +145,14 @@ export default class Coverter { displayValue = await this.resolveReference(displayValue) if (!displayValue.units) displayValue.units = obj.units try { - const nestedNode: TreeNode = WorldTree.getInstance().parse({ + const nestedNode: TreeNode = this.tree.parse({ id: this.getNodeId(displayValue), raw: Object.assign({}, displayValue), atomic: false, children: [] }) await this.convertToNode(displayValue, nestedNode) - WorldTree.getInstance().addNode(nestedNode, childNode) + this.tree.addNode(nestedNode, childNode) await callback({}) // use the parent's metadata! } catch (e) { Logger.warn( @@ -166,14 +163,14 @@ export default class Coverter { for (const element of displayValue) { const val = await this.resolveReference(element) if (!val.units) val.units = obj.units - const nestedNode: TreeNode = WorldTree.getInstance().parse({ + const nestedNode: TreeNode = this.tree.parse({ id: this.getNodeId(val), raw: Object.assign({}, val), atomic: false, children: [] }) await this.convertToNode(val, nestedNode) - WorldTree.getInstance().addNode(nestedNode, childNode) + this.tree.addNode(nestedNode, childNode) await callback({}) } } @@ -309,13 +306,13 @@ export default class Coverter { node.model.raw.definition = definition for (const def of definition.geometry) { const ref = await this.resolveReference(def) - const childNode: TreeNode = WorldTree.getInstance().parse({ + const childNode: TreeNode = this.tree.parse({ id: this.getNodeId(ref), raw: Object.assign({}, ref), atomic: true, children: [] }) - WorldTree.getInstance().addNode(childNode, node) + this.tree.addNode(childNode, node) // console.warn( // `Added child node with id ${childNode.model.id} to parent node ${node.model.id}` // ) @@ -329,7 +326,7 @@ export default class Coverter { if (!list) return for (const def of list) { const ref = await this.resolveReference(def) - const childNode: TreeNode = WorldTree.getInstance().parse({ + const childNode: TreeNode = this.tree.parse({ id: this.getNodeId(ref), raw: Object.assign({}, ref), atomic: true, @@ -338,7 +335,7 @@ export default class Coverter { if (hostId) { childNode.model.raw.host = hostId } - WorldTree.getInstance().addNode(childNode, node) + this.tree.addNode(childNode, node) await this.convertToNode(ref, childNode) } } @@ -362,14 +359,14 @@ export default class Coverter { let displayValue = obj.displayValue || obj.displayMesh if (Array.isArray(displayValue)) displayValue = displayValue[0] //Just take the first display value for now (not ideal) const ref = await this.resolveReference(displayValue) - const nestedNode: TreeNode = WorldTree.getInstance().parse({ + const nestedNode: TreeNode = this.tree.parse({ id: this.getNodeId(ref), raw: Object.assign({}, ref), atomic: false, children: [] }) await this.convertToNode(ref, nestedNode) - WorldTree.getInstance().addNode(nestedNode, node) + this.tree.addNode(nestedNode, node) // deletes known unneeded fields delete obj.Edges @@ -433,7 +430,7 @@ export default class Coverter { element = await this.resolveReference(element) } } - const nestedNode: TreeNode = WorldTree.getInstance().parse({ + const nestedNode: TreeNode = this.tree.parse({ id: this.getNodeId(element), raw: Object.assign({}, element), atomic: false, @@ -442,7 +439,7 @@ export default class Coverter { await this.convertToNode(element, nestedNode) /** We're not adding the segments as children since they shouldn't exist as individual line elements */ node.model.nestedNodes.push(nestedNode) - // WorldTree.getInstance().addNode(nestedNode, node) + // WorldTree.getInstance(this.treeInstaceId).addNode(nestedNode, node) } } @@ -455,14 +452,14 @@ export default class Coverter { } const displayValue = await this.resolveReference(obj.displayValue) displayValue.units = displayValue.units || obj.units - const nestedNode: TreeNode = WorldTree.getInstance().parse({ + const nestedNode: TreeNode = this.tree.parse({ id: this.getNodeId(displayValue), raw: Object.assign({}, displayValue), atomic: false, children: [] }) await this.convertToNode(displayValue, nestedNode) - WorldTree.getInstance().addNode(nestedNode, node) + this.tree.addNode(nestedNode, node) } private async CircleToNode(obj, node) { diff --git a/packages/viewer/src/modules/filtering/FilteringManager.ts b/packages/viewer/src/modules/filtering/FilteringManager.ts index c0d310172..cf15fc815 100644 --- a/packages/viewer/src/modules/filtering/FilteringManager.ts +++ b/packages/viewer/src/modules/filtering/FilteringManager.ts @@ -9,6 +9,7 @@ import { NumericPropertyInfo } from './PropertyManager' import SpeckleRenderer from '../SpeckleRenderer' +import { RenderTree } from '../tree/RenderTree' export type FilteringState = { selectedObjects?: string[] @@ -39,6 +40,7 @@ export interface FilterMaterial { export class FilteringManager { public WTI: WorldTree + public RTI: RenderTree private Renderer: SpeckleRenderer private StateKey: string = null @@ -50,8 +52,9 @@ export class FilteringManager { private UserspaceColorState = new UserspaceColorState() private ColorStringFilterState2: ColorStringFilterState = null - public constructor(renderer: SpeckleRenderer) { - this.WTI = WorldTree.getInstance() + public constructor(renderer: SpeckleRenderer, tree: WorldTree) { + this.WTI = tree + this.RTI = tree.getRenderTree() this.Renderer = renderer } @@ -173,16 +176,14 @@ export class FilteringManager { private visibilityWalk(node: TreeNode): boolean { if (!node.model.atomic) return true if (this.VisibilityState.ids.indexOf(node.model.raw.id) !== -1) { - this.VisibilityState.rvs.push( - ...WorldTree.getRenderTree().getRenderViewsForNode(node, node) - ) + this.VisibilityState.rvs.push(...this.RTI.getRenderViewsForNode(node, node)) } return true } private isolationWalk(node: TreeNode): boolean { - if (!node.model.atomic || node.model.id === 'MOTHERSHIP') return true - const rvs = WorldTree.getRenderTree().getRenderViewsForNode(node, node) + if (!node.model.atomic || this.WTI.isRoot(node)) return true + const rvs = this.RTI.getRenderViewsForNode(node, node) if (this.VisibilityState.ids.indexOf(node.model.raw.id) === -1) { this.VisibilityState.rvs.push(...rvs) } else { @@ -223,10 +224,9 @@ export class FilteringManager { const nonMatchingRvs: NodeRenderView[] = [] const colorGroups: ValueGroupColorItemNumericProps[] = [] - WorldTree.getInstance().walk((node: TreeNode) => { - if (!node.model.atomic || node.model.id === 'MOTHERSHIP' || node.model.root) - return true - const rvs = WorldTree.getRenderTree().getRenderViewsForNode(node, node) + this.WTI.walk((node: TreeNode) => { + if (!node.model.atomic || this.WTI.isRoot(node)) return true + const rvs = this.RTI.getRenderViewsForNode(node, node) const idx = matchingIds.indexOf(node.model.raw.id) if (idx === -1) { nonMatchingRvs.push(...rvs) @@ -267,11 +267,11 @@ export class FilteringManager { // windows (family instances) inside walls get the same color as the walls, even though // they are identified as a different category. this.WTI.walk((node: TreeNode) => { - if (!node.model.atomic || node.model.id === 'MOTHERSHIP') { + if (!node.model.atomic || this.WTI.isRoot(node)) { return true } const vg = valueGroupColors.find((v) => v.ids.indexOf(node.model.raw.id) !== -1) - const rvs = WorldTree.getRenderTree().getRenderViewsForNode(node, node) + const rvs = this.RTI.getRenderViewsForNode(node, node) if (!vg) { nonMatchingRvs.push(...rvs) return true @@ -316,14 +316,14 @@ export class FilteringManager { return { ...g, nodes: [], rvs: [] } }) - WorldTree.getInstance().walk((node: TreeNode) => { + this.WTI.walk((node: TreeNode) => { if (!node.model?.raw?.id) return true for (const group of localGroups) { if (group.objectIds.includes(node.model.raw.id)) { group.nodes.push(node) - const rvsNodes = WorldTree.getRenderTree() - .getRenderViewNodesForNode(node, node) - .map((rvNode) => rvNode.model.renderView) + const rvsNodes = this.RTI.getRenderViewNodesForNode(node, node).map( + (rvNode) => rvNode.model.renderView + ) if (rvsNodes) group.rvs.push(...rvsNodes) } } @@ -354,7 +354,7 @@ export class FilteringManager { const nodes = [] if (ids.length !== 0) { /** This walk still takes longer than we'd like */ - WorldTree.getInstance().walk((node: TreeNode) => { + this.WTI.walk((node: TreeNode) => { if (ids.indexOf(node.model.raw.id) !== -1) { nodes.push(node) } @@ -364,10 +364,7 @@ export class FilteringManager { /** There's also quite a lot of redundancy here as well. The nodes coming are * hierarchical and we end up getting the same render views more than once. */ - const rvs = WorldTree.getRenderTree().getRenderViewNodesForNode( - nodes[k], - nodes[k] - ) + const rvs = this.RTI.getRenderViewNodesForNode(nodes[k], nodes[k]) if (rvs) { state.rvs.push(...rvs.map((e) => e.model.renderView)) state.ids.push(...rvs.map((e) => e.model.raw.id)) diff --git a/packages/viewer/src/modules/filtering/PropertyManager.ts b/packages/viewer/src/modules/filtering/PropertyManager.ts index f1b70b4e8..d4937ebb0 100644 --- a/packages/viewer/src/modules/filtering/PropertyManager.ts +++ b/packages/viewer/src/modules/filtering/PropertyManager.ts @@ -2,9 +2,7 @@ import flatten from '../../helpers/flatten' import { TreeNode, WorldTree } from '../tree/WorldTree' export class PropertyManager { - private static WT: WorldTree = WorldTree.getInstance() - - private static propCache = {} as Record + private propCache = {} as Record /** * @@ -12,11 +10,12 @@ export class PropertyManager { * @param bypassCache Forces a full rescan if set to true. * @returns a list of property infos containing basic information for filtering purposes. */ - public static getProperties( + public getProperties( + tree: WorldTree, resourceUrl: string = null, bypassCache = false ): PropertyInfo[] { - let rootNode: TreeNode = PropertyManager.WT.root + let rootNode: TreeNode = tree.root if (!bypassCache && this.propCache[resourceUrl ? resourceUrl : rootNode.model.id]) return this.propCache[resourceUrl ? resourceUrl : rootNode.model.id] @@ -31,7 +30,7 @@ export class PropertyManager { const propValues = {} - PropertyManager.WT.walk((node: TreeNode) => { + tree.walk((node: TreeNode) => { if (!node.model.atomic) return true const obj = flatten(node.model.raw) for (const key in obj) { diff --git a/packages/viewer/src/modules/tree/DataTree.ts b/packages/viewer/src/modules/tree/DataTree.ts index 7b53ad098..e8d1c114e 100644 --- a/packages/viewer/src/modules/tree/DataTree.ts +++ b/packages/viewer/src/modules/tree/DataTree.ts @@ -16,7 +16,7 @@ class DataTreeInternal implements DataTree { public constructor() { this.tree = new TreeModel() - this.root = this.tree.parse({ guid: 'MOTHERSHIP' }) + this.root = this.tree.parse({ guid: WorldTree.ROOT_ID }) } public findAll(predicate: ObjectPredicate): SpeckleObject[] { return this.root @@ -43,10 +43,10 @@ class DataTreeInternal implements DataTree { } export class DataTreeBuilder { - public static build(root: TreeNode): DataTree { + public static build(tree: WorldTree): DataTree { const dataTree = new DataTreeInternal() let parent = null - WorldTree.getInstance().walk((node: TreeNode) => { + tree.root.walk((node: TreeNode) => { if (!node.parent) { parent = dataTree.root return true @@ -56,7 +56,7 @@ export class DataTreeBuilder { return localNode.model.guid === node.parent.model.id }) - const _node: TreeNode = WorldTree.getInstance().parse({ + const _node: TreeNode = tree.parse({ guid: node.model.id, data: node.model.raw, atomic: node.model.atomic, @@ -65,7 +65,7 @@ export class DataTreeBuilder { parent.addChild(_node) return true - }, root) + }, tree.root) return dataTree as DataTree } } diff --git a/packages/viewer/src/modules/tree/RenderTree.ts b/packages/viewer/src/modules/tree/RenderTree.ts index 2c71affd1..de8689066 100644 --- a/packages/viewer/src/modules/tree/RenderTree.ts +++ b/packages/viewer/src/modules/tree/RenderTree.ts @@ -7,6 +7,7 @@ import { Geometry } from '../converter/Geometry' import Logger from 'js-logger' export class RenderTree { + private tree: WorldTree private root: TreeNode private _treeBounds: Box3 = new Box3() private cancel = false @@ -15,12 +16,17 @@ export class RenderTree { return this._treeBounds } - public constructor(root: TreeNode) { - this.root = root + public get id(): string { + return this.root.model.id + } + + public constructor(tree: WorldTree, subtreeRoot: TreeNode) { + this.tree = tree + this.root = subtreeRoot } public buildRenderTree() { - this.root.walk((node: TreeNode): boolean => { + this.tree.walk((node: TreeNode): boolean => { const rendeNode = this.buildRenderNode(node) node.model.renderView = rendeNode ? new NodeRenderView(rendeNode) : null if (node.model.renderView && node.model.renderView.hasGeometry) { @@ -42,7 +48,7 @@ export class RenderTree { } public buildRenderTreeAsync(priority: number): Promise { - const p = WorldTree.getInstance().walkAsync( + const p = this.tree.walkAsync( (node: TreeNode): boolean => { const rendeNode = this.buildRenderNode(node) node.model.renderView = rendeNode ? new NodeRenderView(rendeNode) : null @@ -93,7 +99,7 @@ export class RenderTree { if (node.model.raw.renderMaterial) { return node } - const ancestors = WorldTree.getInstance().getAncestors(node) + const ancestors = this.tree.getAncestors(node) for (let k = 0; k < ancestors.length; k++) { if (ancestors[k].model.raw.renderMaterial) { return ancestors[k] @@ -105,7 +111,7 @@ export class RenderTree { if (node.model.raw.displayStyle) { return node } - const ancestors = WorldTree.getInstance().getAncestors(node) + const ancestors = this.tree.getAncestors(node) for (let k = 0; k < ancestors.length; k++) { if (ancestors[k].model.raw.displayStyle) { return ancestors[k] @@ -115,7 +121,7 @@ export class RenderTree { public computeTransform(node: TreeNode): Matrix4 { const transform = new Matrix4() - const ancestors = WorldTree.getInstance().getAncestors(node) + const ancestors = this.tree.getAncestors(node) for (let k = 0; k < ancestors.length; k++) { if (ancestors[k].model.renderView) { const renderNode: NodeRenderData = ancestors[k].model.renderView.renderData @@ -177,13 +183,11 @@ export class RenderTree { if (node.model.atomic) { return node.model.renderView } - return WorldTree.getInstance() - .getAncestors(node) - .find((node) => node.model.atomic) + return this.tree.getAncestors(node).find((node) => node.model.atomic) } public getRenderViewsForNodeId(id: string): NodeRenderView[] { - const node = WorldTree.getInstance().findId(id) + const node = this.tree.findId(id) if (!node) { Logger.warn(`Id ${id} does not exist`) return null @@ -192,7 +196,7 @@ export class RenderTree { } public getRenderViewForNodeId(id: string): NodeRenderView { - const node = WorldTree.getInstance().findId(id) + const node = this.tree.findId(id) if (!node) { Logger.warn(`Id ${id} does not exist`) return null @@ -201,12 +205,12 @@ export class RenderTree { } public purge() { - this.root = null + this.tree = null } - public cancelBuild(id: string) { + public cancelBuild(subtreeId: string) { this.cancel = true - WorldTree.getInstance().purge(id) + this.tree.purge(subtreeId) this.purge() } } diff --git a/packages/viewer/src/modules/tree/WorldTree.ts b/packages/viewer/src/modules/tree/WorldTree.ts index bc1cb84df..149b3d408 100644 --- a/packages/viewer/src/modules/tree/WorldTree.ts +++ b/packages/viewer/src/modules/tree/WorldTree.ts @@ -15,56 +15,42 @@ export interface NodeData { children: TreeNode[] nestedNodes: TreeNode[] atomic: boolean - /** - * Keeps track wether this the root commit object or not. - * TODO: Ask Alex wether this is somehow avoidable. - */ - root: boolean renderView?: NodeRenderView } export class WorldTree { - private static instance: WorldTree - private static renderTreeInstances: { [id: string]: RenderTree } = {} + private renderTreeInstances: { [id: string]: RenderTree } = {} private readonly supressWarnings = true + public static readonly ROOT_ID = 'ROOT' - private constructor() { + public constructor() { this.tree = new TreeModel() + this._root = this.parse({ + id: WorldTree.ROOT_ID, + raw: {}, + atomic: true, + children: [], + renderView: null + }) } - public static getInstance(): WorldTree { - if (!WorldTree.instance) { - WorldTree.instance = new WorldTree() - WorldTree.instance._root = WorldTree.getInstance().parse({ - id: 'MOTHERSHIP', - raw: {}, - atomic: true, - children: [], - renderView: null - }) - } - - return WorldTree.instance - } - - public static getRenderTree(subtreeId?: string): RenderTree { - if (!WorldTree.getInstance()._root) { + public getRenderTree(subtreeId?: string): RenderTree { + if (!this._root) { console.error(`WorldTree not initialised`) return null } - const id = subtreeId ? subtreeId : WorldTree.getInstance().root.model.id - if (!WorldTree.renderTreeInstances[id]) { - WorldTree.renderTreeInstances[id] = new RenderTree( - WorldTree.getInstance().findId(id) - ) + const id = subtreeId ? subtreeId : this.root.model.id + + if (!this.renderTreeInstances[id]) { + this.renderTreeInstances[id] = new RenderTree(this, this.findId(id)) } - return WorldTree.renderTreeInstances[id] + return this.renderTreeInstances[id] } - public static getDataTree(): DataTree { - return DataTreeBuilder.build(WorldTree.instance._root) + public getDataTree(): DataTree { + return DataTreeBuilder.build(this) } private tree: TreeModel @@ -74,6 +60,10 @@ export class WorldTree { return this._root } + public isRoot(node: TreeNode) { + return node === this._root + } + public parse(model) { return this.tree.parse(model) } @@ -155,19 +145,19 @@ export class WorldTree { public purge(subtreeId?: string) { if (subtreeId) { - delete WorldTree.renderTreeInstances[subtreeId] + delete this.renderTreeInstances[subtreeId] this.removeNode(this.findId(subtreeId)) return } - Object.keys(WorldTree.renderTreeInstances).forEach( - (key) => delete WorldTree.renderTreeInstances[key] + Object.keys(this.renderTreeInstances).forEach( + (key) => delete this.renderTreeInstances[key] ) this._root.drop() this._root.children.length = 0 this.tree = new TreeModel() - this._root = WorldTree.getInstance().parse({ - id: 'MOTHERSHIP', + this._root = this.tree.parse({ + id: WorldTree.ROOT_ID, raw: {}, atomic: true, children: []