# Adding two 4x4 matrix objects to form another

## Recommended Posts

In my scene graph I have to make it so a transformation node adds the parent nodes transformation matrix to the local transformation matrix to form a final local matrix, I'm using 4x4 matrix objects, defined here:
#ifndef MATH3D_H
#define MATH3D_H

#include <cmath>

namespace Math3D {

// Define this to have Math3D.cp generate a main which tests these classes
//#define TEST_MATH3D

// Define this to allow streaming output of vectors and matrices
// Automatically enabled by TEST_MATH3D
//#define OSTREAM_MATH3D

// definition of the scalar type
typedef float scalar_t;
// inline pass-throughs to various basic math functions
// written in this style to allow for easy substitution with more efficient versions
inline scalar_t SINE_FUNCTION (scalar_t x) 		{ return sin(x); }
inline scalar_t COSINE_FUNCTION (scalar_t x) 	{ return cos(x); }
inline scalar_t SQRT_FUNCTION (scalar_t x) 		{ return sqrt(x); }

// 4 element vector
class Vector4 {
public:
Vector4 (void) {}
Vector4 (scalar_t a, scalar_t b, scalar_t c, scalar_t d = 1)
{ e[0]=a; e[1]=b; e[2]=c; e[3]=d; }

// The int parameter is the number of elements to copy from initArray (3 or 4)
explicit Vector4(scalar_t* initArray, int arraySize = 3)
{ for (int i = 0;i<arraySize;++i) e[i] = initArray[i]; if (arraySize == 3) e[3] = 1; }

// [] is to read, () is to write (const correctness)
const scalar_t& operator[] (int i) const { return e[i]; }
scalar_t& operator() (int i) { return e[i]; }

// Provides access to the underlying array; useful for passing this class off to C APIs
const scalar_t* readArray(void) { return e; }
scalar_t* getArray(void) { return e; }

private:
scalar_t e[4];
};

// 4 element matrix
class Matrix4
{
public:
Matrix4 (void) {}

// When defining matrices in C arrays, it is easiest to define them with
// the column increasing fastest.  However, some APIs (OpenGL in particular) do this
// backwards, hence the "constructor" from C matrices, or from OpenGL matrices.
// Note that matrices are stored internally in OpenGL format.
void C_Matrix (scalar_t* initArray)
{ int i = 0; for (int y=0;y<4;++y) for (int x=0;x<4;++x) (*this)(x)[y] = initArray[i++]; }
void OpenGL_Matrix (scalar_t* initArray)
{ int i = 0; for (int x = 0; x < 4; ++x) for (int y=0;y<4;++y) (*this)(x)[y] = initArray[i++]; }

// [] is to read, () is to write (const correctness)
// m[x][y] or m(x)[y] is the correct form
const scalar_t* operator[] (int i) const { return &e[i<<2]; }
scalar_t* operator() (int i) { return &e[i<<2]; }

const scalar_t* readArray (void) { return e; }
scalar_t* getArray(void) { return e; }

// Construct various matrices; REPLACES CURRENT CONTENTS OF THE MATRIX!
// Written this way to work in-place and hence be somewhat more efficient
void Identity (void) { for (int i=0;i<16;++i) e[i] = 0; e[0] = 1; e[5] = 1; e[10] = 1; e[15] = 1; }
inline Matrix4& Rotation (scalar_t angle, Vector4 axis);
inline Matrix4& Translation(const Vector4& translation);
inline Matrix4& Scale (scalar_t x, scalar_t y, scalar_t z);
inline Matrix4& BasisChange (const Vector4& v, const Vector4& n);
inline Matrix4& BasisChange (const Vector4& u, const Vector4& v, const Vector4& n);
inline Matrix4& ProjectionMatrix (bool perspective, scalar_t l, scalar_t r, scalar_t t, scalar_t b, scalar_t n, scalar_t f);

private:
scalar_t e[16];
};

// Scalar operations

// Returns false if there are 0 solutions
inline bool SolveQuadratic (scalar_t a, scalar_t b, scalar_t c, scalar_t* x1, scalar_t* x2);

// Vector operations
inline bool operator== (const Vector4&, const Vector4&);
inline bool operator< (const Vector4&, const Vector4&);

inline Vector4 operator- (const Vector4&);
inline Vector4 operator* (const Vector4&, scalar_t);
inline Vector4 operator* (scalar_t, const Vector4&);
inline Vector4& operator*= (Vector4&, scalar_t);
inline Vector4 operator/ (const Vector4&, scalar_t);
inline Vector4& operator/= (Vector4&, scalar_t);

inline Vector4 operator+ (const Vector4&, const Vector4&);
inline Vector4& operator+= (Vector4&, const Vector4&);
inline Vector4 operator- (const Vector4&, const Vector4&);
inline Vector4& operator-= (Vector4&, const Vector4&);

// X3 is the 3 element version of a function, X4 is four element
inline scalar_t LengthSquared3 (const Vector4&);
inline scalar_t LengthSquared4 (const Vector4&);
inline scalar_t Length3 (const Vector4&);
inline scalar_t Length4 (const Vector4&);
inline Vector4 Normalize3 (const Vector4&);
inline Vector4 Normalize4 (const Vector4&);
inline scalar_t DotProduct3 (const Vector4&, const Vector4&);
inline scalar_t DotProduct4 (const Vector4&, const Vector4&);
// Cross product is only defined for 3 elements
inline Vector4 CrossProduct (const Vector4&, const Vector4&);

inline Vector4 operator* (const Matrix4&, const Vector4&);

// Matrix operations
inline bool operator== (const Matrix4&, const Matrix4&);
inline bool operator< (const Matrix4&, const Matrix4&);

inline Matrix4 operator* (const Matrix4&, const Matrix4&);

inline Matrix4 Transpose (const Matrix4&);
scalar_t Determinant (const Matrix4&);
Matrix4 Inverse (const Matrix4&);

// Inline implementations follow
inline bool SolveQuadratic (scalar_t a, scalar_t b, scalar_t c, scalar_t* x1, scalar_t* x2) {
// If a == 0, solve a linear equation
if (a == 0) {
if (b == 0) return false;
*x1 = c / b;
*x2 = *x1;
return true;
} else {
scalar_t det = b * b - 4 * a * c;
if (det < 0) return false;
det = SQRT_FUNCTION(det) / (2 * a);
scalar_t prefix = -b / (2*a);
*x1 = prefix + det;
*x2 = prefix - det;
return true;
}
}

inline bool operator== (const Vector4& v1, const Vector4& v2)
{ return (v1[0]==v2[0]&&v1[1]==v2[1]&&v1[2]==v2[2]&&v1[3]==v2[3]); }

inline bool operator< (const Vector4& v1, const Vector4& v2) {
for (int i=0;i<4;++i)
if (v1[i] < v2[i]) return true;
else if (v1[i] > v2[i]) return false;
return false;
}

inline Vector4 operator- (const Vector4& v)
{ return Vector4(-v[0], -v[1], -v[2], -v[3]); }

inline Vector4 operator* (const Vector4& v, scalar_t k)
{ return Vector4(k*v[0], k*v[1], k*v[2], k*v[3]); }

inline Vector4 operator* (scalar_t k, const Vector4& v)
{ return v * k; }

inline Vector4& operator*= (Vector4& v, scalar_t k)
{ for (int i=0;i<4;++i) v(i) *= k; return v; }

inline Vector4 operator/ (const Vector4& v, scalar_t k)
{ return Vector4(v[0]/k, v[1]/k, v[2]/k, v[3]/k); }

inline Vector4& operator/= (Vector4& v, scalar_t k)
{ for (int i=0;i<4;++i) v(i) /= k; return v; }

inline scalar_t LengthSquared3 (const Vector4& v)
{ return DotProduct3(v,v); }
inline scalar_t LengthSquared4 (const Vector4& v)
{ return DotProduct4(v,v); }

inline scalar_t Length3 (const Vector4& v)
{ return SQRT_FUNCTION(LengthSquared3(v)); }
inline scalar_t Length4 (const Vector4& v)
{ return SQRT_FUNCTION(LengthSquared4(v)); }

inline Vector4 Normalize3 (const Vector4& v)
{	Vector4 retVal = v / Length3(v); retVal(3) = 1; return retVal; }
inline Vector4 Normalize4 (const Vector4& v)
{	return v / Length4(v); }

inline Vector4 operator+ (const Vector4& v1, const Vector4& v2)
{ return Vector4(v1[0]+v2[0], v1[1]+v2[1], v1[2]+v2[2], v1[3]+v2[3]); }

inline Vector4& operator+= (Vector4& v1, const Vector4& v2)
{ for (int i=0;i<4;++i) v1(i) += v2[i]; return v1; }

inline Vector4 operator- (const Vector4& v1, const Vector4& v2)
{ return Vector4(v1[0]-v2[0], v1[1]-v2[1], v1[2]-v2[2], v1[3]-v2[3]); }

inline Vector4& operator-= (Vector4& v1, const Vector4& v2)
{ for (int i=0;i<4;++i) v1(i) -= v2[i]; return v1; }

inline scalar_t DotProduct3 (const Vector4& v1, const Vector4& v2)
{ return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; }

inline scalar_t DotProduct4 (const Vector4& v1, const Vector4& v2)
{ return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2] + v1[3]*v2[3]; }

