• Keine Ergebnisse gefunden

Programmierung einer Vektorklasse

Kontakt und Updates

2.2 Einführung in die 3D-Grafik

2.2.2 Theorie der 3D-Grafik

2.2.3.9 Programmierung einer Vektorklasse

r r r r

r r

Man kann also das Punktprodukt der beiden Vektoren berechnen (Zähler im rechten Bruch) und dann durch die Länge des ersten Vektors multipliziert mit der Länge des zweiten Vektors teilen. Davon berechnet man den Arcus-Kosinus und erhält den gesuchten Winkel.

2.2.3.9 Programmierung einer Vektorklasse

Wir wollen nun eine Klasse programmieren, die uns die Arbeit mit Vektoren erleichtert. Die Klasse, die wir implementieren werden, trägt den Namen tbVector3, und sie wird zur TriBase-Engine gehören. Die „3“ am Ende bedeutet, dass wir es mit einem dreidimensionalen Vektor zu tun haben.

Die fertige Klasse legen wir in die Dateien TBVECTOR3.H und TBVECTOR3.CPP im INCLUDE- beziehungsweise SRC-Verzeichnis ab (H-Datei: Deklaration und Inline-Methoden; CPP-Datei:

Definition/Implementierung).

Variablen der Klasse

Woraus besteht ein Vektor? In diesem Fall ganz einfach aus drei Zahlen. Wir wählen für sie den Typ float und nennen die Variablen x, y und z. Diese Variablen sollten von außen einseh- und veränderbar sein, darum legen wir sie in die public-Sektion der Klasse. Es wäre zu um-ständlich, für diese Variablen set- und get-Methoden zu verwenden, weil man einfach viel zu oft auf sie zugreift.

// Klasse für dreidimensionale Vektoren class TRIBASE_API tbVector3

{ public:

float x, y, z; // Vektorkomponenten };

Listing 2.1 Erste Version von tbVector3

Das Makro „TRIBASE_API“ sagt dem Compiler, dass diese Klasse zur TriBase-DLL-Datei ge-hört. Wenn die Engine selbst kompiliert wird, nimmt dieses Makro automatisch den Wert

__declspec(dllexport) an. Dieser Bezeichner weist den Compiler an, die Funktion, die Klasse oder die Variable, auf die er angewandt wird, in die DLL-Datei zu exportieren. Verwendet man die TriBase-Engine jedoch aus einem anderen Projekt heraus an, dann hat das Makro den Wert __declspec(dllimport). Dadurch weiß der Compiler, dass er die Implementierung in der DLL-Datei findet. TRIBASE_API ist wie folgt definiert:

#ifdef TRIBASE_EXPORTS

#define TRIBASE_API __declspec(dllexport)

#else

#define TRIBASE_API __declspec(dllimport)

#endif

Listing 2.2 Das Makro TRIBASE_API

Das Makro TRIBASE_EXPORTS wird vom Compiler automatisch gesetzt, aber nur, wenn man wirklich die Engine kompiliert und nicht bei einer Anwendung, die Gebrauch von ihr macht.

Man kann dies in Visual C++ unter den Projekteinstellungen festlegen.

Konstruktoren

Als Nächstes wollen wir ein paar Konstruktoren für die Klasse implementieren.

ƒ Der Standardkonstruktor, der keine Parameter erwartet, tut einfach gar nichts.

ƒ Der Kopierkonstruktor erwartet eine Referenz auf ein anderes tbVector3-Objekt und ko-piert einfach dessen Komponenten.

ƒ Anschließend fügen wir einen Konstruktor hinzu, der drei Parameter erwartet, nämlich die Vektorkomponenten. Diese werden dann einfach in die Variablen x, y und z eingesetzt.

Und so sieht die Implementierung der Konstruktoren aus:

// Standardkonstruktor: tut nichts tbVector3() { }

// Kopierkonstruktor: kopiert den angegebenen Vektor tbVector3(const tbVector3& v) : x(v.x), y(v.y), z(v.z) { } // Konstruktor, der die angegebenen Vektorkomponenten einsetzt tbVector3(const float vx,

const float vy,

const float vz) : x(vx), y(vy), z(vz) { } Listing 2.3 Drei Konstruktoren von tbVector3

