// 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; } }