/* <copyright>
This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
(c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
</copyright> */
/* 
 * camera class 
 */
var RDGE = RDGE || {};

RDGE.camera = function () {
    this.proj = RDGE.mat4.identity();
    this.view = RDGE.mat4.identity();
    this.world = RDGE.mat4.identity();
    this.viewProj = RDGE.mat4.identity();
    this.invViewProj = RDGE.mat4.identity();
    this.frustum = [];
    this.frustumPts = [];
    this.controller = null;

    this.setPerspective = function (fov, aratio, near, far) {
        this.ortho = null;
        this.persp = {};
        this.persp.fov = fov;
        this.persp.aratio = aratio;
        this.persp.near = near;
        this.persp.far = far;
        this.proj = RDGE.mat4.perspective(fov, aratio, near, far);
        this.recalc();
    };

    this.reset = function () {
        this.world = RDGE.mat4.identity();
        this.recalc();
    };

    this.copy = function (cam) {
        RDGE.mat4.inplace_copy(this.view, cam.view);
        RDGE.mat4.inplace_copy(this.world, cam.world);
        RDGE.mat4.inplace_copy(this.proj, cam.proj);
        RDGE.mat4.inplace_copy(this.viewProj, cam.viewProj);
        RDGE.mat4.inplace_copy(this.invViewProj, cam.invViewProj);
        this.frustum = cam.frustum.slice();
        this.frustumPts = cam.frustumPts.slice();
    };

    this.recalc = function () {
        // update frustum planes
        this.frustum = [];
        var vp = this.viewProj;

        normalizePlane = function (p) {
            var len = RDGE.vec3.length(p);
            if (Math.abs(1.0 - len) > 0.001) {
                p[0] /= len;
                p[1] /= len;
                p[2] /= len;
                p[3] /= len;
            }
            return p;
        };

        /* This is the old way 
        var t = this.persp.near * Math.tan(0.017453292519943295769236 * this.persp.fov * 0.5);
        var r = t * this.persp.aratio;
        var u = t;
        var l = -r;
        var b = -u;

        tview = RDGE.mat4.transpose(this.view);
        this.frustum.push( normalizePlane( RDGE.mat4.transformPoint(tview, [this.persp.near, 0.0, l, 0.0] ) ) );     // left
        this.frustum.push( normalizePlane( RDGE.mat4.transformPoint(tview, [-this.persp.near, 0.0, -r, 0.0] ) ) );   // right
        this.frustum.push( normalizePlane( RDGE.mat4.transformPoint(tview, [0.0, this.persp.near, b, 0.0] ) ) );     // bottom
        this.frustum.push( normalizePlane( RDGE.mat4.transformPoint(tview, [0.0, -this.persp.near, -u, 0.0] ) ) );   // top
        this.frustum.push( normalizePlane( RDGE.mat4.transformPoint(tview, [0.0, 0.0, -1.0, -this.persp.near] ) ) ); // near
        this.frustum.push( normalizePlane( RDGE.mat4.transformPoint(tview, [0.0, 0.0, 1.0, this.persp.far] ) ) );    // far
        */
        var l = normalizePlane([vp[3] + vp[0], vp[7] + vp[4], vp[11] + vp[8], vp[15] + vp[12]]);
        var r = normalizePlane([vp[3] - vp[0], vp[7] - vp[4], vp[11] - vp[8], vp[15] - vp[12]]);
        var t = normalizePlane([vp[3] - vp[1], vp[7] - vp[5], vp[11] - vp[9], vp[15] - vp[13]]);
        var b = normalizePlane([vp[3] + vp[1], vp[7] + vp[5], vp[11] + vp[9], vp[15] + vp[13]]);
        var n = normalizePlane([vp[3] + vp[2], vp[7] + vp[6], vp[11] + vp[10], vp[15] + vp[14]]);
        var f = normalizePlane([vp[3] - vp[2], vp[7] - vp[6], vp[11] - vp[10], vp[15] - vp[14]]);

        this.frustum.push(l);
        this.frustum.push(r);
        this.frustum.push(t);
        this.frustum.push(b);
        this.frustum.push(n);
        this.frustum.push(f);

        // update frustum points
        this.frustumPts = [];
        var invvp = this.viewProj;
        this.frustumPts.push(RDGE.mat4.transformPoint(invvp, [-1, -1, -1]));
        this.frustumPts.push(RDGE.mat4.transformPoint(invvp, [-1, 1, -1]));
        this.frustumPts.push(RDGE.mat4.transformPoint(invvp, [1, 1, -1]));
        this.frustumPts.push(RDGE.mat4.transformPoint(invvp, [1, -1, -1]));
        this.frustumPts.push(RDGE.mat4.transformPoint(invvp, [-1, -1, 1]));
        this.frustumPts.push(RDGE.mat4.transformPoint(invvp, [-1, 1, 1]));
        this.frustumPts.push(RDGE.mat4.transformPoint(invvp, [1, 1, 1]));
        this.frustumPts.push(RDGE.mat4.transformPoint(invvp, [1, -1, 1]));
    };

    this.setWorld = function (m) {
        this.world = m;
        this.view = RDGE.mat4.inverse(m);
        this.viewProj = RDGE.mat4.mul(this.view, this.proj);
        this.invViewProj = RDGE.mat4.inverse(this.viewProj);
        this.recalc();
    };

    this.setView = function (m) {
        this.view = m;
        this.world = RDGE.mat4.inverse(m);
        this.viewProj = RDGE.mat4.mul(this.view, this.proj);
        this.invViewProj = RDGE.mat4.inverse(this.viewProj);
        this.recalc();
    };

    this.setLookAt = function (eyePos, targetPos, upVec) {
        this.setWorld(RDGE.mat4.lookAt(eyePos, targetPos, upVec));
        //this.recalc();
    };

    this.setPerspective = function (fov, aratio, near, far) {
        this.ortho = null;
        this.persp = {};
        this.persp.fov = fov;
        this.persp.aratio = aratio;
        this.persp.near = near;
        this.persp.far = far;
        this.proj = RDGE.mat4.perspective(fov, aratio, near, far);
        this.recalc();
    };

    this.setOrthographic = function (l, r, t, b, n, f) {
        this.persp = null;
        this.ortho = {};
        this.ortho.left = l;
        this.ortho.right = r;
        this.ortho.top = t;
        this.ortho.bottom = b;
        this.ortho.near = n;
        this.ortho.far = f;
        this.proj = RDGE.mat4.orthographic(l, r, t, b, n, f);
        this.recalc();
    };

    this.onResize = function (x, y, width, height) {
        if (this.persp) {
            this.setPerspective(this.persp.fov, width / height, this.persp.near, this.persp.far);
        }
        if (this.ortho) {
            this.setOrthographic(x, x + width, y, y + height, this.ortho.near, this.ortho.far);
        }
    };

    this.zNear = function () {
        if (this.persp) {
            return this.persp.near;
        }

        if (this.ortho) {
            return this.ortho.near;
        }

        return 0.0;
    };

    this.zFar = function () {
        if (this.persp) {
            return this.persp.far;
        }

        if (this.ortho) {
            return this.ortho.far;
        }

        return 0.0;
    };

    // this is used by ambient occlusion... 
    this.getFTR = function () {
        var fovyRad = (this.persp.fov * 0.5) * Math.PI / 180.0;
        return [
		Math.tan(fovyRad) * this.persp.far,
		Math.tan(fovyRad / this.persp.aratio) * this.persp.far,
		this.persp.far];
    };

    this.attachCameraToNode = function (node) {
        this.controller = node;
    };
};


