// Javascript library basic 3d rendering using text.
// v0.1a
//
// Copyright (C) 2007  W. Xavier Snelgrove
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
// http://www.wxs.ca/gpl.txt

var PHI = 1.61803398874989484820458683;
var PHI_INV = 1/PHI;


// This function multiplies a 4x4 matrix by a 4x1 vector
function multV(m,v0,v1,v2,v3) {
    // Initialize the result vector.
    var result = new Array(3);

    // Perform the multiplication
    result[0] = m[0][0]*v0 + m[0][1]*v1 + m[0][2]*v2 + m[0][3]*v3;
    result[1] = m[1][0]*v0 + m[1][1]*v1 + m[1][2]*v2 + m[1][3]*v3;
    result[2] = m[2][0]*v0 + m[2][1]*v1 + m[2][2]*v2 + m[2][3]*v3;
    result[3] = m[3][0]*v0 + m[3][1]*v1 + m[3][2]*v2 + m[3][3]*v3;
    return result;
}

// This funciton returns the identity matrix
function identity() {
    return [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]];
}


// This class will represent a single stand-alone renderer.
function JS3D(renderDiv, maxZ) {

    // The div in which all will be drawn.
    this.rField=document.getElementById(renderDiv);

    // xCut is the maximum displayable z value.
    if (maxZ) this.maxZ = maxZ;
    else this.maxZ = 0;
    // Find the coordinates of the canvas thing.
    this.xPos = this.rField.style.left.substring(0,this.rField.style.left.indexOf("px"));
    this.yPos = this.rField.style.top.substring(0,this.rField.style.left.indexOf("px"));

    this.width = this.rField.style.width.substring(0,this.rField.style.width.indexOf("px"));
//  alert("width"+this.rField.style.width);
    this.height = this.rField.style.height.substring(0,this.rField.style.height.indexOf("px"));

    //this.width = 800;
    //this.height = 800;

    this.resetMatrix();

    // This array will contain all the points to be drawn.
    this.points = new Array();

    this.debug = false;
}

JS3D.prototype.rotateX = rotateX;
JS3D.prototype.rotateY = rotateY;
JS3D.prototype.rotateZ = rotateZ;
JS3D.prototype.scale = scale;
JS3D.prototype.translate = translate;
JS3D.prototype.addPoint = addPoint;
JS3D.prototype.addPointV = addPointV;
JS3D.prototype.clearPoints = clearPoints;
JS3D.prototype.drawPoint = drawPoint;
JS3D.prototype.paint = paint;
JS3D.prototype.resetMatrix = resetMatrix;
JS3D.prototype.addLine = addLine;
JS3D.prototype.setDebug = setDebug;

function setDebug(mode) {
    this.debug = mode;
}

function resetMatrix() {
    // translate the axes to the center of the canvas.
    this.matrix = identity();
    this.matrix = this.translate(this.width/2,this.height/2,0);
}
function rotateX(angle,m) {
    if(!m) m = this.matrix;
    var c = Math.cos(angle);
    var s = Math.sin(angle);
    return mult(m,[[1, 0, 0, 0],[0, c, -s, 0],[0, s, c, 0],[0, 0, 0, 1]]);
}
function rotateY(angle,m) {
    if(!m) m = this.matrix;
    var c = Math.cos(angle);
    var s = Math.sin(angle);
    return mult(m,[[c, 0, s, 0],[0, 1, 0, 0],[-s, 0, c, 0],[0, 0, 0, 1]]);
}
function rotateZ(angle,m) {
    if(!m) m = this.matrix;
    var c = Math.cos(angle);
    var s = Math.sin(angle);
    return mult(m,[[c, -s, 0, 0],[s, c, 0, 0],[0, 0, 1, 0],[0, 0, 0, 1]]);
}
function scale(scaleX,scaleY,scaleZ,m) {
    if(!m) m = this.matrix;
    return mult(m,[[scaleX,0,0,0],[0,scaleY,0,0],[0,0,scaleZ,0],[0,0,0,1]]);
}

function translate(dX, dY, dZ,m) {
    if(!m) m = this.matrix;
    return mult(m,[[1,0,0,dX],[0,1,0,dY],[0,0,1,dZ],[0,0,0,1]]);
}

