Welcome to Arkanis Development

Math 3D - A simple vector and matrix library for C

Published

If you've done a bit of graphics programming with OpenGL you're probably familiar with vector and matrix math. Every point or direction in 3D space can be represented as a vector and most manipulations of those can be represented as matrices (moving them around, rotating them, projecting them on the screen, etc.).

I wasn't happy with the C math libraries I found so I wrote a new one (surprise!). Read on if you want to know why. But here is what using it looks like:

mat4_t projection = m4_perspective(60, 800.0 / 600.0, 1, 10);
vec3_t from = vec3(0, 0.5, 2), to = vec3(0, 0, 0), up = vec3(0, 1, 0);
mat4_t transform = m4_look_at(from, to, up);

vec3_t world_space = vec3(1, 1, -1);
mat4_t world_to_screen_space = m4_mul(projection, transform);
vec3_t screen_space = m4_mul_pos(world_to_screen_space, world_space);

OpenGL provides a lot of vector and matrix math for shaders that run on the GPU but on the CPU you have to do it yourself. I like it this way because it allows you to choose what kind of 3D math library you want: One that tries to optimize every last bit of performance but is complicated to use or one that is easy to use but lax on performance. Depending on the project you can pick what you need.

I do pretty much all of my low level graphics programming in C (I know, I'm weird, but OpenGL is a C API after all). And I haven't found a 3D math library I'm happy with for C. I don't know all of them but the ones I've looked at were complicated to use and vague on their semantics. Where they written with the OpenGL or Direct3D conventions in mind? How are matrices laid out in memory? Can I pass them into OpenGL directly or do I have to transpose them before hand?

There are good C++ math libraries that perfectly fit my purpose but I'm an awful C++ programmer. I spend way more time fiddling around with the language than I spend on solving the problem. So I stayed with C. Maybe someone else will find non-templated math code similarly appealing.

All this drove me to write my own small 3D math library for C. Just the basics, nothing fancy. A friend of mine joined in and together we started out from scratch. It was quite a nice learning experience. We spend a lot of time on the whiteboard, calculated a lot of the math by hand and wrote a lot of tests until we understood what the math should actually mean. It was just there that we realized that the perspective divide step in OpenGL is just an fixed function hack to fit a perspective projection into a 4x4 matrix. Kind of pointless with shaders...

Anyway, if you ever do OpenGL stuff in C and need a simple to use math library you can pick it up here: math_3d.h. It's a single header-file library in the style of stb_image.h so it's easy to integrate into projects.

It covers basic 3D vector math, transformation and camera matrices (translation, rotation, scaling and look at), projection matrices (orthographic and perspective) as well as basic matrix math (matrix-matrix, matrix-point and matrix-direction multiplication, inversion of affine transformations). The documentation is in the file itself so take a look if you're interested.

Happy programming. :)

6 comments for this post

leave a new one

#1 by
Patrick
,

oh man, you saved my life. there are, as you already said, only overly complex and overloaded c math libraries around. I have been searching for something like your for quite some time. c ist also my favorite, but not very popular around other devs, as it seems. thank your very much, that lib will be part of my routine as soon as I am finished writing this comment. really great work :)

#2 by
Stephan
,

Thanks for the comment. It's always good to know that the work is useful to someone else, too. :)

Have a good time with the library and don't hessitate to let me know if something isn't like you expect it to be.

#3 by
Skandar
,

You saved me too. I was looking exactly for this a vector math for C. I was using C++ but I got tired of it, As you said just fighting the language instead getting job done.

#4 by
bartek
,

Thank you so much - exactly what I was looking for ;)

#5 by
Sébastien Le Roux
,

Hello there, first of all thanks for this library, that is a great tool indeed ! I write first to let you know that I think that there is an error in your file, I pointed it in the 'm4_invert_affine(mat4_t matrix)' calculation: The following condition is not accurate enough:

if (fabsf(det) < 0.00001)
			return m4_identity();

You should write instead:

if (fabsf(det) == 0.00000)
			return m4_identity();

I actually came up with the error case and I struggled to find out that error was coming from this, and it does.

Also I came up with few improvements of the file, like the 'vec4_t' type (in particular for quaternions), 'm4_quat_rotation (vec4_t q)' for quaternion rotation, 'm4_frustum' … I will be happy to share it with you, but I think this message box is too small for that. If you are interested just let me know how to contact you.

Best.

#6 by
Stephan
,

Thanks for using and improving the library Sébastien. Especially for sharing your experience and improvements! You can send me a push request on GitHub or send me the new file directly via mail (see 3rd line of the file for my mail address).

Regarding the m4_invert_affine() determinant threshold: Can you send me the matrix that triggered the error so I can reproduce it and put it into the test case? It would also be great if you can send me how you constructed the matrix (what kind of rotations, translations, etc. you used to create it). That way Tobias and I can verify analytically if the function works correctly or not.

While we researched pretty much every other aspect of that function we haven't given the 0.00001 value much thought and it's unfortunate that you ran into a bug because of it. We'll have to do a bit more research to figure out what a proper threshold should be in that case. Or to put it in another way how the determinant looks like when the matrix can't be inverted.

I'm also not very happy that the function returns an identity matrix in that case. Maybe it's best to return a matrix full of NaNs so you at least know that something is broken. What would have helped you to catch the bug more easily?

Regarding the quaternions: We didn't include vec4_t because it makes matrix multiplication more confusing. More specifically the perspective division. But quaternions really do make sense. I haven't used them much but Tobias uses them, too. I'm curious what operations you implemented for them. :)

m4_frustum() sounds useful. Now that you mentioned it… kind of funny I forgot to put it in. :D

Leave a new comment

Having thoughts on your mind about this stuff here? Want to tell me and the rest of the world your opinion? Write and post it right here. Be sure to check out the format help (focus the large text field) and give the preview button a try.

Format help

Please us the following stuff to spice up your comment.

An empty line starts a new paragraph. ---- print "---- lines start/end code" ---- * List items start with a * or -

or