// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
//      Magicbane Emulator Project © 2013 - 2022
//                www.magicbane.com

package engine.math;

import org.pmw.tinylog.Logger;

import java.nio.FloatBuffer;


/**
 * <code>Matrix3f</code> defines a 3x3 matrix. Matrix data is maintained
 * internally and is accessible via the get and set methods. Convenience methods
 * are used for matrix operations as well as generating a matrix from a given
 * set of values.
 */
public class Matrix3f {
    public float m00, m01, m02;
    public float m10, m11, m12;
    public float m20, m21, m22;

    /**
     * Constructor instantiates a new <code>Matrix3f</code> object. The initial
     * values for the matrix is that of the identity matrix.
     */
    public Matrix3f() {
        loadIdentity();
    }

    /**
     * constructs a matrix with the given values.
     *
     * @param m00 0x0 in the matrix.
     * @param m01 0x1 in the matrix.
     * @param m02 0x2 in the matrix.
     * @param m10 1x0 in the matrix.
     * @param m11 1x1 in the matrix.
     * @param m12 1x2 in the matrix.
     * @param m20 2x0 in the matrix.
     * @param m21 2x1 in the matrix.
     * @param m22 2x2 in the matrix.
     */
    public Matrix3f(float m00, float m01, float m02, float m10, float m11,
                    float m12, float m20, float m21, float m22) {

        this.m00 = m00;
        this.m01 = m01;
        this.m02 = m02;
        this.m10 = m10;
        this.m11 = m11;
        this.m12 = m12;
        this.m20 = m20;
        this.m21 = m21;
        this.m22 = m22;
    }

    /**
     * Copy constructor that creates a new <code>Matrix3f</code> object that is
     * the same as the provided matrix.
     *
     * @param mat the matrix to copy.
     */
    public Matrix3f(Matrix3f mat) {
        copy(mat);
    }

    static boolean equalIdentity(Matrix3f mat) {
        if (Math.abs(mat.m00 - 1) > 1e-4)
            return false;
        if (Math.abs(mat.m11 - 1) > 1e-4)
            return false;
        if (Math.abs(mat.m22 - 1) > 1e-4)
            return false;

        if (Math.abs(mat.m01) > 1e-4)
            return false;
        if (Math.abs(mat.m02) > 1e-4)
            return false;

        if (Math.abs(mat.m10) > 1e-4)
            return false;
        if (Math.abs(mat.m12) > 1e-4)
            return false;

        if (Math.abs(mat.m20) > 1e-4)
            return false;
        return !(Math.abs(mat.m21) > 1e-4);
    }

    /**
     * <code>copy</code> transfers the contents of a given matrix to this
     * matrix. If a null matrix is supplied, this matrix is set to the identity
     * matrix.
     *
     * @param matrix the matrix to copy.
     */
    public void copy(Matrix3f matrix) {
        if (null == matrix) {
            loadIdentity();
        } else {
            m00 = matrix.m00;
            m01 = matrix.m01;
            m02 = matrix.m02;
            m10 = matrix.m10;
            m11 = matrix.m11;
            m12 = matrix.m12;
            m20 = matrix.m20;
            m21 = matrix.m21;
            m22 = matrix.m22;
        }
    }

    /**
     * <code>get</code> retrieves a value from the matrix at the given position.
     * If the position is invalid a <code>Exception</code> is thrown.
     *
     * @param i the row index.
     * @param j the column index.
     * @return the value at (i, j).
     * @throws Exception
     */
    public float get(int i, int j) throws Exception {
        switch (i) {
            case 0:
                switch (j) {
                    case 0:
                        return m00;
                    case 1:
                        return m01;
                    case 2:
                        return m02;
                }
            case 1:
                switch (j) {
                    case 0:
                        return m10;
                    case 1:
                        return m11;
                    case 2:
                        return m12;
                }
            case 2:
                switch (j) {
                    case 0:
                        return m20;
                    case 1:
                        return m21;
                    case 2:
                        return m22;
                }
        }
        throw new Exception("Invalid indices into matrix.");
    }

