package com.flowpowered.math.matrix;

import java.io.Serializable;

import com.flowpowered.math.GenericMath;
import com.flowpowered.math.HashFunctions;
import com.flowpowered.math.TrigMath;
import com.flowpowered.math.imaginary.Complexf;
import com.flowpowered.math.imaginary.Quaternionf;
import com.flowpowered.math.vector.Vector3f;
import com.flowpowered.math.vector.Vector4f;

public class Matrix4f implements Matrixf, Serializable, Cloneable {
    private static final long serialVersionUID = 1;
    public static final Matrix4f ZERO = new Matrix4f(
            0, 0, 0, 0,
            0, 0, 0, 0,
            0, 0, 0, 0,
            0, 0, 0, 0);
    public static final Matrix4f IDENTITY = new Matrix4f();
    private final float m00, m01, m02, m03;
    private final float m10, m11, m12, m13;
    private final float m20, m21, m22, m23;
    private final float m30, m31, m32, m33;
    private transient volatile boolean hashed = false;
    private transient volatile int hashCode = 0;

    public Matrix4f() {
        this(
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1);
    }

    public Matrix4f(Matrix2f m) {
        this(
                m.get(0, 0), m.get(0, 1), 0, 0,
                m.get(1, 0), m.get(1, 1), 0, 0,
                0, 0, 0, 0,
                0, 0, 0, 0);
    }

    public Matrix4f(Matrix3f m) {
        this(
                m.get(0, 0), m.get(0, 1), m.get(0, 2), 0,
                m.get(1, 0), m.get(1, 1), m.get(1, 2), 0,
                m.get(2, 0), m.get(2, 1), m.get(2, 2), 0,
                0, 0, 0, 0);
    }

    public Matrix4f(Matrix4f m) {
        this(
                m.m00, m.m01, m.m02, m.m03,
                m.m10, m.m11, m.m12, m.m13,
                m.m20, m.m21, m.m22, m.m23,
                m.m30, m.m31, m.m32, m.m33);
    }

    public Matrix4f(MatrixNf m) {
        m00 = m.get(0, 0);
        m01 = m.get(0, 1);
        m10 = m.get(1, 0);
        m11 = m.get(1, 1);
        if (m.size() > 2) {
            m02 = m.get(0, 2);
            m12 = m.get(1, 2);
            m20 = m.get(2, 0);
            m21 = m.get(2, 1);
            m22 = m.get(2, 2);
            if (m.size() > 3) {
                m03 = m.get(0, 3);
                m13 = m.get(1, 3);
                m23 = m.get(2, 3);
                m30 = m.get(3, 0);
                m31 = m.get(3, 1);
                m32 = m.get(3, 2);
                m33 = m.get(3, 3);
            } else {
                m03 = 0;
                m13 = 0;
                m23 = 0;
                m30 = 0;
                m31 = 0;
                m32 = 0;
                m33 = 0;
            }
        } else {
            m02 = 0;
            m12 = 0;
            m20 = 0;
            m21 = 0;
            m22 = 0;
            m03 = 0;
            m13 = 0;
            m23 = 0;
            m30 = 0;
            m31 = 0;
            m32 = 0;
            m33 = 0;
        }
    }

    public Matrix4f(
            double m00, double m01, double m02, double m03,
            double m10, double m11, double m12, double m13,
            double m20, double m21, double m22, double m23,
            double m30, double m31, double m32, double m33) {
        this(
                (float) m00, (float) m01, (float) m02, (float) m03,
                (float) m10, (float) m11, (float) m12, (float) m13,
                (float) m20, (float) m21, (float) m22, (float) m23,
                (float) m30, (float) m31, (float) m32, (float) m33);
    }

    public Matrix4f(
            float m00, float m01, float m02, float m03,
            float m10, float m11, float m12, float m13,
            float m20, float m21, float m22, float m23,
            float m30, float m31, float m32, float m33) {
        this.m00 = m00;
        this.m01 = m01;
        this.m02 = m02;
        this.m03 = m03;
        this.m10 = m10;
        this.m11 = m11;
        this.m12 = m12;
        this.m13 = m13;
        this.m20 = m20;
        this.m21 = m21;
        this.m22 = m22;
        this.m23 = m23;
        this.m30 = m30;
        this.m31 = m31;
        this.m32 = m32;
        this.m33 = m33;
    }

    @Override
    public float get(int row, int col) {
        switch (row) {
        case 0:
            switch (col) {
            case 0:
                return m00;
            case 1:
                return m01;
            case 2:
                return m02;
            case 3:
                return m03;
            }
        case 1:
            switch (col) {
            case 0:
                return m10;
            case 1:
                return m11;
            case 2:
                return m12;
            case 3:
                return m13;
            }
        case 2:
            switch (col) {
            case 0:
                return m20;
            case 1:
                return m21;
            case 2:
                return m22;
            case 3:
                return m23;
            }
        case 3:
            switch (col) {
            case 0:
                return m30;
            case 1:
                return m31;
            case 2:
                return m32;
            case 3:
                return m33;
            }
        }
        throw new IllegalArgumentException(
                (row < 0 || row > 2 ? "row must be greater than zero and smaller than 3. " : "") +
                (col < 0 || col > 2 ? "col must be greater than zero and smaller than 3." : ""));
    }

    @Override
    public Vector4f getRow(int row) {
        return new Vector4f(get(row, 0), get(row, 1), get(row, 2), get(row, 3));
    }

    @Override
    public Vector4f getColumn(int col) {
        return new Vector4f(get(0, col), get(1, col), get(2, col), get(3, col));
    }

    public Matrix4f add(Matrix4f m) {
        return new Matrix4f(
                m00 + m.m00, m01 + m.m01, m02 + m.m02, m03 + m.m03,
                m10 + m.m10, m11 + m.m11, m12 + m.m12, m13 + m.m13,
                m20 + m.m20, m21 + m.m21, m22 + m.m22, m23 + m.m23,
                m30 + m.m30, m31 + m.m31, m32 + m.m32, m33 + m.m33);
    }

    public Matrix4f sub(Matrix4f m) {
        return new Matrix4f(
                m00 - m.m00, m01 - m.m01, m02 - m.m02, m03 - m.m03,
                m10 - m.m10, m11 - m.m11, m12 - m.m12, m13 - m.m13,
                m20 - m.m20, m21 - m.m21, m22 - m.m22, m23 - m.m23,
                m30 - m.m30, m31 - m.m31, m32 - m.m32, m33 - m.m33);
    }

    public Matrix4f mul(double a) {
        return mul((float) a);
    }

    @Override
    public Matrix4f mul(float a) {
        return new Matrix4f(
                m00 * a, m01 * a, m02 * a, m03 * a,
                m10 * a, m11 * a, m12 * a, m13 * a,
                m20 * a, m21 * a, m22 * a, m23 * a,
                m30 * a, m31 * a, m32 * a, m33 * a);
    }

    public Matrix4f mul(Matrix4f m) {
        return new Matrix4f(
                m00 * m.m00 + m01 * m.m10 + m02 * m.m20 + m03 * m.m30,
                m00 * m.m01 + m01 * m.m11 + m02 * m.m21 + m03 * m.m31,
                m00 * m.m02 + m01 * m.m12 + m02 * m.m22 + m03 * m.m32,
                m00 * m.m03 + m01 * m.m13 + m02 * m.m23 + m03 * m.m33,
                m10 * m.m00 + m11 * m.m10 + m12 * m.m20 + m13 * m.m30,
                m10 * m.m01 + m11 * m.m11 + m12 * m.m21 + m13 * m.m31,
                m10 * m.m02 + m11 * m.m12 + m12 * m.m22 + m13 * m.m32,
                m10 * m.m03 + m11 * m.m13 + m12 * m.m23 + m13 * m.m33,
                m20 * m.m00 + m21 * m.m10 + m22 * m.m20 + m23 * m.m30,
                m20 * m.m01 + m21 * m.m11 + m22 * m.m21 + m23 * m.m31,
                m20 * m.m02 + m21 * m.m12 + m22 * m.m22 + m23 * m.m32,
                m20 * m.m03 + m21 * m.m13 + m22 * m.m23 + m23 * m.m33,
                m30 * m.m00 + m31 * m.m10 + m32 * m.m20 + m33 * m.m30,
                m30 * m.m01 + m31 * m.m11 + m32 * m.m21 + m33 * m.m31,
                m30 * m.m02 + m31 * m.m12 + m32 * m.m22 + m33 * m.m32,
                m30 * m.m03 + m31 * m.m13 + m32 * m.m23 + m33 * m.m33);
    }

