@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
1,018 lines (1,017 loc) • 70.1 kB
JavaScript
/** This file must only contain pure code and pure imports */
import { __esDecorate, __runInitializers } from "../tslib.es6.js";
import { serialize, serializeAsVector3 } from "../Misc/decorators.js";
import { SmartArray } from "../Misc/smartArray.js";
import { Observable } from "../Misc/observable.pure.js";
import { Matrix, Vector3, Quaternion } from "../Maths/math.vector.pure.js";
import { Node } from "../node.js";
import { Logger } from "../Misc/logger.js";
import { GetClass } from "../Misc/typeStore.js";
import { _WarnImport } from "../Misc/devTools.js";
import { Viewport } from "../Maths/math.viewport.js";
import { Frustum } from "../Maths/math.frustum.js";
import { RegisterAbstractEngineRenderPass } from "../Engines/AbstractEngine/abstractEngine.renderPass.pure.js";
import { SerializationHelper } from "../Misc/decorators.serialization.js";
/**
* This is the base class of all the camera used in the application.
* @see https://doc.babylonjs.com/features/featuresDeepDive/cameras
*/
let Camera = (() => {
var _a;
let _classSuper = Node;
let _instanceExtraInitializers = [];
let __position_decorators;
let __position_initializers = [];
let __position_extraInitializers = [];
let __upVector_decorators;
let __upVector_initializers = [];
let __upVector_extraInitializers = [];
let _get_orthoLeft_decorators;
let _get_orthoRight_decorators;
let _get_orthoBottom_decorators;
let _get_orthoTop_decorators;
let _fov_decorators;
let _fov_initializers = [];
let _fov_extraInitializers = [];
let _projectionPlaneTilt_decorators;
let _projectionPlaneTilt_initializers = [];
let _projectionPlaneTilt_extraInitializers = [];
let _minZ_decorators;
let _minZ_initializers = [];
let _minZ_extraInitializers = [];
let _maxZ_decorators;
let _maxZ_initializers = [];
let _maxZ_extraInitializers = [];
let _get_inertia_decorators;
let _get_mode_decorators;
let _layerMask_decorators;
let _layerMask_initializers = [];
let _layerMask_extraInitializers = [];
let _fovMode_decorators;
let _fovMode_initializers = [];
let _fovMode_extraInitializers = [];
let _cameraRigMode_decorators;
let _cameraRigMode_initializers = [];
let _cameraRigMode_extraInitializers = [];
let _interaxialDistance_decorators;
let _interaxialDistance_initializers = [];
let _interaxialDistance_extraInitializers = [];
let _isStereoscopicSideBySide_decorators;
let _isStereoscopicSideBySide_initializers = [];
let _isStereoscopicSideBySide_extraInitializers = [];
let _ignoreCameraMaxZ_decorators;
let _ignoreCameraMaxZ_initializers = [];
let _ignoreCameraMaxZ_extraInitializers = [];
return _a = class Camera extends _classSuper {
/**
* Define the current local position of the camera in the scene
*/
get position() {
return this._position;
}
set position(newPosition) {
this._position = newPosition;
}
/**
* The vector the camera should consider as up.
* (default is Vector3(0, 1, 0) aka Vector3.Up())
*/
set upVector(vec) {
this._upVector = vec;
}
get upVector() {
return this._upVector;
}
/**
* The screen area in scene units squared
*/
get screenArea() {
let x;
let y;
if (this.mode === _a.PERSPECTIVE_CAMERA) {
if (this.fovMode === _a.FOVMODE_VERTICAL_FIXED) {
y = this.minZ * 2 * Math.tan(this.fov / 2);
x = this.getEngine().getAspectRatio(this) * y;
}
else {
x = this.minZ * 2 * Math.tan(this.fov / 2);
y = x / this.getEngine().getAspectRatio(this);
}
}
else {
const halfWidth = this.getEngine().getRenderWidth() / 2.0;
const halfHeight = this.getEngine().getRenderHeight() / 2.0;
x = (this.orthoRight ?? halfWidth) - (this.orthoLeft ?? -halfWidth);
y = (this.orthoTop ?? halfHeight) - (this.orthoBottom ?? -halfHeight);
}
return x * y;
}
/**
* Define the current limit on the left side for an orthographic camera
* In scene unit
*/
set orthoLeft(value) {
this._orthoLeft = value;
for (const rigCamera of this._rigCameras) {
rigCamera.orthoLeft = value;
}
}
get orthoLeft() {
return this._orthoLeft;
}
/**
* Define the current limit on the right side for an orthographic camera
* In scene unit
*/
set orthoRight(value) {
this._orthoRight = value;
for (const rigCamera of this._rigCameras) {
rigCamera.orthoRight = value;
}
}
get orthoRight() {
return this._orthoRight;
}
/**
* Define the current limit on the bottom side for an orthographic camera
* In scene unit
*/
set orthoBottom(value) {
this._orthoBottom = value;
for (const rigCamera of this._rigCameras) {
rigCamera.orthoBottom = value;
}
}
get orthoBottom() {
return this._orthoBottom;
}
/**
* Define the current limit on the top side for an orthographic camera
* In scene unit
*/
set orthoTop(value) {
this._orthoTop = value;
for (const rigCamera of this._rigCameras) {
rigCamera.orthoTop = value;
}
}
get orthoTop() {
return this._orthoTop;
}
/**
* Sets the camera's field of view in radians based on the focal length and sensor size.
* @param value the focal length of the camera in mm.
* @param sensorSize the sensor width size of the camera in mm. (default is 36mm, which is a full frame sensor)
*/
setFocalLength(value, sensorSize = 36) {
this.fov = 2 * Math.atan(sensorSize / (2 * value));
}
/**
* Define the default inertia of the camera.
* This helps giving a smooth feeling to the camera movement.
*/
get inertia() {
return this._baseInertia;
}
set inertia(value) {
this._baseInertia = value;
}
/**
* Define the mode of the camera (Camera.PERSPECTIVE_CAMERA or Camera.ORTHOGRAPHIC_CAMERA)
*/
set mode(mode) {
this._mode = mode;
// Pass the mode down to the rig cameras
for (const rigCamera of this._rigCameras) {
rigCamera.mode = mode;
}
}
get mode() {
return this._mode;
}
/**
* Gets a flag indicating that the camera has moved in some way since the last call to Camera.update()
*/
get hasMoved() {
return this._hasMoved;
}
/**
* Instantiates a new camera object.
* This should not be used directly but through the inherited cameras: ArcRotate, Free...
* @see https://doc.babylonjs.com/features/featuresDeepDive/cameras
* @param name Defines the name of the camera in the scene
* @param position Defines the position of the camera
* @param scene Defines the scene the camera belongs too
* @param setActiveOnSceneIfNoneActive Defines if the camera should be set as active after creation if no other camera have been defined in the scene
*/
constructor(name, position, scene, setActiveOnSceneIfNoneActive = true) {
RegisterAbstractEngineRenderPass();
super(name, scene, false);
/**
* Define the input manager associated with the camera.
*/
this.inputs = __runInitializers(this, _instanceExtraInitializers);
/** @internal */
this._position = __runInitializers(this, __position_initializers, Vector3.Zero());
this._upVector = (__runInitializers(this, __position_extraInitializers), __runInitializers(this, __upVector_initializers, Vector3.Up()));
/**
* Object containing oblique projection values (only used with ORTHOGRAPHIC_CAMERA)
*/
this.oblique = (__runInitializers(this, __upVector_extraInitializers), null);
this._orthoLeft = null;
this._orthoRight = null;
this._orthoBottom = null;
this._orthoTop = null;
/**
* Field Of View is set in Radians. (default is 0.8)
*/
this.fov = __runInitializers(this, _fov_initializers, 0.8);
/**
* Projection plane tilt around the X axis (horizontal), set in Radians. (default is 0)
* Can be used to make vertical lines in world space actually vertical on the screen.
* See https://forum.babylonjs.com/t/add-vertical-shift-to-3ds-max-exporter-babylon-cameras/17480
*/
this.projectionPlaneTilt = (__runInitializers(this, _fov_extraInitializers), __runInitializers(this, _projectionPlaneTilt_initializers, 0));
/**
* Define the minimum distance the camera can see from.
* This is important to note that the depth buffer are not infinite and the closer it starts
* the more your scene might encounter depth fighting issue.
*/
this.minZ = (__runInitializers(this, _projectionPlaneTilt_extraInitializers), __runInitializers(this, _minZ_initializers, 1));
/**
* Define the maximum distance the camera can see to. (default is 10000)
* This is important to note that the depth buffer are not infinite and the further it end
* the more your scene might encounter depth fighting issue.
*/
this.maxZ = (__runInitializers(this, _minZ_extraInitializers), __runInitializers(this, _maxZ_initializers, 10000.0));
this._baseInertia = (__runInitializers(this, _maxZ_extraInitializers), 0.9);
this._mode = _a.PERSPECTIVE_CAMERA;
/**
* Define whether the camera is intermediate.
* This is useful to not present the output directly to the screen in case of rig without post process for instance
*/
this.isIntermediate = false;
/**
* Define the viewport of the camera.
* This correspond to the portion of the screen the camera will render to in normalized 0 to 1 unit.
*/
this.viewport = new Viewport(0, 0, 1.0, 1.0);
/**
* Restricts the camera to viewing objects with the same layerMask.
* A camera with a layerMask of 1 will render mesh.layerMask & camera.layerMask!== 0
*/
this.layerMask = __runInitializers(this, _layerMask_initializers, 0x0fffffff);
/**
* fovMode sets the camera frustum bounds to the viewport bounds. (default is FOVMODE_VERTICAL_FIXED)
*/
this.fovMode = (__runInitializers(this, _layerMask_extraInitializers), __runInitializers(this, _fovMode_initializers, _a.FOVMODE_VERTICAL_FIXED));
/**
* Rig mode of the camera.
* This is useful to create the camera with two "eyes" instead of one to create VR or stereoscopic scenes.
* This is normally controlled byt the camera themselves as internal use.
*/
this.cameraRigMode = (__runInitializers(this, _fovMode_extraInitializers), __runInitializers(this, _cameraRigMode_initializers, _a.RIG_MODE_NONE));
/**
* Defines the distance between both "eyes" in case of a RIG
*/
this.interaxialDistance = (__runInitializers(this, _cameraRigMode_extraInitializers), __runInitializers(this, _interaxialDistance_initializers, void 0));
/**
* Defines if stereoscopic rendering is done side by side or over under.
*/
this.isStereoscopicSideBySide = (__runInitializers(this, _interaxialDistance_extraInitializers), __runInitializers(this, _isStereoscopicSideBySide_initializers, void 0));
/**
* Ignores camera maxZ when computing the projection matrix (ie. use 0 instead of maxZ), meaning objects won't be culled by the far plane
*/
this.ignoreCameraMaxZ = (__runInitializers(this, _isStereoscopicSideBySide_extraInitializers), __runInitializers(this, _ignoreCameraMaxZ_initializers, false));
/**
* Defines the list of custom render target which are rendered to and then used as the input to this camera's render. Eg. display another camera view on a TV in the main scene
* This is pretty helpful if you wish to make a camera render to a texture you could reuse somewhere
* else in the scene. (Eg. security camera)
*
* To change the final output target of the camera, camera.outputRenderTarget should be used instead (eg. webXR renders to a render target corresponding to an HMD)
*/
this.customRenderTargets = (__runInitializers(this, _ignoreCameraMaxZ_extraInitializers), []);
/**
* When set, the camera will render to this render target instead of the default canvas
*
* If the desire is to use the output of a camera as a texture in the scene consider using camera.customRenderTargets instead
*/
this.outputRenderTarget = null;
/**
* Observable triggered when the camera view matrix has changed.
* Beware of reentrance! Some methods like Camera.getViewMatrix and Camera.getWorldMatrix can trigger the onViewMatrixChangedObservable
* observable, so using them inside an observer will require additional logic to avoid a stack overflow error.
*/
this.onViewMatrixChangedObservable = new Observable();
/**
* Observable triggered when the camera Projection matrix has changed.
*/
this.onProjectionMatrixChangedObservable = new Observable();
/**
* Observable triggered when the inputs have been processed.
*/
this.onAfterCheckInputsObservable = new Observable();
/**
* Observable triggered when reset has been called and applied to the camera.
*/
this.onRestoreStateObservable = new Observable();
/**
* Is this camera a part of a rig system?
*/
this.isRigCamera = false;
this._hasMoved = false;
/** @internal */
this._rigCameras = new Array();
/** @internal */
this._skipRendering = false;
/** @internal */
this._projectionMatrix = new Matrix();
/** @internal */
this._postProcesses = new Array();
/** @internal */
this._activeMeshes = new SmartArray(256);
this._globalPosition = Vector3.Zero();
/** @internal */
this._computedViewMatrix = Matrix.Identity();
this._doNotComputeProjectionMatrix = false;
this._transformMatrix = Matrix.Zero();
this._refreshFrustumPlanes = true;
this._absoluteRotation = Quaternion.Identity();
/** @internal */
this._isCamera = true;
/** @internal */
this._isLeftCamera = false;
/** @internal */
this._isRightCamera = false;
this.layerMask = this.getScene().defaultCameraLayerMask;
this.getScene().addCamera(this);
if (setActiveOnSceneIfNoneActive && !this.getScene().activeCamera) {
this.getScene().activeCamera = this;
}
this.position = position;
this.renderPassId = this.getScene().getEngine().createRenderPassId(`Camera ${name}`);
}
/**
* Store current camera state (fov, position, etc..)
* @returns the camera
*/
storeState() {
this._stateStored = true;
this._storedFov = this.fov;
return this;
}
/**
* Returns true if a state has been stored by calling storeState method.
* @returns true if state has been stored.
*/
hasStateStored() {
return !!this._stateStored;
}
/**
* Restores the camera state values if it has been stored. You must call storeState() first
* @returns true if restored and false otherwise
*/
_restoreStateValues() {
if (!this._stateStored) {
return false;
}
this.fov = this._storedFov;
return true;
}
/**
* Restored camera state. You must call storeState() first.
* @returns true if restored and false otherwise
*/
restoreState() {
if (this._restoreStateValues()) {
this.onRestoreStateObservable.notifyObservers(this);
return true;
}
return false;
}
/**
* Gets the class name of the camera.
* @returns the class name
*/
getClassName() {
return "Camera";
}
/**
* Gets a string representation of the camera useful for debug purpose.
* @param fullDetails Defines that a more verbose level of logging is required
* @returns the string representation
*/
toString(fullDetails) {
let ret = "Name: " + this.name;
ret += ", type: " + this.getClassName();
if (this.animations) {
for (let i = 0; i < this.animations.length; i++) {
ret += ", animation[0]: " + this.animations[i].toString(fullDetails);
}
}
return ret;
}
/**
* Automatically tilts the projection plane, using `projectionPlaneTilt`, to correct the perspective effect on vertical lines.
*/
applyVerticalCorrection() {
const rot = this.absoluteRotation.toEulerAngles();
this.projectionPlaneTilt = this._scene.useRightHandedSystem ? -rot.x : rot.x;
}
/**
* Gets the current world space position of the camera.
*/
get globalPosition() {
return this._globalPosition;
}
/**
* Gets the list of active meshes this frame (meshes no culled or excluded by lod s in the frame)
* @returns the active meshe list
*/
getActiveMeshes() {
return this._activeMeshes;
}
/**
* Check whether a mesh is part of the current active mesh list of the camera
* @param mesh Defines the mesh to check
* @returns true if active, false otherwise
*/
isActiveMesh(mesh) {
return this._activeMeshes.indexOf(mesh) !== -1;
}
/**
* Is this camera ready to be used/rendered
* @param completeCheck defines if a complete check (including post processes) has to be done (false by default)
* @returns true if the camera is ready
*/
isReady(completeCheck = false) {
if (completeCheck) {
for (const pp of this._postProcesses) {
if (pp && !pp.isReady()) {
return false;
}
}
}
return super.isReady(completeCheck);
}
/** @internal */
_initCache() {
super._initCache();
this._cache.position = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
this._cache.upVector = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
this._cache.mode = undefined;
this._cache.minZ = undefined;
this._cache.maxZ = undefined;
this._cache.fov = undefined;
this._cache.fovMode = undefined;
this._cache.aspectRatio = undefined;
this._cache.orthoLeft = undefined;
this._cache.orthoRight = undefined;
this._cache.orthoBottom = undefined;
this._cache.orthoTop = undefined;
this._cache.obliqueAngle = undefined;
this._cache.obliqueLength = undefined;
this._cache.obliqueOffset = undefined;
this._cache.renderWidth = undefined;
this._cache.renderHeight = undefined;
}
/**
* @internal
*/
_updateCache(ignoreParentClass) {
if (!ignoreParentClass) {
super._updateCache();
}
this._cache.position.copyFrom(this.position);
this._cache.upVector.copyFrom(this.upVector);
}
/** @internal */
_isSynchronized() {
return this._isSynchronizedViewMatrix() && this._isSynchronizedProjectionMatrix();
}
/** @internal */
_isSynchronizedViewMatrix() {
if (!super._isSynchronized()) {
return false;
}
return this._cache.position.equals(this.position) && this._cache.upVector.equals(this.upVector) && this.isSynchronizedWithParent();
}
/** @internal */
_isSynchronizedProjectionMatrix() {
const maxZ = this.ignoreCameraMaxZ ? 0 : this.maxZ;
let isSynchronized = this._cache.mode === this.mode && this._cache.minZ === this.minZ && this._cache.maxZ === maxZ;
if (!isSynchronized) {
return false;
}
const engine = this.getEngine();
if (this.mode === _a.PERSPECTIVE_CAMERA) {
isSynchronized =
this._cache.fov === this.fov &&
this._cache.fovMode === this.fovMode &&
this._cache.aspectRatio === engine.getAspectRatio(this) &&
this._cache.projectionPlaneTilt === this.projectionPlaneTilt;
}
else {
isSynchronized =
this._cache.orthoLeft === this.orthoLeft &&
this._cache.orthoRight === this.orthoRight &&
this._cache.orthoBottom === this.orthoBottom &&
this._cache.orthoTop === this.orthoTop &&
this._cache.renderWidth === engine.getRenderWidth() &&
this._cache.renderHeight === engine.getRenderHeight();
if (this.oblique) {
isSynchronized =
isSynchronized &&
this._cache.obliqueAngle === this.oblique.angle &&
this._cache.obliqueLength === this.oblique.length &&
this._cache.obliqueOffset === this.oblique.offset;
}
}
return isSynchronized;
}
/**
* Attach the input controls to a specific dom element to get the input from.
* This function is here because typescript removes the typing of the last function.
* @param _ignored defines an ignored parameter kept for backward compatibility.
* @param _noPreventDefault Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
*/
attachControl(_ignored, _noPreventDefault) { }
/**
* Detach the current controls from the specified dom element.
* This function is here because typescript removes the typing of the last function.
* @param _ignored defines an ignored parameter kept for backward compatibility.
*/
detachControl(_ignored) { }
/**
* Update the camera state according to the different inputs gathered during the frame.
*/
update() {
this._hasMoved = false;
this._checkInputs();
if (this.cameraRigMode !== _a.RIG_MODE_NONE) {
this._updateRigCameras();
}
// Attempt to update the camera's view and projection matrices.
// This call is being made because these matrices are no longer being updated
// as a part of the picking ray process (in addition to scene.render).
this.getViewMatrix();
this.getProjectionMatrix();
}
/** @internal */
_checkInputs() {
this.onAfterCheckInputsObservable.notifyObservers(this);
}
/** @internal */
get rigCameras() {
return this._rigCameras;
}
/**
* Gets the post process used by the rig cameras
*/
get rigPostProcess() {
return this._rigPostProcess;
}
/**
* Internal, gets the first post process.
* @returns the first post process to be run on this camera.
*/
_getFirstPostProcess() {
for (let ppIndex = 0; ppIndex < this._postProcesses.length; ppIndex++) {
if (this._postProcesses[ppIndex] !== null) {
return this._postProcesses[ppIndex];
}
}
return null;
}
_cascadePostProcessesToRigCams() {
// invalidate framebuffer
const firstPostProcess = this._getFirstPostProcess();
if (firstPostProcess) {
firstPostProcess.markTextureDirty();
}
// glue the rigPostProcess to the end of the user postprocesses & assign to each sub-camera
for (let i = 0, len = this._rigCameras.length; i < len; i++) {
const cam = this._rigCameras[i];
const rigPostProcess = cam._rigPostProcess;
// for VR rig, there does not have to be a post process
if (rigPostProcess) {
const isPass = rigPostProcess.getEffectName() === "pass";
if (isPass) {
// any rig which has a PassPostProcess for rig[0], cannot be isIntermediate when there are also user postProcesses
cam.isIntermediate = this._postProcesses.length === 0;
}
cam._postProcesses = this._postProcesses.slice(0).concat(rigPostProcess);
rigPostProcess.markTextureDirty();
}
else {
cam._postProcesses = this._postProcesses.slice(0);
}
}
}
/**
* Attach a post process to the camera.
* @see https://doc.babylonjs.com/features/featuresDeepDive/postProcesses/usePostProcesses#attach-postprocess
* @param postProcess The post process to attach to the camera
* @param insertAt The position of the post process in case several of them are in use in the scene
* @returns the position the post process has been inserted at
*/
attachPostProcess(postProcess, insertAt = null) {
if (!postProcess.isReusable() && this._postProcesses.indexOf(postProcess) > -1) {
Logger.Error("You're trying to reuse a post process not defined as reusable.");
return 0;
}
if (insertAt == null || insertAt < 0) {
this._postProcesses.push(postProcess);
}
else if (this._postProcesses[insertAt] === null) {
this._postProcesses[insertAt] = postProcess;
}
else {
this._postProcesses.splice(insertAt, 0, postProcess);
}
this._cascadePostProcessesToRigCams(); // also ensures framebuffer invalidated
// Update prePass
if (this._scene.prePassRenderer) {
this._scene.prePassRenderer.markAsDirty();
}
return this._postProcesses.indexOf(postProcess);
}
/**
* Detach a post process to the camera.
* @see https://doc.babylonjs.com/features/featuresDeepDive/postProcesses/usePostProcesses#attach-postprocess
* @param postProcess The post process to detach from the camera
*/
detachPostProcess(postProcess) {
const idx = this._postProcesses.indexOf(postProcess);
if (idx !== -1) {
this._postProcesses[idx] = null;
}
// Update prePass
if (this._scene.prePassRenderer) {
this._scene.prePassRenderer.markAsDirty();
}
this._cascadePostProcessesToRigCams(); // also ensures framebuffer invalidated
}
/**
* Gets the current world matrix of the camera
* @returns the world matrix
*/
getWorldMatrix() {
if (this._isSynchronizedViewMatrix()) {
return this._worldMatrix;
}
// Getting the view matrix will also compute the world matrix.
this.getViewMatrix();
return this._worldMatrix;
}
/** @internal */
_getViewMatrix() {
return Matrix.Identity();
}
/**
* Gets the current view matrix of the camera.
* @param force forces the camera to recompute the matrix without looking at the cached state
* @returns the view matrix
*/
getViewMatrix(force) {
if (!force && this._isSynchronizedViewMatrix()) {
return this._computedViewMatrix;
}
this._hasMoved = true;
this.updateCache();
this._computedViewMatrix = this._getViewMatrix();
this._currentRenderId = this.getScene().getRenderId();
this._childUpdateId++;
this._refreshFrustumPlanes = true;
if (this._cameraRigParams && this._cameraRigParams.vrPreViewMatrix) {
this._computedViewMatrix.multiplyToRef(this._cameraRigParams.vrPreViewMatrix, this._computedViewMatrix);
}
// Notify parent camera if rig camera is changed
if (this.parent && this.parent.onViewMatrixChangedObservable) {
this.parent.onViewMatrixChangedObservable.notifyObservers(this.parent);
}
this.onViewMatrixChangedObservable.notifyObservers(this);
this._computedViewMatrix.invertToRef(this._worldMatrix);
this._worldMatrix.getTranslationToRef(this._globalPosition);
return this._computedViewMatrix;
}
/**
* Freeze the projection matrix.
* It will prevent the cache check of the camera projection compute and can speed up perf
* if no parameter of the camera are meant to change
* @param projection Defines manually a projection if necessary
*/
freezeProjectionMatrix(projection) {
this._doNotComputeProjectionMatrix = true;
if (projection !== undefined) {
this._projectionMatrix = projection;
}
}
/**
* Unfreeze the projection matrix if it has previously been freezed by freezeProjectionMatrix.
*/
unfreezeProjectionMatrix() {
this._doNotComputeProjectionMatrix = false;
}
/**
* Gets the current projection matrix of the camera.
* @param force forces the camera to recompute the matrix without looking at the cached state
* @returns the projection matrix
*/
getProjectionMatrix(force) {
if (this._doNotComputeProjectionMatrix || (!force && this._isSynchronizedProjectionMatrix())) {
return this._projectionMatrix;
}
const maxZ = this.ignoreCameraMaxZ ? 0 : this.maxZ;
// Cache
this._cache.mode = this.mode;
this._cache.minZ = this.minZ;
this._cache.maxZ = maxZ;
// Matrix
this._refreshFrustumPlanes = true;
const engine = this.getEngine();
const scene = this.getScene();
const reverseDepth = engine.useReverseDepthBuffer;
if (this.mode === _a.PERSPECTIVE_CAMERA) {
this._cache.fov = this.fov;
this._cache.fovMode = this.fovMode;
this._cache.aspectRatio = engine.getAspectRatio(this);
this._cache.projectionPlaneTilt = this.projectionPlaneTilt;
if (this.minZ <= 0) {
this.minZ = 0.1;
}
let getProjectionMatrix;
if (scene.useRightHandedSystem) {
getProjectionMatrix = Matrix.PerspectiveFovRHToRef;
}
else {
getProjectionMatrix = Matrix.PerspectiveFovLHToRef;
}
getProjectionMatrix(this.fov, engine.getAspectRatio(this), reverseDepth ? maxZ : this.minZ, reverseDepth ? this.minZ : maxZ, this._projectionMatrix, this.fovMode === _a.FOVMODE_VERTICAL_FIXED, engine.isNDCHalfZRange, this.projectionPlaneTilt, reverseDepth);
}
else {
const halfWidth = engine.getRenderWidth() / 2.0;
const halfHeight = engine.getRenderHeight() / 2.0;
if (scene.useRightHandedSystem) {
if (this.oblique) {
Matrix.ObliqueOffCenterRHToRef(this.orthoLeft ?? -halfWidth, this.orthoRight ?? halfWidth, this.orthoBottom ?? -halfHeight, this.orthoTop ?? halfHeight, reverseDepth ? maxZ : this.minZ, reverseDepth ? this.minZ : maxZ, this.oblique.length, this.oblique.angle, this._computeObliqueDistance(this.oblique.offset), this._projectionMatrix, engine.isNDCHalfZRange);
}
else {
Matrix.OrthoOffCenterRHToRef(this.orthoLeft ?? -halfWidth, this.orthoRight ?? halfWidth, this.orthoBottom ?? -halfHeight, this.orthoTop ?? halfHeight, reverseDepth ? maxZ : this.minZ, reverseDepth ? this.minZ : maxZ, this._projectionMatrix, engine.isNDCHalfZRange);
}
}
else {
if (this.oblique) {
Matrix.ObliqueOffCenterLHToRef(this.orthoLeft ?? -halfWidth, this.orthoRight ?? halfWidth, this.orthoBottom ?? -halfHeight, this.orthoTop ?? halfHeight, reverseDepth ? maxZ : this.minZ, reverseDepth ? this.minZ : maxZ, this.oblique.length, this.oblique.angle, this._computeObliqueDistance(this.oblique.offset), this._projectionMatrix, engine.isNDCHalfZRange);
}
else {
Matrix.OrthoOffCenterLHToRef(this.orthoLeft ?? -halfWidth, this.orthoRight ?? halfWidth, this.orthoBottom ?? -halfHeight, this.orthoTop ?? halfHeight, reverseDepth ? maxZ : this.minZ, reverseDepth ? this.minZ : maxZ, this._projectionMatrix, engine.isNDCHalfZRange);
}
}
this._cache.orthoLeft = this.orthoLeft;
this._cache.orthoRight = this.orthoRight;
this._cache.orthoBottom = this.orthoBottom;
this._cache.orthoTop = this.orthoTop;
this._cache.obliqueAngle = this.oblique?.angle;
this._cache.obliqueLength = this.oblique?.length;
this._cache.obliqueOffset = this.oblique?.offset;
this._cache.renderWidth = engine.getRenderWidth();
this._cache.renderHeight = engine.getRenderHeight();
}
this.onProjectionMatrixChangedObservable.notifyObservers(this);
return this._projectionMatrix;
}
/**
* Gets the transformation matrix (ie. the multiplication of view by projection matrices)
* @returns a Matrix
*/
getTransformationMatrix() {
this._computedViewMatrix.multiplyToRef(this._projectionMatrix, this._transformMatrix);
return this._transformMatrix;
}
_computeObliqueDistance(offset) {
const arcRotateCamera = this;
const targetCamera = this;
return (arcRotateCamera.radius || (targetCamera.target ? Vector3.Distance(this.position, targetCamera.target) : this.position.length())) + offset;
}
/** @internal */
_updateFrustumPlanes() {
if (!this._refreshFrustumPlanes) {
return;
}
this.getTransformationMatrix();
if (!this._frustumPlanes) {
this._frustumPlanes = Frustum.GetPlanes(this._transformMatrix);
}
else {
Frustum.GetPlanesToRef(this._transformMatrix, this._frustumPlanes);
}
this._refreshFrustumPlanes = false;
}
/**
* Checks if a cullable object (mesh...) is in the camera frustum
* This checks the bounding box center. See isCompletelyInFrustum for a full bounding check
* @param target The object to check
* @param checkRigCameras If the rig cameras should be checked (eg. with VR camera both eyes should be checked) (Default: false)
* @returns true if the object is in frustum otherwise false
*/
isInFrustum(target, checkRigCameras = false) {
this._updateFrustumPlanes();
if (checkRigCameras && this.rigCameras.length > 0) {
let result = false;
for (const cam of this.rigCameras) {
cam._updateFrustumPlanes();
result = result || target.isInFrustum(cam._frustumPlanes);
}
return result;
}
else {
return target.isInFrustum(this._frustumPlanes);
}
}
/**
* Checks if a cullable object (mesh...) is in the camera frustum
* Unlike isInFrustum this checks the full bounding box
* @param target The object to check
* @returns true if the object is in frustum otherwise false
*/
isCompletelyInFrustum(target) {
this._updateFrustumPlanes();
return target.isCompletelyInFrustum(this._frustumPlanes);
}
// eslint-disable-next-line jsdoc/require-returns-check
/**
* Gets a ray in the forward direction from the camera.
* @param length Defines the length of the ray to create
* @param transform Defines the transform to apply to the ray, by default the world matrix is used to create a world space ray
* @param origin Defines the start point of the ray which defaults to the camera position
* @returns the forward ray
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getForwardRay(length = 100, transform, origin) {
throw _WarnImport("Ray");
}
// eslint-disable-next-line jsdoc/require-returns-check
/**
* Gets a ray in the forward direction from the camera.
* @param refRay the ray to (re)use when setting the values
* @param length Defines the length of the ray to create
* @param transform Defines the transform to apply to the ray, by default the world matrix is used to create a world space ray
* @param origin Defines the start point of the ray which defaults to the camera position
* @returns the forward ray
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getForwardRayToRef(refRay, length = 100, transform, origin) {
throw _WarnImport("Ray");
}
/**
* Releases resources associated with this node.
* @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
* @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
*/
dispose(doNotRecurse, disposeMaterialAndTextures = false) {
// Observables
this.onViewMatrixChangedObservable.clear();
this.onProjectionMatrixChangedObservable.clear();
this.onAfterCheckInputsObservable.clear();
this.onRestoreStateObservable.clear();
// Inputs
if (this.inputs) {
this.inputs.clear();
}
// Animations
this.getScene().stopAnimation(this);
// Remove from scene
this.getScene().removeCamera(this);
while (this._rigCameras.length > 0) {
const camera = this._rigCameras.pop();
if (camera) {
camera.dispose();
}
}
if (this._parentContainer) {
const index = this._parentContainer.cameras.indexOf(this);
if (index > -1) {
this._parentContainer.cameras.splice(index, 1);
}
this._parentContainer = null;
}
// Postprocesses
if (this._rigPostProcess) {
this._rigPostProcess.dispose(this);
this._rigPostProcess = null;
this._postProcesses.length = 0;
}
else if (this.cameraRigMode !== _a.RIG_MODE_NONE) {
this._rigPostProcess = null;
this._postProcesses.length = 0;
}
else {
let i = this._postProcesses.length;
while (--i >= 0) {
const postProcess = this._postProcesses[i];
if (postProcess) {
postProcess.dispose(this);
}
}
}
// Render targets
let i = this.customRenderTargets.length;
while (--i >= 0) {
this.customRenderTargets[i].dispose();
}
this.customRenderTargets.length = 0;
// Active Meshes
this._activeMeshes.dispose();
this.getScene().getEngine().releaseRenderPassId(this.renderPassId);
super.dispose(doNotRecurse, disposeMaterialAndTextures);
}
/**
* Gets the left camera of a rig setup in case of Rigged Camera
*/
get isLeftCamera() {
return this._isLeftCamera;
}
/**
* Gets the right camera of a rig setup in case of Rigged Camera
*/
get isRightCamera() {
return this._isRightCamera;
}
/**
* Gets the left camera of a rig setup in case of Rigged Camera
*/
get leftCamera() {
if (this._rigCameras.length < 1) {
return null;
}
return this._rigCameras[0];
}
/**
* Gets the right camera of a rig setup in case of Rigged Camera
*/
get rightCamera() {
if (this._rigCameras.length < 2) {
return null;
}
return this._rigCameras[1];
}
/**
* Gets the left camera target of a rig setup in case of Rigged Camera
* @returns the target position
*/
getLeftTarget() {
if (this._rigCameras.length < 1) {
return null;
}
return this._rigCameras[0].getTarget();
}
/**
* Gets the right camera target of a rig setup in case of Rigged Camera
* @returns the target position
*/
getRightTarget() {
if (this._rigCameras.length < 2) {
return null;
}
return this._rigCameras[1].getTarget();
}
/**
* @internal
*/
setCameraRigMode(mode, rigParams) {
if (this.cameraRigMode === mode) {
return;
}
while (this._rigCameras.length > 0) {
const camera = this._rigCameras.pop();
if (camera) {
camera.dispose();
}
}
this.cameraRigMode = mode;
this._cameraRigParams = {};
//we have to implement stereo camera calcultating left and right viewpoints from interaxialDistance and target,
//not from a given angle as it is now, but until that complete code rewriting provisional stereoHalfAngle value is introduced
this._cameraRigParams.interaxialDistance = rigParams.interaxialDistance || 0.0637;
this._cameraRigParams.stereoHalfAngle = (this._cameraRigParams.interaxialDistance / 0.0637) * (Math.PI / 180);
// create the rig cameras, unless none
if (this.cameraRigMode !== _a.RIG_MODE_NONE) {
const leftCamera = this.createRigCamera(this.name + "_L", 0);
if (leftCamera) {
leftCamera._isLeftCamera = true;
}
const rightCamera = this.createRigCamera(this.name + "_R", 1);
if (rightCamera) {
rightCamera._isRightCamera = true;
}
if (leftCamera && rightCamera) {
this._rigCameras.push(leftCamera);
this._rigCameras.push(rightCamera);
}
}
this._setRigMode(rigParams);
this._cascadePostProcessesToRigCams();
this.update();
}
// eslint-disable-next-line @typescript-eslint/no-unused-var