public class Matrix3D {
	double data_[][] = new double[4][4];
	static private Matrix3D multiplyTemp_ = new Matrix3D(),
		constantTemp_ = new Matrix3D();

	public void set(int i, int j, double val) {
		data_[i][j] = val;
	}
	public double get(int i, int j) { 
		return data_[i][j];
	}
	public Matrix3D copy(Matrix3D src) {
		for(int i = 0; i<4; ++i) 
            for(int j = 0; j<4; ++j)
				data_[i][j] = src.data_[i][j];
		return this;
	}
	public Matrix3D multiply(Matrix3D M, Matrix3D out) {
		for(int i = 0; i<4; ++i)
			for(int j = 0; j<4; ++j) {
				/* a tiny premature optimization
				double sum = .0;
				for(int k = 0; k<4; ++k)
					sum += data_[i][k]*M.data_[k][j]; */
				/* seems to be backward
				out.data_[i][j] = data_[i][0]*M.data_[0][j]
							    + data_[i][1]*M.data_[1][j]
							    + data_[i][2]*M.data_[2][j]
							    + data_[i][3]*M.data_[3][j];
				*/
				out.data_[i][j] = data_[0][j]*M.data_[i][0]
							    + data_[1][j]*M.data_[i][1]
							    + data_[2][j]*M.data_[i][2]
							    + data_[3][j]*M.data_[i][3];				
			}
		return out;
	}
	public Matrix3D multiply(Matrix3D M) {
		return multiplyTemp_.copy(this).multiply(M,this);
	}
	// nouns
	public void zero() {
		for(int i = 0; i<4; ++i) 
            for(int j = 0; j<4; ++j)
				data_[i][j] = .0;
	}
	public Matrix3D identity() {
		zero();
		data_[0][0] = 
			data_[1][1] = 
				data_[2][2] = 
					data_[3][3] = 1.;
		return this;
	}
    public Matrix3D translation(double x, double y, double z) {
		identity();
		data_[0][3] = x;
		data_[1][3] = y;
		data_[2][3] = z;
		return this;
	}
    public Matrix3D translation(Vector3D v) {
		return translation(v.x(),v.y(),v.z());
	}
   	public Matrix3D xRotation(double theta) {
		identity();	
		data_[1][1] = Math.cos(theta);
		data_[1][2] = -Math.sin(theta);
		data_[2][1] = Math.sin(theta);
		data_[2][2] = Math.cos(theta);
		return this;
	}
    public Matrix3D yRotation(double theta) {
		identity();
		data_[0][0] = Math.cos(theta);
		data_[0][2] = Math.sin(theta);
		data_[2][0] = -Math.sin(theta);
		data_[2][2] = Math.cos(theta);
		return this;
	}
    public Matrix3D zRotation(double theta) {
		identity();
		data_[0][0] = Math.cos(theta);
		data_[0][1] = -Math.sin(theta);
		data_[1][0] = Math.sin(theta);
		data_[1][1] = Math.cos(theta);
		return this;
	}
    public Matrix3D scaler(double x, double y, double z) {
		zero();
		data_[0][0] = x;
		data_[1][1] = y;
		data_[2][2] = z;
		data_[3][3] = 1.;
		return this;
	}
    public Matrix3D scaler(Vector3D v) {
		return scaler(v.x(),v.y(),v.z());
	}
	// verbs
	public Matrix3D translate(double x, double y, double z, Matrix3D out) {
		return multiply(constantTemp_.translation(x,y,z), out);
	}
	public Matrix3D translate(Vector3D v, Matrix3D out) {
		return translate(v.x(),v.y(),v.z(),out);
	}
	public Matrix3D translate(double x, double y, double z) {
		return multiply(constantTemp_.translation(x,y,z));
	}
	public Matrix3D translate(Vector3D v) {
		return translate(v.x(),v.y(),v.z());
	}
	public Matrix3D rotateX(double theta, Matrix3D out) {
		return multiply(constantTemp_.xRotation(theta),out);
	}
	public Matrix3D rotateX(double theta) {
		return multiply(constantTemp_.xRotation(theta));
	}
	public Matrix3D rotateY(double theta, Matrix3D out) {
		return multiply(constantTemp_.yRotation(theta),out);
	}
	public Matrix3D rotateY(double theta) {
		return multiply(constantTemp_.yRotation(theta));
	}
	public Matrix3D rotateZ(double theta, Matrix3D out) {
		return multiply(constantTemp_.zRotation(theta),out);
	}
	public Matrix3D rotateZ(double theta) {
		return multiply(constantTemp_.zRotation(theta));
	}
	public Matrix3D scale(double x, double y, double z, Matrix3D out) {
		return multiply(constantTemp_.scaler(x,y,z),out);
	}
	public Matrix3D scale(Vector3D v, Matrix3D out) {
		return scale(v.x(),v.y(),v.z(),out);
	}
	public Matrix3D scale(double x, double y, double z) {
		return multiply(constantTemp_.scaler(x,y,z));
	}
	public Matrix3D scale(Vector3D v) {
		return scale(v.x(),v.y(),v.z());
	}
   	public void transform(double src[], double dst[]) {
   		for (int i = 0; i<3; ++i)
        	dst[i] = data_[i][0] * src[0] + data_[i][1] * src[1] + data_[i][2] * src[2] + data_[i][3];
    }
    public void transform(Vector3D src, Vector3D dst) {
		transform(src.vector(),dst.vector());
	}
	public String toString() {
		StringBuffer buf = new StringBuffer(100);
		buf.append("{");
		for(int i = 0; i<4; ++i) {
			buf.append("{");
			for(int j = 0; j<4; ++j) {
				buf.append(data_[i][j]);
				if(j!=3)
					buf.append(",");
			}
			buf.append("}");
			if(i!=3)
				buf.append(",");
		}
		buf.append("}");
		return buf.toString();
	}
}