Andrew Pouliot

Vector Math with C++ and GLKit

If you do a lot of graphics development, you're sure to use lots of vector math. A while ago I wrote some C++ extensions to Apple's GLKit Math utilities, but haven't had a chance to explain why they would be helpful, and how they can improve your graphics code.

So, say you have a simple numerical solver that keeps track of the position of a ball over time written in straight C or Objective-C. You might have some code that looks like this:

###Scalar math

float ball_x, ball_y, ball_z;

...

ball*x = ball_x + change_x * dt;
ball*y = ball_y + change_y * dt;
ball_z = ball_z + change_z \* dt;

###Structs

That's a tad awkward, so defining a struct to contain x,y,z floats together makes a lot of sense:

typedef struct {
  float x, y, z;
} vector3;

vector3 ball;

...

ball.x = ball.x + change.x _ dt;
ball.y = ball.y + change.y _ dt;
ball.z = ball.z + change.z * dt;

##Functions operating on structs

Well that's kind of better, but I want to do the same operation on every coordinate, and I don't want to keep peppering around these identical bits of code everywhere, so I'll define some convenience functions:

typedef struct {
  float x,y,z;
} vector3;

vector3 ball;

vector3 mulVectorScalar(vector3 a, float k) {
  vector3 r;
  r.x = a.x _ k;
  r.y = a.y _ k;
  r.z = a.z \* k;
  return r;
}

vector3 addVector(vector3 a, vector3 b) {
  vector3 r;
  r.x = a.x + b.x;
  r.y = a.y + b.y;
  r.z = a.z + b.z;
  return r;
}

...

ball = addVector(ball, mulVectorScalar(b, dt))

We can even do some optimizations for the processor architecture that we're running on. For instance, ARMv7 (available on recent iOS devices) has a set of extensions called "NEON" that allow operations on multiple data simultaniously at a hardware level.

For iOS 5's new GLKit framework, Apple created a set of functions like our addVector that use NEON vector instructions to accelerate operations on vectors and matrices without having to pepper your own code with ifdefs.

From <GLKit/GLKVector2.h>:

static inline GLKVector2 GLKVector2Add(GLKVector2 vectorLeft, GLKVector2 vectorRight)
{
#if defined(ARM_NEON)
  float32x2*t v = vadd_f32(*(float32x2*t *)&vectorLeft,
  _(float32x2_t _)&vectorRight);
  return _(GLKVector2 _)&v;
#else
  GLKVector2 v = { vectorLeft.v[0] + vectorRight.v[0],
  vectorLeft.v[1] + vectorRight.v[1] };
  return v;
#endif
}

The problem now is that these function names addVector and mulVectorScalar have taken the place of much more intuitive operators like + and *. Also, if we decide that the ball only needs to be a 2-element vector instead of a 3-element vector, we would need to switch all of the places where we update its position to new functions and new structs.

If we switch over to c++ land, we have a tool (operator overloading) that will let us have something both concise and efficient.

#import "GLKMath_cpp.h"

GLKVector2 ball;

...

ball = ball + change \* dt;

This is implemented in GLKVector2_cpp.h as follows:

#import <GLKit/GLKVector2.h>

...

inline GLKVector2 operator + (const GLKVector2& left, const GLKVector2& right) {
return GLKVector2Add(left, right);
}

inline GLKVector2 operator * (const float& left, const GLKVector2& right) {
return GLKVector2MultiplyScalar(right, left);
}

...

So, if you don't mind switching to c++, you can make your vector math a lot clearer to read (and hopefully reduce typos). If you notice anything amiss in with these headers or anything you think should be added, please create an issue or send a pull request on github

Fork on GitHub

More old posts