Source: Cameras/Camera.js

/* 
 * File: Camera.js
 * Encapsulates the user define WC and Viewport functionality
 */

/*jslint node: true, vars: true, bitwise: true */
/*global gEngine, SimpleShader, Renderable, mat4, vec2, vec3, BoundingBox, CameraState */
/* find out more about jslint: http://www.jslint.com/help.html */
"use strict";

/**
 * Information to be updated once per render for efficiency concerns
 * @class PerRenderCache
 * @returns {PerRenderCache} New instance of PerRenderCache
 */
function PerRenderCache() {
    this.mWCToPixelRatio = 1;  // WC to pixel transformation
    this.mCameraOrgX = 1; // Lower-left corner of camera in WC 
    this.mCameraOrgY = 1;
    this.mCameraPosInPixelSpace = vec3.fromValues(0, 0, 0); //
}

/**
 * Default Constructor<p>
 * Height of the user defined WC is implicitly defined by the viewport aspect ratio<p>
 * viewportRect: an array of 4 elements<p>
 *      [0] [1]: (x,y) position of lower left corner on the canvas (in pixel)<p>
 *      [2]: width of viewport<p>
 *      [3]: height of viewport
 * @class Camera
 * @param {vec2} wcCenter Center position of Camera
 * @param {Number} wcWidth Width of Camera
 * @param {Float[]} viewportArray position and size of viewport [x, y, width, height]
 * @param {Number} bound viewport border
 * @returns {Camera} New instance of Camera
 */
function Camera(wcCenter, wcWidth, viewportArray, bound) {
    // WC and viewport position and size
    this.mCameraState = new CameraState(wcCenter, wcWidth);
    this.mCameraShake = null;

    this.mViewport = [];  // [x, y, width, height]
    this.mViewportBound = 0;
    if (bound !== undefined) {
        this.mViewportBound = bound;
    }
    this.mScissorBound = [];  // use for bounds
    this.setViewport(viewportArray, this.mViewportBound);
    this.mNearPlane = 0;
    this.mFarPlane = 1000;
    
    this.kCameraZ = 10;  // This is for illumination computation
    
    // transformation matrices
    this.mViewMatrix = mat4.create();
    this.mProjMatrix = mat4.create();
    this.mVPMatrix = mat4.create();

    // background color
    this.mBgColor = [0.8, 0.8, 0.8, 1]; // RGB and Alpha

    // per-rendering cached information
    // needed for computing transforms for shaders
    // updated each time in SetupViewProjection()
    this.mRenderCache = new PerRenderCache();
        // SHOULD NOT be used except 
        // xform operations during the rendering
        // Client game should not access this!
}

/**
 * Viewport enum
 * @memberOf Camera
 * @type {enum|eViewport}
 */
Camera.eViewport = Object.freeze({
    eOrgX: 0,
    eOrgY: 1,
    eWidth: 2,
    eHeight: 3
});

// <editor-fold desc="Public Methods">
// <editor-fold desc="Getter/Setter">
// <editor-fold desc="setter/getter of WC and viewport">
/**
 * Set the World Coordinate center position
 * @memberOf Camera
 * @param {Number} xPos World Coordinate X position
 * @param {Number} yPos World Coordinate Y position
 * @returns {void}
 */
Camera.prototype.setWCCenter = function (xPos, yPos) {
    var p = vec2.fromValues(xPos, yPos);
    this.mCameraState.setCenter(p);
};

/**
 * Return Camera position in pixel space
 * @memberOf Camera
 * @returns {vec3} Camera position in pixel space
 */
Camera.prototype.getPosInPixelSpace = function () { return this.mRenderCache.mCameraPosInPixelSpace; };

/**
 * Return the World Coordinate center position
 * @memberOf Camera
 * @returns {vec2} World Coordinate center position
 */
Camera.prototype.getWCCenter = function () { return this.mCameraState.getCenter(); };

/**
 * Set the World Coordinate width
 * @memberOf Camera
 * @param {Number} width of the World Coordinate
 * @returns {void}
 */
Camera.prototype.setWCWidth = function (width) { this.mCameraState.setWidth(width); };

