# 4x4 matrix implementation in C++

I've been doing some 3D graphics in OpenGL lately and I needed a way to work with 4x4 matrices. My implementation supports the following operations:

• Matrix-matrix subtraction.
• Matrix-matrix multiplication.
• Transformation matrix creation.
• View matrix creation.
• Perspective projection matrix creation.
• Orthographic projection matrix creation.

matrix.h

#ifndef MATRIX_H_
#define MATRIX_H_
#define _USE_MATH_DEFINES

#include <cmath>
#include <math.h>
#include <Windows.h>
#include <gl/GL.h>
#include <gl/GLU.h>
#include <gl/glext.h>
#include <gl/wglext.h>
#include <gl/glcorearb.h>
#include <SOIL.h>
#include "SWOGLL.h"
#include "vector4.h"

struct Matrix4x4
{
GLfloat m_elements[16];

Matrix4x4();
Matrix4x4(GLfloat elements[]);
GLfloat* GetElementsPointer();
static Matrix4x4 CreateScale(Vector4 scale);
static Matrix4x4 CreateTranslation(Vector4 translation);
static Matrix4x4 CreateRotationX(GLfloat angle);
static Matrix4x4 CreateRotationY(GLfloat angle);
static Matrix4x4 CreateRotationZ(GLfloat angle);
static Matrix4x4 CreateView(Vector4 forward, Vector4 up, Vector4 right, Vector4 position);
static Matrix4x4 CreatePerspectiveProjection(GLfloat width, GLfloat height, GLfloat fov, GLfloat nearPlane, GLfloat farPlane);
static Matrix4x4 CreateOrthographicProjection(GLfloat left, GLfloat right, GLfloat top, GLfloat bottom, GLfloat farPlane, GLfloat nearPlane);

inline Matrix4x4& operator+=(const Matrix4x4& rhs)
{
for(int i = 0; i < 16; i++)
{
this->m_elements[i] += rhs.m_elements[i];
}
return *this;
}

inline Matrix4x4& operator-=(const Matrix4x4& rhs)
{
for(int i = 0; i < 16; i++)
{
this->m_elements[i] -= rhs.m_elements[i];
}
return *this;
}

inline Matrix4x4& operator*=(const Matrix4x4& rhs)
{
this->m_elements[0] = this->m_elements[0] * rhs.m_elements[0] + this->m_elements[1] * rhs.m_elements[4] + this->m_elements[2] * rhs.m_elements[8] + this->m_elements[3] * rhs.m_elements[12];
this->m_elements[1] = this->m_elements[0] * rhs.m_elements[1] + this->m_elements[1] * rhs.m_elements[5] + this->m_elements[2] * rhs.m_elements[9] + this->m_elements[3] * rhs.m_elements[13];
this->m_elements[2] = this->m_elements[0] * rhs.m_elements[2] + this->m_elements[1] * rhs.m_elements[6] + this->m_elements[2] * rhs.m_elements[10] + this->m_elements[3] * rhs.m_elements[14];
this->m_elements[3] = this->m_elements[0] * rhs.m_elements[3] + this->m_elements[1] * rhs.m_elements[7] + this->m_elements[2] * rhs.m_elements[11] + this->m_elements[3] * rhs.m_elements[15];

this->m_elements[4] = this->m_elements[4] * rhs.m_elements[0] + this->m_elements[5] * rhs.m_elements[4] + this->m_elements[6] * rhs.m_elements[8] + this->m_elements[7] * rhs.m_elements[12];
this->m_elements[5] = this->m_elements[4] * rhs.m_elements[1] + this->m_elements[5] * rhs.m_elements[5] + this->m_elements[6] * rhs.m_elements[9] + this->m_elements[7] * rhs.m_elements[13];
this->m_elements[6] = this->m_elements[4] * rhs.m_elements[2] + this->m_elements[5] * rhs.m_elements[6] + this->m_elements[6] * rhs.m_elements[10] + this->m_elements[7] * rhs.m_elements[14];
this->m_elements[7] = this->m_elements[4] * rhs.m_elements[3] + this->m_elements[5] * rhs.m_elements[7] + this->m_elements[6] * rhs.m_elements[11] + this->m_elements[7] * rhs.m_elements[15];

this->m_elements[8] = this->m_elements[8] * rhs.m_elements[0] + this->m_elements[9] * rhs.m_elements[4] + this->m_elements[10] * rhs.m_elements[8] + this->m_elements[11] * rhs.m_elements[12];
this->m_elements[9] = this->m_elements[8] * rhs.m_elements[1] + this->m_elements[9] * rhs.m_elements[5] + this->m_elements[10] * rhs.m_elements[9] + this->m_elements[11] * rhs.m_elements[13];
this->m_elements[10] = this->m_elements[8] * rhs.m_elements[2] + this->m_elements[9] * rhs.m_elements[6] + this->m_elements[10] * rhs.m_elements[10] + this->m_elements[11] * rhs.m_elements[14];
this->m_elements[11] = this->m_elements[8] * rhs.m_elements[3] + this->m_elements[9] * rhs.m_elements[7] + this->m_elements[10] * rhs.m_elements[11] + this->m_elements[11] * rhs.m_elements[15];

this->m_elements[12] = this->m_elements[12] * rhs.m_elements[0] + this->m_elements[13] * rhs.m_elements[4] + this->m_elements[14] * rhs.m_elements[8] + this->m_elements[15] * rhs.m_elements[12];
this->m_elements[13] = this->m_elements[12] * rhs.m_elements[1] + this->m_elements[13] * rhs.m_elements[5] + this->m_elements[14] * rhs.m_elements[9] + this->m_elements[15] * rhs.m_elements[13];
this->m_elements[14] = this->m_elements[12] * rhs.m_elements[2] + this->m_elements[13] * rhs.m_elements[6] + this->m_elements[14] * rhs.m_elements[10] + this->m_elements[15] * rhs.m_elements[14];
this->m_elements[15] = this->m_elements[12] * rhs.m_elements[3] + this->m_elements[13] * rhs.m_elements[7] + this->m_elements[14] * rhs.m_elements[11] + this->m_elements[15] * rhs.m_elements[15];
return *this;
}

inline Matrix4x4& operator*=(const GLfloat& rhs)
{
for(int i = 0; i < 16; i++)
{
this->m_elements[i] *= rhs;
}
return *this;
}
};

