Source: scene/camera.js

Object.assign(pc, function () {
    // pre-allocated temp variables
    var _deviceCoord = new pc.Vec3();
    var _far = new pc.Vec3();
    var _farW = new pc.Vec3();
    var _invViewProjMat = new pc.Mat4();
    /**
     * @private
     * @constructor
     * @name pc.Camera
     * @classdesc A camera.
     */
    var Camera = function () {
        this._projection = pc.PROJECTION_PERSPECTIVE;
        this._nearClip = 0.1;
        this._farClip = 10000;
        this._shaderParams = new Float32Array(4);
        this._fov = 45;
        this._orthoHeight = 10;
        this._aspect = 16 / 9;
        this._aspectRatioMode = pc.ASPECT_AUTO;
        this._horizontalFov = false;
        this.frustumCulling = false;
        this.cullingMask = 0xFFFFFFFF;
        this._renderDepthRequests = 0;

        this._projMatDirty = true;
        this._projMat = new pc.Mat4();
        this._viewMatDirty = true;
        this._viewMat = new pc.Mat4();
        this._viewProjMatDirty = true;
        this._viewProjMat = new pc.Mat4();

        this.vrDisplay = null;

        this._rect = {
            x: 0,
            y: 0,
            width: 1,
            height: 1
        };

        this._scissorRect = {
            x: 0,
            y: 0,
            width: 1,
            height: 1
        };

        this.frustum = new pc.Frustum(this._projMat, this._viewMat);

        // Create a full size viewport onto the backbuffer
        this.renderTarget = null;
        this._depthTarget = null;

        // Create the clear options
        this._clearOptions = {
            color: [0.5, 0.5, 0.5, 1.0],
            depth: 1.0,
            stencil: 0,
            flags: pc.CLEARFLAG_COLOR | pc.CLEARFLAG_DEPTH | pc.CLEARFLAG_STENCIL
        };

        this._node = null;

        this.calculateTransform = null;
        this.overrideCalculateTransform = false;
        this.calculateProjection = null;
        this.overrideCalculateProjection = false;
        this._cullFaces = true;
        this._flipFaces = false;

        this._component = null;
    };

    Object.assign(Camera.prototype, {
        /**
         * @private
         * @function
         * @name pc.Camera#clone
         * @description Duplicates a camera node but does not 'deep copy' the hierarchy.
         * @returns {pc.Camera} A cloned Camera.
         */
        clone: function () {
            var clone = new pc.Camera();
            clone.projection = this._projection;
            clone.nearClip = this._nearClip;
            clone.farClip = this._farClip;
            clone._shaderParams = this._shaderParams.slice();
            clone.fov = this._fov;
            clone.aspectRatio = this._aspect;
            clone._aspectRatioMode = this._aspectRatioMode;
            clone.renderTarget = this.renderTarget;
            clone.setClearOptions(this.getClearOptions());
            clone.frustumCulling = this.frustumCulling;
            clone.cullingMask = this.cullingMask;
            return clone;
        },

        /**
         * @private
         * @function
         * @name pc.Camera#worldToScreen
         * @description Convert a point from 3D world space to 2D canvas pixel space.
         * @param {pc.Vec3} worldCoord The world space coordinate to transform.
         * @param {Number} cw The width of PlayCanvas' canvas element.
         * @param {Number} ch The height of PlayCanvas' canvas element.
         * @param {pc.Vec3} [screenCoord] 3D vector to receive screen coordinate result.
         * @returns {pc.Vec3} The screen space coordinate.
         */
        worldToScreen: function (worldCoord, cw, ch, screenCoord) {
            if (screenCoord === undefined) {
                screenCoord = new pc.Vec3();
            }

            if (this._projMatDirty || this._viewMatDirty || this._viewProjMatDirty) {
                var projMat = this.getProjectionMatrix();
                var viewMat = this.getViewMatrix();
                this._viewProjMat.mul2(projMat, viewMat);
                this._viewProjMatDirty = false;
            }
            this._viewProjMat.transformPoint(worldCoord, screenCoord);

            // calculate w co-coord
            var vpm = this._viewProjMat.data;
            var w = worldCoord.x * vpm[3] +
                    worldCoord.y * vpm[7] +
                    worldCoord.z * vpm[11] +
                               1 * vpm[15];

            screenCoord.x = (screenCoord.x / w + 1) * 0.5 * cw;
            screenCoord.y = (1 - screenCoord.y / w) * 0.5 * ch;

            return screenCoord;
        },

        /**
         * @private
         * @function
         * @name pc.Camera#screenToWorld
         * @description Convert a point from 2D canvas pixel space to 3D world space.
         * @param {Number} x x coordinate on PlayCanvas' canvas element.
         * @param {Number} y y coordinate on PlayCanvas' canvas element.
         * @param {Number} z The distance from the camera in world space to create the new point.
         * @param {Number} cw The width of PlayCanvas' canvas element.
         * @param {Number} ch The height of PlayCanvas' canvas element.
         * @param {pc.Vec3} [worldCoord] 3D vector to receive world coordinate result.
         * @returns {pc.Vec3} The world space coordinate.
         */
        screenToWorld: function (x, y, z, cw, ch, worldCoord) {
            if (worldCoord === undefined) {
                worldCoord = new pc.Vec3();
            }

            if (this._projMatDirty || this._viewMatDirty || this._viewProjMatDirty) {
                var projMat = this.getProjectionMatrix();
                var viewMat = this.getViewMatrix();
                this._viewProjMat.mul2(projMat, viewMat);
                this._viewProjMatDirty = false;
            }
            _invViewProjMat.copy(this._viewProjMat).invert();

            if (this._projection === pc.PROJECTION_PERSPECTIVE) {
                // Calculate the screen click as a point on the far plane of the
                // normalized device coordinate 'box' (z=1)
                _far.set(x / cw * 2 - 1, (ch - y) / ch * 2 - 1, 1);

                // Transform to world space
                _invViewProjMat.transformPoint(_far, _farW);

                var w = _far.x * _invViewProjMat.data[3] +
                        _far.y * _invViewProjMat.data[7] +
                        _far.z * _invViewProjMat.data[11] +
                        _invViewProjMat.data[15];

                _farW.scale(1 / w);

                var alpha = z / this._farClip;
                worldCoord.lerp(this._node.getPosition(), _farW, alpha);
            } else {
                // Calculate the screen click as a point on the far plane of the
                // normalized device coordinate 'box' (z=1)
                var range = this._farClip - this._nearClip;
                _deviceCoord.set(x / cw, (ch - y) / ch, z / range);
                _deviceCoord.scale(2);
                _deviceCoord.sub(pc.Vec3.ONE);

                // Transform to world space
                _invViewProjMat.transformPoint(_deviceCoord, worldCoord);
            }

            return worldCoord;
        },

        /**
         * @private
         * @function
         * @name pc.Camera#getClearOptions
         * @description Retrieves the options used to determine how the camera's render target will be cleared.
         * @returns {Object} The options determining the behaviour of render target clears.
         */
        getClearOptions: function () {
            return this._clearOptions;
        },

        /**
         * @private
         * @function
         * @name pc.Camera#getProjectionMatrix
         * @description Retrieves the projection matrix for the specified camera.
         * @returns {pc.Mat4} The camera's projection matrix.
         */
        getProjectionMatrix: function () {
            if (this._projMatDirty) {
                if (this._projection === pc.PROJECTION_PERSPECTIVE) {
                    this._projMat.setPerspective(this._fov, this._aspect, this._nearClip, this._farClip, this._horizontalFov);
                } else {
                    var y = this._orthoHeight;
                    var x = y * this._aspect;
                    this._projMat.setOrtho(-x, x, -y, y, this._nearClip, this._farClip);
                }

                var n = this._nearClip;
                var f = this._farClip;
                this._shaderParams[0] = 1 / f;
                this._shaderParams[1] = f;
                this._shaderParams[2] = (1 - f / n) / 2;
                this._shaderParams[3] = (1 + f / n) / 2;

                this._projMatDirty = false;
            }
            return this._projMat;
        },

        /**
         * @private
         * @function
         * @name pc.Camera#getViewMatrix
         * @description Retrieves the view matrix for the specified camera based on the entity world transformation.
         * @returns {pc.Mat4} The camera's view matrix.
         */
        getViewMatrix: function () {
            if (this._viewMatDirty) {
                var wtm = this._node.getWorldTransform();
                this._viewMat.copy(wtm).invert();
                this._viewMatDirty = false;
            }
            return this._viewMat;
        },

        getRect: function () {
            return this._rect;
        },

        /**
         * @private
         * @function
         * @name pc.Camera#setClearOptions
         * @description Sets the options used to determine how the camera's render target will be cleared.
         * @param {Object} options The options determining the behaviour of subsequent render target clears.
         * @param {Number[]} options.color The options determining the behaviour of subsequent render target clears.
         * @param {Number} options.depth The options determining the behaviour of subsequent render target clears.
         * @param {pc.CLEARFLAG} options.flags The options determining the behaviour of subsequent render target clears.
         */
        setClearOptions: function (options) {
            this._clearOptions.color[0] = options.color[0];
            this._clearOptions.color[1] = options.color[1];
            this._clearOptions.color[2] = options.color[2];
            this._clearOptions.color[3] = options.color[3];
            this._clearOptions.depth = options.depth;
            this._clearOptions.stencil = options.stencil;
            this._clearOptions.flags = options.flags;
        },

        setRect: function (x, y, width, height) {
            this._rect.x = x;
            this._rect.y = y;
            this._rect.width = width;
            this._rect.height = height;
        },

        setScissorRect: function (x, y, width, height) {
            this._scissorRect.x = x;
            this._scissorRect.y = y;
            this._scissorRect.width = width;
            this._scissorRect.height = height;
        },

        requestDepthMap: function () {
            this._renderDepthRequests++;
        },

        releaseDepthMap: function () {
            this._renderDepthRequests--;
        }
    });

    /**
     * @private
     * @type Number
     * @name pc.Camera#aspectRatio
     * @description Camera's aspect ratio.
     */
    Object.defineProperty(Camera.prototype, 'aspectRatio', {
        get: function () {
            return this._aspect;
        },
        set: function (v) {
            if (this._aspect !== v) {
                this._aspect = v;
                this._projMatDirty = true;
            }
        }
    });

    /**
     * @private
     * @type Number
     * @name pc.Camera#projection
     * @description Camera's projection type, to specify whether projection is orthographic (parallel projection) or perspective. Can be:
     * <ul>
     *     <li>{@link pc.PROJECTION_PERSPECTIVE}</li>
     *     <li>{@link pc.PROJECTION_ORTHOGRAPHIC}</li>
     * </ul>
     */
    Object.defineProperty(Camera.prototype, 'projection', {
        get: function () {
            return this._projection;
        },
        set: function (v) {
            if (this._projection !== v) {
                this._projection = v;
                this._projMatDirty = true;
            }
        }
    });

    /**
     * @private
     * @type Number
     * @name pc.Camera#nearClip
     * @description Camera's distance to near clipping plane
     */
    Object.defineProperty(Camera.prototype, 'nearClip', {
        get: function () {
            return this._nearClip;
        },
        set: function (v) {
            if (this._nearClip !== v) {
                this._nearClip = v;
                this._projMatDirty = true;
            }
        }
    });

    /**
     * @private
     * @type Number
     * @name pc.Camera#farClip
     * @description Camera's distance to far clipping plane
     */
    Object.defineProperty(Camera.prototype, 'farClip', {
        get: function () {
            return this._farClip;
        },
        set: function (v) {
            if (this._farClip !== v) {
                this._farClip = v;
                this._projMatDirty = true;
            }
        }
    });

    /**
     * @private
     * @type Number
     * @name pc.Camera#fov
     * @description Camera's field of view in degrees. This angle is in degrees
     * and is measured vertically or horizontally between the sides of camera planes.
     * hirozontalFov property defines the fov axis - vertical or horizontal.
     */
    Object.defineProperty(Camera.prototype, 'fov', {
        get: function () {
            return this._fov;
        },
        set: function (v) {
            if (this._fov !== v) {
                this._fov = v;
                this._projMatDirty = true;
            }
        }
    });

    /**
     * @private
     * @type Boolean
     * @name pc.Camera#horizontalFov
     * @description Camera's horizontal or vertical field of view.
     */
    Object.defineProperty(Camera.prototype, 'horizontalFov', {
        get: function () {
            return this._horizontalFov;
        },
        set: function (v) {
            if (this._horizontalFov !== v) {
                this._horizontalFov = v;
                this._projMatDirty = true;
            }
        }
    });

    /**
     * @private
     * @type Number
     * @name pc.Camera#orthoHeight
     * @description Camera's half height of the orthographics view.
     */
    Object.defineProperty(Camera.prototype, 'orthoHeight', {
        get: function () {
            return this._orthoHeight;
        },
        set: function (v) {
            if (this._orthoHeight !== v) {
                this._orthoHeight = v;
                this._projMatDirty = true;
            }
        }
    });

    /**
     * @private
     * @type Array
     * @name pc.Camera#clearColor
     * @description Camera's clear color.
     */
    Object.defineProperty(Camera.prototype, 'clearColor', {
        get: function () {
            return this._clearOptions.color;
        },
        set: function (v) {
            this._clearOptions.color[0] = v[0];
            this._clearOptions.color[1] = v[1];
            this._clearOptions.color[2] = v[2];
            this._clearOptions.color[3] = v[3];
        }
    });

    /**
     * @private
     * @type Number
     * @name pc.Camera#clearDepth
     * @description Camera's clear depth value.
     */
    Object.defineProperty(Camera.prototype, 'clearDepth', {
        get: function () {
            return this._clearOptions.depth;
        },
        set: function (v) {
            this._clearOptions.depth = v;
        }
    });

    /**
     * @private
     * @type Number
     * @name pc.Camera#clearStencil
     * @description Camera's clear stencil value.
     */
    Object.defineProperty(Camera.prototype, 'clearStencil', {
        get: function () {
            return this._clearOptions.stencil;
        },
        set: function (v) {
            this._clearOptions.stencil = v;
        }
    });

    /**
     * @private
     * @type Number
     * @name pc.Camera#clearFlags
     * @description Camera's clear flags bits value.
     */
    Object.defineProperty(Camera.prototype, 'clearFlags', {
        get: function () {
            return this._clearOptions.flags;
        },
        set: function (v) {
            this._clearOptions.flags = v;
        }
    });

    return {
        Camera: Camera
    };
}());