import Matrix;
import Point3D;

public class Matrix3D extends Matrix{ 
	// 
	// Constructors
	//

	static Matrix identity;
	static Matrix normalIdentity;
	Matrix normalTransform;

	static
	{
		identity = new Matrix(4,4);
		float newarray[] = { 1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1};
		identity.array = newarray;
		normalIdentity = new Matrix(3,3);
		float newNarray[] = { 1,0,0,0,1,0,0,0,1 };
		normalIdentity.array = newNarray;
	}

	
		public Matrix3D()                  // initialize with identity transform
	{
		super(identity);
		normalTransform = new Matrix(normalIdentity);
	}
		public Matrix3D(Matrix3D copy)     // initialize with copy of source
	{
		super((Matrix)copy);
		normalTransform = new Matrix(copy.normalTransform);
	}
		public Matrix3D(Raster r)           // initialize with a mapping from
		{                                   // canonical space to screen space
		super(identity);
		normalTransform = new Matrix(normalIdentity);
		// canonical volume is the cube (-1,-1,-1) to (1,1,1);
		// map that volume to the rectangularparallelopiped
		// (0,0,0) to (width,height,0)

		// translate to middle
		set(1,4,(r.width-1)/2);
		set(2,4,(r.height-1)/2);
		
		// scale
		set(1,1,(r.width-1)/2);    // scale x
		set(2,2,(r.height-1)/2);   // scale y

	}
		public Matrix3D(ZRaster r)           // initialize with a mapping from
		{                                   // canonical space to screen space
		super(identity);
		normalTransform = new Matrix(normalIdentity);
		// canonical volume is the cube (-1,-1,-1) to (1,1,1);
		// map that volume to the rectangularparallelopiped
		// (0,0,0) to (width,height,depth)

		// translate to middle
		set(1,4,(r.width-1)/2);
		set(2,4,(r.height-1)/2);
		set(3,4,(r.MAXZ-1)/2);
		
		// scale
		set(1,1,(r.width-1)/2);    // scale x
		set(2,2,(r.height-1)/2);   // scale y
		set(3,3,(r.MAXZ-1)/2);	   // scale z

		// Since there is no rotation, I do not need to transform the rotation matrix.

	}
		public final void compose(Matrix3D src)                       // this = this * src
		{
			try {multiplyTo(src);}
			catch (SizeException e) {}
			try {normalTransform.multiplyTo(src.normalTransform);}
			catch (SizeException e) {}
		}
		//
		// Assume the following projection transformations
		// transform points into the canonical viewing space
		//
		public void frustum(float left, float right,                // this = this * persp
								float bottom, float top,
								float near, float far)
		{
			/*
			float rml = right - left;
			float bmt = bottom - top;
			float fmn = far - near;
			
			Matrix mat = new Matrix(identity);
			Matrix normalMat = new Matrix(normalIdentity);
			mat.array[0] = normalMat.array[0] = 2*near / rml;
			mat.array[5] = normalMat.array[4] = 2*near / bmt;
			mat.array[8] = normalMat.array[6] = -(right+left)/rml;
			mat.array[9] = normalMat.array[7] = -(top+bottom)/bmt;
			mat.array[10] = normalMat.array[8] = -(far+near)/fmn;
			mat.array[11] = 1;
			mat.array[14] = -2*far*near/fmn;
			mat.array[15] = 0;
			*/

			       float t0, t1, t2, t3;

		t0 = 1f / (right - left);
		t1 = 1f / (bottom - top);
		t2 = 1f / (far - near);

		float m13 = -t0*(right + left);
		float m23 = -t1*(bottom + top);
		float m33 = t2*(far + near);

		near *= 2;
		float m11 = t0*near;
		float m22 = t1*near;
		float m34 = -t2*far*near;

		for (int i = 0; i < 16; i += 4) {
			t0 = array[i];
			t1 = array[i+1];
			t2 = array[i+2];
			array[i  ] = t0*m11;
			array[i+1] = t1*m22;
			array[i+2] = t0*m13 + t1*m23 + t2*m33 + array[i+3];
			array[i+3] = t2*m34;
		}
		
/*	    	try {multiplyTo(mat);}
	    	catch (SizeException e){}
	    	try {normalTransform.multiplyTo(normalMat);}
	    	catch (SizeException e){}*/

		}
		public void loadIdentity()                                     // this = identity
	{
		array = (float[])identity.array.clone();
		normalTransform.array = (float[])normalIdentity.array.clone();
	}
		public void lookAt(float eyex, float eyey, float eyez,
						   float atx,  float aty,  float atz,
						   float upx,  float upy,  float upz)          // this = this * lookat
	    {
	    	float lx = atx - eyex;
	    	float ly = aty - eyey;
	    	float lz = atz - eyez;
	    	
	    	float scale = (float)Math.sqrt( lx*lx + ly*ly + lz*lz);
	    	lx /= scale;
	    	ly /= scale;
	    	lz /= scale;
	    	
	    	// now, the l vector cross the up vector is the r vector;
	    	
	    	float rx = ly*upz - lz*upy;
	    	float ry = lz*upx - lx*upz;
	    	float rz = lx*upy - ly*upx;

	    	scale = (float)Math.sqrt( rx*rx + ry*ry + rz*rz);
	    	rx /= scale;
	    	ry /= scale;
	    	rz /= scale;
	    	
	    	// now, the r vector cross the n vector gives the u vector;
	    	
			float ux = ry*lz - rz*ly;
	    	float uy = rz*lx - rx*lz;
	    	float uz = rx*ly - ry*lx;
	    	
	    	scale = (float)Math.sqrt( ux*ux + uy*uy + uz*uz);
	    	ux /= scale;
	    	uy /= scale;
	    	uz /= scale;
	    	
	    	//The new vector is the combination of the rotation and translation matrices
	    	
	    	Matrix mat = new Matrix(identity);
	    	Matrix normalMat = new Matrix(normalIdentity);
	    	mat.array[0] = normalMat.array[0] = rx;
	    	mat.array[1] = normalMat.array[1] = ux;
	    	mat.array[2] = normalMat.array[2] = -lx;
	    	mat.array[4] = normalMat.array[3] = ry;
	    	mat.array[5] = normalMat.array[4] = uy;
	    	mat.array[6] = normalMat.array[5] = -ly;
	    	mat.array[8] = normalMat.array[6] = rz;
	    	mat.array[9] = normalMat.array[7] = uz;
	    	mat.array[10] = normalMat.array[8] = -lz;
	    	mat.array[12] = -eyex*rx-eyey*ry-eyez*rz;
	    	mat.array[13] = -eyex*ux-eyey*uy-eyez*uz;
	    	mat.array[14] = eyex*lx+eyey*ly+eyez*lz;
	    		    	
	    	try {multiplyTo(mat);}
	    	catch (SizeException e){}
		}
		public void orthographic(float left, float right,               // this = this * ortho
								 float bottom, float top,
								 float near, float far)
		{
			float scalex = 2/(right - left);
			float scaley = 2/(bottom - top);
			float scalez = 2/(far - near);
			
			Matrix ortho = new Matrix(identity);
			Matrix orthoNormal = new Matrix(normalIdentity); //sic
			
			ortho.set(1,1,scalex);
			ortho.set(2,2,scaley);
			ortho.set(3,3,scalez);
			ortho.set(1,4,scalex/-2*(right+left));
			ortho.set(2,4,scaley/-2*(bottom+top));
			ortho.set(3,4,scalez/-2*(far-near));
			orthoNormal.set(1,1,scalex);
			orthoNormal.set(2,2,scaley);
			orthoNormal.set(3,3,scalez);
			
			try {multiplyTo(ortho);}
	    	catch (SizeException e){}
	    	try {normalTransform.multiplyTo(orthoNormal);}
	    	catch (SizeException e){}
			
			
		}
		public void rotate(float ax, float ay, float az, float angle)  // this = this * rotate 
	{
		Matrix temp1, temp2;
		Matrix symmetric;
		Matrix skew;
		Matrix rotation;
		Matrix rotationNormal;
		float cos0 = (float)Math.cos(angle);
		float sin0 = (float)Math.sin(angle);

		// the vector must be normalized
		float scale = (float)Math.sqrt(ax*ax+ay*ay+az*az);
		ax = ax / scale;
		ay = ay / scale;
		az = az / scale;
		
		temp1 = new Matrix(1,3);
		temp1.set(1,1,ax);
		temp1.set(1,2,ay);
		temp1.set(1,3,az);
		temp2 = new Matrix(3,1);
		temp2.set(1,1,ax);
		temp2.set(2,1,ay);
		temp2.set(3,1,az);
				try 
		{
		    symmetric = temp2.multiply(temp1);
		    symmetric.multiplyScalar(1 - cos0);
		}
		catch (SizeException e) {return;}

		skew = new Matrix(3,3);
		skew.set(1,1,0);
		skew.set(1,2,-az);
		skew.set(1,3,ay);
		skew.set(2,1,az);
		skew.set(2,2,0);
		skew.set(2,3,-ax);
		skew.set(3,1,-ay);
		skew.set(3,2,ax);
		skew.set(3,3,0);

		skew.multiplyScalar(sin0);

		rotation = new Matrix(identity);
		rotation.multiplyScalar(cos0);
		rotationNormal = new Matrix(normalIdentity);
		rotationNormal.multiplyScalar(cos0);

		for ( int j = 0; j < 3; j++)
			for (int i = 0;i < 3; i++)
			{
			rotationNormal.array[j*3+i] = rotation.array[j*4+i] += symmetric.array[j*3+i] 
						+ skew.array[j*3+i];
			}
		rotation.set(4,4,1);
		try
		{	this.multiplyTo(rotation);}
		catch (SizeException e) { return;}
		try
		{	normalTransform.multiplyTo(rotationNormal);}
		catch (SizeException e) { return;}
	}
		public void scale(float sx, float sy, float sz)                // this = this * scale
		{
			Matrix scaleMat = new Matrix(identity);
			scaleMat.set(1,1,sx);
			scaleMat.set(2,2,sy);
			scaleMat.set(3,3,sz);
			try {multiplyTo(scaleMat);}
			catch (SizeException e){}
		}
		//
		// Transform points from the in array to the out array
		// using the current matrix. The subset of points transformed
		// begins at the start index and has the specified length
		public void transform(Point3D in[], Point3D out[], int length)
	{
		for (int i = 0; i < length; i ++)
		{
		    try{
			out[i] = new Point3D(this.multiply(in[i]));
			}
		    catch (SizeException e)
		    {
			return;
		    }
		}
	}
		//
		// Transform points from the in array to the out array
		// using the current matrix. The subset of points transformed
		// begins at the start index and has the specified length
		public void transform(Point3D in[], Point3D out[], int start, int length)
	{
		for (int i = 0; i < length; i ++)
		{
		    try{
			out[start+i] = new Point3D(this.multiply(in[start+i]));
			}
		    catch (SizeException e)
		    {
			return;
		    }
		}
	}
		//
		// Transform points from the in array to the out array
		// using the current matrix. The subset of points transformed
		// begins at the start index and has the specified length
		public void transform(Vertex3D in[], Vertex3D out[], int length)
	{
		for (int i = 0; i < length; i ++)
		{
		    try{
			out[i] = new Vertex3D(this.multiply(in[i]));
			}
		    catch (SizeException e)
		    {
			return;
		    }
		}
	}
		//
		// Transform points from the in array to the out array
		// using the current matrix. The subset of points transformed
		// begins at the start index and has the specified length
		public Vertex3D transform(Vertex3D in)
	{
		Vertex3D out;
			
		try{out = new Vertex3D(this.multiply(in));}
		catch (SizeException e){return null;}
		
		return out;
	}
		public void translate(float tx, float ty, float tz)            // this = this * t
	{
		Matrix trans = new Matrix(identity);
		trans.set(4,1,tx);
		trans.set(4,2,ty);
		trans.set(4,3,tz);

		try {multiplyTo(trans);}
		catch (SizeException e){}
	}
}