2022-06-25 00:23:03 +08:00

504 lines
12 KiB
JavaScript

// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
import { Vec3, Mat4, lerp, Vec4 } from '../../core/value-types';
import { Ray } from '../../core/geom-utils';
import enums from '../enums';
let _tmp_mat4 = new Mat4();
let _matView = new Mat4();
let _matViewInv = new Mat4();
let _matProj = new Mat4();
let _matViewProj = new Mat4();
let _matInvViewProj = new Mat4();
let _tmp_v3 = new Vec3();
let _tmp2_v3 = new Vec3();
/**
* A representation of a camera instance
*/
export default class Camera {
_poolID = -1;
_node = null;
_projection = enums.PROJ_PERSPECTIVE;
// priority. the smaller one will be rendered first
_priority = 0;
// clear options
_color = new Vec4(0.2, 0.3, 0.47, 1);
_depth = 1;
_stencil = 0;
_clearFlags = enums.CLEAR_COLOR | enums.CLEAR_DEPTH;
_clearModel = null;
// stages & framebuffer
_stages = [];
_framebuffer = null;
// projection properties
_near = 0.01;
_far = 1000.0;
_fov = Math.PI / 4.0; // vertical fov
_rect = {
x: 0, y: 0, w: 1, h: 1
};
// ortho properties
_orthoHeight = 10;
_cullingMask = 0xffffffff;
// culling mask
get cullingMask () {
return this._cullingMask;
}
set cullingMask (mask) {
this._cullingMask = mask;
}
setCullingMask (mask) {
this._cullingMask = mask;
}
/**
* Get the hosting node of this camera
* @returns {Node} the hosting node
*/
getNode () {
return this._node;
}
/**
* Set the hosting node of this camera
* @param {Node} node the hosting node
*/
setNode (node) {
this._node = node;
}
/**
* Get the projection type of the camera
* @returns {number} camera projection type
*/
getType () {
return this._projection;
}
/**
* Set the projection type of the camera
* @param {number} type camera projection type
*/
setType (type) {
this._projection = type;
}
/**
* Get the priority of the camera
* @returns {number} camera priority
*/
getPriority () {
return this._priority;
}
/**
* Set the priority of the camera
* @param {number} priority camera priority
*/
setPriority (priority) {
this._priority = priority;
}
/**
* Get the orthogonal height of the camera
* @returns {number} camera height
*/
getOrthoHeight () {
return this._orthoHeight;
}
/**
* Set the orthogonal height of the camera
* @param {number} val camera height
*/
setOrthoHeight (val) {
this._orthoHeight = val;
}
/**
* Get the field of view of the camera
* @returns {number} camera field of view
*/
getFov () {
return this._fov;
}
/**
* Set the field of view of the camera
* @param {number} fov camera field of view
*/
setFov (fov) {
this._fov = fov;
}
/**
* Get the near clipping distance of the camera
* @returns {number} camera near clipping distance
*/
getNear () {
return this._near;
}
/**
* Set the near clipping distance of the camera
* @param {number} near camera near clipping distance
*/
setNear (near) {
this._near = near;
}
/**
* Get the far clipping distance of the camera
* @returns {number} camera far clipping distance
*/
getFar () {
return this._far;
}
/**
* Set the far clipping distance of the camera
* @param {number} far camera far clipping distance
*/
setFar (far) {
this._far = far;
}
/**
* Get the clear color of the camera
* @returns {Vec4} out the receiving color vector
*/
getColor (out) {
return Vec4.copy(out, this._color);
}
/**
* Set the clear color of the camera
* @param {number} r red channel of camera clear color
* @param {number} g green channel of camera clear color
* @param {number} b blue channel of camera clear color
* @param {number} a alpha channel of camera clear color
*/
setColor (r, g, b, a) {
Vec4.set(this._color, r, g, b, a);
}
/**
* Get the clear depth of the camera
* @returns {number} camera clear depth
*/
getDepth () {
return this._depth;
}
/**
* Set the clear depth of the camera
* @param {number} depth camera clear depth
*/
setDepth (depth) {
this._depth = depth;
}
/**
* Get the clearing stencil value of the camera
* @returns {number} camera clearing stencil value
*/
getStencil () {
return this._stencil;
}
/**
* Set the clearing stencil value of the camera
* @param {number} stencil camera clearing stencil value
*/
setStencil (stencil) {
this._stencil = stencil;
}
/**
* Get the clearing flags of the camera
* @returns {number} camera clearing flags
*/
getClearFlags () {
return this._clearFlags;
}
/**
* Set the clearing flags of the camera
* @param {number} flags camera clearing flags
*/
setClearFlags (flags) {
this._clearFlags = flags;
}
/**
* Get the rect of the camera
* @param {Object} out the receiving object
* @returns {Object} camera rect
*/
getRect (out) {
out.x = this._rect.x;
out.y = this._rect.y;
out.w = this._rect.w;
out.h = this._rect.h;
return out;
}
/**
* Set the rect of the camera
* @param {Number} x - [0,1]
* @param {Number} y - [0,1]
* @param {Number} w - [0,1]
* @param {Number} h - [0,1]
*/
setRect (x, y, w, h) {
this._rect.x = x;
this._rect.y = y;
this._rect.w = w;
this._rect.h = h;
}
/**
* Get the stages of the camera
* @returns {string[]} camera stages
*/
getStages () {
return this._stages;
}
/**
* Set the stages of the camera
* @param {string[]} stages camera stages
*/
setStages (stages) {
this._stages = stages;
}
/**
* Get the framebuffer of the camera
* @returns {FrameBuffer} camera framebuffer
*/
getFramebuffer () {
return this._framebuffer;
}
/**
* Set the framebuffer of the camera
* @param {FrameBuffer} framebuffer camera framebuffer
*/
setFrameBuffer (framebuffer) {
this._framebuffer = framebuffer;
}
_calcMatrices (width, height) {
// view matrix
this._node.getWorldRT(_matViewInv);
Mat4.invert(_matView, _matViewInv);
// projection matrix
let aspect = width / height;
if (this._projection === enums.PROJ_PERSPECTIVE) {
Mat4.perspective(_matProj,
this._fov,
aspect,
this._near,
this._far
);
} else {
let x = this._orthoHeight * aspect;
let y = this._orthoHeight;
Mat4.ortho(_matProj,
-x, x, -y, y, this._near, this._far
);
}
// view-projection
Mat4.mul(_matViewProj, _matProj, _matView);
// inv view-projection
Mat4.invert(_matInvViewProj, _matViewProj);
}
/**
* extract a view of this camera
* @param {View} out the receiving view
* @param {number} width framebuffer width
* @param {number} height framebuffer height
*/
extractView (out, width, height) {
if (this._framebuffer) {
width = this._framebuffer._width;
height = this._framebuffer._height;
}
// priority
out._priority = this._priority;
// rect
out._rect.x = this._rect.x * width;
out._rect.y = this._rect.y * height;
out._rect.w = this._rect.w * width;
out._rect.h = this._rect.h * height;
// clear opts
this.getColor(out._color);
out._depth = this._depth;
out._stencil = this._stencil;
out._clearFlags = this._clearFlags;
out._clearModel = this._clearModel;
// stages & framebuffer
out._stages = this._stages;
out._framebuffer = this._framebuffer;
this._calcMatrices(width, height);
Mat4.copy(out._matView, _matView);
Mat4.copy(out._matViewInv, _matViewInv);
Mat4.copy(out._matProj, _matProj);
Mat4.copy(out._matViewProj, _matViewProj);
Mat4.copy(out._matInvViewProj, _matInvViewProj);
out._cullingMask = this._cullingMask;
}
/**
* transform a screen position to a world space ray
* @param {number} x the screen x position to be transformed
* @param {number} y the screen y position to be transformed
* @param {number} width framebuffer width
* @param {number} height framebuffer height
* @param {Ray} out the resulting ray
* @returns {Ray} the resulting ray
*/
screenPointToRay (x, y, width, height, out) {
if (!cc.geomUtils) return out;
out = out || new Ray();
this._calcMatrices(width, height);
let cx = this._rect.x * width;
let cy = this._rect.y * height;
let cw = this._rect.w * width;
let ch = this._rect.h * height;
// far plane intersection
Vec3.set(_tmp2_v3, (x - cx) / cw * 2 - 1, (y - cy) / ch * 2 - 1, 1);
Vec3.transformMat4(_tmp2_v3, _tmp2_v3, _matInvViewProj);
if (this._projection === enums.PROJ_PERSPECTIVE) {
// camera origin
this._node.getWorldPosition(_tmp_v3);
} else {
// near plane intersection
Vec3.set(_tmp_v3, (x - cx) / cw * 2 - 1, (y - cy) / ch * 2 - 1, -1);
Vec3.transformMat4(_tmp_v3, _tmp_v3, _matInvViewProj);
}
return Ray.fromPoints(out, _tmp_v3, _tmp2_v3);
}
/**
* transform a screen position to world space
* @param {Vec3} out the resulting vector
* @param {Vec3} screenPos the screen position to be transformed
* @param {number} width framebuffer width
* @param {number} height framebuffer height
* @returns {Vec3} the resulting vector
*/
screenToWorld (out, screenPos, width, height) {
this._calcMatrices(width, height);
let cx = this._rect.x * width;
let cy = this._rect.y * height;
let cw = this._rect.w * width;
let ch = this._rect.h * height;
if (this._projection === enums.PROJ_PERSPECTIVE) {
// calculate screen pos in far clip plane
Vec3.set(out,
(screenPos.x - cx) / cw * 2 - 1,
(screenPos.y - cy) / ch * 2 - 1,
0.9999
);
// transform to world
Vec3.transformMat4(out, out, _matInvViewProj);
// lerp to depth z
this._node.getWorldPosition(_tmp_v3);
Vec3.lerp(out, _tmp_v3, out, lerp(this._near / this._far, 1, screenPos.z));
} else {
Vec3.set(out,
(screenPos.x - cx) / cw * 2 - 1,
(screenPos.y - cy) / ch * 2 - 1,
screenPos.z * 2 - 1
);
// transform to world
Vec3.transformMat4(out, out, _matInvViewProj);
}
return out;
}
/**
* transform a world space position to screen space
* @param {Vec3} out the resulting vector
* @param {Vec3} worldPos the world space position to be transformed
* @param {number} width framebuffer width
* @param {number} height framebuffer height
* @returns {Vec3} the resulting vector
*/
worldToScreen (out, worldPos, width, height) {
this._calcMatrices(width, height);
let cx = this._rect.x * width;
let cy = this._rect.y * height;
let cw = this._rect.w * width;
let ch = this._rect.h * height;
Vec3.transformMat4(out, worldPos, _matViewProj);
out.x = cx + (out.x + 1) * 0.5 * cw;
out.y = cy + (out.y + 1) * 0.5 * ch;
out.z = out.z * 0.5 + 0.5;
return out;
}
/**
* transform a world space matrix to screen space
* @param {Mat4} out the resulting vector
* @param {Mat4} worldMatrix the world space matrix to be transformed
* @param {number} width framebuffer width
* @param {number} height framebuffer height
* @returns {Mat4} the resulting vector
*/
worldMatrixToScreen (out, worldMatrix, width, height) {
this._calcMatrices(width, height);
Mat4.mul(out, _matViewProj, worldMatrix);
let halfWidth = width / 2;
let halfHeight = height / 2;
Mat4.identity(_tmp_mat4);
Mat4.transform(_tmp_mat4, _tmp_mat4, Vec3.set(_tmp_v3, halfWidth, halfHeight, 0));
Mat4.scale(_tmp_mat4, _tmp_mat4, Vec3.set(_tmp_v3, halfWidth, halfHeight, 1));
Mat4.mul(out, _tmp_mat4, out);
return out;
}
}