    /**
     * <code>get(float[])</code> returns the matrix in row-major or column-major
     * order.
     *
     * @param data     The array to return the data into. This array can be 9 or 16
     *                 floats in size. Only the upper 3x3 are assigned to in the case
     *                 of a 16 element array.
     * @param rowMajor True for row major storage in the array (translation in
     *                 elements 3, 7, 11 for a 4x4), false for column major
     *                 (translation in elements 12, 13, 14 for a 4x4).
     * @throws Exception
     */
    public void get(float[] data, boolean rowMajor) throws Exception {
        if (data.length == 9) {
            if (rowMajor) {
                data[0] = m00;
                data[1] = m01;
                data[2] = m02;
                data[3] = m10;
                data[4] = m11;
                data[5] = m12;
                data[6] = m20;
                data[7] = m21;
                data[8] = m22;
            } else {
                data[0] = m00;
                data[1] = m10;
                data[2] = m20;
                data[3] = m01;
                data[4] = m11;
                data[5] = m21;
                data[6] = m02;
                data[7] = m12;
                data[8] = m22;
            }
        } else if (data.length == 16) {
            if (rowMajor) {
                data[0] = m00;
                data[1] = m01;
                data[2] = m02;
                data[4] = m10;
                data[5] = m11;
                data[6] = m12;
                data[8] = m20;
                data[9] = m21;
                data[10] = m22;
            } else {
                data[0] = m00;
                data[1] = m10;
                data[2] = m20;
                data[4] = m01;
                data[5] = m11;
                data[6] = m21;
                data[8] = m02;
                data[9] = m12;
                data[10] = m22;
            }
        } else {
            throw new Exception("Array size must be 9 or 16 in Matrix3f.get().");
        }
    }

    /**
     * <code>getColumn</code> returns one of three columns specified by the
     * parameter. This column is returned as a <code>Vector3f</code> object.
     *
     * @param i the column to retrieve. Must be between 0 and 2.
     * @return the column specified by the index.
     * @throws Exception
     */
    public Vector3f getColumn(int i) throws Exception {
        return getColumn(i, null);
    }

    /**
     * <code>getColumn</code> returns one of three columns specified by the
     * parameter. This column is returned as a <code>Vector3f</code> object.
     *
     * @param i     the column to retrieve. Must be between 0 and 2.
     * @param store the vector object to store the result in. if null, a new one
     *              is created.
     * @return the column specified by the index.
     * @throws Exception
     */
    public Vector3f getColumn(int i, Vector3f store) throws Exception {
        if (store == null)
            store = new Vector3f();
        switch (i) {
            case 0:
                store.x = m00;
                store.y = m10;
                store.z = m20;
                break;
            case 1:
                store.x = m01;
                store.y = m11;
                store.z = m21;
                break;
            case 2:
                store.x = m02;
                store.y = m12;
                store.z = m22;
                break;
            default:
                throw new Exception("Invalid column index. " + i);
        }
        return store;
    }

    /**
     * <code>getColumn</code> returns one of three rows as specified by the
     * parameter. This row is returned as a <code>Vector3f</code> object.
     *
     * @param i the row to retrieve. Must be between 0 and 2.
     * @return the row specified by the index.
     * @throws Exception
     */
    public Vector3f getRow(int i) throws Exception {
        return getRow(i, null);
    }

    /**
     * <code>getRow</code> returns one of three rows as specified by the
     * parameter. This row is returned as a <code>Vector3f</code> object.
     *
     * @param i     the row to retrieve. Must be between 0 and 2.
     * @param store the vector object to store the result in. if null, a new one
     *              is created.
     * @return the row specified by the index.
     * @throws Exception
     */
    public Vector3f getRow(int i, Vector3f store) throws Exception {
        if (store == null)
            store = new Vector3f();
        switch (i) {
            case 0:
                store.x = m00;
                store.y = m01;
                store.z = m02;
                break;
            case 1:
                store.x = m10;
                store.y = m11;
                store.z = m12;
                break;
            case 2:
                store.x = m20;
                store.y = m21;
                store.z = m22;
                break;
            default:
                throw new Exception("Invalid row index. " + i);
        }
        return store;
    }