Möglicherweise ist Ihnen ein Teil aus dem vorherigen Listing nicht geläufig: die Initialisie-rungsliste. Die Initialisierungsliste folgt direkt nach einem Doppelpunkt hinter dem Kopf des Konstruktors. Dort kann man jedem der Member-Variablen der Klasse einen Wert zuweisen.

Da die Arbeit der Konstruktoren in diesem Fall nur aus Wertzuweisungen besteht, ist der Funktionsrumpf dementsprechend leer.

Operatoren

Kommen wir nun zum wichtigsten Teil unserer kleinen Vektorklasse! Ein Glück, dass C++

das Überladen von Operatoren erlaubt, denn so lassen sich Vektoradditionen oder Multiplika-tionen viel leichter durchführen (ansonsten wäre man gezwungen, für jede Operation eine ei-gene Funktion anzufertigen, die man dann wahrscheinlich tbVector3Add, tbVector3Multiply o-der ähnlich nennen würde).

Wir beginnen mit den arithmetischen Operatoren. Dazu gehören +, -, * und /. Bei der Multi-plikation (*) und der Division (/) rechnen wir komponentenweise, also genau wie bei der Ad-dition und der Multiplikation. Sonderregelungen wie das Punkt- oder das Kreuzprodukt wer-den wir später separat implementieren. Bei Multiplikation und Division gibt es zwei Möglich-keiten: Einmal können wir mit einem Vektor multiplizieren beziehungsweise durch ihn teilen, und einmal können wir das mit einem Skalar tun, also mit einer Zahl.

// Addition von zwei Vektoren

inline tbVector3 operator + (const tbVector3& a, const tbVector3& b) {

// Komponentenweise Addition der Vektoren a und b return tbVector3(a.x + b.x, a.y + b.y, a.z + b.z);

}

// Subtraktion zweier Vektoren

inline tbVector3 operator - (const tbVector3& a, const tbVector3& b) {

// Hier ist alles genau wie bei der Addition, nur dass subtrahiert wird.

return tbVector3(a.x - b.x, a.y - b.y, a.z - b.z);

}

// Multiplikation zweier Vektoren und Division zweier Vektoren:

// Genau wie bei Addition/Subtraktion, nur * bzw. / anstelle von + bzw. -.

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

// ...

}

inline tbVector3 operator / (const tbVector3& a, const tbVector3& b) {

// ...

}

// Multiplikation mit einem Skalar (float) inline tbVector3 operator * (const tbVector3& v, const float f) {

// Jede Vektorkomponente mit dem Skalar multiplizieren return tbVector3(v.x * f, v.y * f, v.z * f);

}

// Division durch einen Skalar: analog

inline tbVector3 operator / (const tbVector3& v, const float f) {

// ...

}

Listing 2.4 Die arithmetischen Operatoren der tbVector3-Klasse

Eine Kleinigkeit wurde noch übersehen: Nun können wir einen Vektor mit einem Skalar mul-tiplizieren, aber können wir auch einen Skalar mit einem Vektor multiplizieren (vertauscht)?

Nein! Natürlich gilt hier das Kommutativgesetz (Vertauschungsgesetz), aber das kann der C++-Compiler nicht wissen. Darum müssen wir für die Multiplikation mit einem Skalar auch noch eine zweite Version bereitstellen, die zuerst den Skalar und danach den Vektor erwartet:

// Multiplikation mit einem Skalar (vertauscht) inline tbVector3 operator * (const float f, const tbVector3& v) {

// Jede Vektorkomponente mit dem Skalar multiplizieren return tbVector3(v.x * f, v.y * f, v.z * f);

}

Listing 2.5 Vertauschte Multiplikation

Zuweisungsoperatoren

Zu den Zuweisungsoperatoren zählen =, +=, -=, *= und /=. Ich werde hier nur ein paar davon zeigen, denn im Grunde sind sie alle gleich. Beachten Sie, dass die Zuweisungsoperatoren in-nerhalb der Klasse definiert werden, im Gegensatz zu den oben beschriebenen arithmetischen Operatoren, die auf globaler Ebene angelegt sind.

// Zuweisung eines anderen Vektors tbVector3& operator = (const tbVector3& v) {

// Vektorkomponenten kopieren x = v.x;

y = v.y;

