import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;

public class Matrix3D {

  public float[][] matrix;

  //
  // Constructors
  //
  public Matrix3D() {                 // initialize with identity transform
    matrix = new float[4][4];
    loadIdentity();
  }
  
  
  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++)
	set(j, i, copy.get(j,i));
  }


  // initialize with a mapping from
  // canonical space to screen space
  public Matrix3D(Raster r) {
    this();
    int width = r.getWidth()/2;
    int height = r.getHeight()/2;
    // initialize with a mapping from canonical space to screen space
    set(0, 0, width);
    set(3, 0, width);
    set(1, 1, height);
    set(3, 1, height);
  }
  


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


  final 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
  //
  public void transform(Point3D in[], Point3D out[], int start, int length) {
    for (int i=0; i<length; i++) {
      float  w = get(0, 3)*in[start+i].x + get(1, 3)*in[start+i].y + get(2, 3)*in[start+i].z + get(3, 3);
      out[start+i].x = (get(0, 0)*in[start+i].x + get(1, 0)*in[start+i].y +
	get(2, 0)*in[start+i].z + get(3, 0))/w;
      out[start+i].y = (get(0, 1)*in[start+i].x + get(1, 1)*in[start+i].y +
	get(2, 1)*in[start+i].z + get(3, 1))/w;
      out[start+i].z = (get(0, 2)*in[start+i].x + get(1, 2)*in[start+i].y +
	get(2, 2)*in[start+i].z + get(3, 2))/w;

      // for debugging
      //System.out.println(out[start+i].toString());
    }
  }


  public final void compose(Matrix3D src) {
    // this = this * src
    Matrix3D tmp = new Matrix3D();
    for (int i=0; i<4; i++)
      for (int j=0; j<4; j++) {
	float value = get(0, i)*src.get(j, 0) + get(1, i)*src.get(j, 1) +
	  get(2, i)*src.get(j, 2) + get(3, i)*src.get(j, 3);
	tmp.set(j, i, value);
      }
    // copy the result from tmp to matrix
    for (int i=0; i<4; i++)
      for (int j=0; j<4; j++)
	set(j, i, tmp.get(j, i));
  }


  public void loadIdentity() {
    // this = identity
    for (int i=0; i<4; i++)
      for (int j=0; j<4; j++) {
	if (i == j)
	  set(j, i, 1);
	else
	  set(j, i, 0);
      }
  }


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



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


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


  public void rotate(float ax, float ay, float az, float angle) {
    // this = this * rotate
    float length = (float) Math.sqrt(ax*ax + ay*ay + az*az);
    ax = ax/length;
    ay = ay/length;
    az = az/length;
    Matrix3D id = new Matrix3D();
    Matrix3D symm = new Matrix3D();
    Matrix3D sk = new Matrix3D();
    symm.symmetric(ax, ay, az);          // create symmetric matrix
    sk.skewsymm(ax, ay, az);             // create skew symmetric matrix
    float wsymm = 1 - (float) Math.cos(angle);     // weight for symmetric matrix
    float wsk = (float) Math.sin(angle);              // weight for skew matrix
    float wid = (float) Math.cos(angle);              // weight for identity matrix
    symm.multscalar(wsymm);
    sk.multscalar(wsk);
    id.multscalar(wid);
    sk.add(id);                    // add all weighted matrices together
    symm.add(sk);
    symm.set(3, 3, 1);
    compose(symm);
  }
  

  public void symmetric(float ax, float ay, float az) {
   set(0, 0, ax * ax);
   set(1, 0, ax * ay);
   set(2, 0, ax * az);
   set(0, 1, ax * ay);
   set(1, 1, ay * ay);
   set(2, 1, ay * az);
   set(0, 2, ax * az);
   set(1, 2, ay * az);
   set(2, 2, az * az);
  }


  public void skewsymm(float ax, float ay, float az) {
   set(0, 0, 0);
   set(1, 0, -az);
   set(2, 0, ay);
   set(0, 1, az);
   set(1, 1, 0);
   set(2, 1, -ax);
   set(0, 2, -ay);
   set(1, 2, ax);
   set(2, 2, 0);
  }


  // multiply this by scalar s
  public void multscalar(float s) {
   for (int i=0; i<4; i++)
    for (int j=0; j<4; j++)
     set(j, i, get(j, i) * s);
  }


  // add matrix m to this 
  public void add(Matrix3D m) {
   for (int i=0; i<4; i++)
    for (int j=0; j<4; j++)
     set(j, i, get(j, i) + m.get(j, i));
  }


  
  public void lookAt(float eyex, float eyey, float eyez,     // this = this * lookat
		     float atx,  float aty,  float atz,
		     float upx,  float upy,  float upz) {   
    Matrix3D m = new Matrix3D();
    //Point3D eye = new Point3D(eyex, eyey, eyez);
    //Point3D at = new Point3D(atx, aty, atz);
    //Point3D up = new Point3D(upx, upy, upz);
    
    Point3D l = new Point3D(atx-eyex, aty-eyey, atz-eyez);
    Point3D r = new Point3D();
    // r = l X up
    r.x = -l.z * upy + l.y * upz;
    r.y = l.z * upx - l.x * upz;
    r.z = -l.y * upx + l.x * upy;  
  
    Point3D u = new Point3D();
    // u = r X l
    u.x = -r.z * l.y + r.y * l.z;
    u.y = r.z * l.x - r.x * l.z;
    u.z = -r.y * l.x + r.x * l.y;  
 
    // normalize l, r, and u
    float llength = (float) Math.sqrt(l.x*l.x + l.y*l.y + l.z*l.z);
    float rlength = (float) Math.sqrt(r.x*r.x + r.y*r.y + r.z*r.z);
    float ulength = (float) Math.sqrt(u.x*u.x + u.y*u.y + u.z*u.z);
    l.x = l.x/llength;
    l.y = l.y/llength;
    l.z = l.z/llength;
    r.x = r.x/rlength;
    r.y = r.y/rlength;
    r.z = r.z/rlength;
    u.x = u.x/ulength;
    u.y = u.y/ulength;
    u.z = u.z/ulength;
    
    // set up the matrix
    m.set(0, 0, r.x);
    m.set(1, 0, r.y);
    m.set(2, 0, r.z);
    m.set(0, 1, u.x);
    m.set(1, 1, u.y);
    m.set(2, 1, u.z);
    m.set(0, 2, -l.x);
    m.set(1, 2, -l.y);
    m.set(2, 2, -l.z);
    m.set(3, 0, -(r.x * eyex + r.y * eyey + r.z * eyez));
    m.set(3, 1, -(u.x * eyex + u.y * eyey + u.z * eyez));
    m.set(3, 2, l.x * eyex + l.y * eyey + l.z * eyez);
    compose(m);
  }
  


  //
  // 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) {
    Matrix3D m = new Matrix3D();
    m.set(0, 0, 2*near/(right-left));
    m.set(2, 0, -(right+left)/(right-left));
    m.set(1, 1, 2*near/(bottom-top));
    m.set(2, 1, -(bottom+top)/(bottom-top));
    m.set(2, 2, (far+near)/(far-near));
    m.set(3, 2, -2*far*near/(far-near));
    m.set(2, 3, 1);
    m.set(3, 3, 0);
    compose(m);
  }
  



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

  public String toString() {
    return new String ("Matrix3D is [["
		       +get(0,0)+", "+get(1,0)+", "+get(2,0)+", "+get(3,0)+"] ["
		       +get(0,1)+", "+get(1,1)+", "+get(2,1)+", "+get(3,1)+"] ["
		       +get(0,2)+", "+get(1,2)+", "+get(2,2)+", "+get(3,2)+"] ["
		       +get(0,3)+", "+get(1,3)+", "+get(2,3)+", "+get(3,3)+"]]");
  }
  
}

