diff --git a/viewer/packages/360-images/src/Image360Facade.ts b/viewer/packages/360-images/src/Image360Facade.ts index 21684ec412f..2d54666140e 100644 --- a/viewer/packages/360-images/src/Image360Facade.ts +++ b/viewer/packages/360-images/src/Image360Facade.ts @@ -37,8 +37,10 @@ export class Image360Facade { this.getCollectionContainingEntity(entity).setSelectedVisibility(visible); } - hideAllHoverIcons(): void { - this._image360Collections.forEach(collection => collection.setSelectedVisibility(false)); + hideAllHoverIcons(): boolean { + return this._image360Collections.reduce((changed, collection) => { + return collection.setSelectedVisibility(false) || changed; + }, false); } set allIconCullingScheme(scheme: IconCullingScheme) { diff --git a/viewer/packages/360-images/src/collection/DefaultImage360Collection.ts b/viewer/packages/360-images/src/collection/DefaultImage360Collection.ts index 0853a0feacc..c525d6a97fc 100644 --- a/viewer/packages/360-images/src/collection/DefaultImage360Collection.ts +++ b/viewer/packages/360-images/src/collection/DefaultImage360Collection.ts @@ -209,8 +209,12 @@ export class DefaultImage360Collection implements Image360Collection { this.image360Entities.forEach(entity => (entity.icon.selected = selected)); } - public setSelectedVisibility(visible: boolean): void { + public setSelectedVisibility(visible: boolean): boolean { + if (this._icons.hoverSpriteVisibility == visible) { + return false; + } this._icons.hoverSpriteVisibility = visible; + return true; } public setCullingScheme(scheme: IconCullingScheme): void { diff --git a/viewer/packages/3d-overlays/src/Overlay3DIcon.ts b/viewer/packages/3d-overlays/src/Overlay3DIcon.ts index 368f364dd1a..64d405f0dd4 100644 --- a/viewer/packages/3d-overlays/src/Overlay3DIcon.ts +++ b/viewer/packages/3d-overlays/src/Overlay3DIcon.ts @@ -122,8 +122,9 @@ export class Overlay3DIcon implements } updateHoverSpriteScale(): void { - if (!this._hoverSprite) return; - + if (!this._hoverSprite) { + return; + } this._hoverSprite.scale.set(this._adaptiveScale * 2, this._adaptiveScale * 2, 1); } diff --git a/viewer/packages/api/src/api-helpers/Image360ApiHelper.ts b/viewer/packages/api/src/api-helpers/Image360ApiHelper.ts index 2e47c7c3474..a2415cb1a6a 100644 --- a/viewer/packages/api/src/api-helpers/Image360ApiHelper.ts +++ b/viewer/packages/api/src/api-helpers/Image360ApiHelper.ts @@ -49,6 +49,8 @@ export class Image360ApiHelper { private _transitionInProgress: boolean = false; private readonly _raycaster = new Raycaster(); private _needsRedraw: boolean = false; + private readonly _hasEventListeners: boolean; + private readonly _inputHandler?: InputHandler; private readonly _interactionState: { currentImage360Hovered?: Image360Entity; @@ -58,13 +60,6 @@ export class Image360ApiHelper { lastMousePosition?: { offsetX: number; offsetY: number }; }; - private readonly _eventHandlers: { - setHoverIconEventHandler: (event: MouseEvent) => void; - enter360Image: (event: PointerEvent) => Promise; - exit360ImageOnEscapeKey: (event: KeyboardEvent) => void; - updateHoverStateOnRender: () => void; - }; - private readonly _debouncePreLoad = debounce( entity => { this._image360Facade.preload(entity, this.findRevisionIdToEnter(entity)).catch(() => {}); @@ -80,6 +75,21 @@ export class Image360ApiHelper { private readonly _onBeforeSceneRenderedEvent: EventTrigger; private _cachedCameraManager: CameraManager | undefined; + private readonly exit360ImageOnEscapeKey = (event: KeyboardEvent) => this.exit360ImageOnEscape(event); + public readonly setHoverIconEventHandler = (event: MouseEvent): void => + this.setHoverIconOnIntersect(event.offsetX, event.offsetY); + + public readonly enter360ImageHandler = (event: PointerEventData): Promise => + this.enter360ImageOnIntersect(event); + + private readonly updateHoverStateOnRenderHandler = () => { + const lastOffset = this._interactionState.lastMousePosition; + if (lastOffset === undefined) { + return; + } + this.setHoverIconOnIntersect(lastOffset.offsetX, lastOffset.offsetY); + }; + constructor( cogniteClient: CogniteClient, sceneHandler: SceneHandler, @@ -87,8 +97,10 @@ export class Image360ApiHelper { activeCameraManager: ProxyCameraManager, inputHandler: InputHandler, onBeforeSceneRendered: EventTrigger, + hasEventListeners?: boolean, iconsOptions?: IconsOptions ) { + this._hasEventListeners = hasEventListeners ?? true; const image360EventDescriptorProvider = new Cdf360EventDescriptorProvider(cogniteClient); const image360DataModelsDescriptorProvider = new Cdf360DataModelsDescriptorProvider(cogniteClient); const combinedDescriptorProvider = new Cdf360CombinedDescriptorProvider( @@ -119,30 +131,12 @@ export class Image360ApiHelper { this._stationaryCameraManager = new StationaryCameraManager(domElement, activeCameraManager.getCamera().clone()); this._cachedCameraManager = activeCameraManager.innerCameraManager; } - const setHoverIconEventHandler = (event: MouseEvent) => this.setHoverIconOnIntersect(event.offsetX, event.offsetY); - domElement.addEventListener('mousemove', setHoverIconEventHandler); - - const enter360Image = (event: PointerEventData) => this.enter360ImageOnIntersect(event); - inputHandler.on('click', enter360Image); - - const exit360ImageOnEscapeKey = (event: KeyboardEvent) => this.exit360ImageOnEscape(event); - - const updateHoverStateOnRender = () => { - const lastOffset = this._interactionState.lastMousePosition; - if (lastOffset === undefined) { - return; - } - this.setHoverIconOnIntersect(lastOffset.offsetX, lastOffset.offsetY); - }; - - onBeforeSceneRendered.subscribe(updateHoverStateOnRender); - - this._eventHandlers = { - setHoverIconEventHandler, - enter360Image, - exit360ImageOnEscapeKey, - updateHoverStateOnRender - }; + if (this._hasEventListeners) { + domElement.addEventListener('mousemove', this.setHoverIconEventHandler); + this._inputHandler = inputHandler; + this._inputHandler.on('click', this.enter360ImageHandler); + } + onBeforeSceneRendered.subscribe(this.updateHoverStateOnRenderHandler); } get needsRedraw(): boolean { @@ -240,9 +234,16 @@ export class Image360ApiHelper { } public async enter360Image(image360Entity: Image360Entity, revision?: Image360RevisionEntity): Promise { + await this.enter360ImageInternal(image360Entity, revision); + } + + public async enter360ImageInternal( + image360Entity: Image360Entity, + revision?: Image360RevisionEntity + ): Promise { const revisionToEnter = revision ?? this.findRevisionIdToEnter(image360Entity); if (revisionToEnter === this._interactionState.revisionSelectedForEntry) { - return; + return false; } this._interactionState.revisionSelectedForEntry = revisionToEnter; try { @@ -251,10 +252,10 @@ export class Image360ApiHelper { if (this._interactionState.revisionSelectedForEntry === revisionToEnter) { this._interactionState.revisionSelectedForEntry = undefined; } - return; + return false; } if (this._interactionState.revisionSelectedForEntry !== revisionToEnter) { - return; + return false; } const lastEntered360ImageEntity = this._interactionState.currentImage360Entered; this._interactionState.currentImage360Entered = image360Entity; @@ -304,10 +305,11 @@ export class Image360ApiHelper { } this._transitionInProgress = false; } - this._domElement.addEventListener('keydown', this._eventHandlers.exit360ImageOnEscapeKey); + this._domElement.addEventListener('keydown', this.exit360ImageOnEscapeKey); this.applyFullResolutionTextures(revisionToEnter); imageCollection.events.image360Entered.fire(image360Entity, revisionToEnter); + return true; } private async applyFullResolutionTextures(revision: Image360RevisionEntity) { @@ -471,7 +473,7 @@ export class Image360ApiHelper { this._activeCameraManager.setActiveCameraManager(this._cachedCameraManager); setCameraTarget1MeterInFrontOfCamera(this._activeCameraManager, position, rotation); } - this._domElement.removeEventListener('keydown', this._eventHandlers.exit360ImageOnEscapeKey); + this._domElement.removeEventListener('keydown', this.exit360ImageOnEscapeKey); function setCameraTarget1MeterInFrontOfCamera(manager: CameraManager, position: Vector3, rotation: Quaternion) { manager.setCameraState({ @@ -482,9 +484,14 @@ export class Image360ApiHelper { } public dispose(): void { - this._onBeforeSceneRenderedEvent.unsubscribe(this._eventHandlers.updateHoverStateOnRender); - this._domElement.removeEventListener('mousemove', this._eventHandlers.setHoverIconEventHandler); - this._domElement.removeEventListener('keydown', this._eventHandlers.exit360ImageOnEscapeKey); + this._onBeforeSceneRenderedEvent.unsubscribe(this.updateHoverStateOnRenderHandler); + if (this._hasEventListeners) { + this._domElement.removeEventListener('mousemove', this.setHoverIconEventHandler); + if (this._inputHandler != undefined) { + this._inputHandler.off('click', this.enter360ImageHandler); + } + } + this._domElement.removeEventListener('keydown', this.exit360ImageOnEscapeKey); if (this._stationaryCameraManager && this._cachedCameraManager) { if (this._activeCameraManager.innerCameraManager === this._stationaryCameraManager) { @@ -500,15 +507,15 @@ export class Image360ApiHelper { return targetDate ? image360Entity.getRevisionClosestToDate(targetDate) : image360Entity.getMostRecentRevision(); } - private enter360ImageOnIntersect(event: PointerEventData): Promise { + private enter360ImageOnIntersect(event: PointerEventData): Promise { if (this._transitionInProgress) { - return Promise.resolve(); + return Promise.resolve(false); } const entity = this.intersect360ImageIcons(event.offsetX, event.offsetY); if (entity === undefined) { - return Promise.resolve(); + return Promise.resolve(false); } - return this.enter360Image(entity); + return this.enter360ImageInternal(entity); } public intersect360ImageIcons(offsetX: number, offsetY: number): Image360Entity | undefined { @@ -554,7 +561,6 @@ export class Image360ApiHelper { if (entity === this._interactionState.currentImage360Hovered) { entity?.icon.updateHoverSpriteScale(); - return; } @@ -563,9 +569,11 @@ export class Image360ApiHelper { entity.icon.selected = true; this._debouncePreLoad(entity); } else { - this._image360Facade.hideAllHoverIcons(); + if (!this._image360Facade.hideAllHoverIcons()) { + this._interactionState.currentImage360Hovered = undefined; + return; + } } - this._needsRedraw = true; this._interactionState.currentImage360Hovered = entity; } diff --git a/viewer/packages/api/src/public/migration/Cognite3DViewer.ts b/viewer/packages/api/src/public/migration/Cognite3DViewer.ts index 6cd7d3a7482..212b7315939 100644 --- a/viewer/packages/api/src/public/migration/Cognite3DViewer.ts +++ b/viewer/packages/api/src/public/migration/Cognite3DViewer.ts @@ -189,7 +189,7 @@ export class Cognite3DViewer { private readonly spinner: Spinner; /** - * Enbles us to ensure models are added in the order their load is initialized. + * Enable us to ensure models are added in the order their load is initialized. */ private readonly _addModelSequencer: AsyncSequencer = new AsyncSequencer(); @@ -350,6 +350,7 @@ export class Cognite3DViewer { this._activeCameraManager, this._mouseHandler, this._events.beforeSceneRendered, + options.hasEventListeners, { platformMaxPointsSize: getMaxPointSize(this._renderer) } @@ -1664,6 +1665,33 @@ export class Cognite3DViewer { return intersection; } + /** + * Event function to click on 360 images. + * @param event The event type. + * @returns True if the event was handled, false otherwise. + * @beta + */ + public async onClick360Images(event: PointerEvent): Promise { + if (this._image360ApiHelper === undefined) { + return false; + } + return this._image360ApiHelper.enter360ImageHandler({ offsetX: event.offsetX, offsetY: event.offsetY }); + } + + /** + * Event function to to move the mouse. + * @param event The event type. + * @returns True if the event was handled, false otherwise. + * @beta + */ + public onHover360Images(event: PointerEvent): boolean { + if (this._image360ApiHelper === undefined) { + return false; + } + this._image360ApiHelper.setHoverIconEventHandler(event); + return true; + } + private isIntersecting360Icon(vector: THREE.Vector2): boolean { if (this._image360ApiHelper === undefined) { return false; diff --git a/viewer/packages/api/src/public/migration/types.ts b/viewer/packages/api/src/public/migration/types.ts index ec001f53cc4..3721bcfa311 100644 --- a/viewer/packages/api/src/public/migration/types.ts +++ b/viewer/packages/api/src/public/migration/types.ts @@ -198,7 +198,7 @@ export interface Cognite3DViewerOptions { useFlexibleCameraManager?: boolean; /** - * Add event listerers around, default is having event listeners. + * Add event listeners around, default is having event listeners. * @beta */ hasEventListeners?: boolean; diff --git a/viewer/packages/camera-manager/src/Flexible/FlexibleCameraManager.ts b/viewer/packages/camera-manager/src/Flexible/FlexibleCameraManager.ts index 2227301ac1f..500f4273764 100644 --- a/viewer/packages/camera-manager/src/Flexible/FlexibleCameraManager.ts +++ b/viewer/packages/camera-manager/src/Flexible/FlexibleCameraManager.ts @@ -48,7 +48,7 @@ export class FlexibleCameraManager extends PointerEvents implements IFlexibleCam private _isEnableClickAndDoubleClick = true; private _nearAndFarNeedsUpdate = false; private readonly _raycastCallback: RaycastCallback; - private readonly _haveEventListeners: boolean; + private readonly _hasEventListeners: boolean; private readonly cameraManagerHelper = new CameraManagerHelper(); @@ -61,15 +61,15 @@ export class FlexibleCameraManager extends PointerEvents implements IFlexibleCam raycastCallback: RaycastCallback, camera?: PerspectiveCamera, scene?: Scene, - haveEventListeners?: boolean + hasEventListeners?: boolean ) { super(); - this._haveEventListeners = haveEventListeners ?? true; + this._hasEventListeners = hasEventListeners ?? true; this._controls = new FlexibleControls(camera, domElement, new FlexibleControlsOptions()); this._controls.getPickedPointByPixelCoordinates = this.getPickedPointByPixelCoordinates; this._raycastCallback = raycastCallback; - if (this._haveEventListeners) { + if (this._hasEventListeners) { this._pointerEventsTarget = new PointerEventsTarget(domElement, this); this.addEventListeners(); } @@ -393,7 +393,7 @@ export class FlexibleCameraManager extends PointerEvents implements IFlexibleCam //================================================ private addEventListeners() { - if (!this._haveEventListeners) { + if (!this._hasEventListeners) { return; } this._pointerEventsTarget?.addEventListeners(); @@ -401,7 +401,7 @@ export class FlexibleCameraManager extends PointerEvents implements IFlexibleCam } private removeEventListeners(): void { - if (!this._haveEventListeners) { + if (!this._hasEventListeners) { return; } this._pointerEventsTarget?.removeEventListeners(); diff --git a/viewer/reveal.api.md b/viewer/reveal.api.md index 83d9180a7da..05a18a05aec 100644 --- a/viewer/reveal.api.md +++ b/viewer/reveal.api.md @@ -482,6 +482,10 @@ export class Cognite3DViewer { on(event: 'cameraStop', callback: CameraStopDelegate): void; on(event: 'beforeSceneRendered', callback: BeforeSceneRenderedDelegate): void; on(event: 'sceneRendered', callback: SceneRenderedDelegate): void; + // @beta + onClick360Images(event: PointerEvent): Promise; + // @beta + onHover360Images(event: PointerEvent): boolean; get pointCloudBudget(): PointCloudBudget; set pointCloudBudget(budget: PointCloudBudget); // @deprecated