/*
 * pMatrix.java		v0.01	May 16th, 1998
 *
 * A general purpose (rotation and translation) matrix.
 * (05/16/1998,05/17/1998)
 *
 * Copyright(c) 1998, Alex S, Particle
 */

import java.lang.String;
import java.util.Vector;

/**
 * pMatrix class, to handle all the matrix thingies.
 * the class represents a 4x4 matrix, with some 
 * useful operations defined on it.
 *
 * Note: This thing is Right Handed!; just like OpenGL :-)
 */
public class pMatrix{

	/**
	 * the matrix stored as 2D array.
	 */
	protected double[][] matrix;
	
	/**
	 * the stack to hold working matrices.
	 */
	protected static Vector stack;
	
	/**
	 * get the stack going...
	 */
	static{
		stack = new Vector();
	}

	/**
	 * default constructor, initializes the matrix, 
	 * and makes sure it's an "intentity" matrix.
	 */
	public pMatrix(){
		matrix = new double[4][4];
		ident();
	}

	/**
	 * a copy constructor.
	 *
	 * @param m The matrix to copy.
	 */
	public pMatrix(pMatrix m){
		matrix = new double[4][4];
		for(int i=0;i<4;i++)
			for(int j=0;j<4;j++)
				matrix[i][j] = m.matrix[i][j];
	}

	/**
	 * pushes the current matrix onto the stack, 
	 * (the current matrix is still there though)
	 */
	public void push(){
		pMatrix tmp = new pMatrix(this);
		stack.addElement(tmp.matrix);
	}

	/**
	 * pops the last pushed matrix from the stack,
	 * and makes it current. (the previous one is 
	 * erased)
	 * <p>
	 * NOTE: no error checking is performed, you WILL
	 * get a NoSuchElementException if you're not careful
	 * and try to pop an empty stack.
	 *
	 * @return The freshly poped matrix.
	 */
	public pMatrix pop(){
		matrix = (double[][])stack.lastElement();
		stack.removeElement(matrix);
		return this;
	}

	/**
	 * makes this matrix into an identity matrix.
	 * (current info of the matrix is erased)
	 *
	 * @return Current identity matrix.
	 */
	public pMatrix ident(){
		for(int i=0;i<4;i++)
			for(int j=0;j<4;j++)
				matrix[i][j] = i == j ? 1:0;
		return this;
	}

	/**
	 * add another matrix to this one.
	 * (changes current matrix)
	 *
	 * @param m The matrix to add.
	 * @return The current changed matrix.
	 */
	public pMatrix add(pMatrix m){
		for(int i=0;i<4;i++)
			for(int j=0;j<4;j++)
				matrix[i][j] += m.matrix[i][j];
		return this;
	}

	/**
	 * subtract a matrix from this one
	 * (changes the current matrix)
	 *
	 * @param m The matrix to subtract.
	 * @return The current changed matrix.
	 */
	public pMatrix sub(pMatrix m){
		for(int i=0;i<4;i++)
			for(int j=0;j<4;j++)
				matrix[i][j] -= m.matrix[i][j];
		return this;
	}
	
	/**
	 * get function to the rest of the world.
	 *
	 * @param i The column.
	 * @param j The row.
	 * @return The location of that row and column.
	 */
	public double get(int i,int j){
		return matrix[i][j];
	}

	/**
	 * set function for the rest of the world.
	 *
	 * @param i The column.
	 * @param j The row.
	 * @param v The value of the new location.
	 */
	public void set(int i,int j,double v){
		matrix[i][j] = v;
	}

	/**
	 * function to multiply this matrix by another.
	 * (the result is a cross multiply of this matrix
	 * by the parameter matrix.)
	 * (current matrix changes!)
	 *
	 * @param m The matrix to multiply by.
	 * @return The current changed matrix.
	 */
	public pMatrix mult(pMatrix m){
		pMatrix tmp = new pMatrix(this);
		for(int i=0;i<4;i++)
			for(int j=0;j<4;j++){
				matrix[i][j] = 0.0;
				for(int k=0;k<4;k++)
					matrix[i][j] += tmp.matrix[i][k] * m.matrix[k][j];
			}
		return this;
	}

