////////////////////////////////////////////////////////////////////////////////////////
//
//  Copyright 2026 OVITO GmbH, Germany
//
//  This file is part of OVITO (Open Visualization Tool).
//
//  OVITO is free software; you can redistribute it and/or modify it either under the
//  terms of the GNU General Public License version 3 as published by the Free Software
//  Foundation (the "GPL") or, at your option, under the terms of the MIT License.
//  If you do not alter this notice, a recipient may use your version of this
//  file under either the GPL or the MIT License.
//
//  You should have received a copy of the GPL along with this program in a
//  file LICENSE.GPL.txt.  You should have received a copy of the MIT License along
//  with this program in a file LICENSE.MIT.txt
//
//  This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
//  either express or implied. See the GPL or the MIT License for the specific language
//  governing rights and limitations.
//
////////////////////////////////////////////////////////////////////////////////////////

/**
 * \file
 * \brief Contains the definition of the Ovito::Matrix_4 class template.
 */

#pragma once


#include <ovito/core/Core.h>
#include <ovito/core/utilities/io/SaveStream.h>
#include <ovito/core/utilities/io/LoadStream.h>
#include "Vector3.h"
#include "Point3.h"
#include "Matrix3.h"
#include "Vector4.h"
#include "AffineTransformation.h"

namespace Ovito {

/**
 * \brief A 4x4 matrix.
 *
 * In contrast to the AffineTransformation matrix class this Matrix4 class
 * can describe perspective projections.
 */
template<typename T>
class Matrix_4 : public std::array<Vector_4<T>,4>
{
public:

    /// An empty type that denotes a 4x4 matrix with all elements equal to zero.
    struct Zero {};

    /// An empty type that denotes the 4x4 identity matrix.
    struct Identity {};

    /// The type of a single element of the matrix.
    typedef T element_type;

    /// The type of a single column of the matrix.
    typedef Vector_4<T> column_type;

    using typename std::array<Vector_4<T>, 4>::size_type;
    using typename std::array<Vector_4<T>, 4>::difference_type;
    using typename std::array<Vector_4<T>, 4>::value_type;
    using typename std::array<Vector_4<T>, 4>::iterator;
    using typename std::array<Vector_4<T>, 4>::const_iterator;

public:

    /// \brief Empty default constructor that does not initialize the matrix elements (for performance reasons).
    ///        The matrix elements will have an undefined value and need to be initialized later.
    Matrix_4() noexcept = default;

    /// \brief Constructor that initializes 9 elements of the matrix to the given values. All other elements are set to zero.
    /// \note Values are given in row-major order, i.e. row by row.
    constexpr Matrix_4(T m11, T m12, T m13,
                       T m21, T m22, T m23,
                       T m31, T m32, T m33) noexcept
        : std::array<Vector_4<T>,4>{{{m11,m21,m31,T(0)},{m12,m22,m32,T(0)},{m13,m23,m33,T(0)}, typename Vector_4<T>::Zero()}} {}

    /// \brief Constructor that initializes the 12 elements of the 3x4 submatrix to the given values.
    ///        All other elements are set to zero.
    /// \note Elements need to be specified in row-major order, i.e. row by row.
    constexpr Matrix_4(
                        T m11, T m12, T m13, T m14,
                        T m21, T m22, T m23, T m24,
                        T m31, T m32, T m33, T m34) noexcept
        : std::array<Vector_4<T>,4>{{
            Vector_4<T>(m11,m21,m31,T(0)),
            Vector_4<T>(m12,m22,m32,T(0)),
            Vector_4<T>(m13,m23,m33,T(0)),
            Vector_4<T>(m14,m24,m34,T(0))}} {}

    /// \brief Constructor that initializes 16 elements of the matrix to the given values.
    /// \note Elements need to be specified in row-major order, i.e. row by row.
    constexpr Matrix_4(
                        T m11, T m12, T m13, T m14,
                        T m21, T m22, T m23, T m24,
                        T m31, T m32, T m33, T m34,
                        T m41, T m42, T m43, T m44) noexcept
        : std::array<Vector_4<T>,4>{{
            Vector_4<T>(m11,m21,m31,m41),
            Vector_4<T>(m12,m22,m32,m42),
            Vector_4<T>(m13,m23,m33,m43),
            Vector_4<T>(m14,m24,m34,m44)}} {}

