// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qdoublematrix4x4_p.h"
#include <QtCore/qmath.h>
//#include <QtCore/qvariant.h>
#include <QtCore/qdatastream.h>
#include <cmath>

QT_BEGIN_NAMESPACE

QDoubleMatrix4x4::QDoubleMatrix4x4(const double *values)
{
    for (int row = 0; row < 4; ++row)
        for (int col = 0; col < 4; ++col)
            m[col][row] = values[row * 4 + col];
    flagBits = General;
}

QDoubleMatrix4x4::QDoubleMatrix4x4(const double *values, int cols, int rows)
{
    for (int col = 0; col < 4; ++col) {
        for (int row = 0; row < 4; ++row) {
            if (col < cols && row < rows)
                m[col][row] = values[col * rows + row];
            else if (col == row)
                m[col][row] = 1.0;
            else
                m[col][row] = 0.0;
        }
    }
    flagBits = General;
}

static inline double matrixDet2(const double m[4][4], int col0, int col1, int row0, int row1)
{
    return m[col0][row0] * m[col1][row1] - m[col0][row1] * m[col1][row0];
}

static inline double matrixDet3
    (const double m[4][4], int col0, int col1, int col2,
     int row0, int row1, int row2)
{
    return m[col0][row0] * matrixDet2(m, col1, col2, row1, row2)
            - m[col1][row0] * matrixDet2(m, col0, col2, row1, row2)
            + m[col2][row0] * matrixDet2(m, col0, col1, row1, row2);
}

static inline double matrixDet4(const double m[4][4])
{
    double det;
    det  = m[0][0] * matrixDet3(m, 1, 2, 3, 1, 2, 3);
    det -= m[1][0] * matrixDet3(m, 0, 2, 3, 1, 2, 3);
    det += m[2][0] * matrixDet3(m, 0, 1, 3, 1, 2, 3);
    det -= m[3][0] * matrixDet3(m, 0, 1, 2, 1, 2, 3);
    return det;
}

double QDoubleMatrix4x4::determinant() const
{
    if ((flagBits & ~(Translation | Rotation2D | Rotation)) == Identity)
        return 1.0;

    if (flagBits < Rotation2D)
        return m[0][0] * m[1][1] * m[2][2]; // Translation | Scale
    if (flagBits < Perspective)
        return matrixDet3(m, 0, 1, 2, 0, 1, 2);
    return matrixDet4(m);
}

QDoubleMatrix4x4 QDoubleMatrix4x4::inverted(bool *invertible) const
{
    // Handle some of the easy cases first.
    if (flagBits == Identity) {
        if (invertible)
            *invertible = true;
        return QDoubleMatrix4x4();
    } else if (flagBits == Translation) {
        QDoubleMatrix4x4 inv;
        inv.m[3][0] = -m[3][0];
        inv.m[3][1] = -m[3][1];
        inv.m[3][2] = -m[3][2];
        inv.flagBits = Translation;
        if (invertible)
            *invertible = true;
        return inv;
    } else if (flagBits < Rotation2D) {
        // Translation | Scale
        if (m[0][0] == 0 || m[1][1] == 0 || m[2][2] == 0) {
            if (invertible)
                *invertible = false;
            return QDoubleMatrix4x4();
        }
        QDoubleMatrix4x4 inv;
        inv.m[0][0] = 1.0 / m[0][0];
        inv.m[1][1] = 1.0 / m[1][1];
        inv.m[2][2] = 1.0 / m[2][2];
        inv.m[3][0] = -m[3][0] * inv.m[0][0];
        inv.m[3][1] = -m[3][1] * inv.m[1][1];
        inv.m[3][2] = -m[3][2] * inv.m[2][2];
        inv.flagBits = flagBits;

        if (invertible)
            *invertible = true;
        return inv;
    } else if ((flagBits & ~(Translation | Rotation2D | Rotation)) == Identity) {
        if (invertible)
            *invertible = true;
        return orthonormalInverse();
    } else if (flagBits < Perspective) {
        QDoubleMatrix4x4 inv(1); // The "1" says to not load the identity.

        double det = matrixDet3(m, 0, 1, 2, 0, 1, 2);
        if (det == 0.0) {
            if (invertible)
                *invertible = false;
            return QDoubleMatrix4x4();
        }
        det = 1.0 / det;

        inv.m[0][0] =  matrixDet2(m, 1, 2, 1, 2) * det;
        inv.m[0][1] = -matrixDet2(m, 0, 2, 1, 2) * det;
        inv.m[0][2] =  matrixDet2(m, 0, 1, 1, 2) * det;
        inv.m[0][3] = 0;
        inv.m[1][0] = -matrixDet2(m, 1, 2, 0, 2) * det;
        inv.m[1][1] =  matrixDet2(m, 0, 2, 0, 2) * det;
        inv.m[1][2] = -matrixDet2(m, 0, 1, 0, 2) * det;
        inv.m[1][3] = 0;
        inv.m[2][0] =  matrixDet2(m, 1, 2, 0, 1) * det;
        inv.m[2][1] = -matrixDet2(m, 0, 2, 0, 1) * det;
        inv.m[2][2] =  matrixDet2(m, 0, 1, 0, 1) * det;
        inv.m[2][3] = 0;
        inv.m[3][0] = -inv.m[0][0] * m[3][0] - inv.m[1][0] * m[3][1] - inv.m[2][0] * m[3][2];
        inv.m[3][1] = -inv.m[0][1] * m[3][0] - inv.m[1][1] * m[3][1] - inv.m[2][1] * m[3][2];
        inv.m[3][2] = -inv.m[0][2] * m[3][0] - inv.m[1][2] * m[3][1] - inv.m[2][2] * m[3][2];
        inv.m[3][3] = 1;
        inv.flagBits = flagBits;

        if (invertible)
            *invertible = true;
        return inv;
    }

    QDoubleMatrix4x4 inv(1); // The "1" says to not load the identity.

    double det = matrixDet4(m);
    if (det == 0.0) {
        if (invertible)
            *invertible = false;
        return QDoubleMatrix4x4();
    }
    det = 1.0 / det;

    inv.m[0][0] =  matrixDet3(m, 1, 2, 3, 1, 2, 3) * det;
    inv.m[0][1] = -matrixDet3(m, 0, 2, 3, 1, 2, 3) * det;
    inv.m[0][2] =  matrixDet3(m, 0, 1, 3, 1, 2, 3) * det;
    inv.m[0][3] = -matrixDet3(m, 0, 1, 2, 1, 2, 3) * det;
    inv.m[1][0] = -matrixDet3(m, 1, 2, 3, 0, 2, 3) * det;
    inv.m[1][1] =  matrixDet3(m, 0, 2, 3, 0, 2, 3) * det;
    inv.m[1][2] = -matrixDet3(m, 0, 1, 3, 0, 2, 3) * det;
    inv.m[1][3] =  matrixDet3(m, 0, 1, 2, 0, 2, 3) * det;
    inv.m[2][0] =  matrixDet3(m, 1, 2, 3, 0, 1, 3) * det;
    inv.m[2][1] = -matrixDet3(m, 0, 2, 3, 0, 1, 3) * det;
    inv.m[2][2] =  matrixDet3(m, 0, 1, 3, 0, 1, 3) * det;
    inv.m[2][3] = -matrixDet3(m, 0, 1, 2, 0, 1, 3) * det;
    inv.m[3][0] = -matrixDet3(m, 1, 2, 3, 0, 1, 2) * det;
    inv.m[3][1] =  matrixDet3(m, 0, 2, 3, 0, 1, 2) * det;
    inv.m[3][2] = -matrixDet3(m, 0, 1, 3, 0, 1, 2) * det;
    inv.m[3][3] =  matrixDet3(m, 0, 1, 2, 0, 1, 2) * det;
    inv.flagBits = flagBits;

    if (invertible)
        *invertible = true;
    return inv;
}