    public Matrix4f div(double a) {
        return div((float) a);
    }

    @Override
    public Matrix4f div(float a) {
        return new Matrix4f(
                m00 / a, m01 / a, m02 / a, m03 / a,
                m10 / a, m11 / a, m12 / a, m13 / a,
                m20 / a, m21 / a, m22 / a, m23 / a,
                m30 / a, m31 / a, m32 / a, m33 / a);
    }

    public Matrix4f div(Matrix4f m) {
        return mul(m.invert());
    }

    public Matrix4f pow(double pow) {
        return pow((float) pow);
    }

    @Override
    public Matrix4f pow(float pow) {
        return new Matrix4f(
                Math.pow(m00, pow), Math.pow(m01, pow), Math.pow(m02, pow), Math.pow(m03, pow),
                Math.pow(m10, pow), Math.pow(m11, pow), Math.pow(m12, pow), Math.pow(m13, pow),
                Math.pow(m20, pow), Math.pow(m21, pow), Math.pow(m22, pow), Math.pow(m23, pow),
                Math.pow(m30, pow), Math.pow(m31, pow), Math.pow(m32, pow), Math.pow(m33, pow));
    }

    public Matrix4f translate(Vector3f v) {
        return translate(v.getX(), v.getY(), v.getZ());
    }

    public Matrix4f translate(double x, double y, double z) {
        return translate((float) x, (float) y, (float) z);
    }

    public Matrix4f translate(float x, float y, float z) {
        return createTranslation(x, y, z).mul(this);
    }

    public Matrix4f scale(double scale) {
        return scale((float) scale);
    }

    public Matrix4f scale(float scale) {
        return scale(scale, scale, scale, scale);
    }

    public Matrix4f scale(Vector4f v) {
        return scale(v.getX(), v.getY(), v.getZ(), v.getW());
    }

    public Matrix4f scale(double x, double y, double z, double w) {
        return scale((float) x, (float) y, (float) z, (float) w);
    }

    public Matrix4f scale(float x, float y, float z, float w) {
        return createScaling(x, y, z, w).mul(this);
    }

    public Matrix4f rotate(Complexf rot) {
        return createRotation(rot).mul(this);
    }

    public Matrix4f rotate(Quaternionf rot) {
        return createRotation(rot).mul(this);
    }

    public Vector4f transform(Vector4f v) {
        return transform(v.getX(), v.getY(), v.getZ(), v.getW());
    }

    public Vector4f transform(double x, double y, double z, double w) {
        return transform((float) x, (float) y, (float) z, (float) w);
    }

    public Vector4f transform(float x, float y, float z, float w) {
        return new Vector4f(
                m00 * x + m01 * y + m02 * z + m03 * w,
                m10 * x + m11 * y + m12 * z + m13 * w,
                m20 * x + m21 * y + m22 * z + m23 * w,
                m30 * x + m31 * y + m32 * z + m33 * w);
    }

    @Override
    public Matrix4f floor() {
        return new Matrix4f(
                GenericMath.floor(m00), GenericMath.floor(m01), GenericMath.floor(m02), GenericMath.floor(m03),
                GenericMath.floor(m10), GenericMath.floor(m11), GenericMath.floor(m12), GenericMath.floor(m13),
                GenericMath.floor(m20), GenericMath.floor(m21), GenericMath.floor(m22), GenericMath.floor(m23),
                GenericMath.floor(m30), GenericMath.floor(m31), GenericMath.floor(m32), GenericMath.floor(m33));
    }

    @Override
    public Matrix4f ceil() {
        return new Matrix4f(
                Math.ceil(m00), Math.ceil(m01), Math.ceil(m02), Math.ceil(m03),
                Math.ceil(m10), Math.ceil(m11), Math.ceil(m12), Math.ceil(m13),
                Math.ceil(m20), Math.ceil(m21), Math.ceil(m22), Math.ceil(m23),
                Math.ceil(m30), Math.ceil(m31), Math.ceil(m32), Math.ceil(m33));
    }

    @Override
    public Matrix4f round() {
        return new Matrix4f(
                Math.round(m00), Math.round(m01), Math.round(m02), Math.round(m03),
                Math.round(m10), Math.round(m11), Math.round(m12), Math.round(m13),
                Math.round(m20), Math.round(m21), Math.round(m22), Math.round(m23),
                Math.round(m30), Math.round(m31), Math.round(m32), Math.round(m33));
    }

    @Override
    public Matrix4f abs() {
        return new Matrix4f(
                Math.abs(m00), Math.abs(m01), Math.abs(m02), Math.abs(m03),
                Math.abs(m10), Math.abs(m11), Math.abs(m12), Math.abs(m13),
                Math.abs(m20), Math.abs(m21), Math.abs(m22), Math.abs(m23),
                Math.abs(m30), Math.abs(m31), Math.abs(m32), Math.abs(m33));
    }

    @Override
    public Matrix4f negate() {
        return new Matrix4f(
                -m00, -m01, -m02, -m03,
                -m10, -m11, -m12, -m13,
                -m20, -m21, -m22, -m23,
                -m30, -m31, -m32, -m33);
    }

    @Override
    public Matrix4f transpose() {
        return new Matrix4f(
                m00, m10, m20, m30,
                m01, m11, m21, m31,
                m02, m12, m22, m32,
                m03, m13, m23, m33);
    }

    @Override
    public float trace() {
        return m00 + m11 + m22 + m33;
    }

    @Override
    public float determinant() {
        return m00 * (m11 * m22 * m33 + m21 * m32 * m13 + m31 * m12 * m23 - m31 * m22 * m13 - m11 * m32 * m23 - m21 * m12 * m33)
                - m10 * (m01 * m22 * m33 + m21 * m32 * m03 + m31 * m02 * m23 - m31 * m22 * m03 - m01 * m32 * m23 - m21 * m02 * m33)
                + m20 * (m01 * m12 * m33 + m11 * m32 * m03 + m31 * m02 * m13 - m31 * m12 * m03 - m01 * m32 * m13 - m11 * m02 * m33)
                - m30 * (m01 * m12 * m23 + m11 * m22 * m03 + m21 * m02 * m13 - m21 * m12 * m03 - m01 * m22 * m13 - m11 * m02 * m23);
    }

    @Override
    public Matrix4f invert() {
        final float det = determinant();
        if (Math.abs(det) < GenericMath.FLT_EPSILON) {
            throw new ArithmeticException("Cannot inverse a matrix with a zero determinant");
        }
        return new Matrix4f(
                det3(m11, m21, m31, m12, m22, m32, m13, m23, m33) / det, -det3(m01, m21, m31, m02, m22, m32, m03, m23, m33) / det,
                det3(m01, m11, m31, m02, m12, m32, m03, m13, m33) / det, -det3(m01, m11, m21, m02, m12, m22, m03, m13, m23) / det,
                -det3(m10, m20, m30, m12, m22, m32, m13, m23, m33) / det, det3(m00, m20, m30, m02, m22, m32, m03, m23, m33) / det,
                -det3(m00, m10, m30, m02, m12, m32, m03, m13, m33) / det, det3(m00, m10, m20, m02, m12, m22, m03, m13, m23) / det,
                det3(m10, m20, m30, m11, m21, m31, m13, m23, m33) / det, -det3(m00, m20, m30, m01, m21, m31, m03, m23, m33) / det,
                det3(m00, m10, m30, m01, m11, m31, m03, m13, m33) / det, -det3(m00, m10, m20, m01, m11, m21, m03, m13, m23) / det,
                -det3(m10, m20, m30, m11, m21, m31, m12, m22, m32) / det, det3(m00, m20, m30, m01, m21, m31, m02, m22, m32) / det,
                -det3(m00, m10, m30, m01, m11, m31, m02, m12, m32) / det, det3(m00, m10, m20, m01, m11, m21, m02, m12, m22) / det);
    }