inline Vector4 CrossProduct (const Vector4& v1, const Vector4& v2) {
return Vector4( 	 v1[1] * v2[2] - v1[2] * v2[1]
,v2[0] * v1[2] - v2[2] * v1[0]
,v1[0] * v2[1] - v1[1] * v2[0]
,1);
}

inline Vector4 operator* (const Matrix4& m, const Vector4& v) {
return Vector4( v[0]*m[0][0] + v[1]*m[1][0] + v[2]*m[2][0] + v[3]*m[3][0],
v[0]*m[0][1] + v[1]*m[1][1] + v[2]*m[2][1] + v[3]*m[3][1],
v[0]*m[0][2] + v[1]*m[1][2] + v[2]*m[2][2] + v[3]*m[3][2],
v[0]*m[0][3] + v[1]*m[1][3] + v[2]*m[2][3] + v[3]*m[3][3]);

}

inline bool operator== (const Matrix4& m1, const Matrix4& m2) {
for (int x=0;x<4;++x) for (int y=0;y<4;++y)
if (m1[x][y] != m2[x][y]) return false;
return true;
}

inline bool operator< (const Matrix4& m1, const Matrix4& m2) {
for (int x=0;x<4;++x) for (int y=0;y<4;++y)
if (m1[x][y] < m2[x][y]) return true;
else if (m1[x][y] > m2[x][y]) return false;
return false;
}

