package SModeling;


/**
 *  4x4 matrix for homogeneous coordinate transformations. 4th column is 
 *  not explicitly represented because it is always (0,0,0,1)T.
 *  Constructors for identity, transformation, rotation and scaling.
 *  Methods for matrix and vector multiplication. The inverse of all scalings
 *  and rotations performed on the matrix are saved as a inverse matrix.<p> 
 *
* <hr>
 * Copyright (c) 1995, H. Robert Frost, Stanford University.
 * All rights reserved.<p>
 * Copyright (c) 1996, H. Robert Frost, Enterprise Integration Technologies,
 * Inc. All rights reserved.<p>
 *
 * RESTRICTED RIGHTS LEGEND: Use, duplication or disclosure by the 
 * Government is subject to restrictions as set forth in 
 * subparagraph(c)(1)(ii) of the Rights in Technical Data and Computer
 * Software clause at DFARS 252.227-7013 and in similar clauses in the
 * FAR and NASA FAR supplement.<p>
 * 
 * This software is bound by the terms and conditions listed in the
 * attached <a href="../LICENSE">LICENSE file</A>.
 * <hr>
 * @author <A HREF="http://cdr.stanford.edu/html/people/frost-bio.html"> Rob Frost</A>
 * @version 5/21/96 for Java(tm) Language Version 1.0.2
 */

public class matrix implements Cloneable {

  float x1, x2, x3, x4;
  float y1, y2, y3, y4;
  float z1, z2, z3, z4;
  static final double pi = 3.14159265;
  boolean has_inverse;

  /**
   * The inverse of the total sum of rotations and scalings performed 
   * on the matrix.
   */

  public matrix inverse;

 
  /**
   * Creates an identity matrix.
   * @param bool True if the inverse for this matrix should be computed.
   */

  public matrix (boolean bool) {
    x1 = y2 = z3 = 1;
    x2 = x3 = y1 = y3 = z1 = z2 = x4 = y4 = z4 = 0;
    has_inverse = bool;
    if(has_inverse) {
      inverse = new matrix(false);
    }
  }

  /**
   * Creates a translation matrix, (x,y,z) to (x + dx, y + dy, z + dz).
   * @param dx Translation in x direction.
   * @param dy Translation in y direction.
   * @param dz Translation in z direction.
   */

  public matrix (float dx, float dy, float dz) {
    this(false); /* create identity matrix */
    x4 = dx;
    y4 = dy;
    z4 = dz;
  }

 /**	
   * Allows the object to be cloned. Must cast to appropriate
   * object type.
   * @return Copy of the object.
   */

  public Object clone() {
    Object result = null;
    try{
      result = super.clone();
      return result;
    } catch (CloneNotSupportedException e){}
    return result;
  } 

  /**
   * Reinitializes this matrix to the identity matrix.
   */

  public void unit () {
    x1 = y2 = z3 = 1;
    x2 = x3 = y1 = y3 = z1 = z2 = x4 = y4 = z4 = 0;
  }

  /**
   * Creates a right-hand rotation matrix about the specified axis.
   * @param theta Angle of rotation.
   * @param axis The axis about which to rotate (1=x,2=y,3=z)
   */
  public matrix (double theta, int axis) {

    this(false); /* create identity matrix */ 
    theta *= (pi/180.0);
      
    double cos_theta = Math.cos(theta);
    double sin_theta = Math.sin(theta);

    switch(axis) {
   
    case 1: /* x-axis rotation */
      y2 =  (float)cos_theta;
      y3 =  (float)-sin_theta;
      z2 =  (float)sin_theta;
      z3 =  (float)cos_theta;
      break;
    case 2: /* y-axis rotation */
      x1 =  (float)cos_theta;
      x3 =  (float)sin_theta;
      z1 =  (float)-sin_theta;
      z3 =  (float)cos_theta;
      break;
    case 3: /* z-axis rotation */
      x1 =  (float)cos_theta;
      x2 = (float)-sin_theta;
      y1 =  (float)sin_theta;
      y2 =  (float)cos_theta;
      break;
    default:
      break;
    }

  }

  /**
   * Creates a scaling matrix.
   * @param sx Scale factor in x-direction
   * @param sy Scale factor in y-direction
   * @param sz Scale factor in z-direction
   * @param dummy Used to distinguish from the translation constructor
   */

  public matrix ( float sx, float sy, float sz, boolean dummy) {
    this(false);
    x1 = sx;
    y2 = sy;
    z3 = sz;
  }

  /**
   * Applies a translation to the current matrix.
   * @param dx Amount to translate in x-direction
   * @param dy Amount to translate in y-direction
   * @param dz Amount to translate in z-direction
   */

  public void translate(float dx, float dy, float dz ) {
    matrix m = new matrix(dx,dy,dz);
    matrix inv = new matrix(-dx,-dy,-dz);
    multiply(m);
    if(has_inverse) {
      inverse.rev_multiply(inv);
    } 
 }

  /**
   * Applies a right-hand rotation about the specified axis.
   * @param theta Angle of rotation.
   * @param axis The axis about which to rotate (1=x,2=y,3=z)
   */

