• Keine Ergebnisse gefunden

Kontakt und Updates

2.2 Einführung in die 3D-Grafik

2.2.2 Theorie der 3D-Grafik

2.2.4.7 Die Klasse tbMatrix

Wir wollen uns nun an die Implementierung einer Matrixklasse machen. Ich habe sie auf den Namen tbMatrix getauft. Der Code befindet sich in den Dateien TBMATRIX.H und TBMATRIX. Ich werde diese Klasse nicht mehr so ausführlich behandeln wie die Vektorklasse, da viele Dinge identisch sind.

Variablen der Klasse

Die tbMatrix-Klasse wird 16 float-Variablen besitzen, die wir m11 bis m44 nennen. Ansonsten benötigen wir keine weiteren Variablen.

Konstruktoren

Drei grundlegende Konstruktoren sind:

ƒ Ohne Parameter: Tut gar nichts.

ƒ Kopierkonstruktor mit Referenz auf eine andere Matrix als Parameter: Kopiert die angege-bene Matrix.

ƒ 16 float-Parameter: Kopiert die Werte in die Matrix hinein.

// Standardkonstruktor tbMatrix::tbMatrix() {

}

// Kopierkonstruktor

tbMatrix(const tbMatrix& m) : m11(m.m11), m12(m.m12), m13(m.m13), m14(m.m14), m21(m.m21), m22(m.m22), m23(m.m23), m24(m.m24), m31(m.m31), m32(m.m32), m33(m.m33), m34(m.m34), m41(m.m41), m42(m.m42), m43(m.m43), m44(m.m44) {

}

// Konstruktor mit 16 Parametern

tbMatrix(float c11, float c12, float c13, float c14, float c21, float c22, float c23, float c24, float c31, float c32, float c33, float c34, float c41, float c42, float c43, float c44)

: m11(c11), m12(c12), m13(c13), m14(c14), m21(c21), m22(c22), m23(c23), m24(c24), m31(c31), m32(c32), m33(c33), m34(c34), m41(c41), m42(c42), m43(c43), m44(c44) {

}

Listing 2.22 Verschiedene Konstruktoren der Klasse tbMatrix

Operatoren

Bei den Operatoren gibt es eigentlich nicht viele Unterschiede zur Vektorklasse tbVector3. Ausnahme ist hier natürlich die Multiplikation mit einer anderen Matrix. Daher ist dies auch der einzige Operator, den ich hier zeigen werde.

Erwähnenswert ist auch der Divisionsoperator: Er invertiert die rechte Matrix und multipliziert die linke damit. Pures Umkehren oder Transponieren einer Matrix kann nicht mit Operatoren durchgeführt werden, nutzen Sie stattdessen die Hilfsfunktion tbMatrixInvert beziehungsweise

tbMatrixTranspose (diese Funktionen werden später erklärt).

Vektortransformationen müssen ebenfalls mit Hilfe von Hilfsfunktionen ausgeführt werden, denn nur mit Hilfe eines Operators könnte man nicht bestimmen, ob es sich um einen Positi-ons- oder Richtungsvektor handelt. Wie bereits gesagt wurde, müssen diese verschieden be-handelt werden.

Es folgt die (etwas längliche) Multiplikation von zwei Matrizen.

// Multiplikation zweier Matrizen

inline tbMatrix operator * (const tbMatrix& a, const tbMatrix& b) {

return tbMatrix(b.m11 * a.m11 + b.m21 * a.m12 + b.m31 * a.m13 + b.m41 * a.m14, b.m12 * a.m11 + b.m22 * a.m12 + b.m32 * a.m13 + b.m42 * a.m14, b.m13 * a.m11 + b.m23 * a.m12 + b.m33 * a.m13 + b.m43 * a.m14, b.m14 * a.m11 + b.m24 * a.m12 + b.m34 * a.m13 + b.m44 * a.m14, b.m11 * a.m21 + b.m21 * a.m22 + b.m31 * a.m23 + b.m41 * a.m24, b.m12 * a.m21 + b.m22 * a.m22 + b.m32 * a.m23 + b.m42 * a.m24, b.m13 * a.m21 + b.m23 * a.m22 + b.m33 * a.m23 + b.m43 * a.m24, b.m14 * a.m21 + b.m24 * a.m22 + b.m34 * a.m23 + b.m44 * a.m24, b.m11 * a.m31 + b.m21 * a.m32 + b.m31 * a.m33 + b.m41 * a.m34, b.m12 * a.m31 + b.m22 * a.m32 + b.m32 * a.m33 + b.m42 * a.m34, b.m13 * a.m31 + b.m23 * a.m32 + b.m33 * a.m33 + b.m43 * a.m34, b.m14 * a.m31 + b.m24 * a.m32 + b.m34 * a.m33 + b.m44 * a.m34, b.m11 * a.m41 + b.m21 * a.m42 + b.m31 * a.m43 + b.m41 * a.m44, b.m12 * a.m41 + b.m22 * a.m42 + b.m32 * a.m43 + b.m42 * a.m44, b.m13 * a.m41 + b.m23 * a.m42 + b.m33 * a.m43 + b.m43 * a.m44, b.m14 * a.m41 + b.m24 * a.m42 + b.m34 * a.m43 + b.m44 * a.m44);

}