function addPoint(x,y,z,text,m) {
    if(!m) m=identity();
    var v = multV(m,x,y,z,1);
    var l = this.points.length;
    this.points[l]=new Point(v[0],v[1],v[2],text);
    if(this.debug) {
        this.points[l].div.style.border = "1px solid #000";
    }
    this.rField.appendChild(this.points[l].div);
    return this.points[l];
}
function addPointV(v,text) {
    this.addPoint(v[0],v[1],v[2],text);
}
function clearPoints() {
    /*
    for (var i = 0; i <this.points.length; i++) {
        this.rField.removeChild(this.points[i].div);
    }
    */
    this.points.length=0;
}
function drawPoint(index) {
    // The transformed position of this point.
    var tPoint = multV(this.matrix,this.points[index].x,this.points[index].y,this.points[index].z,this.points[index].W);

    var fontSize = parseInt(500/(-tPoint[2]));

    if(fontSize>50) {
        // max
        fontSize = 50;
    }
    if(fontSize<10) {
        // min
        fontSize = 10;
    }

    var div = this.points[index].div;

    if(this.debug) {
        div.innerHTML = "X<br />"+fontSize+"/"+parseInt(tPoint[0])+"x"+parseInt(tPoint[1])+"x"+parseInt(tPoint[2]);
        div.style.border = "1px solid #000";
    }

    // make sure we don't draw outside the canvas
    if (tPoint[0] < 0 ||
        tPoint[0]+(fontSize*div.innerHTML.length) > this.width ||
        tPoint[1] < 0 ||
        tPoint[1]+fontSize > this.height ||
        tPoint[2] > this.maxZ) {
        //console.log(tPoint);
        if(this.debug) {
            div.style.border = "1px solid red";
        } else {
            div.style.visibility = "hidden";
        }
        return;
    }
    div.style.visibility = "visible";
    div.style.left=tPoint[0]+"px";
    div.style.top=tPoint[1]+"px";
    div.style.fontSize = fontSize+"px";
}
// This function paints each point onto the screen.
function paint() {
    for(var i = 0; i<this.points.length; i++) {
        this.drawPoint(i);
    }
}

// This function multiplies two 4x4 matrices
function mult(m1, m2) {
    //Initialize the result matrix
    var result = new Array(4);
    for (var i = 0; i<4;i++) {
        result[i] = new Array(4);
    }

    // Perform the multiplication.
    for(var i = 0; i < 3; i++){
        result[i][0] = m1[i][0] * m2[0][0] + m1[i][1] * m2[1][0] + m1[i][2] * m2[2][0] + m1[i][3] * m2[3][0];
        result[i][1] = m1[i][0] * m2[0][1] + m1[i][1] * m2[1][1] + m1[i][2] * m2[2][1] + m1[i][3] * m2[3][1];
        result[i][2] = m1[i][0] * m2[0][2] + m1[i][1] * m2[1][2] + m1[i][2] * m2[2][2] + m1[i][3] * m2[3][2];
        result[i][3] = m1[i][0] * m2[0][3] + m1[i][1] * m2[1][3] + m1[i][2] * m2[2][3] + m1[i][3] * m2[3][3];
    }
    return result;
}


function Point(x, y, z, text) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.W = 1;
    if(text) {
        this.label = text;
    } else {
        this.label = "*";
    }
    this.div = document.createElement("div");
    this.div.innerHTML = this.label;
    this.div.style.position = "absolute";
    this.div.style.fontSize = "10px";
    this.div.style.fontFamily = "HelveticaNeueLT65MediumRg";
}
// Draws a line from the given point to the next.
function addLine(x1,y1,z1,x2,y2,z2,num,text,m) {
    if(!text) text="*";
    if(!m) m=identity();
    var dx = x2-x1;
    var dy = y2-y1;
    var dz = z2-z1;
    var length = Math.sqrt(dx*dx+dy*dy+dz*dz);
    for(var i = 0; i <= num; i++) {
        var px = x1+dx*i/num;
        var py = y1+dy*i/num;
        var pz = z1+dz*i/num;
        this.addPoint(px,py,pz,text,m);
    }
}