  public void rotate(double theta, int axis ) {
    matrix m = new matrix(theta, axis);
    matrix inv = new matrix(-theta,axis);
    multiply(m);
    if(has_inverse) {
      inverse.rev_multiply(inv);
    }
  }

  /**
   * Scales the current matrix.
   * @param sx Scale factor in x-direction
   * @param sy Scale factor in y-direction
   * @param sz Scale factor in z-direction
   * @param dummy Used to distinguish from the translation
   */

  public void scale(float sx, float sy, float sz, boolean dummy ) {
    matrix m = new matrix(sx,sy,sz,dummy);
    matrix inv = new matrix(1/sx, 1/sy, 1/sz, dummy);
    multiply(m);
    if(has_inverse) {
      inverse.rev_multiply(inv);
    }
  }

  /**
   * Post multiplies by matrix m, (this * m)
   * @param m matrix to multiply
   */

  public void multiply (matrix m) {
    matrix temp = new matrix(false);
    temp = (matrix)this.clone();

    x1 = temp.x1*m.x1 + temp.y1*m.x2 + temp.z1*m.x3;
    x2 = temp.x2*m.x1 + temp.y2*m.x2 + temp.z2*m.x3;
    x3 = temp.x3*m.x1 + temp.y3*m.x2 + temp.z3*m.x3;
    x4 = temp.x4*m.x1 + temp.y4*m.x2 + temp.z4*m.x3 + m.x4;

    y1 = temp.x1*m.y1 + temp.y1*m.y2 + temp.z1*m.y3;
    y2 = temp.x2*m.y1 + temp.y2*m.y2 + temp.z2*m.y3;
    y3 = temp.x3*m.y1 + temp.y3*m.y2 + temp.z3*m.y3;
    y4 = temp.x4*m.y1 + temp.y4*m.y2 +temp. z4*m.y3 + m.y4;

    z1 = temp.x1*m.z1 + temp.y1*m.z2 + temp.z1*m.z3;
    z2 = temp.x2*m.z1 + temp.y2*m.z2 + temp.z2*m.z3;
    z3 = temp.x3*m.z1 + temp.y3*m.z2 + temp.z3*m.z3;
    z4 = temp.x4*m.z1 + temp.y4*m.z2 + temp.z4*m.z3 + m.z4;
  }
  
  /**
   * Pre multiplies by matrix m, (m*this)
   * @param m matrix to multiply
   */

  public void rev_multiply (matrix m) {
    matrix temp = new matrix(false);
    temp = (matrix)this.clone();

    x1 = m.x1*temp.x1 + m.y1*temp.x2 + m.z1*temp.x3;
    x2 = m.x2*temp.x1 + m.y2*temp.x2 + m.z2*temp.x3;
    x3 = m.x3*temp.x1 + m.y3*temp.x2 + m.z3*temp.x3;
    x4 = m.x4*temp.x1 + m.y4*temp.x2 + m.z4*temp.x3 + temp.x4;

    y1 = m.x1*temp.y1 + m.y1*temp.y2 + m.z1*temp.y3;
    y2 = m.x2*temp.y1 + m.y2*temp.y2 + m.z2*temp.y3;
    y3 = m.x3*temp.y1 + m.y3*temp.y2 + m.z3*temp.y3;
    y4 = m.x4*temp.y1 + m.y4*temp.y2 +m. z4*temp.y3 + temp.y4;

    z1 = m.x1*temp.z1 + m.y1*temp.z2 + m.z1*temp.z3;
    z2 = m.x2*temp.z1 + m.y2*temp.z2 + m.z2*temp.z3;
    z3 = m.x3*temp.z1 + m.y3*temp.z2 + m.z3*temp.z3;
    z4 = m.x4*temp.z1 + m.y4*temp.z2 + m. z4*temp.z3 + temp.z4;
  } 

  /**
   * Post multiplies by a vector, the vector is replaced with the result.
   * @param hp Vector to multiply with.
   */

  public void multiply (hpoint hp) {
    hpoint temp = new hpoint();
    temp = (hpoint)hp.clone();

    hp.xprime = x1 * temp.xprime + x2 * temp.yprime + x3 * temp.zprime +
      x4 * temp.w;
    hp.yprime = y1 * temp.xprime + y2 * temp.yprime + y3 * temp.zprime +
      y4 * temp.w;
    hp.zprime = z1 * temp.xprime + z2 * temp.yprime + z3 * temp.zprime +
      z4 * temp.w;
  }

  /**
   * Returns a string representation of the matrix.
   * @return String rep of the matrix
   */

  public String toString() {
    String answer = "";
    answer = Float.toString(x1) + " " + Float.toString(y1)  + " " + Float.toString(z1) + "\n" +
      Float.toString(x2)  + " " + Float.toString(y2) + " " + Float.toString(z2) + "\n" +
      Float.toString(x3) + " " + Float.toString(y3) + " " + Float.toString(z3) + "\n" +
      Float.toString(x4) + " " + Float.toString(y4) + " " + Float.toString(z4);
    return answer;
  } 

  /**
   * Returns a string representation the inverse matrix.
   * @return String rep of the inverse matrix
   */

  public String printInverse() {
    if(has_inverse) {
      return inverse.toString();
    }
    else return "";
  }

}