QDoubleMatrix4x4 QDoubleMatrix4x4::transposed() const
{
    QDoubleMatrix4x4 result(1); // The "1" says to not load the identity.
    for (int row = 0; row < 4; ++row) {
        for (int col = 0; col < 4; ++col) {
            result.m[col][row] = m[row][col];
        }
    }
    // When a translation is transposed, it becomes a perspective transformation.
    result.flagBits = (flagBits & Translation ? General : flagBits);
    return result;
}

QDoubleMatrix4x4& QDoubleMatrix4x4::operator/=(double divisor)
{
    m[0][0] /= divisor;
    m[0][1] /= divisor;
    m[0][2] /= divisor;
    m[0][3] /= divisor;
    m[1][0] /= divisor;
    m[1][1] /= divisor;
    m[1][2] /= divisor;
    m[1][3] /= divisor;
    m[2][0] /= divisor;
    m[2][1] /= divisor;
    m[2][2] /= divisor;
    m[2][3] /= divisor;
    m[3][0] /= divisor;
    m[3][1] /= divisor;
    m[3][2] /= divisor;
    m[3][3] /= divisor;
    flagBits = General;
    return *this;
}

QDoubleMatrix4x4 operator/(const QDoubleMatrix4x4& matrix, double divisor)
{
    QDoubleMatrix4x4 m(1); // The "1" says to not load the identity.
    m.m[0][0] = matrix.m[0][0] / divisor;
    m.m[0][1] = matrix.m[0][1] / divisor;
    m.m[0][2] = matrix.m[0][2] / divisor;
    m.m[0][3] = matrix.m[0][3] / divisor;
    m.m[1][0] = matrix.m[1][0] / divisor;
    m.m[1][1] = matrix.m[1][1] / divisor;
    m.m[1][2] = matrix.m[1][2] / divisor;
    m.m[1][3] = matrix.m[1][3] / divisor;
    m.m[2][0] = matrix.m[2][0] / divisor;
    m.m[2][1] = matrix.m[2][1] / divisor;
    m.m[2][2] = matrix.m[2][2] / divisor;
    m.m[2][3] = matrix.m[2][3] / divisor;
    m.m[3][0] = matrix.m[3][0] / divisor;
    m.m[3][1] = matrix.m[3][1] / divisor;
    m.m[3][2] = matrix.m[3][2] / divisor;
    m.m[3][3] = matrix.m[3][3] / divisor;
    m.flagBits = QDoubleMatrix4x4::General;
    return m;
}