    public Matrix2f toMatrix2() {
        return new Matrix2f(this);
    }

    public Matrix3f toMatrix3() {
        return new Matrix3f(this);
    }

    public MatrixNf toMatrixN() {
        return new MatrixNf(this);
    }

    public float[] toArray() {
        return toArray(false);
    }

    @Override
    public float[] toArray(boolean columnMajor) {
        if (columnMajor) {
            return new float[]{
                    m00, m10, m20, m30,
                    m01, m11, m21, m31,
                    m02, m12, m22, m32,
                    m03, m13, m23, m33
            };
        } else {
            return new float[]{
                    m00, m01, m02, m03,
                    m10, m11, m12, m13,
                    m20, m21, m22, m23,
                    m30, m31, m32, m33
            };
        }
    }

    @Override
    public Matrix4f toFloat() {
        return new Matrix4f(
                m00, m01, m02, m03,
                m10, m11, m12, m13,
                m20, m21, m22, m23,
                m30, m31, m32, m33
        );
    }

    @Override
    public Matrix4d toDouble() {
        return new Matrix4d(
                m00, m01, m02, m03,
                m10, m11, m12, m13,
                m20, m21, m22, m23,
                m30, m31, m32, m33
        );
    }

    @Override
    public String toString() {
        return m00 + " " + m01 + " " + m02 + " " + m03 + "\n"
                + m10 + " " + m11 + " " + m12 + " " + m13 + "\n"
                + m20 + " " + m21 + " " + m22 + " " + m23 + "\n"
                + m30 + " " + m31 + " " + m32 + " " + m33 + "\n";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Matrix4f)) {
            return false;
        }
        final Matrix4f matrix4 = (Matrix4f) o;
        if (Float.compare(matrix4.m00, m00) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m01, m01) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m02, m02) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m03, m03) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m10, m10) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m11, m11) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m12, m12) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m13, m13) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m20, m20) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m21, m21) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m22, m22) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m23, m23) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m30, m30) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m31, m31) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m32, m32) != 0) {
            return false;
        }
        if (Float.compare(matrix4.m33, m33) != 0) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        if (!hashed) {
            int result = (m00 != +0.0f ? HashFunctions.hash(m00) : 0);
            result = 31 * result + (m01 != +0.0f ? HashFunctions.hash(m01) : 0);
            result = 31 * result + (m02 != +0.0f ? HashFunctions.hash(m02) : 0);
            result = 31 * result + (m03 != +0.0f ? HashFunctions.hash(m03) : 0);
            result = 31 * result + (m10 != +0.0f ? HashFunctions.hash(m10) : 0);
            result = 31 * result + (m11 != +0.0f ? HashFunctions.hash(m11) : 0);
            result = 31 * result + (m12 != +0.0f ? HashFunctions.hash(m12) : 0);
            result = 31 * result + (m13 != +0.0f ? HashFunctions.hash(m13) : 0);
            result = 31 * result + (m20 != +0.0f ? HashFunctions.hash(m20) : 0);
            result = 31 * result + (m21 != +0.0f ? HashFunctions.hash(m21) : 0);
            result = 31 * result + (m22 != +0.0f ? HashFunctions.hash(m22) : 0);
            result = 31 * result + (m23 != +0.0f ? HashFunctions.hash(m23) : 0);
            result = 31 * result + (m30 != +0.0f ? HashFunctions.hash(m30) : 0);
            result = 31 * result + (m31 != +0.0f ? HashFunctions.hash(m31) : 0);
            result = 31 * result + (m32 != +0.0f ? HashFunctions.hash(m32) : 0);
            hashCode = 31 * result + (m33 != +0.0f ? HashFunctions.hash(m33) : 0);
            hashed = true;
        }
        return hashCode;
    }

    @Override
    public Matrix4f clone() {
        return new Matrix4f(this);
    }

    public static Matrix4f from(float n) {
         return n == 0 ? ZERO : new Matrix4f(n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n);
    }

    public static Matrix4f from(float m00, float m01, float m02, float m03,
                                float m10, float m11, float m12, float m13,
                                float m20, float m21, float m22, float m23,
                                float m30, float m31, float m32, float m33) {
         return m00 == 0 && m01 == 0 && m02 == 0 && m03 == 0
             && m10 == 0 && m11 == 0 && m12 == 0 && m13 == 0
             && m20 == 0 && m21 == 0 && m22 == 0 && m23 == 0
             && m30 == 0 && m31 == 0 && m32 == 0 && m33 == 0
         ? ZERO : new Matrix4f(m00, m01, m02, m03,
                                 m10, m11, m12, m13,
                                 m20, m21, m22, m23,
                                 m30, m31, m32, m33);
    }

    public static Matrix4f fromDiagonal(float m00, float m11, float m22, float m33) {
         return m00 == 0 && m11 == 0 && m22 == 0 && m33 == 0 ? ZERO : new Matrix4f(m00, 0, 0, 0,
                                                                                0, m11, 0, 0,
                                                                                0, 0, m22, 0,
                                                                                0, 0, 0, m33);
    }

    public static Matrix4f createScaling(double scale) {
        return createScaling((float) scale);
    }

    public static Matrix4f createScaling(float scale) {
        return createScaling(scale, scale, scale, scale);
    }

    public static Matrix4f createScaling(Vector4f v) {
        return createScaling(v.getX(), v.getY(), v.getZ(), v.getW());
    }

    public static Matrix4f createScaling(double x, double y, double z, double w) {
        return createScaling((float) x, (float) y, (float) z, (float) w);
    }

    public static Matrix4f createScaling(float x, float y, float z, float w) {
        return new Matrix4f(
                x, 0, 0, 0,
                0, y, 0, 0,
                0, 0, z, 0,
                0, 0, 0, w);
    }

    public static Matrix4f createTranslation(Vector3f v) {
        return createTranslation(v.getX(), v.getY(), v.getZ());
    }

    public static Matrix4f createTranslation(double x, double y, double z) {
        return createTranslation((float) x, (float) y, (float) z);
    }

    public static Matrix4f createTranslation(float x, float y, float z) {
        return new Matrix4f(
                1, 0, 0, x,
                0, 1, 0, y,
                0, 0, 1, z,
                0, 0, 0, 1);
    }

    public static Matrix4f createRotation(Complexf rot) {
        rot = rot.normalize();
        return new Matrix4f(
                rot.getX(), -rot.getY(), 0, 0,
                rot.getY(), rot.getX(), 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1);
    }

    public static Matrix4f createRotation(Quaternionf rot) {
        rot = rot.normalize();
        return new Matrix4f(
                1 - 2 * rot.getY() * rot.getY() - 2 * rot.getZ() * rot.getZ(),
                2 * rot.getX() * rot.getY() - 2 * rot.getW() * rot.getZ(),
                2 * rot.getX() * rot.getZ() + 2 * rot.getW() * rot.getY(), 0,
                2 * rot.getX() * rot.getY() + 2 * rot.getW() * rot.getZ(),
                1 - 2 * rot.getX() * rot.getX() - 2 * rot.getZ() * rot.getZ(),
                2 * rot.getY() * rot.getZ() - 2 * rot.getW() * rot.getX(), 0,
                2 * rot.getX() * rot.getZ() - 2 * rot.getW() * rot.getY(),
                2 * rot.getY() * rot.getZ() + 2 * rot.getX() * rot.getW(),
                1 - 2 * rot.getX() * rot.getX() - 2 * rot.getY() * rot.getY(), 0,
                0, 0, 0, 1);
    }

    /**
     * Creates a "look at" matrix for the given eye point.
     *
     * @param eye The position of the camera
     * @param at The point that the camera is looking at
     * @param up The "up" vector
     * @return A rotational transform that corresponds to a camera looking at the given point
     */
    public static Matrix4f createLookAt(Vector3f eye, Vector3f at, Vector3f up) {
        final Vector3f f = at.sub(eye).normalize();
        final Vector3f s = f.cross(up).normalize();
        final Vector3f u = s.cross(f);
        final Matrix4f mat = new Matrix4f(
                s.getX(), s.getY(), s.getZ(), 0,
                u.getX(), u.getY(), u.getZ(), 0,
                -f.getX(), -f.getY(), -f.getZ(), 0,
                0, 0, 0, 1);
        return mat.translate(eye.negate());
    }

    /**
     * Creates a perspective projection matrix with the given (x) FOV, aspect, near and far planes
     *
     * @param fov The field of view in the x direction
     * @param aspect The aspect ratio, usually width/height
     * @param near The near plane, cannot be 0
     * @param far the far plane, far cannot equal near
     * @return A perspective projection matrix built from the given values
     */
    public static Matrix4f createPerspective(double fov, double aspect, double near, double far) {
        return createPerspective((float) fov, (float) aspect, (float) near, (float) far);
    }

    /**
     * Creates a perspective projection matrix with the given (x) FOV, aspect, near and far planes
     *
     * @param fov The field of view in the x direction
     * @param aspect The aspect ratio, usually width/height
     * @param near The near plane, cannot be 0
     * @param far the far plane, far cannot equal near
     * @return A perspective projection matrix built from the given values
     */
    public static Matrix4f createPerspective(float fov, float aspect, float near, float far) {
        final float scale = 1 / TrigMath.tan(fov * (float) TrigMath.HALF_DEG_TO_RAD);
        return new Matrix4f(
                scale / aspect, 0, 0, 0,
                0, scale, 0, 0,
                0, 0, (far + near) / (near - far), 2 * far * near / (near - far),
                0, 0, -1, 0);
    }

    /**
     * Creates an orthographic viewing frustum built from the provided values
     *
     * @param right the right most plane of the viewing frustum
     * @param left the left most plane of the viewing frustum
     * @param top the top plane of the viewing frustum
     * @param bottom the bottom plane of the viewing frustum
     * @param near the near plane of the viewing frustum
     * @param far the far plane of the viewing frustum
     * @return A viewing frustum built from the provided values
     */
    public static Matrix4f createOrthographic(double right, double left, double top, double bottom,
            double near, double far) {
        return createOrthographic((float) right, (float) left, (float) top, (float) bottom, (float) near, (float) far);
    }

    /**
     * Creates an orthographic viewing frustum built from the provided values
     *
     * @param right the right most plane of the viewing frustum
     * @param left the left most plane of the viewing frustum
     * @param top the top plane of the viewing frustum
     * @param bottom the bottom plane of the viewing frustum
     * @param near the near plane of the viewing frustum
     * @param far the far plane of the viewing frustum
     * @return A viewing frustum built from the provided values
     */
    public static Matrix4f createOrthographic(float right, float left, float top, float bottom,
            float near, float far) {
        return new Matrix4f(
                2 / (right - left), 0, 0, -(right + left) / (right - left),
                0, 2 / (top - bottom), 0, -(top + bottom) / (top - bottom),
                0, 0, -2 / (far - near), -(far + near) / (far - near),
                0, 0, 0, 1);
    }

    private static float det3(float m00, float m01, float m02,
            float m10, float m11, float m12,
            float m20, float m21, float m22) {
        return m00 * (m11 * m22 - m12 * m21) - m01 * (m10 * m22 - m12 * m20) + m02 * (m10 * m21 - m11 * m20);
    }
}


