Skip to content

Commit

Permalink
feat (beta): Add new class CustomObject and use it to store custom ob…
Browse files Browse the repository at this point in the history
…jects in the scene (#4208)

* Add new class CustomObject

* Add unit test

* Add unit test

* Update CustomObject.ts

* Add beta

* Update reveal.api.md

* Add intersection for custom objects

* Fixed according to review

* Fix according to review2

* Update reveal.api.md

---------

Co-authored-by: cognite-bulldozer[bot] <51074376+cognite-bulldozer[bot]@users.noreply.github.com>
  • Loading branch information
nilscognite and cognite-bulldozer[bot] authored Feb 23, 2024
1 parent 9041b1d commit 2f1bba2
Show file tree
Hide file tree
Showing 19 changed files with 403 additions and 37 deletions.
3 changes: 3 additions & 0 deletions viewer/api-entry-points/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export {
PointerEventDelegate,
PointerEventData,
DisposedDelegate,
CustomObject,
CustomObjectIntersection,
CustomObjectIntersectInput,
CDF_TO_VIEWER_TRANSFORMATION
} from '../packages/utilities';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export class Image360VisualizationBox implements Image360Visualization {
this.setAnnotationsVisibility(false);
this._visualizationMesh = visualizationMesh;

this._sceneHandler.addCustomObject(this._visualizationMesh);
this._sceneHandler.addObject3D(this._visualizationMesh);

function getFaceTexture(face: Image360Face['face']) {
const texture = textures.find(p => p.face === face);
Expand Down Expand Up @@ -164,7 +164,7 @@ export class Image360VisualizationBox implements Image360Visualization {
if (this._visualizationMesh === undefined) {
return;
}
this._sceneHandler.removeCustomObject(this._visualizationMesh);
this._sceneHandler.removeObject3D(this._visualizationMesh);
const imageContainerMaterial = this._visualizationMesh.material;
const materials =
imageContainerMaterial instanceof THREE.Material ? [imageContainerMaterial] : imageContainerMaterial;
Expand Down
6 changes: 3 additions & 3 deletions viewer/packages/360-images/src/icons/IconCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export class IconCollection {
this._pointsObject = iconsSprites;
this._onBeforeSceneRenderedEvent = onBeforeSceneRendered;

sceneHandler.addCustomObject(iconsSprites);
sceneHandler.addObject3D(iconsSprites);
}

private setIconClustersByLOD(octree: IconOctree, iconSprites: OverlayPointsObject): BeforeSceneRenderedDelegate {
Expand Down Expand Up @@ -178,7 +178,7 @@ export class IconCollection {
sceneHandler: SceneHandler,
onBeforeSceneRendered: EventTrigger<BeforeSceneRenderedDelegate>
): Overlay3DIcon[] {
sceneHandler.addCustomObject(this._hoverSprite);
sceneHandler.addObject3D(this._hoverSprite);

const icons = points.map(
point =>
Expand Down Expand Up @@ -214,7 +214,7 @@ export class IconCollection {

public dispose(): void {
this._onBeforeSceneRenderedEvent.unsubscribe(this._activeCullingSchemeEventHandeler);
this._sceneHandler.removeCustomObject(this._pointsObject);
this._sceneHandler.removeObject3D(this._pointsObject);
this._icons.forEach(icon => icon.dispose());
this._icons.splice(0, this._icons.length);
this._pointsObject.dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe(Image360CollectionFactory.name, () => {
}
]);

const mockSceneHandler = new Mock<SceneHandler>().setup(p => p.addCustomObject(It.IsAny())).returns();
const mockSceneHandler = new Mock<SceneHandler>().setup(p => p.addObject3D(It.IsAny())).returns();
const desktopDevice: DeviceDescriptor = { deviceType: 'desktop' };

const image360EntityFactory = new Image360CollectionFactory(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function createMockImage360(options?: { customTranslation?: THREE.Matrix4 }) {
]
};

const mockSceneHandler = new Mock<SceneHandler>().setup(p => p.addCustomObject(It.IsAny())).returns();
const mockSceneHandler = new Mock<SceneHandler>().setup(p => p.addObject3D(It.IsAny())).returns();
const mock360ImageProvider = new Mock<Image360Provider<any>>();
const mock360ImageIcon = new Overlay3DIcon(
{ position: new THREE.Vector3(), minPixelSize: 10, maxPixelSize: 10, iconRadius: 10 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default class Image360VisualTestFixture extends StreamingVisualTestFixtur
entities[1].setIconColor(new THREE.Color(1.0, 0.0, 1.0));

const icons = entities.map(entity => entity.icon);
sceneHandler.addCustomObject(this.getOctreeVisualizationObject(icons));
sceneHandler.addObject3D(this.getOctreeVisualizationObject(icons));

this.setupGUI(entities);

Expand Down
21 changes: 20 additions & 1 deletion viewer/packages/api/src/public/migration/Cognite3DViewer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Cognite3DViewer } from './Cognite3DViewer';

import nock from 'nock';
import { Mock } from 'moq.ts';
import { BeforeSceneRenderedDelegate, DisposedDelegate, SceneRenderedDelegate } from '@reveal/utilities';
import { BeforeSceneRenderedDelegate, CustomObject, DisposedDelegate, SceneRenderedDelegate } from '@reveal/utilities';
import { createGlContext, mockClientAuthentication } from '../../../../../test-utilities';

import { jest } from '@jest/globals';
Expand Down Expand Up @@ -216,6 +216,25 @@ describe('Cognite3DViewer', () => {
expect(() => viewer.removeObject3D(obj)).not.toThrowError();
});

test('viewer can add/remove CustomObject on scene', () => {
const viewer = new Cognite3DViewer({ sdk, renderer, _sectorCuller });

// Create 10 custom objects
const customObjects: CustomObject[] = [];
for (let i = 0; i < 10; i++) {
const obj = new THREE.Mesh(new THREE.SphereGeometry(i), new THREE.MeshBasicMaterial());
customObjects.push(new CustomObject(obj));
}
// Add them to the viewer
for (const customObject of customObjects) {
expect(() => viewer.addCustomObject(customObject)).not.toThrowError();
}
// Remove them from the viewer
for (const customObject of customObjects) {
expect(() => viewer.removeCustomObject(customObject)).not.toThrowError();
}
});

test('beforeSceneRendered and sceneRendered triggers before/after rendering', () => {
// Setup a fake rendering loop
const requestAnimationFrameSpy = jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => {
Expand Down
133 changes: 121 additions & 12 deletions viewer/packages/api/src/public/migration/Cognite3DViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
DisposedDelegate,
determineCurrentDevice,
SceneHandler,
BeforeSceneRenderedDelegate
BeforeSceneRenderedDelegate,
CustomObjectIntersection
} from '@reveal/utilities';

import { SessionLogger, MetricsLogger } from '@reveal/metrics';
Expand Down Expand Up @@ -83,8 +84,9 @@ import {
import { Image360ApiHelper } from '../../api-helpers/Image360ApiHelper';
import html2canvas from 'html2canvas';
import { AsyncSequencer, SequencerFunction } from '../../../../utilities/src/AsyncSequencer';
import { getNormalizedPixelCoordinates } from '@reveal/utilities';
import { getNormalizedPixelCoordinates, CustomObject } from '@reveal/utilities';
import { FlexibleCameraManager } from '@reveal/camera-manager';
import { CustomObjectIntersectInput } from '@reveal/utilities/src/customObject/CustomObjectIntersectInput';

type Cognite3DViewerEvents =
| 'click'
Expand Down Expand Up @@ -1047,11 +1049,38 @@ export class Cognite3DViewer {
return;
}
object.updateMatrixWorld(true);
this._sceneHandler.addCustomObject(object);
this._sceneHandler.addObject3D(object);
this.revealManager.requestRedraw();
this.recalculateBoundingBox();
}

/**
* Add a CustomObject to the viewer.
* @param customObject
* @example
* ```js
* const sphere = new THREE.Mesh(
* new THREE.SphereGeometry(),
* new THREE.MeshBasicMaterial()
* );
* const customObject = CustomObject(sphere);
* customObject.isPartOfBoundingBox = false;
* viewer.addCustomObject(customObject);
* ```
* @beta
*/
addCustomObject(customObject: CustomObject): void {
if (this.isDisposed) {
return;
}
customObject.object.updateMatrixWorld(true);
this._sceneHandler.addCustomObject(customObject);
this.revealManager.requestRedraw();
if (customObject.isPartOfBoundingBox) {
this.recalculateBoundingBox();
}
}

/**
* Remove a THREE.Object3D from the viewer.
* @param object
Expand All @@ -1066,11 +1095,34 @@ export class Cognite3DViewer {
if (this.isDisposed) {
return;
}
this._sceneHandler.removeCustomObject(object);
this._sceneHandler.removeObject3D(object);
this.revealManager.requestRedraw();
this.recalculateBoundingBox();
}

/**
* Remove a CustomObject from the viewer.
* @param customObject
* @example
* ```js
* const sphere = new THREE.Mesh(new THREE.SphereGeometry(), new THREE.MeshBasicMaterial());
* const customObject = CustomObject(sphere);
* viewer.addCustomObject(sphere);
* viewer.removeCustomObject(sphere);
* ```
* @beta
*/
removeCustomObject(customObject: CustomObject): void {
if (this.isDisposed) {
return;
}
this._sceneHandler.removeCustomObject(customObject);
this.revealManager.requestRedraw();
if (customObject.isPartOfBoundingBox) {
this.recalculateBoundingBox();
}
}

/**
* Sets the color used as the clear color of the renderer.
* @param backgroundColor
Expand Down Expand Up @@ -1619,6 +1671,39 @@ export class Cognite3DViewer {
return intersections.length > 0 ? intersections[0] : null;
}

private getCustomObjectIntersectionIfCloser(
offsetX: number,
offsetY: number,
closestDistanceToCamera: number | undefined
): CustomObjectIntersection | undefined {
let intersectInput: CustomObjectIntersectInput | undefined = undefined; // Lazy creation for speed
let closestIntersection: CustomObjectIntersection | undefined = undefined;
this._sceneHandler.customObjects.forEach(customObject => {
if (!customObject.shouldPick) {
return;
}
if (!intersectInput) {
intersectInput = this.createCustomObjectIntersectInput(offsetX, offsetY);
}
const intersection = customObject.intersectIfCloser(intersectInput, closestDistanceToCamera);
if (!intersection) {
return;
}
closestDistanceToCamera = intersection.distanceToCamera;
closestIntersection = intersection;
});
return closestIntersection;
}

private createCustomObjectIntersectInput(offsetX: number, offsetY: number): CustomObjectIntersectInput {
const normalizedCoords = getNormalizedPixelCoordinates(this.domElement, offsetX, offsetY);
return new CustomObjectIntersectInput(
normalizedCoords,
this.cameraManager.getCamera(),
this.getGlobalClippingPlanes()
);
}

/**
* Callback used by DefaultCameraManager to do model intersection. Made synchronous to avoid
* input lag when zooming in and out. Default implementation is async. See PR #2405 for more info.
Expand All @@ -1629,8 +1714,30 @@ export class Cognite3DViewer {
offsetY: number,
pickBoundingBox: boolean
): Promise<CameraManagerCallbackData> {
const intersection = await this.intersectModels(offsetX, offsetY, { asyncCADIntersection: false });
const modelIntersection = await this.intersectModels(offsetX, offsetY, { asyncCADIntersection: false });

// Find any custom object intersection closer to the camera than the model intersection
const customObjectIntersection = this.getCustomObjectIntersectionIfCloser(
offsetX,
offsetY,
modelIntersection?.distanceToCamera
);
if (customObjectIntersection === undefined || modelIntersection === null) {
// No intersection
return {
intersection: null,
pickedBoundingBox: undefined,
modelsBoundingBox: this._updateNearAndFarPlaneBuffers.combinedBbox
};
}
if (customObjectIntersection) {
// Custom object intersection
return {
intersection: customObjectIntersection,
pickedBoundingBox: pickBoundingBox ? customObjectIntersection.boundingBox : undefined,
modelsBoundingBox: this._updateNearAndFarPlaneBuffers.combinedBbox
};
}
const getBoundingBox = async (intersection: Intersection | null): Promise<THREE.Box3 | undefined> => {
if (intersection?.type !== 'cad') {
return undefined;
Expand All @@ -1639,9 +1746,12 @@ export class Cognite3DViewer {
const treeIndex = intersection.treeIndex;
return model.getBoundingBoxByTreeIndex(treeIndex);
};

const pickedBoundingBox = pickBoundingBox ? await getBoundingBox(intersection) : undefined;
return { intersection, pickedBoundingBox, modelsBoundingBox: this._updateNearAndFarPlaneBuffers.combinedBbox };
// Model intersection
return {
intersection: modelIntersection,
pickedBoundingBox: pickBoundingBox ? await getBoundingBox(modelIntersection) : undefined,
modelsBoundingBox: this._updateNearAndFarPlaneBuffers.combinedBbox
};
}

/** @private */
Expand All @@ -1660,15 +1770,14 @@ export class Cognite3DViewer {
combinedBbox.union(bbox);
}
});
this._sceneHandler.customObjects.forEach(obj => {
if (obj.name === 'CogniteGroundPlane') {
this._sceneHandler.customObjects.forEach(customObject => {
if (!customObject.isPartOfBoundingBox) {
return;
}
bbox.setFromObject(obj);
bbox.setFromObject(customObject.object);
if (bbox.isEmpty()) {
return;
}
// Todo: Mark some of the object not to be included in the bounding box
combinedBbox.union(bbox);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default class CustomWithStylingVisualTest extends StreamingVisualTestFixt
const sphere = new THREE.SphereGeometry(5, 32, 16);
const sphereMesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ color: 'red' }));
sphereMesh.position.set(12, -3, -2);
sceneHandler.addCustomObject(sphereMesh);
sceneHandler.addObject3D(sphereMesh);

// Styles are not applied immidiatly, so wait a little for styling to take effect
await new Promise(resolve => setTimeout(resolve, 100));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ describe(DefaultRenderPipelineProvider.name, () => {

const sceneHandler = new SceneHandler();

sceneHandler.addCustomObject(new THREE.Object3D());
sceneHandler.addObject3D(new THREE.Object3D());

const defaultRenderPipelineProvider = new DefaultRenderPipelineProvider(
materialManagerMock.object(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SceneHandler, WebGLRendererStateHelper } from '@reveal/utilities';
import { PointCloudRenderPipelineProvider } from './PointCloudRenderPipelineProvider';
import { PointCloudMaterialManager } from '../PointCloudMaterialManager';
import { SettableRenderTarget } from '../rendering/SettableRenderTarget';
import { CustomObject } from '@reveal/utilities/src/customObject/CustomObject';

export class DefaultRenderPipelineProvider implements RenderPipelineProvider, SettableRenderTarget {
private readonly _viewerScene: THREE.Scene;
Expand All @@ -30,7 +31,7 @@ export class DefaultRenderPipelineProvider implements RenderPipelineProvider, Se
pointCloudNode: THREE.Object3D;
modelIdentifier: symbol;
}[];
private readonly _customObjects: THREE.Object3D[];
private readonly _customObjects: CustomObject[];
private _autoResizeOutputTarget: boolean;
private _outputRenderTarget: THREE.WebGLRenderTarget | null;
private readonly _cadGeometryRenderPipeline: CadGeometryRenderPipelineProvider;
Expand Down Expand Up @@ -204,7 +205,7 @@ export class DefaultRenderPipelineProvider implements RenderPipelineProvider, Se

this._viewerScene.matrixWorldAutoUpdate = false;

this._customObjects?.forEach(customObject => customObject.updateMatrixWorld(true));
this._customObjects?.forEach(customObject => customObject.object.updateMatrixWorld(true));

this.updateRenderTargetSizes(renderer);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ export default class RenderingVisualTestFixture extends StreamingVisualTestFixtu
this.pipelineExecutor = stepPipelineExecutor;

const grid = this.getGridFromBoundingBox(model.boundingBox);
sceneHandler.addCustomObject(grid);
sceneHandler.addObject3D(grid);

const customBox = this.getCustomBoxFromBoundingBox(model.boundingBox);
sceneHandler.addCustomObject(customBox);
sceneHandler.addObject3D(customBox);

const transformControls = this.attachTransformControlsTo(customBox, camera, renderer.domElement);
sceneHandler.addCustomObject(transformControls);
sceneHandler.addObject3D(transformControls);

await this.setupMockCadStyling(cadMaterialManager, model.geometryNode.type);

Expand Down
4 changes: 4 additions & 0 deletions viewer/packages/utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export { DeferredPromise } from './src/DeferredPromise';

export { SceneHandler } from './src/SceneHandler';

export { CustomObject } from './src/customObject/CustomObject';
export { CustomObjectIntersectInput } from './src/customObject/CustomObjectIntersectInput';
export { CustomObjectIntersection } from './src/customObject/CustomObjectIntersection';

export { CDF_TO_VIEWER_TRANSFORMATION } from './src/constants';

export * from './src/workers/workerize-transferable';
Expand Down
Loading

0 comments on commit 2f1bba2

Please sign in to comment.