void QDoubleMatrix4x4::scale(const QDoubleVector3D& vector)
{
    double vx = vector.x();
    double vy = vector.y();
    double vz = vector.z();
    if (flagBits < Scale) {
        m[0][0] = vx;
        m[1][1] = vy;
        m[2][2] = vz;
    } else if (flagBits < Rotation2D) {
        m[0][0] *= vx;
        m[1][1] *= vy;
        m[2][2] *= vz;
    } else if (flagBits < Rotation) {
        m[0][0] *= vx;
        m[0][1] *= vx;
        m[1][0] *= vy;
        m[1][1] *= vy;
        m[2][2] *= vz;
    } else {
        m[0][0] *= vx;
        m[0][1] *= vx;
        m[0][2] *= vx;
        m[0][3] *= vx;
        m[1][0] *= vy;
        m[1][1] *= vy;
        m[1][2] *= vy;
        m[1][3] *= vy;
        m[2][0] *= vz;
        m[2][1] *= vz;
        m[2][2] *= vz;
        m[2][3] *= vz;
    }
    flagBits |= Scale;
}

void QDoubleMatrix4x4::scale(double x, double y)
{
    if (flagBits < Scale) {
        m[0][0] = x;
        m[1][1] = y;
    } else if (flagBits < Rotation2D) {
        m[0][0] *= x;
        m[1][1] *= y;
    } else if (flagBits < Rotation) {
        m[0][0] *= x;
        m[0][1] *= x;
        m[1][0] *= y;
        m[1][1] *= y;
    } else {
        m[0][0] *= x;
        m[0][1] *= x;
        m[0][2] *= x;
        m[0][3] *= x;
        m[1][0] *= y;
        m[1][1] *= y;
        m[1][2] *= y;
        m[1][3] *= y;
    }
    flagBits |= Scale;
}

void QDoubleMatrix4x4::scale(double x, double y, double z)
{
    if (flagBits < Scale) {
        m[0][0] = x;
        m[1][1] = y;
        m[2][2] = z;
    } else if (flagBits < Rotation2D) {
        m[0][0] *= x;
        m[1][1] *= y;
        m[2][2] *= z;
    } else if (flagBits < Rotation) {
        m[0][0] *= x;
        m[0][1] *= x;
        m[1][0] *= y;
        m[1][1] *= y;
        m[2][2] *= z;
    } else {
        m[0][0] *= x;
        m[0][1] *= x;
        m[0][2] *= x;
        m[0][3] *= x;
        m[1][0] *= y;
        m[1][1] *= y;
        m[1][2] *= y;
        m[1][3] *= y;
        m[2][0] *= z;
        m[2][1] *= z;
        m[2][2] *= z;
        m[2][3] *= z;
    }
    flagBits |= Scale;
}

void QDoubleMatrix4x4::scale(double factor)
{
    if (flagBits < Scale) {
        m[0][0] = factor;
        m[1][1] = factor;
        m[2][2] = factor;
    } else if (flagBits < Rotation2D) {
        m[0][0] *= factor;
        m[1][1] *= factor;
        m[2][2] *= factor;
    } else if (flagBits < Rotation) {
        m[0][0] *= factor;
        m[0][1] *= factor;
        m[1][0] *= factor;
        m[1][1] *= factor;
        m[2][2] *= factor;
    } else {
        m[0][0] *= factor;
        m[0][1] *= factor;
        m[0][2] *= factor;
        m[0][3] *= factor;
        m[1][0] *= factor;
        m[1][1] *= factor;
        m[1][2] *= factor;
        m[1][3] *= factor;
        m[2][0] *= factor;
        m[2][1] *= factor;
        m[2][2] *= factor;
        m[2][3] *= factor;
    }
    flagBits |= Scale;
}

void QDoubleMatrix4x4::translate(const QDoubleVector3D& vector)
{
    double vx = vector.x();
    double vy = vector.y();
    double vz = vector.z();
    if (flagBits == Identity) {
        m[3][0] = vx;
        m[3][1] = vy;
        m[3][2] = vz;
    } else if (flagBits == Translation) {
        m[3][0] += vx;
        m[3][1] += vy;
        m[3][2] += vz;
    } else if (flagBits == Scale) {
        m[3][0] = m[0][0] * vx;
        m[3][1] = m[1][1] * vy;
        m[3][2] = m[2][2] * vz;
    } else if (flagBits == (Translation | Scale)) {
        m[3][0] += m[0][0] * vx;
        m[3][1] += m[1][1] * vy;
        m[3][2] += m[2][2] * vz;
    } else if (flagBits < Rotation) {
        m[3][0] += m[0][0] * vx + m[1][0] * vy;
        m[3][1] += m[0][1] * vx + m[1][1] * vy;
        m[3][2] += m[2][2] * vz;
    } else {
        m[3][0] += m[0][0] * vx + m[1][0] * vy + m[2][0] * vz;
        m[3][1] += m[0][1] * vx + m[1][1] * vy + m[2][1] * vz;
        m[3][2] += m[0][2] * vx + m[1][2] * vy + m[2][2] * vz;
        m[3][3] += m[0][3] * vx + m[1][3] * vy + m[2][3] * vz;
    }
    flagBits |= Translation;
}