package com.flowpowered.math.imaginary;

import java.io.Serializable;
import java.lang.Override;

import com.flowpowered.math.GenericMath;
import com.flowpowered.math.HashFunctions;
import com.flowpowered.math.TrigMath;
import com.flowpowered.math.vector.Vector2f;
import com.flowpowered.math.vector.Vector3f;

/**
 * Represent a complex number of the form x + yi. The x and y components are stored as floats. This class is immutable.
 */
public class Complexf implements Imaginaryf, Comparable<Complexf>, Serializable, Cloneable {
    private static final long serialVersionUID = 1;
    /**
     * An immutable identity (0, 0) complex.
     */
    public static final Complexf ZERO = new Complexf(0, 0);
    /**
     * An immutable identity (1, 0) complex.
     */
    public static final Complexf IDENTITY = new Complexf(1, 0);
    private final float x;
    private final float y;
    private transient volatile boolean hashed = false;
    private transient volatile int hashCode = 0;

    /**
     * Constructs a new complex. The components are set to the identity (1, 0).
     */
    public Complexf() {
        this(1, 0);
    }

    /**
     * Constructs a new complex from the double components.
     *
     * @param x The x (real) component
     * @param y The y (imaginary) component
     */
    public Complexf(double x, double y) {
        this((float) x, (float) y);
    }

    /**
     * Constructs a new complex from the float components.
     *
     * @param x The x (real) component
     * @param y The y (imaginary) component
     */
    public Complexf(float x, float y) {
        this.x = x;
        this.y = y;
    }

    /**
     * Copy constructor.
     *
     * @param c The complex to copy
     */
    public Complexf(Complexf c) {
        this.x = c.x;
        this.y = c.y;
    }

    /**
     * Gets the x (real) component of this complex.
     *
     * @return The x (real) component
     */
    public float getX() {
        return x;
    }

    /**
     * Gets the y (imaginary) component of this complex.
     *
     * @return The y (imaginary) component
     */
    public float getY() {
        return y;
    }

    /**
     * Adds another complex to this one.
     *
     * @param c The complex to add
     * @return A new complex, which is the sum of both
     */
    public Complexf add(Complexf c) {
        return add(c.x, c.y);
    }

    /**
     * Adds the double components of another complex to this one.
     *
     * @param x The x (real) component of the complex to add
     * @param y The y (imaginary) component of the complex to add
     * @return A new complex, which is the sum of both
     */
    public Complexf add(double x, double y) {
        return add((float) x, (float) y);
    }

    /**
     * Adds the float components of another complex to this one.
     *
     * @param x The x (real) component of the complex to add
     * @param y The y (imaginary) component of the complex to add
     * @return A new complex, which is the sum of both
     */
    public Complexf add(float x, float y) {
        return new Complexf(this.x + x, this.y + y);
    }

    /**
     * Subtracts another complex from this one.
     *
     * @param c The complex to subtract
     * @return A new complex, which is the difference of both
     */
    public Complexf sub(Complexf c) {
        return sub(c.x, c.y);
    }

    /**
     * Subtracts the double components of another complex from this one.
     *
     * @param x The x (real) component of the complex to subtract
     * @param y The y (imaginary) component of the complex to subtract
     * @return A new complex, which is the difference of both
     */
    public Complexf sub(double x, double y) {
        return sub((float) x, (float) y);
    }

    /**
     * Subtracts the float components of another complex from this one.
     *
     * @param x The x (real) component of the complex to subtract
     * @param y The y (imaginary) component of the complex to subtract
     * @return A new complex, which is the difference of both
     */
    public Complexf sub(float x, float y) {
        return new Complexf(this.x - x, this.y - y);
    }

    /**
     * Multiplies the components of this complex by a double scalar.
     *
     * @param a The multiplication scalar
     * @return A new complex, which has each component multiplied by the scalar
     */
    public Complexf mul(double a) {
        return mul((float) a);
    }

    /**
     * Multiplies the components of this complex by a float scalar.
     *
     * @param a The multiplication scalar
     * @return A new complex, which has each component multiplied by the scalar
     */
    @Override
    public Complexf mul(float a) {
        return new Complexf(x * a, y * a);
    }

    /**
     * Multiplies another complex with this one.
     *
     * @param c The complex to multiply with
     * @return A new complex, which is the product of both
     */
    public Complexf mul(Complexf c) {
        return mul(c.x, c.y);
    }

    /**
     * Multiplies the double components of another complex with this one.
     *
     * @param x The x (real) component of the complex to multiply with
     * @param y The y (imaginary) component of the complex to multiply with
     * @return A new complex, which is the product of both
     */
    public Complexf mul(double x, double y) {
        return mul((float) x, (float) y);
    }

    /**
     * Multiplies the float components of another complex with this one.
     *
     * @param x The x (real) component of the complex to multiply with
     * @param y The y (imaginary) component of the complex to multiply with
     * @return A new complex, which is the product of both
     */
    public Complexf mul(float x, float y) {
        return new Complexf(
                this.x * x - this.y * y,
                this.x * y + this.y * x);
    }

