// Implementation of homogeneous vectors and matrices
import java.util.*;

// ALL get() functions return a value from a specified location
// ALL set() functions put a value into the specified location
// The different versions of the functions serve to convert different
// ways of saying the same thing into actually doing the same thing

// Geometric vectors of size N
public class VectorN {
   private double v[];
   VectorN(int n) { v = new double[n]; }
   int size() { return v.length; }
   double get(int j) { return v[j]; }
   void set(int j, double f) { v[j] = f; }
   void set(VectorN vec) { for (int j = 0 ; j < size() ; j++) set(j, vec.get(j)); }
   //Converts the VectorN into a printable String
   public String toString() {
      String s = "{";
      for (int j = 0 ; j < size() ; j++) s += (j == 0 ? "" : ",") + get(j);
      return s + "}";
   }
   //changes the coordinate system for the matrix
   void transform(MatrixN mat) {
      VectorN tmp = new VectorN(size());
      double f;
      for (int i = 0 ; i < size() ; i++) {
	 f = 0.;
         for (int j = 0 ; j < size() ; j++) f += mat.get(i,j) * get(j);
         tmp.set(i, f);
      }
      set(tmp);
   }
   double distance(VectorN vec) {
      double x, y, d = 0;
      for (int i = 0 ; i < size() ; i++) {
	 x = vec.get(0) - get(0);
	 y = vec.get(1) - get(1);
	 d += x * x + y * y;
      }
      return Math.sqrt(d);
   }
}

// N x N matrices
// Geometric matrices of size N x N
class MatrixN {
   private VectorN v[];
   MatrixN(int n) {
      v = new VectorN[n];
      for (int i = 0 ; i < n ; i++) v[i] = new VectorN(n);
   }
   int size() { return v.length; }
   double get(int i, int j) { return get(i).get(j); }
   void set(int i, int j, double f) { v[i].set(j,f); }
   VectorN get(int i) { return v[i]; }
   void set(int i, VectorN vec) { v[i].set(vec); }
   void set(MatrixN mat) { for (int i = 0 ; i < size() ; i++) set(i, mat.get(i)); }
   public String toString() {                   // print the values of the matrix
      String s = "{";
      for (int i = 0 ; i < size() ; i++) s += (i == 0 ? "" : ",") + get(i);
      return s + "}";
   }
   void identity() {                // make this an N x N identity matrix
      for (int j = 0 ; j < size() ; j++)
      for (int i = 0 ; i < size() ; i++) set(i, j, (i == j ? 1 : 0));
   }
   void preMultiply(MatrixN mat) {     // mat * this
      MatrixN tmp = new MatrixN(size());
      double f;
      for (int j = 0 ; j < size() ; j++)
      for (int i = 0 ; i < size() ; i++) {
	 f = 0.;
         for (int k = 0 ; k < size() ; k++) f += mat.get(i,k) * get(k,j);
	 tmp.set(i, j, f);
      }
      set(tmp);
   }
   void postMultiply(MatrixN mat) {    // this * mat
      MatrixN tmp = new MatrixN(size());
      double f;
      for (int j = 0 ; j < size() ; j++)
      for (int i = 0 ; i < size() ; i++) {
	 f = 0.;
         for (int k = 0 ; k < size() ; k++) f += get(i,k) * mat.get(k,j);
	 tmp.set(i, j, f);
      }
      set(tmp);
   }
}

// Homogeneous vectors in three dimensions
class Vector3D extends VectorN {
   Vector3D() { super(4); }
   void set(double x,double y,double z,double w){set(0,x);set(1,y);set(2,z);set(3,w);}
   void set(double x, double y, double z) { set(x, y, z, 1); }
}

// Homogeneous matrices in three dimensions
class Matrix3D extends MatrixN {
   Matrix3D() { super(4); identity(); }
   //rotate the model in the given axes by the given angle
   void rotateX(double theta) { rotate(0, theta); }
   void rotateY(double theta) { rotate(1, theta); }
   void rotateZ(double theta) { rotate(2, theta); }
   void rotate(int axis, double theta) {
      int i = (axis+1)%3;
      int j = (axis+2)%3;
      Matrix3D tmp = new Matrix3D();
      double c = Math.cos(theta * Math.PI / 180);
      double s = Math.sin(theta * Math.PI / 180);
      tmp.set(i,i, c); tmp.set(i,j, s);
      tmp.set(j,i,-s); tmp.set(j,j, c);
      postMultiply(tmp);
   }
   //changes the coordinate system
   void translate(double a, double b, double c) { // translate
      Matrix3D tmp = new Matrix3D();
      tmp.set(0,3, a);
      tmp.set(1,3, b);
      tmp.set(2,3, c);
      postMultiply(tmp);
   }
   void scale(double s) { scale(s, s, s); }
   void scale(double x, double y, double z) { // scale non-uniformly
      Matrix3D tmp = new Matrix3D();
      tmp.set(0,0,x);
      tmp.set(1,1,y);
      tmp.set(2,2,z);
      postMultiply(tmp);
   }
}