void QDoubleMatrix4x4::translate(double x, double y)
{
    if (flagBits == Identity) {
        m[3][0] = x;
        m[3][1] = y;
    } else if (flagBits == Translation) {
        m[3][0] += x;
        m[3][1] += y;
    } else if (flagBits == Scale) {
        m[3][0] = m[0][0] * x;
        m[3][1] = m[1][1] * y;
    } else if (flagBits == (Translation | Scale)) {
        m[3][0] += m[0][0] * x;
        m[3][1] += m[1][1] * y;
    } else if (flagBits < Rotation) {
        m[3][0] += m[0][0] * x + m[1][0] * y;
        m[3][1] += m[0][1] * x + m[1][1] * y;
    } else {
        m[3][0] += m[0][0] * x + m[1][0] * y;
        m[3][1] += m[0][1] * x + m[1][1] * y;
        m[3][2] += m[0][2] * x + m[1][2] * y;
        m[3][3] += m[0][3] * x + m[1][3] * y;
    }
    flagBits |= Translation;
}

void QDoubleMatrix4x4::translate(double x, double y, double z)
{
    if (flagBits == Identity) {
        m[3][0] = x;
        m[3][1] = y;
        m[3][2] = z;
    } else if (flagBits == Translation) {
        m[3][0] += x;
        m[3][1] += y;
        m[3][2] += z;
    } else if (flagBits == Scale) {
        m[3][0] = m[0][0] * x;
        m[3][1] = m[1][1] * y;
        m[3][2] = m[2][2] * z;
    } else if (flagBits == (Translation | Scale)) {
        m[3][0] += m[0][0] * x;
        m[3][1] += m[1][1] * y;
        m[3][2] += m[2][2] * z;
    } else if (flagBits < Rotation) {
        m[3][0] += m[0][0] * x + m[1][0] * y;
        m[3][1] += m[0][1] * x + m[1][1] * y;
        m[3][2] += m[2][2] * z;
    } else {
        m[3][0] += m[0][0] * x + m[1][0] * y + m[2][0] * z;
        m[3][1] += m[0][1] * x + m[1][1] * y + m[2][1] * z;
        m[3][2] += m[0][2] * x + m[1][2] * y + m[2][2] * z;
        m[3][3] += m[0][3] * x + m[1][3] * y + m[2][3] * z;
    }
    flagBits |= Translation;
}

void QDoubleMatrix4x4::rotate(double angle, const QDoubleVector3D& vector)
{
    rotate(angle, vector.x(), vector.y(), vector.z());
}

void QDoubleMatrix4x4::rotate(double angle, double x, double y, double z)
{
    if (angle == 0.0)
        return;
    double c, s;
    if (angle == 90.0 || angle == -270.0) {
        s = 1.0;
        c = 0.0;
    } else if (angle == -90.0 || angle == 270.0) {
        s = -1.0;
        c = 0.0;
    } else if (angle == 180.0 || angle == -180.0) {
        s = 0.0;
        c = -1.0;
    } else {
        double a = qDegreesToRadians(angle);
        c = std::cos(a);
        s = std::sin(a);
    }
    if (x == 0.0) {
        if (y == 0.0) {
            if (z != 0.0) {
                // Rotate around the Z axis.
                if (z < 0)
                    s = -s;
                double tmp;
                m[0][0] = (tmp = m[0][0]) * c + m[1][0] * s;
                m[1][0] = m[1][0] * c - tmp * s;
                m[0][1] = (tmp = m[0][1]) * c + m[1][1] * s;
                m[1][1] = m[1][1] * c - tmp * s;
                m[0][2] = (tmp = m[0][2]) * c + m[1][2] * s;
                m[1][2] = m[1][2] * c - tmp * s;
                m[0][3] = (tmp = m[0][3]) * c + m[1][3] * s;
                m[1][3] = m[1][3] * c - tmp * s;

                flagBits |= Rotation2D;
                return;
            }
        } else if (z == 0.0) {
            // Rotate around the Y axis.
            if (y < 0)
                s = -s;
            double tmp;
            m[2][0] = (tmp = m[2][0]) * c + m[0][0] * s;
            m[0][0] = m[0][0] * c - tmp * s;
            m[2][1] = (tmp = m[2][1]) * c + m[0][1] * s;
            m[0][1] = m[0][1] * c - tmp * s;
            m[2][2] = (tmp = m[2][2]) * c + m[0][2] * s;
            m[0][2] = m[0][2] * c - tmp * s;
            m[2][3] = (tmp = m[2][3]) * c + m[0][3] * s;
            m[0][3] = m[0][3] * c - tmp * s;

            flagBits |= Rotation;
            return;
        }
    } else if (y == 0.0 && z == 0.0) {
        // Rotate around the X axis.
        if (x < 0)
            s = -s;
        double tmp;
        m[1][0] = (tmp = m[1][0]) * c + m[2][0] * s;
        m[2][0] = m[2][0] * c - tmp * s;
        m[1][1] = (tmp = m[1][1]) * c + m[2][1] * s;
        m[2][1] = m[2][1] * c - tmp * s;
        m[1][2] = (tmp = m[1][2]) * c + m[2][2] * s;
        m[2][2] = m[2][2] * c - tmp * s;
        m[1][3] = (tmp = m[1][3]) * c + m[2][3] * s;
        m[2][3] = m[2][3] * c - tmp * s;

        flagBits |= Rotation;
        return;
    }

    if (double len = qHypot(x, y, z);
        !qFuzzyIsNull(len) && !qFuzzyCompare(len, 1.0)) {
        x /= len;
        y /= len;
        z /= len;
    }
    double ic = 1.0 - c;
    QDoubleMatrix4x4 rot(1); // The "1" says to not load the identity.
    rot.m[0][0] = x * x * ic + c;
    rot.m[1][0] = x * y * ic - z * s;
    rot.m[2][0] = x * z * ic + y * s;
    rot.m[3][0] = 0.0;
    rot.m[0][1] = y * x * ic + z * s;
    rot.m[1][1] = y * y * ic + c;
    rot.m[2][1] = y * z * ic - x * s;
    rot.m[3][1] = 0.0;
    rot.m[0][2] = x * z * ic - y * s;
    rot.m[1][2] = y * z * ic + x * s;
    rot.m[2][2] = z * z * ic + c;
    rot.m[3][2] = 0.0;
    rot.m[0][3] = 0.0;
    rot.m[1][3] = 0.0;
    rot.m[2][3] = 0.0;
    rot.m[3][3] = 1.0;
    rot.flagBits = Rotation;
    *this *= rot;
}