    /**
     * Divides the components of this complex by a double scalar.
     *
     * @param a The division scalar
     * @return A new complex, which has each component divided by the scalar
     */
    public Complexf div(double a) {
        return div((float) a);
    }

    /**
     * Divides the components of this complex by a float scalar.
     *
     * @param a The division scalar
     * @return A new complex, which has each component divided by the scalar
     */
    @Override
    public Complexf div(float a) {
        return new Complexf(x / a, y / a);
    }

    /**
     * Divides this complex by another one.
     *
     * @param c The complex to divide with
     * @return The quotient of the two complexes
     */
    public Complexf div(Complexf c) {
        return div(c.x, c.y);
    }

    /**
     * Divides this complex by the double components of another one.
     *
     * @param x The x (real) component of the complex to divide with
     * @param y The y (imaginary) component of the complex to divide with
     * @return The quotient of the two complexes
     */
    public Complexf div(double x, double y) {
        return div((float) x, (float) y);
    }

    /**
     * Divides this complex by the float components of another one.
     *
     * @param x The x (real) component of the complex to divide with
     * @param y The y (imaginary) component of the complex to divide with
     * @return The quotient of the two complexes
     */
    public Complexf div(float x, float y) {
        final float d = x * x + y * y;
        return new Complexf(
                (this.x * x + this.y * y) / d,
                (this.y * x - this.x * y) / d);
    }

    /**
     * Returns the dot product of this complex with another one.
     *
     * @param c The complex to calculate the dot product with
     * @return The dot product of the two complexes
     */
    public float dot(Complexf c) {
        return dot(c.x, c.y);
    }

    /**
     * Returns the dot product of this complex with the double components of another one.
     *
     * @param x The x (real) component of the complex to calculate the dot product with
     * @param y The y (imaginary) component of the complex to calculate the dot product with
     * @return The dot product of the two complexes
     */
    public float dot(double x, double y) {
        return dot((float) x, (float) y);
    }

    /**
     * Returns the dot product of this complex with the float components of another one.
     *
     * @param x The x (real) component of the complex to calculate the dot product with
     * @param y The y (imaginary) component of the complex to calculate the dot product with
     * @return The dot product of the two complexes
     */
    public float dot(float x, float y) {
        return this.x * x + this.y * y;
    }

    /**
     * Rotates a vector by this complex.
     *
     * @param v The vector to rotate
     * @return The rotated vector
     */
    public Vector2f rotate(Vector2f v) {
        return rotate(v.getX(), v.getY());
    }

    /**
     * Rotates the double components of a vector by this complex.
     *
     * @param x The x component of the vector
     * @param y The y component of the vector
     * @return The rotated vector
     */
    public Vector2f rotate(double x, double y) {
        return rotate((float) x, (float) y);
    }

    /**
     * Rotates the float components of a vector by this complex.
     *
     * @param x The x component of the vector
     * @param y The y component of the vector
     * @return The rotated vector
     */
    public Vector2f rotate(float x, float y) {
        final float length = length();
        if (Math.abs(length) < GenericMath.FLT_EPSILON) {
            throw new ArithmeticException("Cannot rotate by the zero complex");
        }
        final float nx = this.x / length;
        final float ny = this.y / length;
        return new Vector2f(x * nx - y * ny, y * nx + x * ny);
    }

    /**
     * Returns a unit vector pointing in the same direction as this complex on the complex plane.
     *
     * @return The vector representing the direction this complex is pointing to
     */
    public Vector2f getDirection() {
        return new Vector2f(x, y).normalize();
    }

    /**
     * Returns the angle in radians formed by the direction vector of this complex on the complex plane.
     *
     * @return The angle in radians of the direction vector of this complex
     */
    public float getAngleRad() {
        return (float) TrigMath.atan2(y, x);
    }

    /**
     * Returns the angle in degrees formed by the direction vector of this complex on the complex plane.
     *
     * @return The angle in degrees of the direction vector of this complex
     */
    public float getAngleDeg() {
        return (float) Math.toDegrees(getAngleRad());
    }

    /**
     * Returns the conjugate of this complex.
 Conjugation of a complex a is an operation returning complex a' such that a' * a = a * a' = |a|2 where
     * |a|2 is squared length of a.
     *
     * @return A new complex, which is the conjugate of this one
     */
    @Override
    public Complexf conjugate() {
        return new Complexf(x, -y);
    }

    /**
     * Returns the inverse of this complex.
 Inversion of a complex a returns complex a-1 = a' / |a|2 where a' is {@link #conjugate()
     * conjugation} of a, and |a|2 is squared length of a.
 For any complexes a, b, c, such that a * b = c equations
     * a-1 * c = b and c * b-1 = a are true.
     *
     * @return A new complex, which is the inverse of this one
     */
    @Override
    public Complexf invert() {
        final float lengthSquared = lengthSquared();
        if (Math.abs(lengthSquared) < GenericMath.FLT_EPSILON) {
            throw new ArithmeticException("Cannot invert a complex of length zero");
        }
        return conjugate().div(lengthSquared);
    }

    /**
     * Returns the square of the length of this complex.
     *
     * @return The square of the length
     */
    @Override
    public float lengthSquared() {
        return x * x + y * y;
    }

    /**
     * Returns the length of this complex.
     *
     * @return The length
     */
    @Override
    public float length() {
        return (float) Math.sqrt(lengthSquared());
    }

    /**
     * Normalizes this complex.
     *
     * @return A new complex of unit length
     */
    @Override
    public Complexf normalize() {
        final float length = length();
        if (Math.abs(length) < GenericMath.FLT_EPSILON) {
            throw new ArithmeticException("Cannot normalize the zero complex");
        }
        return new Complexf(x / length, y / length);
    }

    /**
     * Converts this complex to a quaternion by
     * using (0, 0, 1) as a rotation axis.
     *
     * @return A quaternion of this rotation around the unit z
     */
    public Quaternionf toQuaternion() {
        return toQuaternion(Vector3f.UNIT_Z);
    }

    /**
     * Converts this complex to a quaternion by
     * using the provided vector as a rotation axis.
     *
     * @param axis The rotation axis
     * @return A quaternion of this rotation around the given axis
     */
    public Quaternionf toQuaternion(Vector3f axis) {
        return toQuaternion(axis.getX(), axis.getY(), axis.getZ());
    }

    /**
     * Converts this complex to a quaternion by
     * using the provided double components vector
     * as a rotation axis.
     *
     * @param x The x component of the axis vector
     * @param y The y component of the axis vector
     * @param z The z component of the axis vector
     * @return A quaternion of this rotation around the given axis
     */
    public Quaternionf toQuaternion(double x, double y, double z) {
        return toQuaternion((float) x, (float) y, (float) z);
    }

    /**
     * Converts this complex to a quaternion by
     * using the provided float components vector
     * as a rotation axis.
     *
     * @param x The x component of the axis vector
     * @param y The y component of the axis vector
     * @param z The z component of the axis vector
     * @return A quaternion of this rotation around the given axis
     */
    public Quaternionf toQuaternion(float x, float y, float z) {
        return Quaternionf.fromAngleRadAxis(getAngleRad(), x, y, z);
    }

    @Override
    public Complexf toFloat() {
        return new Complexf(x, y);
    }

