import java.lang.*;

public class Matrix3D {
  protected float xform[][] = new float[4][4];
//  Implementation by Herman Bernard Walker
//  for 6.837, Fall 1998 session.
//  Last Update 11-8-1998
//
//  Implementation fairly stock; used a 4*4 float
//  array to implement a homogeneous coordinate
//  transformation matrix, and manipulate
//  the contents during the various methods.


  //        // Constructors        //
  public Matrix3D()
    // initialize with identity transform
  {
    float[][] initl = { {1, 0, 0, 0}, {0, 1, 0, 0},
	      {0, 0, 1, 0}, {0, 0, 0, 1}};
    xform = initl;
  }
  
  public Matrix3D(Raster r)          
       // initialize with a mapping from
       // canonical space to screen space
  {
    float wid = r.getWidth();
    float hgt = r.getHeight();
    float[][] initl = { {wid/2,0,0,(wid-1)/2}, {0, hgt/2, 0, (hgt-1)/2},
	      {0,0,1,1},{0,0,0,1}};
    xform = initl;
  }
  
  //        // General interface methods        //
  public void set(int i, int j, float value)
       // set element [j][i] to value
  {
    xform[j][i] = value;
  }
  
  public float get(int i, int j)
       // return element [j][i]
  {
    return xform[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 (int 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 w = (xform[3][0]*in[start+i].x
//        +xform[3][1]*in[start+i].y
//        +xform[3][2]*in[start+i].z
//        +xform[3][3]);
        
      out[start+i].x = (xform[0][0] * in[start+i].x + xform[0][1] *
			in[start+i].y + xform[0][3]*in[start+i].z +
			xform[0][3]);

      out[start+i].y = (xform[1][0] * in[start+i].x + xform[1][1] *
			in[start+i].y + xform[1][3]*in[start+i].z +
			xform[1][3]);

      out[start+i].z = (xform[2][0] * in[start+i].x + xform[2][1] *
			in[start+i].y + xform[2][3]*in[start+i].z +
			xform[2][3]);
    }
  }
  public final void compose(Matrix3D src) { 
    // this = this * src
//    float[][] temp = new float[4][4];
    Matrix3D temp = new Matrix3D();
    for (int a = 0; a <4; a++) {    //row
      for (int b = 0; b<4;b++) {    //column
    	temp.set(b,a,this.get(0,a)*src.get(b,0) 
	        + this.get(1,a)*src.get(b,1) 
	        + this.get(2,a)*src.get(b,2) 
    	    + this.get(3,a)*src.get(b,3));
      }
    }
    this.xform = temp.xform;
  }
  
  public void loadIdentity()                                    
       // this = identity
  {
    float[][] initl = {{1,0,0,0},{0,1,0,0},
	     {0,0,1,0},{0,0,0,1}};
    xform = initl;
  }
  
  public void translate(float tx, float ty, float tz){           
    // this = this * t
    Matrix3D temp = new Matrix3D();
    temp.set(3,0,tx);
    temp.set(3,1,ty);
    temp.set(3,2,tz);
    this.compose(temp);
  }

  public void scale(float sx, float sy, float sz)               
       // this = this * scale
  {
    Matrix3D temp = new Matrix3D();
    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();
    temp.set(1,0,kxy);
    temp.set(2,0,kxz);
    temp.set(2,1,kyz);
    this.compose(temp);
  }
  
  public void rotate(float ax, float ay, float az, float angle)  
       // this = this * rotate
  {
    float amag = (float) Math.sqrt(ax*ax+ay*ay+az*az);
    float ax2 = (ax * ax)/(amag*amag);
    float ay2 = (ay * ay)/(amag*amag);
    float az2 = (az * az)/(amag*amag);
    float axy = (ax * ay)/(amag*amag);
    float ayz = (ay * az)/(amag*amag);
    float axz = (ax * az)/(amag*amag);
    float cosang = (float) Math.cos(angle);
    float oneminus = 1 - cosang;
    float sinang = (float) Math.sin(angle);
    Matrix3D temp = new Matrix3D();
    temp.set(0,0,(ax2 * oneminus + cosang));
    temp.set(1,0,(axy*oneminus - az * sinang/amag));
    temp.set(2,0,(axz*oneminus +ay*sinang/amag));
    temp.set(0,1,(axy*oneminus +az*sinang/amag));
    temp.set(1,1,(ay2*oneminus + cosang));
    temp.set(2,1,(ayz*oneminus -ax*sinang/amag));
    temp.set(0,2,(axz*oneminus -ay*sinang/amag));
    temp.set(1,2,(ayz*oneminus +ax*sinang/amag));
    temp.set(2,2,(az2*oneminus +cosang));

    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
  {
    // vector l -- vector pointed away from lookat point
    //             from eye point
    // vector u -- vector pointing to up point from eye
    //             point.
    // vecotr r -- vector pointed to right of l and r;
    //             finished transformed coordinate basis.
    
    float l1 = atx-eyex;
    float l2 = aty-eyey;
    float l3 = atz - eyez;
    //the 'x'mag variables are the magnitudes of the various
    //vectors used in the translation, used in normalization.
    float lmag = (float) Math.sqrt(l1*l1 + l2*l2+l3*l3);
    float[] ltrns ={-l1/lmag,-l2/lmag,-l3/lmag,(l1*eyex + l2*eyey + l3*eyez)/lmag};
    
    float r1= -l3*(upy-eyey) + l2*(upz-eyez);
    float r2= l3*(upx-eyex) - l1*(upz-eyez);
    float r3= l1*(upy-eyey) - l2*(upz-eyex);
    float rmag = (float) Math.sqrt(r1*r1 + r2*r2 + r3*r3);
    float[] rtrns ={r1/rmag,r2/rmag,r3/rmag,(-r1*eyex -r2*eyey -r3*eyez)/rmag};

    float u1 = r2*l3 - r3*l2;
    float u2 = r3*l1 - r1*l3;
    float u3= r1*l2 - r2*l1;
    float umag = (float) Math.sqrt(u1*u1+u2*u2+u3*u3);
    float[] utrns = {u1/umag,u2/umag,u3/umag,(-u1*eyex -u2*eyey -u3*eyez)/umag};


    Matrix3D tmp= new Matrix3D();

    tmp.xform[0] = rtrns;
    tmp.xform[1] = utrns;
    tmp.xform[2] = ltrns;


    this.compose(tmp);

  }


  //        // 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();
    temp.set(0,0,near/(right-left));
    temp.set(1,1,near/(bottom-top));
    temp.set(2,2,far/(far-near));
    temp.set(3,3,0);
    temp.set(0,2,-left/(right-left));
    temp.set(1,2,-top/(bottom-top));
    temp.set(2,3,(far*near)/(far-near));
    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();
    temp.set(0,0,1/(right-left));
    temp.set(1,1,1/(bottom-top));
    temp.set(2,2,1/(far-near));
    temp.set(0,3,-left/(right-left));
    temp.set(1,3,-top/(bottom-top));
    temp.set(2,3,-near/(far-near));
    this.compose(temp);
  }

  public Matrix3D(Matrix3D copy)     
       // initialize with copy of source
  {
    for (int a=0; a < 4; a++) {
      for (int b=0; b <4; b++) {
	xform[a][b] = copy.get(b,a);
      }
    }
  }
}

