// 3D Transformation object. By John Haggerty (Slime).
// http://www.slimeland.com/
// Created January 21, 2003

// Honestly, I think I overcomplicated this a little bit. It works fine though, so I'm not going to bother to rewrite it now.
// requires vector.js.

// (also needs ray.js for Ray.accuracy, or you can set up your own accuracy value like this:
// var Ray = new Object(); Ray.accuracy = 0.00000001;

function Transformation(vx,vy,vz,c, dontfindinverse, actualorder)
{
	if (actualorder) {
		this.vx = vx;
		this.vy = vy;
		this.vz = vz;
	}
	else {
		this.vx = new Vector(vx.x,vy.x,vz.x);
		this.vy = new Vector(vx.y,vy.y,vz.y);
		this.vz = new Vector(vx.z,vy.z,vz.z);
	}
	this.c  = c;
	this.identity = false;
	if (Vector.compare(vx,Vector.X) && Vector.compare(vy,Vector.Y) && Vector.compare(vz,Vector.Z) && Vector.compare(c,Vector.O))
		this.identity = true;
	if (typeof(dontfindinverse) == 'undefined' || !dontfindinverse) {
		if (this.identity) this.inverse = this;
		else this.inverse = Transformation.findinverse(this);
	}
}
Transformation.prototype.copy = function() {
	var usethisinverse = true;
	if (typeof(this.inverse) == 'undefined') usethisinverse = false;
	var toreturn = new Transformation(this.vx.copy(),this.vy.copy(),this.vz.copy(),this.c.copy(),usethisinverse,true);
	if (usethisinverse)
	{
		if (!this.identity)
			toreturn.inverse = new Transformation(this.inverse.vx.copy(),this.inverse.vy.copy(),this.inverse.vz.copy(),this.inverse.c.copy(),true,true);
		else
			toreturn.inverse = toreturn;
	}
	return toreturn;
}
Transformation.findinverse = function (trans)
{
	var readfrom = Transformation.reducedrowechelonform([[trans.vx.x,trans.vx.y,trans.vx.z,1,0,0],[trans.vy.x,trans.vy.y,trans.vy.z,0,1,0],[trans.vz.x,trans.vz.y,trans.vz.z,0,0,1]]);
	var vx = new Vector(readfrom[0][3],readfrom[0][4],readfrom[0][5]);
	var vy = new Vector(readfrom[1][3],readfrom[1][4],readfrom[1][5]);
	var vz = new Vector(readfrom[2][3],readfrom[2][4],readfrom[2][5]);
	var temptrans = new Transformation(new Vector(vx.x,vy.x,vz.x),new Vector(vx.y,vy.y,vz.y),new Vector(vx.z,vy.z,vz.z),new Vector(0,0,0), true);
	temptrans.c = trans.c.transformed(temptrans).neg();
	temptrans.inverse = trans;
	return temptrans;
}
Transformation.reducedrowechelonform = function (rows)
{
	var m = rows.length;
	var n = rows[0].length;
	var temp;
	for (var r=0; r<m; r++)
	{
		if (Math.abs(rows[r][r] - 0) < Ray.accuracy) { // testing against zero is mathematically correct, but the tiny errors can get magnified a *ton*; this is a small compromise.
			for (var r2=r+1; r2<m; r2++)
			{
				if (Math.abs(rows[r2][r] - 0) > Ray.accuracy)
				{
					// swap
					for (var c=0; c<n; c++)
					{
						temp = rows[r][c];
						rows[r][c] = rows[r2][c];
						rows[r2][c] = temp;
					}
					break;
				}
			}
		}
		divby = rows[r][r];
		for (var c=0; c<n; c++)
			rows[r][c] = rows[r][c] / divby;
		for (var r2=0; r2<m; r2++)
		{
			if (r2 != r)
			{
				multby = rows[r2][r];
				for (var c=r; c<n; c++)
				{
					rows[r2][c] -= rows[r][c]*multby;
				}
			}
		}
	}
	return rows;
}
Transformation.prototype.toString = function() {
	var rows = [[this.vx.x,this.vx.y,this.vx.z],[this.vy.x,this.vy.y,this.vy.z],[this.vz.x,this.vz.y,this.vz.z],[this.c.x,this.c.y,this.c.z]];
	str = '[ ';
	for (r=0; r < rows.length; r++)
	{
		for (c=0; c < rows[r].length; c++)
		{
			if (Math.abs(rows[r][c]-0) < Ray.accuracy)
				str += '0 ';
			else
				str += rows[r][c] + ' ';
		}
		if (r < rows.length-1) str += '\n  ';
	}
	return str + ' ]';
}
Transformation.multipletrans = function(transs) // this is an extremely helpful function that takes an array of transformations and turns them all into one single transformation.
{
	if (transs.length == 0) return Transformation.IdentityTrans;
	
	var currenttrans;
	if (transs.length == 1) currenttrans = transs[0].copy();
	else currenttrans = transs[0];
	
	for (a=1; a<transs.length; a++)
	{
		// multiply this one times what we already have
		currenttrans = new Transformation(
			new Vector(Vector.dot(transs[a].vx,new Vector(currenttrans.vx.x,currenttrans.vy.x,currenttrans.vz.x)), Vector.dot(transs[a].vx,new Vector(currenttrans.vx.y,currenttrans.vy.y,currenttrans.vz.y)), Vector.dot(transs[a].vx,new Vector(currenttrans.vx.z,currenttrans.vy.z,currenttrans.vz.z))),
			new Vector(Vector.dot(transs[a].vy,new Vector(currenttrans.vx.x,currenttrans.vy.x,currenttrans.vz.x)), Vector.dot(transs[a].vy,new Vector(currenttrans.vx.y,currenttrans.vy.y,currenttrans.vz.y)), Vector.dot(transs[a].vy,new Vector(currenttrans.vx.z,currenttrans.vy.z,currenttrans.vz.z))),
			new Vector(Vector.dot(transs[a].vz,new Vector(currenttrans.vx.x,currenttrans.vy.x,currenttrans.vz.x)), Vector.dot(transs[a].vz,new Vector(currenttrans.vx.y,currenttrans.vy.y,currenttrans.vz.y)), Vector.dot(transs[a].vz,new Vector(currenttrans.vx.z,currenttrans.vy.z,currenttrans.vz.z))),
			currenttrans.c.transformed(transs[a]), false, true
		);
	}
	return currenttrans;
}
Transformation.scale = function(amount)
{
	return new Transformation(Vector.scalar(Vector.X,amount.x),Vector.scalar(Vector.Y,amount.y),Vector.scalar(Vector.Z,amount.z),Vector.O);
}
Transformation.rotate = function(dim,amnt)
{
	if (dim == 0 || dim == 'x')
		return new Transformation(Vector.X,Vector.add(Vector.scalar(Vector.Y,Math.cos(amnt)),Vector.scalar(Vector.Z,Math.sin(amnt))),Vector.add(Vector.scalar(Vector.Z,Math.cos(amnt)),Vector.scalar(Vector.Y,Math.sin(amnt)).neg()),Vector.O);
	if (dim == 1 || dim == 'y')
		return new Transformation(Vector.add(Vector.scalar(Vector.X,Math.cos(amnt)),Vector.scalar(Vector.Z,Math.sin(amnt)).neg()),Vector.Y,Vector.add(Vector.scalar(Vector.Z,Math.cos(amnt)),Vector.scalar(Vector.X,Math.sin(amnt))),Vector.O);
	if (dim == 2 || dim == 'z')
		return new Transformation(Vector.add(Vector.scalar(Vector.X,Math.cos(amnt)),Vector.scalar(Vector.Y,Math.sin(amnt))),Vector.add(Vector.scalar(Vector.Y,Math.cos(amnt)),Vector.scalar(Vector.X,Math.sin(amnt)).neg()),Vector.Z,Vector.O);
}
Transformation.translate = function(amount)
{
	return new Transformation(Vector.X,Vector.Y,Vector.Z,amount);
}
Transformation.IdentityTrans = new Transformation(Vector.X,Vector.Y,Vector.Z,Vector.O);
