import java.util.*;
import java.awt.*;
import Raster;
import Point3D;

public class Matrix3D {

  public float val[][] = new float[4][4];
  private int i, j, k;
  // 
  // Constructors
  //
  public Matrix3D(float invals[][]) {// real constructor
    for(i=0; i<4; i++){
      for(j=0; j<4; j++){
	val[i][j] = invals[i][j];
      }
    }
  }
  public Matrix3D(){// initialize with identity transform
    float blah[][] = {{1, 0, 0, 0},
                      {0, 1, 0, 0},
                      {0, 0, 1, 0},
		      {0, 0, 0, 1}};
    for(i=0; i<4; i++){
      for(j=0; j<4; j++){
	val[i][j] = blah[i][j];
      }
    }
  }
  public Matrix3D(Matrix3D copy){// initialize with copy of source  
    for(i=0; i<4; i++){
      for(j=0; j<4; j++){
	val[i][j] = copy.val[i][j];
      }
    }
  }
  public Matrix3D(Raster r){
    // initialize with a mapping from
    // canonical space to screen space
    // This is just a big SCALE/TRANSLATION from (-1..1) (ranges of x & y before) 
    // to (0..(width||height)) (ranges of x & y now) 
    // z is unchanged, so tz = 0 and sig_z = 1.
    float blah[][] = {{r.width/2, 0, 0, r.width/2}, // X
		      {0, r.height/2, 0, r.height/2}, //Y
		      {0, 0, 1, 0}, //Z
		      {0, 0, 0, 1}};
    for(i=0; i<4; i++){
      for(j=0; j<4; j++){
	val[i][j] = blah[i][j];
      }
    }
  }  
  //
  // General interface methods
  //        
  public void set(int i, int j, float value){          // set element [j][i] to value
    val[j][i] = value;
  }
  public float get(int i, int j){ // return element [j][i]
    return(val[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 (i=0; i<length; i++){
      float scalefact = in[start+i].x*val[3][0] + in[start+i].y*val[3][1] + in[start+i].z*val[3][2]+val[3][3];
      out[start+i].x = (in[start+i].x*val[0][0] + in[start+i].y*val[0][1] + in[start+i].z*val[0][2]+val[0][3])/scalefact;
      out[start+i].y = (in[start+i].x*val[1][0] + in[start+i].y*val[1][1] + in[start+i].z*val[1][2]+val[1][3])/scalefact;
      out[start+i].z = (in[start+i].x*val[2][0] + in[start+i].y*val[2][1] + in[start+i].z*val[2][2]+val[2][3])/scalefact;      
      
      //      System.out.println(out[start+i].toString() + "\n");
    }
  }
  public final void compose(Matrix3D src){                        // this = this * src
    for (i=0; i<4; i++){
      for (j=0; j<4; j++){
	float accum = 0;
	for (k=0; k<4; k++){
	  accum = accum + val[j][k]*src.val[k][i];
	}
	val[j][i] = accum;  
      }
    }
  }
  public void loadIdentity(){                                     // this = identity
    float blah[][] = {{1, 0, 0, 0},
		  {0, 1, 0, 0},
		  {0, 0, 1, 0},
		  {0, 0, 0, 1}};   
    for(i=0; i<4; i++){
      for(j=0; j<4; j++){
	val[i][j] = blah[i][j];
      }
    } 
  }
  public void translate(float tx, float ty, float tz){            // this = this * t
    float t[][] = {{1, 0, 0, tx},
		   {0, 1, 0, ty},
		   {0, 0, 1, tz},
		   {0, 0, 0, 1}};
    compose(new Matrix3D(t));
  }
  public void scale(float sx, float sy, float sz){                // this = this * scale
    float sigma[][] = {{sx, 0, 0, 0},
		       {0, sy, 0, 0},
		       {0, 0, sz, 0},
		       {0, 0, 0, 1}};
    compose(new Matrix3D(sigma));
  }
  public void skew(float kxy, float kxz, float kyz){              // this = this * skew
    float kappa[][] = {{1, kxy, kxz, 0},
		       {0, 1, kyz, 0},
		       {0, 0, 1, 0},
		       {0, 0, 0, 1}};
    compose(new Matrix3D(kappa));
  }
  public void rotate(float ax, float ay, float az, float angle){  // this = this * rotate 
    float n1 = (float)-1.0;
    float symmetric[][] = {{ax*ax, ax*ay, ax*az},
			   {ax*ay, ay*ay, ay*az},
			   {ax*az, ay*az, az*az}};
    float skew[][] = {{0, n1*az, ay},
		      {az, 0, n1*ax},
		      {n1*ay, ax, 0}};
    float id[][] = {{1, 0, 0},
		    {0, 1, 0},
		    {0, 0, 1}};
    float rotate[][] = {{1, 0, 0, 0},              // we put the finished stuff here
			{0, 1, 0, 0},
			{0, 0, 1, 0},
			{0, 0, 0, 1}};
    float foo = 1 - (float)Math.cos(angle);
    float bar = (float)Math.sin(angle);
    float baz = (float)Math.cos(angle);
    System.out.println("ROT: "+angle+" "+foo+" "+bar+" "+baz+"\n");
    for(i=0; i<3; i++){
      for(j=0; j<3; j++){
	symmetric[i][j] = symmetric[i][j] * foo;
	skew[i][j] = skew[i][j] * bar;
	id[i][j] = id[i][j] * baz;
	rotate[i][j] = symmetric[i][j] + skew[i][j] + id[i][j];
      }
    }
    compose(new Matrix3D(rotate));
  }
 public void lookAt(float eyex, float eyey, float eyez,
                     float atx,  float aty,  float atz,
                     float upx,  float upy,  float upz) { 
                                                       // this = this * lookat
   // retain precision here
    double scale;

    // make l vector
    float lx = atx - eyex;
    float ly = aty - eyey;
    float lz = atz - eyez;
    scale = Math.sqrt(lx*lx + ly*ly + lz*lz);
    float lnx = (float)(lx / scale);
    float lny = (float)(ly / scale);
    float lnz = (float)(lz / scale);

    // make r vector
    float rx = ly*upz - lz*upy; 
    float ry = lz*upx - lx*upz;
    float rz = lx*upy - ly*upx;
    scale = Math.sqrt(rx*rx + ry*ry + rz*rz);
    float rnx = (float)(rx / scale);
    float rny = (float)(ry / scale);
    float rnz = (float)(rz / scale);

    // make u vector
    // just use up vector
    float ux = upx; 
    float uy = upy;
    float uz = upz;
    scale = Math.sqrt(ux*ux + uy*uy + uz*uz);
    float unx = (float)(ux / scale);
    float uny = (float)(uy / scale);
    float unz = (float)(uz / scale);

    // translators
    float foo = -(rnx*eyex + rny*eyey + rnz*eyez);
    float bar = -(unx*eyex + uny*eyey + unz*eyez);
    float baz = lnx*eyex + lny*eyey + lnz*eyez;

    // do it
    float blah[][]= {{ rnx, rny, rnz, foo},
                         { unx, uny, unz, bar},
                         {-lnx, -lny, -lnz, baz},
                         {0, 0, 0, 1}};

    compose(new Matrix3D(blah));
    }
  
  //
  // 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 suck[][] = {{(((float)2.0)*near)/(right-left), 0, -(right+left)/(right-left), 0},
    //		      {0, (((float)2.0)*near)/(bottom-top), -(bottom+top)/(bottom-top), 0},
    //		      {0, 0, (far+near)/(far-near), (((float)-2.0)*far*near)/(far-near)},
    //		      {0, 0, 1, 0}};
    float suck[][] = {{(((float)2.0)*near)/(right-left), 0, -(right+left)/(right-left), 0},
		      {0, (((float)2.0)*near)/(bottom-top), -(bottom+top)/(bottom-top), 0},
		      {0, 0, (far+near)/(far-near), (((float)-2.0)*far*near)/(far-near)},
		      {0, 0, 1, 0}};
    System.out.println("Perspectifying\n");
    compose(new Matrix3D(suck));    
  }
  public void orthographic(float left, float right,               // this = this * ortho
			   float bottom, float top,
			   float near, float far){
    float ortho[][] = {{((float)2.0)/(right-left), 0, 0, -(right+left)/(right-left)},
		       {0, ((float)2.0)/(bottom-top), 0, -(bottom+top)/(bottom-top)},
		       {0, 0, ((float)2.0)/(far-near), -(far+near)/(far-near)},
		       {0, 0, 0, 1}};
    compose(new Matrix3D(ortho));
  }
}