extern inline Matrix4x4 operator+(const Matrix4x4& lhs, const Matrix4x4& rhs);
extern inline Matrix4x4 operator-(const Matrix4x4& lhs, const Matrix4x4& rhs);
extern inline Matrix4x4 operator*(const Matrix4x4& lhs, const Matrix4x4& rhs);
extern inline Matrix4x4 operator*(const Matrix4x4& lhs, const GLfloat& rhs);

#endif


matrix.cpp

#include "matrix.h"

Matrix4x4::Matrix4x4()
{
memset(&this->m_elements, 0, sizeof(this->m_elements));
this->m_elements[0] = 1;
this->m_elements[5] = 1;
this->m_elements[10] = 1;
this->m_elements[15] = 1;
}

Matrix4x4::Matrix4x4(GLfloat elements[])
{
memset(&this->m_elements, 0, sizeof(this->m_elements));
for(int i = 0; i < 16; i++)
{
this->m_elements[i] = elements[i];
}
}

GLfloat* Matrix4x4::GetElementsPointer()
{
return this->m_elements;
}

Matrix4x4 Matrix4x4::CreateScale(Vector4 scale)
{
Matrix4x4 newMatrix = Matrix4x4();
newMatrix.m_elements[0] = scale.x;
newMatrix.m_elements[5] = scale.y;
newMatrix.m_elements[10] = scale.z;
return newMatrix;
}

Matrix4x4 Matrix4x4::CreateTranslation(Vector4 translation)
{
Matrix4x4 newMatrix = Matrix4x4();
newMatrix.m_elements[3] = translation.x;
newMatrix.m_elements[7] = translation.y;
newMatrix.m_elements[11] = translation.z;
return newMatrix;
}