inline Matrix4 operator* (const Matrix4& m1, const Matrix4& m2) {
Matrix4 retVal;
for (int x=0;x<4;++x) for (int y=0;y<4;++y) {
retVal(x)[y] = 0;
for (int i=0;i<4;++i) retVal(x)[y] += m1[i][y] * m2[x][i];
}
return retVal;
}

inline Matrix4 Transpose (const Matrix4& m) {
Matrix4 retVal;
for (int x=0;x<4;++x) for (int y=0;y<4;++y)
retVal(x)[y] = m[y][x];
return retVal;
}

inline Matrix4& Matrix4::Rotation (scalar_t angle, Vector4 axis) {
scalar_t c = COSINE_FUNCTION(angle);
scalar_t s = SINE_FUNCTION(angle);
// One minus c (short name for legibility of formulai)
scalar_t omc = (1 - c);

if (LengthSquared3(axis) != 1) axis = Normalize3(axis);

scalar_t x = axis[0];
scalar_t y = axis[1];
scalar_t z = axis[2];
scalar_t xs = x * s;
scalar_t ys = y * s;
scalar_t zs = z * s;
scalar_t xyomc = x * y * omc;
scalar_t xzomc = x * z * omc;
scalar_t yzomc = y * z * omc;

e[0] = x*x*omc + c;
e[1] = xyomc + zs;
e[2] = xzomc - ys;
e[3] = 0;

e[4] = xyomc - zs;
e[5] = y*y*omc + c;
e[6] = yzomc + xs;
e[7] = 0;

e[8] = xzomc + ys;
e[9] = yzomc - xs;
e[10] = z*z*omc + c;
e[11] = 0;

e[12] = 0;
e[13] = 0;
e[14] = 0;
e[15] = 1;

return *this;
}

inline Matrix4& Matrix4::Translation(const Vector4& translation) {
Identity();
e[12] = translation[0];
e[13] = translation[1];
e[14] = translation[2];
return *this;
}

inline Matrix4& Matrix4::Scale (scalar_t x, scalar_t y, scalar_t z) {
Identity();
e[0] = x;
e[5] = y;
e[10] = z;
return *this;
}

