/*
 * 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 float[][] matrix;

    protected float[][] matrixinvtrans;


    /**
     * 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 float[4][4];
        matrixinvtrans = new float[4][4];
        ident();
    }

    /**
     * a copy constructor.
     *
     * @param m The matrix to copy.
     */
    public pMatrix(pMatrix m){
        matrix = new float[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 = (float[][])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 float 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,float 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] = (float)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 floats,
     * the last element of the array is reserved)
     * 
     * @param v The vector to transform.
     * @return The transformed vector.
     */
    public float[] mult(float[] v){
        float[] tmp = new float[4];
        int i;
        for (i=0;i<4;i++)
            tmp[i] = v[i];
        for (i=0;i<4;i++) {
            v[i] = (float)0.0;
            for (int j=0;j<4;j++)
                v[i] += matrix[i][j] * tmp[j];
        }
        return v;
    }

    /**
     * function to transform a vector, 
     * (a vector consists of a 4 element array of floats,
     * the last element of the array is reserved)
     * 
     * @param v The vector to transform.
     * @return The transformed vector.
     */
    public float[] multinvtrans(float[] v){
        float[] tmp = new float[4];
        int i;
        for (i=0;i<4;i++)
            tmp[i] = v[i];
        for (i=0;i<4;i++) {
            v[i] = (float)0.0;
            for (int j=0;j<4;j++)
                v[i] += matrixinvtrans[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 float[][] mult(float[][] a){
        for (int i=0;i<a.length;i++)
            mult(a[i]);
        return a;
    }

    /**
     * compute inverse and transpose
     */
    public void computeInvTrans(){
        MatrixInverter.invert(matrix,matrixinvtrans);
        for(int i=0;i<4;i++){
            for(int j=0;j<4;j++){
                if(j != i){
                    float t = matrixinvtrans[i][j];
                    matrixinvtrans[i][j] = matrixinvtrans[j][i];
                    matrixinvtrans[j][i] = t;
                }
            }
        }        
    }

    /**
     * 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(float a){
        pMatrix tmp = new pMatrix();
        float cos = (float)Math.cos(a);
        float sin = (float)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(float a){
        pMatrix tmp = new pMatrix();
        float cos = (float)Math.cos(a);
        float sin = (float)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(float a){
        pMatrix tmp = new pMatrix();
        float cos = (float)Math.cos(a);
        float sin = (float)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(float x,float y,float 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(float[] 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(float 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(float x,float y,float 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(float s,float[] 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(float[] s,float[] 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(float zprp,float zvp){
        float t2,t3;
        float d1 = zprp - zvp;
        float a = -zvp/d1;
        float b = -1/d1;
        float c = zvp*(zprp/d1);
        float 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;
    }
}