Matrix4x4 Matrix4x4::CreateRotationX(GLfloat angle)
{
Matrix4x4 newMatrix = Matrix4x4();
newMatrix.m_elements[5] = std::cos(-angle);
newMatrix.m_elements[6] = -std::sin(-angle);
newMatrix.m_elements[9] = std::sin(-angle);
newMatrix.m_elements[10] = std::cos(-angle);
return newMatrix;
}

Matrix4x4 Matrix4x4::CreateRotationY(GLfloat angle)
{
Matrix4x4 newMatrix = Matrix4x4();
newMatrix.m_elements[0] = std::cos(-angle);
newMatrix.m_elements[2] = std::sin(-angle);
newMatrix.m_elements[8] = -std::sin(-angle);
newMatrix.m_elements[10] = std::cos(-angle);
return newMatrix;
}

Matrix4x4 Matrix4x4::CreateRotationZ(GLfloat angle)
{
Matrix4x4 newMatrix = Matrix4x4();
newMatrix.m_elements[0] = std::cos(-angle);
newMatrix.m_elements[1] = -std::sin(-angle);
newMatrix.m_elements[4] = std::sin(-angle);
newMatrix.m_elements[5] = std::cos(-angle);
return newMatrix;
}

Matrix4x4 Matrix4x4::CreateView(Vector4 forward, Vector4 up, Vector4 right, Vector4 position)
{
Matrix4x4 newMatrixOne = Matrix4x4();
newMatrixOne.m_elements[0] = right.x;
newMatrixOne.m_elements[1] = right.y;
newMatrixOne.m_elements[2] = right.z;
newMatrixOne.m_elements[4] = up.x;
newMatrixOne.m_elements[5] = up.y;
newMatrixOne.m_elements[6] = up.z;
newMatrixOne.m_elements[8] = forward.x;
newMatrixOne.m_elements[9] = forward.y;
newMatrixOne.m_elements[10] = forward.z;

Matrix4x4 newMatrixTwo = Matrix4x4();
newMatrixTwo.m_elements[3] = -position.x;
newMatrixTwo.m_elements[7] = -position.y;
newMatrixTwo.m_elements[11] = -position.z;

Matrix4x4 newMatrix = newMatrixOne * newMatrixTwo;
return newMatrix;
}

Matrix4x4 Matrix4x4::CreatePerspectiveProjection(GLfloat width, GLfloat height, GLfloat fov, GLfloat nearPlane, GLfloat farPlane)
{
Matrix4x4 newMatrix = Matrix4x4();
newMatrix.m_elements[0] = (1.0f / std::tan((fov * M_PI / 180.0f) / 2.0f)) / (width / height);
newMatrix.m_elements[5] = 1.0f / std::tan((fov * M_PI / 180.0f) / 2.0f);
newMatrix.m_elements[10] = (farPlane + nearPlane) / (nearPlane - farPlane);
newMatrix.m_elements[11] = (2.0f * farPlane * nearPlane) / (nearPlane - farPlane);
newMatrix.m_elements[14] = -1.0f;
return newMatrix;
}

Matrix4x4 Matrix4x4::CreateOrthographicProjection(GLfloat left, GLfloat right, GLfloat top, GLfloat bottom, GLfloat farPlane, GLfloat nearPlane)
{
Matrix4x4 newMatrix = Matrix4x4();
newMatrix.m_elements[0] = 2.0f / (right - left);
newMatrix.m_elements[3] = -((right + left) / (right - left));
newMatrix.m_elements[5] = 2.0f / (top - bottom);
newMatrix.m_elements[7] = -((top + bottom) / (top - bottom));
newMatrix.m_elements[10] = 2.0f / (farPlane - nearPlane);
newMatrix.m_elements[11] = -((farPlane + nearPlane) / (farPlane - nearPlane));
return newMatrix;
}

inline Matrix4x4 operator+(const Matrix4x4& lhs, const Matrix4x4& rhs)
{
Matrix4x4 newMatrix = Matrix4x4(lhs);
newMatrix += rhs;
return newMatrix;
}

inline Matrix4x4 operator-(const Matrix4x4& lhs, const Matrix4x4& rhs)
{
Matrix4x4 newMatrix = Matrix4x4(lhs);
newMatrix -= rhs;
return newMatrix;
}

