/* * Copyright (c) 2009-2012 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.math; import java.util.logging.Level; import java.util.logging.Logger; public class Eigen3f implements java.io.Serializable { static final long serialVersionUID = 1; private static final Logger logger = Logger.getLogger(Eigen3f.class .getName()); float[] eigenValues = new float[3]; Vector3f[] eigenVectors = new Vector3f[3]; static final double ONE_THIRD_DOUBLE = 1.0 / 3.0; static final double ROOT_THREE_DOUBLE = Math.sqrt(3.0); public Eigen3f() { } public Eigen3f(Matrix3f data) { calculateEigen(data); } public void calculateEigen(Matrix3f data) { // prep work... eigenVectors[0] = new Vector3f(); eigenVectors[1] = new Vector3f(); eigenVectors[2] = new Vector3f(); Matrix3f scaledData = new Matrix3f(data); float maxMagnitude = scaleMatrix(scaledData); // Compute the eigenvalues using double-precision arithmetic. double roots[] = new double[3]; computeRoots(scaledData, roots); eigenValues[0] = (float) roots[0]; eigenValues[1] = (float) roots[1]; eigenValues[2] = (float) roots[2]; float[] maxValues = new float[3]; Vector3f[] maxRows = new Vector3f[3]; maxRows[0] = new Vector3f(); maxRows[1] = new Vector3f(); maxRows[2] = new Vector3f(); for (int i = 0; i < 3; i++) { Matrix3f tempMatrix = new Matrix3f(scaledData); tempMatrix.m00 -= eigenValues[i]; tempMatrix.m11 -= eigenValues[i]; tempMatrix.m22 -= eigenValues[i]; float[] val = new float[1]; val[0] = maxValues[i]; if (!positiveRank(tempMatrix, val, maxRows[i])) { // Rank was 0 (or very close to 0), so just return. // Rescale back to the original size. if (maxMagnitude > 1f) { for (int j = 0; j < 3; j++) { eigenValues[j] *= maxMagnitude; } } eigenVectors[0].set(Vector3f.UNIT_X); eigenVectors[1].set(Vector3f.UNIT_Y); eigenVectors[2].set(Vector3f.UNIT_Z); return; } maxValues[i] = val[0]; } float maxCompare = maxValues[0]; int i = 0; if (maxValues[1] > maxCompare) { maxCompare = maxValues[1]; i = 1; } if (maxValues[2] > maxCompare) { i = 2; } // use the largest row to compute and order the eigen vectors. switch (i) { case 0: maxRows[0].normalizeLocal(); computeVectors(scaledData, maxRows[0], 1, 2, 0); break; case 1: maxRows[1].normalizeLocal(); computeVectors(scaledData, maxRows[1], 2, 0, 1); break; case 2: maxRows[2].normalizeLocal(); computeVectors(scaledData, maxRows[2], 0, 1, 2); break; } // Rescale the values back to the original size. if (maxMagnitude > 1f) { for (i = 0; i < 3; i++) { eigenValues[i] *= maxMagnitude; } } } /** * Scale the matrix so its entries are in [-1,1]. The scaling is applied * only when at least one matrix entry has magnitude larger than 1. * * @return the max magnitude in this matrix */ private float scaleMatrix(Matrix3f mat) { float max = FastMath.abs(mat.m00); float abs = FastMath.abs(mat.m01); if (abs > max) { max = abs; } abs = FastMath.abs(mat.m02); if (abs > max) { max = abs; } abs = FastMath.abs(mat.m11); if (abs > max) { max = abs; } abs = FastMath.abs(mat.m12); if (abs > max) { max = abs; } abs = FastMath.abs(mat.m22); if (abs > max) { max = abs; } if (max > 1f) { float fInvMax = 1f / max; mat.multLocal(fInvMax); } return max; } /** * Compute the eigenvectors of the given Matrix, using the * @param mat * @param vect * @param index1 * @param index2 * @param index3 */ private void computeVectors(Matrix3f mat, Vector3f vect, int index1, int index2, int index3) { Vector3f vectorU = new Vector3f(), vectorV = new Vector3f(); Vector3f.generateComplementBasis(vectorU, vectorV, vect); Vector3f tempVect = mat.mult(vectorU); float p00 = eigenValues[index3] - vectorU.dot(tempVect); float p01 = vectorV.dot(tempVect); float p11 = eigenValues[index3] - vectorV.dot(mat.mult(vectorV)); float invLength; float max = FastMath.abs(p00); int row = 0; float fAbs = FastMath.abs(p01); if (fAbs > max) { max = fAbs; } fAbs = FastMath.abs(p11); if (fAbs > max) { max = fAbs; row = 1; } if (max >= FastMath.ZERO_TOLERANCE) { if (row == 0) { invLength = FastMath.invSqrt(p00 * p00 + p01 * p01); p00 *= invLength; p01 *= invLength; vectorU.mult(p01, eigenVectors[index3]) .addLocal(vectorV.mult(p00)); } else { invLength = FastMath.invSqrt(p11 * p11 + p01 * p01); p11 *= invLength; p01 *= invLength; vectorU.mult(p11, eigenVectors[index3]) .addLocal(vectorV.mult(p01)); } } else { if (row == 0) { eigenVectors[index3] = vectorV; } else { eigenVectors[index3] = vectorU; } } Vector3f vectorS = vect.cross(eigenVectors[index3]); mat.mult(vect, tempVect); p00 = eigenValues[index1] - vect.dot(tempVect); p01 = vectorS.dot(tempVect); p11 = eigenValues[index1] - vectorS.dot(mat.mult(vectorS)); max = FastMath.abs(p00); row = 0; fAbs = FastMath.abs(p01); if (fAbs > max) { max = fAbs; } fAbs = FastMath.abs(p11); if (fAbs > max) { max = fAbs; row = 1; } if (max >= FastMath.ZERO_TOLERANCE) { if (row == 0) { invLength = FastMath.invSqrt(p00 * p00 + p01 * p01); p00 *= invLength; p01 *= invLength; eigenVectors[index1] = vect.mult(p01).add(vectorS.mult(p00)); } else { invLength = FastMath.invSqrt(p11 * p11 + p01 * p01); p11 *= invLength; p01 *= invLength; eigenVectors[index1] = vect.mult(p11).add(vectorS.mult(p01)); } } else { if (row == 0) { eigenVectors[index1].set(vectorS); } else { eigenVectors[index1].set(vect); } } eigenVectors[index3].cross(eigenVectors[index1], eigenVectors[index2]); } /** * Check the rank of the given Matrix to determine if it is positive. While * doing so, store the max magnitude entry in the given float store and the * max row of the matrix in the Vector store. * * @param matrix * the Matrix3f to analyze. * @param maxMagnitudeStore * a float array in which to store (in the 0th position) the max * magnitude entry of the matrix. * @param maxRowStore * a Vector3f to store the values of the row of the matrix * containing the max magnitude entry. * @return true if the given matrix has a non 0 rank. */ private boolean positiveRank(Matrix3f matrix, float[] maxMagnitudeStore, Vector3f maxRowStore) { // Locate the maximum-magnitude entry of the matrix. maxMagnitudeStore[0] = -1f; int iRow, iCol, iMaxRow = -1; for (iRow = 0; iRow < 3; iRow++) { for (iCol = iRow; iCol < 3; iCol++) { float fAbs = FastMath.abs(matrix.get(iRow, iCol)); if (fAbs > maxMagnitudeStore[0]) { maxMagnitudeStore[0] = fAbs; iMaxRow = iRow; } } } // Return the row containing the maximum, to be used for eigenvector // construction. maxRowStore.set(matrix.getRow(iMaxRow)); return maxMagnitudeStore[0] >= FastMath.ZERO_TOLERANCE; } /** * Generate the base eigen values of the given matrix using double precision * math. * * @param mat * the Matrix3f to analyze. * @param rootsStore * a double array to store the results in. Must be at least * length 3. */ private void computeRoots(Matrix3f mat, double[] rootsStore) { // Convert the unique matrix entries to double precision. double a = mat.m00, b = mat.m01, c = mat.m02, d = mat.m11, e = mat.m12, f = mat.m22; // The characteristic equation is x^3 - c2*x^2 + c1*x - c0 = 0. The // eigenvalues are the roots to this equation, all guaranteed to be // real-valued, because the matrix is symmetric. double char0 = a * d * f + 2.0 * b * c * e - a * e * e - d * c * c - f * b * b; double char1 = a * d - b * b + a * f - c * c + d * f - e * e; double char2 = a + d + f; // Construct the parameters used in classifying the roots of the // equation and in solving the equation for the roots in closed form. double char2Div3 = char2 * ONE_THIRD_DOUBLE; double abcDiv3 = (char1 - char2 * char2Div3) * ONE_THIRD_DOUBLE; if (abcDiv3 > 0.0) { abcDiv3 = 0.0; } double mbDiv2 = 0.5 * (char0 + char2Div3 * (2.0 * char2Div3 * char2Div3 - char1)); double q = mbDiv2 * mbDiv2 + abcDiv3 * abcDiv3 * abcDiv3; if (q > 0.0) { q = 0.0; } // Compute the eigenvalues by solving for the roots of the polynomial. double magnitude = Math.sqrt(-abcDiv3); double angle = Math.atan2(Math.sqrt(-q), mbDiv2) * ONE_THIRD_DOUBLE; double cos = Math.cos(angle); double sin = Math.sin(angle); double root0 = char2Div3 + 2.0 * magnitude * cos; double root1 = char2Div3 - magnitude * (cos + ROOT_THREE_DOUBLE * sin); double root2 = char2Div3 - magnitude * (cos - ROOT_THREE_DOUBLE * sin); // Sort in increasing order. if (root1 >= root0) { rootsStore[0] = root0; rootsStore[1] = root1; } else { rootsStore[0] = root1; rootsStore[1] = root0; } if (root2 >= rootsStore[1]) { rootsStore[2] = root2; } else { rootsStore[2] = rootsStore[1]; if (root2 >= rootsStore[0]) { rootsStore[1] = root2; } else { rootsStore[1] = rootsStore[0]; rootsStore[0] = root2; } } } public static void main(String[] args) { Matrix3f mat = new Matrix3f(2, 1, 1, 1, 2, 1, 1, 1, 2); Eigen3f eigenSystem = new Eigen3f(mat); logger.info("eigenvalues = "); for (int i = 0; i < 3; i++) logger.log(Level.FINE, "{0} ", eigenSystem.getEigenValue(i)); logger.info("eigenvectors = "); for (int i = 0; i < 3; i++) { Vector3f vector = eigenSystem.getEigenVector(i); logger.info(vector.toString()); mat.setColumn(i, vector); } logger.info(mat.toString()); // eigenvalues = // 1.000000 1.000000 4.000000 // eigenvectors = // 0.411953 0.704955 0.577350 // 0.404533 -0.709239 0.577350 // -0.816485 0.004284 0.577350 } public float getEigenValue(int i) { return eigenValues[i]; } public Vector3f getEigenVector(int i) { return eigenVectors[i]; } public float[] getEigenValues() { return eigenValues; } public Vector3f[] getEigenVectors() { return eigenVectors; } }