Up until now I’d been using Handmade Math for all my vector and matrix maths needs. I didn’t choose it for any reason beyond knowing I needed something, and that some of Sokol’s examples used Handmade Math.

It did what I needed well enough, but I was always slightly unhappy with it. It was only niggles, but they just kept on niggling: the members of its vector structs were named in uppercase: X, Y, Z. And all its function names were camel case, and overly long: HMM_LengthSquaredVec3() amd HMM_MultiplyMat4ByVec4(). The former just kept tripping me up when I forgot, and made typing just that little bit more tedious (“lowercase variablename dot shift X lowercase comma …”) when I didn’t forget. It’s the sort of thing that just needs to be filed off for comfort. And the latter just made all my maths code overly verbose and harder to read.

See, vector and matrix maths should be concise and simple to write, cause in any 3D game you do a whole awful lot of it. For numeric types it’d be a pain to write FOO_AddInt(a, b) instead of a + b all the time, and vector and matrix types should be as convenient—or as close as possible—to that.

(Here’s your chance to jump in and laugh at me for wanting to stick with plain C. I know C++’s operator overloading would allow a whole lot more convenience than all this function call syntax. But it’s simply not worth the world of pain which C++ brings with it.)

I looked at a few other options, and decided to go with gb_math: it’s also a public domain, single-header library. It has its x, y, z struct members in lowercase. And its functions were also lowercase and a little more concise: gb_vec3_mag2 and gb_mat4_mul_vec4(). (And for type-namespaced functions, I prefer the type name to come first and the function’s name afterwards; it’s worse for autocompleting in IDEs that I don’t use, but it’s much better for reading to have the function name adjacent to its arguments.)

Not that gb_math was without its flaws. Its type names were awkward camel case, like gbVec3. And I side-eyed it pretty hard when I saw instead of the usual run of π-related constants it had:

#define GB_MATH_TAU          6.28318530717958647692528676655900576f
#define GB_MATH_PI           3.14159265358979323846264338327950288f
#define GB_MATH_ONE_OVER_TAU 0.159154943091895335768883763372514362f
#define GB_MATH_ONE_OVER_PI  0.318309886183790671537767526745028724f

#define GB_MATH_TAU_OVER_2   3.14159265358979323846264338327950288f
#define GB_MATH_TAU_OVER_4   1.570796326794896619231321691639751442f
#define GB_MATH_TAU_OVER_8   0.785398163397448309615660845819875721f

Tau. So you’re one of those people.

But for this project at any rate, I had long since decided that I’d use neither degrees nor radians anywhere for angular units, so I really wouldn’t have much need for π, 2π, 1/π etc. constants anyway. Instead, all my angles are in turns—whole rotations; 1 turn == 2π == 360º. It means all angles are in the range [0, 1) and deltas in the range (-1, 1). It means the human-convenient divisions of 180º 90º, 45º, 22.5º are the nice and precise 12, 14, 18, 116. It just makes everything so much simpler.

So I tossed out HandmadeMath.h and added in gb_math.h, and started going through the code and replacing everything.

Well, it didn’t take very long using gb_math before I decided it wasn’t much to my liking after all. It wasn’t the type names, so much as it preferring to have its functions take output pointers as their first argument, and sometimes pointers for other arguments too: void gb_mat4_mul_vec4(gbVec4 *out, gbMat4 *m, gbVec4 in);. This made my vector maths code look (and read) like assembly code. And worse, it required copious quantities of intermediate variables just to have pointers to pass in:

gbVec3 v_dt;
gb_vec3_mul(&v_dt, vel, dt);
gbVec3 f_half_dt2;
gb_vec3_mul(&f_half_dt2, acc, (0.5 * dt * dt));
gbVec3 new_pos = pos;
gb_vec3_addeq(&new_pos, v_dt);
gb_vec3_addeq(&new_pos, f_half_dt2);
gbVec3 new_acc = normalized_force;
gbVec3 half_dt_accs;
gb_vec3_add(&half_dt_accs, acc, new_acc);
gb_vec3_muleq(&half_dt_accs, 0.5 * dt);
gbVec3 new_vel = vel;
gb_vec3_addeq(&new_vel, half_dt_accs);

I decided I had to write my own thin wrapper around it, so I’d be able to read my own maths code at least a little. I typedef’d all the names to things like vec3, mat4, quat, and wrote __always_inline__ wrapper functions to return vectors and matrices instead of needing pointers to them; and then I could rewrite the above code to be this:

vec3 new_pos = vec3_add3(pos, vec3_mul(vel, dt), vec3_mul(acc, (0.5 * dt * dt)));
vec3 new_acc = normalized_force;
vec3 new_vel = vec3_add(vel, vec3_mul(vec3_add(acc, new_acc), (0.5 * dt)));

Now my maths code looks like maths instead of variable-and-address-operator-soup. It’s much easier for me to read and write. The abbreviations are obvious (to me at any rate), and in any case used so often that—like int for integer—they’re second nature. (I’ve seen other code that uses v3 or f4 for vector type names, but to me one syllable is good; one letter is too far.)

In the process of writing the wrapper, I discovered an unexpected feature of C that made the wrapper even more convenient. I’d been generally writing:

vec3 foo = gb_vec3(1, 2, 3);

in preference to the clumsier:

vec3 foo = (vec3){ 1, 2, 3 };

but the gb_ prefix was irritating; and anyway the gb_vec3() and related functions couldn’t be used in static initializers (which I have quite a few of).

And then I realised that, while this is illegal, as it defines vec3 twice:

#define vec3 gbVec3
#define vec3(x,y,z) ((vec3){ (x), (y), (z) }) /* error! */

It turns out that this is perfectly legal:

typdef gbVec3 vec3;
#define vec3(x,y,z) ((vec3){ (x), (y), (z) })

So now I have my short and convenient vec3 type names, and my short and convenient and compile-time constant initializers vec3(1, 2, 3). And I am delighted!