    @Override
    public Complexd toDouble() {
        return new Complexd(x, y);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Complexf)) {
            return false;
        }
        final Complexf complex = (Complexf) o;
        if (Float.compare(complex.x, x) != 0) {
            return false;
        }
        if (Float.compare(complex.y, y) != 0) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        if (!hashed) {
            final int result = (x != +0.0f ? HashFunctions.hash(x) : 0);
            hashCode = 31 * result + (y != +0.0f ? HashFunctions.hash(y) : 0);
            hashed = true;
        }
        return hashCode;
    }

    @Override
    public int compareTo(Complexf c) {
        return (int) Math.signum(lengthSquared() - c.lengthSquared());
    }

    @Override
    public Complexf clone() {
        return new Complexf(this);
    }

    @Override
    public String toString() {
        return "(" + x + ", " + y + ")";
    }

    /**
     * Creates a new complex from the float real component.
     *
     *
The {@link #ZERO} constant is re-used when {@code x} is 0.

     *
     * @param x The x (real) component
     * @return The complex created from the float real component
     */
    public static Complexf fromReal(float x) {
         return x == 0 ? ZERO : new Complexf(x, 0);
    }

    /**
     * Creates a new complex from the float imaginary components.
     *
     *
The {@link #ZERO} constant is re-used when {@code y} is 0.

     *
     * @param y The y (imaginary) component
     * @return The complex created from the float imaginary component
     */
    public static Complexf fromImaginary(float y) {
         return y == 0 ? ZERO : new Complexf(0, y);
    }

    /**
     * Creates a new complex from the float components.
     *
     *
The {@link #ZERO} constant is re-used when both {@code x} and {@code z} are 0.

     *
     * @param x The x (real) component
     * @param y The y (imaginary) component
     * @return The complex created from the float components
     */
    public static Complexf from(float x, float y) {
         return x == 0 && y == 0 ? ZERO : new Complexf(x, y);
    }

    /**
     * Creates a new complex from the angle defined from the first to the second vector.
     *
     * @param from The first vector
     * @param to The second vector
     * @return The complex defined by the angle between the vectors
     */
    public static Complexf fromRotationTo(Vector2f from, Vector2f to) {
        return fromAngleRad(TrigMath.acos(from.dot(to) / (from.length() * to.length())));
    }

    /**
     * Creates a new complex from the angle defined from the first to the second vector.
     *
     * @param from The first vector
     * @param to The second vector
     * @return The complex defined by the angle between the vectors
     */
    public static Complexf fromRotationTo(Vector3f from, Vector3f to) {
        return fromAngleRad(TrigMath.acos(from.dot(to) / (from.length() * to.length())));
    }

    /**
     * Creates a new complex from the double angle in degrees.
     *
     * @param angle The angle in degrees
     * @return The complex defined by the angle
     */
    public static Complexf fromAngleDeg(double angle) {
        return fromAngleRad(Math.toRadians(angle));
    }

    /**
     * Creates a new complex from the double angle in radians.
     *
     * @param angle The angle in radians
     * @return The complex defined by the angle
     */
    public static Complexf fromAngleRad(double angle) {
        return fromAngleRad((float) angle);
    }

    /**
     * Creates a new complex from the float angle in radians.
     *
     * @param angle The angle in radians
     * @return The complex defined by the angle
     */
    public static Complexf fromAngleDeg(float angle) {
        return fromAngleRad((float) Math.toRadians(angle));
    }

    /**
     * Creates a new complex from the float angle in radians.
     *
     * @param angle The angle in radians
     * @return The complex defined by the angle
     */
    public static Complexf fromAngleRad(float angle) {
        return new Complexf(TrigMath.cos(angle), TrigMath.sin(angle));
    }
}


/*
 * This file is part of Flow Math, licensed under the MIT License (MIT).
 *
 * Copyright (c) 2013 Flow Powered <https://flowpowered.com/>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.flowpowered.math;

/**
 * A class designed for fast trigonometry operations. Sine, cosine, tangent, cotangent secant and cosecant use a sine float table.
 */
public class TrigMath {
    // Constants
    public static final double PI = Math.PI;
    public static final double SQUARED_PI = PI * PI;
    public static final double HALF_PI = PI / 2;
    public static final double QUARTER_PI = HALF_PI / 2;
    public static final double TWO_PI = 2 * PI;
    public static final double THREE_PI_HALVES = TWO_PI - HALF_PI;
    public static final double DEG_TO_RAD = PI / 180;
    public static final double HALF_DEG_TO_RAD = PI / 360;
    public static final double RAD_TO_DEG = 180 / PI;
    public static final double SQRT_OF_TWO = Math.sqrt(2);
    public static final double HALF_SQRT_OF_TWO = SQRT_OF_TWO / 2;
    // Trig
    private static final int SIN_BITS = 22;
    private static final int SIN_SIZE = 1 << SIN_BITS;
    private static final int SIN_MASK = SIN_SIZE - 1;
    private static final float[] SIN_TABLE = new float[SIN_SIZE];
    private static final double SIN_CONVERSION_FACTOR = (SIN_SIZE / TWO_PI);
    private static final int COS_OFFSET = SIN_SIZE / 4;
    // Arc trig
    private static final double sq2p1 = 2.414213562373095048802;
    private static final double sq2m1 = 0.414213562373095048802;
    private static final double p4 = 0.161536412982230228262E2;
    private static final double p3 = 0.26842548195503973794141E3;
    private static final double p2 = 0.11530293515404850115428136E4;
    private static final double p1 = 0.178040631643319697105464587E4;
    private static final double p0 = 0.89678597403663861959987488E3;
    private static final double q4 = 0.5895697050844462222791E2;
    private static final double q3 = 0.536265374031215315104235E3;
    private static final double q2 = 0.16667838148816337184521798E4;
    private static final double q1 = 0.207933497444540981287275926E4;
    private static final double q0 = 0.89678597403663861962481162E3;

    static {
        for (int i = 0; i < SIN_SIZE; i++) {
            SIN_TABLE[i] = (float) Math.sin((i * TWO_PI) / SIN_SIZE);
        }
    }

    private TrigMath() {
    }

    /**
     * Sine calculation using a table.
 No interpolation is performed: Accuracy is up to the 6th decimal place
     *
     * @param angle the angle in radians
     * @return the sine of the angle
     */
    public static float sin(double angle) {
        return sinRaw(GenericMath.floor(angle * SIN_CONVERSION_FACTOR));
    }

    /**
     * Cosine calculation using a table.
 No interpolation is performed: Accuracy is up to the 6th decimal place
     *
     * @param angle the angle in radians
     * @return the cosine of the angle
     */
    public static float cos(double angle) {
        return cosRaw(GenericMath.floor(angle * SIN_CONVERSION_FACTOR));
    }

    /**
     * Tangent calculations using a table.
 sin(angle) / cos(angle)

 No interpolation is performed: Accuracy is up to the 6th decimal place
     *
     * @param angle in radians
     * @return the tangent of the angle
     */
    public static float tan(double angle) {
        int idx = GenericMath.floor(angle * SIN_CONVERSION_FACTOR);
        return sinRaw(idx) / cosRaw(idx);
    }

    /**
     * Cosecant calculations using a table.
 1 / sin(angle)

 No interpolation is performed: Accuracy is up to the 6th decimal place
     *
     * @param angle the angle in radians
     * @return the cosecant of the angle
     */
    public static float csc(double angle) {
        return 1 / sin(angle);
    }

    /**
     * Secant calculations using a table:
 1 / cos(angle)

 No interpolation is performed: Accuracy is up to the 6th decimal place
     *
     * @param angle the angle in radians
     * @return the secant of the angle
     */
    public static float sec(double angle) {
        return 1 / cos(angle);
    }

    /**
     * Cotangent calculations using a table.
 cos(angle) / sin(angle)

 No interpolation is performed: Accuracy is up to the 6th decimal place
     *
     * @param angle in radians
     * @return the cotangent of the angle
     */
    public static float cot(double angle) {
        int idx = GenericMath.floor(angle * SIN_CONVERSION_FACTOR);
        return cosRaw(idx) / sinRaw(idx);
    }

    /**
     * Calculates the arc sine of the value specified

 Returns NaN if the input value is outside the sine range
     *
     * @param value of the sine
     * @return sine arc in radians
     */
    public static double asin(double value) {
        if (value > 1) {
            return Double.NaN;
        } else if (value < 0) {
            return -asin(-value);
        } else {
            double temp = Math.sqrt(1 - value * value);
            if (value > 0.7) {
                return HALF_PI - msatan(temp / value);
            } else {
                return msatan(value / temp);
            }
        }
    }

    /**
     * Calculates the arc cosine of the value specified

 Returns NaN if the input value is outside the cosine range
     *
     * @param value of the cosine
     * @return cosine arc in radians
     */
    public static double acos(double value) {
        if (value > 1 || value < -1) {
            return Double.NaN;
        } else {
            return HALF_PI - asin(value);
        }
    }

