¿Necesito el componente 'w' en mi class Vector?

Supongamos que está escribiendo código de matriz que maneja la rotation, la traducción, etc. para el espacio en 3D.

Ahora las matrices de transformación tienen que ser 4×4 para adaptarse al componente de traducción.

Sin embargo, en realidad no necesitas almacenar un componente w en el vector, ¿verdad?

Incluso en la split de perspectiva, puede simplemente calcular y almacenar w fuera del vector y dividir la perspectiva antes de regresar del método.

Por ejemplo:

 // post multiply vec2=matrix*vector Vector operator*( const Matrix & a, const Vector& v ) { Vector r ; // do matrix mult rx = a._11*vx + a._12*vy ... real w = a._41*vx + a._42*vy ... // perspective divide r /= w ; return r ; } 

¿Hay algún punto en almacenar w en la class Vector?

EDITAR Descargo de responsabilidad : Por conveniencia en esta respuesta, los vectores con w == 0 se llaman vectores y con w == 1 se llaman puntos. Aunque como FxIII señaló, 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 types de vectores, me apegaré a ella. Por razones prácticas, esta convención es ampliamente utilizada 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 4×4 que tiene una traducción en su última fila / columna, el vector también se traduciría, lo que es incorrecto, solo se deben traducir los puntos. El cero en el componente 'w' de un vector se ocupa de eso.

Resaltar esta parte de la multiplicación de matriz-vector lo hace más claro:

  rx = ... + a._14 * vw; ry = ... + a._24 * vw; rz = ... + a._34 * vw; rw = ... + a._44 * vw; 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 rotation, el resultado es simplemente erróneo. Al tener su 4º componente cero, puede usar la misma matriz que transforma los puntos para transformar el eje de rotation 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 4to componente, tendría que crear 2 matrices (o 2 funciones de multiplicación diferentes con un 4 ° parámetro implícito) y realizar 2 llamadas a funciones diferentes para puntos y vectores.

Para utilizar los loggings vectoriales de las CPU modernas (SSE, Altivec, SPU), debe pasar flotadores de 4x 32 bits de todos modos (es un logging de 128 bits), además debe tener en count la alignment, generalmente 16 bytes. Por lo tanto, no tienes la oportunidad de proteger el espacio para el 4º componente de todos modos.


EDITAR: La respuesta a la pregunta es básicamente

  1. O almacena el componente w: 1 para las posiciones y 0 para los vectores
  2. O invoque diferentes funciones de multiplicación matriz-vector e implícitamente pase el componente 'w' eligiendo una de las funciones

Uno debe elegir uno de ellos, no es posible almacenar solo {x, y, z} y seguir usando solo una function de multiplicación de matriz vectorial. XNA, por ejemplo, utiliza el último enfoque teniendo 2 funciones de Transformación en su class Vector3 , llamadas Transform y TransformNormal

Aquí hay un ejemplo de código que muestra ambos enfoques y demuestra la necesidad de distinguir ambos types de vectores en 1 de las 2 forms posibles. Moviremos una entidad de juego con una position y una dirección de miras en el mundo transformándola con una matriz. Si no usamos el componente 'w', ya no podemos usar la misma multiplicación matriz-vector, como lo demuestra este ejemplo. Si lo hacemos de todos modos, obtendremos una respuesta incorrecta para el vector look_dir 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 = vx * m[0].x + vy * m[1].x + vz * m[2].x + vw * m[3].x; ret.y = vx * m[0].y + vy * m[1].y + vz * m[2].y + vw * m[3].y; ret.z = vx * m[0].z + vy * m[1].z + vz * m[2].z + vw * m[3].z; ret.w = vx * m[0].w + vy * m[1].w + vz * m[2].w + vw * 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), ie positions vector3 TransformV3( const matrix &m, const vector3 &v ) { vector3 ret; ret.x = vx * m[0].x + vy * m[1].x + vz * m[2].x + 1.0f * m[3].x; ret.y = vx * m[0].y + vy * m[1].y + vz * m[2].y + 1.0f * m[3].y; ret.z = vx * m[0].z + vy * m[1].z + vz * 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 = vx * m[0].x + vy * m[1].x + vz * m[2].x + 0.0f * m[3].x; ret.y = vx * m[0].y + vy * m[1].y + vz * m[2].y + 0.0f * m[3].y; ret.z = vx * m[0].z + vy * m[1].z + vz * 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, px, py, pz, pw ); } void PrintV3(const char *msg, const vector3 &p ) { printf("%-15s: %10.6f %10.6f %10.6f\n", msg, px, py, pz); } #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 inicial de la entidad:

 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 traslación de x + 5 y una rotation de 90 grados alnetworkingedor 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 vectores con w == 0 y posiciones con w == 1 en una de las forms presentadas anteriormente.