void QDoubleMatrix4x4::projectedRotate(double angle, double x, double y, double z,
                                       double distanceToPlane)
{
    // Used by QGraphicsRotation::applyTo() to perform a rotation
    // and projection back to 2D in a single step.
    if (qIsNull(distanceToPlane))
        return rotate(angle, x, y, z);
    if (angle == 0.0)
        return;
    double c, s;
    if (angle == 90.0 || angle == -270.0) {
        s = 1.0;
        c = 0.0;
    } else if (angle == -90.0 || angle == 270.0) {
        s = -1.0;
        c = 0.0;
    } else if (angle == 180.0 || angle == -180.0) {
        s = 0.0;
        c = -1.0;
    } else {
        double a = qDegreesToRadians(angle);
        c = std::cos(a);
        s = std::sin(a);
    }

    const double d = 1.0 / distanceToPlane;
    if (x == 0.0) {
        if (y == 0.0) {
            if (z != 0.0) {
                // Rotate around the Z axis.
                if (z < 0)
                    s = -s;
                double tmp;
                m[0][0] = (tmp = m[0][0]) * c + m[1][0] * s;
                m[1][0] = m[1][0] * c - tmp * s;
                m[0][1] = (tmp = m[0][1]) * c + m[1][1] * s;
                m[1][1] = m[1][1] * c - tmp * s;
                m[0][2] = (tmp = m[0][2]) * c + m[1][2] * s;
                m[1][2] = m[1][2] * c - tmp * s;
                m[0][3] = (tmp = m[0][3]) * c + m[1][3] * s;
                m[1][3] = m[1][3] * c - tmp * s;

                flagBits |= Rotation2D;
                return;
            }
        } else if (z == 0.0) {
            // Rotate around the Y axis.
            if (y < 0)
                s = -s;
            s *= d;
            m[0][0] = m[0][0] * c + m[3][0] * s;
            m[0][1] = m[0][1] * c + m[3][1] * s;
            m[0][2] = m[0][2] * c + m[3][2] * s;
            m[0][3] = m[0][3] * c + m[3][3] * s;
            flagBits = General;
            return;
        }
    } else if (y == 0.0 && z == 0.0) {
        // Rotate around the X axis.
        if (x < 0)
            s = -s;
        s *= d;
        m[1][0] = m[1][0] * c - m[3][0] * s;
        m[1][1] = m[1][1] * c - m[3][1] * s;
        m[1][2] = m[1][2] * c - m[3][2] * s;
        m[1][3] = m[1][3] * c - m[3][3] * s;
        flagBits = General;
        return;
    }

    if (const double len = qHypot(x, y, z);
        !qFuzzyIsNull(len) && !qFuzzyCompare(len, 1.0)) {
        x /= len;
        y /= len;
        z /= len;
    }
    double ic = 1.0 - c;
    QDoubleMatrix4x4 rot(1); // The "1" says to not load the identity.
    rot.m[0][0] = x * x * ic + c;
    rot.m[1][0] = x * y * ic - z * s;
    rot.m[2][0] = 0.0;
    rot.m[3][0] = 0.0;
    rot.m[0][1] = y * x * ic + z * s;
    rot.m[1][1] = y * y * ic + c;
    rot.m[2][1] = 0.0;
    rot.m[3][1] = 0.0;
    rot.m[0][2] = 0.0;
    rot.m[1][2] = 0.0;
    rot.m[2][2] = 1.0;
    rot.m[3][2] = 0.0;
    rot.m[0][3] = (x * z * ic - y * s) * -d;
    rot.m[1][3] = (y * z * ic + x * s) * -d;
    rot.m[2][3] = 0.0;
    rot.m[3][3] = 1.0;
    rot.flagBits = General;
    *this *= rot;
}

void QDoubleMatrix4x4::ortho(const QRect& rect)
{
    // Note: rect.right() and rect.bottom() subtract 1 in QRect,
    // which gives the location of a pixel within the rectangle,
    // instead of the extent of the rectangle.  We want the extent.
    // QRectF expresses the extent properly.
    ortho(rect.x(), rect.x() + rect.width(), rect.y() + rect.height(), rect.y(), -1.0, 1.0);
}

void QDoubleMatrix4x4::ortho(const QRectF& rect)
{
    ortho(rect.left(), rect.right(), rect.bottom(), rect.top(), -1.0, 1.0);
}