    /**
     * <code>fillFloatBuffer</code> fills a FloatBuffer object with the matrix
     * data.
     *
     * @param fb the buffer to fill, starting at current position. Must have
     *           room for 9 more floats.
     * @return matrix data as a FloatBuffer. (position is advanced by 9 and any
     * limit set is not changed).
     */
    public FloatBuffer fillFloatBuffer(FloatBuffer fb) {
        fb.put(m00).put(m01).put(m02);
        fb.put(m10).put(m11).put(m12);
        fb.put(m20).put(m21).put(m22);
        return fb;
    }

    /**
     * <code>setColumn</code> sets a particular column of this matrix to that
     * represented by the provided vector.
     *
     * @param i      the column to set.
     * @param column the data to set.
     * @throws Exception
     */
    public void setColumn(int i, Vector3f column) throws Exception {

        if (column == null) {
            return;
        }
        switch (i) {
            case 0:
                m00 = column.x;
                m10 = column.y;
                m20 = column.z;
                break;
            case 1:
                m01 = column.x;
                m11 = column.y;
                m21 = column.z;
                break;
            case 2:
                m02 = column.x;
                m12 = column.y;
                m22 = column.z;
                break;
            default:
                throw new Exception("Invalid column index. " + i);
        }
    }

    /**
     * <code>setRow</code> sets a particular row of this matrix to that
     * represented by the provided vector.
     *
     * @param i   the row to set.
     * @param row the data to set.
     * @throws Exception
     */
    public void setRow(int i, Vector3f row) throws Exception {

        if (row == null) {
            return;
        }
        switch (i) {
            case 0:
                m00 = row.x;
                m01 = row.y;
                m02 = row.z;
                break;
            case 1:
                m10 = row.x;
                m11 = row.y;
                m12 = row.z;
                break;
            case 2:
                m20 = row.x;
                m21 = row.y;
                m22 = row.z;
                break;
            default:
                throw new Exception("Invalid row index. " + i);
        }
    }

    /**
     * <code>set</code> places a given value into the matrix at the given
     * position. If the position is invalid a <code>Exception</code> is thrown.
     *
     * @param i     the row index.
     * @param j     the column index.
     * @param value the value for (i, j).
     * @throws Exception
     */
    public void set(int i, int j, float value) throws Exception {
        switch (i) {
            case 0:
                switch (j) {
                    case 0:
                        m00 = value;
                        return;
                    case 1:
                        m01 = value;
                        return;
                    case 2:
                        m02 = value;
                        return;
                }
            case 1:
                switch (j) {
                    case 0:
                        m10 = value;
                        return;
                    case 1:
                        m11 = value;
                        return;
                    case 2:
                        m12 = value;
                        return;
                }
            case 2:
                switch (j) {
                    case 0:
                        m20 = value;
                        return;
                    case 1:
                        m21 = value;
                        return;
                    case 2:
                        m22 = value;
                        return;
                }
        }
        throw new Exception("Invalid indices into matrix.");
    }

    /**
     * <code>set</code> sets the values of the matrix to those supplied by the
     * 3x3 two dimenion array.
     *
     * @param matrix the new values of the matrix.
     * @throws Exception if the array is not of size 9.
     */
    public void set(float[][] matrix) throws Exception {
        if (matrix.length != 3 || matrix[0].length != 3) {
            throw new Exception("Array must be of size 9.");
        }

        m00 = matrix[0][0];
        m01 = matrix[0][1];
        m02 = matrix[0][2];
        m10 = matrix[1][0];
        m11 = matrix[1][1];
        m12 = matrix[1][2];
        m20 = matrix[2][0];
        m21 = matrix[2][1];
        m22 = matrix[2][2];
    }

    /**
     * Recreate Matrix using the provided axis.
     *
     * @param uAxis Vector3f
     * @param vAxis Vector3f
     * @param wAxis Vector3f
     */
    public void fromAxes(Vector3f uAxis, Vector3f vAxis, Vector3f wAxis) {
        m00 = uAxis.x;
        m10 = uAxis.y;
        m20 = uAxis.z;

        m01 = vAxis.x;
        m11 = vAxis.y;
        m21 = vAxis.z;

        m02 = wAxis.x;
        m12 = wAxis.y;
        m22 = wAxis.z;
    }