/**
 * Return the width of the World Coordinate
 * @memberOf Camera
 * @returns {Number} width of the World Coordinate
 */
Camera.prototype.getWCWidth = function () { return this.mCameraState.getWidth(); };

/**
 * Return the height of the World Coordinate
 * @memberOf Camera
 * @returns {Number} height of the World Coordinate
 */
Camera.prototype.getWCHeight = function () { return this.mCameraState.getWidth() * this.mViewport[Camera.eViewport.eHeight] / this.mViewport[Camera.eViewport.eWidth]; };
                                                                                                        // viewportH/viewportW
/**
 * Set the Camera viewport
 * @memberOf Camera
 * @param {Float[]} viewportArray viewportArray position and size of viewport [x, y, width, height]
 * @param {Number} bound viewport border
 * @returns {void}
 */
Camera.prototype.setViewport = function (viewportArray, bound) {
    if (bound === undefined) {
        bound = this.mViewportBound;
    }
    // [x, y, width, height]
    this.mViewport[0] = viewportArray[0] + bound;
    this.mViewport[1] = viewportArray[1] + bound;
    this.mViewport[2] = viewportArray[2] - (2 * bound);
    this.mViewport[3] = viewportArray[3] - (2 * bound);
    this.mScissorBound[0] = viewportArray[0];
    this.mScissorBound[1] = viewportArray[1];
    this.mScissorBound[2] = viewportArray[2];
    this.mScissorBound[3] = viewportArray[3];
};

/**
 * Return the Camera viewport
 * @memberOf Camera
 * @returns {Array} camera Viewport [x, y, width, height]
 */
Camera.prototype.getViewport = function () {
    var out = [];
    out[0] = this.mScissorBound[0];
    out[1] = this.mScissorBound[1];
    out[2] = this.mScissorBound[2];
    out[3] = this.mScissorBound[3];
    return out; 
};
//</editor-fold>

//<editor-fold desc="setter/getter of wc background color">
/**
 * Set the background color of the Camera
 * @memberOf Camera
 * @param {Float[]} newColor new color of the background
 * @returns {void}
 */
Camera.prototype.setBackgroundColor = function (newColor) { this.mBgColor = newColor; };

/**
 * Return the background color of the Camera
 * @memberOf Camera
 * @returns {Float[]}
 */
Camera.prototype.getBackgroundColor = function () { return this.mBgColor; };

/**
 * Return the View-Projection transform operator
 * @memberOf Camera
 * @returns {mat4} View-Projection transform
 */
Camera.prototype.getVPMatrix = function () {
    return this.mVPMatrix;
};
// </editor-fold>
// </editor-fold>

/**
 * Initializes the camera to begin drawing
 * @memberOf Camera
 * @returns {void}
 */
