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!!