    /**
     * <code>set</code> sets the values of this matrix from an array of values
     * assuming that the data is rowMajor order;
     *
     * @param matrix the matrix to set the value to.
     * @throws Exception
     */
    public void set(float[] matrix) throws Exception {
        set(matrix, true);
    }

    /**
     * <code>set</code> sets the values of this matrix from an array of values;
     *
     * @param matrix   the matrix to set the value to.
     * @param rowMajor whether the incoming data is in row or column major order.
     */
    public void set(float[] matrix, boolean rowMajor) throws Exception {
        if (matrix.length != 9)
            throw new Exception("Array must be of size 9.");

        if (rowMajor) {
            m00 = matrix[0];
            m01 = matrix[1];
            m02 = matrix[2];
            m10 = matrix[3];
            m11 = matrix[4];
            m12 = matrix[5];
            m20 = matrix[6];
            m21 = matrix[7];
            m22 = matrix[8];
        } else {
            m00 = matrix[0];
            m01 = matrix[3];
            m02 = matrix[6];
            m10 = matrix[1];
            m11 = matrix[4];
            m12 = matrix[7];
            m20 = matrix[2];
            m21 = matrix[5];
            m22 = matrix[8];
        }
    }

    /**
     * <code>set</code> defines the values of the matrix based on a supplied
     * <code>Quaternion</code>. It should be noted that all previous values will
     * be overridden.
     *
     * @param quaternion the quaternion to create a rotational matrix from.
     */
    public void set(Quaternion quaternion) {
        quaternion.toRotationMatrix(this);
    }

    /**
     * <code>loadIdentity</code> sets this matrix to the identity matrix. Where
     * all values are zero except those along the diagonal which are one.
     */
    public void loadIdentity() {
        m01 = m02 = m10 = m12 = m20 = m21 = 0;
        m00 = m11 = m22 = 1;
    }

    /**
     * @return true if this matrix is identity
     */
    public boolean isIdentity() {
        return (m00 == 1 && m01 == 0 && m02 == 0)
                && (m10 == 0 && m11 == 1 && m12 == 0)
                && (m20 == 0 && m21 == 0 && m22 == 1);
    }

    /**
     * <code>fromAngleAxis</code> sets this matrix4f to the values specified by
     * an angle and an axis of rotation. This method creates an object, so use
     * fromAngleNormalAxis if your axis is already normalized.
     *
     * @param angle the angle to rotate (in radians).
     * @param axis  the axis of rotation.
     */
    public void fromAngleAxis(float angle, Vector3f axis) {
        Vector3f normAxis = axis.normalize();
        fromAngleNormalAxis(angle, normAxis);
    }

    /**
     * <code>fromAngleNormalAxis</code> sets this matrix4f to the values
     * specified by an angle and a normalized axis of rotation.
     *
     * @param angle the angle to rotate (in radians).
     * @param axis  the axis of rotation (already normalized).
     */
    public void fromAngleNormalAxis(float angle, Vector3f axis) {
        float fCos = FastMath.cos(angle);
        float fSin = FastMath.sin(angle);
        float fOneMinusCos = ((float) 1.0) - fCos;
        float fX2 = axis.x * axis.x;
        float fY2 = axis.y * axis.y;
        float fZ2 = axis.z * axis.z;
        float fXYM = axis.x * axis.y * fOneMinusCos;
        float fXZM = axis.x * axis.z * fOneMinusCos;
        float fYZM = axis.y * axis.z * fOneMinusCos;
        float fXSin = axis.x * fSin;
        float fYSin = axis.y * fSin;
        float fZSin = axis.z * fSin;

        m00 = fX2 * fOneMinusCos + fCos;
        m01 = fXYM - fZSin;
        m02 = fXZM + fYSin;
        m10 = fXYM + fZSin;
        m11 = fY2 * fOneMinusCos + fCos;
        m12 = fYZM - fXSin;
        m20 = fXZM - fYSin;
        m21 = fYZM + fXSin;
        m22 = fZ2 * fOneMinusCos + fCos;
    }

