import { StringEncoder, Flags } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { MathUtils } from '../math/MathUtils';
import { CameraTransformComponent } from '../components/CameraTransformComponent';
import { CameraLerpComponent } from '../components/CameraLerpComponent';
import { CameraHelperComponent } from '../components/CameraHelperComponent';
import { CameraBoundingComponent } from '../components/CameraBoundingComponent';
import { CameraEffectComponent } from '../components/CameraEffectComponent';
import { CameraPostEffectComponent } from '../components/CameraPostEffectComponent';
import { CameraFogComponent } from '../components/CameraFogComponent';
import { CameraPickerComponent } from '../components/CameraPickerComponent';
import { CameraControlComponent } from '../components/CameraControlComponent';
import { CameraControlEventDispatcherComponent } from '../components/CameraControlEventDispatcherComponent';
import { CameraResourceControllerComponent } from '../components/CameraResourceControllerComponent';
import { RenderTexture } from '../resources/RenderTexture';
import { Object3D } from './Object3D';
import { NodeUserDataType, InheritType, ProjectionType, ViewMode, LerpType, EventType } from '../const';
import { CameraDynamicSkyComponent } from '../components/CameraDynamicSkyComponent';
const _renderOverlayFuncName = StringEncoder.toText("<@secret renderOverlay>");
const _ClearColorBuffer = StringEncoder.toText("<@secret ClearColorBuffer>");
const __ = {
private: Symbol('private'),
}
const Flag = {
Created: 1 << 0,
EnableViewport: 1 << 1,
Ticking: 1 << 2
};
const registerComponentParam = { isResident: true };
/**
* @class Camera
* The camera object.
* @memberof THING
* @extends THING.Object3D
* @public
*/
class Camera extends Object3D {
/**
* The camera object in scene.
* @param {Object} param The initial parameters.
*/
constructor(param = {}) {
super(param);
this[__.private] = {};
let _private = this[__.private];
_private.flags = new Flags();
_private.projectionType = ProjectionType.Perspective;
_private.projectionTween = null;
_private.viewModeInfo = {
type: null,
target: null,
position: null,
lookAt: null
};
_private.viewport = null;
_private.captureRenderTexture = null;
_private.renderTexture = null;
_private.renderableRefCount = 0;
// Get default options
_private.fov = Utils.parseValue(param['fov'], 60);
_private.near = Utils.parseValue(param['near'], 0.1);
_private.far = Utils.parseValue(param['far'], 10000);
// Refresh attributes
this.onRefresh();
// Enable effect component
this.effect.enable = true;
// Disable render to viewport as default
this.enableViewport = false;
// Hook control event dispatcher
this.controlEventDispatcher.enable = true;
// Resize
let size = this.app.size;
this.resize(size[0], size[1]);
// Set default background color
this.background = 0xFFFFFF;
// Setup completed
_private.flags.enable(Flag.Created, true);
}
// #region Private
/**
* Get position, target and up for view mode in perspective
* @param {ViewMode} viewMode The view mode type.
* @param {THING.Object3D} target The target object.
* @returns {Object}
* @private
*/
_getLocationOfViewModeInPerspective(viewMode, target) {
let lookAt, position, up;
let boundingBox = target.boundingBox;
let distance = -(boundingBox.radius / 0.5) / (2 * Math.tan(THING.Math.degToRad(this.fov) / 2));
switch (viewMode) {
case ViewMode.Top:
lookAt = boundingBox.center;
position = MathUtils.addVector(lookAt, [0, -distance, 0.01]);
up = [0, 0, -1];
break;
case ViewMode.Bottom:
lookAt = boundingBox.center;
position = MathUtils.addVector(lookAt, [0, distance, 0.01]);
up = [0, 0, 1];
break;
case ViewMode.Left:
lookAt = boundingBox.center;
position = MathUtils.addVector(lookAt, [-distance, 0, 0]);
up = [0, 1, 0];
break;
case ViewMode.Right:
lookAt = boundingBox.center;
position = MathUtils.addVector(lookAt, [distance, 0, 0]);
up = [0, 1, 0];
break;
case ViewMode.Front:
lookAt = boundingBox.center;
position = MathUtils.addVector(lookAt, [0, 0, -distance]);
up = [0, 1, 0];
break;
case ViewMode.Back:
lookAt = boundingBox.center;
position = MathUtils.addVector(lookAt, [0, 0, distance]);
up = [0, 1, 0];
break;
default:
return;
}
return { lookAt, position, up };
}
/**
* Get position, target and up for view mode in orthographic
* @param {ViewMode} viewMode The view mode type.
* @param {THING.Object3D} target The target object.
* @returns {Object}
* @private
*/
_getLocationOfViewModeInOrthographic(viewMode, target) {
let { lookAt, position, up } = this._getLocationOfViewModeInPerspective(viewMode, target);
let z = MathUtils.getDistance(position, lookAt);
let depth = Math.tan(this.fov / 2.0 * Math.PI / 180.0) * 2.0;
let distance = depth * z * 0.5;
let direction = MathUtils.getDirection(position, lookAt);
lookAt = MathUtils.getPositionOnDirection(position, direction, distance);
return { lookAt, position, up, distance };
}
/**
* Get position, target and up for view mode
* @param {ViewMode} viewMode The view mode type.
* @param {THING.Object3D} target The target object.
* @returns {Object}
* @private
*/
_getLocationOfViewMode(viewMode, target) {
let _private = this[__.private];
switch (_private.projectionType) {
case ProjectionType.Orthographic:
return this._getLocationOfViewModeInOrthographic(viewMode, target);
case ProjectionType.Perspective:
return this._getLocationOfViewModeInPerspective(viewMode, target);
default:
return null;
}
}
_setViewMode(target, viewMode, duration) {
let _private = this[__.private];
let viewModeInfo = _private.viewModeInfo;
viewModeInfo.type = viewMode;
viewModeInfo.target = target;
viewModeInfo.position = viewModeInfo.position || this.position;
viewModeInfo.lookAt = viewModeInfo.lookAt || this.target;
// Enable view mode
if (viewMode) {
let { lookAt, position, up, distance } = this._getLocationOfViewMode(viewMode, target);
// Start to lerp
this.flyTo({
target: lookAt,
position,
up,
duration,
});
// Update distance for ortho
if (distance !== undefined) {
this.orthoDistance = distance;
}
}
// Clear view mode
else {
let lookAt, position;
if (viewModeInfo.position) {
position = viewModeInfo.position;
viewModeInfo.position = null;
}
if (viewModeInfo.lookAt) {
lookAt = viewModeInfo.lookAt;
viewModeInfo.lookAt = null;
}
// Start to lerp
this.flyTo({
target: lookAt,
position,
up: [0, 1, 0],
duration,
});
}
}
_updateRenderableRefCount(value) {
let _private = this[__.private];
let scene = this.app.scene;
if (value) {
_private.renderableRefCount++;
scene.addRenderCamera(this);
}
else {
_private.renderableRefCount--;
if (_private.renderableRefCount) {
return;
}
scene.removeRenderCamera(this);
}
}
_getSize(width, height) {
let currentSize = [];
this.node.getSize(currentSize);
return [
Utils.parseValue(width, currentSize[0]),
Utils.parseValue(height, currentSize[1]),
];
}
// #endregion
// #region Common
get created() {
let _private = this[__.private];
if (!_private) {
return false;
}
return _private.flags.has(Flag.Created);
}
// #endregion
// #region BaseObject Interface
onBeforeSetup(param) {
param['renderableNode'] = param['renderableNode'] || Utils.createObject('Camera');
super.onBeforeSetup(param);
}
onSetupComponent(param) {
super.onSetupComponent(param);
this.addComponent(CameraTransformComponent, 'transform', registerComponentParam);
this.addComponent(CameraControlComponent, 'control', registerComponentParam);
this.addComponent(CameraLerpComponent, 'lerp', registerComponentParam);
this.addComponent(CameraHelperComponent, 'helper', registerComponentParam);
this.addComponent(CameraBoundingComponent, 'bounding', registerComponentParam);
this.addComponent(CameraEffectComponent, 'effect', registerComponentParam);
this.addComponent(CameraPostEffectComponent, 'postEffect', registerComponentParam);
this.addComponent(CameraFogComponent, 'fog', registerComponentParam);
this.addComponent(CameraPickerComponent, 'picker', registerComponentParam);
this.addComponent(CameraControlEventDispatcherComponent, 'controlEventDispatcher', registerComponentParam);
this.addComponent(CameraDynamicSkyComponent, 'dynamicSky', registerComponentParam);
this.addComponent(CameraResourceControllerComponent, 'resourceController', registerComponentParam);
}
onRefresh() {
let _private = this[__.private];
// Update camera options
let node = this.node;
node.setFov(_private.fov);
node.setNear(_private.near);
node.setFar(_private.far);
// Never affect bounding box for camera
node.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Stop;
}
onUpdate(deltaTime) {
super.onUpdate(deltaTime);
// Get camera node
let cameraNode = this.node;
// Update camera
cameraNode.updateMatrixWorld();
cameraNode.update(deltaTime);
}
onBeforeDestroy() {
if (!super.onBeforeDestroy()) {
return false;
}
if (this.app.objectManager.keepAliveObjects.includes(this)) {
return false;
}
let scene = this.app.scene;
scene.removeRenderCamera(this);
let _private = this[__.private];
if (_private.renderTexture) {
_private.renderTexture.release();
_private.renderTexture = null;
}
return true;
}
onAddTickableObject() {
let _private = this[__.private];
// Check whether it's ticking now
if (_private.flags.has(Flag.Ticking)) {
return;
}
// The camera must update first, so we insert it to the front of tickable objects
this.app.objectManager.insertTickableObject(0, this);
// Prevent to add tickable object duplicatly
_private.flags.enable(Flag.Ticking, true);
}
onCopy(object) {
super.onCopy(object);
let viewport = [], size = [];
let node = object.node;
node.getViewport(viewport);
node.getSize(size);
this.node.setViewport(viewport);
this.node.setSize(size);
this.orthoDistance = object.orthoDistance;
this.fov = object.fov;
this.near = object.near;
this.far = object.far;
}
// #endregion
// #region Coordinate Translation
/**
* Convert world position to screen position.
* @param {Array<Number>} position The world position.
* @returns {Array<Number>} The screen position in [x, y, z] format, [z] indicates the sort order for 2D element.
* @example
* let screen0 = THING.App.current.camera.worldToScreen([100, 100, 90]);
* let screen1 = THING.App.current.camera.worldToScreen([100, 100, 100]);
* if (screen0 < screen1) {
* console.log('screen0 is closer to the camera than screen1');
* }
* @public
*/
worldToScreen(position, target = []) {
return this.node.worldToScreen(position, target);
}
/**
* Convert screen position to world position.
* @param {Array<Number>} screenPosition The screen position.
* @returns {Array<Number>}
* @example
* let position = THING.App.current.camera.screenToWorld([100, 100]);
* @public
*/
screenToWorld(screenPosition, target = []) {
return this.node.screenToWorld(screenPosition, target);
}
// #endregion
// #region Resources
onUnloadResource() {
this.control.setController(null);
super.onUnloadResource();
}
onCopyResource(object) {
this.onCopyComponents(object);
}
copy(object) {
this.onBeforeCopy(object);
this.onAfterCopy(object);
return this;
}
// #endregion
// #region Buffers
/**
* Enable/Disable auto clear color buffer.
* @type {Boolean}
* @private
*/
get autoClearColorBuffer() {
return this.node.getAttribute('ClearColorBuffer');
}
set autoClearColorBuffer(value) {
this.node.setAttribute('ClearColorBuffer', value);
}
/**
* Enable/Disable auto clear depth buffer.
* @type {Boolean}
* @private
*/
get autoClearDepthBuffer() {
return this.node.getAttribute('ClearDepthBuffer');
}
set autoClearDepthBuffer(value) {
this.node.setAttribute('ClearDepthBuffer', value);
}
/**
* Enable/Disable auto clear stencil buffer.
* @type {Boolean}
* @private
*/
get autoClearStencilBuffer() {
return this.node.getAttribute('ClearStencilBuffer');
}
set autoClearStencilBuffer(value) {
this.node.setAttribute('ClearStencilBuffer', value);
}
/**
* Capture to render texture.
* @param {Number} width The image width in pixel, if it not provide then use the current width.
* @param {Number} height The image height in pixel, if it not provide then use the current height.
* @returns {THING.RenderTexture}
* @example
* let renderTexture = THING.App.current.camera.captureToRenderTexture();
* renderTexture.download('myScreenshot');
* @public
*/
captureToRenderTexture(width, height) {
let _private = this[__.private];
let node = this.node;
let size = this._getSize(width, height);
let currentSize = [];
node.getSize(currentSize);
let currentViewport = [];
node.getViewport(currentViewport);
if (!_private.captureRenderTexture) {
_private.captureRenderTexture = new RenderTexture({ size });
}
else {
_private.captureRenderTexture.size = size;
}
let renderTexture = _private.captureRenderTexture.getTextureResource();
// We are going to capture screen
this.app.trigger(EventType.BeforeCameraCapture, { width: size[0], height: size[1] });
// Prepare to render
node.setViewport([0, 0, size[0], size[1]]);
node.setSize(size);
// Render scene
node.render(renderTexture);
// Render overlay
let clearColorBuffer = node.getAttribute(_ClearColorBuffer);
node.setAttribute(_ClearColorBuffer, false);
node[_renderOverlayFuncName](renderTexture);
node.setAttribute(_ClearColorBuffer, clearColorBuffer);
// Finish to render
node.setSize(currentSize);
node.setViewport(currentViewport);
// We have finished to capture screen
this.app.trigger(EventType.AfterCameraCapture);
return _private.captureRenderTexture;
}
/**
* Capture render result to pixel buffer in RGBA color format.
* @param {Number} width The image width in pixel, if it not provide then use the current width.
* @param {Number} height The image height in pixel, if it not provide then use the current height.
* @returns {Uint8Array}
* @example
* let data = THING.App.current.camera.captureToData(1024, 768);
* @public
*/
captureToData(width, height) {
let renderTexture = this.captureToRenderTexture(width, height);
return renderTexture.pixelBuffer;
}
/**
* Capture render result into image.
* @param {Number} width The image width in pixel, if it not provide then use the current width.
* @param {Number} height The image height in pixel, if it not provide then use the current height.
* @returns {Object}
* @example
* let image = THING.App.current.camera.captureToImage(1024, 768);
* @public
*/
captureToImage(width, height) {
let size = this._getSize(width, height);
let pixelBuffer = this.captureToData(size[0], size[1]);
return Utils.saveAsImage(size[0], size[1], pixelBuffer);
}
/**
* Capture render result to file(PNG format) in download mode.
* @param {String} fileName The file name.
* @param {Number} width The image width in pixel, if it not provide then use the current width.
* @param {Number} height The image height in pixel, if it not provide then use the current height.
* @example
* THING.App.current.camera.captureToFile('myScreenshot');
* @public
*/
captureToFile(fileName, width, height) {
let renderTexture = this.captureToRenderTexture(width, height);
renderTexture.download(fileName);
}
// #endregion
// #region Render
/**
* Resize.
* @param {Number} width The width in pixel.
* @param {Number} height The height in pixel.
* @private
*/
resize(width, height) {
let _private = this[__.private];
let node = this.node;
let viewport = _private.viewport;
if (viewport) {
node.setViewport(viewport);
node.setSize([viewport[2], viewport[3]]);
}
else {
node.setViewport([0, 0, width, height]);
node.setSize([width, height]);
}
}
/**
* Enable/Disable viewport.
* @type {Boolean}
* @example
* let camera = new THING.Camera();
* camera.enableViewport = true;
* @public
*/
get enableViewport() {
let _private = this[__.private];
return _private.flags.has(Flag.EnableViewport);
}
set enableViewport(value) {
let _private = this[__.private];
if (_private.flags.enable(Flag.EnableViewport, value)) {
this.trigger(EventType.EnableViewport, { value });
}
}
/**
* Get/Set viewport of screen [x, y, width, height].
* @type {Array<Number>}
* @example
* let camera = new THING.Camera();
* camera.viewport = [0, 0, 100, 100];
* @public
*/
get viewport() {
let _private = this[__.private];
return _private.viewport;
}
set viewport(value) {
let _private = this[__.private];
if (value) {
_private.viewport = value.slice(0);
this.node.setViewport(_private.viewport);
this.node.setSize([value[2], value[3]]);
}
else {
_private.viewport = null;
this.node.setViewport([0, 0, 0, 0]);
}
this._updateRenderableRefCount(value);
}
/**
* Get/Set render texture.
* @type {THING.RenderTexture}
* @example
* let renderTexture = new THING.RenderTexture({
* size: THING.App.current.size
* });
*
* let camera = new THING.Camera();
* camera.renderTexture = renderTexture;
* @public
*/
get renderTexture() {
let _private = this[__.private];
return _private.renderTexture;
}
set renderTexture(value) {
let _private = this[__.private];
if (_private.renderTexture) {
_private.renderTexture.release();
}
// Make sure it's render texture
if (value && !value.isRenderTexture) {
Utils.error(`Set camera render texture failed, due to value is not RenderTexture type`);
return;
}
_private.renderTexture = value;
_private.renderTexture.addRef();
this._updateRenderableRefCount(value);
}
/**
* Get projection matrix.
* @type {Array<Number>}
* @private
*/
get projectionMatrix() {
let target = [];
return this.node.getProjectionMatrix(target);
}
// #endregion
// #region Control
/**
* process adjust near
* @public
*/
processAdjustNear() {
this.control.processAdjustNear();
}
/**
* Get projection type.
* @returns {ProjectionType}
* @example
* let projectionType = THING.App.current.camera.getProjectionType();
* @public
*/
getProjectionType() {
let _private = this[__.private];
return _private.projectionType;
}
/**
* Set projection type.
* @param {ProjectionType} value The projection value.
* @param {Number} duration The projection matrix lerping time in milliseconds.
* @example
* THING.App.current.camera.setProjectionType(THING.ProjectionType.Orthographic);
* @public
*/
setProjectionType(value, duration = 1000) {
let _private = this[__.private];
if (_private.projectionType == value) {
return;
}
let tweenManager = this.app.tweenManager;
let cameraNode = this.node;
let weight = cameraNode.getProjectionMatrixWeight();
// Stop current lerping
if (_private.projectionTween) {
_private.projectionTween.stop();
}
if (_private.projectionType == value) {
return;
}
// 2D -> 3D
if (value == ProjectionType.Perspective) {
if (duration) {
_private.projectionTween = tweenManager.lerpTo(weight, 1, duration).easing(LerpType.Exponential.In).onUpdate(ev => {
cameraNode.setProjectionMatrixWeight(ev.value);
});
}
else {
cameraNode.setProjectionMatrixWeight(1);
}
}
// 3D -> 2D
else if (value == ProjectionType.Orthographic) {
if (duration) {
_private.projectionTween = tweenManager.lerpTo(weight, 0, duration).easing(LerpType.Quartic.Out).onUpdate(ev => {
this.orthoDistance = this.distance;
cameraNode.setProjectionMatrixWeight(ev.value);
});
}
else {
this.orthoDistance = this.distance;
cameraNode.setProjectionMatrixWeight(0);
}
}
_private.projectionType = value;
}
/**
* Get view mode type.
* @returns {ViewMode}
* @example
* let viewMode = THING.App.current.camera.getViewMode();
* @public
*/
getViewMode() {
let _private = this[__.private];
return _private.viewModeInfo.type;
}
/**
* Set view mode type.
* @param {THING.Object3D} target The target object, if do not provide then would use level's current object or root object.
* @param {ViewMode} value The view mode value.
* @param {Number} duration The projection matrix lerping time in milliseconds.
* @example
* THING.App.current.camera.setViewMode(THING.App.current.root, THING.ViewModeType.Top);
* @public
*/
setViewMode(target, value, duration = 1000) {
if (Utils.isString(target)) {
duration = value || duration;
value = target;
target = null;
}
let object = target || this.app.levelManager.current || this.app.root;
this._setViewMode(object, value, duration);
}
/**
* Get view mode type.
* @returns {ViewMode}
* @deprecated 2.7
* @private
*/
getViewModeType() {
console.warn('Please use getViewMode(), getViewModeType() has been abandoned, but will be compatible with version 2.7');
return this.getViewMode();
}
/**
* Set view mode type.
* @param {THING.Object3D} target The target object, if do not provide then would use level's current object or root object.
* @param {ViewMode} value The view mode value.
* @param {Number} duration The projection matrix lerping time in milliseconds.
* @deprecated 2.7
* @private
*/
setViewModeType(target, value, duration = 1000) {
console.warn('Please use setViewMode(), setViewModeType() has been abandoned, but will be compatible with version 2.7');
this.setViewMode(target, value, duration);
}
/**
* Get ortho rect [left, right, bottom, top].
* @type {Number}
* @private
*/
get orthoRect() {
let target = [];
return this.node.getOrthographicRect(target);
}
/**
* Get/Set ortho distance.
* @type {Number}
* @private
*/
get orthoDistance() {
return this.node.getAttribute('OrthoDistance');
}
set orthoDistance(value) {
this.node.setAttribute('OrthoDistance', value);
}
/**
* Get/Set fov.
* @type {Number}
* @example
* THING.App.current.camera.fov = 60;
* @public
*/
get fov() {
let _private = this[__.private];
return _private.fov;
}
set fov(value) {
let _private = this[__.private];
_private.fov = value;
this.node.setFov(value);
}
/**
* Get/Set near.
* @type {Number}
* @example
* THING.App.current.camera.near = 0.001;
* @public
*/
get near() {
let _private = this[__.private];
return _private.near;
}
set near(value) {
let _private = this[__.private];
_private.near = value;
this.node.setNear(value);
}
/**
* Get/Set far.
* @type {Number}
* @example
* THING.App.current.camera.far = 1000000;
* @public
*/
get far() {
let _private = this[__.private];
return _private.far;
}
set far(value) {
let _private = this[__.private];
_private.far = value;
this.node.setFar(value);
}
/**
* Get aspect.
* @type {Number}
* @readonly
* @example
* let aspect = THING.App.current.camera.aspect;
* @public
*/
get aspect() {
return this.node.getAspect();
}
/**
* Get/Set projection type.
* @type {ProjectionType}
* @example
* THING.App.current.camera.projectionType = THING.ProjectionType.Orthographic;
* @public
*/
get projectionType() {
return this.getProjectionType();
}
set projectionType(value) {
this.setProjectionType(value, 0);
}
/**
* Get/Set view mode type.
* @type {ViewMode}
* @example
* THING.App.current.camera.viewMode = THING.ViewModeType.Top;
* @public
*/
get viewMode() {
return this.getViewMode();
}
set viewMode(value) {
this.setViewMode(null, value, 0);
}
/**
* Get/Set view mode type.
* @type {ViewMode}
* @deprecated 2.7
* @private
*/
get viewModeType() {
console.warn('Please use viewMode, viewModeType has been abandoned, but will be compatible with version 2.7');
return this.viewMode;
}
set viewModeType(value) {
console.warn('Please use viewMode, viewModeType has been abandoned, but will be compatible with version 2.7');
this.viewMode = value;
}
// #endregion
// #region Picker
/**
* Check intersection between object and region.
* @param {Array<Number>} region The [left, top, width, height] region in screen.
* @param {THING.Object3D} object The object.
* @returns {Boolean}
* @example
* if (THING.App.current.camera.intersectObjectInRegion([0, 0, 100, 100], object)) {
* console.log('intersect OK');
* }
* @public
*/
intersectObjectInRegion(region, object) {
return this.picker.intersectObjectInRegion(region, object);
}
/**
* Pick node.
* @param {Number} x The x coordinate in screen.
* @param {Number} y The y coordinate in screen.
* @param {THING.BaseObject} root? The root object to pick.
* @returns {PickResult}
* @example
* let result = THING.App.current.camera.pick(100, 100);
* if (result) {
* console.log('picked result: ', result.object);
* }
* @public
*/
pick(x, y, root) {
return this.picker.pick(x, y, root);
}
/**
* Pick from cross planes.
* @param {Number} x The x coordinate in screen.
* @param {Number} y The y coordinate in screen.
* @param {Boolean} isVertical Whether vertical and horizontal planes will always be vertical.
* @returns {PickResult}
* @example
* let result = THING.App.current.camera.pickFromCrossPlanes(100, 100);
* if (result) {
* console.log('picked result: ', result.object);
* }
* @public
*/
pickFromCrossPlanes(x, y, isVertical = true) {
return this.picker.pickFromCrossPlanes(x, y, isVertical);
}
/**
* Calculate the points where the plane intersects.
* @param {Number} x The x coordinate in screen.
* @param {Number} y The y coordinate in screen.
* @param {Array<Number>} normal The plane normal. (default [0,1,0])
* @param {Number} constant Distance of plane. (default 0)
* @returns {Array<Number>}
* @private
*/
intersectPlane(x, y, normal = [0, 1, 0], constant = 0) {
return this.picker.intersectPlane(x, y, normal, constant);
}
/**
* Add an effect.
* @param {String} name The effect name.
* @param {Effect} effect The effect.
* @public
*/
addEffect(name, effect) {
this.node.addEffect(name, effect.resource);
}
/**
* Remove an effect.
* @param {String} name The effect name.
* @public
*/
removeEffect(name) {
this.node.removeEffect(name);
}
// #endregion
get isCamera() {
return true;
}
}
export { Camera }