void QDoubleMatrix4x4::ortho(double left, double right, double bottom, double top, double nearPlane, double farPlane)
{
    // Bail out if the projection volume is zero-sized.
    if (left == right || bottom == top || nearPlane == farPlane)
        return;

    // Construct the projection.
    double width = right - left;
    double invheight = top - bottom;
    double clip = farPlane - nearPlane;
    QDoubleMatrix4x4 m(1);
    m.m[0][0] = 2.0 / width;
    m.m[1][0] = 0.0;
    m.m[2][0] = 0.0;
    m.m[3][0] = -(left + right) / width;
    m.m[0][1] = 0.0;
    m.m[1][1] = 2.0 / invheight;
    m.m[2][1] = 0.0;
    m.m[3][1] = -(top + bottom) / invheight;
    m.m[0][2] = 0.0;
    m.m[1][2] = 0.0;
    m.m[2][2] = -2.0 / clip;
    m.m[3][2] = -(nearPlane + farPlane) / clip;
    m.m[0][3] = 0.0;
    m.m[1][3] = 0.0;
    m.m[2][3] = 0.0;
    m.m[3][3] = 1.0;
    m.flagBits = Translation | Scale;

    // Apply the projection.
    *this *= m;
}

void QDoubleMatrix4x4::frustum(double left, double right, double bottom, double top, double nearPlane, double farPlane)
{
    // Bail out if the projection volume is zero-sized.
    if (left == right || bottom == top || nearPlane == farPlane)
        return;

    // Construct the projection.
    QDoubleMatrix4x4 m(1);
    double width = right - left;
    double invheight = top - bottom;
    double clip = farPlane - nearPlane;
    m.m[0][0] = 2.0 * nearPlane / width;
    m.m[1][0] = 0.0;
    m.m[2][0] = (left + right) / width;
    m.m[3][0] = 0.0;
    m.m[0][1] = 0.0;
    m.m[1][1] = 2.0 * nearPlane / invheight;
    m.m[2][1] = (top + bottom) / invheight;
    m.m[3][1] = 0.0;
    m.m[0][2] = 0.0;
    m.m[1][2] = 0.0;
    m.m[2][2] = -(nearPlane + farPlane) / clip;
    m.m[3][2] = -2.0 * nearPlane * farPlane / clip;
    m.m[0][3] = 0.0;
    m.m[1][3] = 0.0;
    m.m[2][3] = -1.0;
    m.m[3][3] = 0.0;
    m.flagBits = General;

    // Apply the projection.
    *this *= m;
}

void QDoubleMatrix4x4::perspective(double verticalAngle, double aspectRatio, double nearPlane, double farPlane)
{
    // Bail out if the projection volume is zero-sized.
    if (nearPlane == farPlane || aspectRatio == 0.0)
        return;

    // Construct the projection.
    QDoubleMatrix4x4 m(1);
    double radians = qDegreesToRadians(verticalAngle / 2.0);
    double sine = std::sin(radians);
    if (sine == 0.0)
        return;
    double cotan = std::cos(radians) / sine;
    double clip = farPlane - nearPlane;
    m.m[0][0] = cotan / aspectRatio;
    m.m[1][0] = 0.0;
    m.m[2][0] = 0.0;
    m.m[3][0] = 0.0;
    m.m[0][1] = 0.0;
    m.m[1][1] = cotan;
    m.m[2][1] = 0.0;
    m.m[3][1] = 0.0;
    m.m[0][2] = 0.0;
    m.m[1][2] = 0.0;
    m.m[2][2] = -(nearPlane + farPlane) / clip;
    m.m[3][2] = -(2.0 * nearPlane * farPlane) / clip;
    m.m[0][3] = 0.0;
    m.m[1][3] = 0.0;
    m.m[2][3] = -1.0;
    m.m[3][3] = 0.0;
    m.flagBits = General;

    // Apply the projection.
    *this *= m;
}

void QDoubleMatrix4x4::lookAt(const QDoubleVector3D& eye, const QDoubleVector3D& center, const QDoubleVector3D& up)
{
    QDoubleVector3D forward = center - eye;
    if (qFuzzyIsNull(forward.x()) && qFuzzyIsNull(forward.y()) && qFuzzyIsNull(forward.z()))
        return;

    forward.normalize();
    QDoubleVector3D side = QDoubleVector3D::crossProduct(forward, up).normalized();
    QDoubleVector3D upVector = QDoubleVector3D::crossProduct(side, forward);

    QDoubleMatrix4x4 m(1);
    m.m[0][0] = side.x();
    m.m[1][0] = side.y();
    m.m[2][0] = side.z();
    m.m[3][0] = 0.0;
    m.m[0][1] = upVector.x();
    m.m[1][1] = upVector.y();
    m.m[2][1] = upVector.z();
    m.m[3][1] = 0.0;
    m.m[0][2] = -forward.x();
    m.m[1][2] = -forward.y();
    m.m[2][2] = -forward.z();
    m.m[3][2] = 0.0;
    m.m[0][3] = 0.0;
    m.m[1][3] = 0.0;
    m.m[2][3] = 0.0;
    m.m[3][3] = 1.0;
    m.flagBits = Rotation;

    *this *= m;
    translate(-eye);
}