    /**
     * <code>mult</code> multiplies this matrix by a given matrix. The result
     * matrix is returned as a new object. If the given matrix is null, a null
     * matrix is returned.
     *
     * @param mat the matrix to multiply this matrix by.
     * @return the result matrix.
     */
    public Matrix3f mult(Matrix3f mat) {
        return mult(mat, null);
    }

    /**
     * <code>mult</code> multiplies this matrix by a given matrix. The result
     * matrix is returned as a new object.
     *
     * @param mat     the matrix to multiply this matrix by.
     * @param product the matrix to store the result in. if null, a new matrix3f is
     *                created. It is safe for mat and product to be the same object.
     * @return a matrix3f object containing the result of this operation
     */
    public Matrix3f mult(Matrix3f mat, Matrix3f product) {

        float temp00, temp01, temp02;
        float temp10, temp11, temp12;
        float temp20, temp21, temp22;

        if (product == null)
            product = new Matrix3f();
        temp00 = m00 * mat.m00 + m01 * mat.m10 + m02 * mat.m20;
        temp01 = m00 * mat.m01 + m01 * mat.m11 + m02 * mat.m21;
        temp02 = m00 * mat.m02 + m01 * mat.m12 + m02 * mat.m22;
        temp10 = m10 * mat.m00 + m11 * mat.m10 + m12 * mat.m20;
        temp11 = m10 * mat.m01 + m11 * mat.m11 + m12 * mat.m21;
        temp12 = m10 * mat.m02 + m11 * mat.m12 + m12 * mat.m22;
        temp20 = m20 * mat.m00 + m21 * mat.m10 + m22 * mat.m20;
        temp21 = m20 * mat.m01 + m21 * mat.m11 + m22 * mat.m21;
        temp22 = m20 * mat.m02 + m21 * mat.m12 + m22 * mat.m22;

        product.m00 = temp00;
        product.m01 = temp01;
        product.m02 = temp02;
        product.m10 = temp10;
        product.m11 = temp11;
        product.m12 = temp12;
        product.m20 = temp20;
        product.m21 = temp21;
        product.m22 = temp22;

        return product;
    }

    /**
     * <code>mult</code> multiplies this matrix by a given <code>Vector3f</code>
     * object. The result vector is returned. If the given vector is null, null
     * will be returned.
     *
     * @param vec the vector to multiply this matrix by.
     * @return the result vector.
     */
    public Vector3f mult(Vector3f vec) {
        return mult(vec, null);
    }

    /**
     * Multiplies this 3x3 matrix by the 1x3 Vector vec and stores the result in
     * product.
     *
     * @param vec     The Vector3f to multiply.
     * @param product The Vector3f to store the result, it is safe for this to be
     *                the same as vec.
     * @return The given product vector.
     */
    public Vector3f mult(Vector3f vec, Vector3f product) {

        if (null == product) {
            product = new Vector3f();
        }

        float x = vec.x;
        float y = vec.y;
        float z = vec.z;

        product.x = m00 * x + m01 * y + m02 * z;
        product.y = m10 * x + m11 * y + m12 * z;
        product.z = m20 * x + m21 * y + m22 * z;
        return product;
    }

    /**
     * <code>multLocal</code> multiplies this matrix internally by a given float
     * scale factor.
     *
     * @param scale the value to scale by.
     * @return this Matrix3f
     */
    public Matrix3f multLocal(float scale) {
        m00 *= scale;
        m01 *= scale;
        m02 *= scale;
        m10 *= scale;
        m11 *= scale;
        m12 *= scale;
        m20 *= scale;
        m21 *= scale;
        m22 *= scale;
        return this;
    }

    /**
     * <code>multLocal</code> multiplies this matrix by a given
     * <code>Vector3f</code> object. The result vector is stored inside the
     * passed vector, then returned . If the given vector is null, null will be
     * returned.
     *
     * @param vec the vector to multiply this matrix by.
     * @return The passed vector after multiplication
     */
    public Vector3f multLocal(Vector3f vec) {
        if (vec == null)
            return null;
        float x = vec.x;
        float y = vec.y;
        vec.x = m00 * x + m01 * y + m02 * vec.z;
        vec.y = m10 * x + m11 * y + m12 * vec.z;
        vec.z = m20 * x + m21 * y + m22 * vec.z;
        return vec;
    }

