von Matrix Klasse erben



  • Hallo,
    Ich habe eine Matrix Klasse, die ich als Basisklasse haben wollte.

    #pragma once
    #include "Array2D_T.h"
    
    namespace zp
    {
    	template<typename T>
    	class Matrix2D
    	{
    	protected:
    		zp::Array2D<T> Matrix;
    
    	public:
    		inline Matrix2D() = default;
    		inline virtual ~Matrix2D() = default;
    
    		inline Matrix2D(const int rows, const int columns, const T v = 0)
    			: Matrix(rows, columns, v) {}
    
    		inline const zp::Array2D<T>& matrix() const { return Matrix; }
    		inline zp::Array2D<T>& matrix() { return Matrix; }
    		
    		inline const Matrix2D& operator * (const Matrix2D& mT) const
    		{
    			try
    			{
    				if (Matrix.columns() != mT.Matrix.rows())
    					throw std::exception("zp::Matrix2D::operator * (Matrix2D)");
    			}
    			catch (const std::exception& err)
    			{
    				std::cerr << "\nerror: " << err.what() << std::endl;
    				return *this;
    			}
    
    			static Matrix2D nmT(Matrix.rows(), mT.Matrix.columns());
    			T v = 0;
    			for (auto c = 0; c < mT.Matrix.columns(); ++c)
    			{
    				for (auto r = 0; r < Matrix.rows(); ++r)
    				{
    					v = this->Matrix(r, 0) * mT.Matrix(0, c);
    					for (auto l = 1; l < Matrix.columns(); ++l)
    					{
    						v += this->Matrix(r, l) * mT.Matrix(l, c);
    					}
    					nmT.Matrix(r, c) = v;
    				}
    				v = 0;
    			}
    			return nmT;
    		}
    
    		inline const Matrix2D& operator + (const T v) const
    		{
    			static Matrix2D nmT(Matrix.rows(), Matrix.columns());
    
    			for (auto r = 0; r < Matrix.rows(); ++r)
    				for (auto c = 0; c < Matrix.columns(); ++c)
    					nmT.Matrix(r, c) = this->Matrix(r, c) + v;
    
    			return nmT;
    		}
    
                   //
                   //  weitere Rechenoperationen...
                   //
             };
    }
    

    Nun habe ich eine Klasse zB Mat3x3<T> die davon erben soll

    #pragma once
    #include "Matrix2D_T.h"
    
    namespace zp
    {
    	template<typename T>
    	class Mat3x3 : public Matrix2D<T>
    	{
    	public:
    		inline Mat3x3() : Matrix2D(3, 3) { makeIdentity(); }
    
    	private:
    		inline void makeIdentity()
    		{
    			this->Matrix(0, 0) = 1;
    			this->Matrix(1, 1) = 1;
    			this->Matrix(2, 2) = 1;
    		}
    	};
    }
    

    Da bekomme ich folgende Fehlermeldung bei den Rechenoperationen:
    Error (active) E0312 no suitable user-defined conversion from "const zp::Matrix2D<float>" to "zp::Mat3x3<float>" exists.

    Jetzt habe ich die Klasse Mat3x3<T> als eigenständige Klasse geschrieben

    #pragma once
    #include "Matrix2D_T.h"
    
    namespace zp
    {
    	template<typename T>
    	class Mat3x3
    	{
    		Matrix2D<T> Matrix;
    
    	public:
    		inline Mat3x3() : Matrix(3, 3) { makeIdentity(); }
    
    		inline const Matrix2D<T>& mat() const { return Matrix; }
    		inline Matrix2D<T>& mat() { return Matrix; }
    		
    	private:
    		inline void makeIdentity()
    		{
    			Matrix.matrix()(0, 0) = 1;
    			Matrix.matrix()(1, 1) = 1;
    			Matrix.matrix()(2, 2) = 1;
    		}
    	};
    }
    
    

    Geht dann, aber die Aufrufe sehen ziemlich wild aus mat3x3.mat().matrix().

    Also wieso klappt die Vererbung bei mir nicht? Oder was wäre da besser?



  • Wie rufst du denn die Operatoren auf? Bedenke, daß du bisher const Matrix2D& zurückgibst.
    Du solltest auch eine Kopie zurückgeben und nicht die Referenz auf eine statische Instanz!

    Wenn du jedoch den Datentyp der abgeleiteten Klasse zurückgeben möchtest, dann mußt du diese selbst als Template-Parameter der Basisklasse mitgeben: Curiously recurring template pattern (CRTP) (wie es z.B. auch Boost operators macht)

    PS: Das inline bei den Memberfunktionen ist überflüssig - das ist automatisch so.



  • @Th69 sagte in von Matrix Klasse erben:

    Wenn du jedoch den Datentyp der abgeleiteten Klasse zurückgeben möchtest,

    Kann ich wegen dem "jedoch" die Antwort so deuten, das es zwei Möglichkeiten gibt? Kopie zurückgeben oder CRTP? Und wie meinst Du "Operatoren aufrufen"?. Bezieht sich das auf dies zB:

    zp::Mat3x3<float> mat1;
    zp::Mat3x3<float> mat2;
    zp::Mat3x3<float> result = mat1 * mat2;
    

    ? Wobei dann in Zeile 3 der Fehler auftritt.

    PS: Das inline bei den Memberfunktionen ist überflüssig - das ist automatisch so.

    Ok, ich dachte bei Template Funktionen sollte man das trotzdem setzen. Aber ist ja kein Ding.



  • Genau diesen Code meinte ich:

    zp::Mat3x3<float> result = mat1 * mat2;
    

    Der *-Operator erzeugt ein Basis Matrix2D<float> Objekt (bzw. in deinem Code eine konstante Referenz darauf), du willst es aber der abgeleiteten Klasse Mat3x3<float> zuweisen und das geht nun mal nicht direkt (daher die entsprechende Fehlermeldung und mein Hinweis auf CRTP).

    Benötigst du denn überhaupt die Polymorphie (willst du also verschiedene Klassen von Matrix2D<T> ableiten und dann verschiedene Objekte davon gemeinsam verwalten)?
    Ansonsten solltest du einfach Rows und Columns als weitere Template-Parameter der Matrix2D<T> Klasse mitgeben.
    Und zur einfachen Benutzung dann entsprechende Type-Aliase anlegen:

    typedef Matrix2D<float, 3, 3> Mat3x3;
    // bzw.
    using Mat3x3 = Matrix2D<float, 3, 3>;
    


  • eventuell wäre es auch nicht schlecht wenn du dir mal anschaust wie es andere machen.
    z.b. glm (https://github.com/g-truc/glm)



  • Genau wie ich schrieb, es wird jeweils ein typedef erzeugt, z.B. glm/ext/matrix_float3x3.hpp.



  • @Th69 sagte in von Matrix Klasse erben:

    Ansonsten solltest du einfach Rows und Columns als weitere Template-Parameter der Matrix2D<T> Klasse mitgeben.
    Und zur einfachen Benutzung dann entsprechende Type-Aliase anlegen:

    typedef Matrix2D<float, 3, 3> Mat3x3;
    // bzw.
    using Mat3x3 = Matrix2D<float, 3, 3>;
    
    template <typename T>
    using Mat3x3 = zp::Matrix2D<T>(3, 3);
    

    Hm, das bekomme ich to many arguments for class template zp::Matrix2D.
    Aber ich hatte das ähnlich, und zwar

    template <typename T>
    using Mat3x3 = zp::Matrix2D<T>(3, 3);
    

    Muss ich jetzt mal überprüfen, warum ich das nicht weiter verfolgt habe. Danke erstmal.
    Ich muss mir dann noch überlegen, wie ich die Methode makeIdentity() unterbringe ohne sie jedesmal händisch aufzurufen.

    Im Konstruktor von Matrix2D ist sie ja unpassend.



  • Ich schrieb Template-Parameter, also

    template<typename T, size_t Rows, size_t Columns>
    class Matrix2D
    

    (evtl. noch mit Default-Werten, also z.B. size_t Columns = 1)

    Die Methode makeIdentity() kann ja mit einer Schleife allgemein geschrieben werden (wobei bei Rows != Columns es eigentlich keine Einheitsmatrix gibt).



  • @Th69 sagte in von Matrix Klasse erben:

    Ich schrieb Template-Parameter, also

    template<typename T, size_t Rows, size_t Columns>
    class Matrix2D
    

    Ah ok, da habe ich wieder nicht lange genug nachgedacht, sondern sofort geantwortet. Das werde ich jetzt nicht so machen, sondern über Deinen zweiten Hinweis erst mal nachdenken. Brauche immer etwas länger.



  • @Th69 sagte in von Matrix Klasse erben:

    (wobei bei Rows != Columns es eigentlich keine Einheitsmatrix gibt).

    Genau, aber bei der Multiplikation muss beachtet werden

    Die Matrizenmultiplikation oder Matrixmultiplikation ist in der Mathematik eine multiplikative
    Verknüpfung von Matrizen. Um zwei Matrizen miteinander multiplizieren zu können, muss die
    Spaltenzahl der ersten Matrix mit der Zeilenzahl der zweiten Matrix übereinstimmen.

    Deshalb will ich diese Methode auch nur bei quadratischen Matrizen haben und nicht im Konstruktor von Matrix2D.

    Ich weiß, das ist Dir klar, aber ich habe geantwortet.



  • Wäre es nicht besser eine statische Klassenmethode zu schreiben, die einem eine Identity Matrix gibt? Also quasi eine Factory Methode.

    Siehe z.B. https://numpy.org/doc/stable/reference/generated/numpy.identity.html

    Finde ich einfach praktischer als wenn der Konstruktor das macht. Da weiß man wenigstens direkt was man bekommt. Und dadurch das du nur eine Dimension angibst, hast du dann auch automatisch deine quadratische Matrix



  • @zeropage sagte in von Matrix Klasse erben:

    Deshalb will ich diese Methode auch nur bei quadratischen Matrizen haben und nicht im Konstruktor von Matrix2D.

    Um nochmal auf dein ursprüngliches Problem zu kommen: Dir ist bewusst, dass Mat3x3 Konvertierungs- und Zusweisungsoperatoren für Matrix2D-Typen braucht, damit der Fehler nicht auftritt? Die werden nämlich nicht automatisch erzeugt, sondern- nur die für den Mat3x3-Typ selbst. Das funktioniert nicht, da eine Matrix2D nicht mit dem Mat3x3(const Mat3x3&) Default-Kopierkonstruktor bzw. der operator=(const Mat3x3&) Default-Zuweisung aufgerufen werden kann.

    Wenn du die Dimensionen in Template-Parameter packen willst, stellt sich die Frage, ob das eine dynamische Matrix-Klasse werden soll, bei der die Dimensionen zur Laufzeit festgelegt werden, oder ob sie statisch sein soll - d.h. Zeilen- und Spaltenzahl sind zur Compile-Zeit bekannt. Für eine dynamische Matrix-Klasse ist dein ursprünglicher Ansatz gar nicht so verkehrt, du brauchts halt die Konvertierungs-Operatoren, damit das funktioniert.

    Wenn das eine statische Matrix werden soll, also Zeilen- und Spaltenzahl als Template-Parameter, dann kann man die zusätzlichen Funktionen wie identity() z.B. zur Compilezeit "hinzuschalten". enable_if ist ein Ansatz, ich hab das allerdings mal mit Mixin-Basisklassen gelöst, was ungefähr so aussah (grober Prototyp mit etwas redundantem Code):

    #include <iostream>
    
    template <typename T, int M, int N>
    class Matrix;
    
    template <typename T, int M, int N, bool IS_VECTOR = (M == 1 || N == 1)>
    struct MatrixAccessorsBase
    {
        auto& operator()(int i, int j) const
        {
            return static_cast<const Matrix<T, M, N>*>(this)->elements[i * N + j];
        }
        
        auto& operator()(int i, int j)
        {
            return static_cast<Matrix<T, M, N>*>(this)->elements[i * N + j];
        }    
    };
    
    template <typename T, int M, int N>
    struct MatrixAccessorsBase<T, M, N, true>
    {
        auto& operator()(int i) const
        {
            return static_cast<const Matrix<T, M, N>*>(this)->elements[i];
        }
    
        auto& operator()(int i)
        {
            return static_cast<Matrix<T, M, N>*>(this)->elements[i];
        }
    
        auto& operator()(int i, int j) const
        {
            return static_cast<const Matrix<T, M, N>*>(this)->elements[i * N + j];
        }    
        
        auto& operator()(int i, int j)
        {
            return static_cast<Matrix<T, M, N>*>(this)->elements[i * N + j];
        }    
    };
    
    template <typename T, int M, int N, bool IS_QUADRATIC = (M == N)>
    struct QuadraticMatrixBase
    {
    };
    
    template <typename T, int M, int N>
    struct QuadraticMatrixBase<T, M, N, true>
    {
        static const auto& identity()
        {
            static auto m = []{
                Matrix<T, M, N> m;
                for (int i = 0; i < M; ++i)
                    m(i, i) = 1;
                return m;
            }();
            return m;
        }
    };
    
    template <typename T, int M, int N>
    struct Matrix : 
        MatrixAccessorsBase<T, M, N>,
        QuadraticMatrixBase<T, M, N>
    {
        T elements[M * N] = {}; 
    };
    
    template <typename T, int M, int N>
    auto operator<<(std::ostream& out, const Matrix<T, M, N>& m) -> std::ostream&
    {
        if (M > 1 || N > 1)
            out << "{ ";
        for (int i = 0; i < M; ++i)
        {
            if (i > 0)
                out << ", ";
            if (N > 1)
                out << "{ ";
            out << m(i, 0);
            for (int j = 1; j < N; ++j)
                out << ", " << m(i, j);
            if (N > 1)
                out << " }";
        }
        if (M > 1 || N > 1)
            out << " }";
        return out;
    }
    
    template <typename T, int N>
    using Vector = Matrix<T, N, 1>;
    
    template <typename T, int N>
    using QuadraticMatrix = Matrix<T, N, N>;
    
    auto main() -> int
    {
        Matrix<float, 2, 3> matrix;
        Vector<float, 3> vector;
    
        matrix(0, 2) = 42;
        matrix(1, 2) = 13;
        vector(2) = 7;
    
        std::cout << matrix << std::endl;
        std::cout << vector << std::endl;
        std::cout << QuadraticMatrix<float, 3>::identity() << std::endl;
    }
    

    https://godbolt.org/z/Ec8z3bEj5

    Vielleicht ist das ja auch für dich ein brauchbarer Ansatz 😉



  • @Leon0402
    also solch eine Methode:

    	        static Matrix2D Identity(const int size)
    		{
    			Matrix2D unit(size, size);
    			for (auto i = 0; i < size; ++i)
    				unit.matrix()(i, i) = 1;
    
    			return unit;
    		}
    

    @Finnegan
    Ok danke, so wie ich das sehe, ist Deine MatrixAccessorsBase mein Array2D<T>. Die Zugriffsoperatoren habe ich aslo schon, ich brauch nur noch die Rechenoperationen, die ich in Matrix2D<T>habe.

    Und ich hätte schon gerne jede spezielle Matrix als eigenen Typ, wo ich aber nicht jedesmal alle Rechenoperationen implementieren muss. Deshalb kam ich auf Vererbung.

    Also schlußendlich möchte ich mit den Matrizen so umgehen, wie in diesen Funktionen:

            template<typename T>
    	inline static zp::Mat3x3<T> makeTranslationMatrix(const zp::Vector2D<T>& translate)
    	{
    		zp::Mat3x3<T> mat;
    		mat.mat().matrix()(2, 0) = translate.x; // ist hier noch meinen Versuchen geschuldet
    		mat.mat().matrix()(2, 1) = translate.y; // sollte so aussehen: mat.matrix()(2, 1) = translate.y;
    		return mat;
    	}
    

    oder

                    inline void transform(VectorObject2D<T>& shape)
    		{
    			const Mat3x3<T> transformMat = calculateMatrix();
    			std::vector<Vector2D<T>> points;
    			for (const auto& point : shape.vertices())
    				points.push_back(point * transformMat);
    
    			shape.getAttributesFrom(points);
    		}
    

    So hatte ich das eigentlich vor. Wahrscheinlich steckt die Lösung schon in Euren Antworten. Ich muss noch ein wenig forschen.



  • @zeropage sagte in von Matrix Klasse erben:

    @Finnegan
    Ok danke, so wie ich das sehe, ist Deine MatrixAccessorsBase mein Array2D<T>.

    Nein. Für MatrixAccessorsBase gibt es bei dir keine Entsprechung. Das ist eine Klasse, die keinerlei Daten-Member hat und lediglich Funktionalität zur Verfügung stellt. In anderen Sprachen nennt man sowas schonmal Mixin, auch wenn man den Begriff in C++ eher selten verwendet.

    Die Funktionalität von MatrixAccessorsBase ist "Element zurückgeben", die eigentlichen Daten sind jedoch in Matrix::elements gespeichert, also in einer Klasse, die von MatrixAccessorsBase erbt. Der Zugriff auf die Daten erfolgt de facto wie mit CRTP, auch wenn das in dem Code hier nicht direkt ersichtlich ist:

    z.B. Zeile 35:

    return static_cast<const Matrix<T, M, N>*>(this)->elements[i * N + j];
    

    MatrixAccessorsBase castet hier seinen eigenen this-Pointer in einen Matrix<T, M, N>-Pointer - das ist ein Downcast wie bei CRTP. Über diesen Pointer ist dann der elements-Datenmember der von MatrixAccessorsBase abgeleiteten Matrix verfügbar.

    So können Member-Funktionen von MatrixAccessorsBase auf Daten der abgeleiteten Klasse arbeiten, also lediglich "Funktionen zur Verfügung stellen", ohne eigene Daten-Member zu haben. Die Idee dahinter ist, dass diese Funktionalität bei Bedarf "zugeschaltet" wird, je nachdem welche Eigenschaften die Matrix-Klasse hat. Im Fall von MatrixAccessorsBase ist sind das die ()-Operatoren.

    Der einzige Grund, die in diese Mixin-Klasse zu packen ist hier für einen Vektor, also eine Matrix, bei der entweder Zeilen oder Spalten gleich 1 sind, auch noch einen operator(int i) zu haben, mit dem man mit nur einen Index-Parameter auf die Elemente zugreifen kann. Siehe Zeile 107:

    vector(2) = 7;
    

    Sorry wenn MatrixAccessorsBase verwirrend gewesen sein sollte, das diente eigentlich nur zur Illustration.

    QuadraticMatrixBase wird ähnlich verwendet, auch wenn hier kein CRTP-Downcast stattfindet, da die Klasse lediglich eine statische Member-Funktion zur Verfügung stellt. Diese Basisklasse ist per default leer, falls aber M == N gilt, es sich also um eine quadratische Matrix handelt, dann hat sie eine statische identity()-Memberfunktion, welche die Einheitsmatrix zurückgibt.

    Die Zugriffsoperatoren habe ich aslo schon, ich brauch nur noch die Rechenoperationen, die ich in Matrix2D<T>habe.

    Wie gesagt, meine Zugriffsoperatoren dienten nur zur Illustration, wie man diese "Mixin-Klassen" einsetzt und von diesen aus auf Elemente der Matrix zugreift.

    Und ich hätte schon gerne jede spezielle Matrix als eigenen Typ, wo ich aber nicht jedesmal alle Rechenoperationen implementieren muss. Deshalb kam ich auf Vererbung.

    Wenn sich die Template-Argumente unterscheiden, dann sind das schon jeweils verschiedene Typen. Ansonsten sollte eigetlich auch dein ursprünglicher Ansatz funktionieren, wenn dir das lieber ist. Du brauchst halt lediglich die konvertierenden Konstruktoren und Zuweisungsoperatoren. Die Rechenoperationen kannst du schon von Matrix2D wiederverwenden. Der weiss halt nur nicht, wie er eine Matrix2D in eine Mat3x3 konvertieren soll.

    Und auch mit meinem Ansatz braucht man die Rechenoperationen nur einmal zu implementieren. Man kann sogar einiges zwischen "Vektor" und "Matrix" wiederverwenden. So eine Vektoraddition bekäme man z.B. geschenkt, wenn man eine Matrixaddition implementiert hat und ein Vektor lediglich eine Matrix<T, N, 1> oder Matrix<T, 1, N> ist. Oder mein Steam-operator<< - einmal implementiert und gibt Vektoren sowie Matrizen aus.



  • @zeropage sagte in von Matrix Klasse erben:

    inline static zp::Mat3x3<T> makeTranslationMatrix
    

    Noch ein Gedanke am Rande: Willst du damit nur so geometrisches Zeug machen und brauchst eventuell gar keine Matrizen, die nicht-quadratisch sind? Das wäre auch noch ein Ansatz: Es gibt halt nur quadratische Matrizen, ergo nur eine Matrix-Klasse 😉



  • Erstmal wow!, wegen Deiner ausfürlichen Erläuterung. Vielen Dank, ich bin mir nicht sicher, ob ich überhaupt soviel Text verdiene 😌

    Ja, zur Zeit benötige ich nur quadratische Matrizen, eigentlich nur Mat3x3 und Mat4x4. Allerdings will ich in meinem Projekt auf alles vorbereitet sein. Ich programmiere mangels Kreativität gerne Sachen auf YouTube nach und da möchte ich mir einen möglichst großen Grundstock anlegen.

    Im Moment aber hört sich Dein Vorschlag ganz vernünftig an. Ich bin etwas hin- und hergerissen was ich nun mache, auch was die Lösung in diesem Fall angeht.



  • @zeropage sagte in von Matrix Klasse erben:

    Im Moment aber hört sich Dein Vorschlag ganz vernünftig an. Ich bin etwas hin- und hergerissen was ich nun mache, auch was die Lösung in diesem Fall angeht.

    Um dich noch mehr hin- und her zu reissen: Mein Vorschlag ist auch nur ein Ansatz unter vielen und der funktioniert auch so nur mit statischen Matrizen - bei dynamischen weiss man ja nicht zur Compile-Zeit ob sie quadratisch sind oder nicht.

    Der grosse Vorteil von den Mixins ist allerdings, dass letztendlich alles dasselbe Matrix<T, M, N>-Template ist und man Operationen wie Multiplikation nicht für eine abgeleitete QuadraticMatrix duplizieren muss oder irgendwelche Konvertierungs-Operatoren braucht. Ein einziger Matrix<...> operator*(const Matrix<...>&, const Matrix<...>&) reicht und greift für beide.

    Den selben Effekt kann man übrigens auch mit std::enable_if erzielen, die CRTP-Mixins find ich aber eleganter und leichter zu lesen... weniger "Template-Rauschen" im Quellcode 😉

    Und am Rande, damit keine Missverständnisse aufkommen: Die Base-Mixin-Klassen werden nur für Funktionen benötigt, die nicht von allen Matritzen unterstützt werden. Alles andere kann man direkt in Matrix implementieren.



  • @zeropage sagte in von Matrix Klasse erben:

    Also schlußendlich möchte ich mit den Matrizen so umgehen, wie in diesen Funktionen:

            template<typename T>
    	inline static zp::Mat3x3<T> makeTranslationMatrix(const zp::Vector2D<T>& translate)
    	{
    		zp::Mat3x3<T> mat;
    		mat.mat().matrix()(2, 0) = translate.x; // ist hier noch meinen Versuchen geschuldet
    		mat.mat().matrix()(2, 1) = translate.y; // sollte so aussehen: mat.matrix()(2, 1) = translate.y;
    		return mat;
    	}
    

    Irgendwie ist mit das ganz schön viel "mat" hier: mat.mat().matrix() und auch mat.matrix()(2, 1) ist jetzt so viel besser ... soll Mat3x3 keine Operatoren für Elementzugriff bekommen oder warum so umständlich? Ginge nicht auch einfach mat(2, 0) = translate.x?

    Meine Wunschsyntax sähe hier übrigens eher so aus:

    auto mat = Mat3x3::identity();
    submatrix<2, 1>(mat, 0, 2) = translate;
    return mat;
    

    submatrix<M, N>(m, i, j) würde einen Matrix<T, M, N>-View auf eine Untermatrix von m zurückliefern, der bei Indizes i, j beginnt. Quasi ein std::span für zweidimensionale Datenbereiche. Z.B. Vektoren in Spalten/Zeilen einer Matrix zu kopieren oder aus diesen auszulesen ist etwas, das man bei geometrischen Problemen tatsächlich öfter mal braucht. Der Translationsvektor, der in die letzte Spalte einer Einheitsmatrix kopiert wird, um eine Translationsmatrix zu erhalten, ist da ein gutes Beispiel für.

    Das bräuchte allerdings noch etwas Arbeit, damit das so funktionert. Ist vielleicht noch etwas Overkill im Moment. Machbar wäre es jedenfalls 😉

    P.S.: Falls es zu Verwirrungen bezüglich Indizes kommen sollte, ich verwende da die mathematische Index-Reihenfolge. M Zeilen x N Spalten mit i als Zeilen- und j als Spaltenindex. Bei deiner Translationsmatrix sieht es so aus als ob du das anders herum machst. Ist nicht falsch, muss man nur wissen.



  • Eigentlich mache ich auch (rows, columns). Warum das bei meiner Matrix einen anderen Eindruck macht, kann ich jetzt gar nicht erklären. Sieht eine Translationsmatrix in 2D nicht so aus?

    | 1 0 0 |
    | 0 1 0 |
    | X Y 1 |
    

    @Finnegan sagte in von Matrix Klasse erben:

    Ginge nicht auch einfach mat(2, 0) = translate.x?

    Wäre schöner, ja. Aber ich habe doch als Matrix in Matrix2D den Typ Array2D. Und um darauf zuzugreifen habe ich matrix() als Rückgabemethode.



  • Das hier wäre wohl quick & simple 😉 ?

            struct mat4x4
    	{
    		float matrix[4][4] = { 0 };
    		mat4x4()
    		{
    			matrix[0][0] = 1.0f;
    			matrix[1][1] = 1.0f;
    			matrix[2][2] = 1.0f;
    			matrix[3][3] = 1.0f;
    		}
    	};
    
    	inline Vec3D multiplyVector(const mat4x4& m, const Vec3D& i)
    	{
    		Vec3D v;
    		v.x = i.x * m.matrix[0][0] + i.y * m.matrix[1][0] + i.z * m.matrix[2][0] + i.w * m.matrix[3][0];
    		v.y = i.x * m.matrix[0][1] + i.y * m.matrix[1][1] + i.z * m.matrix[2][1] + i.w * m.matrix[3][1];
    		v.z = i.x * m.matrix[0][2] + i.y * m.matrix[1][2] + i.z * m.matrix[2][2] + i.w * m.matrix[3][2];
    		v.w = i.x * m.matrix[0][3] + i.y * m.matrix[1][3] + i.z * m.matrix[2][3] + i.w * m.matrix[3][3];
    		return v;
    	}
    
    	inline mat4x4 multiplyMatrix(const mat4x4& lhs, const mat4x4& rhs)
    	{
    		mat4x4 outMat;
    		for (std::size_t c = 0; c < 4; c++)
    			for (std::size_t r = 0; r < 4; r++)
    				outMat.matrix[r][c] =
    				  lhs.matrix[r][0] * rhs.matrix[0][c]
    				+ lhs.matrix[r][1] * rhs.matrix[1][c]
    				+ lhs.matrix[r][2] * rhs.matrix[2][c]
    				+ lhs.matrix[r][3] * rhs.matrix[3][c];
    		return outMat;
    	}
    
    	inline mat4x4 makeRotationX(const float fAngleRad)
    	{
    		mat4x4 outMat;
    		outMat.matrix[1][1] = std::cosf(fAngleRad);
    		outMat.matrix[1][2] = -std::sinf(fAngleRad);
    		outMat.matrix[2][1] = std::sinf(fAngleRad);
    		//outMat.matrix[1][2] = std::sinf(fAngleRad);
    		//outMat.matrix[2][1] = -std::sinf(fAngleRad);
    		outMat.matrix[2][2] = std::cosf(fAngleRad);
    		return outMat;
    	}
    
    	inline mat4x4 makeRotationY(const float fAngleRad)
    	{
    		mat4x4 outMat;
    		outMat.matrix[0][0] = std::cosf(fAngleRad);
    		outMat.matrix[0][2] = std::sinf(fAngleRad);
    		outMat.matrix[2][0] = -std::sinf(fAngleRad);
    		outMat.matrix[2][2] = std::cosf(fAngleRad);
    		return outMat;
    	}
    
    	inline mat4x4 makeRotationZ(const float fAngleRad)
    	{
    		mat4x4 outMat;
    		outMat.matrix[0][0] = std::cosf(fAngleRad);
    		outMat.matrix[0][1] = std::sinf(fAngleRad);
    		outMat.matrix[1][0] = -std::sinf(fAngleRad);
    		outMat.matrix[1][1] = std::cosf(fAngleRad);
    		//outMat.matrix[2][2] = 1.0f;
    		//outMat.matrix[3][3] = 1.0f;
    		return outMat;
    	}
    
    	inline mat4x4 makeTranslation(const float x, const float y, const float z)
    	{
    		mat4x4 outMat;
    		//outMat.matrix[0][0] = 1.0f;
    		//outMat.matrix[1][1] = 1.0f;
    		//outMat.matrix[2][2] = 1.0f;
    		//outMat.matrix[3][3] = 1.0f;
    		outMat.matrix[3][0] = x;
    		outMat.matrix[3][1] = y;
    		outMat.matrix[3][2] = z;
    		return outMat;
    	}
    	inline mat4x4 makeScalation(const float x, const float y, const float z)
    	{
    		mat4x4 outMat;
    		outMat.matrix[0][0] = x;
    		outMat.matrix[1][1] = y;
    		outMat.matrix[2][2] = z;
    		//outMat.matrix[3][3] = 1.0f;
    		return outMat;
    	}
    
    	inline mat4x4 makeReflectionX()
    	{
    		mat4x4 outMat;
    		outMat.matrix[1][1] =  1.0f;
    		outMat.matrix[1][1] = -1.0f;
    		outMat.matrix[2][2] =  1.0f;
    		return outMat;
    	}
    	inline mat4x4 makeReflectionY()
    	{
    		mat4x4 outMat;
    		outMat.matrix[1][1] = -1.0f;
    		outMat.matrix[1][1] =  1.0f;
    		outMat.matrix[2][2] =  1.0f;
    		return outMat;
    	}
    
    	inline mat4x4 makeProjection(const float fFovDegrees, const float fAspectRatio, const float fNear, const float fFar)
    	{
    		float fFovRad = 1.0f / std::tanf(math::toRadian<float>(fFovDegrees / 2.f));
    		mat4x4 outMat;
    		outMat.matrix[0][0] = fAspectRatio * fFovRad;
    		outMat.matrix[1][1] = fFovRad;
    		outMat.matrix[2][2] = fFar / (fFar - fNear);
    		outMat.matrix[3][2] = (-fFar * fNear) / (fFar - fNear);
    		outMat.matrix[2][3] = 1.0f;
    		outMat.matrix[3][3] = 0.0f;
    		return outMat;
    	}
    
    	inline mat4x4 quickInverse(const mat4x4& mat) // Only for Rotation/Translation Matrices
    	{
    		mat4x4 outMat;
    		outMat.matrix[0][0] = mat.matrix[0][0]; outMat.matrix[0][1] = mat.matrix[1][0]; outMat.matrix[0][2] = mat.matrix[2][0]; outMat.matrix[0][3] = 0.0f;
    		outMat.matrix[1][0] = mat.matrix[0][1]; outMat.matrix[1][1] = mat.matrix[1][1]; outMat.matrix[1][2] = mat.matrix[2][1]; outMat.matrix[1][3] = 0.0f;
    		outMat.matrix[2][0] = mat.matrix[0][2]; outMat.matrix[2][1] = mat.matrix[1][2]; outMat.matrix[2][2] = mat.matrix[2][2]; outMat.matrix[2][3] = 0.0f;
    		outMat.matrix[3][0] = -(mat.matrix[3][0] * outMat.matrix[0][0] + mat.matrix[3][1] * outMat.matrix[1][0] + mat.matrix[3][2] * outMat.matrix[2][0]);
    		outMat.matrix[3][1] = -(mat.matrix[3][0] * outMat.matrix[0][1] + mat.matrix[3][1] * outMat.matrix[1][1] + mat.matrix[3][2] * outMat.matrix[2][1]);
    		outMat.matrix[3][2] = -(mat.matrix[3][0] * outMat.matrix[0][2] + mat.matrix[3][1] * outMat.matrix[1][2] + mat.matrix[3][2] * outMat.matrix[2][2]);
    		outMat.matrix[3][3] = 1.0f;
    		return outMat;
    	}
    

Anmelden zum Antworten