/*
* $Id: Matrix.java,v 1.2 1998/12/12 06:22:39 doug Exp $
*
* Generalised nxm Matrix operations
*/
/**
* <P>
* The matrix.Matrix class provides a generalised 2-dimensional
* matrix package.
* Matrices are treated as values wherever possible, so matrix
* operations such as multiplication produce a new matrix as the
* result, rather than operate directly on the matrix.
* <P>
* This package is optimised towards the 4x4 transformation
* matrices which can be used for 3D projections.
* There are a number of class methods for generating transformation
* matrices.
*
* @version $Revision: 1.2 $
* @author Doug Palmer (<A HREF="mailto:dougal@projectx.com.au">dougal@projectx.com.au</A>)
*/
// Modified by LKS to remove package and associated exceptions!
public class Matrix extends Object implements Cloneable {
/**
* Matrix dimensions.
*/
protected int rows, cols;
/**
* Matrix data.
*/
protected double matrix[][];
// Constants
/**
* X-axis.
* Position of X-axis data in a transformation matrix.
*/
protected static final int X = 0;
/**
* Y-axis.
* Position of Y-axis data in a transformation matrix.
*/
protected static final int Y = 1;
/**
* Z-axis.
* Position of Z-axis data in a transformation matrix.
*/
protected static final int Z = 2;
/**
* Normalisation.
* Position of normalisation data in a transformation matrix.
*/
protected static final int N = 3;
/**
* Transformation size.
* Size of a transformation matrix.
*/
protected static final int TD = N + 1;
/**
* Isometric factor.
* Amount of weight to give to X and Y axes during
* isometric transformation.
*/
protected static final double ISOMETRIC = 1.0 / Math.sqrt(2.0);
// Constructors
/**
* Unit matrix.
* Unit matrix of size <VAR>rows</VAR> x <VAR>cols</VAR>.
* @param rows Number of rows in matrix.
* @param cols Number of columns in matrix.
*/
public Matrix(int rows, int cols)
{
int i, j;
this.rows = rows;
this.cols = cols;
matrix = new double[this.rows][this.cols];
for (i = 0; i < this.rows; i++) {
for (j = 0; j < this.cols; j++)
this.matrix[i][j] = 0.0;
if (i < this.cols)
this.matrix[i][i] = 1.0;
}
}
/**
* Initialised matrix.
* Make a matrix from the 2D array <VAR>init</VAR>.
* @param init An array of arrays containing the initial data.
* @exception matrix.MatrixDimensionException Thrown if <VAR>init</VAR> contains rows of differing length.
*/
public Matrix(double[][] init)
{
int i, j;
this.rows = init.length;
this.cols = init[0].length;
matrix = new double[this.rows][this.cols];
for (i = 0; i < this.rows; i++) {
if (init[i].length != this.cols)
System.out.println("Matrix(double[][] init) : MatrixDimensionException(\"Uneven rows\")");
for (j = 0; j < this.cols; j++)
this.matrix[i][j] = init[i][j];
}
}
/**
* Initialised matrix.
* Make a vector matrix from the 1D array <VAR>init</VAR>.
* @param init Array to initialize the matrix.
*/
public Matrix(double[] init)
{
int i, j;
this.rows = init.length;
this.cols = 1;
matrix = new double[this.rows][this.cols];
for (i = 0; i < this.rows; i++)
this.matrix[i][0] = init[i];
}
/**
* Copied matrix.
* Make a copy of <VAR>copy</VAR>.
* This also makes an internal copy of the matrix data,
* to avoid later clashes.
* @param copy The matrix to copy from.
*/
public Matrix(Matrix copy)
{
int i, j;
this.rows = copy.rows;
this.cols = copy.cols;
this.matrix = new double[this.rows][this.cols];
for (i = 0; i < this.rows; i++)
for (j = 0; j < this.cols; j++)
this.matrix[i][j] = copy.matrix[i][j];
}
/**
* Unit transformation matrix.
* @return A transformation matrix.
*/
public static Matrix transformation()
{
return new Matrix(TD, TD);
}
/**
* Scaling transformation matrix.
* @param sx Scaling factor for X axis.
* @param sy Scaling factor for Y axis.
* @param sz Scaling factor for Z axis.
* @return A transformation matrix.
*/
public static Matrix scaling(double sx, double sy, double sz)
{
Matrix scaling = Matrix.transformation();
scaling.matrix[X][X] = sx;
scaling.matrix[Y][Y] = sy;
scaling.matrix[Z][Z] = sz;
return scaling;
}
/**
* Private rotation generating method.
*/
private static Matrix rotateAxis(double angle, int a1, int a2)
{
double sin = Math.sin(angle), cos = Math.cos(angle);
Matrix rotate = Matrix.transformation();
rotate.matrix[a1][a1] = cos;
rotate.matrix[a1][a2] = -sin;
rotate.matrix[a2][a2] = cos;
rotate.matrix[a2][a1] = sin;
return rotate;
}
/**
* Rotation matrix.
* Transformation matrix for a rotation about the X axis.
* @param angle Number of radians to rotate about the axis.
* @return A transformation matrix.
*/
public static Matrix rotateX(double angle)
{
return rotateAxis(angle, Y, Z);
}
/**
* Rotation matrix.
* Transformation matrix for a rotation about the Y axis.
* @param angle Number of radians to rotate about the axis.
* @return A transformation matrix.
*/
public static Matrix rotateY(double angle)
{
return rotateAxis(angle, X, Z);
}
/**
* Rotation matrix.
* Transformation matrix for a rotation about the Z axis.
* @param angle Number of radians to rotate about the axis.
* @return A transformation matrix.
*/
public static Matrix rotateZ(double angle)
{
return rotateAxis(angle, X, Y);
}
/**
* Translation matrix.
* Translation transformation matrix.
* @param tx Amount to translate a point along the X axis by.
* @param ty Amount to translate a point along the Y axis by.
* @param tz Amount to translate a point along the Z axis by.
* @return A transformation matrix.
*/
public static Matrix translate(double tx, double ty, double tz)
{
Matrix translation = Matrix.transformation();
translation.matrix[X][N] = tx;
translation.matrix[Y][N] = ty;
translation.matrix[Z][N] = tz;
return translation;
}
/**
* Private reflection generating method
*/
private static Matrix reflect(int axis)
{
Matrix reflect = Matrix.transformation();
reflect.matrix[axis][axis] = -reflect.matrix[axis][axis];
return reflect;
}
/**
* Reflection matrix.
* Reflect coordinates about the X axis.
* @return A transformation matrix.
*/
public static Matrix reflectX()
{
return Matrix.reflect(X);
}
/**
* Reflection matrix.
* Reflect coordinates about the Y axis.
* @return A transformation matrix.
*/
public static Matrix reflectY()
{
return Matrix.reflect(Y);
}
/**
* Reflection matrix.
* Reflect coordinates about the Z axis.
* @return A transformation matrix.
*/
public static Matrix reflectZ()
{
return Matrix.reflect(Z);
}
/**
* Private permutation generating method
*/
private static Matrix permute(int xt, int yt, int zt)
{
Matrix permute = Matrix.transformation();
permute.matrix[X][X] = 0.0;
permute.matrix[Y][Y] = 0.0;
permute.matrix[Z][Z] = 0.0;
permute.matrix[xt][X] = 1.0;
permute.matrix[yt][Y] = 1.0;
permute.matrix[zt][Z] = 1.0;
return permute;
}
/**
* Permutation matrix.
* Permute coordinates <VAR>(x, y, z) -> (y, z, x)</VAR>
* @return A transformation matrix.
*/
public static Matrix permuteYZX()
{
return permute(Z, X, Y);
}
/**
* Permutation matrix.
* Permute coordinates <VAR>(x, y, z) -> (z, x, y)</VAR>
* @return A transformation matrix.
*/
public static Matrix permuteZXY()
{
return permute(Y, Z, X);
}
/**
* Permutation matrix.
* Permute coordinates <VAR>(x, y, z) -> (y, x, z)</VAR>
* @return A transformation matrix.
*/
public static Matrix permuteYXZ()
{
return permute(Y, X, Z);
}
/**
* Permutation matrix.
* Permute coordinates <VAR>(x, y, z) -> (x, z, y)</VAR>
* @return A transformation matrix.
*/
public static Matrix permuteXZY()
{
return permute(X, Z, Y);
}
/**
* Permutation matrix.
* Permute coordinates <VAR>(x, y, z) -> (z, y, x)</VAR>
* @return A transformation matrix.
*/
public static Matrix permuteZYX()
{
return permute(Z, Y, X);
}
/**
* Isometric matrix.
* Isometric transform along the Z axis.
* @return A transformation matrix.
*/
public static Matrix isometric()
{
Matrix isometric = Matrix.transformation();
isometric.matrix[X][Z] = ISOMETRIC;
isometric.matrix[Y][Z] = ISOMETRIC;
return isometric;
}
/**
* Cavalier projection.
* Cavalier projection onto the X-Y plane at <VAR>angle</VAR>
* @param angle Angle of rotation in radians
* @return A transformation matrix.
*/
public static Matrix cavalier(double angle)
{
double c = Math.cos(angle);
double s = Math.sin(angle);
Matrix cavalier = Matrix.transformation();
cavalier.matrix[X][Z] = c;
cavalier.matrix[Y][Z] = s;
return cavalier;
}
/**
* Cabinet projection.
* Cabinet projection onto the X-Y plane at <VAR>angle</VAR>
* @param angle Angle of rotation in radians
* @return A transformation matrix.
*/
public static Matrix cabinet(double angle)
{
double c = Math.cos(angle) / 2;
double s = Math.sin(angle) / 2;
Matrix cabinet = Matrix.transformation();
cabinet.matrix[X][Z] = c;
cabinet.matrix[Y][Z] = s;
return cabinet;
}
/**
* Perspective matrix.
* Perspective transformation along the Z axis,
* with a vanishing point at <VAR>(0, 0, vz)</VAR>.
* @param vz Z coordinate of vanishing point.
* @return A transformation matrix.
*/
public static Matrix perspective(double vz)
{
Matrix perspective = Matrix.transformation();
perspective.matrix[N][Z] = 1.0 / vz;
return perspective;
}
/**
* @return Matrix rank (2).
*/
public int getRank()
{
return 2;
}
/**
* @return Number of rows in matrix.
*/
public int getRows()
{
return this.rows;
}
/**
* @return Number of columns in matrix.
*/
public int getCols()
{
return this.cols;
}
/**
* @param i Row number of element.
* @param j Column number of element.
* @return Element at <VAR>(i, j)</VAR>.
*/
public double at(int i, int j)
{
return this.matrix[i][j];
}
/**
* @param i Row number of element.
* @param j Column number of element.
* @param val Value to set <VAR>(i, j)</VAR> to.
*/
public void put(int i, int j, double val)
{
this.matrix[i][j] = val;
}
/**
* Private copying method.
*/
protected void copyFrom(Matrix copy)
{
int i, j, cpn, cpm;
cpn = Math.min(this.rows, copy.rows);
cpm = Math.min(this.cols, copy.cols);
for (i = 0; i < cpn; i++)
for (j = 0; j < cpm; j++)
this.matrix[i][j] = copy.matrix[i][j];
}
/**
* Copying
* @returns Cloned matrix.
*/
public Object clone()
{
return new Matrix(this);
}
/**
* Copying.
* Clone this matrix with a new size of
* <VAR>rows</VAR> x <VAR>cols</VAR>.
* As much of the matrix that can be copied will be copied, with
* excess parts being discarded, and remaining parts of the
* copy filled with a unit matrix.
* @param rows New number of rows.
* @param cols New number of columns.
* @return Cloned matrix.
*/
protected Object clone(int rows, int cols)
{
Matrix res = new Matrix(rows, cols);
res.copyFrom(this);
return res;
}
/**
* Equality.
* Two matrices are equal if they have the same size and
* equal elements.
* @param obj Object to test matrix against.
* @return Boolean equality value.
*/
public boolean equals(Object obj)
{
Matrix mat;
int i, j;
if (!(obj instanceof Matrix))
return false;
mat = (Matrix) obj;
if (this.rows != mat.rows || this.cols != mat.cols)
return false;
for (i = 0; i < this.rows; i++)
for (j = 0; j < this.cols; j++)
if (this.matrix[i][j] != mat.matrix[i][j])
return false;
return true;
}
/**
* Operations.
* Hash value for insertion into hash tables.
* @return Hash value.
*/
public int hashCode()
{
int i, n = Math.min(this.rows, this.cols);
int sum = 32767;
double v;
for (i = 0; i < n; i++) {
v = this.matrix[i][i];
if (v > Integer.MIN_VALUE && v < Integer.MAX_VALUE)
sum ^= (int) v;
}
return sum;
}
/**
* Operations.
* The trace of a matrix is the sum of the elements along
* it's major diagonal:
* <VAR>M.trace() = Sum<SUB>i=0</SUB><SUP>n</SUP> M<SUB>ii</SUB></VAR>.
* @return Trace.
*/
public double trace()
{
int i, n = Math.min(this.rows, this.cols);
double sum = 0.0;
for (i = 0; i < n; i++)
sum += this.matrix[i][i];
return sum;
}
/**
* Arithmetic.
* Add two matrices together using the rules of matrix arithmetic:
* <VAR>M.add(N)<SUB>ij</SUB> = M<SUB>ij</SUB> + N<SUB>ij</SUB></VAR>.
* @param arg Matrix to add to this matrix.
* @return A new matrix which is the sum of this matrix and <VAR>arg</VAR>.
* @exception matrix.MatrixDimensionException Thrown if the two matrices do not have the same size.
*/
public Matrix add(Matrix arg)
{
Matrix result;
int i, j;
if (this.rows != arg.rows || this.cols != arg.cols)
System.out.println("Matrix add(Matrix arg) : MatrixDimensionException(\"Attempted to add incompatible matrices\")");
result = (Matrix) this.clone();
for (i = 0; i < this.rows; i++)
for (j = 0; j < this.cols; j++)
result.matrix[i][j] = this.matrix[i][j] + arg.matrix[i][j];
return result;
}
/**
* Arithmetic.
* Add <VAR>arg</VAR> to each element of the main diagonal.
* Similar to adding a diagonal matrix of <VAR>arg</VAR>.
* @param arg The value to add to this matrix.
* @return A new matrix which is the sum of <VAR>this</VAR> and <VAR>arg</VAR>.
* @see matrix.Matrix#add(Matrix)
*/
public Matrix add(double arg)
{
Matrix result;
int i;
result = (Matrix) this.clone();
for (i = 0; i < this.rows; i++)
result.matrix[i][i] += arg;
return result;
}
/**
* Arithmetic.
* Subtract two matrices using the rules of matrix arithmetic:
* <VAR>M.subtract(N)<SUB>ij</SUB> = M<SUB>ij</SUB> - N<SUB>ij</SUB></VAR>.
* @param arg Matrix to subtract from this matrix.
* @return A new matrix which is the difference between this matrix and <VAR>arg</VAR>.
* @exception matrix.MatrixDimensionException Thrown if the two matrices do not have the same size.
*/
public Matrix subtract(Matrix arg)
{
Matrix result;
int i, j;
if (this.rows != arg.rows || this.cols != arg.cols)
System.out.println("subtract(Matrix arg) : MatrixDimensionException(\"Attempted to subtacr incompatible matrices\")");
result = (Matrix) this.clone();
for (i = 0; i < this.rows; i++)
for (j = 0; j < this.cols; j++)
result.matrix[i][j] = this.matrix[i][j] - arg.matrix[i][j];
return result;
}
/**
* Arithmetic.
* Subtract <VAR>arg</VAR> from each element of the main diagonal.
* Similar to subtracting a diagonal matrix of <VAR>arg</VAR>.
* @param arg The value to subtract from this matrix.
* @return A new matrix which is the difference between <VAR>this</VAR> and <VAR>arg</VAR>.
* @see matrix.Matrix#subtract(Matrix)
*/
public Matrix subtract(double arg)
{
Matrix result;
int i;
result = (Matrix) this.clone();
for (i = 0; i < this.rows; i++)
result.matrix[i][i] -= arg;
return result;
}
/**
* Arithmetic.
* Multiply this matrix by <VAR>arg</VAR> using the rules of
* matrix arithmetic:
* <VAR>M.multiply(N)<SUB>ij</SUB> =
* Sum<SUB>k = 0</SUB><SUP>n</SUP> M<SUB>ik</SUB> * N<SUB>kj</SUB></VAR>.
* @param arg The matrix to multiply this matrix by.
* @return A new matrix which is the product of this matrix and <VAR>arg</VAR>
* @exception matrix.MatrixDimensionException Thrown if <VAR>arg</VAR> does not have the same number of rows as
* this matrix has columns.
*/
public Matrix multiply(Matrix arg)
{
Matrix result;
int i, j, k;
double sum;
if (this.cols != arg.rows)
System.out.println("multiply(Matrix arg) : MatrixDimensionException(\"Attempted to multiply incompatible matrices\")");
result = (Matrix) this.clone(this.rows, arg.cols);
for (i = 0; i < this.rows; i++)
for (j = 0; j < arg.cols; j++) {
sum = 0.0;
for (k = 0; k < this.cols; k++)
sum += this.matrix[i][k] * arg.matrix[k][j];
result.matrix[i][j] = sum;
}
return result;
}
/**
* Arithmetic.
* Multiply this matrix by <VAR>arg</VAR> using the rules of
* matrix arithmetic.
* <VAR>arg</VAR> is treated as a n x 1 vector for this purpose.
* @param arg The matrix to multiply this matrix by.
* @return An array containing the resultant vector.
* @exception matrix.MatrixDimensionException Thrown if <VAR>arg</VAR> does not have the same number of rows as this matrix has columns.
* @see matrix.Matrix#multiply(Matrix)
*/
public double[] multiply(double arg[])
{
double result[];
int i, j;
double sum;
if (arg.length == TD && this.rows == TD && this.rows == TD)
return specialMultiply(arg);
if (this.cols != arg.length)
System.out.println("double[] multiply(double arg[]) : MatrixDimensionException(\"Attempted to multiply incompatible matrices\")");
result = new double[this.rows];
for (i = 0; i < this.rows; i++) {
sum = 0.0;
for (j = 0; j < this.rows; j++)
sum += this.matrix[i][j] * arg[j];
result[i] = sum;
}
return result;
}
public void multiply(double src[], double result[])
{
int i, j;
double sum;
for (i = 0; i < this.rows; i++)
{
sum = 0.0;
for (j = 0; j < this.rows; j++)
sum += this.matrix[i][j] * src[j];
result[i] = sum;
}
}
/**
* Arithmetic.
* Special case multiplication for transformation matrices and vectors.
* This method is automatically invoked by multiply when needed.
* @see matrix.Matrix#multiply(double[])
*/
protected double[] specialMultiply(double arg[])
{
// Fast multiply for transformations
double x = arg[0];
double y = arg[1];
double z = arg[2];
double n = arg[3];
double result[] = new double[TD];
result[0] =
this.matrix[0][0] * x + this.matrix[0][1] * y +
this.matrix[0][2] * z + this.matrix[0][3] * n;
result[1] =
this.matrix[1][0] * x + this.matrix[1][1] * y +
this.matrix[1][2] * z + this.matrix[1][3] * n;
result[2] =
this.matrix[2][0] * x + this.matrix[2][1] * y +
this.matrix[2][2] * z + this.matrix[2][3] * n;
result[3] =
this.matrix[3][0] * x + this.matrix[3][1] * y +
this.matrix[3][2] * z + this.matrix[3][3] * n;
return result;
}
/**
* Arithmetic.
* Multiply a matrix by a scalar:
* <VAR>M.multiply(a)<SUB>ij</SUB> = M<SUB>ij</SUB> * a</VAR>.
* @param arg The scalar to multiply by.
* @return A new matrix with each element of this matrix multiplied by <VAR>arg</VAR>.
* @see matrix.Matrix#multiply(Matrix)
*/
public Matrix multiply(double arg)
{
Matrix result;
int i, j;
result = (Matrix) this.clone();
for (i = 0; i < this.rows; i++)
for (j = 0; j < this.cols; j++)
result.matrix[i][j] *= arg;
return result;
}
/**
* Operations.
* Transpose a matrix:
* <VAR>M.transposed()<SUB>ij</SUB> = M<SUB>ji</SUB></VAR>
* @return A new matrix which is this matrix transposed.
*/
public Matrix transposed()
{
int i, j;
Matrix result = new Matrix(this.cols, this.rows);
for (i = 0; i < this.rows; i++)
for (j = 0; j < this.cols; j++)
result.matrix[j][i] = this.matrix[i][j];
return result;
}
/**
* Operations.
* Negate a matrix:
* <VAR>M.negated()<SUB>ij</SUB> = -M<SUB>ij</SUB></VAR>.
* @return A new matrix which is this matrix with each element negated.
*/
public Matrix negated()
{
int i, j;
Matrix result = new Matrix(this.cols, this.rows);
for (i = 0; i < this.rows; i++)
for (j = 0; j < this.cols; j++)
result.matrix[i][j] = - this.matrix[i][j];
return result;
}
/**
* Operations.
* Invert a square matrix:
* <VAR>M * M.inverted() = I</VAR>.
* @return A new matrix which is the inverse of this matrix.
* @exception matrix.MatrixDimensionException if the matrix is not a sqaure matrix.
* @exception matrix.MatrixSingularException if the matrix cannot be inverted (is singular).
*/
public Matrix inverted()
{
Matrix inverted = new Matrix(this.rows, this.cols);
Matrix copy = (Matrix) this.clone();
double scale;
int i, j, k;
if (this.rows != this.cols)
System.out.println("Matrix inverted() : MatrixDimensionException(\"Can only invert square matrices\")");
for (i = 0; i < this.rows; i++) {
scale = copy.matrix[i][i];
if (scale == 0.0)
System.out.println("Matrix inverted() : MatrixSingularException()");
for (j = 0; j < this.cols; j++) {
copy.matrix[i][j] /= scale;
inverted.matrix[i][j] /= scale;
}
for (j = 0; j < this.rows; j++)
if (i != j) {
scale = copy.matrix[j][i];
for (k = 0; k < this.cols; k++) {
copy.matrix[j][k] -= copy.matrix[i][k] * scale;
inverted.matrix[j][k] -= inverted.matrix[i][k] * scale;
}
}
}
return inverted;
}
/**
* Printing.
* Converts the matrix into a string of form
* <EM>
* [[m<SUB>00</SUB>, ..., m<SUB>0c</SUB>],
* ...,
* [m<SUB>r0</SUB>, ..., m<SUB>cc</SUB>]]
* </EM>
* @return The string representation of this matrix.
*/
public String toString()
{
StringBuffer sb = new StringBuffer();
int i, j;
sb.append("[");
for (i = 0; i < this.rows; i++) {
sb.append("[");
for (j = 0; j < this.cols; j++) {
sb.append(this.matrix[i][j]);
if (j == this.cols - 1)
sb.append("]");
else
sb.append(", ");
}
if (i < this.rows - 1)
sb.append(", ");
}
sb.append("]");
return sb.toString();
}
}