ARTIKEL2 - VEKTOREN
von SumFuka
Mathematik - Wuuaaahhh! Ob du es mögen wirst oder nicht, aber es ist
sehr wichtig, dass du die hier genannten Sachen verstehst! Jedes mal, wenn
du eine Rakete abfeuerst, brauchst du einen Vektor. Jedesmal, wenn sich irgendetwas
bewegt, brauchst du Vektoren.
WAS IST EIN VEKTOR?
In einer dreidimensionalen Welt wie Quake besteht ein Vektor einfach aus drei
Zahlen: einer x-Komponente, einer y-Komponente und einer z-Komponente. In
q_shared.h sehen wir folgende Definition:
typedef float vec_t;
typedef vec_t vec2_t[2];
typedef vec_t vec3_t[3];
typedef vec_t vec4_t[4];
typedef vec_t vec5_t[5];
Falls du noch nicht so erfahren mit C bist, die typdef-Anweisung
verknüpft einen komplizierten Datentyp mit einem anderen. Die erste Zeile
definiert, dass ein einfach dimensionaler Vektortyp, vec_t, einfach eine Gleitkommazahl
sein soll (also z.B: 23.4567, im Gegensatz zu 23). Die nächsten typedef's
definieren 2, 3, 4 und 5 dimensionale Vektortypen. Der am häufigsten
gebrauchte ist ein 3D Vektor, vec3_t. Er besteht einfach aus einem Array (Feld)
von drei vec_t's (einfacher gesagt, einem Feld von drei floats). Diese drei
floats repräsentieren das, was wir x-, y-, und z-Komponente nennen.
DER URSPRUNG
Ok, der einfachste Vektor ist der Ursprungsvektor. Diesen schreibt man in
Koordiantenform so: (0.0, 0.0, 0.0). Die x-, y- und z-Komponenten sind
alle 0. Laß uns
diesen in C nachbauen:
vec3_t origin;
origin[0] = 0.0;
origin[1] = 0.0;
origin[2] = 0.0;
Nun laß uns einen Vektor basteln, der die Position des
player0 bei (55.0, -23.0, 12.5) darstellt:
vec3_t player0pos;
player0pos[0] = 55.0;
player0pos[1] = -23.0;
player0pos[2] = 12.5;
Also, wo steht unser player0 nun? Nunja, das ist abhängig
vom...
KOORDINATENSYSTEM
Laß uns annehmen, unser Quake Level ist dreidimensional (und mehrfach
übereinander geschichtet). Zum Beispiel eine Spacemap. Laß uns
weiter annehmen, der Ort (0.0, 0.0, 0.0) ist genau über einer Plattform,
die irgendwo mitten in der Karte ist, die sich links und rechts, vorne
und
hinten, nach oben und nach unten ein wenig ausdehnt. Aber was ist "links,
rechts, vorne, hinten, oben, unten"?
Quake benutzt das sogenannte "linkshändige 3D Koordiantensystem".
Das bedeuted, du nimmst deine linke Hand, und zeigst damit nach vorne. Laß
deinen Daumen nach oben zeigen, deinen Zeigefinger nach vorne (also von dir
weg) und den Mittelfinger nach rechts. Laß uns annehmen, wir stehen
direkt im Ursprung der Space Map, und schauen in Richtung Norden.
In dem Fall zeigt dein Mittelfinger in die Richtung der positiven
x-Achse, dein Zeigefinger zeigt auf die positive y Achse, und dein Daumen
zeigt auf die positive Z-Achse.
Zurück zum Beispiel der Position von player0. Wir sehen
nun, der player0 steht rechts von uns (x=55.0 ist positiv), hinter uns (y=
-23.0 ist negativ) und über uns (z=12.5 ist positiv).
Wenn wir unseren player0 also spontan nach 64 Einheiten Ost,
und 64 Einheiten nach oben teleportieren wollen, wie können wir das tun?
Das geht z.B. so:
player0pos[0] += 64.0;
player0pos[2] += 64.0;
Doch dafür gibt es einen noch viel besseren Weg...
GESCHWINDIGKEITSVEKTOREN
Alle Vektoren, mit denen wir bisher gearbeitet haben waren Positionsvektoren
- also eine (x,y,z) Position irgendwo in der Spiele-Welt. Es gibt aber noch
eine andere Verwendung für Vektoren - die Darstellung der Geschwindigkeit
(velocity). Wie beschreiben wir die Geschwindigkeit einer Rakete, die 45 Grad
nach oben, mit 900 Einheiten pro Sekunde fliegt? Mit einem Vektor!
Das mag ein wenig verwirrend sein, nachdem die Rakete eine Position
und eine Geschwindigkeit hat, beide dargestellt durch vec3_t.
vec3_t rocketpos;
vec3_t rocketvel;
rocketpos[0] = 0.0;
rocketpos[1] = 0.0;
rocketpos[2] = 0.0;
rocketvel[0] = 636.39;
rocketvel[1] = 0.0;
rocketvel[2] = 636.39;
// nun bewegen wir die Rakete in der
Annahme, das eine Sekunde vergangen ist
rocketpos[0] += rocketvel[0];
rocketpos[1] += rocketvel[1];
rocketpos[2] += rocketvel[2];
// eine weiter Sekunde später...
rocketpos[0] += rocketvel[0];
rocketpos[1] += rocketvel[1];
rocketpos[2] += rocketvel[2];
Das Ergebnis: Nach einer Sekunde befindet sich unsere Rakete
bei (636.39, 0, 636.39). Aber warum haben wir 636.39 benutzt, und nicht 900?
Nun, die Antwort ist einfach. Wir bewegen uns hier ja im zweidimensionalen
(sowohl x als auch y Richtung der Rakete ändert sich). Daher ist der
gesamt zurückgelegte Wert (900) die längste Seite in dem Dreieck,
während die kürzeren beiden jeweils den Unterschied in der jeweiligen
Dimension angeben (in unserem Fall 636.39). Hier kommt der gute alte Pythagoras
mit a^2 + b^2 = c^2 zu Gute. Also wenn a und b die Werte 636.39 haben... dann
hat C 900. Bei dreidimensionalen Bewegungen wird das Ganze noch ein Stück
komplizierter...