Listing 2.23 Jedes Element der resultierenden Matrix wird nach der Multiplikationsregel berechnet.

Hier würde es sich übrigens auch durchaus lohnen, auf eventuell vorhandene CPU-Features wie SSE oder 3DNow! zu setzen. Zu Letzterem befindet sich ein Tutorial auf meiner Home-page. Mit den genannten Technologien kann man mehrere Fließkommaberechnungen (bei 3DNow! sind es 2, bei SSE sogar 4) parallel ausführen lassen.

Zugriffsoperatoren

Manchmal wäre es hilfreich, wenn man alle Elemente einer Matrix auf einheitliche Weise an-sprechen könnte – das ist aber dadurch, dass jedes Element einen anderen Namen hat, nicht so einfach. Daher überladen wir den „()“-Operator der tbMatrix-Klasse. So soll beispielsweise folgender Code kompilierbar sein:

tbMatrix m;

// Element in Zeile 1, Spalte 3 auf den Wert 17 setzen m(1, 3) = 17.0f;

// Element in Zeile 4, Spalte 2 auf den Wert 1 setzen m(4, 2) = 1.0f;

Listing 2.24 Setzen von Matrixelementen auf eine andere Weise

Dies hat einen Vorteil: Man könnte auch anstelle der Zahlen (1, 3) und (4, 2) Variablen ver-wenden. Das wäre ohne weiteres nicht möglich, wenn wir dabei blieben, jedes Element in eine eigene Variable mit einem eigenen Namen zu packen.

Wir benötigen hier ein zweidimensionales Array mit 4 x 4 Einträgen. Dieses Array soll den Speicher der 16 einzelnen float-Variablen der Matrix (m11, m12, m13, m14, m21 …) mitbenutzen.

Dazu verwenden wir eine union.

class TRIBASE_API tbMatrix {

public:

// Variablen union {

struct {

float m11, m12, m13, m14, // Elemente der Matrix m21, m22, m23, m24,

m31, m32, m33, m34, m41, m42, m43, m44;

};

float m[4][4]; // Zweidimensionales Array der Elemente };

// Und so weiter ...

Listing 2.25 Die angepasste Deklaration der Klassenvariablen

Den „()“-Operator zu überladen, ist jetzt nicht mehr besonders schwer. Er soll zwei Parameter erwarten: die Zeile und die Spalte, wobei hier die Eins die erste Zeile beziehungsweise die ers-te Spalers-te darsers-tellt (nicht null). Wir ersers-tellen zwei Versionen des Operators: Einer liefert eine Referenz (den brauchen wir, um Matrixelemente zu verändern), und der andere liefert einen normalen float-Wert (der Compiler wird diese Version verwenden, wenn ein Matrixelement lediglich abgefragt werden soll). Die zweite Version kann dann entsprechend als const dekla-riert werden:

class TRIBASE_API tbMatrix {

public:

// ...

// Zugriffsoperatoren

float& operator () (int iRow, int iColumn) {return m[iRow - 1][iColumn - 1];}

float operator () (int iRow, int iColumn) const {return m[iRow - 1][iColumn - 1];}

};

// Beispiel für den ersten Operator (Matrix wird verändert):

tbMatrix m;

m(1, 3) = 100.0f;

m(2, 1) = -50.0f;

// Beispiel für den zweiten Operator (keine Veränderung der Matrix):

float f = m(1, 2);

Listing 2.26 Die Zugriffsoperatoren

Identitätsmatrix erzeugen

Mit der Funktion tbMatrixIdentity soll dem Programmierer die Identitätsmatrix geliefert wer-den. Sie erinnern sich: Wird ein Vektor oder eine Matrix mit der Identitätsmatrix multipliziert, so ist das vergleichbar mit einer Multiplikation mit 1 – es erfolgt keine Änderung.

// Identitätsmatrix liefern inline tbMatrix tbMatrixIdentity() {

return tbMatrix(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);

}