inline Matrix4x4 operator*(const Matrix4x4& lhs, const Matrix4x4& rhs)
{
Matrix4x4 newMatrix = Matrix4x4(lhs);
newMatrix *= rhs;
return newMatrix;
}

inline Matrix4x4 operator*(const Matrix4x4& lhs, const GLfloat& rhs)
{
Matrix4x4 newMatrix = Matrix4x4(lhs);
newMatrix *= rhs;
return newMatrix;
}


What can I improve?

The header SWOGLL.h is my custom OpenGL loader, which can be found here: https://github.com/Ethan-Bierlein/SWOGLL. The header vector4.h is simply my implementation of a four-component vector.

• I feel like inline Matrix4x4& operator*=(const Matrix4x4& rhs) is the perfect opportunity to use a macro. However, macros are heavily shunned upon so idk exactly... Something to consider, but the code is a little verbose in there and you could probably simplify each line with a macro.
– Dair
Oct 16, 2016 at 22:36
• In terms of what you can improve - I almost always need to transpose and/or invert a matrix, as well as rotation around an arbitrary vector. Wouldn't hurt to have those! Also, have you seen glm. It creates C++ classes that mimic glsl types like vec4 and mat4. Oct 17, 2016 at 4:45

All in all I really like your code. It's good and easy to read, and there's nothing really wrong with it AFAICS. In the following I'm writing down whatever's coming to my mind what could theoretically be improved/changed. Therefore don't take that as a list of things to change, but rather as points to think about.

### Too much of this

Whenever you refer the data members of your class, you use this:

this->m_elements[i] = elements[i];


I think you do this to clearly separate between local variables / function parameters / global variables and the data members of the class. I've done this, too. But I think it's a reasonable assumption to make that somebody reading your code knows that data members can be accessed without this, and to know (at least roughly) the rules regarding name resolution.

And, moreover, you already have something in place to distinguish between data members and "the rest": That m_ prefix. I read that as member_elements, or alternatively as matrix_elements.

Having both just means more to type and more to read. And the long lines suffer somewhat from that "more". Compare:

this->m_elements[0] = this->m_elements[0] * rhs.m_elements[0] + this->m_elements[1] * rhs.m_elements[4] + this->m_elements[2] * rhs.m_elements[8] + this->m_elements[3] * rhs.m_elements[12];

// No this
m_elements[0] = m_elements[0] * rhs.m_elements[0] + m_elements[1] * rhs.m_elements[4] + m_elements[2] * rhs.m_elements[8] + m_elements[3] * rhs.m_elements[12];


This point is highly debatable though, so if that's just "your style" then feel free to ignore it ;)

### C style arrays are out of fashion

GLfloat m_elements[16];


you could (should?) use

std::array<GLfloat, 16> m_elements;


The benefit is more information - the array size - for you (and the compiler) in some circumstances. A C style array "decays" (is implicitly converted) to a pointer to its first element in most situations. This conversion loses the information about the array size.

I doubt that this has any benefit to you in your particular case, though, because you can use ...

### range based for

... even with plain C arrays, too. This saves you from accidentally wrong indexing and other, often hard to find bugs:

// your code
for(int i = 0; i < 16; i++)
{
this->m_elements[i] *= rhs;
}

// with range based loop
for(GLfloat & element : m_elements)
{
element *= rhs;
}


You can also go one step further and use std::for_each, possibly making use of execution policies when (if) you switch to C++17.

### Getting rid of more "manual" for loops

Using std::transform you can get rid of more of these manual indexing loops:

// your code
for(int i = 0; i < 16; i++)
{
this->m_elements[i] += rhs.m_elements[i];
}

// using std::transform
std::transform(std::begin(m_elements), std::end(m_elements),
std::begin(rhs.m_elements),
std::begin(m_elements),
[] (GLfloat const & l, GLfloat const & r) { return l + r; });


Whether that's better in terms of readability is ... eh ... let's say "debatable", too. And of course it changes semantics: operator+ and operator= are now used, instead of operator+=.