    /// \brief Initializes the 4x4 matrix from a 3x3 matrix.
    /// The lower matrix row and the right column are initialized to (0,0,0,1).
    constexpr explicit Matrix_4(const Matrix_3<T>& tm) noexcept
        : std::array<Vector_4<T>,4>{{
            Vector_4<T>(tm(0,0),tm(1,0),tm(2,0),T(0)),
            Vector_4<T>(tm(0,1),tm(1,1),tm(2,1),T(0)),
            Vector_4<T>(tm(0,2),tm(1,2),tm(2,2),T(0)),
            Vector_4<T>(   T(0),   T(0),   T(0),T(1))}} {}

    /// \brief Constructor that initializes the matrix from four column vectors.
    constexpr Matrix_4(const Vector_4<T>& c1, const Vector_4<T>& c2, const Vector_4<T>& c3, const Vector_4<T>& c4) noexcept
        : std::array<Vector_4<T>,4>{{c1, c2, c3, c4}} {}

    /// \brief Initializes the 4x4 matrix from a 3x4 matrix.
    /// The lower matrix row is initialized to (0,0,0,1).
    constexpr explicit Matrix_4(const AffineTransformationT<T>& tm) noexcept
        : std::array<Vector_4<T>,4>{{
            Vector_4<T>(tm(0,0),tm(1,0),tm(2,0),T(0)),
            Vector_4<T>(tm(0,1),tm(1,1),tm(2,1),T(0)),
            Vector_4<T>(tm(0,2),tm(1,2),tm(2,2),T(0)),
            Vector_4<T>(tm(0,3),tm(1,3),tm(2,3),T(1))}} {}

    /// \brief Constructor that initializes the top 3x4 submatrix from four column 3-vectors.
    /// The lower matrix row is initialized to (0,0,0,1).
    constexpr Matrix_4(const Vector_3<T>& c1, const Vector_3<T>& c2, const Vector_3<T>& c3, const Vector_3<T>& c4) noexcept
        : std::array<Vector_4<T>,4>{{
            Vector_4<T>(c1[0],c1[1],c1[2],T(0)),
            Vector_4<T>(c2[0],c2[1],c2[2],T(0)),
            Vector_4<T>(c3[0],c3[1],c3[2],T(0)),
            Vector_4<T>(c4[0],c4[1],c4[2],T(1))}} {}

    /// \brief Initializes the matrix to the null matrix.
    /// All matrix elements are set to zero by this constructor.
    constexpr Matrix_4(Zero) noexcept
        : std::array<Vector_4<T>,4>{{
            typename Vector_4<T>::Zero(),
            typename Vector_4<T>::Zero(),
            typename Vector_4<T>::Zero(),
            typename Vector_4<T>::Zero()}} {}

    /// \brief Initializes the matrix to the identity matrix.
    /// All diagonal elements are set to one, and all off-diagonal elements are set to zero.
    constexpr Matrix_4(Identity) noexcept
        : std::array<Vector_4<T>,4>{{
            Vector_4<T>(T(1),T(0),T(0),T(0)),
            Vector_4<T>(T(0),T(1),T(0),T(0)),
            Vector_4<T>(T(0),T(0),T(1),T(0)),
            Vector_4<T>(T(0),T(0),T(0),T(1))}} {}

    /// \brief Casts the matrix to a matrix with another data type.
    template<typename U>
    constexpr auto toDataType() const noexcept -> std::conditional_t<!std::is_same_v<T,U>, Matrix_4<U>, const Matrix_4<T>&> {
        if constexpr(!std::is_same_v<T,U>)
            return Matrix_4<U>(
                static_cast<U>((*this)(0,0)), static_cast<U>((*this)(0,1)), static_cast<U>((*this)(0,2)), static_cast<U>((*this)(0,3)),
                static_cast<U>((*this)(1,0)), static_cast<U>((*this)(1,1)), static_cast<U>((*this)(1,2)), static_cast<U>((*this)(1,3)),
                static_cast<U>((*this)(2,0)), static_cast<U>((*this)(2,1)), static_cast<U>((*this)(2,2)), static_cast<U>((*this)(2,3)),
                static_cast<U>((*this)(3,0)), static_cast<U>((*this)(3,1)), static_cast<U>((*this)(3,2)), static_cast<U>((*this)(3,3)));
        else
            return *this;  // When casting to the same type \a T, this method becomes a no-op.
    }