z = v.z;

// Liefert eine Referenz auf sich selbst return *this;

}

// Zuweisung und Addition

tbVector3& operator += (const tbVector3& v) {

// Vektor v hinzuaddieren x += v.x;

y += v.y;

z += v.z;

return *this;

}

// Analog für die anderen Operatoren ...

Listing 2.6 Die Zuweisungsoperatoren

Vergleichsoperatoren

Nun fehlen nur noch zwei kleine Operatoren, und wir sind fast fertig! Es fehlen noch die Ope-ratoren == und != zur Überprüfung von Gleichheit beziehungsweise Ungleichheit zweier Vek-toren. Diese sind sehr schnell implementiert:

// Gleichheit: Sind die Vektoren a und b gleich?

bool operator == (tbVector3& a, tbVector3& b) {

// Zwei Vektoren sind gleich, wenn alle ihre Komponenten gleich sind.

return a.x == b.x && a.y == b.y && a.z == b.z;

}

// Ungleichheit

bool operator != (tbVector3& a, tbVector3& b) {

// Zwei Vektoren sind ungleich, wenn sie sich // in mindestens einer Komponente unterscheiden.

return a.x != b.x || a.y != b.y || a.z != b.z;

}

Listing 2.7 Test auf Gleichheit und Ungleichheit

D3DVECTOR

Direct3D verwendet an manchen Stellen eine Struktur namens D3DVECTOR zur Darstellung von Vektoren. Innerlich ist diese Struktur mit unserer tbVector3-Klasse identisch: Sie enthält auch drei float-Elemente namens x, y und z, in denen die Koordinaten des Vektors gespeichert werden. Wenn nun irgendwo ein D3DVECTOR-Wert erwartet wird, wäre es doch schön, wenn wir ganz einfach einen tbVector3-Wert angeben könnten, der dann automatisch umgewandelt wird.

Dazu müssen wir einen Casting-Operator definieren:

// Casting nach D3DVECTOR&

operator D3DVECTOR& () {

// D3DVECTOR ist genauso aufgebaut wie tbVector3!

// Also können wir den this-Zeiger nach D3DVECTOR* casten und dann dereferenzieren.

return *((D3DVECTOR*)(this));

}

Listing 2.8 Casting nach D3DVECTOR3

Umgekehrt sollte es auch möglich sein, einen D3DVECTOR-Wert anzugeben, wo tbVector3 erwar-tet wird. Das können wir lösen, indem wir der tbVector3-Klasse einen weiteren Konstruktor verpassen, die einen D3DVECTOR3-Wert erwartet:

// tbVector3 aus D3DVECTOR erzeugen

tbVector3(const D3DVECTOR& v) : x(v.x), y(v.y), z(v.z) {

// Hier wird nichts getan!

// Die Werte x, y und z werden bereits in der Initialisierungsliste ausgefüllt.

}

Listing 2.9 tbVector3 aus D3DVECTOR erzeugen

Anwendungsbeispiel

Folgendes Listing zeigt, wie man mit der tbVector3-Klasse arbeiten kann. Hinter den Rech-nungen, die hier gemacht werden, steckt übrigens kein besonderer Sinn, sie zeigen eben nur die Funktionsweise.

// Verschiedene Vektoradditionen durchführen

tbVector3 a(tbVector3(17.0f, 23.0f, -1.0f) + tbVector3(0.924f, -0.004f, 9.28f));

tbVector3 b(a + tbVector3(10.0f));

tbVector3 c(a + b);

a += c;

c += a + b;

b += a;

// Vektorsubtraktionen

a = tbVector3(10.0f) – tbVector3(5.0f, 6.0f, 7.0f);

b = a - tbVector3(99.0f);

c -= a - (c - b);

// Vektormultiplikationen float f = 15.0f;

a = tbVector3(1.0f, 1.0f, 1.0f) * f;

b = a * f;

b = f * a;

c = a * b;

c *= a;

a *= b * c;

// Vektordivisionen f = -0.25f;

a = tbVector3(10.0f) / tbVector3(20.0f);

b = a / tbVector3(17.5f);

a /= b;

b = a / f;

Listing 2.10 Anwendung der Operatoren +, -, *, /, +=, -=, *= und /= der tbVector3-Klasse