diff --git a/viewer/packages/3d-overlays/index.ts b/viewer/packages/3d-overlays/index.ts index b0f21d6e89c..c931c2bfc11 100644 --- a/viewer/packages/3d-overlays/index.ts +++ b/viewer/packages/3d-overlays/index.ts @@ -7,4 +7,3 @@ export { Overlay3DIcon } from './src/Overlay3DIcon'; export { Overlay3D } from './src/Overlay3D'; export { OverlayCollection, OverlayInfo, DefaultOverlay3DContentType } from './src/OverlayCollection'; export { IconOctree } from './src/IconOctree'; -export { TextOverlay } from './src/TextOverlay'; diff --git a/viewer/packages/3d-overlays/src/CameraChangeThrottler.ts b/viewer/packages/3d-overlays/src/CameraChangeThrottler.ts new file mode 100644 index 00000000000..7894cde50a2 --- /dev/null +++ b/viewer/packages/3d-overlays/src/CameraChangeThrottler.ts @@ -0,0 +1,27 @@ +/*! + * Copyright 2024 Cognite AS + */ +import throttle from 'lodash/throttle'; +import { Camera, Matrix4 } from 'three'; + +export type Callback = () => void; + +export class CameraChangeThrottler { + private readonly _prevCameraMatrix: Matrix4 = new Matrix4(); + + public call: (camera: Camera, callback: Callback) => void; + + constructor(wait: number = 500) { + this.call = throttle((camera: Camera, callback: Callback) => this._call(camera, callback), wait); + } + + private _call(camera: Camera, callback: Callback) { + if (camera.matrix.equals(this._prevCameraMatrix)) { + return; + } + + callback(); + + this._prevCameraMatrix.copy(camera.matrix); + } +} diff --git a/viewer/packages/3d-overlays/src/Overlay3DCollection.ts b/viewer/packages/3d-overlays/src/Overlay3DCollection.ts index 72aef216a13..86434470c70 100644 --- a/viewer/packages/3d-overlays/src/Overlay3DCollection.ts +++ b/viewer/packages/3d-overlays/src/Overlay3DCollection.ts @@ -2,13 +2,14 @@ * Copyright 2023 Cognite AS */ -import { CanvasTexture, Color, Texture, Object3D, Camera, Vector2, Raycaster } from 'three'; +import { CanvasTexture, Color, Texture, Object3D, Camera, Vector2, Raycaster, WebGLRenderer, Scene } from 'three'; import { Overlay3DIcon } from './Overlay3DIcon'; import { Overlay3D } from './Overlay3D'; import { OverlayPointsObject } from './OverlayPointsObject'; import { IconOctree } from './IconOctree'; import { DefaultOverlay3DContentType, OverlayCollection, OverlayInfo } from './OverlayCollection'; import minBy from 'lodash/minBy'; +import { CameraChangeThrottler } from './CameraChangeThrottler'; /** * Constructor options for the Overlay3DCollection @@ -54,6 +55,7 @@ export class Overlay3DCollection //@ts-ignore Will be removed when clustering is added. private _octree: IconOctree; private readonly _rayCaster = new Raycaster(); + private readonly _cameraChangeDebouncer = new CameraChangeThrottler(); constructor(overlayInfos: OverlayInfo[], options?: Overlay3DCollectionOptions) { super(); @@ -66,17 +68,20 @@ export class Overlay3DCollection mask: options?.overlayTextureMask ?? (options?.overlayTexture ? undefined : defaultOverlayTextures.mask) }; - this._overlayPoints = new OverlayPointsObject(overlayInfos ? overlayInfos.length : this.DefaultMaxPoints, { - spriteTexture: this._sharedTextures.color, - maskTexture: this._sharedTextures.mask, - minPixelSize: this.MinPixelSize, - maxPixelSize: options?.maxPointSize ?? this.MaxPixelSize, - radius: this._iconRadius - }); - - this._overlays = this.initializeOverlay3DIcons(overlayInfos ?? []); + this._overlayPoints = new OverlayPointsObject( + overlayInfos ? overlayInfos.length : this.DefaultMaxPoints, + { + spriteTexture: this._sharedTextures.color, + maskTexture: this._sharedTextures.mask, + minPixelSize: this.MinPixelSize, + maxPixelSize: options?.maxPointSize ?? this.MaxPixelSize, + radius: this._iconRadius + }, + (...args) => this.onBeforeRenderDelegate(...args) + ); + + this._overlays = this.initializeOverlay3DIcons(overlayInfos); this.add(this._overlayPoints); - this.updatePointsObject(); this._octree = this.rebuildOctree(); @@ -112,6 +117,14 @@ export class Overlay3DCollection return newIcons; } + private readonly onBeforeRenderDelegate: Object3D['onBeforeRender'] = ( + _renderer: WebGLRenderer, + _scene: Scene, + camera: Camera + ) => { + this._cameraChangeDebouncer.call(camera, () => this.sortOverlaysRelativeToCamera(camera)); + }; + private sortOverlaysRelativeToCamera(camera: Camera): void { this._overlays = this._overlays.sort((a, b) => { return b.getPosition().distanceToSquared(camera.position) - a.getPosition().distanceToSquared(camera.position); diff --git a/viewer/packages/3d-overlays/src/OverlayPointsObject.ts b/viewer/packages/3d-overlays/src/OverlayPointsObject.ts index c9740686882..41a28ac4a8a 100644 --- a/viewer/packages/3d-overlays/src/OverlayPointsObject.ts +++ b/viewer/packages/3d-overlays/src/OverlayPointsObject.ts @@ -13,6 +13,7 @@ import { Group, LessEqualDepth, Matrix4, + Object3D, Points, RawShaderMaterial, ShaderMaterial, @@ -43,9 +44,14 @@ export class OverlayPointsObject extends Group { private readonly _colorBuffer: Float32Array; private readonly _colorAttribute: BufferAttribute; private readonly _points: { frontPoints: Points; backPoints: Points }; + private readonly _onBeforeRender?: Object3D['onBeforeRender']; private _modelTransform: Matrix4; - constructor(maxNumberOfPoints: number, materialParameters: OverlayPointsParameters) { + constructor( + maxNumberOfPoints: number, + materialParameters: OverlayPointsParameters, + onBeforeRender?: Object3D['onBeforeRender'] + ) { super(); const geometry = new BufferGeometry(); this._positionBuffer = new Float32Array(maxNumberOfPoints * 3); @@ -101,6 +107,7 @@ export class OverlayPointsObject extends Group { this._geometry = geometry; this._frontMaterial = frontMaterial; this._points = { frontPoints, backPoints }; + this._onBeforeRender = onBeforeRender; } public setPoints(points: Vector3[], colors?: Color[]): void { @@ -163,7 +170,8 @@ export class OverlayPointsObject extends Group { private initializePoints(geometry: BufferGeometry, frontMaterial: ShaderMaterial): Points { const frontPoints = createPoints(geometry, frontMaterial); - frontPoints.onBeforeRender = renderer => { + frontPoints.onBeforeRender = (renderer, ...rest) => { + this._onBeforeRender?.(renderer, ...rest); setUniforms(renderer, frontMaterial); }; diff --git a/viewer/packages/tools/src/Overlay3D/Overlay3DTool.ts b/viewer/packages/tools/src/Overlay3D/Overlay3DTool.ts index 9008dc97a7f..fda23e762d4 100644 --- a/viewer/packages/tools/src/Overlay3D/Overlay3DTool.ts +++ b/viewer/packages/tools/src/Overlay3D/Overlay3DTool.ts @@ -16,9 +16,9 @@ import { OverlayInfo, DefaultOverlay3DContentType, OverlayCollection, - Overlay3D, - TextOverlay + Overlay3D } from '@reveal/3d-overlays'; +import { TextOverlay } from './TextOverlay'; import { Cognite3DViewerToolBase } from '../Cognite3DViewerToolBase'; /** @@ -86,7 +86,7 @@ export class Overlay3DTool extends Co private _overlayCollections: Overlay3DCollection[] = []; private _isVisible = true; - private _textOverlay: TextOverlay; + private readonly _textOverlay: TextOverlay; private readonly _events = { hover: new EventTrigger>(), @@ -118,8 +118,9 @@ export class Overlay3DTool extends Co const { _viewer: viewer } = this; const points = new Overlay3DCollection(overlays ?? [], { - ...options, - defaultOverlayColor: options?.defaultOverlayColor ?? this._defaultOverlayColor + defaultOverlayColor: options?.defaultOverlayColor ?? this._defaultOverlayColor, + overlayTexture: options?.overlayTexture, + overlayTextureMask: options?.overlayTextureMask }); this._overlayCollections.push(points); @@ -171,6 +172,21 @@ export class Overlay3DTool extends Co return this._isVisible; } + /** + * Sets whether text overlay is visible. + * Default is false. + */ + setTextOverlayVisible(visible: boolean): void { + this._textOverlay.setTextOverlayEnabled(visible); + } + + /** + * Gets whether text overlay is visible. + */ + getTextOverlayVisible(): boolean { + return this._textOverlay.getTextOverlayEnabled(); + } + /** * Removes all overlays. */ @@ -284,7 +300,7 @@ export class Overlay3DTool extends Co const intersections: [Overlay3D, THREE.Vector3][] = []; for (const points of this._overlayCollections) { - const intersection = points.intersectOverlays(normalizedCoordinates); + const intersection = points.intersectOverlays(normalizedCoordinates, camera); if (intersection !== undefined) { intersections.push([intersection, intersection.getPosition().clone()]); } diff --git a/viewer/packages/3d-overlays/src/TextOverlay.ts b/viewer/packages/tools/src/Overlay3D/TextOverlay.ts similarity index 100% rename from viewer/packages/3d-overlays/src/TextOverlay.ts rename to viewer/packages/tools/src/Overlay3D/TextOverlay.ts diff --git a/viewer/reveal.api.md b/viewer/reveal.api.md index 2836e779067..8964a68e63b 100644 --- a/viewer/reveal.api.md +++ b/viewer/reveal.api.md @@ -9,6 +9,7 @@ import { AnnotationsAssetRef } from '@cognite/sdk'; import { AnnotationsCogniteAnnotationTypesImagesAssetLink } from '@cognite/sdk'; import { AnnotationStatus } from '@cognite/sdk'; import { Box3 } from 'three'; +import { Camera } from 'three'; import { CogniteClient } from '@cognite/sdk'; import { CogniteInternalId } from '@cognite/sdk'; import { Color } from 'three'; @@ -1573,11 +1574,11 @@ export interface Overlay3D { // @public export class Overlay3DCollection extends Object3D implements OverlayCollection { - constructor(overlayInfos: OverlayInfo[], cameraManager: CameraManager, options?: Overlay3DCollectionOptions); + constructor(overlayInfos: OverlayInfo[], options?: Overlay3DCollectionOptions); addOverlays(overlayInfos: OverlayInfo[]): Overlay3D[]; dispose(): void; getOverlays(): Overlay3D[]; - intersectOverlays(normalizedCoordinates: Vector2): Overlay3D | undefined; + intersectOverlays(normalizedCoordinates: Vector2, camera: Camera): Overlay3D | undefined; removeAllOverlays(): void; removeOverlays(overlays: Overlay3D[]): void; setVisibility(visibility: boolean): void;