    /// \brief Returns the number of rows of this matrix.
    static constexpr size_type row_count() { return 4; }

    /// \brief Returns the number of columns of this matrix.
    static constexpr size_type col_count() { return 4; }

    /// \brief Returns the value of a matrix element.
    /// \param row The row of the element to return.
    /// \param col The column of the element to return.
    /// \return The value of the matrix element.
    inline constexpr T operator()(size_type row, size_type col) const {
        return (*this)[col][row];
    }

    /// \brief Returns a reference to a matrix element.
    /// \param row The row of the element to return.
    /// \param col The column of the element to return.
    inline constexpr T& operator()(size_type row, size_type col) {
        return (*this)[col][row];
    }

    /// \brief Returns a column vector in the matrix.
    /// \param col The index of the column to return.
    /// \return The i-th column of the matrix as a vector.
    inline constexpr const column_type& column(size_type col) const {
        return (*this)[col];
    }

    /// \brief Returns a reference to a column vector of the matrix.
    /// \param col The column to return.
    /// \return The i-th column of the matrix as a vector reference. Modifying the vector modifies the matrix.
    inline constexpr column_type& column(size_type col) {
        return (*this)[col];
    }

    /// \brief Returns a row from the matrix.
    /// \param row The row to return.
    /// \return The i-th row of the matrix as a vector.
    constexpr Vector_4<T> row(size_type row) const {
        return { (*this)[0][row], (*this)[1][row], (*this)[2][row], (*this)[3][row] };
    }

    /// \brief Replaces a row of the matrix.
    /// \param row The row to replace.
    /// \param v The new row vector.
    constexpr void setRow(size_type row, const Vector_4<T>& v) {
        (*this)[0][row] = v[0];
        (*this)[1][row] = v[1];
        (*this)[2][row] = v[2];
        (*this)[3][row] = v[3];
    }

    /// Returns a pointer to the 16 elements of the matrix (stored in column-major order).
    constexpr const element_type* elements() const {
        OVITO_STATIC_ASSERT(sizeof(*this) == sizeof(element_type)*16);
        return column(0).data();
    }

    /// Returns a pointer to the 16 elements of the matrix (stored in column-major order).
    constexpr element_type* elements() {
        OVITO_STATIC_ASSERT(sizeof(*this) == sizeof(element_type)*16);
        return column(0).data();
    }

    /// \brief Sets all components of the matrix to zero.
    constexpr void setZero() {
        for(size_type i = 0; i < col_count(); i++)
            column(i).setZero();
    }

    /// \brief Sets all components of the matrix to zero.
    constexpr Matrix_4& operator=(Zero) {
        setZero();
        return *this;
    }

    /// \brief Sets the matrix to the identity matrix.
    constexpr void setIdentity() {
        (*this)[0][0] = T(1); (*this)[0][1] = T(0); (*this)[0][2] = T(0); (*this)[0][3] = T(0);
        (*this)[1][0] = T(0); (*this)[1][1] = T(1); (*this)[1][2] = T(0); (*this)[1][3] = T(0);
        (*this)[2][0] = T(0); (*this)[2][1] = T(0); (*this)[2][2] = T(1); (*this)[2][3] = T(0);
        (*this)[3][0] = T(0); (*this)[3][1] = T(0); (*this)[3][2] = T(0); (*this)[3][3] = T(1);
    }

    /// \brief Sets the matrix to the identity matrix.
    constexpr Matrix_4& operator=(Identity) {
        setIdentity();
        return *this;
    }

    ////////////////////////////////// Comparison ///////////////////////////////////

    /// \brief Tests if two matrices are equal within a given tolerance.
    /// \param m The matrix to compare to.
    /// \param tolerance A non-negative threshold for the equality test. The two matrices are considered equal if
    ///        the element-wise differences are all less than this tolerance value.
    /// \return \c true if this matrix is equal to \a m within the given tolerance; \c false otherwise.
    inline constexpr bool equals(const Matrix_4& m, T tolerance = Ovito::epsilon_v<T>) const
    {
        for(size_type i = 0; i < col_count(); i++)
            if(!column(i).equals(m.column(i), tolerance)) return false;
        return true;
    }

