Exención de responsabilidad de EDIT : por conveniencia en esta respuesta, los vectores con w == 0 se denominan vectores y con w == 1 se denominan puntos. Aunque, como señaló FxIII, esa no es una terminología matemáticamente correcta. Sin embargo, dado que el punto de la respuesta no es la terminología, sino la necesidad de distinguir ambos tipos de vectores, me atendré a ella. Por razones prácticas, esta convención se usa ampliamente en el desarrollo de juegos.
No es posible distinguir entre vectores y puntos sin un componente 'w'. Es 1 para puntos y 0 para vectores.
Si los vectores se multiplican con una matriz de transformación afín 4x4 que tiene una traducción en su última fila / columna, el vector también se traduciría, lo cual es incorrecto, solo se deben traducir los puntos. El cero en el componente 'w' de un vector se encarga de eso.
Al resaltar esta parte de la multiplicación matriz-vector, queda más claro:
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)
Es decir, sería incorrecto traducir un vector, por ejemplo, un eje de rotación, el resultado es simplemente incorrecto. Al tener su cuarto componente cero, aún puede usar la misma matriz que transforma los puntos para transformar el eje de rotación y el resultado será válido y su longitud se conserva siempre que no haya escala en la matriz. Ese es el comportamiento que desea para los vectores. Sin el cuarto componente, tendría que crear 2 matrices (o 2 funciones de multiplicación diferentes con un cuarto parámetro implícito) y realizar 2 llamadas a funciones diferentes para puntos y vectores.
Para usar los registros vectoriales de las CPU modernas (SSE, Altivec, SPU), debe pasar 4x 32 bits flotantes de todos modos (es un registro de 128 bits), además de tener que ocuparse de la alineación, generalmente 16 bytes. Por lo tanto, no tiene la oportunidad de proteger el espacio para el cuarto componente de todos modos.
EDITAR:
la respuesta a la pregunta es básicamente
- Almacene el componente w: 1 para posiciones y 0 para vectores
- O llame a diferentes funciones de multiplicación de matriz-vector y pase implícitamente el componente 'w' eligiendo una de las funciones
Uno debe elegir uno de ellos, no es posible almacenar solo {x, y, z} y aún usar solo una función de multiplicación de matriz-vector. XNA, por ejemplo, utiliza el último enfoque al tener 2 funciones de transformación en su clase Vector3 , llamadas Transform
yTransformNormal
Aquí hay un ejemplo de código que muestra ambos enfoques y demuestra la necesidad de distinguir ambos tipos de vectores en 1 de las 2 formas posibles. Vamos a mover una entidad de juego con una posición y una dirección de mirada en el mundo transformándola con una matriz. Si no usamos el componente 'w', ya no podemos usar la misma multiplicación de matriz-vector, como lo demuestra este ejemplo. Si lo hacemos de todos modos, obtendremos una respuesta incorrecta para el look_dir
vector transformado :
#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;
}
Estado de entidad inicial:
position : 0.000000 0.000000 0.000000 1.000000
look_dir : 1.000000 0.000000 0.000000 0.000000
Ahora se aplicará a esta entidad una transformación con una traducción de x + 5 y una rotación de 90 grados alrededor del eje y. La respuesta correcta después de la transformación es:
position : 5.000000 0.000000 0.000000 1.000000
look_dir : 0.000000 0.000000 1.000000 0.000000
Solo obtendremos la respuesta correcta si distinguimos los vectores con w == 0 y las posiciones con w == 1 en una de las formas presentadas anteriormente.
r.x = ... + a._14*v.w;
r.y = ... + a._24*v.w;
r.z = ... + a._34*v.w;
r.w = ... + a._44*v.w;
mira mi respuesta para más detalles