Camera.prototype.setupViewProjection = function () {
    var gl = gEngine.Core.getGL();
    //<editor-fold desc="Step A: Set up and clear the Viewport">
    // Step A1: Set up the viewport: area on canvas to be drawn
    gl.viewport(this.mViewport[0],  // x position of bottom-left corner of the area to be drawn
                this.mViewport[1],  // y position of bottom-left corner of the area to be drawn
                this.mViewport[2],  // width of the area to be drawn
                this.mViewport[3]); // height of the area to be drawn
    // Step A2: set up the corresponding scissor area to limit the clear area
    gl.scissor(this.mScissorBound[0], // x position of bottom-left corner of the area to be drawn
               this.mScissorBound[1], // y position of bottom-left corner of the area to be drawn
               this.mScissorBound[2], // width of the area to be drawn
               this.mScissorBound[3]);// height of the area to be drawn
    // Step A3: set the color to be clear
    gl.clearColor(this.mBgColor[0], this.mBgColor[1], this.mBgColor[2], this.mBgColor[3]);  // set the color to be cleared
    // Step A4: enable the scissor area, clear, and then disable the scissor area
    gl.enable(gl.SCISSOR_TEST);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.disable(gl.SCISSOR_TEST);
    //</editor-fold>

    //<editor-fold desc="Step  B: Set up the View-Projection transform operator"> 
    // Step B1: define the view matrix
    var center = [];
    if (this.mCameraShake !== null) {
        center = this.mCameraShake.getCenter();
    } else {
        center = this.getWCCenter();
    }

    mat4.lookAt(this.mViewMatrix,
        [center[0], center[1], this.kCameraZ],   // WC center
        [center[0], center[1], 0],    // 
        [0, 1, 0]);     // orientation

    // Step B2: define the projection matrix
    var halfWCWidth = 0.5 * this.getWCWidth();
    var halfWCHeight = 0.5 * this.getWCHeight(); // 
    mat4.ortho(this.mProjMatrix,
        -halfWCWidth,   // distance to left of WC
         halfWCWidth,   // distance to right of WC
        -halfWCHeight,  // distance to bottom of WC
         halfWCHeight,  // distance to top of WC
         this.mNearPlane,   // z-distance to near plane 
         this.mFarPlane  // z-distance to far plane 
        );

    // Step B3: concatenate view and project matrices
    mat4.multiply(this.mVPMatrix, this.mProjMatrix, this.mViewMatrix);
    //</editor-fold>

    // Step B4: compute and cache per-rendering information
    this.mRenderCache.mWCToPixelRatio = this.mViewport[Camera.eViewport.eWidth] / this.getWCWidth();
    this.mRenderCache.mCameraOrgX = center[0] - (this.getWCWidth() / 2);
    this.mRenderCache.mCameraOrgY = center[1] - (this.getWCHeight() / 2);
    var p = this.wcPosToPixel(this.getWCCenter());
    this.mRenderCache.mCameraPosInPixelSpace[0] = p[0];
    this.mRenderCache.mCameraPosInPixelSpace[1] = p[1];
    this.mRenderCache.mCameraPosInPixelSpace[2] = this.fakeZInPixelSpace(this.kCameraZ);
};

/**
 * Check if parameter transform collides with the camera border
 * @memberOf Camera
 * @param {Transform} aXform to check collision status
 * @param {Float} zone distance from the camera border to collide with
 * @returns {eboundCollideStatus} Collision status of the parameter transform and Camera
 */
Camera.prototype.collideWCBound = function (aXform, zone) {
    var bbox = new BoundingBox(aXform.getPosition(), aXform.getWidth(), aXform.getHeight());
    var w = zone * this.getWCWidth();
    var h = zone * this.getWCHeight();
    var cameraBound = new BoundingBox(this.getWCCenter(), w, h);
    return cameraBound.boundCollideStatus(bbox);
};

/**
 * prevents the xform from moving outside of the WC boundary<p>
 * by clamping the aXfrom at the boundary of WC
 * @memberOf Camera
 * @param {Transform} aXform to check collision status
 * @param {Float} zone distance from the camera border to collide with
 * @returns {eboundCollideStatus} Collision status of the parameter transform and Camera
 */
Camera.prototype.clampAtBoundary = function (aXform, zone) {
    var status = this.collideWCBound(aXform, zone);
    if (status !== BoundingBox.eboundCollideStatus.eInside) {
        var pos = aXform.getPosition();
        if ((status & BoundingBox.eboundCollideStatus.eCollideTop) !== 0) {
            pos[1] = (this.getWCCenter())[1] + (zone * this.getWCHeight() / 2) - (aXform.getHeight() / 2);
        }
        if ((status & BoundingBox.eboundCollideStatus.eCollideBottom) !== 0) {
            pos[1] = (this.getWCCenter())[1] - (zone * this.getWCHeight() / 2) + (aXform.getHeight() / 2);
        }
        if ((status & BoundingBox.eboundCollideStatus.eCollideRight) !== 0) {
            pos[0] = (this.getWCCenter())[0] + (zone * this.getWCWidth() / 2) - (aXform.getWidth() / 2);
        }
        if ((status & BoundingBox.eboundCollideStatus.eCollideLeft) !== 0) {
            pos[0] = (this.getWCCenter())[0] - (zone * this.getWCWidth() / 2) + (aXform.getWidth() / 2);
        }
    }
    return status;
};
//</editor-fold>