Instead you could also keep the manual indexing, but make sure you run over the correct range:

size_t const number_elements =
std::distance(std::begin(m_elements), std::end(m_elements));
// or with std::array use
//  m_elements.size();
for(size_t i = 0; i < number_elements; i++)
{
m_elements[i] += rhs.m_elements[i];
}


Oh, and I like indexing with size_t as that's what's returned from the various size() member functions and may protect you from fallacies with the "usual arithmetic conversions" between unsigned and signed integer types. And of course when dealing with large amounts of data, int might be too narrow to hold possible indices. But of course, this doesn't apply here.

### memset can be dangerous, better avoid it

Instead of memset you could use the C++ (almost) equivalent std::fill. memset is a bad idea when using anything but plain old data types, and even then I'm not sure whether aliasing (and in the future also "laundering" or lack thereof) could still cause undefined behavior. Thus I recommend to avoid it.

### DRY using the evil preprocessor

The definitions of operator+, operator- and the two operator* are almost identical. You can use a macro to save you from that repetition:

#define OP_FROM_SELF_ASSIGN_OP(op, lhstype, rhstype)         \
inline Matrix4x4 operator op (lhstype lhs, rhstype rhs) \
{                                                          \
Matrix4x4 newMatrix = Matrix4x4(lhs);                  \
newMatrix op ## = rhs;                                 \
return newMatrix;                                      \
}


