import { StringEncoder, Flags, Frustum } 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 << 20,
EnableViewport: 1 << 21,
Ticking: 1 << 22
};
const registerComponentParam = { isResident: true };
let frustum = new Frustum();
/**
* @public
* @class Camera
* 相机对象
* @memberof THING
* @extends THING.Object3D
*/
class Camera extends Object3D {
/**
* 相机对象的构造方法 我们一般不会调用Camera的构造方法,因为new App时会默认创建Camera,直接通过app.camera可获取相机对象
*/
constructor(param = {}) {
super(param);
this[__.private] = {};
let _private = this[__.private];
_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;
// Set default value
_private.fov = 60;
_private.near = 0.1;
_private.far = 10000;
// Sync options
this._syncOptions(param);
// 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
this._flags.enable(Flag.Created, true);
}
/**
* 相机的效果组件
* @member {THING.CameraEffectComponent} effect
* @memberof THING.Camera
* @public
* @instance
*/
/**
* 相机的后期效果组件
* @member {THING.CameraPostEffectComponent} postEffect
* @memberof THING.Camera
* @public
* @instance
*/
/**
* 相机的控制器组件
* @member {THING.CameraControlComponent} control
* @memberof THING.Camera
* @public
* @instance
*/
/**
* 相机的雾效组件
* @member {THING.CameraFogComponent} fog
* @memberof THING.Camera
* @public
* @instance
*/
// #region Private
_syncOptions(param) {
Utils.syncOptions(this, [
'fov',
'near',
'far'
], param, param['extras'] || param['external']);
}
/**
* Get position, target and up for view mode in perspective
* @param {THING.ViewMode} viewMode The view mode type.
* @param {THING.Object3D} target The target object.
* @returns {object} The position, target and up.
* @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 null;
}
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} The position, target and up.
* @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 {THING.ViewMode} viewMode The view mode type.
* @param {THING.Object3D} target The target object.
* @returns {object} The position, target and up.
* @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);
this.control.getController().setTargetWorldPosition(lookAt);
// 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;
}
this.control.getController().setTargetWorldPosition(lookAt);
// 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() {
return this._flags.has(Flag.Created);
}
/**
* 检查给定的对象是否在当前相机的可视范围内
* @param {THING.Object3D} object 需要检查的对象
* @returns {boolean} 返回该对象是否在相机视野中
* @public
*/
isInView(object) {
if (!object || !object.isObject3D) {
return false;
}
frustum.setMatrixWorld(this.position, this.quaternion, this.scale);
let mat4_1 = MathUtils.createMat4();
let matrixWorldInverse = MathUtils.createMat4();
MathUtils.mat4.invert(matrixWorldInverse, this.matrixWorld);
MathUtils.mat4.multiply(mat4_1, this.projectionMatrix, matrixWorldInverse);
frustum.setFromProjectionMatrix(mat4_1);
let box = object.getAABB();
return frustum.intersectsBoxV1(box);
}
// #endregion
// #region BaseObject Interface
onImportExternalData(external, options) {
super.onImportExternalData(external, options);
if (external) {
this._syncOptions(external);
}
}
onExportExternalData() {
let external = Object.assign({}, super.onExportExternalData());
Utils.setAttributeIfExist(external, 'fov', this);
Utils.setAttributeIfExist(external, 'near', this);
Utils.setAttributeIfExist(external, 'far', this);
return external;
}
onBeforeSetup(param) {
param['renderableNode'] = param['renderableNode'] || Utils.createObject('Camera', { view: this.app.view, scene: this.app.scene.context });
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 (this.app.objectManager.keepAliveObjects.includes(this)) {
return false;
}
if (!super.onBeforeDestroy()) {
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() {
// Check whether it's ticking now
if (this._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
this._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
/**
* 世界坐标转屏幕坐标
* @param {number|Array<number>} x 世界坐标 x 或包含 x,y,z 的数组
* @param {number} [y] 世界坐标 y
* @param {number} [z] 世界坐标 z
* @param {Array<number>} [target=[]] 用于存储结果的数组
* @returns {Array<number>} 屏幕坐标
* @example
* let screen0 = THING.App.current.camera.worldToScreen(100, 100, 90);
* let screen1 = THING.App.current.camera.worldToScreen([100, 100, 100]);
* if (screen0[2] < screen1[2]) {
* console.log('screen0 is closer to the camera than screen1');
* }
* @public
*/
worldToScreen() {
let position, target;
if (Array.isArray(arguments[0])) {
position = arguments[0];
target = arguments[1] || [];
}
else {
position = [arguments[0], arguments[1], arguments[2]];
target = arguments[3] || [];
}
return this.node.worldToScreen(position, target);
}
/**
* 屏幕坐标转世界坐标
* @param {number|Array<number>} x 屏幕坐标 x 或包含 x,y 的数组
* @param {number} [y] 屏幕坐标 y
* @param {Array<number>} [target=[]] 用于存储结果的数组
* @returns {Array<number>} 世界坐标
* @example
* let position1 = THING.App.current.camera.screenToWorld(100, 100);
* let position2 = THING.App.current.camera.screenToWorld([200, 200]);
* @public
*/
screenToWorld() {
let screenPosition, target;
if (Array.isArray(arguments[0])) {
screenPosition = arguments[0];
target = arguments[1] || [];
}
else {
screenPosition = [arguments[0], arguments[1]];
target = arguments[2] || [];
}
return this.node.screenToWorld(screenPosition, target);
}
// #endregion
// #region Resources
onUnloadResource() {
this.control.setController(null);
super.onUnloadResource();
}
copy(object) {
this.onBeforeCopy(object);
this.onCopyComponents(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);
}
/**
* scale of gpupicker‘s buffer
* @type {number}
* @private
*/
get gpuPickerBufferScale() {
return this.node.getAttribute('GPUPickerBufferScale');
}
set gpuPickerBufferScale(value) {
if (value < 0) {
value = 0;
}
else if (value > 1) {
value = 1;
}
this.node.setAttribute('GPUPickerBufferScale', value);
}
/**
* 对当前场景截图 并输出到纹理
* @param {number} width 纹理宽度
* @param {number} 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, app: this.app });
}
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);
// Update camera matrix world before render
node.updateMatrixWorld();
// 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;
}
/**
* 对当前场景截图 并以UInt8Array的方式输出
* @param {number} width 图片宽度
* @param {number} 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;
}
/**
* 对当前场景截图 并输出到img对象
* @param {number} width 图片宽度
* @param {number} height 图片宽度
* @returns {HTMLImageElement} img对象 以base64字符串作为src
* @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);
}
/**
* 异步:对当前场景截图并输出到纹理。
* 在截图前后等待 GPU 更新,避免与异步拾取产生竞态。
* @param {number} width 纹理宽度
* @param {number} height 纹理高度
* @returns {Promise<THING.RenderTexture>} Promise,解析为 RenderTexture
* @example
* const rt = await THING.App.current.camera.captureToRenderTextureAsync(1024, 768);
* rt.download('asyncScreenshot');
* @public
*/
async captureToRenderTextureAsync(width, height) {
// 等待当前 GPU 队列稳定(例如拾取缓冲写入完成)
await this.picker.waitForGPUUpdate();
const renderTexture = this.captureToRenderTexture(width, height);
// 等待截图渲染命令提交/完成,确保后续读取像素或拾取不互相干扰
await this.picker.waitForGPUUpdate();
return renderTexture;
}
/**
* 异步:对当前场景截图 并以 UInt8Array 的方式输出。
* @param {number} width 图片宽度
* @param {number} height 图片高度
* @returns {Promise<Uint8Array>} Promise,解析为图片的像素数据
* @example
* const data = await THING.App.current.camera.captureToDataAsync(1024, 768);
* // data.length 应为 1024 * 768 * 4
* @public
*/
async captureToDataAsync(width, height) {
const renderTexture = await this.captureToRenderTextureAsync(width, height);
return renderTexture.pixelBuffer;
}
/**
* 异步:对当前场景截图 并输出到 img 对象(base64 字符串作为 src)。
* @param {number} width 图片宽度
* @param {number} height 图片高度
* @returns {Promise<HTMLImageElement>} Promise,解析为 img 对象
* @example
* const image = await THING.App.current.camera.captureToImageAsync(1024, 768);
* document.body.appendChild(image);
* @public
*/
async captureToImageAsync(width, height) {
const size = this._getSize(width, height);
const pixelBuffer = await this.captureToDataAsync(size[0], size[1]);
return Utils.saveAsImage(size[0], size[1], pixelBuffer);
}
/**
* 对当前场景截图 并输出到文件 调用后浏览器会自动下载该文件
* @param {string} fileName 文件名称
* @param {number} width 图片宽度
* @param {number} 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]);
}
}
/**
* 启用/禁用 视口渲染模式(即指定视口位置和尺寸,仅在该范围内进行渲染)
* @type {boolean}
* @example
* let camera = new THING.Camera();
* camera.enableViewport = true;
* @public
*/
get enableViewport() {
return this._flags.has(Flag.EnableViewport);
}
set enableViewport(value) {
if (this._flags.enable(Flag.EnableViewport, value)) {
this.trigger(EventType.EnableViewport, { value });
}
}
/**
* 获取/设置视口参数 (屏幕左上角的坐标以及视口宽高)
* @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) {
let values = value.map(Math.floor);
_private.viewport = values;
this.node.setViewport(_private.viewport);
this.node.setSize([values[2], values[3]]);
}
else {
_private.viewport = null;
this.node.setViewport([0, 0, 0, 0]);
}
this._updateRenderableRefCount(value);
}
/**
* 设置/获取 RenderTexture
* @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
/**
* 校正近剪裁面
* @public
*/
processAdjustNear() {
this.control.processAdjustNear();
}
/**
* 获取投影类型 (正交/透视)
* @returns {ProjectionType} 投影类型
* @example
* let projectionType = THING.App.current.camera.getProjectionType();
* @public
*/
getProjectionType() {
let _private = this[__.private];
return _private.projectionType;
}
/**
* 设置投影类型
* @param {ProjectionType} value 投影类型
* @param {number} duration 投影类型切换的时间
* @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;
}
/**
* 获取视图模式
* @returns {ViewMode} 视图模式
* @example
* let viewMode = THING.App.current.camera.getViewMode();
* @public
*/
getViewMode() {
let _private = this[__.private];
return _private.viewModeInfo.type;
}
/**
* 设置视图模式
* @param {THING.Object3D} [target] 目标物体,如果不提供,则使用当前场景的物体或根物体。
* @param {ViewMode} value 视图模式值。
* @param {number} [duration=1000] 投影矩阵过渡时间(毫秒)。
* @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);
}
/**
* 获取/设置 相机的视场角 这个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);
}
/**
* 获取/设置 相机的近裁面
* @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);
}
/**
* 设置/获取 相机的远裁面
* @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);
}
/**
* 获取视口的宽高比
* @type {number}
* @readonly
* @example
* let aspect = THING.App.current.camera.aspect;
* @public
*/
get aspect() {
return this.node.getAspect();
}
/**
* 设置/获取 相机投影类型
* @type {ProjectionType}
* @example
* THING.App.current.camera.projectionType = THING.ProjectionType.Orthographic;
* @public
*/
get projectionType() {
return this.getProjectionType();
}
set projectionType(value) {
this.setProjectionType(value, 0);
this.trigger('projectionTypeChange', { value });
}
/**
* 设置/获取 相机视图模式
* @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
/**
* 获取屏幕指定范围内的物体
* @param {Array<number>} region 屏幕指定范围(从左上角计算,数组每个元素的单位是像素)
* @param {THING.Object3D} object 需要检查的对象
* @param {boolean} included It fully includes the default false
* @returns {boolean} 是否相交
* @example
* if (THING.App.current.camera.intersectObjectInRegion([0, 0, 100, 100], object)) {
* console.log('intersect OK');
* }
* @public
*/
intersectObjectInRegion(region, object, included = false) {
return this.picker.intersectObjectInRegion(region, object, included);
}
/**
* 拾取物体
* @param {number} x 屏幕坐标x
* @param {number} y 屏幕坐标y
* @param {THING.BaseObject} [root] 拾取的根节点 默认是app.root
* @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);
}
/**
* Wait for GPU buffer update.
* @returns {Promise} The promise object.
*/
waitForGPUUpdate() {
return this.picker.waitForGPUUpdate();
}
/**
* 在点击空白区域时,为了保证能够拾取到物体,根据交叉平面进行拾取,
* 这个交叉平面是指与相机位置与相机目标点方向垂直且穿过相机目标点的垂直平面以及穿过相机位置与目标点且与垂直平面垂直的水平面
* @param {number} x 屏幕坐标x
* @param {number} y 屏幕坐标y
* @param {boolean} [isVertical=true] true代表使用水平面和垂直面进行拾取,false代表仅适用水平面进行拾取
* @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);
}
/**
* 添加效果
* @param {string} name 效果名称
* @param {Effect} effect 效果对象
* @param {number} order 效果顺序
* @public
*/
addEffect(name, effect, order = 100) {
this.node.addEffect(name, effect.resource, order);
}
/**
* 移除效果
* @param {string} name 后期效果名称
* @public
*/
removeEffect(name) {
this.node.removeEffect(name);
}
// #endregion
get isCamera() {
return true;
}
set transmissionRender(value) {
this.node.setTransmission(value);
}
get transmissionRender() {
return this.node.getTransmission();
}
}
export { Camera }