von Matrix Klasse erben



  • @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;
    	}
    


  • @zeropage sagte in von Matrix Klasse erben:

    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.

    https://martinfowler.com/bliki/TellDontAsk.html

    Du willst doch eig. nich unbedingt, dass man auf den darunterliegenden Datentypen zugreifen muss, oder?



  • @zeropage sagte in von Matrix Klasse erben:

    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];

    Mal eine Frage in die Runde. Hat hier jemand Erfahrungen bezüglich std::fma()?



  • @zeropage sagte in von Matrix Klasse erben:

    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 |
    

    Ich hab bisher immer mit der transponierten Version von deiner Translationsmatrix gearbeitet, daher meine Verwirrung. Aber das funktioniert genau so gut auch so, deshalb sind deine Vektoren auch Zeilenvektoren (1×31 \times 3-Matrizen) und du multiplizierst diese von links. Man kann auch mit Spaltenvektoren (3×13 \times 1-Matrizen) arbeiten - dann multipliziert man von rechts und die Translationsmatrix ist transponiert. Das ist symmetrisch und eigentlich nur eine Sache der Konvention. Die Vektor-Indizes vertauschen (was ja im Endeffekt ein Transponieren ist) sieht da fast genau so aus.

    @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.

    Ist eigentlich egal wie viele Objekte man da drum herum gepackt hat. Hauptsache das äußere Objekt hat einen operator() der den Aufruf z.B. bis zum innersten Objekt durchreicht. Ich denke sowas wird mit Optimierungen ohnehin komplett oder auf höchstens einen Fuktionsaufruf geinlined. Gerade bei Funktionen, die eben nur "durchreichen".



  • @zeropage sagte in von Matrix Klasse erben:

    Das hier wäre wohl quick & simple 😉 ?

    Damit käme man erstmal sehr schnell weiter und würde ich auch so empfehlen wenn du jetzt weniger Drang verspürst, eine möglichst generische Matrixklasse zu perfektionieren.

    Du kannst dir ja ein schönes Interface ausdenken, so wie du das nachher alles am liebsten verwenden würdest. Dann kannst du später immer noch eine oder mehr generische Matrixklasse(n) programmieren, die dasselbe Interface zur Verfügung stellen - so musst du dann nur den Matrix-Code und nicht mehr den ganzen Rest deines Programms anfassen.

    Hätte auch den Vorteil, dass du später vielleicht besser weisst, was für Operationen du alles brauchst.



  • @Quiche-Lorraine sagte in von Matrix Klasse erben:

    @zeropage sagte in von Matrix Klasse erben:

    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];

    Mal eine Frage in die Runde. Hat hier jemand Erfahrungen bezüglich std::fma()?

    Erfahrung nicht, aber das ist ja dokumentiert. Die Zeile da oben sollte mit fma formuliert dann eigentlich so aussehen:

    v.x = std::fma(
        i.x,
        m.matrix[0][0],
        std::fma(
            i.y,
            m.matrix[1][0],
            std::fma(
                i.z,
                m.matrix[2][0],
                i.w * m.matrix[3][0]
            )
        )
    )
    

    ... oder etwas weniger geschachtelt:

    v.x = std::fma(i.y, m.matrix[1][0], i.x * m.matrix[0][0])
    v.x = std::fma(i.z, m.matrix[2][0], v.x)
    v.x = std::fma(i.w, m.matrix[3][0], v.x)
    

    ... oder eben irgendeine äquivalente Formulierung - die genaue Berechnungsreihenfolge kann ja z.B. irgendwann für die numerische Stabilität relevant sein.

    Kann man ja mal ausprobieren ob das eventuell besser abschneidet - falls der Compiler da nicht ohnehin schon den selben Code erzeugt, wenn die Optimierungs-Flags richtig gewählt sind.



  • @Finnegan sagte in von Matrix Klasse erben:

    Ist eigentlich egal wie viele Objekte man da drum herum gepackt hat. Hauptsache das äußere Objekt hat einen operator() der den Aufruf z.B. bis zum innersten Objekt durchreicht. Ich denke sowas wird mit Optimierungen ohnehin komplett oder auf höchstens einen Fuktionsaufruf geinlined. Gerade bei Funktionen, die eben nur "durchreichen".

    Ja, die vielen durchreichenden Methoden schreibt man ja nur einmal in den Berechnungen. Aber diese sind ja selber in Methoden gepackt, so das die Durchreichungen am Ende gar nicht mehr auftauchen.

    Alles was ich hier an Code geschrieben habe, funktioniert natürlich auch. Es ist ja nicht so, das ich keine Matrizen anwenden kann, ich hatte nur nachgedacht, wie ich das generischer schreiben kann.

    Bin immer noch hin- und hergerrissen, wie ich das am Ende mache. Habe aber auch gerade eine Woche Pause, also nicht wundern, wenn ich keine konkreten Antworten hatte. Es ist aber immer spannend hier mitzulesen 😘


Anmelden zum Antworten