Listing 2.27 Diese simple Funktion liefert die Identitätsmatrix.

Translationsmatrix

Eine Translationsmatrix, also eine Verschiebungsmatrix, zu erzeugen, ist zum Glück auch nicht viel komplizierter. Die Funktion tbMatrixTranslation soll einen Vektor als Parameter erwarten, nämlich den Verschiebungsvektor, und uns dann die passende Matrix dazu liefern:

// Translationsmatrix berechnen

TRIBASE_API tbMatrix tbMatrixTranslation(const tbVector3& v) {

return tbMatrix(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, v.x, v.y, v.z, 1.0f);

}

Listing 2.28 Erzeugen einer Translationsmatrix

Rotation um x-, y- und z-Achse

Wie die Matrizen für eine Rotation um die x-, y- oder z-Achse aussehen, wurde bereits gezeigt.

Nun gilt es, das in drei Funktionen umzusetzen: tbMatrixRotationX, tbMatrixRotationY und

tbMatrixRotationZ. Beachten Sie, dass ich hier die Sinus- und Kosinuswerte des angegebenen Winkels (im Bogenmaß!) nur einmal berechne. So spart man ein wenig Zeit.

// Rotationsmatrix für Rotation um die x-Achse berechnen TRIBASE_API tbMatrix tbMatrixRotationX(const float f) {

tbMatrix mResult;

// Den Rahmen der Matrix setzen (Identitätsmatrix!)

mResult.m11 = 1.0f; mResult.m12 = 0.0f; mResult.m13 = 0.0f; mResult.m14 = 0.0f;

mResult.m21 = 0.0f; mResult.m24 = 0.0f;

mResult.m31 = 0.0f; mResult.m34 = 0.0f;

mResult.m41 = 0.0f; mResult.m42 = 0.0f; mResult.m43 = 0.0f; mResult.m44 = 1.0f;

// Die fehlenden Elemente setzen mResult.m22 = mResult.m33 = cosf(f);

mResult.m23 = sinf(f);

mResult.m32 = -mResult.m23;

return mResult;

}

// Rotationsmatrix für Rotation um die y-Achse berechnen TRIBASE_API tbMatrix tbMatrixRotationY(const float f) {

tbMatrix mResult;

// Rotationsmatrix berechnen

mResult.m12 = 0.0f; mResult.m14 = 0.0f;

mResult.m21 = 0.0f; mResult.m22 = 1.0f; mResult.m23 = 0.0f; mResult.m24 = 0.0f;

mResult.m32 = 0.0f; mResult.m34 = 0.0f;

mResult.m41 = 0.0f; mResult.m42 = 0.0f; mResult.m43 = 0.0f; mResult.m44 = 1.0f;

mResult.m11 = mResult.m33 = cosf(f);

mResult.m31 = sinf(f);

mResult.m13 = -mResult.m31;

return mResult;

}

// Rotationsmatrix für Rotation um die z-Achse berechnen TRIBASE_API tbMatrix tbMatrixRotationZ(const float f) {

tbMatrix mResult;

// Rotationsmatrix berechnen

mResult.m13 = 0.0f; mResult.m14 = 0.0f;

mResult.m23 = 0.0f; mResult.m24 = 0.0f;

mResult.m31 = 0.0f; mResult.m32 = 0.0f; mResult.m33 = 1.0f; mResult.m34 = 0.0f;

mResult.m41 = 0.0f; mResult.m42 = 0.0f; mResult.m43 = 0.0f; mResult.m44 = 1.0f;

mResult.m11 = mResult.m22 = cosf(f);

mResult.m12 = sinf(f);

mResult.m21 = -mResult.m12;

return mResult;

}

Listing 2.29 Erzeugen von Rotationsmatrizen

Rotation um alle drei Achsen

Soll ein Objekt um alle drei Achsen gedreht werden, so wäre es natürlich ein wenig umständ-lich, jeweils einmal tbMatrixRotationX, tbMatrixRotationY und tbMatrixRotationZ aufzurufen und die Matrizen zu multiplizieren. Da ist es praktischer, eine Funktion zu haben, die drei Pa-rameter erwartet – einen für jede Achse. Wir rotieren dabei zuerst um die z-Achse, dann um die x-Achse und zum Schluss um die y-Achse, denn das ist für die meisten Anwendungen am besten geeignet. Diese drei float-Parameter kann man auch direkt als Vektor betrachten, wes-halb es auch zwei Versionen der Funktion tbMatrixRotation gibt:

