From d7f03251923fa6abc7efefaf2ef6a2b9088c3937 Mon Sep 17 00:00:00 2001 From: Alexandru Popovici Date: Fri, 5 Apr 2024 18:07:02 +0300 Subject: [PATCH] Extension Access Changes (#2193) * Removed the concept of Providers entirely. Now extentions specify explicity extention types or archypes in their inject lists. Removed the concept of core-extensions entirely. All extensions are now equal. The concept of CameraProvider was replaced by SpeckleCamera which the SpeckleRenderer now uses and relie on the default camera controller extension to seed it. * Fixed some compile errors * Fixed compile errors. Had to make Extension concrete, but meh, it's fine I guess * fix viewer types * Removed generic arguments since they're no longer needed --------- Co-authored-by: Kristaps Fabians Geikins --- .../src/Extensions/BoxSelection.ts | 15 ++- .../src/Extensions/CameraPlanes.ts | 7 ++ .../src/Extensions/RotateCamera.ts | 13 ++- packages/viewer-sandbox/src/Sandbox.ts | 8 +- packages/viewer-sandbox/src/main.ts | 103 +----------------- packages/viewer/package.json | 1 + packages/viewer/src/IViewer.ts | 7 +- packages/viewer/src/index.ts | 16 +-- packages/viewer/src/modules/LegacyViewer.ts | 13 +-- .../viewer/src/modules/SpeckleRenderer.ts | 37 +++---- packages/viewer/src/modules/Viewer.ts | 69 ++++++------ .../{core-extensions => }/CameraController.ts | 59 ++++++---- .../src/modules/extensions/DiffExtension.ts | 10 +- .../modules/extensions/ExplodeExtension.ts | 13 ++- .../src/modules/extensions/Extension.ts | 40 +++++++ .../modules/extensions/FilteringExtension.ts | 9 +- .../src/modules/extensions/SectionOutlines.ts | 22 ++-- .../src/modules/extensions/SectionTool.ts | 21 ++-- .../modules/extensions/SelectionExtension.ts | 8 +- .../extensions/core-extensions/Extension.ts | 30 ----- .../extensions/core-extensions/Providers.ts | 78 ------------- .../measurements/MeasurementsExtension.ts | 9 +- .../src/modules/objects/SpeckleCamera.ts | 21 ++++ yarn.lock | 8 ++ 24 files changed, 266 insertions(+), 351 deletions(-) rename packages/viewer/src/modules/extensions/{core-extensions => }/CameraController.ts (93%) create mode 100644 packages/viewer/src/modules/extensions/Extension.ts delete mode 100644 packages/viewer/src/modules/extensions/core-extensions/Extension.ts delete mode 100644 packages/viewer/src/modules/extensions/core-extensions/Providers.ts create mode 100644 packages/viewer/src/modules/objects/SpeckleCamera.ts diff --git a/packages/viewer-sandbox/src/Extensions/BoxSelection.ts b/packages/viewer-sandbox/src/Extensions/BoxSelection.ts index 04ac3ca57..d39011524 100644 --- a/packages/viewer-sandbox/src/Extensions/BoxSelection.ts +++ b/packages/viewer-sandbox/src/Extensions/BoxSelection.ts @@ -6,10 +6,10 @@ import { SelectionExtension } from '@speckle/viewer' import { BatchObject } from '@speckle/viewer' import { Extension, - ICameraProvider, IViewer, GeometryType, - MeshBatch + MeshBatch, + CameraController } from '@speckle/viewer' import { Matrix4, @@ -25,7 +25,7 @@ import { export class BoxSelection extends Extension { get inject() { - return [ICameraProvider.Symbol] + return [CameraController] } private selectionExtension: SelectionExtension @@ -36,7 +36,14 @@ export class BoxSelection extends Extension { private idsToSelect: Array | null = [] - public constructor(viewer: IViewer, private cameraController: ICameraProvider) { + get enabled(): boolean { + return this._enabled + } + set enabled(value: boolean) { + this._enabled = value + } + + public constructor(viewer: IViewer, private cameraController: CameraController) { super(viewer) /** Get the SelectionExtension. We'll need it to remotely enable/disable it */ //@ts-ignore diff --git a/packages/viewer-sandbox/src/Extensions/CameraPlanes.ts b/packages/viewer-sandbox/src/Extensions/CameraPlanes.ts index dda95a90d..7ed337836 100644 --- a/packages/viewer-sandbox/src/Extensions/CameraPlanes.ts +++ b/packages/viewer-sandbox/src/Extensions/CameraPlanes.ts @@ -11,6 +11,13 @@ import { PerspectiveCamera } from 'three' export class CameraPlanes extends Extension { private camerController: CameraController + get enabled(): boolean { + return this._enabled + } + set enabled(value: boolean) { + this._enabled = value + } + public constructor(viewer: IViewer) { super(viewer) this.camerController = viewer.getExtension( diff --git a/packages/viewer-sandbox/src/Extensions/RotateCamera.ts b/packages/viewer-sandbox/src/Extensions/RotateCamera.ts index 8620cb623..98bcdbfbb 100644 --- a/packages/viewer-sandbox/src/Extensions/RotateCamera.ts +++ b/packages/viewer-sandbox/src/Extensions/RotateCamera.ts @@ -1,8 +1,8 @@ -import { Vector3, ICameraProvider, Extension, IViewer } from '@speckle/viewer' +import { Vector3, Extension, IViewer, CameraController } from '@speckle/viewer' export class RotateCamera extends Extension { get inject() { - return [ICameraProvider.Symbol] + return [CameraController] } private polar = { azimuth: 0.001, @@ -11,7 +11,14 @@ export class RotateCamera extends Extension { origin: new Vector3() } - public constructor(viewer: IViewer, private cameraController: ICameraProvider) { + get enabled(): boolean { + return this._enabled + } + set enabled(value: boolean) { + this._enabled = value + } + + public constructor(viewer: IViewer, private cameraController: CameraController) { super(viewer) } diff --git a/packages/viewer-sandbox/src/Sandbox.ts b/packages/viewer-sandbox/src/Sandbox.ts index 6ec827177..fb9ed0ec0 100644 --- a/packages/viewer-sandbox/src/Sandbox.ts +++ b/packages/viewer-sandbox/src/Sandbox.ts @@ -21,8 +21,8 @@ import { DiffResult } from '@speckle/viewer' import type { PipelineOptions } from '@speckle/viewer/dist/modules/pipeline/Pipeline' import { Units } from '@speckle/viewer' import { SelectionExtension } from '@speckle/viewer' -import { MeasurementsExtension } from '@speckle/viewer' import { FilteringExtension } from '@speckle/viewer' +import { MeasurementsExtension } from '@speckle/viewer' import { CameraController } from '@speckle/viewer' import { UpdateFlags } from '@speckle/viewer' import { Viewer } from '@speckle/viewer' @@ -401,15 +401,15 @@ export default class Sandbox { if (!box) { box = this.viewer.getRenderer().sceneBox } - this.viewer.getExtension(SectionTool).setBox(box) - this.viewer.getExtension(SectionTool).toggle() + this.viewer.getExtension(SectionTool).setBox(box) + this.viewer.getExtension(SectionTool).toggle() }) const toggleProjection = this.tabs.pages[0].addButton({ title: 'Toggle Projection' }) toggleProjection.on('click', () => { - this.viewer.getExtension(CameraController).toggleCameras() + this.viewer.getExtension(CameraController).toggleCameras() }) const zoomExtents = this.tabs.pages[0].addButton({ diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 89a6ccb0a..263e233b4 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -5,10 +5,7 @@ import { SelectionEvent, ViewerEvent, DebugViewer, - Viewer, - Batch, - DrawRanges, - SpeckleBasicMaterial + Viewer } from '@speckle/viewer' import './style.css' @@ -23,11 +20,6 @@ import { } from '@speckle/viewer' import { SectionTool } from '@speckle/viewer' import { SectionOutlines } from '@speckle/viewer' -import { BoxSelection } from './Extensions/BoxSelection' -import { GeometryType } from '@speckle/viewer' -import { SpeckleStandardMaterial } from '@speckle/viewer' -import { Color, FrontSide } from 'three' - const createViewer = async (containerName: string, stream: string) => { const container = document.querySelector(containerName) @@ -58,7 +50,7 @@ const createViewer = async (containerName: string, stream: string) => { const filtering = viewer.createExtension(FilteringExtension) const explode = viewer.createExtension(ExplodeExtension) const diff = viewer.createExtension(DiffExtension) - const boxSelect = viewer.createExtension(BoxSelection) + // const boxSelect = viewer.createExtension(BoxSelection) // const rotateCamera = viewer.createExtension(RotateCamera) cameraController // use it selection // use it @@ -98,97 +90,6 @@ const createViewer = async (containerName: string, stream: string) => { Object.assign(sandbox.sceneParams.worldSize, viewer.World.worldSize) Object.assign(sandbox.sceneParams.worldOrigin, viewer.World.worldOrigin) sandbox.refresh() - - const meshBatch = viewer - .getRenderer() - .batcher.getBatches(undefined, GeometryType.MESH) - .find((batch: Batch) => batch.renderViews.length > 2) - // const geom = meshBatch.mesh.geometry - // geom.groups.length = 0 - // geom.addGroup(0, 216, 0) - // geom.addGroup(216, 1323, 0) - // geom.addGroup(1539, 540, 0) - // geom.addGroup(2079, 32268, 0) - - // const material = new SpeckleStandardMaterial( - // { - // color: new Color('#00ff00'), - // emissive: 0x0, - // roughness: 1, - // metalness: 0, - // opacity: 1, - // side: FrontSide - // }, - // ['USE_RTE'] - // ) - // meshBatch.setDrawRanges( - // { - // offset: 36, - // count: 36, - // material - // } - // { - // offset: 180, - // count: 1395, - // material - // }, - // { - // offset: 1581, - // count: 32766, - // material - // } - // ) - // const material = new SpeckleStandardMaterial( - // { - // color: new Color('#00ff00'), - // emissive: 0x0, - // roughness: 1, - // metalness: 0, - // opacity: 1, - // side: FrontSide - // }, - // ['USE_RTE'] - // ) - // meshBatch.setDrawRanges( - // { - // offset: 9018, - // count: 36, - // material - // }, - // { - // offset: 13878, - // count: 36, - // material - // } - // ) - // const material0 = new SpeckleBasicMaterial({ color: 0xff0000 }) - // const material1 = new SpeckleBasicMaterial({ color: 0x00ff00 }) - // let groups = [ - // { - // start: 0, - // count: 1350, - // materialIndex: 0 - // } - // ] - - // const drawRange = new DrawRanges() - - // groups = drawRange.integrateRanges( - // groups, - // [material0, material1], - // [ - // { - // offset: 0, - // count: 258, - // material: material1 - // }, - // { - // offset: 258, - // count: 1032, - // material: material1 - // } - // ] - // ) }) viewer.on(ViewerEvent.UnloadComplete, () => { diff --git a/packages/viewer/package.json b/packages/viewer/package.json index 8e2497efe..7b23b7db1 100644 --- a/packages/viewer/package.json +++ b/packages/viewer/package.json @@ -65,6 +65,7 @@ "three-mesh-bvh": "0.5.17", "tree-model": "1.0.7", "troika-three-text": "0.47.2", + "type-fest": "^4.15.0", "underscore": "1.13.6" }, "devDependencies": { diff --git a/packages/viewer/src/IViewer.ts b/packages/viewer/src/IViewer.ts index 13689d990..516e4e9c5 100644 --- a/packages/viewer/src/IViewer.ts +++ b/packages/viewer/src/IViewer.ts @@ -7,9 +7,10 @@ import { TreeNode, WorldTree } from './modules/tree/WorldTree' import { Utils } from './modules/Utils' import { World } from './modules/World' import SpeckleRenderer from './modules/SpeckleRenderer' -import { Extension } from './modules/extensions/core-extensions/Extension' +import { Extension } from './modules/extensions/Extension' import Input from './modules/input/Input' import { Loader } from './modules/loaders/Loader' +import { type Constructor } from 'type-fest' export interface ViewerParams { showStats: boolean @@ -173,8 +174,8 @@ export interface IViewer { getRenderer(): SpeckleRenderer getContainer(): HTMLElement - createExtension(type: new () => T): T - getExtension(type: new () => T): T + createExtension(type: Constructor): T + getExtension(type: Constructor): T dispose(): void } diff --git a/packages/viewer/src/index.ts b/packages/viewer/src/index.ts index eaa4833cb..1088142ca 100644 --- a/packages/viewer/src/index.ts +++ b/packages/viewer/src/index.ts @@ -37,20 +37,17 @@ import { } from './modules/extensions/measurements/MeasurementsExtension' import { Units } from './modules/converter/Units' import { SelectionExtension } from './modules/extensions/SelectionExtension' -import { CameraController } from './modules/extensions/core-extensions/CameraController' -import { - CameraControllerEvent, - CanonicalView, - ICameraProvider, - InlineView -} from './modules/extensions/core-extensions/Providers' +import { CameraController } from './modules/extensions/CameraController' +import { InlineView } from './modules/extensions/CameraController' +import { CanonicalView } from './modules/extensions/CameraController' +import { CameraEvent } from './modules/objects/SpeckleCamera' import { SectionTool } from './modules/extensions/SectionTool' import { SectionOutlines } from './modules/extensions/SectionOutlines' import { FilteringExtension, FilteringState } from './modules/extensions/FilteringExtension' -import { Extension } from './modules/extensions/core-extensions/Extension' +import { Extension } from './modules/extensions/Extension' import { ExplodeExtension } from './modules/extensions/ExplodeExtension' import { DiffExtension, @@ -86,14 +83,13 @@ export { MeasurementType, Units, Extension, - ICameraProvider, SelectionExtension, CameraController, SectionTool, SectionOutlines, MeasurementsExtension, FilteringExtension, - CameraControllerEvent, + CameraEvent, ExplodeExtension, DiffExtension, Loader, diff --git a/packages/viewer/src/modules/LegacyViewer.ts b/packages/viewer/src/modules/LegacyViewer.ts index 78d69b91b..ad1aa862b 100644 --- a/packages/viewer/src/modules/LegacyViewer.ts +++ b/packages/viewer/src/modules/LegacyViewer.ts @@ -1,11 +1,8 @@ import { MathUtils } from 'three' import { FilteringExtension, FilteringState } from './extensions/FilteringExtension' -import { - CanonicalView, - ICameraProvider, - InlineView, - PolarView -} from './extensions/core-extensions/Providers' +import { PolarView } from './extensions/CameraController' +import { InlineView } from './extensions/CameraController' +import { CanonicalView } from './extensions/CameraController' import { SpeckleType } from './loaders/GeometryConverter' import { Queries } from './queries/Queries' import { Query, QueryArgsResultMap, QueryResult } from './queries/Query' @@ -25,7 +22,7 @@ import { } from '../IViewer' import { TreeNode, WorldTree } from './tree/WorldTree' import { Viewer } from './Viewer' -import { CameraController } from './extensions/core-extensions/CameraController' +import { CameraController } from './extensions/CameraController' import { SectionTool } from './extensions/SectionTool' import { SectionOutlines } from './extensions/SectionOutlines' import { @@ -49,7 +46,7 @@ class LegacySelectionExtension extends SelectionExtension { } class HighlightExtension extends SelectionExtension { - public constructor(viewer: IViewer, protected cameraProvider: ICameraProvider) { + public constructor(viewer: IViewer, protected cameraProvider: CameraController) { super(viewer, cameraProvider) const highlightMaterialData: SelectionExtensionOptions = { selectionMaterialData: { diff --git a/packages/viewer/src/modules/SpeckleRenderer.ts b/packages/viewer/src/modules/SpeckleRenderer.ts index 33bc2c086..f9a95a52a 100644 --- a/packages/viewer/src/modules/SpeckleRenderer.ts +++ b/packages/viewer/src/modules/SpeckleRenderer.ts @@ -43,10 +43,7 @@ import { Shadowcatcher } from './Shadowcatcher' import SpeckleMesh from './objects/SpeckleMesh' import { ExtendedIntersection } from './objects/SpeckleRaycaster' import { BatchObject } from './batching/BatchObject' -import { - ICameraProvider, - CameraControllerEvent -} from './extensions/core-extensions/Providers' +import { CameraEvent, SpeckleCamera } from './objects/SpeckleCamera' import Materials, { RenderMaterial, DisplayStyle, @@ -58,7 +55,6 @@ import { SpeckleWebGLRenderer } from './objects/SpeckleWebGLRenderer' import { SpeckleTypeAllRenderables } from './loaders/GeometryConverter' import SpeckleInstancedMesh from './objects/SpeckleInstancedMesh' import { BaseSpecklePass } from './pipeline/SpecklePass' -import { CameraController } from './extensions/core-extensions/CameraController' import { MeshBatch } from './batching/MeshBatch' export class RenderingStats { @@ -118,7 +114,7 @@ export default class SpeckleRenderer { private _shadowcatcher: Shadowcatcher = null private cancel: { [subtreeId: string]: boolean } = {} - private _cameraProvider: ICameraProvider = null + private _speckleCamera: SpeckleCamera = null private _clippingPlanes: Plane[] = [] private _clippingVolume: Box3 = new Box3() @@ -217,33 +213,30 @@ export default class SpeckleRenderer { /******** * Camera */ - public get cameraProvider() { - return this._cameraProvider + public get speckleCamera(): SpeckleCamera { + return this._speckleCamera } - public set cameraProvider(value: ICameraProvider) { - this._cameraProvider = value - this._cameraProvider.on(CameraControllerEvent.Dynamic, () => { + public set speckleCamera(value: SpeckleCamera) { + this._speckleCamera = value + this._speckleCamera.on(CameraEvent.Dynamic, () => { this._needsRender = true this.pipeline.onStationaryEnd() }) - this._cameraProvider.on(CameraControllerEvent.Stationary, () => { + this._speckleCamera.on(CameraEvent.Stationary, () => { this._needsRender = true this.pipeline.onStationaryBegin() }) - this._cameraProvider.on(CameraControllerEvent.FrameUpdate, (data: boolean) => { - this.needsRender = data - if (this.pipeline.needsAccumulation && data) { + this._speckleCamera.on(CameraEvent.FrameUpdate, (needsUpdate: boolean) => { + this.needsRender = needsUpdate + if (this.pipeline.needsAccumulation && needsUpdate) { this.pipeline.reset() } }) - this.cameraProvider.on(CameraControllerEvent.ProjectionChanged, () => { - ;(this._cameraProvider as CameraController).setCameraPlanes(this.sceneBox) - }) } public get renderingCamera() { - return this._cameraProvider.renderingCamera + return this._speckleCamera.renderingCamera } /********** @@ -394,7 +387,7 @@ export default class SpeckleRenderer { } public update(deltaTime: number) { - if (!this._cameraProvider) return + if (!this._speckleCamera) return this.batcher.update(deltaTime) this.renderingCamera.updateMatrixWorld(true) @@ -548,7 +541,7 @@ export default class SpeckleRenderer { return } - if (!this._cameraProvider) return + if (!this._speckleCamera) return if (this._needsRender || this.pipeline.needsAccumulation) { this._renderinStats.frameStart() this.batcher.render(this.renderer) @@ -614,7 +607,7 @@ export default class SpeckleRenderer { /** We'll just update the shadowcatcher after all batches are loaded */ this.updateShadowCatcher() this.updateClippingPlanes() - ;(this._cameraProvider as CameraController).setCameraPlanes(this.sceneBox) + this._speckleCamera.setCameraPlanes(this.sceneBox) delete this.cancel[subtreeId] } diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index 58f965f39..f0254221a 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -23,12 +23,12 @@ import Logger from 'js-logger' import { Query, QueryArgsResultMap, QueryResult } from './queries/Query' import { Queries } from './queries/Queries' import { Utils } from './Utils' -import { Extension } from './extensions/core-extensions/Extension' -import { ICameraProvider, IProvider } from './extensions/core-extensions/Providers' +import { Extension } from './extensions/Extension' import Input from './input/Input' -import { CameraController } from './extensions/core-extensions/CameraController' +import { CameraController } from './extensions/CameraController' import { SpeckleType } from './loaders/GeometryConverter' import { Loader } from './loaders/Loader' +import { type Constructor } from 'type-fest' export class Viewer extends EventEmitter implements IViewer { /** Container and optional stats element */ @@ -51,7 +51,7 @@ export class Viewer extends EventEmitter implements IViewer { protected loaders: { [id: string]: Loader } = {} protected extensions: { - [id: string]: Extension | IProvider + [id: string]: Extension } = {} /** various utils/helpers */ @@ -75,41 +75,44 @@ export class Viewer extends EventEmitter implements IViewer { return this.speckleRenderer.input } - public createExtension( - type: new (viewer: IViewer, ...args) => T - ): T { - const providersToInject = type.prototype.inject - const providers = [] - Object.values(this.extensions).forEach((extension: IProvider) => { - const provides = extension.provide - if (provides && providersToInject.includes(provides)) providers.push(extension) + private getConstructorChain(obj: object) { + const cs = [] + let pt = obj + do { + if ((pt = Object.getPrototypeOf(pt))) cs.push(pt.constructor || null) + } while (pt !== null) + return cs.map(function (c) { + return c ? c.toString().split(/\s|\(/)[1] : null }) - const extension = new type(this, ...providers) - /** Temporary until we implement proper providing for core */ - if (ICameraProvider.isCameraProvider(extension)) { - this.speckleRenderer.cameraProvider = extension - } - this.extensions[type.name] = extension - return extension } - public getExtension( - type: new (viewer: IViewer, ...args) => T - ): T { - const getConstructorChain = (obj) => { - const cs = [] - let pt = obj - do { - if ((pt = Object.getPrototypeOf(pt))) cs.push(pt.constructor || null) - } while (pt !== null) - return cs.map(function (c) { - return c ? c.toString().split(/\s|\(/)[1] : null - }) - } + public createExtension(type: Constructor): T { + const extensionsToInject: Array Extension> = + type.prototype.inject + const injectedExtensions: Array = [] + extensionsToInject.forEach((value: new (viewer: IViewer, ...args) => Extension) => { + if (this.extensions[value.name]) { + injectedExtensions.push(this.extensions[value.name]) + return + } + for (const k in this.extensions) { + const prototypeChain = this.getConstructorChain(this.extensions[k]) + if (prototypeChain.includes(value.name)) { + injectedExtensions.push(this.extensions[k]) + } + } + }) + + const extension = new type(this, ...injectedExtensions) + this.extensions[type.name] = extension + return extension as T + } + + public getExtension(type: Constructor): T { if (this.extensions[type.name]) return this.extensions[type.name] as T else { for (const k in this.extensions) { - const prototypeChain = getConstructorChain(this.extensions[k]) + const prototypeChain = this.getConstructorChain(this.extensions[k]) if (prototypeChain.includes(type.name)) { return this.extensions[k] as T } diff --git a/packages/viewer/src/modules/extensions/core-extensions/CameraController.ts b/packages/viewer/src/modules/extensions/CameraController.ts similarity index 93% rename from packages/viewer/src/modules/extensions/core-extensions/CameraController.ts rename to packages/viewer/src/modules/extensions/CameraController.ts index e73df02dc..0fb417d67 100644 --- a/packages/viewer/src/modules/extensions/core-extensions/CameraController.ts +++ b/packages/viewer/src/modules/extensions/CameraController.ts @@ -1,23 +1,38 @@ import * as THREE from 'three' import CameraControls from 'camera-controls' import { Extension } from './Extension' -import { SpeckleCameraControls } from '../../objects/SpeckleCameraControls' +import { SpeckleCameraControls } from '../objects/SpeckleCameraControls' import { Box3, OrthographicCamera, PerspectiveCamera, Sphere, Vector3 } from 'three' import { KeyboardKeyHold, HOLD_EVENT_TYPE } from 'hold-event' -import { CanonicalView, SpeckleView, InlineView, IViewer } from '../../..' -import { - CameraControllerEvent, - CameraProjection, - ICameraProvider, - PolarView -} from './Providers' +import { CameraProjection } from '../objects/SpeckleCamera' +import { CameraEvent, SpeckleCamera } from '../objects/SpeckleCamera' import Logger from 'js-logger' +import { IViewer, SpeckleView } from '../../IViewer' -export class CameraController extends Extension implements ICameraProvider { - get provide() { - return ICameraProvider.Symbol - } +export type CanonicalView = + | 'front' + | 'back' + | 'up' + | 'top' + | 'down' + | 'bottom' + | 'right' + | 'left' + | '3d' + | '3D' +export type InlineView = { + position: Vector3 + target: Vector3 +} +export type PolarView = { + azimuth: number + polar: number + radius?: number + origin?: Vector3 +} + +export class CameraController extends Extension implements SpeckleCamera { protected _renderingCamera: PerspectiveCamera | OrthographicCamera = null protected perspectiveCamera: PerspectiveCamera = null protected orthographicCamera: OrthographicCamera = null @@ -99,19 +114,21 @@ export class CameraController extends Extension implements ICameraProvider { this.setupWASDControls() this._controls.addEventListener('rest', () => { - this.emit(CameraControllerEvent.Stationary) + this.emit(CameraEvent.Stationary) }) this._controls.addEventListener('controlstart', () => { - this.emit(CameraControllerEvent.Dynamic) + this.emit(CameraEvent.Dynamic) }) this._controls.addEventListener('controlend', () => { - if (this._controls.hasRested) this.emit(CameraControllerEvent.Stationary) + if (this._controls.hasRested) this.emit(CameraEvent.Stationary) }) this._controls.addEventListener('control', () => { - this.emit(CameraControllerEvent.Dynamic) + this.emit(CameraEvent.Dynamic) }) + + this.viewer.getRenderer().speckleCamera = this } setCameraView(objectIds: string[], transition: boolean, fit?: number): void @@ -134,12 +151,12 @@ export class CameraController extends Extension implements ICameraProvider { } else { this.setView(arg0, arg1) } - this.emit(CameraControllerEvent.Dynamic) + this.emit(CameraEvent.Dynamic) } public onEarlyUpdate(deltaTime: number) { const changed = this._controls.update(deltaTime) - this.emit(CameraControllerEvent.FrameUpdate, changed) + this.emit(CameraEvent.FrameUpdate, changed) } public onResize() { @@ -219,7 +236,8 @@ export class CameraController extends Extension implements ICameraProvider { this.orthographicCamera.updateProjectionMatrix() this._controls.camera = this.orthographicCamera - this.emit(CameraControllerEvent.ProjectionChanged, CameraProjection.ORTHOGRAPHIC) + this.setCameraPlanes(this.viewer.getRenderer().sceneBox) + this.emit(CameraEvent.ProjectionChanged, CameraProjection.ORTHOGRAPHIC) } protected setupPerspectiveCamera() { @@ -230,7 +248,8 @@ export class CameraController extends Extension implements ICameraProvider { this._controls.camera = this.perspectiveCamera this._controls.zoomTo(1) this.enableRotations() - this.emit(CameraControllerEvent.ProjectionChanged, CameraProjection.PERSPECTIVE) + this.setCameraPlanes(this.viewer.getRenderer().sceneBox) + this.emit(CameraEvent.ProjectionChanged, CameraProjection.PERSPECTIVE) } public disableRotations() { diff --git a/packages/viewer/src/modules/extensions/DiffExtension.ts b/packages/viewer/src/modules/extensions/DiffExtension.ts index 9401f473d..1b3846409 100644 --- a/packages/viewer/src/modules/extensions/DiffExtension.ts +++ b/packages/viewer/src/modules/extensions/DiffExtension.ts @@ -9,7 +9,7 @@ import SpecklePointMaterial from '../materials/SpecklePointMaterial' import SpeckleStandardMaterial from '../materials/SpeckleStandardMaterial' import { NodeRenderView } from '../tree/NodeRenderView' import { IViewer } from '../../IViewer' -import { Extension } from './core-extensions/Extension' +import { Extension } from './Extension' import { SpeckleTypeAllRenderables } from '../loaders/GeometryConverter' import { SpeckleLoader } from '../loaders/Speckle/SpeckleLoader' @@ -41,6 +41,14 @@ interface VisualDiffResult { } export class DiffExtension extends Extension { + public get enabled(): boolean { + return this._enabled + } + /* eslint-disable @typescript-eslint/no-unused-vars */ + public set enabled(value: boolean) { + this._enabled = value + } + protected tree: WorldTree = null private addedMaterialMesh: SpeckleStandardMaterial = null private changedNewMaterialMesh: SpeckleStandardMaterial = null diff --git a/packages/viewer/src/modules/extensions/ExplodeExtension.ts b/packages/viewer/src/modules/extensions/ExplodeExtension.ts index 118aa663f..67f8acd2a 100644 --- a/packages/viewer/src/modules/extensions/ExplodeExtension.ts +++ b/packages/viewer/src/modules/extensions/ExplodeExtension.ts @@ -1,12 +1,23 @@ import { Vector3 } from 'three' -import { Extension } from './core-extensions/Extension' +import { Extension } from './Extension' import { UpdateFlags } from '../../IViewer' export class ExplodeExtension extends Extension { + protected _enabled: boolean = true + + public get enabled(): boolean { + return this._enabled + } + public set enabled(value: boolean) { + this._enabled = value + } + private explodeTime = -1 private explodeRange = 0 public onEarlyUpdate() { + if (!this._enabled) return + if (this.explodeTime > -1) { this.explode(this.explodeTime, this.explodeRange) this.explodeTime = -1 diff --git a/packages/viewer/src/modules/extensions/Extension.ts b/packages/viewer/src/modules/extensions/Extension.ts new file mode 100644 index 000000000..011b28173 --- /dev/null +++ b/packages/viewer/src/modules/extensions/Extension.ts @@ -0,0 +1,40 @@ +import { IViewer } from '../..' +import EventEmitter from '../EventEmitter' + +export class Extension extends EventEmitter { + public get inject(): Array Extension> { + return [] + } + + protected viewer: IViewer + protected _enabled: boolean + + public get enabled(): boolean { + return this._enabled + } + + public set enabled(value: boolean) { + this._enabled = value + } + + public constructor(viewer: IViewer, ...args: Extension[]) { + args + super() + this.viewer = viewer + } + + public onEarlyUpdate(deltaTime?: number) { + deltaTime + /* EMPTY*/ + } + public onLateUpdate(deltaTime?: number) { + deltaTime + /* EMPTY*/ + } + public onRender() { + /* EMPTY*/ + } + public onResize() { + /* EMPTY*/ + } +} diff --git a/packages/viewer/src/modules/extensions/FilteringExtension.ts b/packages/viewer/src/modules/extensions/FilteringExtension.ts index 5c5e229ad..dcbfd0491 100644 --- a/packages/viewer/src/modules/extensions/FilteringExtension.ts +++ b/packages/viewer/src/modules/extensions/FilteringExtension.ts @@ -5,7 +5,7 @@ import { Assets } from '../Assets' import SpeckleRenderer from '../SpeckleRenderer' import { FilterMaterialType } from '../materials/Materials' import { NodeRenderView } from '../tree/NodeRenderView' -import { Extension } from './core-extensions/Extension' +import { Extension } from './Extension' import { TreeNode, WorldTree } from '../tree/WorldTree' import { IViewer, UpdateFlags, ViewerEvent } from '../../IViewer' import { @@ -40,6 +40,13 @@ export class FilteringExtension extends Extension { return this.CurrentFilteringState } + public get enabled(): boolean { + return this._enabled + } + public set enabled(value: boolean) { + this._enabled = value + } + public constructor(viewer: IViewer) { super(viewer) this.WTI = this.viewer.getWorldTree() diff --git a/packages/viewer/src/modules/extensions/SectionOutlines.ts b/packages/viewer/src/modules/extensions/SectionOutlines.ts index 2ad80dce1..b9e6cdbff 100644 --- a/packages/viewer/src/modules/extensions/SectionOutlines.ts +++ b/packages/viewer/src/modules/extensions/SectionOutlines.ts @@ -14,10 +14,9 @@ import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeome import { Geometry } from '../converter/Geometry' import SpeckleGhostMaterial from '../materials/SpeckleGhostMaterial' import SpeckleLineMaterial from '../materials/SpeckleLineMaterial' -import { Extension } from './core-extensions/Extension' +import { Extension } from './Extension' import { IViewer } from '../..' -import { ISectionProvider } from './core-extensions/Providers' -import { SectionToolEvent } from './SectionTool' +import { SectionTool, SectionToolEvent } from './SectionTool' import { GeometryType } from '../batching/Batch' import { ObjectLayers } from '../../IViewer' import { MeshBatch } from '../batching/MeshBatch' @@ -38,7 +37,7 @@ export interface PlaneOutline { export class SectionOutlines extends Extension { public get inject() { - return [ISectionProvider.Symbol] + return [SectionTool] } private static readonly INITIAL_BUFFER_SIZE = 60000 // Must be a multiple of 6 private static readonly Z_OFFSET = -0.001 @@ -55,9 +54,8 @@ export class SectionOutlines extends Extension { private planeOutlines: Record = {} private lastSectionPlanes: Plane[] = [] private sectionPlanesChanged: Plane[] = [] - private _enabled = false - public constructor(viewer: IViewer, protected sectionProvider: ISectionProvider) { + public constructor(viewer: IViewer, protected sectionProvider: SectionTool) { super(viewer) this.planeOutlines[PlaneId.POSITIVE_X] = this.createPlaneOutline(PlaneId.POSITIVE_X) this.planeOutlines[PlaneId.NEGATIVE_X] = this.createPlaneOutline(PlaneId.NEGATIVE_X) @@ -100,7 +98,11 @@ export class SectionOutlines extends Extension { return this.planeOutlines[planeId] } - public enable(value: boolean) { + public get enabled(): boolean { + return this._enabled + } + + public set enabled(value: boolean) { this._enabled = value for (const k in this.planeOutlines) { this.planeOutlines[k].renderable.visible = value @@ -108,7 +110,7 @@ export class SectionOutlines extends Extension { } public sectionUpdated(planes: Plane[]) { - if (!this.sectionProvider.enabled) this.enable(false) + if (!this.sectionProvider.enabled) this.enabled = false for (const plane in this.planeOutlines) { const clippingPlanes = planes.filter((value) => this.getPlaneId(value) !== plane) this.planeOutlines[plane].renderable.material.clippingPlanes = clippingPlanes @@ -315,7 +317,7 @@ export class SectionOutlines extends Extension { } private onSectionBoxDragStart() { - this.enable(false) + this.enabled = false } private onSectionBoxDragEnd() { @@ -346,7 +348,7 @@ export class SectionOutlines extends Extension { planes[k] ) } - this.enable(this.sectionProvider.enabled) + this.enabled = this.sectionProvider.enabled Logger.warn('Outline time: ', performance.now() - start) } diff --git a/packages/viewer/src/modules/extensions/SectionTool.ts b/packages/viewer/src/modules/extensions/SectionTool.ts index e55fb28a6..cf95f83d5 100644 --- a/packages/viewer/src/modules/extensions/SectionTool.ts +++ b/packages/viewer/src/modules/extensions/SectionTool.ts @@ -17,10 +17,10 @@ import { } from 'three' import { TransformControls } from 'three/examples/jsm/controls/TransformControls' import { IViewer, ObjectLayers } from '../../IViewer' -import { Extension } from './core-extensions/Extension' -import { CameraControllerEvent, ICameraProvider } from './core-extensions/Providers' +import { Extension } from './Extension' +import { CameraEvent } from '../objects/SpeckleCamera' import { InputEvent } from '../input/Input' -import { ISectionProvider } from './core-extensions/Providers' +import { CameraController } from './CameraController' export enum SectionToolEvent { DragStart = 'section-box-drag-start', @@ -28,13 +28,9 @@ export enum SectionToolEvent { Updated = 'section-box-changed' } -export class SectionTool extends Extension implements ISectionProvider { +export class SectionTool extends Extension { public get inject() { - return [ICameraProvider.Symbol] - } - - public get provide(): string { - return ISectionProvider.Symbol + return [CameraController] } protected dragging = false @@ -60,7 +56,6 @@ export class SectionTool extends Extension implements ISectionProvider { protected raycaster: Raycaster - private _enabled = false public get enabled() { return this._enabled } @@ -73,7 +68,7 @@ export class SectionTool extends Extension implements ISectionProvider { this.viewer.requestRender() } - constructor(viewer: IViewer, protected cameraProvider: ICameraProvider) { + constructor(viewer: IViewer, protected cameraProvider: CameraController) { super(viewer) this.viewer = viewer @@ -160,11 +155,11 @@ export class SectionTool extends Extension implements ISectionProvider { this._setupControls() this._attachControlsToBox() - this.cameraProvider.on(CameraControllerEvent.ProjectionChanged, () => { + this.cameraProvider.on(CameraEvent.ProjectionChanged, () => { this._setupControls() this._attachControlsToBox() }) - this.cameraProvider.on(CameraControllerEvent.FrameUpdate, (data: boolean) => { + this.cameraProvider.on(CameraEvent.FrameUpdate, (data: boolean) => { this.allowSelection = !data }) this.viewer.getRenderer().input.on(InputEvent.Click, this._clickHandler.bind(this)) diff --git a/packages/viewer/src/modules/extensions/SelectionExtension.ts b/packages/viewer/src/modules/extensions/SelectionExtension.ts index 003d59ca3..025504b33 100644 --- a/packages/viewer/src/modules/extensions/SelectionExtension.ts +++ b/packages/viewer/src/modules/extensions/SelectionExtension.ts @@ -1,6 +1,5 @@ import { ExtendedIntersection } from '../objects/SpeckleRaycaster' -import { Extension } from './core-extensions/Extension' -import { ICameraProvider } from './core-extensions/Providers' +import { Extension } from './Extension' import { NodeRenderView } from '../tree/NodeRenderView' import { Material } from 'three' import { InputEvent } from '../input/Input' @@ -16,6 +15,7 @@ import Materials, { DisplayStyle, RenderMaterial } from '../materials/Materials' import { StencilOutlineType } from '../../IViewer' import { MaterialOptions } from '../materials/MaterialOptions' import { TreeNode } from '../tree/WorldTree' +import { CameraController } from './CameraController' export interface SelectionExtensionOptions { selectionMaterialData: RenderMaterial & DisplayStyle & MaterialOptions @@ -49,7 +49,7 @@ const DefaultSelectionExtensionOptions: SelectionExtensionOptions = { export class SelectionExtension extends Extension { public get inject() { - return [ICameraProvider.Symbol] + return [CameraController] } protected selectedNodes: Array = [] @@ -77,7 +77,7 @@ export class SelectionExtension extends Extension { this._enabled = value } - public constructor(viewer: IViewer, protected cameraProvider: ICameraProvider) { + public constructor(viewer: IViewer, protected cameraProvider: CameraController) { super(viewer) this.viewer.on(ViewerEvent.ObjectClicked, this.onObjectClicked.bind(this)) this.viewer.on(ViewerEvent.ObjectDoubleClicked, this.onObjectDoubleClick.bind(this)) diff --git a/packages/viewer/src/modules/extensions/core-extensions/Extension.ts b/packages/viewer/src/modules/extensions/core-extensions/Extension.ts deleted file mode 100644 index 4a2a2ef4b..000000000 --- a/packages/viewer/src/modules/extensions/core-extensions/Extension.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { IViewer } from '../../..' -import EventEmitter from '../../EventEmitter' - -export abstract class Extension extends EventEmitter { - public get inject() { - return [] - } - protected viewer: IViewer - - public constructor(viewer: IViewer) { - super() - this.viewer = viewer - } - - public async init?() - public onEarlyUpdate(deltaTime?: number) { - deltaTime - /* EMPTY*/ - } - public onLateUpdate(deltaTime?: number) { - deltaTime - /* EMPTY*/ - } - public onRender() { - /* EMPTY*/ - } - public onResize() { - /* EMPTY*/ - } -} diff --git a/packages/viewer/src/modules/extensions/core-extensions/Providers.ts b/packages/viewer/src/modules/extensions/core-extensions/Providers.ts deleted file mode 100644 index 1d0537d73..000000000 --- a/packages/viewer/src/modules/extensions/core-extensions/Providers.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ -import { PerspectiveCamera, OrthographicCamera, Vector3, Plane, Box3 } from 'three' -import { SpeckleView } from '../../..' -import { SectionToolEvent } from '../SectionTool' -import { SpeckleCameraControls } from '../../objects/SpeckleCameraControls' - -export type CanonicalView = - | 'front' - | 'back' - | 'up' - | 'top' - | 'down' - | 'bottom' - | 'right' - | 'left' - | '3d' - | '3D' - -export type InlineView = { - position: Vector3 - target: Vector3 -} - -export type PolarView = { - azimuth: number - polar: number - radius?: number - origin?: Vector3 -} - -export enum CameraProjection { - PERSPECTIVE, - ORTHOGRAPHIC -} - -export enum CameraControllerEvent { - Stationary, - Dynamic, - FrameUpdate, - ProjectionChanged -} - -export interface IProvider { - get provide(): string -} - -export interface ICameraProvider extends IProvider { - get enabled(): boolean - set enabled(val: boolean) - get renderingCamera(): PerspectiveCamera | OrthographicCamera - get controls(): SpeckleCameraControls - setCameraView(objectIds: string[], transition: boolean, fit?: number) - setCameraView( - view: CanonicalView | SpeckleView | InlineView | PolarView, - transition: boolean - ) - setCameraView(box: Box3, transition: boolean, fit?: number) - on(e: CameraControllerEvent, handler: (data: boolean) => void) - removeListener(e: SectionToolEvent, handler: (data: Plane[]) => void) -} - -export abstract class ICameraProvider { - public static readonly Symbol = 'ICameraProvider' - public static isCameraProvider(extension): extension is ICameraProvider { - return 'renderingCamera' in extension - } -} - -export interface ISectionProvider extends IProvider { - get enabled(): boolean - set enabled(val: boolean) - on(e: SectionToolEvent, handler: (data: Plane[]) => void) - removeListener(e: SectionToolEvent, handler: (data: Plane[]) => void) -} - -export abstract class ISectionProvider { - public static readonly Symbol = 'ISectionProvider' -} diff --git a/packages/viewer/src/modules/extensions/measurements/MeasurementsExtension.ts b/packages/viewer/src/modules/extensions/measurements/MeasurementsExtension.ts index e9f6294f0..d3f7bd7a2 100644 --- a/packages/viewer/src/modules/extensions/measurements/MeasurementsExtension.ts +++ b/packages/viewer/src/modules/extensions/measurements/MeasurementsExtension.ts @@ -9,9 +9,9 @@ import { ExtendedIntersection } from '../../objects/SpeckleRaycaster' import Logger from 'js-logger' import SpeckleMesh from '../../objects/SpeckleMesh' import SpeckleGhostMaterial from '../../materials/SpeckleGhostMaterial' -import { Extension } from '../core-extensions/Extension' +import { Extension } from '../Extension' import { InputEvent } from '../../input/Input' -import { ICameraProvider } from '../core-extensions/Providers' +import { CameraController } from '../CameraController' export enum MeasurementType { PERPENDICULAR, @@ -36,7 +36,7 @@ const DefaultMeasurementsOptions = { export class MeasurementsExtension extends Extension { public get inject() { - return [ICameraProvider.Symbol] + return [CameraController] } protected renderer: SpeckleRenderer = null @@ -48,7 +48,6 @@ export class MeasurementsExtension extends Extension { protected _options: MeasurementOptions = Object.assign({}, DefaultMeasurementsOptions) private _frameLock = false - private _enabled = false private _paused = false private _sceneHit = false @@ -101,7 +100,7 @@ export class MeasurementsExtension extends Extension { return this._activeMeasurement } - public constructor(viewer: IViewer, protected cameraProvider: ICameraProvider) { + public constructor(viewer: IViewer, protected cameraProvider: CameraController) { super(viewer) this.renderer = viewer.getRenderer() this.raycaster = new Raycaster() diff --git a/packages/viewer/src/modules/objects/SpeckleCamera.ts b/packages/viewer/src/modules/objects/SpeckleCamera.ts new file mode 100644 index 000000000..e199ceaf8 --- /dev/null +++ b/packages/viewer/src/modules/objects/SpeckleCamera.ts @@ -0,0 +1,21 @@ +import { Box3, OrthographicCamera, PerspectiveCamera } from 'three' + +export enum CameraEvent { + Stationary, + Dynamic, + FrameUpdate, + ProjectionChanged +} + +export interface SpeckleCamera { + get renderingCamera(): PerspectiveCamera | OrthographicCamera + get fieldOfView(): number + set fieldOfView(value: number) + get aspect(): number + on(type: CameraEvent, handler: (...args) => void) + setCameraPlanes(targetVolume: Box3, offsetScale?: number) +} +export enum CameraProjection { + PERSPECTIVE, + ORTHOGRAPHIC +} diff --git a/yarn.lock b/yarn.lock index f68aa8f38..73f425e58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14606,6 +14606,7 @@ __metadata: three-mesh-bvh: 0.5.17 tree-model: 1.0.7 troika-three-text: 0.47.2 + type-fest: ^4.15.0 typescript: ^4.5.4 underscore: 1.13.6 vitest: ^1.4.0 @@ -45776,6 +45777,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.15.0": + version: 4.15.0 + resolution: "type-fest@npm:4.15.0" + checksum: 8da2b8c4556a6bbafd79c0d50b4f3ba6526942aead9c1687038980276eee72b95a1d195bc6f1408e0ebf96ebfbe9d33436b506b35ed4b68f14f8b3ff56753850 + languageName: node + linkType: hard + "type-is@npm:^1.6.16, type-is@npm:^1.6.18, type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18"