    /// \brief Test if the matrix is zero within a given tolerance.
    /// \param tolerance A non-negative threshold.
    /// \return \c true if the absolute value of each matrix element is all smaller than \a tolerance.
    inline constexpr bool isZero(T tolerance = Ovito::epsilon_v<T>) const
    {
        for(size_type i = 0; i < col_count(); i++)
            if(!column(i).isZero(tolerance)) return false;
        return true;
    }

    ////////////////////////////////// Computations ///////////////////////////////////

    /// \brief Computes the determinant of the matrix.
    constexpr inline T determinant() const {
        return ((*this)[0][3] * (*this)[1][2] * (*this)[2][1] * (*this)[3][0]-(*this)[0][2] * (*this)[1][3] * (*this)[2][1] * (*this)[3][0]-(*this)[0][3] * (*this)[1][1] * (*this)[2][2] * (*this)[3][0]+(*this)[0][1] * (*this)[1][3] * (*this)[2][2] * (*this)[3][0]+
                (*this)[0][2] * (*this)[1][1] * (*this)[2][3] * (*this)[3][0]-(*this)[0][1] * (*this)[1][2] * (*this)[2][3] * (*this)[3][0]-(*this)[0][3] * (*this)[1][2] * (*this)[2][0] * (*this)[3][1]+(*this)[0][2] * (*this)[1][3] * (*this)[2][0] * (*this)[3][1]+
                (*this)[0][3] * (*this)[1][0] * (*this)[2][2] * (*this)[3][1]-(*this)[0][0] * (*this)[1][3] * (*this)[2][2] * (*this)[3][1]-(*this)[0][2] * (*this)[1][0] * (*this)[2][3] * (*this)[3][1]+(*this)[0][0] * (*this)[1][2] * (*this)[2][3] * (*this)[3][1]+
                (*this)[0][3] * (*this)[1][1] * (*this)[2][0] * (*this)[3][2]-(*this)[0][1] * (*this)[1][3] * (*this)[2][0] * (*this)[3][2]-(*this)[0][3] * (*this)[1][0] * (*this)[2][1] * (*this)[3][2]+(*this)[0][0] * (*this)[1][3] * (*this)[2][1] * (*this)[3][2]+
                (*this)[0][1] * (*this)[1][0] * (*this)[2][3] * (*this)[3][2]-(*this)[0][0] * (*this)[1][1] * (*this)[2][3] * (*this)[3][2]-(*this)[0][2] * (*this)[1][1] * (*this)[2][0] * (*this)[3][3]+(*this)[0][1] * (*this)[1][2] * (*this)[2][0] * (*this)[3][3]+
                (*this)[0][2] * (*this)[1][0] * (*this)[2][1] * (*this)[3][3]-(*this)[0][0] * (*this)[1][2] * (*this)[2][1] * (*this)[3][3]-(*this)[0][1] * (*this)[1][0] * (*this)[2][2] * (*this)[3][3]+(*this)[0][0] * (*this)[1][1] * (*this)[2][2] * (*this)[3][3]);
    }