    /**
     * Calculates the arc tangent of the value specified
     *
     * @param value of the tangent
     * @return tangent arc in radians
     */
    public static double atan(double value) {
        if (value > 0) {
            return msatan(value);
        } else {
            return -msatan(-value);
        }
    }

    /**
     * Computes the phase theta by computing an arc tangent of y/x
 Gets the yaw rotation component in radians when looking into the direction specified
     *
     * @param y direction
     * @param x direction
     * @return tangent arc in radians
     */
    public static double atan2(double y, double x) {
        if (y + x == y) {
            return y >= 0 ? HALF_PI : -HALF_PI;
        }
        y = atan(y / x);
        if (x < 0) {
            if (y <= 0) {
                return y + PI;
            } else {
                return y - PI;
            }
        }
        return y;
    }

    /**
     * Calculates the arc cosecant of the value specified

 Returns NaN if the input value is outside the cosecant range
     *
     * @param value of the cosecant
     * @return cosecant arc in radians
     */
    public static double acsc(double value) {
        if (value == 0) {
            return Double.NaN;
        }
        return asin(1 / value);
    }

    /**
     * Calculates the arc secant of the value specified

 Returns NaN if the input value is outside the secant range
     *
     * @param value of the secant
     * @return secant arc in radians
     */
    public static double asec(double value) {
        if (value == 0) {
            return Double.NaN;
        }
        return acos(1 / value);
    }

    /**
     * Calculates the arc cotangent of the value specified

 Returns NaN if the input value is outside the cotangent range
     *
     * @param value of the cotangent
     * @return cotangent arc in radians
     */
    public static double acot(double value) {
        if (value == 0) {
            return Double.NaN;
        } else if (value > 0) {
            return atan(1 / value);
        } else {
            return atan(1 / value) + PI;
        }
    }

    private static float sinRaw(int idx) {
        return SIN_TABLE[idx & SIN_MASK];
    }

    private static float cosRaw(int idx) {
        return SIN_TABLE[(idx + COS_OFFSET) & SIN_MASK];
    }

    private static double mxatan(double arg) {
        final double argsq = arg * arg;
        double value = ((((p4 * argsq + p3) * argsq + p2) * argsq + p1) * argsq + p0);
        value /= ((((argsq + q4) * argsq + q3) * argsq + q2) * argsq + q1) * argsq + q0;
        return value * arg;
    }

    private static double msatan(double arg) {
        if (arg < sq2m1) {
            return mxatan(arg);
        }
        if (arg > sq2p1) {
            return HALF_PI - mxatan(1 / arg);
        }
        return HALF_PI / 2 + mxatan((arg - 1) / (arg + 1));
    }
}


package com.flowpowered.math.vector;

import java.io.Serializable;

import com.flowpowered.math.GenericMath;
import com.flowpowered.math.HashFunctions;

public class Vector4l implements Vectorl, Comparable<Vector4l>, Serializable, Cloneable {
    private static final long serialVersionUID = 1;
    public static final Vector4l ZERO = new Vector4l(0, 0, 0, 0);
    public static final Vector4l UNIT_X = new Vector4l(1, 0, 0, 0);
    public static final Vector4l UNIT_Y = new Vector4l(0, 1, 0, 0);
    public static final Vector4l UNIT_Z = new Vector4l(0, 0, 1, 0);
    public static final Vector4l UNIT_W = new Vector4l(0, 0, 0, 1);
    public static final Vector4l ONE = new Vector4l(1, 1, 1, 1);
    private final long x;
    private final long y;
    private final long z;
    private final long w;
    private transient volatile boolean hashed = false;
    private transient volatile int hashCode = 0;

    public Vector4l() {
        this(0, 0, 0, 0);
    }

    public Vector4l(Vector2l v) {
        this(v, 0, 0);
    }

    public Vector4l(Vector2l v, double z, double w) {
        this(v, GenericMath.floorl(z), GenericMath.floorl(w));
    }

    public Vector4l(Vector2l v, long z, long w) {
        this(v.getX(), v.getY(), z, w);
    }

    public Vector4l(Vector3l v) {
        this(v, 0);
    }

    public Vector4l(Vector3l v, double w) {
        this(v, GenericMath.floorl(w));
    }

    public Vector4l(Vector3l v, long w) {
        this(v.getX(), v.getY(), v.getZ(), w);
    }

    public Vector4l(Vector4l v) {
        this(v.x, v.y, v.z, v.w);
    }

    public Vector4l(VectorNl v) {
        this(v.get(0), v.get(1), v.size() > 2 ? v.get(2) : 0, v.size() > 3 ? v.get(3) : 0);
    }

    public Vector4l(double x, double y, double z, double w) {
        this(GenericMath.floorl(x), GenericMath.floorl(y), GenericMath.floorl(z), GenericMath.floorl(w));
    }

