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"