/**	Camera Manager
 * This class is used to manage the active camera. It provides functionality 
 * for getting and setting the active camera, as well as providing stack operations 
 * to switch to and from multiple cameras. 
 */
RDGE.cameraManager = function () {
    this.stack = [];

    /* Set the active camera.
    * This function sets the active camera to the given camera. 
    */
    this.setActiveCamera = function (c) {
        // pop the active camera off the stack.
        if (this.stack.length > 0) {
            this.stack.pop();
        }
        // push the given camera onto the stack.
        this.stack.push(c);
    };

    /* Get the active camera. 
    * The active camera always resides at the top of the stack. 
    */
    this.getActiveCamera = function () {
        if (this.stack.length > 0) {
            return this.stack[this.stack.length - 1];
        } else {
            return null;
        }
    };

    /* Push a camera onto the stack. 
    * The given camera becomes the active camera.
    */
    this.pushCamera = function (c) {
        this.stack.push(c);
    };

    /* Pop a camera off the stack. 
    * Returns the camera that was popped.
    * The next camera on the stack becomes active.
    */
    this.popCamera = function () {
        return this.stack.pop();
    };

    this.onResize = function (x, y, w, h) {
        var i = this.stack.length - 1;
        while (i >= 0) {
            this.stack[i].onResize(x, y, w, h);
            i--;
        }
    };
};