    public Vector4l(long x, long y, long z, long w) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
    }

    public long getX() {
        return x;
    }

    public long getY() {
        return y;
    }

    public long getZ() {
        return z;
    }

    public long getW() {
        return w;
    }

    public Vector4l add(Vector4l v) {
        return add(v.x, v.y, v.z, v.w);
    }

    public Vector4l add(double x, double y, double z, double w) {
        return add(GenericMath.floorl(x), GenericMath.floorl(y), GenericMath.floorl(z), GenericMath.floorl(w));
    }

    public Vector4l add(long x, long y, long z, long w) {
        return new Vector4l(this.x + x, this.y + y, this.z + z, this.w + w);
    }

    public Vector4l sub(Vector4l v) {
        return sub(v.x, v.y, v.z, v.w);
    }

    public Vector4l sub(double x, double y, double z, double w) {
        return sub(GenericMath.floorl(x), GenericMath.floorl(y), GenericMath.floorl(z), GenericMath.floorl(w));
    }

    public Vector4l sub(long x, long y, long z, long w) {
        return new Vector4l(this.x - x, this.y - y, this.z - z, this.w - w);
    }

    public Vector4l mul(double a) {
        return mul(GenericMath.floorl(a));
    }

    @Override
    public Vector4l mul(long a) {
        return mul(a, a, a, a);
    }

    public Vector4l mul(Vector4l v) {
        return mul(v.x, v.y, v.z, v.w);
    }

    public Vector4l mul(double x, double y, double z, double w) {
        return mul(GenericMath.floorl(x), GenericMath.floorl(y), GenericMath.floorl(z), GenericMath.floorl(w));
    }

    public Vector4l mul(long x, long y, long z, long w) {
        return new Vector4l(this.x * x, this.y * y, this.z * z, this.w * w);
    }

    public Vector4l div(double a) {
        return div(GenericMath.floorl(a));
    }

    @Override
    public Vector4l div(long a) {
        return div(a, a, a, a);
    }

    public Vector4l div(Vector4l v) {
        return div(v.x, v.y, v.z, v.w);
    }

    public Vector4l div(double x, double y, double z, double w) {
        return div(GenericMath.floorl(x), GenericMath.floorl(y), GenericMath.floorl(z), GenericMath.floorl(w));
    }

    public Vector4l div(long x, long y, long z, long w) {
        return new Vector4l(this.x / x, this.y / y, this.z / z, this.w / w);
    }

    public long dot(Vector4l v) {
        return dot(v.x, v.y, v.z, v.w);
    }

    public long dot(double x, double y, double z, double w) {
        return dot(GenericMath.floorl(x), GenericMath.floorl(y), GenericMath.floorl(z), GenericMath.floorl(w));
    }

    public long dot(long x, long y, long z, long w) {
        return this.x * x + this.y * y + this.z * z + this.w * w;
    }

    public Vector4l project(Vector4l v) {
        return project(v.x, v.y, v.z, v.w);
    }

    public Vector4l project(double x, double y, double z, double w) {
        return project(GenericMath.floorl(x), GenericMath.floorl(y), GenericMath.floorl(z), GenericMath.floorl(w));
    }

    public Vector4l project(long x, long y, long z, long w) {
        final long lengthSquared = x * x + y * y + z * z + w * w;
        if (lengthSquared == 0) {
            throw new ArithmeticException("Cannot project onto the zero vector");
        }
        final double a = (double) dot(x, y, z, w) / lengthSquared;
        return new Vector4l(a * x, a * y, a * z, a * w);
    }

    public Vector4l pow(double pow) {
        return pow(GenericMath.floorl(pow));
    }

    @Override
    public Vector4l pow(long power) {
        return new Vector4l(Math.pow(x, power), Math.pow(y, power), Math.pow(z, power), Math.pow(w, power));
    }

    @Override
    public Vector4l abs() {
        return new Vector4l(Math.abs(x), Math.abs(y), Math.abs(z), Math.abs(w));
    }

    @Override
    public Vector4l negate() {
        return new Vector4l(-x, -y, -z, -w);
    }

    public Vector4l min(Vector4l v) {
        return min(v.x, v.y, v.z, v.w);
    }

    public Vector4l min(double x, double y, double z, double w) {
        return min(GenericMath.floorl(x), GenericMath.floorl(y), GenericMath.floorl(z), GenericMath.floorl(w));
    }

    public Vector4l min(long x, long y, long z, long w) {
        return new Vector4l(Math.min(this.x, x), Math.min(this.y, y), Math.min(this.z, z), Math.min(this.w, w));
    }

    public Vector4l max(Vector4l v) {
        return max(v.x, v.y, v.z, v.w);
    }

    public Vector4l max(double x, double y, double z, double w) {
        return max(GenericMath.floorl(x), GenericMath.floorl(y), GenericMath.floorl(z), GenericMath.floorl(w));
    }

    public Vector4l max(long x, long y, long z, long w) {
        return new Vector4l(Math.max(this.x, x), Math.max(this.y, y), Math.max(this.z, z), Math.max(this.w, w));
    }

    public long distanceSquared(Vector4l v) {
        return distanceSquared(v.x, v.y, v.z, v.w);
    }

    public long distanceSquared(double x, double y, double z, double w) {
        return distanceSquared(GenericMath.floorl(x), GenericMath.floorl(y), GenericMath.floorl(z), GenericMath.floorl(w));
    }

    public long distanceSquared(long x, long y, long z, long w) {
        final long dx = this.x - x;
        final long dy = this.y - y;
        final long dz = this.z - z;
        final long dw = this.w - w;
        return dx * dx + dy * dy + dz * dz + dw * dw;
    }

    public double distance(Vector4l v) {
        return distance(v.x, v.y, v.z, v.w);
    }

    public double distance(double x, double y, double z, double w) {
        return distance(GenericMath.floorl(x), GenericMath.floorl(y), GenericMath.floorl(z), GenericMath.floorl(w));
    }

    public double distance(long x, long y, long z, long w) {
        return (double) Math.sqrt(distanceSquared(x, y, z, w));
    }

    @Override
    public long lengthSquared() {
        return x * x + y * y + z * z + w * w;
    }

    @Override
    public double length() {
        return (double) Math.sqrt(lengthSquared());
    }

    /**
     * Return the axis with the minimal value.
     *
     * @return {@link int} axis with minimal value
     */
    @Override
    public int getMinAxis() {
        long value = x;
        int axis = 0;
        if (y < value) {
            value = y;
            axis = 1;
        }
        if (z < value) {
            value = z;
            axis = 2;
        }
        if (w < value) {
            axis = 3;
        }
        return axis;
    }

    /**
     * Return the axis with the maximum value.
     *
     * @return {@link int} axis with maximum value
     */
    @Override
    public int getMaxAxis() {
        long value = x;
        int axis = 0;
        if (y > value) {
            value = y;
            axis = 1;
        }
        if (z > value) {
            value = z;
            axis = 2;
        }
        if (w > value) {
            axis = 3;
        }
        return axis;
    }

    public Vector2l toVector2() {
        return new Vector2l(this);
    }

    public Vector3l toVector3() {
        return new Vector3l(this);
    }

    public VectorNl toVectorN() {
        return new VectorNl(this);
    }

    @Override
    public long[] toArray() {
        return new long[]{x, y, z, w};
    }

    @Override
    public Vector4i toInt() {
        return new Vector4i(x, y, z, w);
    }

    @Override
    public Vector4l toLong() {
        return new Vector4l(x, y, z, w);
    }

    @Override
    public Vector4f toFloat() {
        return new Vector4f(x, y, z, w);
    }

    @Override
    public Vector4d toDouble() {
        return new Vector4d(x, y, z, w);
    }

    @Override
    public int compareTo(Vector4l v) {
        return (int) (lengthSquared() - v.lengthSquared());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Vector4l)) {
            return false;
        }
        final Vector4l vector4 = (Vector4l) o;
        if (vector4.x != x) {
            return false;
        }
        if (vector4.y != y) {
            return false;
        }
        if (vector4.z != z) {
            return false;
        }
        if (vector4.w != w) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        if (!hashed) {
            int result = (x != +0.0f ? HashFunctions.hash(x) : 0);
            result = 31 * result + (y != +0.0f ? HashFunctions.hash(y) : 0);
            result = 31 * result + (z != +0.0f ? HashFunctions.hash(z) : 0);
            hashCode = 31 * result + (w != +0.0f ? HashFunctions.hash(w) : 0);
            hashed = true;
        }
        return hashCode;
    }

    @Override
    public Vector4l clone() {
        return new Vector4l(this);
    }

    @Override
    public String toString() {
        return "(" + x + ", " + y + ", " + z + ", " + w + ")";
    }

    public static Vector4l from(long n) {
         return n == 0 ? ZERO : new Vector4l(n, n, n, n);
    }

    public static Vector4l from(long x, long y, long z, long w) {
         return x == 0 && y == 0 && z == 0 && w == 0 ? ZERO : new Vector4l(x, y, z, w);
    }
}


/*
 * This file is part of Flow Math, licensed under the MIT License (MIT).
 *
 * Copyright (c) 2013 Flow Powered <https://flowpowered.com/>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
/* Copyright (c) 1999 CERN - European Organization for Nuclear Research.
 *
 * Permission to use, copy, modify, distribute and sell this software and
 * its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation. CERN makes no representations about the
 * suitability of this software for any purpose. It is provided "as is"
 * without expressed or implied warranty.
 */
package com.flowpowered.math;

/**
 * Provides various hash functions.
 *
 * @author wolfgang.hoschek@cern.ch
 * @version 1.0, 09/24/99
 */
public final class HashFunctions {
    /**
     * Returns a hashcode for the specified value.
     *
     * @return a hash code value for the specified value.
     */
    public static int hash(double value) {
        assert !Double.isNaN(value) : "Values of NaN are not supported.";

        long bits = Double.doubleToLongBits(value);
        return (int) (bits ^ (bits >>> 32));
        //return (int) Double.doubleToLongBits(value*663608941.737);
        // This avoids excessive hashCollisions in the case values are of the form (1.0, 2.0, 3.0, ...)
    }

    /**
     * Returns a hashcode for the specified value.
     *
     * @return a hash code value for the specified value.
     */
    public static int hash(float value) {
        assert !Float.isNaN(value) : "Values of NaN are not supported.";

        return Float.floatToIntBits(value * 663608941.737f);
        // This avoids excessive hashCollisions in the case values are of the form (1.0, 2.0, 3.0, ...)
    }

    /**
     * Returns a hashcode for the specified value.
     *
     * @return a hash code value for the specified value.
     */
    public static int hash(int value) {
        return value;
    }

    /**
     * Returns a hashcode for the specified value.
     *
     * @return a hash code value for the specified value.
     */
    public static int hash(long value) {
        return ((int) (value ^ (value >>> 32)));
    }

    /**
     * Returns a hashcode for the specified object.
     *
     * @return a hash code value for the specified object.
     */
    public static int hash(Object object) {
        return object == null ? 0 : object.hashCode();
    }

    /**
     * In profiling, it has been found to be faster to have our own local implementation of "ceil" rather than to call to {@link Math#ceil(double)}.
     */
    public static int fastCeil(float v) {
        int possible_result = (int) v;
        if (v - possible_result > 0) {
            possible_result++;
        }
        return possible_result;
    }
}