    /// \brief Computes the inverse of the matrix.
    /// \throw Exception if matrix is not invertible because it is singular.
    constexpr Matrix_4 inverse() const {

        T det = determinant();
        OVITO_ASSERT_MSG(det != T(0), "Matrix4::inverse()", "Singular matrix cannot be inverted: Determinant is zero.");
        if(det == T(0))
            throw Exception("Matrix4 cannot be inverted: determinant is zero.");

        // Assign to individual variable names to aid
        // selecting correct values.
        const T a1 = (*this)[0][0]; const T b1 = (*this)[0][1];
        const T c1 = (*this)[0][2]; const T d1 = (*this)[0][3];
        const T a2 = (*this)[1][0]; const T b2 = (*this)[1][1];
        const T c2 = (*this)[1][2]; const T d2 = (*this)[1][3];
        const T a3 = (*this)[2][0]; const T b3 = (*this)[2][1];
        const T c3 = (*this)[2][2]; const T d3 = (*this)[2][3];
        const T a4 = (*this)[3][0]; const T b4 = (*this)[3][1];
        const T c4 = (*this)[3][2]; const T d4 = (*this)[3][3];

        return Matrix_4(
                det3x3( b2, b3, b4, c2, c3, c4, d2, d3, d4) / det,
              - det3x3( a2, a3, a4, c2, c3, c4, d2, d3, d4) / det,
                det3x3( a2, a3, a4, b2, b3, b4, d2, d3, d4) / det,
              - det3x3( a2, a3, a4, b2, b3, b4, c2, c3, c4) / det,

              - det3x3( b1, b3, b4, c1, c3, c4, d1, d3, d4) / det,
                det3x3( a1, a3, a4, c1, c3, c4, d1, d3, d4) / det,
              - det3x3( a1, a3, a4, b1, b3, b4, d1, d3, d4) / det,
                det3x3( a1, a3, a4, b1, b3, b4, c1, c3, c4) / det,

                det3x3( b1, b2, b4, c1, c2, c4, d1, d2, d4) / det,
              - det3x3( a1, a2, a4, c1, c2, c4, d1, d2, d4) / det,
                det3x3( a1, a2, a4, b1, b2, b4, d1, d2, d4) / det,
              - det3x3( a1, a2, a4, b1, b2, b4, c1, c2, c4) / det,

              - det3x3( b1, b2, b3, c1, c2, c3, d1, d2, d3) / det,
                det3x3( a1, a2, a3, c1, c2, c3, d1, d2, d3) / det,
              - det3x3( a1, a2, a3, b1, b2, b3, d1, d2, d3) / det,
                det3x3( a1, a2, a3, b1, b2, b3, c1, c2, c3) / det);
    }

    /// \brief Converts this matrix to a Qt 4x4 matrix object.
    operator QMatrix4x4() const {
        return QMatrix4x4(
                (*this)(0,0), (*this)(0,1), (*this)(0,2), (*this)(0,3),
                (*this)(1,0), (*this)(1,1), (*this)(1,2), (*this)(1,3),
                (*this)(2,0), (*this)(2,1), (*this)(2,2), (*this)(2,3),
                (*this)(3,0), (*this)(3,1), (*this)(3,2), (*this)(3,3));
    }

    ///////////////////////////// Generation //////////////////////////////////

    /// \brief Generates a translation matrix.
    static constexpr Matrix_4<T> translation(const Vector_3<T>& t) {
        return { T(1), T(0), T(0), t.x(),
                 T(0), T(1), T(0), t.y(),
                 T(0), T(0), T(1), t.z(),
                 T(0), T(0), T(0), T(1) };
    }

    /// \brief Generates a perspective projection matrix.
    static constexpr Matrix_4<T> perspective(T fovy, T aspect, T znear, T zfar) {
        T f = tan(fovy * T(0.5));
        OVITO_ASSERT(f != T(0));
        OVITO_ASSERT(zfar > znear);
        return { T(1)/(aspect*f), T(0), T(0), T(0),
                 T(0), T(1)/f, T(0), T(0),
                 T(0), T(0), -(zfar+znear)/(zfar-znear), -(T(2)*zfar*znear)/(zfar-znear),
                 T(0), T(0), T(-1), T(0) };
    }

    /// \brief Generates an orthogonal projection matrix.
    static constexpr Matrix_4<T> ortho(T left, T right, T bottom, T top, T znear, T zfar) {
        OVITO_ASSERT(znear < zfar);
        OVITO_ASSERT(right != left);
        OVITO_ASSERT(top != bottom);
        return { T(2)/(right-left), T(0),  T(0), -(right+left)/(right-left),
                 T(0), T(2)/(top-bottom), T(0), -(top+bottom)/(top-bottom),
                 T(0), T(0), T(-2)/(zfar-znear), -(zfar+znear)/(zfar-znear),
                 T(0), T(0), T(0), T(1) };
    }

    /// \brief Generates a perspective projection matrix.
    static constexpr Matrix_4<T> frustum(T left, T right, T bottom, T top, T znear, T zfar) {
        OVITO_ASSERT(znear < zfar);
        OVITO_ASSERT(right != left);
        OVITO_ASSERT(top != bottom);
        return { T(2)*znear/(right-left), T(0),  (right + left) / (right - left), T(0),
                 T(0), T(2)*znear/(top-bottom), (top + bottom) / (top - bottom), T(0),
                 T(0), T(0), -(zfar + znear) / (zfar - znear), -(T(2)*zfar*znear)/(zfar - znear),
                 T(0), T(0), T(-1), T(0) };
    }


private:

