diff --git a/examples/public/index.html b/examples/public/index.html index 9edb7e5a149..94fb9dd253c 100644 --- a/examples/public/index.html +++ b/examples/public/index.html @@ -24,7 +24,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> Reveal examples -
diff --git a/examples/src/pages/Viewer.tsx b/examples/src/pages/Viewer.tsx index 99ae56f924d..135c8f327bd 100644 --- a/examples/src/pages/Viewer.tsx +++ b/examples/src/pages/Viewer.tsx @@ -389,7 +389,7 @@ export function Viewer() { const pointCloudUi = new PointCloudUi(viewer, gui.addFolder('Point clouds')); await modelUi.restoreModelsFromUrl(); const image360Ui = new Image360UI(viewer, gui.addFolder('360 Images')); - new Image360StylingUI(image360Ui, gui.addFolder('360 annotation styling')); + new Image360StylingUI(viewer, gui.addFolder('360 annotation styling')); const controlsGui = gui.addFolder('Camera controls'); const mouseWheelActionTypes = ['zoomToCursor', 'zoomPastCursor', 'zoomToTarget']; diff --git a/examples/src/utils/Image360StylingUI.ts b/examples/src/utils/Image360StylingUI.ts index 73bd4122d62..67cfe265d42 100644 --- a/examples/src/utils/Image360StylingUI.ts +++ b/examples/src/utils/Image360StylingUI.ts @@ -4,12 +4,11 @@ import * as dat from 'dat.gui'; -import { Image360UI } from './Image360UI'; - import * as THREE from 'three'; +import { Cognite3DViewer } from '@cognite/reveal'; export class Image360StylingUI { - constructor(image360Ui: Image360UI, gui: dat.GUI) { + constructor(viewer: Cognite3DViewer, gui: dat.GUI) { const state = { color: '#ffffff', visible: true @@ -17,8 +16,8 @@ export class Image360StylingUI { const actions = { addStyle: () => { - image360Ui.collections.forEach(coll => - coll.setDefaultAnnotationStyle({ + viewer.get360ImageCollections().forEach(collections => + collections.setDefaultAnnotationStyle({ color: new THREE.Color(state.color as THREE.ColorRepresentation).convertLinearToSRGB(), visible: state.visible }) diff --git a/examples/src/utils/Image360UI.ts b/examples/src/utils/Image360UI.ts index 80e7e87e57f..6fc45b88032 100644 --- a/examples/src/utils/Image360UI.ts +++ b/examples/src/utils/Image360UI.ts @@ -16,10 +16,8 @@ import * as dat from 'dat.gui'; export class Image360UI { private viewer: Cognite3DViewer; private gui: dat.GUI; - private entities: Image360[] = []; private selectedEntity: Image360 | undefined; private _lastAnnotation: Image360Annotation | undefined = undefined; - private _collections: Image360Collection[] = []; private async handleIntersectionAsync(intersectionPromise: Promise) { const intersection = await intersectionPromise; @@ -33,10 +31,6 @@ export class Image360UI { this._lastAnnotation = intersection.annotation; } - get collections(): Image360Collection[] { - return this._collections; - } - private params = { siteId: getSiteIdFromUrl() ?? '', // For instance: helideck-site-2-jpeg space: getSpaceFromUrl() ?? '', @@ -66,14 +60,16 @@ export class Image360UI { radians: 0 }; - private opacity = { - alpha: 1 + private images360Settings = { + opacity: 1 }; - private iconCulling = { + private icons360Setting = { + visible: true, + opacity: 1, + occludedIconsVisible: true, radius: Infinity, - limit: 50, - hideAll: false + limit: 50 }; private imageRevisions = { @@ -99,8 +95,8 @@ export class Image360UI { }); const optionsFolder = this.gui.addFolder('Add Options (Events)'); - optionsFolder.hide(); const optionsFolderFdm = this.gui.addFolder('Add Options (Data Models)'); + optionsFolderFdm.hide(); // events optionsFolder.add(this.params, 'siteId').name('Site ID'); @@ -124,36 +120,54 @@ export class Image360UI { this.gui.add(this.params, 'add').name('Add image set'); this.gui.add(this.params, 'remove').name('Remove image set'); - this.gui.add(this.opacity, 'alpha', 0, 1, 0.01).onChange(() => { - this.entities.forEach(p => (p.image360Visualization.opacity = this.opacity.alpha)); - this.viewer.requestRedraw(); - }); - gui.add(this.params, 'assetId').name('Asset ID'); gui.add(this.params, 'findAsset').name('Find asset'); this.gui - .add(this.iconCulling, 'radius', 0, 10000, 1) - .name('Culling radius') + .add(this.images360Settings, 'opacity', 0, 1, 0.01) + .name('Image opacity') .onChange(() => { - this.set360IconCullingRestrictions(); + for (const collection of viewer.get360ImageCollections()) { + collection.setImagesOpacity(this.images360Settings.opacity); + } }); this.gui - .add(this.iconCulling, 'limit', 0, 10000, 1) - .name('Number of points') + .add(this.icons360Setting, 'visible') + .name('Show all 360 images') + .onChange(() => { + for (const collection of viewer.get360ImageCollections()) { + collection.setIconsVisibility(this.icons360Setting.visible); + } + }); + this.gui + .add(this.icons360Setting, 'opacity', 0, 1, 0.01) + .name('Icon opacity') + .onChange(() => { + for (const collection of viewer.get360ImageCollections()) { + collection.setIconsOpacity(this.icons360Setting.opacity); + } + }); + this.gui + .add(this.icons360Setting, 'occludedIconsVisible') + .name('Set occluded icons visible') + .onChange(() => { + for (const collection of viewer.get360ImageCollections()) { + collection.setOccludedIconsVisible(this.icons360Setting.occludedIconsVisible); + } + }); + this.gui + .add(this.icons360Setting, 'radius', 0, 10000, 1) + .name('Icon Culling radius') .onChange(() => { this.set360IconCullingRestrictions(); }); this.gui - .add(this.iconCulling, 'hideAll') - .name('Hide all 360 images') + .add(this.icons360Setting, 'limit', 0, 10000, 1) + .name('Icon Number of points') .onChange(() => { - if (this._collections.length > 0) { - this._collections.forEach(p => p.setIconsVisibility(!this.iconCulling.hideAll)); - this.viewer.requestRedraw(); - } + this.set360IconCullingRestrictions(); }); this.gui.add(this.params, 'saveToUrl').name('Save 360 site to URL'); @@ -163,11 +177,13 @@ export class Image360UI { .add(this.imageRevisions, 'targetDate') .name('Revision date (Unix epoch time):') .onChange(() => { - if (this.collections.length === 0) return; - + const collections = this.viewer.get360ImageCollections(); + if (collections.length === 0) { + return; + } const date = this.imageRevisions.targetDate.length > 0 ? new Date(Number(this.imageRevisions.targetDate)) : undefined; - this.collections.forEach(p => (p.targetRevisionDate = date)); + collections.forEach(p => (p.targetRevisionDate = date)); if (this.selectedEntity) viewer.enter360Image(this.selectedEntity); }); @@ -207,14 +223,11 @@ export class Image360UI { const collection = await this.addCollection(); - collection.setIconsVisibility(!this.iconCulling.hideAll); + collection.setIconsVisibility(this.icons360Setting.visible); collection.on('image360Entered', (entity, _) => { this.selectedEntity = entity; }); this.viewer.on('click', event => this.onAnnotationClicked(event)); - this._collections.push(collection); - this.entities = this.entities.concat(collection.image360Entities); - this.viewer.requestRedraw(); } @@ -248,16 +261,16 @@ export class Image360UI { } private async set360IconCullingRestrictions() { - if (this._collections.length > 0) { - this._collections.forEach(p => p.set360IconCullingRestrictions(this.iconCulling.radius, this.iconCulling.limit)); - this.viewer.requestRedraw(); + const collections = this.viewer.get360ImageCollections(); + if (collections.length === 0) { + return; } + collections.forEach(p => p.set360IconCullingRestrictions(this.icons360Setting.radius, this.icons360Setting.limit)); + this.viewer.requestRedraw(); } private async removeAll360Images() { - this._collections.forEach(p => this.viewer.remove360ImageSet(p)); - this.entities = []; - this._collections = []; + this.viewer.get360ImageCollections().forEach(p => this.viewer.remove360ImageSet(p)); } private onAnnotationClicked(event: { offsetX: number; offsetY: number; button?: number }): void { @@ -286,12 +299,12 @@ export class Image360UI { if (this.params.assetId.length === 0) { return; } - const assetId = Number(this.params.assetId); - const revisionsAndEntities = ( await Promise.all( - this.collections.map(async coll => await coll.findImageAnnotations({ assetRef: { id: assetId } })) + this.viewer + .get360ImageCollections() + .map(async collection => await collection.findImageAnnotations({ assetRef: { id: assetId } })) ) ).flat(1); diff --git a/viewer/package.json b/viewer/package.json index 497c1c1500c..8e244a78347 100644 --- a/viewer/package.json +++ b/viewer/package.json @@ -1,6 +1,6 @@ { "name": "@cognite/reveal", - "version": "4.18.0", + "version": "4.19.0", "description": "WebGL based 3D viewer for CAD and point clouds processed in Cognite Data Fusion.", "homepage": "https://github.com/cognitedata/reveal/tree/master/viewer", "repository": { diff --git a/viewer/packages/360-images/src/collection/DefaultImage360Collection.ts b/viewer/packages/360-images/src/collection/DefaultImage360Collection.ts index c525d6a97fc..7075ab4ebbf 100644 --- a/viewer/packages/360-images/src/collection/DefaultImage360Collection.ts +++ b/viewer/packages/360-images/src/collection/DefaultImage360Collection.ts @@ -31,6 +31,7 @@ import { Image360 } from '../entity/Image360'; import { Image360Revision } from '../entity/Image360Revision'; import { ImageAssetLinkAnnotationInfo } from '@reveal/data-providers'; import { Matrix4 } from 'three'; +import { DEFAULT_IMAGE_360_OPACITY } from '../entity/Image360VisualizationBox'; type Image360Events = 'image360Entered' | 'image360Exited'; @@ -161,21 +162,45 @@ export class DefaultImage360Collection implements Image360Collection { this._icons.set360IconCullingRestrictions(radius, pointLimit); } - /** - * Gets visibility of all 360 image icons. - * @returns true if all icons are visible, false if all icons are invisible - */ getIconsVisibility(): boolean { return this._isCollectionVisible; } - /** - * Set visibility of all 360 image icons. - * @param visible If true all icons are made visible according to the active culling scheme. If false all icons are hidden. - */ - public setIconsVisibility(visible: boolean): void { - this._isCollectionVisible = visible; - this.image360Entities.forEach(entity => entity.icon.setVisible(visible)); + public setIconsVisibility(value: boolean): void { + this._isCollectionVisible = value; + this.image360Entities.forEach(entity => entity.icon.setVisible(value)); + this._needsRedraw = true; + } + + public isOccludedIconsVisible(): boolean { + return this._icons.isOccludedVisible(); + } + + public setOccludedIconsVisible(value: boolean): void { + this._icons.setOccludedVisible(value); + this._needsRedraw = true; + } + + public getIconsOpacity(): number { + return this._icons.getOpacity(); + } + + public setIconsOpacity(value: number): void { + this._icons.setOpacity(value); + this._needsRedraw = true; + } + + public getImagesOpacity(): number { + for (const entity of this.image360Entities) { + return entity.image360Visualization.opacity; + } + return DEFAULT_IMAGE_360_OPACITY; + } + + public setImagesOpacity(value: number): void { + for (const entity of this.image360Entities) { + entity.image360Visualization.opacity = value; + } this._needsRedraw = true; } diff --git a/viewer/packages/360-images/src/collection/Image360Collection.ts b/viewer/packages/360-images/src/collection/Image360Collection.ts index 0ef332bc125..323d6d89c30 100644 --- a/viewer/packages/360-images/src/collection/Image360Collection.ts +++ b/viewer/packages/360-images/src/collection/Image360Collection.ts @@ -115,6 +115,42 @@ export interface Image360Collection { */ setIconsVisibility(visible: boolean): void; + /** + * Check if the occluded icons are visible + * @returns true is occluded icons are visible + */ + isOccludedIconsVisible(): boolean; + + /** + * Set the occluded icons visible + * @param visible + */ + setOccludedIconsVisible(visible: boolean): void; + + /** + * Get the opacity of the images + * @returns The opacity of the images + */ + getImagesOpacity(): number; + + /** + * Set the opacity of the images + * @param opacity The opacity of the images + */ + setImagesOpacity(opacity: number): void; + + /** + * Get the opacity of the icons + * @returns The opacity of the icons + */ + getIconsOpacity(): number; + + /** + * Set the opacity of the icons + * @param opacity The opacity of the icons + */ + setIconsOpacity(opacity: number): void; + /** * Subscribes to events on 360 Image datasets. There are several event types: * 'image360Entered' - Subscribes to a event for entering 360 image mode. diff --git a/viewer/packages/360-images/src/entity/Image360VisualizationBox.ts b/viewer/packages/360-images/src/entity/Image360VisualizationBox.ts index 8ef831359d6..1a1d01eab93 100644 --- a/viewer/packages/360-images/src/entity/Image360VisualizationBox.ts +++ b/viewer/packages/360-images/src/entity/Image360VisualizationBox.ts @@ -16,6 +16,8 @@ type VisualizationState = { renderOrder: number; }; +export const DEFAULT_IMAGE_360_OPACITY = 1; + export class Image360VisualizationBox implements Image360Visualization { private readonly MAX_MOBILE_IMAGE_SIZE = 1024; private readonly _worldTransform: THREE.Matrix4; @@ -29,49 +31,49 @@ export class Image360VisualizationBox implements Image360Visualization { private readonly _annotationsGroup: THREE.Group = new THREE.Group(); private readonly _localTransform: THREE.Matrix4; - get opacity(): number { - return this._visualizationState.opacity; - } - - set opacity(alpha: number) { - this._visualizationState.opacity = alpha; - - this._faceMaterials.forEach(material => { - material.opacity = alpha; - }); - } - get visible(): boolean { return this._visualizationState.visible; } - set visible(isVisible: boolean) { - this._visualizationState.visible = isVisible; + set visible(value: boolean) { + this._visualizationState.visible = value; if (this._visualizationMesh === undefined) { return; } - this._visualizationMesh.visible = isVisible; + this._visualizationMesh.visible = value; + } + + get opacity(): number { + return this._visualizationState.opacity; + } + + set opacity(value: number) { + this._visualizationState.opacity = value; + + this._faceMaterials.forEach(material => { + material.opacity = value; + }); } - set scale(newScale: THREE.Vector3) { - this._visualizationState.scale = newScale; + set scale(value: THREE.Vector3) { + this._visualizationState.scale = value; if (this._visualizationMesh === undefined) { return; } - this._visualizationMesh.scale.copy(newScale); + this._visualizationMesh.scale.copy(value); } - set renderOrder(newRenderOrder: number) { - this._visualizationState.renderOrder = newRenderOrder; + set renderOrder(value: number) { + this._visualizationState.renderOrder = value; if (this._visualizationMesh === undefined) { return; } - this._visualizationMesh.renderOrder = newRenderOrder; + this._visualizationMesh.renderOrder = value; } setAnnotations(annotations: ImageAnnotationObject[]): void { @@ -91,7 +93,7 @@ export class Image360VisualizationBox implements Image360Visualization { this._device = device; this._textureLoader = new THREE.TextureLoader(); this._visualizationState = { - opacity: 1, + opacity: DEFAULT_IMAGE_360_OPACITY, renderOrder: 3, scale: new THREE.Vector3(1, 1, 1), visible: true diff --git a/viewer/packages/360-images/src/icons/IconCollection.ts b/viewer/packages/360-images/src/icons/IconCollection.ts index 24b94cc1849..1b9cd0441ea 100644 --- a/viewer/packages/360-images/src/icons/IconCollection.ts +++ b/viewer/packages/360-images/src/icons/IconCollection.ts @@ -79,7 +79,7 @@ export class IconCollection { const sharedTexture = this.createOuterRingsTexture(); - const iconsSprites = new OverlayPointsObject(points.length, { + const pointsObjects = new OverlayPointsObject(points.length, { spriteTexture: sharedTexture, minPixelSize: IconCollection.MinPixelSize, maxPixelSize: this._maxPixelSize, @@ -96,16 +96,16 @@ export class IconCollection { const octree = new IconOctree(this._icons, octreeBounds, 2); this._iconCullingScheme = 'clustered'; - this._computeClustersEventHandler = this.setIconClustersByLOD(octree, iconsSprites); - this._computeProximityPointsEventHandler = this.computeProximityPoints(octree, iconsSprites); + this._computeClustersEventHandler = this.setIconClustersByLOD(octree, pointsObjects); + this._computeProximityPointsEventHandler = this.computeProximityPoints(octree, pointsObjects); this._activeCullingSchemeEventHandeler = this._computeClustersEventHandler; onBeforeSceneRendered.subscribe(this._activeCullingSchemeEventHandeler); this._sceneHandler = sceneHandler; - this._pointsObject = iconsSprites; + this._pointsObject = pointsObjects; this._onBeforeSceneRenderedEvent = onBeforeSceneRendered; - sceneHandler.addObject3D(iconsSprites); + sceneHandler.addObject3D(pointsObjects); } public setTransform(transform: Matrix4): void { @@ -309,4 +309,24 @@ export class IconCollection { context.fill(); } } + + //================================================ + // INSTANCE METHODS: Setter and getters + //================================================ + + public getOpacity(): number { + return this._pointsObject.getOpacity(); + } + + public setOpacity(value: number): void { + this._pointsObject.setOpacity(value); + } + + public isOccludedVisible(): boolean { + return this._pointsObject.isBackPointsVisible(); + } + + public setOccludedVisible(value: boolean): void { + this._pointsObject.setBackPointsVisible(value); + } } diff --git a/viewer/packages/3d-overlays/src/Overlay3DIcon.ts b/viewer/packages/3d-overlays/src/Overlay3DIcon.ts index 64d405f0dd4..a8e15843064 100644 --- a/viewer/packages/3d-overlays/src/Overlay3DIcon.ts +++ b/viewer/packages/3d-overlays/src/Overlay3DIcon.ts @@ -30,7 +30,7 @@ export type IconParameters = { export type SetAdaptiveScaleDelegate = (args: { camera: Camera; renderSize: Vector2; domElement: HTMLElement }) => void; -export type ParametersChangeDelegate = (event: { color: Color; visble: boolean }) => void; +export type ParametersChangeDelegate = (event: { color: Color; visible: boolean }) => void; export type SelectedDelegate = (event: { selected: boolean }) => void; export type IconEvent = 'selected' | 'parametersChange'; @@ -146,7 +146,7 @@ export class Overlay3DIcon implements } else { this._color = color; } - this._events.parametersChange.fire({ color: this._color, visble: this.getVisible() }); + this._events.parametersChange.fire({ color: this._color, visible: this.getVisible() }); } getColor(): Color { @@ -171,7 +171,7 @@ export class Overlay3DIcon implements public setVisible(visible: boolean): void { this._visible = visible; - this._events.parametersChange.fire({ color: this._color, visble: visible }); + this._events.parametersChange.fire({ color: this._color, visible: visible }); } public getVisible(): boolean { diff --git a/viewer/packages/3d-overlays/src/OverlayPointsObject.ts b/viewer/packages/3d-overlays/src/OverlayPointsObject.ts index 41a28ac4a8a..a5b6f384b28 100644 --- a/viewer/packages/3d-overlays/src/OverlayPointsObject.ts +++ b/viewer/packages/3d-overlays/src/OverlayPointsObject.ts @@ -36,6 +36,9 @@ export type OverlayPointsParameters = { collectionOpacity?: number; }; +export const DEFAULT_OVERLAY_FRONT_OPACITY = 1; +export const DEFAULT_OVERLAY_BACK_OPACITY = 0.5; + export class OverlayPointsObject extends Group { private readonly _geometry: BufferGeometry; private readonly _frontMaterial: RawShaderMaterial; @@ -70,7 +73,7 @@ export class OverlayPointsObject extends Group { radius, colorTint = new Color(1, 1, 1), depthMode = LessEqualDepth, - collectionOpacity = 1, + collectionOpacity = DEFAULT_OVERLAY_FRONT_OPACITY, maskTexture } = materialParameters; @@ -89,7 +92,7 @@ export class OverlayPointsObject extends Group { const backMaterial = this.createIconsMaterial( spriteTexture, maskTexture, - 0.5, + DEFAULT_OVERLAY_BACK_OPACITY, GreaterDepth, minPixelSize, maxPixelSize, @@ -110,6 +113,27 @@ export class OverlayPointsObject extends Group { this._onBeforeRender = onBeforeRender; } + public getOpacity(): number { + const frontMaterial = this._points.frontPoints.material as ShaderMaterial; + return frontMaterial.uniforms.collectionOpacity.value; + return 1; + } + + public setOpacity(value: number): void { + const frontMaterial = this._points.frontPoints.material as ShaderMaterial; + frontMaterial.uniforms.collectionOpacity.value = value; + const backMaterial = this._points.backPoints.material as ShaderMaterial; + backMaterial.uniforms.collectionOpacity.value = value / 2; + } + + public isBackPointsVisible(): boolean { + return this._points.backPoints.visible; + } + + public setBackPointsVisible(value: boolean): void { + this._points.backPoints.visible = value; + } + public setPoints(points: Vector3[], colors?: Color[]): void { if (colors && points.length !== colors?.length) throw new Error('Points positions and colors arrays must have the same length'); @@ -168,14 +192,14 @@ export class OverlayPointsObject extends Group { this._geometry.dispose(); } - private initializePoints(geometry: BufferGeometry, frontMaterial: ShaderMaterial): Points { - const frontPoints = createPoints(geometry, frontMaterial); - frontPoints.onBeforeRender = (renderer, ...rest) => { + private initializePoints(geometry: BufferGeometry, material: ShaderMaterial): Points { + const points = createPoints(geometry, material); + points.onBeforeRender = (renderer, ...rest) => { this._onBeforeRender?.(renderer, ...rest); - setUniforms(renderer, frontMaterial); + setUniforms(renderer, material); }; - return frontPoints; + return points; function createPoints(geometry: BufferGeometry, material: ShaderMaterial): Points { const points = new Points(geometry, material); diff --git a/viewer/packages/api/package.json b/viewer/packages/api/package.json index ce6b8c5866d..fcc05f9c479 100644 --- a/viewer/packages/api/package.json +++ b/viewer/packages/api/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@reveal/360-images": "workspace:*", + "@reveal/3d-overlays": "workspace:*", "@reveal/cad-geometry-loaders": "workspace:*", "@reveal/cad-model": "workspace:*", "@reveal/cad-parsers": "workspace:*", diff --git a/viewer/reveal.api.md b/viewer/reveal.api.md index 05a18a05aec..ddbe74df8a0 100644 --- a/viewer/reveal.api.md +++ b/viewer/reveal.api.md @@ -1205,10 +1205,13 @@ export interface Image360Collection { // @deprecated getAssetIds(): Promise; getDefaultAnnotationStyle(): Image360AnnotationAppearance; + getIconsOpacity(): number; getIconsVisibility(): boolean; + getImagesOpacity(): number; getModelTransformation(out?: Matrix4): Matrix4; readonly id: string; readonly image360Entities: Image360[]; + isOccludedIconsVisible(): boolean; readonly label: string | undefined; off(event: 'image360Entered', callback: Image360EnteredDelegate): void; // (undocumented) @@ -1218,8 +1221,11 @@ export interface Image360Collection { on(event: 'image360Exited', callback: Image360ExitedDelegate): void; set360IconCullingRestrictions(radius: number, pointLimit: number): void; setDefaultAnnotationStyle(appearance: Image360AnnotationAppearance): void; + setIconsOpacity(opacity: number): void; setIconsVisibility(visible: boolean): void; + setImagesOpacity(opacity: number): void; setModelTransformation(matrix: Matrix4): void; + setOccludedIconsVisible(visible: boolean): void; targetRevisionDate: Date | undefined; } diff --git a/viewer/yarn.lock b/viewer/yarn.lock index ba008350040..1d002fc5055 100644 --- a/viewer/yarn.lock +++ b/viewer/yarn.lock @@ -1204,6 +1204,7 @@ __metadata: resolution: "@reveal/api@workspace:packages/api" dependencies: "@reveal/360-images": "workspace:*" + "@reveal/3d-overlays": "workspace:*" "@reveal/cad-geometry-loaders": "workspace:*" "@reveal/cad-model": "workspace:*" "@reveal/cad-parsers": "workspace:*"