inline Matrix4& Matrix4::BasisChange (const Vector4& u, const Vector4& v, const Vector4& n) {
e[0] = u[0];
e[1] = v[0];
e[2] = n[0];
e[3] = 0;

e[4] = u[1];
e[5] = v[1];
e[6] = n[1];
e[7] = 0;

e[8] = u[2];
e[9] = v[2];
e[10] = n[2];
e[11] = 0;

e[12] = 0;
e[13] = 0;
e[14] = 0;
e[15] = 1;

return *this;
}

inline Matrix4& Matrix4::BasisChange (const Vector4& v, const Vector4& n) {
Vector4 u = CrossProduct(v,n);
return BasisChange (u, v, n);
}

inline Matrix4& Matrix4::ProjectionMatrix (bool perspective, 	scalar_t left_plane, scalar_t right_plane,
scalar_t top_plane, scalar_t bottom_plane,
scalar_t near_plane, scalar_t far_plane)
{
scalar_t A = (right_plane + left_plane) / (right_plane - left_plane);
scalar_t B = (top_plane + bottom_plane) / (top_plane - bottom_plane);
scalar_t C = (far_plane + near_plane) / (far_plane - near_plane);

Identity();
if (perspective) {
e[0] = 2 * near_plane / (right_plane - left_plane);
e[5] = 2 * near_plane / (top_plane - bottom_plane);
e[8] = A;
e[9] = B;
e[10] = C;
e[11] = -1;
e[14] =	2 * far_plane * near_plane / (far_plane - near_plane);
} else {
e[0] = 2 / (right_plane - left_plane);
e[5] = 2 / (top_plane - bottom_plane);
e[10] = -2 / (far_plane - near_plane);
e[12] = A;
e[13] =  B;
e[14] = C;
}
return *this;
}

} // close namespace
/*
// If we're testing, then we need OSTREAM support
#ifdef TEST_MATH3D
#define OSTREAM_MATH3D
#endif

#ifdef OSTREAM_MATH3D
#include <ostream>
// Streaming support
std::ostream& operator<< (std::ostream& os, const Math3D::Vector4& v) {
os << '[';
for (int i=0; i<4; ++i)
os << ' ' << v[i];
return os << ']';
}

std::ostream& operator<< (std::ostream& os, const Math3D::Matrix4& m) {
for (int y=0; y<4; ++y) {
os << '[';
for (int x=0;x<4;++x)
os << ' ' << m[x][y];
os << " ]" << std::endl;
}
return os;
}
#endif	// OSTREAM_MATH3D
*/
#endif


Now, this vector4 and matrix4 class has support for adding vectors to matrices, and vectors to vectors, but not matrices to matrices! How in the world do I add two 4x4 matrix objects like these? I'm not really asking how to do it mathematically, I know how to do that, but how do I add two of these babies in terms code? I've been looking over it for a while now. Can anyone help me?

##### Share on other sites
It sounds like you want to concatinate the matrices, which is not an add operation but a multiply. Order of multiplication matters when it comes to matrices, A*B != B*A and the correct order is dependant on whether you are using column or row vectors.

Matrix-Matrix Addition is just that, for each element [i][j] in matrix A add element [i][j] from matrix B to calculate the sum matrix C.

Matrix-Matrix Multiplication is a series of 16 dot products between the rows of one matrix and the columns of another where, for instance, MatrixA.Row[i] dot MatrixB.Col[j] yields element [i][j] of the resulting matrix C. Again, whether you use the row or column vector will affect the order in which you multiply matrices, and without confusing the issue further, swapping rows for columns between these matrices has the same effect as changing the order.

If you understand the math, coding it up is trivial.

##### Share on other sites
If you know how to do it mathematically, you should have no problems doing it in code. Do it exactly the same way you would mathematically. Add the components.

Why would you want to though?

##### Share on other sites
So I loop through each element of each matrix adding them together?

Like...

for i=0 i<16 i++
Matrix3 = Matrix1[i] + Matrix2[i]

Simple as that?

EDIT: I would want to so I can have relative transformations of objects..

I.E-> truck object has location matrix a, has 4 wheels, which are relative to the trucks location, matrix b c and d..

We have to add the local matrix b to a to find the final location of the first wheel

Same for the next 3 wheels..

##### Share on other sites
Yes, that is how you perform a matrix addition, HOWEVER, a matrix addition does not provide the result you are after. What you want is a Matrix Concatination, which is a Matrix-Matrix multiplication as I described in my original post.