	/**
	 * function to transform a vector, 
	 * (a vector consists of a 4 element array of doubles,
	 * the last element of the array is reserved)
	 * 
	 * @param v The vector to transform.
	 * @return The transformed vector.
	 */
	public double[] mult(double[] v){
		double[] tmp = new double[4];
		int i;
		for(i=0;i<4;i++)
			tmp[i] = v[i];
		for(i=0;i<4;i++){
			v[i] = 0.0;
			for(int j=0;j<4;j++)
				v[i] += matrix[i][j] * tmp[j];
		}
		return v;
	}

	/**
	 * function to transform an array of vectors...
	 *
	 * @param a The array of arrays of vectors.
	 * @return The transformed array...
	 */
	public double[][] mult(double[][] a){
		for(int i=0;i<a.length;i++)
			mult(a[i]);
		return a;
	}

	/**
	 * rotate this matrix around the X axis.
	 * (chances current matrix)
	 * Note: change sin term signs to change handedness.
	 *
	 * @param a The angle to rotate by.
	 * @return The new rotated matrix.
	 */
	public pMatrix rotatex(double a){
		pMatrix tmp = new pMatrix();
		double cos = Math.cos(a);
		double sin = Math.sin(a);
		tmp.matrix[1][1] = cos;
		tmp.matrix[1][2] = sin;
		tmp.matrix[2][1] = -sin;
		tmp.matrix[2][2] = cos;
		return mult(tmp);
	}

	/**
	 * rotate this matrix around the Y axis.
	 * (chances current matrix)
	 *
	 * @param a The angle to rotate by.
	 * @return The new rotated matrix.
	 */
	public pMatrix rotatey(double a){
		pMatrix tmp = new pMatrix();
		double cos = Math.cos(a);
		double sin = Math.sin(a);
		tmp.matrix[0][0] = cos;
		tmp.matrix[0][2] = -sin;
		tmp.matrix[2][0] = sin;
		tmp.matrix[2][2] = cos;
		return mult(tmp);
	}

	/**
	 * rotate this matrix around the Z axis.
	 * (chances current matrix)
	 *
	 * @param a The angle to rotate by.
	 * @return The new rotated matrix.
	 */
	public pMatrix rotatez(double a){
		pMatrix tmp = new pMatrix();
		double cos = Math.cos(a);
		double sin = Math.sin(a);
		tmp.matrix[0][0] = cos;
		tmp.matrix[0][1] = sin;
		tmp.matrix[1][0] = -sin;
		tmp.matrix[1][1] = cos;
		return mult(tmp);
	}

	/**
	 * translate the current matrix by that amount.
	 * (changes the current matrix)
	 *
	 * @param x The x value to translate.
	 * @param y The y value to translate.
	 * @param z The z value to translate.
	 * @return The translated matrix.
	 */
	public pMatrix translate(double x,double y,double z){
		pMatrix tmp = new pMatrix();
		tmp.matrix[0][3] = x;
		tmp.matrix[1][3] = y;
		tmp.matrix[2][3] = z;
		return mult(tmp);
	}

	/**
	 * translate the current matrix by a 4 element vector.
	 * (changes the current matrix)
	 *
	 * @param v The 4 element vector to translate by.
	 * @return The translated matrix.
	 */
	public pMatrix translate(double[] v){
		pMatrix tmp = new pMatrix();
		tmp.matrix[0][3] = v[0];
		tmp.matrix[1][3] = v[1];
		tmp.matrix[2][3] = v[2];
		return mult(tmp);
	}

	/**
	 * scales the matrix.
	 * (changes the current matrix)
	 *
	 * @param s The scale to apply.
	 * @return The scaled matrix.
	 */
	public pMatrix scale(double s){
		pMatrix tmp = new pMatrix();
		tmp.matrix[0][0] = s;
		tmp.matrix[1][1] = s;
		tmp.matrix[2][2] = s;
		return mult(tmp);
	}