RICHTUNGEN
Anstatt sich darüber Sorgen zu machen 636.39 aus 900 auszurechnen, gibt
es einen besseren Weg um mit der Geschwindigkeit 900 Einheiten pro Sekunde
und 45 Grad nach oben gerichtet zu arbeiten. Wir teilen es einfach in zwei
Komponenten - die Richtung und die Geschwindigkeit.
Ein Richtungsvektor ist ein Vektor in eine bestimmte Richtung,
mit genau der Länge 1.0. Zum Beispiel (1.0, 0.0, 0.0) oder (0.707, 0.0,
-0.707) usw.. Wir werden jeden Vektor mit genau der Länge 1.0 als Normalenvektor
bezeichnen.
Geschwindigkeit (speed) ist eine lineare Angabe (KEIN Vektor!).
Zum Beispiel 900.0, 0.0 oder -450.0.Eine Geschwindigkeit von 0.0 stellt Stillstand
dar, wogegen negative Geschwindigkeit Rückwärtsbewegung darstellt.
Was ist einfacher? Nun, es gibt eine einfache Formel die es
uns erlaubt Entities durch die Welt zu bewegen: enpos = startpos + speed
* direction. Zum Beispiel können wir damit unsere Rakete bewegen:
vec3_t rocketpos;
vec3_t rocketdir;
float rocketspeed;
rocketpos[0] = 0.0;
rocketpos[1] = 0.0;
rocketpos[2] = 0.0;
rocketdir[0] = 0.707;
rocketdir[1] = 0.0;
rocketdir[2] = 0.707;
// nun bewegen wir die Rakete in der
Annahme, das eine Sekunde vergangen ist
rocketpos[0] += rocketspeed * rocketdir[0];
rocketpos[1] += rocketspeed * rocketdir[1];
rocketpos[2] += rocketspeed * rocketdir[2];
Aber wieso ist das einfacher? Quake unterstützt uns mit
verschiedenen Richtungsvektoren im Code. Wir entdecken z.B. öftes den "forward" Vektor.
Dieser zeigt immer gneau in die Richtung, in die wir schauen, und damit zielen.
Also, was tun um eine Rakete abzufeuern? Wir nehmen eine Geschwindigkeit
(velocity) und multiplizieren diese mit 900. Um eine Railgun abzufeuern,
verfolge den Strahl ausgehend vom forward Vektor (also der Blickrichtung,
in die man gesehen hat, als man den Feuerknopf gedrückt hat) um 8192 Einheiten.
John Carmack hat uns zudem eine mini-library auf den Weg gegeben
für
die ...
VEKTOR FUNKTIONEN
Nachdem Vektoren so häufig in Quake benutzt werden ist es durchaus sinnvoll
für die Veränderungen dieser (kopiern, addieren, mutliplizieren...) Funktionen
zu definieren.
Nicht nur, dass es eine gute Übung ist diese Funktionen zu benutzen (insbesondere
nachdem sie schon da sind), sondern es ist auch wichtig sich darüber klar zu
werden, das die Standardoperatioren +, * usw. NICHT benutzt werden dürfen! Denn
vec3_t ist ein Feld, und der standardmäßige mathematische Operator kann in C
nicht auf ein Feld benutzt werden (zumindest nicht so, wie man es erwarten würde).
Als Beispiel:
// vorsicht, das hier ist FALSCH
vector_a *= 100.0; // FALSCH!
vector_c = vector_a + vector_b; // FALSCH!
// wir benutzen Funktionen zum Addieren, Mulitplizieren
usw.
VectorScale (vector_a, 100.0, vector_a); // RICHTIG!
VectorAdd (vector_a, vector_b, vector_c); // RICHTIG!
Hier ist eine vollständige Liste der Funktionen (siehe q_shared.h):
VectorSubtract(a,b,c) - Ziehe b von a ab, und speichere
das Ergebnis in c
VectorAdd(a,b,c) - addiere a zu b, speichere in c
VectorCopy(a,b) - kopiere a nach b
VectorScale(v,s,o) - multipliziere v mit
s, schreibe in o
VectorMA(v,s,b,o) - multipliziere b mit s, addiere v, schreibe
in o
VectorClear(a) - Reset
VectorNegate(a,b) - negiere (Vorzeichenwechsel) a, speichere in b
VectorSet(v,x,y,z) - setze den Vektor v mit den Komponenten x, y, z
Vector4Copy(a,b) - kopiere a nach b - im Falle von 4 dimensionalen Vektoren
SnapVector(v) - runde den Vektor auf Integer-Werte
Alles verstanden? Ist dir aufgefallen, dass die meisten Funktionen
ein, zwei oder drei Parameter entgegennehmen, um sie dann im zweiten, dritten
oder vierten Parameter zu speichern [Ausnahmen sind VectorClear() und VektorSet()]?
Der Aufruf VectorSubtract(a,b,c) wird weder a noch b verändern - das Ergebnis
wird einfach in c gespeichert. [Die Ausnahme wäre es, wenn wir VectorSubtract(a,b,a)
aufrufen würden]
VectorMA(v,s,b,o)
Du hast dich vielleicht schon gefragt, was diese Funktion soll. Es ist eine
sinnvolle Kombination von Vektormultiplikation und Vektoraddition. Sie wird
sehr häufig benutzt, daher ist es wichtig zu verstehen, was sie tut. Gehen
wir zurück zu unserem Raketen-Beispiel...
vec3_t rocketpos;
vec3_t rocketdir;
float rocketspeed;
VectorClear(rocketpos);
VectorSet(rocketdir, 0.707, 0.0, 0.707);
rocketspeed = 900.0;
// nun bewegen wir die Rakete in der
Annahme, das eine Sekunde vergangen ist
VectorMA(rocketpos, rocketspeed, rocketdir, rocketpos);
Dir fällt sicherlich auf, dass wir genau das
Selbe getan haben wie zuvor, nur dieses mal mit weniger als der Hälfte an
Codezeilen. Und es ist um einiges schicker, da wir die vordefinierten Vektorfunktionen
benutzt haben. VectorMA() ist wirklich brauchbar - mach dich mit ihr vertraut...
als ob es deine Freundin wäre...intim vertraut... :o)
ALLES MITEINANDER KOMBINIEREN
Ok, wollen wir nun das erlernte in Aktion betrachten. Öffne g_missile.c und
such nach folgender Stelle:
VectorCopy( start, bolt->s.pos.trBase );
VectorScale( dir, 900, bolt->s.pos.trDelta );
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
VectorCopy (start, bolt->r.currentOrigin);
Zuerst nehmen wir an, dass "start" die Position ist, von der
aus die Rakete abgefeuert wird (also knapp vor dem Spieler). Und "dir" ist
die Richtung, in die der Spieler schaut. Also macht dieses Codestück einfach
folgendes:
Kopiere den Startvektor in die Untervariable trBase der Struktur
bolt
Multipliziere den Richtungsvektor "dir" mit 900 und speichere
in trDelta
Konvertiere die Werte von trDelta von Gleitkommazahlen in Ganzzahlen
Kopiere den Startvektor in die Untervariable currentOrigin
(aktueller Urpsrung) der Struktur bolt
Das ist auch schon alles, was man tun muß. Schau dir noch ein
paar andere Funktionen wie fire_plasma und weapon_railgun_fire an. Versuche
herauszufinden wie schnell sich das Plasma bewegt. Was ist die Reichweite
der Railgun? Oder der Lightning Gun?
Wenn du all diese Fragen beantworten kannst: Gratuliere, du
bist wirklich begabt im Umgang mit Vektoren!
<< zurück/back >>
|