Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug: fix unsafe usage of static variables #4664

Merged
merged 10 commits into from
Jul 17, 2024
4 changes: 3 additions & 1 deletion examples/src/utils/CustomCameraManager.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Plane, Vector3 } from 'three';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import pull from 'lodash/pull';
Expand All @@ -19,6 +20,7 @@ export class CustomCameraManager implements CameraManager {
private _controls: OrbitControls;
private readonly _cameraChangedListener: Array<CameraChangeDelegate> = [];
private _stopEventHandler: DebouncedCameraStopEventTrigger;
private readonly cameraManagerHelper = new CameraManagerHelper();

constructor(domElement: HTMLElement, camera: THREE.PerspectiveCamera) {
this._domElement = domElement;
Expand Down Expand Up @@ -119,7 +121,7 @@ export class CustomCameraManager implements CameraManager {

update(deltaTime: number, boundingBox: THREE.Box3): void {
this._controls.update();
CameraManagerHelper.updateCameraNearAndFar(this._camera, boundingBox);
this.cameraManagerHelper.updateCameraNearAndFar(this._camera, boundingBox);
}

dispose(): void {
Expand Down
113 changes: 90 additions & 23 deletions viewer/packages/camera-manager/src/CameraManagerHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,54 @@
* Copyright 2022 Cognite AS
*/

import { Box3, PerspectiveCamera, Plane, Quaternion, Vector3 } from 'three';
import range from 'lodash/range';
import { fitCameraToBoundingBox } from '@reveal/utilities';
import range from 'lodash/range';
import { Box3, PerspectiveCamera, Plane, Quaternion, Vector3 } from 'three';

type NearAndFarPlaneBuffers = {
corners: Vector3[];
cameraPosition: Vector3;
cameraDirection: Vector3;
};

type CameraFarBuffers = {
nearPlane: Plane;
nearPlaneCoplanarPoint: Vector3;
};

/**
* Helper methods for implementing a camera manager.
*/
export class CameraManagerHelper {
/**
* @deprecated usage of mutable static variables is unsafe
* Reusable buffers used by updateNearAndFarPlane function to avoid allocations.
*/
private static readonly _updateNearAndFarPlaneBuffers = {
private static readonly _updateNearAndFarPlaneBuffers: NearAndFarPlaneBuffers = {
cameraPosition: new Vector3(),
cameraDirection: new Vector3(),
corners: new Array<Vector3>(
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3()
)
};

/**
* @deprecated usage of mutable static variables is unsafe
* @private
*/
private static readonly _calculateCameraFarBuffers: CameraFarBuffers = {
nearPlaneCoplanarPoint: new Vector3(),
nearPlane: new Plane()
};

private readonly _instanceUpdateNearAndFarPlaneBuffers: NearAndFarPlaneBuffers = {
cameraPosition: new Vector3(),
cameraDirection: new Vector3(),
corners: new Array<Vector3>(
Expand All @@ -28,10 +64,11 @@ export class CameraManagerHelper {
)
};

private static readonly _calculateCameraFarBuffers = {
private readonly _instanceCalculateCameraFarBuffers: CameraFarBuffers = {
nearPlaneCoplanarPoint: new Vector3(),
nearPlane: new Plane()
};

/**
* Calculates camera target based on new camera rotation.
* @param camera Used camera instance.
Expand Down Expand Up @@ -76,9 +113,53 @@ export class CameraManagerHelper {
* Updates near and far plane of the camera based on the bounding box.
* @param camera Used camera instance.
* @param boundingBox Bounding box of all objects on the scene.
* @deprecated use instance method instead
*/
static updateCameraNearAndFar(camera: PerspectiveCamera, boundingBox: Box3): void {
const { cameraPosition, cameraDirection, corners } = this._updateNearAndFarPlaneBuffers;
CameraManagerHelper.updateCameraNearAndFarInternal(
camera,
boundingBox,
CameraManagerHelper._updateNearAndFarPlaneBuffers,
CameraManagerHelper._calculateCameraFarBuffers
);
}

/**
* Updates near and far plane of the camera based on the bounding box.
* @param camera Used camera instance.
* @param boundingBox Bounding box of all objects on the scene.
*/
public updateCameraNearAndFar(camera: PerspectiveCamera, boundingBox: Box3): void {
CameraManagerHelper.updateCameraNearAndFarInternal(
camera,
boundingBox,
this._instanceUpdateNearAndFarPlaneBuffers,
this._instanceCalculateCameraFarBuffers
);
}

/**
* Calculates camera position and target that allows to see the content of provided bounding box.
* @param camera Used camera instance.
* @param boundingBox Bounding box to be fitted.
* @param radiusFactor The ratio of the distance from camera to center of box and radius of the box.
* @returns
*/
static calculateCameraStateToFitBoundingBox(
camera: PerspectiveCamera,
boundingBox: Box3,
radiusFactor: number = 2
): { position: Vector3; target: Vector3 } {
return fitCameraToBoundingBox(camera, boundingBox, radiusFactor);
}

private static updateCameraNearAndFarInternal(
camera: PerspectiveCamera,
boundingBox: Box3,
nearAndFarPlaneBuffers: NearAndFarPlaneBuffers,
cameraFarBuffers: CameraFarBuffers
): void {
const { cameraPosition, cameraDirection, corners } = nearAndFarPlaneBuffers;
this.getBoundingBoxCorners(boundingBox, corners);
camera.getWorldPosition(cameraPosition);
camera.getWorldDirection(cameraDirection);
Expand All @@ -89,7 +170,7 @@ export class CameraManagerHelper {

// 2. Compute the far distance to the distance from camera to furthest
// corner of the bounding box that is "in front" of the near plane
const far = this.calculateCameraFar(near, cameraPosition, cameraDirection, corners);
const far = this.calculateCameraFar(near, cameraPosition, cameraDirection, corners, cameraFarBuffers);

// 3. Handle when camera is inside the model by adjusting the near value
if (boundingBox.containsPoint(cameraPosition)) {
Expand All @@ -101,28 +182,14 @@ export class CameraManagerHelper {
camera.updateProjectionMatrix();
}

/**
* Calculates camera position and target that allows to see the content of provided bounding box.
* @param camera Used camera instance.
* @param boundingBox Bounding box to be fitted.
* @param radiusFactor The ratio of the distance from camera to center of box and radius of the box.
* @returns
*/
static calculateCameraStateToFitBoundingBox(
camera: PerspectiveCamera,
boundingBox: Box3,
radiusFactor: number = 2
): { position: Vector3; target: Vector3 } {
return fitCameraToBoundingBox(camera, boundingBox, radiusFactor);
}

private static calculateCameraFar(
near: number,
cameraPosition: Vector3,
cameraDirection: Vector3,
corners: Array<Vector3>
corners: Array<Vector3>,
cameraFarBuffers: CameraFarBuffers
): number {
const { nearPlane, nearPlaneCoplanarPoint } = this._calculateCameraFarBuffers;
const { nearPlane, nearPlaneCoplanarPoint } = cameraFarBuffers;

nearPlaneCoplanarPoint.copy(cameraPosition).addScaledVector(cameraDirection, near);
nearPlane.setFromNormalAndCoplanarPoint(cameraDirection, nearPlaneCoplanarPoint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export class DefaultCameraManager implements CameraManager {
private _isDisposed = false;
private _nearAndFarNeedsUpdate = false;

private readonly cameraManagerHelper = new CameraManagerHelper();

// The active/inactive state of this manager. Does not always match up with the controls
// as these are temporarily disabled to block onWheel input during `zoomToCursor`-mode
private _isEnabled = true;
Expand Down Expand Up @@ -427,7 +429,7 @@ export class DefaultCameraManager implements CameraManager {
return;
}
if (this.automaticNearFarPlane) {
CameraManagerHelper.updateCameraNearAndFar(camera, boundingBox);
this.cameraManagerHelper.updateCameraNearAndFar(camera, boundingBox);
}
if (this.automaticControlsSensitivity) {
const diagonal = boundingBox.min.distanceTo(boundingBox.max);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export class FlexibleCameraManager extends PointerEvents implements IFlexibleCam
private readonly _raycastCallback: RaycastCallback;
private readonly _haveEventListeners: boolean;

private readonly cameraManagerHelper = new CameraManagerHelper();

//================================================
// CONSTRUCTOR
//================================================
Expand Down Expand Up @@ -470,7 +472,7 @@ export class FlexibleCameraManager extends PointerEvents implements IFlexibleCam
if (!this.options.automaticNearFarPlane) {
return;
}
CameraManagerHelper.updateCameraNearAndFar(this.camera, boundingBox);
this.cameraManagerHelper.updateCameraNearAndFar(this.camera, boundingBox);
}

private updateControlsSensitivity(boundingBox: Box3): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class StationaryCameraManager implements CameraManager {
private readonly _stopEventTrigger: DebouncedCameraStopEventTrigger;
private _isDragging = false;
private _pointerEventCache: Array<PointerEvent> = [];
private readonly cameraManagerHelper = new CameraManagerHelper();

constructor(domElement: HTMLElement, camera: PerspectiveCamera) {
this._domElement = domElement;
Expand Down Expand Up @@ -156,7 +157,7 @@ export class StationaryCameraManager implements CameraManager {
}

update(_: number, boundingBox: Box3): void {
CameraManagerHelper.updateCameraNearAndFar(this._camera, boundingBox);
this.cameraManagerHelper.updateCameraNearAndFar(this._camera, boundingBox);
}

dispose(): void {
Expand Down
2 changes: 2 additions & 0 deletions viewer/reveal.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,9 @@ export class CameraManagerHelper {
};
static calculateNewRotationFromTarget(camera: PerspectiveCamera, newTarget: Vector3): Quaternion;
static calculateNewTargetFromRotation(camera: PerspectiveCamera, rotation: Quaternion, currentTarget: Vector3, position: Vector3): Vector3;
// @deprecated
static updateCameraNearAndFar(camera: PerspectiveCamera, boundingBox: Box3): void;
updateCameraNearAndFar(camera: PerspectiveCamera, boundingBox: Box3): void;
}

// @public (undocumented)
Expand Down
Loading