	/**
	 * scales the matrix.
	 * (changes the current matrix)
	 *
	 * @param s The scale to apply.
	 * @return The scaled matrix.
	 */
	public pMatrix scale(double x,double y,double z){
		pMatrix tmp = new pMatrix();
		tmp.matrix[0][0] = x;
		tmp.matrix[1][1] = y;
		tmp.matrix[2][2] = z;
		return mult(tmp);
	}


	/**
	 * scales a matrix in relation to a point
	 * (changes the curernt matrix)
	 *
	 * @param s The scale.
	 * @param v The center of point where to scale.
	 * @return The scaled matrrix.
	 */
	public pMatrix scale(double s,double[] v){
		pMatrix tmp = new pMatrix();
		tmp.matrix[0][0] = s;
		tmp.matrix[0][3] = (1 - s) * v[0];
		tmp.matrix[1][1] = s;
		tmp.matrix[1][3] = (1 - s) * v[1];
		tmp.matrix[2][2] = s;
		tmp.matrix[2][3] = (1 - s) * v[2];
		return mult(tmp);
	}

	/**
	 * scales a matrix in relation to a point
	 * (changes the curernt matrix)
	 *
	 * @param s The scales (one for each coord).
	 * @param v The center of point where to scale.
	 * @return The scaled matrrix.
	 */
	public pMatrix scale(double[] s,double[] v){
		pMatrix tmp = new pMatrix();
		tmp.matrix[0][0] = s[0];
		tmp.matrix[0][3] = (1 - s[0]) * v[0];
		tmp.matrix[1][1] = s[1];
		tmp.matrix[1][3] = (1 - s[1]) * v[1];
		tmp.matrix[2][2] = s[2];
		tmp.matrix[2][3] = (1 - s[2]) * v[2];
		return mult(tmp);
	}

	/**
	 * reflect in the X axis.
	 *
	 * @return The refelected matrix.
	 */
	public pMatrix reflectx(){
		pMatrix tmp = new pMatrix();
		tmp.matrix[0][0] = -tmp.matrix[0][0];
		return mult(tmp);
	}

	/**
	 * reflect in the Y axis.
	 *
	 * @return The refelected matrix.
	 */
	public pMatrix reflecty(){
		pMatrix tmp = new pMatrix();
		tmp.matrix[1][1] = -tmp.matrix[1][1];
		return mult(tmp);
	}

	/**
	 * reflect in the Z axis.
	 *
	 * also known as conversion from right handled
	 * to left handled coord system, or vice versa.
	 *
	 * @return The refelected matrix.
	 */
	public pMatrix reflectz(){
		pMatrix tmp = new pMatrix();
		tmp.matrix[2][2] = -tmp.matrix[2][2];
		return mult(tmp);
	}


	/** 
	 * create perspective matrix; turns current matrix into 
	 * perspective matrix.
     *
     * @param zrpr at what z is the eye?
     * @param zvp at what z is the view plane?
	 */
	public pMatrix perspective(double zprp,double zvp){
        double t2,t3;
        double d1 = zprp - zvp;
        double a = -zvp/d1;
        double b = -1/d1;
        double c = zvp*(zprp/d1);
        double d = zvp/d1;
        for (int i=0;i<4;i++) {
            t2 = matrix[i][2];
            t3 = matrix[i][3];
            matrix[i][2] = t2*a + t3*b;
            matrix[i][3] = t2*c + t3*d;
        }
		return this;
	}




	/**
	 * standard toString method to allow for 
	 * conherent printing of this object.
	 *
	 * @return The String object representing this matrix.
	 */
	public String toString(){
		String s = new String("pMatrix:\n");
		for(int i=0;i<4;i++){
			for(int j=0;j<4;j++){
				s += matrix[i][j]+", ";
			}
			s += "\n";
		}
		return s;
	}
}



