EDIT Disclaimer : In dieser Antwort werden Vektoren mit w == 0 als Vektoren und mit w == 1 als Punkte bezeichnet. Obwohl FxIII darauf hinwies, ist dies keine mathematisch korrekte Terminologie. Da der Punkt der Antwort jedoch nicht die Terminologie ist, sondern die Notwendigkeit, beide Vektortypen zu unterscheiden, werde ich mich daran halten. Aus praktischen Gründen ist diese Konvention in der Spieleentwicklung weit verbreitet.
Es ist nicht möglich, zwischen Vektoren und Punkten ohne 'w'-Komponente zu unterscheiden. Es ist 1 für Punkte und 0 für Vektoren.
Wenn Vektoren mit einer affinen 4x4-Transformationsmatrix multipliziert werden, deren letzte Zeile / Spalte eine Translation enthält, wird der Vektor ebenfalls translatiert, was falsch ist. Es müssen nur Punkte translatiert werden. Dafür sorgt die Null in der 'w'-Komponente eines Vektors.
Das Hervorheben dieses Teils der Matrix-Vektor-Multiplikation macht es klarer:
r.x = ... + a._14 * v.w;
r.y = ... + a._24 * v.w;
r.z = ... + a._34 * v.w;
r.w = ... + a._44 * v.w;
a._14, a._24 and a._34 is the translational part of the affine matrix.
Without a 'w' component one has to set it implicitly to 0 (vector) or to 1 (point)
Das heißt, es wäre falsch, einen Vektor, beispielsweise eine Rotationsachse, zu verschieben. Das Ergebnis ist einfach falsch. Wenn Sie die vierte Komponente Null haben, können Sie immer noch dieselbe Matrix verwenden, die die Punkte transformiert, um die Rotationsachse zu transformieren. Das Ergebnis ist dann gültig und seine Länge bleibt erhalten, solange es keine Skala in der Matrix gibt. Das ist das Verhalten, das Sie für Vektoren wünschen. Ohne die 4. Komponente müssten Sie 2 Matrizen (oder 2 verschiedene Multiplikationsfunktionen mit einem impliziten 4. Parameter) erstellen und 2 verschiedene Funktionsaufrufe für Punkte und Vektoren durchführen.
Um die Vektorregister moderner CPUs (SSE, Altivec, SPUs) zu verwenden, müssen Sie ohnehin 4x 32-Bit-Floats (ein 128-Bit-Register) übergeben und sich um die Ausrichtung kümmern, normalerweise 16 Byte. Sie haben also ohnehin keine Chance, den Platz für die 4. Komponente zu sichern.
EDIT:
Die Antwort auf die Frage ist im Grunde
- Speichern Sie entweder die w-Komponente: 1 für Positionen und 0 für Vektoren
- Oder rufen Sie verschiedene Matrix-Vektor-Multiplikationsfunktionen auf und übergeben Sie implizit die 'w'-Komponente, indem Sie eine der beiden Funktionen auswählen
Man muss eine davon auswählen, es ist nicht möglich, nur {x, y, z} zu speichern und trotzdem nur eine Matrix-Vektor-Multiplikationsfunktion zu verwenden. XNA verwendet zum Beispiel den letzteren Ansatz, indem es 2 Transformationsfunktionen in seiner Vector3- Klasse namens Transform
und hatTransformNormal
Hier ist ein Codebeispiel, das beide Ansätze zeigt und die Notwendigkeit demonstriert, beide Arten von Vektoren auf eine der beiden möglichen Arten zu unterscheiden. Wir werden eine Spieleinheit mit einer Position und einer Blickrichtung in der Welt bewegen, indem wir sie mit einer Matrix transformieren. Wenn wir die 'w'-Komponente nicht verwenden, können wir nicht mehr dieselbe Matrix-Vektor-Multiplikation verwenden, wie dieses Beispiel zeigt. Wenn wir es trotzdem tun, erhalten wir eine falsche Antwort für den transformierten look_dir
Vektor:
#include <cstdio>
#include <cmath>
struct vector3
{
vector3() {}
vector3(float _x, float _y, float _z) { x = _x; y = _y; z = _z; }
float x, y, z;
};
struct vector4
{
vector4() {}
vector4(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; }
float x, y, z, w;
};
struct matrix
{
// convenience column accessors
vector4& operator[](int col) { return cols[col]; }
const vector4& operator[](int col) const { return cols[col]; }
vector4 cols[4];
};
// since we transform a vector that stores the 'w' component,
// we just need this one matrix-vector multiplication
vector4 operator*( const matrix &m, const vector4 &v )
{
vector4 ret;
ret.x = v.x * m[0].x + v.y * m[1].x + v.z * m[2].x + v.w * m[3].x;
ret.y = v.x * m[0].y + v.y * m[1].y + v.z * m[2].y + v.w * m[3].y;
ret.z = v.x * m[0].z + v.y * m[1].z + v.z * m[2].z + v.w * m[3].z;
ret.w = v.x * m[0].w + v.y * m[1].w + v.z * m[2].w + v.w * m[3].w;
return ret;
}
// if we don't store 'w' in the vector we need 2 different transform functions
// this to transform points (w==1), i.e. positions
vector3 TransformV3( const matrix &m, const vector3 &v )
{
vector3 ret;
ret.x = v.x * m[0].x + v.y * m[1].x + v.z * m[2].x + 1.0f * m[3].x;
ret.y = v.x * m[0].y + v.y * m[1].y + v.z * m[2].y + 1.0f * m[3].y;
ret.z = v.x * m[0].z + v.y * m[1].z + v.z * m[2].z + 1.0f * m[3].z;
return ret;
}
// and this one is to transform vectors (w==0), like a direction-vector
vector3 TransformNormalV3( const matrix &m, const vector3 &v )
{
vector3 ret;
ret.x = v.x * m[0].x + v.y * m[1].x + v.z * m[2].x + 0.0f * m[3].x;
ret.y = v.x * m[0].y + v.y * m[1].y + v.z * m[2].y + 0.0f * m[3].y;
ret.z = v.x * m[0].z + v.y * m[1].z + v.z * m[2].z + 0.0f * m[3].z;
return ret;
}
// some helpers to output the results
void PrintV4(const char *msg, const vector4 &p ) { printf("%-15s: %10.6f %10.6f %10.6f %10.6f\n", msg, p.x, p.y, p.z, p.w ); }
void PrintV3(const char *msg, const vector3 &p ) { printf("%-15s: %10.6f %10.6f %10.6f\n", msg, p.x, p.y, p.z); }
#define STORE_W 1
int main()
{
// suppose we have a "position" of an entity and its
// look direction "look_dir" which is a unit vector
// we will move this entity in the world
// the entity will be moved in the world by a translation
// in x+5 and a rotation of 90 degrees around the y-axis
// let's create that matrix first
// the rotation angle, 90 degrees in radians
float a = 1.570796326794896619f;
matrix moveEntity;
moveEntity[0] = vector4( cos(a), 0.0f, sin(a), 0.0f);
moveEntity[1] = vector4( 0.0f, 1.0f, 0.0f, 0.0f);
moveEntity[2] = vector4(-sin(a), 0.0f, cos(a), 0.0f);
moveEntity[3] = vector4( 5.0f, 0.0f, 0.0f, 1.0f);
#if STORE_W
vector4 position(0.0f, 0.0f, 0.0f, 1.0f);
// entity is looking towards the positive x-axis
vector4 look_dir(1.0f, 0.0f, 0.0f, 0.0f);
// move the entity using the matrix
// we can use the same function for the matrix-vector multiplication to transform
// the position and the unit vector since we store 'w' in the vector
position = moveEntity * position;
look_dir = moveEntity * look_dir;
PrintV4("position", position);
PrintV4("look_dir", look_dir);
#else
vector3 position(0.0f, 0.0f, 0.0f);
// entity is looking towards the positive x-axis
vector3 look_dir(1.0f, 0.0f, 0.0f);
// move the entity using the matrix
// we have to call 2 different transform functions one to transform the position
// and the other one to transform the unit-vector since we don't
// store 'w' in the vector
position = TransformV3(moveEntity, position);
look_dir = TransformNormalV3(moveEntity, look_dir);
PrintV3("position", position);
PrintV3("look_dir", look_dir);
#endif
return 0;
}
Ursprünglicher Entitätsstatus:
position : 0.000000 0.000000 0.000000 1.000000
look_dir : 1.000000 0.000000 0.000000 0.000000
Nun wird eine Transformation mit einer Verschiebung von x + 5 und einer Drehung von 90 Grad um die y-Achse auf dieses Objekt angewendet. Die richtige Antwort nach der Transformation lautet:
position : 5.000000 0.000000 0.000000 1.000000
look_dir : 0.000000 0.000000 1.000000 0.000000
Die richtige Antwort erhalten wir nur, wenn wir Vektoren mit w == 0 und Positionen mit w == 1 auf eine der oben dargestellten Arten unterscheiden.
r.x = ... + a._14*v.w;
r.y = ... + a._24*v.w;
r.z = ... + a._34*v.w;
r.w = ... + a._44*v.w;
Sehen Sie sich meine Antwort für Details an