/* * JFLAP - Formal Languages and Automata Package * * * Susan H. Rodger * Computer Science Department * Duke University * August 27, 2009 * Copyright (c) 2002-2009 * All rights reserved. * JFLAP is open source software. Please see the LICENSE for terms. * */ package gui.transform; import java.awt.geom.Point2D; import java.io.Serializable; /** * This is an affine transform matrix rather like the AffineTransform * of jawa.awt.geom fame, but for three dimensions. This type of * matrix does not support shearing or scaling. * * @author Thomas Finley */ public class Matrix implements Cloneable, Serializable { /** * Instantiates a new identity matrix. */ public Matrix() { this(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); } /** * Instantiates a new matrix with the given entries. Twelve entries are * given as parameters, as if reading the entries of the matrix off from * left to right, and then from top to bottom. */ public Matrix(double a11, double a12, double a13, double a14, double a21, double a22, double a23, double a24, double a31, double a32, double a33, double a34) { entry = new double[][] { { a11, a12, a13, a14 }, { a21, a22, a23, a24 }, { a31, a32, a33, a34 }, { 0.0, 0.0, 0.0, 1.0 } }; } /** * Instantiates a copy of the passed in matrix. * * @param m * the matrix to copy */ public Matrix(Matrix m) { this(m.entry[0][0], m.entry[0][1], m.entry[0][2], m.entry[0][3], m.entry[1][0], m.entry[1][1], m.entry[1][2], m.entry[1][3], m.entry[2][0], m.entry[2][1], m.entry[2][2], m.entry[2][3]); } /** * Returns a copy of this object. * * @return a copy of this matrix */ public Object clone() { return new Matrix(this); } /** * Returns the entry at the given entry. * * @param row * the row index, must be 0 through 3 * @param column * the column index, must be 0 through 3 * @return the entry at the given row and column */ public final double valueAt(int row, int column) { return entry[row][column]; } /** * Given another matrix, this will premultiply that matrix times this * matrix, and store the result in this matrix. If A is this matrix * and B is the matrix passed in as a parameter, then this is * similar to A = BA. * * @param matrix * the matrix to premultiply */ public final void premultiply(Matrix matrix) { entry2[3][0] = entry[3][1] = entry[3][2] = 0.0; entry2[3][3] = 1.0; for (int i = 0; i < 3; i++) for (int j = 0; j < 4; j++) { entry2[i][j] = 0.0; for (int k = 0; k < 4; k++) entry2[i][j] += matrix.entry[i][k] * entry[k][j]; } // Swap! double[][] oldentry = entry; entry = entry2; entry2 = oldentry; } /** * Given another matrix, this will premultiply that matrix times this * matrix, and store the result in this matrix. If B is this matrix * and A is the matrix passed in as a parameter, then this is * similar to B = BA. * * @param matrix * the matrix to premultiply */ public final void postmultiply(Matrix matrix) { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) { entry2[i][j] = 0.0; for (int k = 0; k < 4; k++) entry2[i][j] += entry[i][k] * matrix.entry[k][j]; } // Swap! double[][] oldentry = entry; entry = entry2; entry2 = oldentry; } /** * Turns the current matrix about the X-axis. * * @param angle * the angle to turn */ public final void pitch(double angle) { if (XAXIS_ANGLE == -angle) { XAXIS_ANGLE = angle; XAXIS_TURN.entry[1][2] = -XAXIS_TURN.entry[1][2]; XAXIS_TURN.entry[2][1] = -XAXIS_TURN.entry[2][1]; } else if (XAXIS_ANGLE != angle) { // Cache it! XAXIS_ANGLE = angle; angle = Math.toRadians(angle); double c = Math.cos(angle), s = Math.sin(angle); XAXIS_TURN = new Matrix(1.0, 0.0, 0.0, 0.0, 0.0, c, -s, 0.0, 0.0, s, c, 0.0); } premultiply(XAXIS_TURN); } /** * Turns the current matrix about the Y-axis. * * @param angle * the angle to turn */ public final void roll(double angle) { if (YAXIS_ANGLE == -angle) { YAXIS_ANGLE = angle; YAXIS_TURN.entry[0][2] = -YAXIS_TURN.entry[0][2]; YAXIS_TURN.entry[2][0] = -YAXIS_TURN.entry[2][0]; } else if (YAXIS_ANGLE != angle) { // Cache it! YAXIS_ANGLE = angle; angle = Math.toRadians(angle); double c = Math.cos(angle), s = Math.sin(angle); YAXIS_TURN = new Matrix(c, 0.0, s, 0.0, 0.0, 1.0, 0.0, 0.0, -s, 0.0, c, 0.0); } premultiply(YAXIS_TURN); } /** * Turns the current matrix about the Z-axis. * * @param angle * the angle to turn */ public final void yaw(double angle) { if (ZAXIS_ANGLE == -angle) { ZAXIS_ANGLE = angle; ZAXIS_TURN.entry[0][1] = -ZAXIS_TURN.entry[0][1]; ZAXIS_TURN.entry[1][0] = -ZAXIS_TURN.entry[1][0]; } else if (ZAXIS_ANGLE != angle) { // Cache it! ZAXIS_ANGLE = angle; angle = Math.toRadians(angle); double c = Math.cos(angle), s = Math.sin(angle); ZAXIS_TURN = new Matrix(c, -s, 0.0, 0.0, s, c, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); } premultiply(ZAXIS_TURN); } /** * Translates the current matrix */ public final void translate(double x, double y, double z) { if (DIRS[0] != x || DIRS[1] != y || DIRS[2] != z) { // Cache it! DIRS[0] = TRANSLATE.entry[0][3] = x; DIRS[1] = TRANSLATE.entry[1][3] = y; DIRS[2] = TRANSLATE.entry[2][3] = z; } premultiply(TRANSLATE); } /** * Returns the point (the x and y coordinates) of the transformed origin. * * @param point * the point to store the location in * @return either the point passed in or, if that point was null, a newly * allocated point */ public final Point2D origin(Point2D point) { if (point == null) point = new Point2D.Double(); origin(ORIGIN_REUSE); point.setLocation(ORIGIN_REUSE[0], ORIGIN_REUSE[1]); return point; } /** * Returns the x, y, and z coordinates of the transformed origin. * * @param array * the array of three entries which will hold, in order, the x, * y, and z coordinates, or null if you wish a * newly allocated array * @return the array */ public final double[] origin(double[] array) { if (array == null) array = new double[3]; for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) INVERSE.entry[i][j] = entry[j][i]; premultiply(INVERSE); for (int i = 0; i < 3; i++) array[i] = entry[i][3]; // Swap it back! double[][] old = entry; entry = entry2; entry2 = old; // Yay. return array; } /** * Returns a string representation of this matrix. * * @return a string representation of this matrix */ public final String toString() { StringBuffer sb = new StringBuffer(); sb.append('('); for (int i = 0; i < 4; i++) { if (i != 0) sb.append("; "); for (int j = 0; j < 4; j++) { if (j != 0) sb.append(','); sb.append(' '); sb.append(entry[i][j]); } } sb.append(" )"); return sb.toString(); } /** * The entries, where the first index in the array is the row, and the * second index is the column. */ public double[][] entry; /** The backup entries. */ private double[][] entry2 = new double[4][4]; /** The old angles for each of the cached turn matrices. */ private static double XAXIS_ANGLE = Double.NaN, YAXIS_ANGLE = Double.NaN, ZAXIS_ANGLE = Double.NaN; /** The cached matrices for turning. */ private static Matrix XAXIS_TURN, YAXIS_TURN, ZAXIS_TURN; /** The old distances for each of the cache translation matrices. */ private static double[] DIRS = new double[] { 0.0, 0.0, 0.0 }; /** The old translation matrix. */ private static final Matrix TRANSLATE = new Matrix(); /** * The old matrix used for computing inversions back to user space... */ private static final Matrix INVERSE = new Matrix(); /** * Used for the origin methods, so new arrays needn't constantly be * allocated. */ private static final double[] ORIGIN_REUSE = new double[3]; public static final String arrayString(double[] d) { return "( " + d[0] + ", " + d[1] + ", " + d[2] + " )"; } }