// Annamaria Cherubin: last edited on 11-5-98
// 6.837 - Project #3
// Matrix3D.java

import java.math.*;

public class Matrix3D {

  // Instance variables
  private float matrix[][];

  // Constructors
  public Matrix3D() {                  
    // initialize with identity transform
    matrix = new float[4][4];
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
	if (i == j) matrix[j][i] = 1.0f;
	else matrix[j][i] = 0.0f;
      }
    }
  }

  public Matrix3D(Matrix3D copy) {
    // initialize with copy of source
    matrix = new float[4][4];
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
	matrix[j][i] = copy.get(i, j);
      }
    }
  }

  public Matrix3D(Raster r) {
    // initialize with a mapping from
    // canonical (-1 to 1) space to screen space
    matrix = new float[4][4];
    this.loadIdentity();
    float w = r.getWidth();
    float h = r.getHeight();
    this.set(0, 0, w/2);
    this.set(1, 1, h/2);
    this.set(0, 3, w/2);
    this.set(1, 3, h/2);
  }

  // General interface methods
  public void set(int i, int j, float value) {
    // set element [j][i] to value
    matrix[j][i] = value;
  }

  public float get(int i, int j) {
    // return element [j][i]
    return matrix[j][i]; 
  }

  // 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
  //
  //    for (i = 0; i < length; i++)
  //        out[start+i] = this * in[start+i]
  //
  public void transform(Point3D in[], Point3D out[], int start, int length) {
    for (int i = 0; i < length; i++) {
      float xout, yout, zout;
      float xin = in[start+i].x;
      float yin = in[start+i].y;
      float zin = in[start+i].z;

      xout = get(0,0)*xin + get(0,1)*yin + get(0,2)*zin + get(0,3);
      yout = get(1,0)*xin + get(1,1)*yin + get(1,2)*zin + get(1,3);
      zout = get(2,0)*xin + get(2,1)*yin + get(2,2)*zin + get(2,3);

      out[start+i].x = xout;
      out[start+i].y = yout;
      out[start+i].z = zout;
    }
  }
  
  public final void compose(Matrix3D src) {
    // this = this * src
    Matrix3D temp = new Matrix3D(); 
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
	
	temp.set(i, j, (this.get(i,0)*src.get(0,j) + 
			this.get(i,1)*src.get(1,j) +
			this.get(i,2)*src.get(2,j) + 
			this.get(i,3)*src.get(3,j)));
      }
    }
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
	this.set(i, j, temp.get(i, j));
      }
    }
  }
  
  public void loadIdentity() {
    // this = identity
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
	if (i == j) matrix[j][i] = 1.0f;
	else matrix[j][i] = 0.0f;
      }
    }    
  }

  public void translate(float tx, float ty, float tz) {
    // this = this * t
    Matrix3D temp = new Matrix3D();    // identity matrix
    temp.set(0, 3, tx);                // temp[3][0] = tx;
    temp.set(1, 3, ty);                // temp[3][1] = ty;
    temp.set(2, 3, tz);                // temp[3][2] = tz;
    this.compose(temp); 
  }

  public void scale(float sx, float sy, float sz) {
    // this = this * scale
    Matrix3D temp = new Matrix3D();    // identity matrix
    temp.set(0, 0, sx);
    temp.set(1, 1, sy);
    temp.set(2, 2, sz);
    this.compose(temp);
  }

  public void skew(float kxy, float kxz, float kyz) {
    // this = this * skew
    Matrix3D temp = new Matrix3D();    // identity matrix
    temp.set(0, 1, kxy);
    temp.set(0, 2, kxz);
    temp.set(1, 2, kyz);
    this.compose(temp);
  }

  public void rotate(float ax, float ay, float az, float angle) {
    // this = this * rotate 
    Matrix3D temp = new Matrix3D();    // identity matrix
    float cos = (float) Math.cos(angle);
    float sin = (float) Math.sin(angle);  
  
    ax = (float) (ax / (Math.sqrt(ax*ax + ay*ay + az*az)));
    ay = (float) (ay / (Math.sqrt(ax*ax + ay*ay + az*az)));
    az = (float) (az / (Math.sqrt(ax*ax + ay*ay + az*az)));
    
    float r11 = (float) (ax*ax*(1-cos) + cos);
    float r12 = (float) (ax*ay*(1-cos) - az*sin);
    float r13 = (float) (ax*az*(1-cos) + ay*sin);
    float r21 = (float) (ax*ay*(1-cos) + az*sin);
    float r22 = (float) (ay*ay*(1-cos) + cos);
    float r23 = (float) (ay*az*(1-cos) - ax*sin);
    float r31 = (float) (ax*az*(1-cos) - ay*sin);
    float r32 = (float) (ay*az*(1-cos) + ax*sin);
    float r33 = (float) (az*az*(1-cos) + cos);
    
    this.set(0, 0, r11);
    this.set(0, 1, r12);
    this.set(0, 2, r13);
    this.set(1, 0, r21);
    this.set(1, 1, r22);
    this.set(1, 2, r23);
    this.set(2, 0, r31);
    this.set(2, 1, r32);
    this.set(2, 2, r33);    
    this.compose(temp);
  }

  public void lookAt(float eyex, float eyey, float eyez,
		     float atx,  float aty,  float atz,
		     float upx,  float upy,  float upz) {
    // this = this * lookat
    Matrix3D temp = new Matrix3D();

    float l_x = atx - eyex;
    float l_y = aty - eyey;
    float l_z = atz - eyez;
    float lhat_x = (float)(l_x / (Math.sqrt(l_x*l_x + l_y*l_y + l_z*l_z)));
    float lhat_y = (float)(l_y / (Math.sqrt(l_x*l_x + l_y*l_y + l_z*l_z)));
    float lhat_z = (float)(l_z / (Math.sqrt(l_x*l_x + l_y*l_y + l_z*l_z)));

    float r_x = l_y*upz - l_z*upy;
    float r_y = l_z*upx - l_x*upz;
    float r_z = l_x*upy - l_y*upx;
    float rhat_x = (float)(r_x / (Math.sqrt(r_x*r_x + r_y*r_y + r_z*r_z)));
    float rhat_y = (float)(r_y / (Math.sqrt(r_x*r_x + r_y*r_y + r_z*r_z)));
    float rhat_z = (float)(r_z / (Math.sqrt(r_x*r_x + r_y*r_y + r_z*r_z)));

    float u_x = upx;
    float u_y = upy;
    float u_z = upz;
    float uhat_x = (float)(u_x / (Math.sqrt(u_x*u_x + u_y*u_y + u_z*u_z)));
    float uhat_y = (float)(u_y / (Math.sqrt(u_x*u_x + u_y*u_y + u_z*u_z)));
    float uhat_z = (float)(u_z / (Math.sqrt(u_x*u_x + u_y*u_y + u_z*u_z)));

    float t1 = -(rhat_x*eyex + rhat_y*eyey + rhat_z*eyez);
    float t2 = -(uhat_x*eyex + uhat_y*eyey + uhat_z*eyez);
    float t3 = lhat_x*eyex + lhat_y*eyey + lhat_z*eyez;

    temp.set(0, 0, rhat_x);
    temp.set(0, 1, rhat_y);
    temp.set(0, 2, rhat_z);
    temp.set(0, 3, t1);
    temp.set(1, 0, uhat_x);
    temp.set(1, 1, uhat_y);
    temp.set(1, 2, uhat_z);
    temp.set(1, 3, t2);
    temp.set(2, 0, -lhat_x);
    temp.set(2, 1, -lhat_y);
    temp.set(2, 2, -lhat_z);
    temp.set(2, 3, t3);
    this.compose(temp);
  }

  // Assume the following projection transformations
  // transform points into the canonical viewing space
  //
  public void perspective(float left, float right, float bottom, float top,
			  float near, float far) {
                // this = this * persp
    Matrix3D temp = new Matrix3D();    // identity matrix
    this.set(0, 0, 2*near/(right-left));
    this.set(0, 2, -(right+left)/(right-left));
    this.set(1, 1, 2*near/(bottom-top));
    this.set(1, 2, -(bottom+top)/(bottom-top));
    this.set(2, 2, (far+near)/(far-near));
    this.set(2, 3, -2*far*near/(far-near));
    this.set(3, 2, 1.0f);
    this.set(3, 3, 0.0f);
    this.compose(temp);
  }

  public void orthographic(float left, float right, float bottom, float top,
			   float near, float far) {
               // this = this * ortho
    Matrix3D temp = new Matrix3D();    // identity matrix
    this.set(0, 0, 2/(right-left));
    this.set(0, 3, -(right+left)/(right-left));
    this.set(1, 1, 2/(bottom-top));
    this.set(1, 3, -(bottom+top)/(bottom-top));
    this.set(2, 2, 2/(far-near));
    this.set(2, 3, -(far+near)/(far-near));
    this.compose(temp);
  }

}


