EDIT Avertissement : Pour plus de commodité dans cette réponse, les vecteurs avec w == 0 sont appelés vecteurs et avec w == 1 sont appelés points. Bien que, comme l'a souligné FxIII, ce n'est pas une terminologie mathématiquement correcte. Cependant, puisque le point de la réponse n'est pas la terminologie, mais la nécessité de distinguer les deux types de vecteurs, je m'en tiendrai. Pour des raisons pratiques, cette convention est largement utilisée dans le développement de jeux.
Il n'est pas possible de faire la distinction entre les vecteurs et les points sans composante «w». C'est 1 pour les points et 0 pour les vecteurs.
Si les vecteurs sont multipliés avec une matrice de transformation affine 4x4 qui a une traduction dans sa dernière ligne / colonne, le vecteur serait également traduit, ce qui est faux, seuls les points doivent être traduits. Le zéro dans la composante «w» d'un vecteur s'en occupe.
La mise en évidence de cette partie de la multiplication matrice-vecteur le rend plus clair:
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)
C'est-à-dire qu'il serait faux de traduire un vecteur, par exemple un axe de rotation, le résultat est tout simplement faux, En ayant son 4ème composant zéro, vous pouvez toujours utiliser la même matrice qui transforme les points pour transformer l'axe de rotation et le résultat sera valide et sa longueur est conservée tant qu'il n'y a pas d'échelle dans la matrice. C'est le comportement que vous souhaitez pour les vecteurs. Sans le 4ème composant, vous devriez créer 2 matrices (ou 2 fonctions de multiplication différentes avec un 4ème paramètre implicite. Et faire 2 appels de fonction différents pour les points et les vecteurs.
Pour utiliser les registres vectoriels des processeurs modernes (SSE, Altivec, SPU), vous devez quand même passer 4x flottants 32 bits (c'est un registre 128 bits), plus vous devez vous occuper de l'alignement, généralement 16 octets. Vous n'avez donc pas la possibilité de sécuriser l'espace pour le 4ème composant de toute façon.
EDIT:
La réponse à la question est essentiellement
- Soit stocker la composante w: 1 pour les positions et 0 pour les vecteurs
- Ou appelez différentes fonctions de multiplication matrice-vecteur et passez implicitement le composant «w» en choisissant l'une des deux fonctions
Il faut en choisir une, il n'est pas possible de ne stocker que {x, y, z} et de n'utiliser qu'une seule fonction de multiplication matrice-vecteur. XNA par exemple utilise cette dernière approche en ayant 2 fonctions Transform dans sa classe Vector3 , appelées Transform
etTransformNormal
Voici un exemple de code qui montre les deux approches et démontre la nécessité de distinguer les deux types de vecteurs de 1 des 2 façons possibles. Nous déplacerons une entité de jeu avec une position et une direction de regard dans le monde en la transformant avec une matrice. Si nous n'utilisons pas le composant «w», nous ne pouvons plus utiliser la même multiplication matrice-vecteur, comme le montre cet exemple. Si nous le faisons de toute façon, nous obtiendrons une mauvaise réponse pour le look_dir
vecteur transformé :
#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;
}
État initial de l'entité:
position : 0.000000 0.000000 0.000000 1.000000
look_dir : 1.000000 0.000000 0.000000 0.000000
Maintenant, une transformation avec une translation de x + 5 et une rotation de 90 degrés autour de l'axe y sera appliquée à cette entité. La bonne réponse après la transformation est:
position : 5.000000 0.000000 0.000000 1.000000
look_dir : 0.000000 0.000000 1.000000 0.000000
Nous n'obtiendrons la bonne réponse que si nous distinguons les vecteurs avec w == 0 et les positions avec w == 1 de l'une des manières présentées ci-dessus.
r.x = ... + a._14*v.w;
r.y = ... + a._24*v.w;
r.z = ... + a._34*v.w;
r.w = ... + a._44*v.w;
regardez ma réponse pour plus de détails