    /**
     * <code>mult</code> multiplies this matrix by a given matrix. The result
     * matrix is saved in the current matrix. If the given matrix is null,
     * nothing happens. The current matrix is returned. This is equivalent to
     * this*=mat
     *
     * @param mat the matrix to multiply this matrix by.
     * @return This matrix, after the multiplication
     */
    public Matrix3f multLocal(Matrix3f mat) {

        return mult(mat, this);
    }

    /**
     * Transposes this matrix in place. Returns this matrix for chaining
     *
     * @return This matrix after transpose
     * @throws Exception
     */
    public Matrix3f transposeLocal() throws Exception {
        float[] tmp = new float[9];
        get(tmp, false);
        set(tmp, true);
        return this;
    }

    /**
     * Inverts this matrix as a new Matrix3f.
     *
     * @return The new inverse matrix
     */
    public Matrix3f invert() {
        return invert(null);
    }

    /**
     * Inverts this matrix and stores it in the given store.
     *
     * @return The store
     */
    public Matrix3f invert(Matrix3f store) {
        if (store == null)
            store = new Matrix3f();

        float det = determinant();
        if (FastMath.abs(det) <= 0)
            return store.zero();

        store.m00 = m11 * m22 - m12 * m21;
        store.m01 = m02 * m21 - m01 * m22;
        store.m02 = m01 * m12 - m02 * m11;
        store.m10 = m12 * m20 - m10 * m22;
        store.m11 = m00 * m22 - m02 * m20;
        store.m12 = m02 * m10 - m00 * m12;
        store.m20 = m10 * m21 - m11 * m20;
        store.m21 = m01 * m20 - m00 * m21;
        store.m22 = m00 * m11 - m01 * m10;

        store.multLocal(1f / det);
        return store;
    }

    /**
     * Inverts this matrix locally.
     *
     * @return this
     */
    public Matrix3f invertLocal() {
        float det = determinant();
        if (FastMath.abs(det) <= FastMath.FLT_EPSILON)
            return zero();

        float f00 = m11 * m22 - m12 * m21;
        float f01 = m02 * m21 - m01 * m22;
        float f02 = m01 * m12 - m02 * m11;
        float f10 = m12 * m20 - m10 * m22;
        float f11 = m00 * m22 - m02 * m20;
        float f12 = m02 * m10 - m00 * m12;
        float f20 = m10 * m21 - m11 * m20;
        float f21 = m01 * m20 - m00 * m21;
        float f22 = m00 * m11 - m01 * m10;

        m00 = f00;
        m01 = f01;
        m02 = f02;
        m10 = f10;
        m11 = f11;
        m12 = f12;
        m20 = f20;
        m21 = f21;
        m22 = f22;

        multLocal(1f / det);
        return this;
    }

    /**
     * Returns a new matrix representing the adjoint of this matrix.
     *
     * @return The adjoint matrix
     */
    public Matrix3f adjoint() {
        return adjoint(null);
    }

    /**
     * Places the adjoint of this matrix in store (creates store if null.)
     *
     * @param store The matrix to store the result in. If null, a new matrix is
     *              created.
     * @return store
     */
    public Matrix3f adjoint(Matrix3f store) {
        if (store == null)
            store = new Matrix3f();

        store.m00 = m11 * m22 - m12 * m21;
        store.m01 = m02 * m21 - m01 * m22;
        store.m02 = m01 * m12 - m02 * m11;
        store.m10 = m12 * m20 - m10 * m22;
        store.m11 = m00 * m22 - m02 * m20;
        store.m12 = m02 * m10 - m00 * m12;
        store.m20 = m10 * m21 - m11 * m20;
        store.m21 = m01 * m20 - m00 * m21;
        store.m22 = m00 * m11 - m01 * m10;

        return store;
    }

    /**
     * <code>determinant</code> generates the determinate of this matrix.
     *
     * @return the determinate
     */
    public float determinant() {
        float fCo00 = m11 * m22 - m12 * m21;
        float fCo10 = m12 * m20 - m10 * m22;
        float fCo20 = m10 * m21 - m11 * m20;
        return m00 * fCo00 + m01 * fCo10 + m02 * fCo20;
    }