Not only does matrix addition not do what you want, I don't believe it even provides ANY usefull calculation in terms of 3 Dimensional transformations.

##### Share on other sites
The reason I'm having trouble adding these 2 matrices in code is because the syntax is so goofed up I can't pick out how exactly to do it with these Matrix4 objects..

EDIT: Oh, Concatination?

Hmm, Okay... Lets see here then...

Maybe this library already provides a method for that?

Matrix multiplication is when my matrix math gets a little clouded and fuzzy :
Like this?
inline Matrix4 operator* (const Matrix4& m1, const Matrix4& m2) {	Matrix4 retVal;	for (int x=0;x<4;++x) for (int y=0;y<4;++y) {		retVal(x)[y] = 0;		for (int i=0;i<4;++i) retVal(x)[y] += m1[i][y] * m2[x][i];	}	return retVal;}

##### Share on other sites
	void Update()	{		this->FinalMatrix = LocalMatrix * ParentNode->FinalMatrix;		glPushMatrix();		glLoadMatrixf(this->FinalMatrix.readArray());		CSceneNode::Update();		glPopMatrix();	}

Okay, um, this is, um, not working the way I'd like it to :D, to say the least.

When the program starts up, my camera doesn't work, its locked, it seems like the model is all goofed up..

Does this have something to do with being in model/world matrix? Heehee, I'll post a screenshot if you wish.

##### Share on other sites
check to make sure you are multiplying the matrices in the right order. matrices are non-commutative.

that means in the matrix world, for two matrices A and B, A*B is not necessarily equal to B*A. In fact it isn't most of the time.

##### Share on other sites
Tis strange, either way I multiply the two matrices, I still get the same bug...

I'm viewing a strange direction, the model looks weird, (i might be inside the model), and my camera is locked...

Perhaps it is due to this line?

glLoadMatrixf(this->FinalMatrix.readArray());

You can check to see what readarray does up at the origional post...

Wouldn't it just translate everything to 0,0,0 if I don't set the translation for the local matrix?

##### Share on other sites
Make sure that glMatrixMode is called as necessary, make sure your matrices is actually matrices you want to combine, etc.

Anyway... to yourself do what you want you need to learn more about matrices etc. It is possible to tell what to fix in your code but you need to get some understanding of subject first so you can then figure things out.

As for "camera is locked", did you somehow implement moving camera ? (copy-pasted some code?) You probably need to multiply camera matrix with it somewhere depending to implementation. In general you need understanding of code you use, i.e. if you copy-pasted something from somewhere it won't ever work much like what you want, except sometimes by chance. As a guess, it might start kinda working if you use glMultMatrix instead of glLoadMatrix, but again it's better if you learn something about matrices, at least most basic operations.

##### Share on other sites
Yeah, camera code is copy pasted stuff..

But I understand your average matrix math, adding, subtracting, multiplying, determinents, etc...

But I believe it is logic here that is failing me, or opengl syntax..

LocalMatrix * ParentNode->FinalMatrix should result in a matrix that looks like this

[0 0 0 0]
[0 0 0 0]
[0 0 0 0]
[0 0 0 0]

As I havn't set any informations inside the matrices

With these elements being the translation:

[0 0 0 X]
[0 0 0 Y]
[0 0 0 Z]
[0 0 0 0]

It should be exactly the same as calling glTranslatef(0.0, 0.0, 0.0);

Or maybe the problem is that the scalar value is 0? I don't see why though..

I really believe that it is a matter of not understanding the gl matrix and transformation functions..

the MODELVIEW matrix should be in use, it is set after setting up the opengl world matrix in the resize_gl function..

##### Share on other sites
I still suggest to read more on matrices, it seems that you don't really understand multiplications, or additions, or other things. If you previously learned matrices in school or something, you may need to refresh knowledge.
Also, the way how matrices is used to transform vertices you render is
transformed vector = matrix*vector . It is not some "opengl thing", it's how you apply linear transformations (e.g. rotations, etc).

You don't "add" matrix to vector to transform(i.e. rotate or whatever) things . It doesn't even make sense. You multiply it. So the matrix that leaves vector unchanged after multiplication is identity matrix
1 0 0 00 1 0 00 0 1 00 0 0 1

, not zero matrix. (if you multiply by zero matrix, all your points just get compressed into infinitely small dot, kind of)

