Saturday, July 13, 2019

mathematics - Do I need the 'w' component in my Vector class?


Assume you're writing matrix code that handles rotation, translation etc for 3d space.


Now the transformation matrices have to be 4x4 to fit the translation component in.


However, you don't actually need to store a w component in the vector do you?


Even in perspective division, you can simply compute and store w outside of the vector, and perspective divide before returning from the method.


For example:


// post multiply vec2=matrix*vector

Vector operator*( const Matrix & a, const Vector& v )
{
Vector r ;
// do matrix mult
r.x = a._11*v.x + a._12*v.y ...

real w = a._41*v.x + a._42*v.y ...

// perspective divide
r /= w ;


return r ;
}

Is there a point in storing w in the Vector class?



Answer



EDIT Disclaimer: For convenience in this answer vectors with w==0 are called vectors and with w==1 are called points. Although as FxIII pointed out, that is not a mathematically correct terminology. However, since the point of the answer is not the terminology, but the need to distinguish both types of vectors, I'll stick to it. For practical reason this convention is widely used in game-development.




It is not possible to distinguish between vectors and points without a 'w' component. It is 1 for points and 0 for vectors.


If vectors are multiplied with a 4x4 affine transformation-matrix that has a translation in its last row/column, the vector would also be translated, which is wrong, only points must be translated. The zero in the 'w' component of a vector takes care of that.



Highlighting this part of the matrix-vector multiplication makes it more clear:


    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)

I.e. it would be wrong to translate a vector, for instance a rotation axis, the result is simply wrong, By having it's 4th component zero you can still use the same matrix that transforms the points to transform the rotation axis and the result will be valid and its length is preserved as long as there is no scale in the matrix. That is the behavior you want for vectors. Without the 4th component you would have to create 2 matrices (or 2 different multiplication functions with an implicit 4th parameter. and make 2 different function calls for points and vectors.



In order to use the vector registers of modern CPUs (SSE, Altivec, SPUs) you have to pass 4x 32 bit floats anyway (its a 128 bit register), plus you have to take care of the alignment, usually 16 bytes. So you don't have the chance to safe the space for the 4th component anyway.




EDIT: The answer to the question is basically



  1. Either store the w-component: 1 for positions and 0 for vectors

  2. Or call different matrix-vector multiplication functions and implicitly pass the 'w' component by choosing one of either functions


One must pick one of them, it is not possible to store only {x, y, z} and still use only one matrix-vector multiplication function. XNA for example uses the latter approach by having 2 Transform functions in its Vector3 class, called Transform and TransformNormal


Here is a code example that shows both approaches and demonstrates the need to distinguish both kind of vectors in 1 of the 2 possible ways. We will move a game-entity with a position and a look-direction in the world by transforming it with a matrix. If we don't use the 'w' component, we can't use the same matrix-vector multiplication anymore, as this example demonstrates. If we do it anyway, we will get a wrong answer for the transformed look_dir vector:


#include 

#include

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;
}

Initial Entity state:


position       :   0.000000   0.000000   0.000000   1.000000

look_dir : 1.000000 0.000000 0.000000 0.000000

Now a transformation with a translation of x+5 and a rotation of 90 degrees around the y-axis will be applied to this entity. The correct answer after the tranformation is:


position       :   5.000000   0.000000   0.000000   1.000000
look_dir : 0.000000 0.000000 1.000000 0.000000

We will only get the correct answer if we distinguish vectors with w==0 and positions with w==1 in one of the above presented ways.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...