import Matrix;
import Point3D;

public class Matrix3D extends Matrix{ 
	// 
	// Constructors
	//

	static Matrix identity;

	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;
	}

        public Matrix3D()                  // initialize with identity transform
	{
		super(identity);
	}

        public Matrix3D(Matrix3D copy)     // initialize with copy of source
	{
		super((Matrix)copy);
	}

        public Matrix3D(Raster r)           // initialize with a mapping from
		{                                   // canonical space to screen space
		super(4,4);
		loadIdentity();
		// 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

	}

        //
        // 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;
		    }
		}
	}

        public final void compose(Matrix3D src)                       // this = this * src
        {
        	try {multiplyTo(src);}
        	catch (SizeException e) {}
        }
        
        public void loadIdentity()                                     // this = identity
	{
		array = (float[])identity.array.clone();
	}
	
        public void translate(float tx, float ty, float tz)            // this = this * t
	{
		Matrix trans = new Matrix(identity);
		trans.set(1,4,tx);
		trans.set(2,4,ty);
		trans.set(3,4,tz);

		try {multiplyTo(trans);}
		catch (SizeException e){}
	}

        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){}
        }
        	
        public void skew(float kxy, float kxz, float kyz)              // this = this * skew
		{
		    Matrix skew = new Matrix(identity);
			skew.set(1,1,0);
			skew.set(1,2, kxy);
			skew.set(1,3,-kxz);
			skew.set(2,1,-kxy);
			skew.set(2,2,0);
			skew.set(2,3,kyz);
			skew.set(3,1,kxz);
			skew.set(3,2,-kyz);
			skew.set(3,3,0);
			
			try {multiplyTo(skew);}
			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;
		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);
		
		for ( int j = 0; j < 3; j++)
			for (int i = 0;i < 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;}
		
	}
	
        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);
	    	mat.array[0] = rx;
	    	mat.array[1] = ux;
	    	mat.array[2] = -lx;
	    	mat.array[4] = ry;
	    	mat.array[5] = uy;
	    	mat.array[6] = -ly;
	    	mat.array[8] = rz;
	    	mat.array[9] = uz;
	    	mat.array[10] = -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){}
		}
        //
        // Assume the following projection transformations
        // transform points into the canonical viewing space
        //
        public void perspective(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);
        	mat.array[0] = 2*near / rml;
        	mat.array[5] = 2*near / bmt;
        	mat.array[8] = -(left+right)/rml;
        	mat.array[9] = -(bottom+top)/bmt;
        	mat.array[10] = (far+near)/fmn;
        	mat.array[11] = 1;
        	mat.array[14] = -2*far*near/fmn;
        	mat.array[15] = 0;
        	
	    	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);
			
			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));
			
			try {multiplyTo(ortho);}
	    	catch (SizeException e){}
			
		}                               
    }