// Rotiert um alle drei Achsen

TRIBASE_API tbMatrix tbMatrixRotation(const float x, const float y, const float z) {

return tbMatrixRotationZ(z) * tbMatrixRotationX(x) * tbMatrixRotationY(y);

}

// Rotiert um alle drei Achsen (Winkel in Vektor) TRIBASE_API tbMatrix tbMatrixRotation(const tbVector3& v) {

return tbMatrixRotationZ(v.z) * tbMatrixRotationX(v.x) * tbMatrixRotationY(v.y);

}

Listing 2.30 Rotation um alle drei Achsen

Rotation um eine beliebige Achse

Jetzt können wir Punkte um die drei Achsen des Koordinatensystems rotieren, aber wie sieht es mit beliebigen Achsen aus? Die Matrix für die Rotation mit dem Winkel α um eine beliebi-ge Achse, deren Richtung durch den Vektor vr angegeben wird, sieht so aus:

( ) ( )

Die Funktion tbMatrixRotationAxis erzeugt genau so eine Matrix:

// Rotationsmatrix für Rotation um eine beliebige Achse berechnen TRIBASE_API tbMatrix tbMatrixRotationAxis(const tbVector3& v, const float f) {

// Sinus und Kosinus berechnen

const float fSin = sinf(-f), fCos = cosf(-f);

const float fOneMinusCos = 1.0f - fCos;

// Achsenvektor normalisieren

const tbVector3 vAxis(tbVector3Normalize(v));

// Matrix erstellen