void QDoubleMatrix4x4::viewport(double left, double bottom, double width, double height, double nearPlane, double farPlane)
{
    const double w2 = width / 2.0;
    const double h2 = height / 2.0;

    QDoubleMatrix4x4 m(1);
    m.m[0][0] = w2;
    m.m[1][0] = 0.0;
    m.m[2][0] = 0.0;
    m.m[3][0] = left + w2;
    m.m[0][1] = 0.0;
    m.m[1][1] = h2;
    m.m[2][1] = 0.0;
    m.m[3][1] = bottom + h2;
    m.m[0][2] = 0.0;
    m.m[1][2] = 0.0;
    m.m[2][2] = (farPlane - nearPlane) / 2.0;
    m.m[3][2] = (nearPlane + farPlane) / 2.0;
    m.m[0][3] = 0.0;
    m.m[1][3] = 0.0;
    m.m[2][3] = 0.0;
    m.m[3][3] = 1.0;
    m.flagBits = General;

    *this *= m;
}

void QDoubleMatrix4x4::flipCoordinates()
{
    // Multiplying the y and z coordinates with -1 does NOT flip between right-handed and
    // left-handed coordinate systems, it just rotates 180 degrees around the x axis, so
    // I'm deprecating this function.
    if (flagBits < Rotation2D) {
        // Translation | Scale
        m[1][1] = -m[1][1];
        m[2][2] = -m[2][2];
    } else {
        m[1][0] = -m[1][0];
        m[1][1] = -m[1][1];
        m[1][2] = -m[1][2];
        m[1][3] = -m[1][3];
        m[2][0] = -m[2][0];
        m[2][1] = -m[2][1];
        m[2][2] = -m[2][2];
        m[2][3] = -m[2][3];
    }
    flagBits |= Scale;
}

void QDoubleMatrix4x4::copyDataTo(double *values) const
{
    for (int row = 0; row < 4; ++row)
        for (int col = 0; col < 4; ++col)
            values[row * 4 + col] = double(m[col][row]);
}

QRect QDoubleMatrix4x4::mapRect(const QRect& rect) const
{
    if (flagBits < Scale) {
        // Translation
        return QRect(qRound(rect.x() + m[3][0]),
                     qRound(rect.y() + m[3][1]),
                     rect.width(), rect.height());
    } else if (flagBits < Rotation2D) {
        // Translation | Scale
        double x = rect.x() * m[0][0] + m[3][0];
        double y = rect.y() * m[1][1] + m[3][1];
        double w = rect.width() * m[0][0];
        double h = rect.height() * m[1][1];
        if (w < 0) {
            w = -w;
            x -= w;
        }
        if (h < 0) {
            h = -h;
            y -= h;
        }
        return QRect(qRound(x), qRound(y), qRound(w), qRound(h));
    }

    QPoint tl = map(rect.topLeft());
    QPoint tr = map(QPoint(rect.x() + rect.width(), rect.y()));
    QPoint bl = map(QPoint(rect.x(), rect.y() + rect.height()));
    QPoint br = map(QPoint(rect.x() + rect.width(),
                           rect.y() + rect.height()));

    int xmin = qMin(qMin(tl.x(), tr.x()), qMin(bl.x(), br.x()));
    int xmax = qMax(qMax(tl.x(), tr.x()), qMax(bl.x(), br.x()));
    int ymin = qMin(qMin(tl.y(), tr.y()), qMin(bl.y(), br.y()));
    int ymax = qMax(qMax(tl.y(), tr.y()), qMax(bl.y(), br.y()));

    return QRect(xmin, ymin, xmax - xmin, ymax - ymin);
}

QRectF QDoubleMatrix4x4::mapRect(const QRectF& rect) const
{
    if (flagBits < Scale) {
        // Translation
        return rect.translated(m[3][0], m[3][1]);
    } else if (flagBits < Rotation2D) {
        // Translation | Scale
        double x = rect.x() * m[0][0] + m[3][0];
        double y = rect.y() * m[1][1] + m[3][1];
        double w = rect.width() * m[0][0];
        double h = rect.height() * m[1][1];
        if (w < 0) {
            w = -w;
            x -= w;
        }
        if (h < 0) {
            h = -h;
            y -= h;
        }
        return QRectF(x, y, w, h);
    }

    QPointF tl = map(rect.topLeft()); QPointF tr = map(rect.topRight());
    QPointF bl = map(rect.bottomLeft()); QPointF br = map(rect.bottomRight());

    double xmin = qMin(qMin(tl.x(), tr.x()), qMin(bl.x(), br.x()));
    double xmax = qMax(qMax(tl.x(), tr.x()), qMax(bl.x(), br.x()));
    double ymin = qMin(qMin(tl.y(), tr.y()), qMin(bl.y(), br.y()));
    double ymax = qMax(qMax(tl.y(), tr.y()), qMax(bl.y(), br.y()));

    return QRectF(QPointF(xmin, ymin), QPointF(xmax, ymax));
}

QDoubleMatrix4x4 QDoubleMatrix4x4::orthonormalInverse() const
{
    QDoubleMatrix4x4 result(1);  // The '1' says not to load identity

    result.m[0][0] = m[0][0];
    result.m[1][0] = m[0][1];
    result.m[2][0] = m[0][2];

    result.m[0][1] = m[1][0];
    result.m[1][1] = m[1][1];
    result.m[2][1] = m[1][2];

    result.m[0][2] = m[2][0];
    result.m[1][2] = m[2][1];
    result.m[2][2] = m[2][2];

    result.m[0][3] = 0.0;
    result.m[1][3] = 0.0;
    result.m[2][3] = 0.0;

    result.m[3][0] = -(result.m[0][0] * m[3][0] + result.m[1][0] * m[3][1] + result.m[2][0] * m[3][2]);
    result.m[3][1] = -(result.m[0][1] * m[3][0] + result.m[1][1] * m[3][1] + result.m[2][1] * m[3][2]);
    result.m[3][2] = -(result.m[0][2] * m[3][0] + result.m[1][2] * m[3][1] + result.m[2][2] * m[3][2]);
    result.m[3][3] = 1.0;

    result.flagBits = flagBits;

    return result;
}

