EDIT Disclaimer : Dla wygody w tej odpowiedzi wektory o w == 0 są nazywane wektorami, a w = = 1 są nazywane punktami. Chociaż, jak wskazał FxIII, nie jest to matematycznie poprawna terminologia. Ponieważ jednak odpowiedzią nie jest terminologia, ale potrzeba rozróżnienia obu typów wektorów, będę się jej trzymać. Ze względów praktycznych konwencja ta jest szeroko stosowana w tworzeniu gier.
Niemożliwe jest rozróżnienie wektorów i punktów bez komponentu „w”. Wynosi 1 dla punktów i 0 dla wektorów.
Jeśli wektory zostaną pomnożone przez macierz transformacji afinicznej 4x4, która ma translację w swoim ostatnim wierszu / kolumnie, wektor również zostałby przetłumaczony, co jest błędne, muszą zostać przetłumaczone tylko punkty. Zajmuje się tym zero w składniku „w” wektora.
Podkreślenie tej części mnożenia macierzy i wektora czyni jaśniej:
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)
Tzn. Źle byłoby przetłumaczyć wektor, na przykład oś obrotu, wynik jest po prostu zły. Mając czwarty składnik zerowy, nadal możesz użyć tej samej macierzy, która przekształca punkty, aby przekształcić oś obrotu, a wynik będzie ważny a jego długość jest zachowana, dopóki matryca nie ma skali. Takie zachowanie chcesz dla wektorów. Bez czwartego komponentu musielibyśmy utworzyć 2 macierze (lub 2 różne funkcje mnożenia z niejawnym czwartym parametrem.) I wykonać 2 różne wywołania funkcji dla punktów i wektorów.
Aby użyć rejestrów wektorowych współczesnych procesorów (SSE, Altivec, SPU), musisz przekazać 4x 32-bitowe zmiennoprzecinkowe (jest to rejestr 128-bitowy), a ponadto musisz zadbać o wyrównanie, zwykle 16 bajtów. Więc i tak nie masz szansy zabezpieczyć miejsca dla czwartego komponentu.
EDYCJA:
Odpowiedź na pytanie jest w zasadzie
- Albo przechowuj składnik w: 1 dla pozycji i 0 dla wektorów
- Lub wywołaj różne funkcje mnożenia macierzy i wektora i pośrednio przekaż składnik „w”, wybierając jedną z obu funkcji
Trzeba wybrać jedną z nich, nie można zapisać tylko {x, y, z} i nadal używać tylko jednej funkcji mnożenia macierzy-wektora. XNA na przykład wykorzystuje to drugie podejście, mając 2 funkcje transformacji w swojej klasie Vector3 o nazwie Transform
iTransformNormal
Oto przykład kodu, który pokazuje oba podejścia i pokazuje potrzebę rozróżnienia obu rodzajów wektorów na 1 z 2 możliwych sposobów. Poruszamy byt gry o pozycji i kierunku patrzenia w świecie, przekształcając go za pomocą matrycy. Jeśli nie użyjemy komponentu „w”, nie będziemy już mogli używać tego samego mnożenia macierzy i wektora, jak pokazuje ten przykład. Jeśli i tak to zrobimy, otrzymamy złą odpowiedź na transformowany look_dir
wektor:
#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;
}
Stan jednostki początkowej:
position : 0.000000 0.000000 0.000000 1.000000
look_dir : 1.000000 0.000000 0.000000 0.000000
Teraz transformacja z translacją x + 5 i obrotem o 90 stopni wokół osi y zostanie zastosowana do tego elementu. Prawidłowa odpowiedź po transformacji to:
position : 5.000000 0.000000 0.000000 1.000000
look_dir : 0.000000 0.000000 1.000000 0.000000
Poprawną odpowiedź otrzymamy tylko wtedy, gdy rozróżnimy wektory o == 0 i pozycje o = = 1 na jeden z powyższych sposobów.
r.x = ... + a._14*v.w;
r.y = ... + a._24*v.w;
r.z = ... + a._34*v.w;
r.w = ... + a._44*v.w;
spójrz na moją odpowiedź, aby poznać szczegóły