Note: Untested, it may be necessary to have the token concatenation (##) in a helper macro.

Using it as such:

OP_FROM_SELF_ASSIGN_OP(+, const Matrix4x4&, const Matrix4x4&)
OP_FROM_SELF_ASSIGN_OP(-, const Matrix4x4&, const Matrix4x4&)
OP_FROM_SELF_ASSIGN_OP(*, const Matrix4x4&, const Matrix4x4&)
OP_FROM_SELF_ASSIGN_OP(*, const Matrix4x4&, const GLfloat&)


Note that you could do similar tricks in the header file for operator+= and operator-=, but I'd advise against that as the benefit is minor compared to the issues that may arise if the used macros interfere with code including that header. Better keep ones macros behind closed doors ;)

### No pointers to arrays without a size or end, please

Matrix4x4::Matrix4x4(GLfloat elements[])


This constructor rings alarm bells, though. Here you accept a pointer to (probably) an array of GLfloats, but you have no idea how many you'll get neither give the caller any chance to tell you how many objects live behind that pointer.

Sure we can expect people to be able to infer from the name Matrix4x4 that there are 16 GLfloats required, but it's a dangerous practice / API design nonetheless.

You could also provide a constructor accepting a std::initializer_list. Or an (explicit please) constructor accepting a std::array.

### Don't break encapsulation in your factory functions

In your factory (static) functions you (IMO) break encapsulation by directly accessing the underlying array. Better let the constructor do the work:

Matrix4x4 Matrix4x4::CreateScale(Vector4 scale)
{
GLfloat elements[] = {
scale.x, 0,       0,       0,
0,       scale.y, 0,       0,
0,       0,       scale.z, 0,
0,       0,       0,       0};
return Matrix4x4{elements}; // Note: Using your constructor without size/end
}

• Is there a way to get a C-style array or array pointer using std::array? The only reason I used C-style arrays here is because that's what OpenGL supports. Oct 18, 2016 at 17:38
• @EthanBierlein Yep: en.cppreference.com/w/cpp/container/array/data Oct 19, 2016 at 21:36

There is a problem with your matrix multiplication, where it overwrites existing matrix members

Here is a simple but dirty fix:

inline Matrix4x4& operator*=(const Matrix4x4& rhs)
{
Matrix4x4 newMatrix;
float temp_0= this->mElements[0] * rhs.mElements[0] + this->mElements[1] * rhs.mElements[4] + this->mElements[2] * rhs.mElements[8] + this->mElements[3] * rhs.mElements[12];
float temp_1= this->mElements[0] * rhs.mElements[1] + this->mElements[1] * rhs.mElements[5] + this->mElements[2] * rhs.mElements[9] + this->mElements[3] * rhs.mElements[13];
float temp_2= this->mElements[0] * rhs.mElements[2] + this->mElements[1] * rhs.mElements[6] + this->mElements[2] * rhs.mElements[10] + this->mElements[3] * rhs.mElements[14];
float temp_3= this->mElements[0] * rhs.mElements[3] + this->mElements[1] * rhs.mElements[7] + this->mElements[2] * rhs.mElements[11] + this->mElements[3] * rhs.mElements[15];

float temp_4= this->mElements[4] * rhs.mElements[0] + this->mElements[5] * rhs.mElements[4] + this->mElements[6] * rhs.mElements[8] + this->mElements[7] * rhs.mElements[12];
float temp_5= this->mElements[4] * rhs.mElements[1] + this->mElements[5] * rhs.mElements[5] + this->mElements[6] * rhs.mElements[9] + this->mElements[7] * rhs.mElements[13];
float temp_6= this->mElements[4] * rhs.mElements[2] + this->mElements[5] * rhs.mElements[6] + this->mElements[6] * rhs.mElements[10] + this->mElements[7] * rhs.mElements[14];
float temp_7= this->mElements[4] * rhs.mElements[3] + this->mElements[5] * rhs.mElements[7] + this->mElements[6] * rhs.mElements[11] + this->mElements[7] * rhs.mElements[15];

float temp_8= this->mElements[8] * rhs.mElements[0] + this->mElements[9] * rhs.mElements[4] + this->mElements[10] * rhs.mElements[8] + this->mElements[11] * rhs.mElements[12];
float temp_9= this->mElements[8] * rhs.mElements[1] + this->mElements[9] * rhs.mElements[5] + this->mElements[10] * rhs.mElements[9] + this->mElements[11] * rhs.mElements[13];
float temp_10 = this->mElements[8] * rhs.mElements[2] + this->mElements[9] * rhs.mElements[6] + this->mElements[10] * rhs.mElements[10] + this->mElements[11] * rhs.mElements[14];
float temp_11 = this->mElements[8] * rhs.mElements[3] + this->mElements[9] * rhs.mElements[7] + this->mElements[10] * rhs.mElements[11] + this->mElements[11] * rhs.mElements[15];

float temp_12 = this->mElements[12] * rhs.mElements[0] + this->mElements[13] * rhs.mElements[4] + this->mElements[14] * rhs.mElements[8] + this->mElements[15] * rhs.mElements[12];
float temp_13 = this->mElements[12] * rhs.mElements[1] + this->mElements[13] * rhs.mElements[5] + this->mElements[14] * rhs.mElements[9] + this->mElements[15] * rhs.mElements[13];
float temp_14 = this->mElements[12] * rhs.mElements[2] + this->mElements[13] * rhs.mElements[6] + this->mElements[14] * rhs.mElements[10] + this->mElements[15] * rhs.mElements[14];
float temp_15 = this->mElements[12] * rhs.mElements[3] + this->mElements[13] * rhs.mElements[7] + this->mElements[14] * rhs.mElements[11] + this->mElements[15] * rhs.mElements[15];

this->mElements[0] =  temp_0;
this->mElements[1] =  temp_1;
this->mElements[2] =  temp_2;
this->mElements[3] =  temp_3;

this->mElements[4] =  temp_4;
this->mElements[5] =  temp_5;
this->mElements[6] =  temp_6;
this->mElements[7] =  temp_7;

this->mElements[8] =  temp_8;
this->mElements[9] =  temp_9;
this->mElements[10] = temp_10;
this->mElements[11] = temp_11;

this->mElements[12] = temp_12;
this->mElements[13] = temp_13;
this->mElements[14] = temp_14;
this->mElements[15] = temp_15;

return  *this;
}

• There is a problem You should add an explanation of what the problem is, and how your code fixes it. Otherwise you're leaving it up the reader to figure it out. May 30, 2019 at 2:47
• Welcome to Code Review! You have presented an alternative solution, but haven't reviewed the code. Please edit to show what aspects of the question code prompted you to write this version, and in what ways it's an improvement over the original. It may be worth (re-)reading How to Answer. May 30, 2019 at 12:06