    /**
     * Sets all of the values in this matrix to zero.
     *
     * @return this matrix
     */
    public Matrix3f zero() {
        m00 = m01 = m02 = m10 = m11 = m12 = m20 = m21 = m22 = 0.0f;
        return this;
    }

    /**
     * <code>add</code> adds the values of a parameter matrix to this matrix.
     *
     * @param mat the matrix to add to this.
     */
    public void add(Matrix3f mat) {
        m00 += mat.m00;
        m01 += mat.m01;
        m02 += mat.m02;
        m10 += mat.m10;
        m11 += mat.m11;
        m12 += mat.m12;
        m20 += mat.m20;
        m21 += mat.m21;
        m22 += mat.m22;
    }

    /**
     * <code>transpose</code> <b>locally</b> transposes this Matrix. This is
     * inconsistent with general value vs local semantics, but is preserved for
     * backwards compatibility. Use transposeNew() to transpose to a new object
     * (value).
     *
     * @return this object for chaining.
     * @throws Exception
     */
    public Matrix3f transpose() throws Exception {
        return transposeLocal();
    }

    /**
     * <code>transposeNew</code> returns a transposed version of this matrix.
     *
     * @return The new Matrix3f object.
     */
    public Matrix3f transposeNew() {
        return new Matrix3f(m00, m10, m20, m01, m11, m21, m02, m12, m22);
    }

    /**
     * <code>toString</code> returns the string representation of this object.
     * It is in a format of a 3x3 matrix. For example, an identity matrix would
     * be represented by the following string. com.jme.math.Matrix3f <br>
     * [<br>
     * 1.0 0.0 0.0 <br>
     * 0.0 1.0 0.0 <br>
     * 0.0 0.0 1.0 <br>
     * ]<br>
     *
     * @return the string representation of this object.
     */
    @Override
    public String toString() {
        StringBuffer result = new StringBuffer("com.jme.math.Matrix3f\n[\n");
        result.append(' ');
        result.append(m00);
        result.append("  ");
        result.append(m01);
        result.append("  ");
        result.append(m02);
        result.append(" \n");
        result.append(' ');
        result.append(m10);
        result.append("  ");
        result.append(m11);
        result.append("  ");
        result.append(m12);
        result.append(" \n");
        result.append(' ');
        result.append(m20);
        result.append("  ");
        result.append(m21);
        result.append("  ");
        result.append(m22);
        result.append(" \n]");
        return result.toString();
    }