void QDoubleMatrix4x4::optimize()
{
    // If the last row is not (0, 0, 0, 1), the matrix is not a special type.
    flagBits = General;
    if (m[0][3] != 0 || m[1][3] != 0 || m[2][3] != 0 || m[3][3] != 1)
        return;

    flagBits &= ~Perspective;

    // If the last column is (0, 0, 0, 1), then there is no translation.
    if (m[3][0] == 0 && m[3][1] == 0 && m[3][2] == 0)
        flagBits &= ~Translation;

    // If the two first elements of row 3 and column 3 are 0, then any rotation must be about Z.
    if (!m[0][2] && !m[1][2] && !m[2][0] && !m[2][1]) {
        flagBits &= ~Rotation;
        // If the six non-diagonal elements in the top left 3x3 matrix are 0, there is no rotation.
        if (!m[0][1] && !m[1][0]) {
            flagBits &= ~Rotation2D;
            // Check for identity.
            if (m[0][0] == 1 && m[1][1] == 1 && m[2][2] == 1)
                flagBits &= ~Scale;
        } else {
            // If the columns are orthonormal and form a right-handed system, then there is no scale.
            double det = matrixDet2(m, 0, 1, 0, 1);
            double lenX = m[0][0] * m[0][0] + m[0][1] * m[0][1];
            double lenY = m[1][0] * m[1][0] + m[1][1] * m[1][1];
            double lenZ = m[2][2];
            if (qFuzzyCompare(det, 1.0) && qFuzzyCompare(lenX, 1.0)
                    && qFuzzyCompare(lenY, 1.0) && qFuzzyCompare(lenZ, 1.0))
            {
                flagBits &= ~Scale;
            }
        }
    } else {
        // If the columns are orthonormal and form a right-handed system, then there is no scale.
        double det = matrixDet3(m, 0, 1, 2, 0, 1, 2);
        double lenX = m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2];
        double lenY = m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2];
        double lenZ = m[2][0] * m[2][0] + m[2][1] * m[2][1] + m[2][2] * m[2][2];
        if (qFuzzyCompare(det, 1.0) && qFuzzyCompare(lenX, 1.0)
                && qFuzzyCompare(lenY, 1.0) && qFuzzyCompare(lenZ, 1.0))
        {
            flagBits &= ~Scale;
        }
    }
}

#ifndef QT_NO_DEBUG_STREAM

QDebug operator<<(QDebug dbg, const QDoubleMatrix4x4 &m)
{
    QDebugStateSaver saver(dbg);
    // Create a string that represents the matrix type.
    QByteArray bits;
    if (m.flagBits == QDoubleMatrix4x4::Identity) {
        bits = "Identity";
    } else if (m.flagBits == QDoubleMatrix4x4::General) {
        bits = "General";
    } else {
        if ((m.flagBits & QDoubleMatrix4x4::Translation) != 0)
            bits += "Translation,";
        if ((m.flagBits & QDoubleMatrix4x4::Scale) != 0)
            bits += "Scale,";
        if ((m.flagBits & QDoubleMatrix4x4::Rotation2D) != 0)
            bits += "Rotation2D,";
        if ((m.flagBits & QDoubleMatrix4x4::Rotation) != 0)
            bits += "Rotation,";
        if ((m.flagBits & QDoubleMatrix4x4::Perspective) != 0)
            bits += "Perspective,";
        if (!bits.isEmpty())
            bits = bits.left(bits.size() - 1);
    }

    // Output in row-major order because it is more human-readable.
    dbg.nospace() << "QDoubleMatrix4x4(type:" << bits.constData() << Qt::endl
        << qSetFieldWidth(10)
        << m(0, 0) << m(0, 1) << m(0, 2) << m(0, 3) << Qt::endl
        << m(1, 0) << m(1, 1) << m(1, 2) << m(1, 3) << Qt::endl
        << m(2, 0) << m(2, 1) << m(2, 2) << m(2, 3) << Qt::endl
        << m(3, 0) << m(3, 1) << m(3, 2) << m(3, 3) << Qt::endl
        << qSetFieldWidth(0) << ')';
    return dbg;
}

#endif

#ifndef QT_NO_DATASTREAM

QDataStream &operator<<(QDataStream &stream, const QDoubleMatrix4x4 &matrix)
{
    for (int row = 0; row < 4; ++row)
        for (int col = 0; col < 4; ++col)
            stream << matrix(row, col);
    return stream;
}

QDataStream &operator>>(QDataStream &stream, QDoubleMatrix4x4 &matrix)
{
    double x;
    for (int row = 0; row < 4; ++row) {
        for (int col = 0; col < 4; ++col) {
            stream >> x;
            matrix(row, col) = x;
        }
    }
    matrix.optimize();
    return stream;
}

#endif // QT_NO_DATASTREAM

QT_END_NAMESPACE
