Matrix class

Hi folks,

I’m trying to write a matrix class for use in a matrix stack that I will apply to vertices before sending them to a VBO. But I’m a little confused by row-major versus column-major order. This is my matrix class so far, but it doesn’t seem to be applying any translations to the vertices I send it.

Please help me find my bug (it’s probably a silly mistake on my part), and any friendly advice about implementing a matrix stack for GL is much appreciated. Thanks new friends!


class Matrix
{
public:
	//---Construction
	Matrix()
	{
		identify();
	}
	Matrix(float m[4][4])
	{
		for (int y = 0; y < 4; y++)
			for (int x = 0; x < 4; x++)
				matrix[y][x] = m[y][x];
	}
	
	//---Arithmetic
	const Matrix operator * (const Matrix& m) const
	{
		float result[4][4];
		
		for (int y = 0; y < 4; y++)
			for (int x = 0; x < 4; x++)
			{
				result[y][x] = 0.f;
				for (int z = 0; z < 4; z++)
					result[y][x] += matrix[y][z]*m.matrix[z][x];
			}
		
		return Matrix(result);
	}
	
	//---Transformations
	Matrix& identify()
	{
		memset(matrix, 0.f, 4*4*sizeof(float));
		for (int i = 0; i < 4; i++)
			matrix[i][i] = 1.f;
		return *this;
	}
	Matrix& translate(float tx, float ty, float tz)
	{
		Matrix other;
		
		other.matrix[0][4] = tx;
		other.matrix[1][4] = ty;
		other.matrix[2][4] = tz;
		
		*this = (*this)*other;
		
		return *this;
	}
	
	//---Apply to vector
	Matrix& transform(float vector[4])
	{
		float result[4];
		
		//Calculate result
		for (int y = 0; y < 4; y++)
		{
			result[y] = 0.f;
			for (int z = 0; z < 4; z++)
				result[y] += matrix[y][z]*vector[z];
		}
		
		//Copy result into vector
		for (int y = 0; y < 4; y++)
			vector[y] = result[y];
		
		return *this;
	}
	
private:
	float matrix[4][4];
};

The test I’m running is this:


float vector[4] = {0.f, 0.f, 0.f, 1.f};
matrix.identify().translate(0.f, 1.f, 0.f).transform(vector);
printf("%f, %f, %f, %f
", vector[0], vector[1], vector[2], vector[3]);    

Fixed it! It was a silly error like I thought (in my translate function I was accessing index 4 in a 4-element array, which is of course an overflow; meant to access 3).

I also implemented the matrix stack class. It was easier than I expected–I thought I was going to have to do some crazy stuff to make the operations apply in reverse order the way they do in OpenGL, but apparently it already does this…I guess because I’m using row-major order? I don’t really understand, but I’m happy with it.

Here is the functional code in case anyone else needed it too.


class Matrix
{
public:
	//---Construction
	Matrix()
	{
		identify();
	}
	Matrix(float m[4][4])
	{
		for (int y = 0; y < 4; y++)
			for (int x = 0; x < 4; x++)
				matrix[y][x] = m[y][x];
	}
	
	//---Arithmetic
	const Matrix operator * (const Matrix& m) const
	{
		float result[4][4];
		
		for (int y = 0; y < 4; y++)
			for (int x = 0; x < 4; x++)
			{
				result[y][x] = 0.f;
				for (int z = 0; z < 4; z++)
					result[y][x] += matrix[y][z]*m.matrix[z][x];
			}
		
		return Matrix(result);
	}
	
	//---Transformations
	Matrix& identify()
	{
		memset(matrix, 0.f, 4*4*sizeof(float));
		for (int i = 0; i < 4; i++)
			matrix[i][i] = 1.f;
		return *this;
	}
	Matrix& translate(float tx, float ty, float tz)
	{
		Matrix other;
		
		other.matrix[0][3] = tx;
		other.matrix[1][3] = ty;
		other.matrix[2][3] = tz;
		
		*this = (*this)*other;
		
		return *this;
	}
	Matrix& scale(float sx, float sy, float sz)
	{
		Matrix other;
		
		other.matrix[0][0] = sx;
		other.matrix[1][1] = sy;
		other.matrix[2][2] = sz;
		
		*this = (*this)*other;
		
		return *this;
	}
	Matrix& rotate(float theta, float rx, float ry, float rz)
	{
		float other[4][4];
		float sint = sin(theta), cost = cos(theta);
		
		other[0][0] = rx*rx*(1 - cost) + cost;
		other[1][0] = rx*ry*(1 - cost) + rz*sint;
		other[2][0] = rx*rz*(1 - cost) - ry*sint;
		
		other[0][1] = ry*rx*(1 - cost) - rz*sint;
		other[1][1] = ry*ry*(1 - cost) + cost;
		other[2][1] = ry*rz*(1 - cost) + rx*sint;
		
		other[0][2] = rz*rx*(1 - cost) + ry*sint;
		other[1][2] = rz*ry*(1 - cost) - rx*sint;
		other[2][2] = rz*rz*(1 - cost) + cost;
		
		other[0][3] = 0.f;
		other[1][3] = 0.f;
		other[2][3] = 0.f;
		other[3][0] = 0.f;
		other[3][1] = 0.f;
		other[3][2] = 0.f;
		other[3][3] = 1.f;
		
		*this = (*this)*Matrix(other);
		
		return *this;
	}
	
	//---Apply to vector
	Matrix& transform(float vector[4])
	{
		float result[4];
		
		//Calculate result
		for (int y = 0; y < 4; y++)
		{
			result[y] = 0.f;
			for (int z = 0; z < 4; z++)
				result[y] += matrix[y][z]*vector[z];
		}
		
		//Copy result into vector
		for (int y = 0; y < 4; y++)
			vector[y] = result[y];
		
		return *this;
	}
	
private:
	float matrix[4][4];
};



class MatrixStack
{
public:
	//---Construction & destruction
	MatrixStack(int size = 100)
	{
		stack_size = std::max(size, 0);
		stack_top = 0;
		matrix = new Matrix[stack_size];
		matrix[0].identify();
	}
	MatrixStack(int size, const Matrix& m)
	{
		stack_size = size;
		stack_top = 0;
		matrix = new Matrix[stack_size];
		matrix[0] = m;
	}
	MatrixStack(const MatrixStack& ms)
	{
		stack_size = ms.stack_size;
		stack_top = ms.stack_top;
		matrix = new Matrix[stack_size];
		for (int m = 0; m < stack_top; m++)
			matrix[m] = ms.matrix[m];
	}
	~MatrixStack()
	{
		delete[] matrix;
	}
	
	//---Copying
	MatrixStack& operator = (const MatrixStack& ms)
	{
		delete[] matrix;
		stack_size = ms.stack_size;
		stack_top = ms.stack_top;
		matrix = new Matrix[stack_size];
		for (int m = 0; m < stack_top; m++)
			matrix[m] = ms.matrix[m];
		return *this;
	}
	MatrixStack& operator = (const Matrix& m)
	{
		matrix[stack_top] = m;
		return *this;
	}
	
	//---Resizing
	MatrixStack& resize(int size)
	{
		delete[] matrix;
		stack_size = std::max(size, 0);
		stack_top = 0;
		matrix = new Matrix[stack_size];
		matrix[0].identify();
		return *this;
	}
	
	//---Pushing, popping, and clearing stack
	MatrixStack& push()
	{
		if (stack_top < stack_size - 1) 
		{
			stack_top++;
			matrix[stack_top] = matrix[stack_top - 1];
		}
		else fatal_error("
ERROR: matrix stack capacity was exceeded
");
		return *this;
	}
	MatrixStack& pop()
	{
		if (stack_top > 0) stack_top--;
		else fatal_error("
ERROR: popped below bottom of matrix stack
");
		return *this;
	}
	MatrixStack& clear()
	{
		stack_top = 0;
		matrix[0].identify();
		return *this;
	}
	int check_headway() const
	{
		return stack_size - stack_top - 1;
	}
	
	//---Transformations
	MatrixStack& identify()
	{
		matrix[stack_top].identify();
		return *this;
	}
	MatrixStack& translate(float tx, float ty, float tz)
	{
		matrix[stack_top].translate(tx, ty, tz);
		return *this;
	}
	MatrixStack& scale(float sx, float sy, float sz)
	{
		matrix[stack_top].scale(sx, sy, sz);
		return *this;
	}
	MatrixStack& rotate(float theta, float rx, float ry, float rz)
	{
		matrix[stack_top].rotate(theta, rx, ry, rz);
		return *this;
	}
	
	//---Apply to vector
	MatrixStack& transform(float vector[4])
	{
		matrix[stack_top].transform(vector);
		return *this;
	}
	
private:
	Matrix* matrix;
	int stack_top, stack_size;
};

But note that, in the push and pop functions, I make a call to ‘fatal_error’ which I have not included. You will need to replace this with your own error handling functions.

Suggestions and constructive criticism are welcome!!