    /**
     * <code>hashCode</code> returns the hash code value as an integer and is
     * supported for the benefit of hashing based collection classes such as
     * Hashtable, HashMap, HashSet etc.
     *
     * @return the hashcode for this instance of Matrix4f.
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        int hash = 37;
        hash = 37 * hash + Float.floatToIntBits(m00);
        hash = 37 * hash + Float.floatToIntBits(m01);
        hash = 37 * hash + Float.floatToIntBits(m02);

        hash = 37 * hash + Float.floatToIntBits(m10);
        hash = 37 * hash + Float.floatToIntBits(m11);
        hash = 37 * hash + Float.floatToIntBits(m12);

        hash = 37 * hash + Float.floatToIntBits(m20);
        hash = 37 * hash + Float.floatToIntBits(m21);
        hash = 37 * hash + Float.floatToIntBits(m22);

        return hash;
    }

    /**
     * are these two matrices the same? they are is they both have the same mXX
     * values.
     *
     * @param o the object to compare for equality
     * @return true if they are equal
     */
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Matrix3f) || o == null) {
            return false;
        }

        if (this == o) {
            return true;
        }

        Matrix3f comp = (Matrix3f) o;
        if (Float.compare(m00, comp.m00) != 0)
            return false;
        if (Float.compare(m01, comp.m01) != 0)
            return false;
        if (Float.compare(m02, comp.m02) != 0)
            return false;

        if (Float.compare(m10, comp.m10) != 0)
            return false;
        if (Float.compare(m11, comp.m11) != 0)
            return false;
        if (Float.compare(m12, comp.m12) != 0)
            return false;

        if (Float.compare(m20, comp.m20) != 0)
            return false;
        if (Float.compare(m21, comp.m21) != 0)
            return false;
        return Float.compare(m22, comp.m22) == 0;
    }

    /**
     * A function for creating a rotation matrix that rotates a vector called
     * "start" into another vector called "end".
     *
     * @param start normalized non-zero starting vector
     * @param end   normalized non-zero ending vector
     * @throws Exception
     * @see "Tomas Miller, John Hughes \"Efficiently Building a Matrix to Rotate
     * \ One Vector to Another\" Journal of Graphics Tools, 4(4):1-4, 1999"
     */
    public void fromStartEndVectors(Vector3f start, Vector3f end)
            throws Exception {
        Vector3f v = new Vector3f();
        float e, h, f;

        start.cross(end, v);
        e = start.dot(end);
        f = (e < 0) ? -e : e;

        // if "from" and "to" vectors are nearly parallel
        if (f > 1.0f - FastMath.ZERO_TOLERANCE) {
            Vector3f u = new Vector3f();
            Vector3f x = new Vector3f();
            float c1, c2, c3; /* coefficients for later use */
            int i, j;

            x.x = (start.x > 0.0) ? start.x : -start.x;
            x.y = (start.y > 0.0) ? start.y : -start.y;
            x.z = (start.z > 0.0) ? start.z : -start.z;

            if (x.x < x.y) {
                if (x.x < x.z) {
                    x.x = 1.0f;
                    x.y = x.z = 0.0f;
                } else {
                    x.z = 1.0f;
                    x.x = x.y = 0.0f;
                }
            } else {
                if (x.y < x.z) {
                    x.y = 1.0f;
                    x.x = x.z = 0.0f;
                } else {
                    x.z = 1.0f;
                    x.x = x.y = 0.0f;
                }
            }

            u.x = x.x - start.x;
            u.y = x.y - start.y;
            u.z = x.z - start.z;
            v.x = x.x - end.x;
            v.y = x.y - end.y;
            v.z = x.z - end.z;

            c1 = 2.0f / u.dot(u);
            c2 = 2.0f / v.dot(v);
            c3 = c1 * c2 * u.dot(v);

            for (i = 0; i < 3; i++) {
                for (j = 0; j < 3; j++) {
                    float val = -c1 * u.get(i) * u.get(j) - c2 * v.get(i)
                            * v.get(j) + c3 * v.get(i) * u.get(j);
                    set(i, j, val);
                }
                float val = get(i, i);
                set(i, i, val + 1.0f);
            }
        } else {
            // the most common case, unless "start"="end", or "start"=-"end"
            float hvx, hvz, hvxy, hvxz, hvyz;
            h = 1.0f / (1.0f + e);
            hvx = h * v.x;
            hvz = h * v.z;
            hvxy = hvx * v.y;
            hvxz = hvx * v.z;
            hvyz = hvz * v.y;
            set(0, 0, e + hvx * v.x);
            set(0, 1, hvxy - v.z);
            set(0, 2, hvxz + v.y);

            set(1, 0, hvxy + v.z);
            set(1, 1, e + h * v.y * v.y);
            set(1, 2, hvyz - v.x);

            set(2, 0, hvxz - v.y);
            set(2, 1, hvyz + v.x);
            set(2, 2, e + hvz * v.z);
        }
    }

    /**
     * <code>scale</code> scales the operation performed by this matrix on a
     * per-component basis.
     *
     * @param scale The scale applied to each of the X, Y and Z output values.
     */
    public void scale(Vector3f scale) {
        m00 *= scale.x;
        m10 *= scale.x;
        m20 *= scale.x;
        m01 *= scale.y;
        m11 *= scale.y;
        m21 *= scale.y;
        m02 *= scale.z;
        m12 *= scale.z;
        m22 *= scale.z;
    }

    @Override
    public Matrix3f clone() {
        try {
            return (Matrix3f) super.clone();
        } catch (CloneNotSupportedException e) {
            Logger.error(e);
            throw new AssertionError(); // can not happen
        }
    }
}