As for other problems, you probably confused "add" as in english language (e.g. add more code to get some effect, "add rotation", etc), and "add" as in mathematics. It's rather common mistake, actually.

##### Share on other sites
Did you read my last post?

I didn't say the word add for, quite some time, in fact I havn't said add since like, my first 2 or 3 posts?

And the only reason it would end up as a 0 matrix is the fact that I didn't set up the scalar values of the matrices when I initialized them.

##### Share on other sites
Yep, sure i did read it. It's just that, if you knew the matrices, how and why matrices is used for rotations etc. you'd see why these matrices needs 1's on diagonal, for example, and would also be able to solve your problem already.

edit: What i mean is that when problem arises purely because of lack of information the best suggestion is to get this information and this will solve the problem.

##### Share on other sites
Quote:
 Original post by Xetheriel...LocalMatrix * ParentNode->FinalMatrix should result in a matrix that looks like this[0 0 0 0][0 0 0 0][0 0 0 0][0 0 0 0]As I havn't set any informations inside the matricesWith these elements being the translation:[0 0 0 X][0 0 0 Y][0 0 0 Z][0 0 0 0]It should be exactly the same as calling glTranslatef(0.0, 0.0, 0.0);Or glLoadIdentity...Or maybe the problem is that the scalar value is 0? I don't see why though..I really believe that it is a matter of not understanding the gl matrix and transformation functions..the MODELVIEW matrix should be in use, it is set after setting up the opengl world matrix in the resize_gl function..

Calling glTranslatef(X, Y, Z) does not produce the matrix:
[0 0 0 X]
[0 0 0 Y]
[0 0 0 Z]
[0 0 0 0]

It actually produces:
[1 0 0 X]
[0 1 0 Y]
[0 0 1 Z]
[0 0 0 1]

The reason starts to make sense when you try transformation using matrices on a piece of paper. Lets say you have a point (5, 6, 7, 1) in 3D homogenous coordinates. Try multiplying out the translation matrix with the point:

[1 0 0 X]   [5][0 1 0 Y]   [6][0 0 1 Z] * [7][0 0 0 1]   [1]

You'll see that the answer is [5+X, 6+Y, 7+Z, 1] which is just what you want to do translation. If you don't have the ones along the diagonal, you'll see that it just doesn't work out mathematically.

If you want to combine transformation, you have to multiply the matrices together. Search on google to get a better idea on how transformations using matrices work.

##### Share on other sites

You have to understand, the last thing I feel like doing is diving into math tutorials when I already know how to do basic matrix multiplication/addition/translation etc etc..

What I didn't understand was a three dimensional concept - you need those 1's in there to get the right results.. I believe those are the scalar values?

Now that I know this, I can fix my program so I set them to an identity matrix before I initialize any translation. Because multiplying those two matrices together was like, messing up (I don't feel like doing a bit of matrix math to figure out exactly what is happening, but i know it isn't good)...

I think the fact that I need the scalar values was quite different from "revisiting the basics of matrices again"..

Sorry for getting a little short, i just really didn't feel like opening up a math book on this one, and I really didn't think i needed to..

So, yeah, please spare my rating :)

We learn arbitrary stuff in school, not the specifics of 3dimensional rendering via 4x4 matrices :)

##### Share on other sites
Well, it's simply phenomena of copy paste programming, where code is copy-pasted from elsewhere... you sure can make something kind of work this way, but will have ton of very simple problems that you could easily solve if you learn and/or refresh knowledge.

Thing is, if one does know relevant math (and how and why this math is used in this case) at adequate level to program 3D software, one will necessarily see why these 1's in diagonal is necessary(even if he didn't learn any specifics). 3D game programming is math-intensive thing, and it's for most part not some exotic math but general stuff. Also, it is easy to misjudge own knowledge of mathematics.

As for some basic things, check out matrix and quaternion FAQ. Forum FAQ also contains few good links.

(btw i didn't touch your rating either way)

##### Share on other sites
Ew lame, who did then :\....

The nerve of people :
I rated you and the other guy up.

##### Share on other sites
Mentioning your rating is a sure way to get rated down in any case.

##### Share on other sites
This topic is now closed to further replies.

• ## Partner Spotlight

• ### Forum Statistics

• Total Topics
627667
• Total Posts
2978532

• 10
• 10
• 12
• 22
• 13