return tbMatrix((vAxis.x * vAxis.x) * fOneMinusCos + fCos,

(vAxis.x * vAxis.y) * fOneMinusCos - (vAxis.z * fSin),

Listing 2.31 Rotation um eine beliebige Achse

Skalierungsmatrix

Die Funktion tbMatrixScaling soll uns eine Skalierungsmatrix liefern. Als Parameter geben wir ihr einen Skalierungsvektor, der die Skalierung auf allen drei Achsen beinhaltet.

// Skalierungsmatrix berechnen

TRIBASE_API tbMatrix tbMatrixScaling(const tbVector3& v) {

// Skalierungsmatrix berechnen

return tbMatrix(v.x, 0.0f, 0.0f, 0.0f, 0.0f, v.y, 0.0f, 0.0f, 0.0f, 0.0f, v.z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);

}

Listing 2.32 Generieren einer Skalierungsmatrix

Die Achsenmatrix

Die Achsenmatrix kann ein Objekt entlang dreier Achsen ausrichten. Dazu packt man den Richtungsvektor der x-Achse in die erste Zeile der Matrix, den der y-Achse in die zweite, und der Richtungsvektor der z-Achse kommt in die dritte Zeile:

// Liefert eine Achsenmatrix

TRIBASE_API tbMatrix tbMatrixAxes(const tbVector3& vXAxis, const tbVector3& vYAxis, const tbVector3& vZAxis) {

return tbMatrix(vXAxis.x, vXAxis.y, vXAxis.z, 0.0f, vYAxis.x, vYAxis.y, vYAxis.z, 0.0f, vZAxis.x, vZAxis.y, vZAxis.z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);

}

Listing 2.33 Die Achsenmatrix

Die Determinante einer Matrix

Wenn wir später eine Matrix invertieren wollen, dann müssen wir in der Lage sein, ihre De-terminante zu berechnen. Vielleicht kennen Sie das Verfahren noch aus dem Mathematikun-terricht: Lineare Gleichungssysteme lassen sich sehr leicht mit Hilfe von Determinanten lösen.

Bei 4x4-Matrizen ist die Berechnung der Determinanten leider etwas kompliziert, doch es reicht, wenn wir nur die ersten drei Zeilen und Spalten der Matrix betrachten und deren De-terminante berechnen. Dabei gilt die Regel: Summe der Hauptdiagonalen (von links oben nach rechts unten) minus Summe der Nebendiagonalen (von links unten nach rechts oben).

( ) ( ) ( ) ( ) ( ) ( )

Packen wir das nun in eine Funktion (tbMatrixDet), dann kann das so aussehen:

// Determinante einer Matrix berechnen

TRIBASE_API float tbMatrixDet(const tbMatrix& m) {

// Determinante der linken oberen 3x3-Teilmatrix berechnen return m.m11 * (m.m22 * m.m33 - m.m23 * m.m32) -

m.m12 * (m.m21 * m.m33 - m.m23 * m.m31) + m.m13 * (m.m21 * m.m32 - m.m22 * m.m31);

}

Listing 2.34 Wir berechnen die Determinante der linken oberen 3x3-Teilmatrix der 4x4-Matrix.

Invertieren einer Matrix

(Wenn die Implementierungsdetails für Sie nicht von Interesse sind, können Sie diesen Ab-schnitt auch überspringen oder nur überfliegen. Es reicht, wenn Sie wissen, wie die Funktio-nen heißen und was sie tun.)

Nun wird unsere Funktion tbMatrixDet zur Berechnung der Determinanten ihre Anwendung finden, nämlich beim Invertieren einer Matrix. Sie erinnern Sich: Die invertierte Matrix hat genau die entgegengesetzte Wirkung, wenn man sie mit einer anderen Matrix oder einem Vek-tor multipliziert.

Eine 3x3-Matrix kann wie folgt invertiert werden:

1

Wir dürfen dieses Prinzip auch auf eine 4x4-Matrix übertragen, wenn wir davon ausgehen, dass die rechte Spalte (0, 0, 0, 1) ist, was normalerweise auch der Fall ist – außer bei Projekti-onsmatrizen. Die unterste Zeile wird dann separat berechnet, wie die Funktion tbMatrixInvert

zeigt:

// Invertierte Matrix berechnen

TRIBASE_API tbMatrix tbMatrixInvert(const tbMatrix& m) {

// Kehrwert der Determinante vorberechnen float fInvDet = tbMatrixDet(m);

if(fInvDet == 0.0f) return tbMatrixIdentity();

fInvDet = 1.0f / fInvDet;

Listing 2.35 Berechnen der invertierten Matrix – der Kehrwert der Determinante wird nur einmal berech-net, um die Divisionen schneller ausführen zu können (Multiplikation mit dem Kehrwert).

Transponierte Matrizen

Eine Matrix zu transponieren bedeutet ja, dass jedes Element an der Stelle (x, y) mit dem Ele-ment (y, x) vertauscht wird. Das in eine Funktion zu packen, ist sehr leicht:

// Transponierte Matrix berechnen

TRIBASE_API tbMatrix tbMatrixTranspose(const tbMatrix& m) {

// Matrix transponieren

return tbMatrix(m.m11, m.m21, m.m31, m.m41, m.m12, m.m22, m.m32, m.m42, m.m13, m.m23, m.m33, m.m43, m.m14, m.m24, m.m34, m.m44);

}

Listing 2.36 Die Funktion tbMatrixTranspose

Die Projektionsmatrix

Die Theorie, die hinter der Erzeugung einer Projektionsmatrix steckt, ist leider sehr kompli-ziert. Sie finden eine kleine Abhandlung darüber in der DirectX-SDK-Dokumentation unter

„Projection Transformation“.

Die Kameramatrix

Die Kameramatrix hingegen lässt sich relativ leicht erzeugen. Der erste Schritt ist, eine Trans-lationsmatrix entgegengesetzt der Kameraposition zu erzeugen. Wenn die Kamera nach rechts bewegt wird, bewegen wir damit praktisch die Umgebung nach links, denn in Wirklichkeit gibt es gar keine Kamera. Der nächste Schritt ist, die Achsenvektoren der Kamera in eine Mat-rix einzutragen, und zwar die x-Achse in die erste Spalte, die y-Achse in die zweite und die z-Achse in die dritte. Nun multipliziert man die beiden Matrizen und erhält die Kameramatrix.

Ein Problem ist, die Richtungsvektoren der Kameraachsen zu erzeugen. Unserer Funktion

tbMatrixCamera soll einmal die Kameraposition angegeben werden und weiterhin der Punkt, auf den die Kamera schauen soll. Doch das reicht noch nicht, denn die Kamera könnte auch noch „rollen“, sich also um ihre eigene z-Achse drehen. Darum benötigen wir noch einen wei-teren Vektor, den Nach-oben-Vektor. Normalerweise ist er (0, 1, 0). Drehte man die Kamera beispielsweise um 90° nach rechts, so wäre der Vektor (1, 0, 0). Mit ein paar Kreuzprodukten lassen sich dann alle Achsen berechnen:

// Kameramatrix berechnen

TRIBASE_API tbMatrix tbMatrixCamera(const tbVector3& vPos, const tbVector3& vLookAt,

const tbVector3& vUp) // = tbVector3(0.0f, 1.0f, 0.0f) {

tbVector3 vZAxis(tbVector3Normalize(vLookAt - vPos)); // z-Achse berechnen // x- und y-Achse werden durch das Kreuzprodukt berechnet.

tbVector3 vXAxis(tbVector3Normalize(tbVector3Cross(vUp, vZAxis)));

tbVector3 vYAxis(tbVector3Normalize(tbVector3Cross(vZAxis, vXAxis)));

// Rotationsmatrix erzeugen und die Translationsmatrix mit ihr multiplizieren return tbMatrixTranslation(-vPos) * tbMatrix(vXAxis.x, vYAxis.x, vZAxis.x, 0.0f, vXAxis.y, vYAxis.y, vZAxis.y, 0.0f, vXAxis.z, vYAxis.z, vZAxis.z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);

}

Listing 2.37 Wir erzeugen eine Kameramatrix.

Transformation eines Vektors

Kommen wir nun zu den Funktionen, die einen Vektor mit einer Matrix multiplizieren. Dabei müssen wir zwischen Positions- und Richtungsvektoren unterscheiden. Die Version für Positi-onsvektoren nennen wir tbVector3TransformCoords, und die für Richtungsvektoren nennen wir

tbVector3TransformNormal.

Beide Funktionen sind recht simpel. Betrachten wir zuerst tbVector3TransformCoords. Die x-, y- und z-Koordinate des resultierenden Vektors berechnen wir ganz einfach anhand der Rechen-gesetze für die Multiplikation eines 3D-Vektors mit einer 4x4-Matrix. Doch nun könnte es noch sein, dass die Matrix eine Projektion durchführt. Für den Fall berechnen wir auch noch die w-Koordinate, und wenn diese nicht 1 ist, wird der Vektor noch durch sie dividiert.

Wir wollen dem Benutzer noch die Möglichkeit geben, die während der Transformation be-rechnete w-Koordinate abzufragen. Darum erwartet die Funktion neben dem Vektor und der Matrix noch einen float-Zeiger, den die Funktion mit der w-Koordinate ausfüllt, wenn der Zeiger nicht NULL ist.

// 3D-Positionsvektor transformieren

TRIBASE_API tbVector3 tbVector3TransformCoords(const tbVector3& v, const tbMatrix& m,

// Vierte Koordinate (w) berechnen. Wenn diese ungleich eins // ist, müssen die anderen Vektorelemente durch sie geteilt // werden.

Listing 2.38 Transformation eines Positionsvektors

Bei Richtungsvektoren arbeiten wir ein wenig anders. Erstens dürfen wir nicht mit der unver-änderten Matrix arbeiten, sondern wir benötigen die transponierte invertierte Matrix. Außer-dem wollen wir dafür sorgen, dass der transformierte Vektor dieselbe Länge hat wie der Ori-ginalvektor. Das erreichen wir, indem wir die ursprüngliche Länge speichern und den resultie-renden Vektor später ebenso auf diese Länge bringen (normalisieren und mit der Länge mul-tiplizieren). Die Projektion entfällt hier, da Richtungsvektoren normalerweise nicht projiziert werden.

// 3D-Richtungsvektor transformieren

TRIBASE_API tbVector3 tbVector3TransformNormal(const tbVector3& v, const tbMatrix& m) {

// Vektorlänge berechnen

const float fLength = tbVector3Length(v);

if(fLength == 0.0f) return v;

// Transponierte invertierte Matrix berechnen

const tbMatrix mNew(tbMatrixTranspose(tbMatrixInvert(m)));

// Vektor mit Matrix transformieren und ursprüngliche Länge wiederherstellen

return tbVector3Normalize(tbVector3(v.x * mNew.m11 + v.y * mNew.m21 + v.z * mNew.m31, v.x * mNew.m12 + v.y * mNew.m22 + v.z * mNew.m32, v.x * mNew.m13 + v.y * mNew.m23 + v.z * mNew.m33)) * fLength;

}

Listing 2.39 Transformation eines Richtungsvektors

Leider ist gerade das Invertieren einer Matrix relativ langsam. Wenn dem Programm die transponierte invertierte Matrix bereits bekannt ist, wäre es unsinnig, diese noch einmal hier in der Funktion zu berechnen. Von daher implementieren wir noch eine weitere Version der Funktion namens tbVector3TransformNormal_TranspInv. Durch den Namen wird gesagt, dass die Funktion bereits die transponierte invertierte Matrix als Parameter erwartet. Daher ist sie auch einfacher aufgebaut:

// 3D-Richtungsvektor transformieren (transponierte invertierte Matrix) TRIBASE_API tbVector3 tbVector3TransformNormal_TranspInv(const tbVector3& v, const tbMatrix& m) {

// Vektorlänge berechnen

const float fLength = tbVector3Length(v);

if(fLength == 0.0f) return v;

// Vektor mit Matrix transformieren und ursprüngliche Länge wiederherstellen

return fLength * tbVector3Normalize(tbVector3(v.x * m.m11 + v.y * m.m21 + v.z * m.m31, v.x * m.m12 + v.y * m.m22 + v.z * m.m32, v.x * m.m13 + v.y * m.m23 + v.z * m.m33)) }

Listing 2.40 Das geht schneller, wenn die transponierte invertierte Matrix bereits bekannt ist.

Und was ist mit 2D-Vektoren? Dafür gibt es die Funktionen tbVector2TransformCoords und

tbVector2TransformNormal(_TranspInv). Da ist es nicht viel anders, nur dass eben nicht so viele Berechnungen anfallen.

Übersicht der Hilfsfunktionen

Nun haben wir eine weitere Klasse und die dazugehörigen Hilfsfunktionen implementiert.

Folgende Tabelle könnte für Sie nützlich sein, um noch einmal die Bedeutung der verschiede-nen Funktioverschiede-nen nachzuschlagen.

Tabelle 2.2 Hilfsfunktionen für die tbMatrix-Klasse

Funktionsname Ausgabetyp Beschreibung

tbMatrixIdentity tbMatrix Liefert die Identitätsmatrix zurück.

tbMatrixTranslation tbMatrix Erstellt eine Translationsmatrix mit dem angegebe-nen Vektor.

tbMatrixRotationX tbMatrix Rotationsmatrix um die x-Achse um den im Bogen-maß angegebenen Winkel generieren. Diese Rotati-on erfolgt im Uhrzeigersinn.

tbMatrixRotationY tbMatrix Rotation um die y-Achse tbMatrixRotationZ tbMatrix Rotation um die z-Achse

tbMatrixRotation tbMatrix Erstellt eine Rotationsmatrix, die einen Punkt um alle drei Achsen um die angegebenen Winkel dreht.

Funktionsname Ausgabetyp Beschreibung

tbMatrixRotationAxis tbMatrix Rotation um eine beliebige Achse (erster Parameter tbVector3) um den im zweiten Parameter ange-gebenen Winkel im Bogenmaß

tbMatrixScaling tbMatrix Skalierungsmatrix mit dem angegebenen Skalie-rungsvektor (x-, y- und z-Skalierung) erzeugen.

tbMatrixAxes tbMatrix Berechnet eine Achsenmatrix. Man gibt ihr einfach die drei Achsenvektoren an (x-, y- und z-Achse).

Wird diese Matrix als Weltmatrix verwendet, kann dadurch die Ausrichtung eines Objekts auf einfache Weise angegeben werden – zum Beispiel (0, 0, –1), (0, 1, 0) und (1, 0, 0), um ein Objekt entlang der x-Achse auszurichten (da die z-x-Achse des Objekts (1, 0, 0) ist).

tbMatrixDet float Bestimmt die Determinante einer Matrix.

tbMatrixInvert tbMatrix Invertiert die angegebene Matrix (falls nicht möglich:

Identitätsmatrix) tbMatrixTranspose tbMatrix Transponiert eine Matrix

tbMatrixProjection tbMatrix Erzeugt eine Projektionsmatrix mit folgenden Anga-ben: halber Sichtwinkel im Bogenmaß (zum Beispiel 1/2π für 90°), Bildseitenverhältnis (Breite : Höhe), Entfernung der nahen Clipping-Ebene, Entfernung der fernen Clipping-Ebene.

tbMatrixCamera tbMatrix Erzeugt eine Kameramatrix mit den angegebenen drei Vektoren: Position der Kamera, Blickpunkt der Kamera und der Nach-oben-Vektor (für gewöhnlich (0, 1, 0)).

tbVector2TransformCoords tbVector3TransformCoords

tbVector2 tbVector3

Transformiert den im ersten Parameter angegebe-nen Vektor mit der im zweiten angegebeangegebe-nen Matrix (für Positionsvektoren).

tbWriteMatrixToLog Schreibt die angegebene Matrix in die Logbuchdatei (alle Matrixelemente).

Beispiele für die meisten dieser Hilfsfunktionen zeigt das folgende Listing. Wieder einmal gilt: Es steckt kein besonderer Sinn dahinter, es dient nur der Demonstrierung der Funktionen.

// Identitätsmatrix erzeugen

tbMatrix mIdentity(tbMatrixIdentity());

// Translationsmatrix erzeugen. Diese Matrix verschiebt einen Vektor um (100, -15, 63.75).

tbMatrix mTranslation(tbMatrixTranslation(tbVector3(100.0f, -15.0f, 63.75f)));

// Rotationsmatrix: Erst um 45° um die x-Achse und danach um 90° um die z-Achse drehen.

tbMatrix mRotationX(tbMatrixRotationX(TB_DEG_TO_RAD(45.0f)));

tbMatrix mRotationZ(tbMatrixRotationZ(TB_DEG_TO_RAD(90.0f)));

tbMatrix mRotation(mRotationX * mRotationZ);

// Skalierungsmatrix generieren: 100% auf der x-Achse, 50% auf der y-Achse // und 500% auf der z-Achse.

tbMatrix mScaling(tbMatrixScaling(tbVector3(1.0f, 0.5f, 5.0f)));

// Determinante einer Matrix berechnen float fDet = tbMatrixDet(mRotation);

// Die Translationsmatrix umkehren. Das wird eine weitere Translationsmatrix hervorbringen, // die einen Vektor in genau die entgegengesetzte Richtung verschiebt.

tbMatrix mInverted(tbMatrixInvert(mTranslation));

// Und nun noch transponieren (nur so zum Spaß) tbMatrix mTransposed(tbMatrixTranspose(mTranslation));

// Eine Projektionsmatrix erzeugen, bei welcher der Betrachter ein Sichtfeld von 120° hat.

// Wir nehmen eine Bildbreite von 800 Pixeln und eine Höhe von 600 Pixeln an. Damit ist das // Bildseitenverhältnis 800 : 600.

// Alle Dreiecke, die sich näher als 0.1 Einheiten auf der z-Achse vom Betrachter weg // befinden, werden geclippt (abgeschnitten). Das gleiche passiert mit weiter als 100 // Einheiten auf der z-Achse entfernten Dreiecken.

tbMatrix mProjection(tbMatrixProjection(TB_DEG_TO_RAD(60.0f), // Sichtfeld

800.0f / 600.0f, // Bildseitenverhältnis 0.1f, 100.0f)); // Clipping-Ebenen

// Eine Kameramatrix erzeugen. Die Kamera soll sich auf der Position (50, 20, -10) befinden // und entlang der z-Achse schauen (Blickpunkt: (50, 20, -9)).

// Es wird keine Kameradrehung durchgeführt, daher ist die y-Achse (0, 1, 0).

tbMatrix mCamera(tbMatrixCamera(tbVector3(50.0f, 20.0f, -10.0f), // Position

tbVector3(50.0f, 20.0f, -9.0f), // Blickpunkt der Kamera tbVector3(0.0f, 1.0f, 0.0f))); // y-Achse

// Nun transformieren wir einen Positionsvektor vUntransformed mit einigen der zuvor // generierten Matrizen, so dass am Ende ein transformierter (und durch die

// Projektionsmatrix projizierter) Vektor in vTransformed herauskommt.

// Der Originalvektor liegt einige Einheiten vor der Kamera. Je weiter er weg liegt, desto // weiter wandert die projizierte x-Koordinate im Bildzentrum bei (0, 0).

tbVector3 Untransformed(55.0f, 20.0f, 0.0f);

tbVector3 vTransformed(tbVector3TransformCoords(vUntransformed, mScaling)); // Skalierung vTransformed = tbVector3TransformCoords(vTransformed, mRotation); // Rotation vTransformed = tbVector3TransformCoords(vTransformed, mTranslation); // Translation vTransformed = tbVector3TransformCoords(vTransformed, mCamera); // Kamera vTransformed = tbVector3TransformCoords(vTransformed, mProjection); // Projektion Listing 2.41 Wie man die Matrix- und Vektorhilfsfunktionen einsetzt, um verschiedene Transformations-matrizen zu generieren, zu manipulieren und Vektoren zu transformieren

2.2.5 Ebenen

Eine Ebene im dreidimensionalen Raum kann man sich wie eine endlos in vier Richtungen ausgedehnte flache Oberfläche vorstellen. Ebenen werden an sich zwar nicht direkt zum Zeichnen verwendet, aber man benötigt sie für viele Berechnungen, deshalb ist es wichtig, dass Sie sich wenigstens ein bisschen mit ihnen auskennen.