Si estás haciendo una class Vector, supongo que la class almacenará la descripción de un vector 3D. Los vectores 3D tienen magnitudes x, y y z. Entonces, a less que su vector necesite una magnitud w arbitraria, no, no lo almacenará en la class.

Hay una gran diferencia entre un vector y una matriz de transformación. Dado que DirectX y OpenGL se ocupan de las matrices por usted, normalmente no almaceno una matriz 4×4 en mi código; más bien, guardo rotaciones de Euler (o Quaternions si lo desea, que casualmente sí tiene un componente aw) y x, y, z traducción. La traducción es un vector si lo desea, y la rotation también cabría técnicamente en un vector, donde cada componente almacenaría la cantidad de rotation alnetworkingedor de su eje.

Si quieres sumergirte un poco más en las matemáticas de un vector, un vector euclidiano es solo una dirección y una magnitud. Por lo general, esto se representa por una tripleta de numbers, donde cada número es la magnitud a lo largo de un eje; su dirección está implícita por la combinación de estas tres magnitudes, y la magnitud se puede encontrar con la fórmula de distancia euclidiana . O, a veces, se almacena como una dirección (un vector con longitud = 1) y una magnitud (un flotante), si eso es lo conveniente (por ejemplo, si la magnitud cambia más a menudo que la dirección, puede ser más conveniente simplemente cambiar ese número de magnitud que tomar un vector, normalizarlo y multiplicar las componentes por la nueva magnitud).

La cuarta dimensión en el vector 3D se usa para calcular las transformaciones afines que serán imposibles de calcular usando solo matrices. El espacio sigue siendo tridimensional por lo que esto significa que el cuarto está mapeado en el espacio 3D de alguna manera.

Asignar una dimensión significa que diferentes vectores 4D indican el mismo punto 3D. El map es que si A = [x ', y', z'.w '] y B = [x ", y", z ", w"] representan el mismo punto si x' / x "= y ' / y "= z '/ z" = w' / w "= α, es decir, el componente es proporcional para el mismo coeficiente α.

Dijo que puede express un punto – digamos (1,3,7) – de maneras infinitas como (1,3,7,1) o (2,6,14,2) o (131,393,917,131) o en general (α · 1, α · 3, α · 7, α).

Esto significa que puede escalar un vector 4D a otro que represente el mismo punto 3D de modo que w = 1: la forma (x, y, z, 1) es la forma canónica.

Cuando aplica una matriz a este vector, puede get un vector que no tiene w = 1, pero siempre puede escalar los resultados para almacenarlo en forma canónica. Entonces, la respuesta parece ser "deberías usar vectores 4D al hacer matemáticas pero no almacenar el cuarto componente" .

Esto es bastante cierto, pero hay algunos puntos que no se pueden poner en forma canónica: puntos como (4,2,5,0). Esos puntos son especiales, representan un punto infinito dirigido y se pueden normalizar en un vector unitario consistentemente: puedes ir al infinito de forma segura y regresar (incluso dos veces) sin ser Chuck Norris. Obtendrás una split miserable por cero si tratas de forzar esos vectores en forma canónica.

Ahora ya sabes, ¡entonces la elección es tuya!

Si tu puedes. Tu transformación es incorrecta para algunos types de vectores. Puede ver esto en la biblioteca matemática D3DX: tienen dos funciones diferentes de multiplicación matriz-vector, una para w = 0 y otra para w = 1.

Depende de lo que quieras y necesites. 🙂

Lo almacenaría, porque es necesario para las transformaciones (no se puede multiplicar un vector de 3 con una matriz de 4×4), aunque si siempre tienes un valor de 1, supongo que puedes simularlo.