    // Computes the determinant of a 2x2 sub-matrix. This is for internal use only.
    static constexpr inline T det2x2(T a, T b, T c, T d) { return (a * d - b * c); }

    // Computes the determinant of a 3x3 sub-matrix. This is for internal use only.
    static constexpr inline T det3x3(T a1, T a2, T a3, T b1, T b2, T b3, T c1, T c2, T c3) {
        return (a1 * det2x2( b2, b3, c2, c3 )
            - b1 * det2x2( a2, a3, c2, c3 )
            + c1 * det2x2( a2, a3, b2, b3 ));
    }

};

/// \brief Computes the product of a 4x4 matrix and a Vector4.
/// \relates Matrix_4
template<typename T>
constexpr inline Vector_4<T> operator*(const Matrix_4<T>& a, const Vector_4<T>& v)
{
    return {
        a(0,0)*v[0] + a(0,1)*v[1] + a(0,2)*v[2] + a(0,3)*v[3],
        a(1,0)*v[0] + a(1,1)*v[1] + a(1,2)*v[2] + a(1,3)*v[3],
        a(2,0)*v[0] + a(2,1)*v[1] + a(2,2)*v[2] + a(2,3)*v[3],
        a(3,0)*v[0] + a(3,1)*v[1] + a(3,2)*v[2] + a(3,3)*v[3]
    };
}

/// \brief Computes the product of a 4x4 matrix and a Vector3 (which is assumed to be a 4-vector with the last element equal to 0).
/// \relates Matrix_4
template<typename T>
constexpr inline Vector_3<T> operator*(const Matrix_4<T>& a, const Vector_3<T>& v)
{
    const T s = a(3,0)*v[0] + a(3,1)*v[1] + a(3,2)*v[2] + a(3,3);
    OVITO_ASSERT(s != 0);
    return {
        (a(0,0)*v[0] + a(0,1)*v[1] + a(0,2)*v[2]) / s,
        (a(1,0)*v[0] + a(1,1)*v[1] + a(1,2)*v[2]) / s,
        (a(2,0)*v[0] + a(2,1)*v[1] + a(2,2)*v[2]) / s
    };
}

/// \brief Computes the product of a 4x4 matrix and a Point3 (which is assumed to be a 4-vector with the last element equal to 1).
/// \relates Matrix_4
template<typename T>
constexpr inline Point_3<T> operator*(const Matrix_4<T>& a, const Point_3<T>& v)
{
    const T s = a(3,0)*v[0] + a(3,1)*v[1] + a(3,2)*v[2] + a(3,3);
    OVITO_ASSERT(s != 0);
    return {
        (a(0,0)*v[0] + a(0,1)*v[1] + a(0,2)*v[2] + a(0,3)) / s,
        (a(1,0)*v[0] + a(1,1)*v[1] + a(1,2)*v[2] + a(1,3)) / s,
        (a(2,0)*v[0] + a(2,1)*v[1] + a(2,2)*v[2] + a(2,3)) / s
    };
}

/// \brief Computes the product of a 4x4 matrix and the origin Point3 (which is assumed to be a 4-vector with the last element equal to 1).
/// \relates Matrix_4
template<typename T>
constexpr inline Point_3<T> operator*(const Matrix_4<T>& a, typename Point_3<T>::Origin o)
{
    const T s = a(3,3);
    OVITO_ASSERT(s != 0);
    return {
        a(0,3) / s,
        a(1,3) / s,
        a(2,3) / s
    };
}

/// Computes the product of two 4x4 matrices.
/// \relates Matrix_4
template<typename T>
constexpr inline Matrix_4<T> operator*(const Matrix_4<T>& a, const Matrix_4<T>& b)
{
    Matrix_4<T> res;
    for(typename Matrix_4<T>::size_type i = 0; i < 4; i++) {
        for(typename Matrix_4<T>::size_type j = 0; j < 4; j++) {
            res(i,j) = a(i,0)*b(0,j) + a(i,1)*b(1,j) + a(i,2)*b(2,j) + a(i,3)*b(3,j);
        }
    }
    return res;
}

/// Computes the product of a 4x4 matrix and a 3x4 Matrix.
/// \relates Matrix_4
template<typename T>
constexpr inline Matrix_4<T> operator*(const Matrix_4<T>& a, const AffineTransformationT<T>& b)
{
    Matrix_4<T> res;
    for(typename Matrix_4<T>::size_type i = 0; i < 4; i++) {
        for(typename Matrix_4<T>::size_type j = 0; j < 3; j++) {
            res(i,j) = a(i,0)*b(0,j) + a(i,1)*b(1,j) + a(i,2)*b(2,j);
        }
        res(i,3) = a(i,0)*b(0,3) + a(i,1)*b(1,3) + a(i,2)*b(2,3) + a(i,3);
    }
    return res;
}

/// Multiplies a 4x4 matrix with a scalar.
/// \relates Matrix_4
template<typename T>
constexpr inline Matrix_4<T> operator*(const Matrix_4<T>& a, T s)
{
    return { a.column(0)*s, a.column(1)*s, a.column(2)*s, a.column(3)*s };
}

/// Multiplies a 4x4 matrix with a scalar.
/// \relates Matrix_4
template<typename T>
constexpr inline Matrix_4<T> operator*(T s, const Matrix_4<T>& a)
{
    return a * s;
}

/// Prints a matrix to an output stream.
/// \relates Matrix_4
template<typename T>
inline std::ostream& operator<<(std::ostream &os, const Matrix_4<T>& m) {
    for(typename Matrix_4<T>::size_type row = 0; row < m.row_count(); row++)
        os << m.row(row) << std::endl;
    return os;
}

/// \brief Prints a matrix to a Qt debug stream.
/// \relates Matrix_4
template<typename T>
inline QDebug operator<<(QDebug dbg, const Matrix_4<T>& m) {
    for(typename Matrix_4<T>::size_type row = 0; row < m.row_count(); row++)
        dbg.nospace() << m(row,0) << " " << m(row,1) << " " << m(row,2) << " " << m(row,3) << "\n";
    return dbg.space();
}

/// \brief Writes a matrix to a binary output stream.
/// \relates Matrix_4
template<typename T>
inline SaveStream& operator<<(SaveStream& stream, const Matrix_4<T>& m)
{
    for(typename Matrix_4<T>::size_type col = 0; col < m.col_count(); col++)
        stream << m.col(col);
    return stream;
}

/// \brief Reads a matrix from a binary input stream.
/// \relates Matrix_4
template<typename T>
inline LoadStream& operator>>(LoadStream& stream, Matrix_4<T>& m)
{
    for(typename Matrix_4<T>::size_type col = 0; col < m.col_count(); col++)
        stream >> m.col(col);
    return stream;
}

/// \brief Writes a matrix to a Qt data stream.
/// \relates Matrix_4
template<typename T>
inline QDataStream& operator<<(QDataStream& stream, const Matrix_4<T>& m) {
    for(typename Matrix_4<T>::size_type col = 0; col < m.col_count(); col++)
        stream << m.column(col);
    return stream;
}

/// \brief Reads a matrix from a Qt data stream.
/// \relates Matrix_4
template<typename T>
inline QDataStream& operator>>(QDataStream& stream, Matrix_4<T>& m) {
    for(typename Matrix_4<T>::size_type col = 0; col < m.col_count(); col++)
        stream >> m.column(col);
    return stream;
}

/**
 * \brief Instantiation of the Matrix_4 class template with the default floating-point type (double precision).
 * \relates Matrix_4
 */
using Matrix4 = Matrix_4<FloatType>;

/**
 * \brief Instantiation of the Matrix_4 class template with the single-precision floating-point type.
 * \relates Matrix_4
 */
using Matrix4F = Matrix_4<float>;

/**
 * \brief Instantiation of the Matrix_4 class template with the low-precision floating-point type used for graphics data.
 * \relates Matrix_4
 */
using Matrix4G = Matrix_4<GraphicsFloatType>;

}   // End of namespace

Q_DECLARE_METATYPE(Ovito::Matrix4);
Q_DECLARE_METATYPE(Ovito::Matrix4F);
Q_DECLARE_TYPEINFO(Ovito::Matrix4, Q_PRIMITIVE_TYPE);
Q_DECLARE_TYPEINFO(Ovito::Matrix4F, Q_PRIMITIVE_TYPE);
