EDIT Застереження : Для зручності у цій відповіді вектори з w == 0 називаються векторами, а з w == 1 називаються точками. Хоча, як зазначав FxIII, це не є математично правильною термінологією. Однак, оскільки суть відповіді - не термінологія, а необхідність розрізняти обидва типи векторів, я дотримуюся цього. З практичної причини ця конвенція широко використовується в ігровій розробці.
Неможливо розрізнити вектори та точки без компонента 'w'. Це 1 для балів і 0 для векторів.
Якщо вектори множать на матрицю перетворення афінної трансформації 4х4, яка має переклад в останньому рядку / стовпчику, вектор також буде переведений, що невірно, повинні бути переведені лише точки. Нуль в '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;
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)
Тобто було б неправильно перекладати вектор, наприклад, вісь обертання, результат просто неправильний, маючи 4-й нульовий компонент, ви все одно можете використовувати ту саму матрицю, яка перетворює точки для перетворення осі обертання, і результат буде дійсним і його довжина зберігається до тих пір, поки в матриці немає шкали. Така поведінка ви хочете для векторів. Без 4-го компонента вам доведеться створити 2 матриці (або 2 різні функції множення з неявним 4-м параметром.) Та зробити 2 різні виклики функцій для точок і векторів.
Для того, щоб використовувати векторні регістри сучасних процесорів (SSE, Altivec, SPU), вам в будь-якому випадку потрібно пройти 4х 32-бітові поплавці (це 128-бітний регістр), плюс вам потрібно подбати про вирівнювання, як правило, 16 байт. Таким чином, у вас немає шансів зберегти місце для 4-го компонента.
EDIT:
Відповідь на питання в основному
- Або зберігайте w-компонент: 1 для позицій і 0 для векторів
- Або викликати різні функції множення матричного вектора та неявно передавати компонент 'w', вибравши одну з будь-яких функцій
Треба вибрати одну з них, зберігати не можна лише {x, y, z} і все ж використовувати лише одну функцію множення матрично-векторного. Наприклад, XNA використовує останній підхід, маючи 2 функції трансформації у своєму класі Vector3 , звані Transform
таTransformNormal
Ось приклад коду, який показує обидва підходи та демонструє необхідність розрізняти обидва типи векторів за 1 із 2 можливих способів. Ми перемістимо гру-сутність з позицією та поглядом у світ, перетворивши її за допомогою матриці. Якщо ми не використовуємо компонент 'w', ми більше не можемо використовувати те саме множення матричного вектора, як це показує цей приклад. Якщо ми все одно це зробимо, ми отримаємо неправильну відповідь для трансформованого look_dir
вектора:
#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;
}
Початкова держава:
position : 0.000000 0.000000 0.000000 1.000000
look_dir : 1.000000 0.000000 0.000000 0.000000
Тепер до цієї сутності буде застосовано перетворення з перекладом x + 5 та обертанням на 90 градусів навколо осі y. Правильна відповідь після перетворення:
position : 5.000000 0.000000 0.000000 1.000000
look_dir : 0.000000 0.000000 1.000000 0.000000
Правильну відповідь ми отримаємо лише в тому випадку, якщо виділимо вектори з w == 0 і позиції з w == 1 одним із представлених вище способів.
r.x = ... + a._14*v.w;
r.y = ... + a._24*v.w;
r.z = ... + a._34*v.w;
r.w = ... + a._44*v.w;
подивіться детально мою відповідь