Monday, June 23, 2014

Remember, our game engine is a learning experience...

So, when I first started the adventure of making a game engine, I had some grandiose ideas of what it would do.  Quite a few of my initial goals have actually been met (almost suprisingly); but that isn't to say I had quite a few bumps in the road with some faulty goals.  Most of the time these misbegotten ideas were crafted up because I didn't understand something or know enough about the topic to begin with (like 3d data formats with animation!).

Here, I will showcase some of my mistakes and talk about my troubles.  For your time, I will offer my lessons learned and elaborate on why what I was doing was good/bad/indifferent.

So, here we go, some of the times where I really messed up.  For fun, I'm going to rate how bad I messed something up on a scale of 1-10, with 1 being a minor lapse in judgement to 10 being evidence for logical absurdity and possibly insanity.

Floating point precision (Score: 9 / 10, I will regret this later in life once I figured out what happened).

So, you're playing a game, and lo and behold we fall through the floor.  Sheesh, these programmers must be lousy, they can't even tell where the ground is!  As it turns out, figuring out where the ground is located is actually not a trivial task!

Other neat things include objects moving through each other, falling out of a game level through arbitrary walls, the game camera clips though walls, things get stuck in the floor, and of course falling through the floor.

I can't speak for all engines, but in all likelyness, floating point precision is a likely culprit of at least some of these problems.

Early in my engine code, I had things like:

void someFunction(float x, float y)
{
	if (x == y)
		doStuff();
	else
		doOtherStuff();
}

While this looks innocent enough, it is a very, very bad idea.  Not only is this a bad idea, it is a stupendously, compoundingly bad idea with cross-platform development and bad for your general sanity when you use this code and get unexpected results in ways you have no idea yet.

Bruce Dawson has two really good articles about floating point comparisons that you really need to read that will hopefully enlighten you enough to realize a couple things.  One, computers suck at math, just like us, and two, you need to know what they are doing to use fractional numbers correctly (like, just where is that floor in 3d space anyways).  Sort of summing up what Bruce talks about (you should read it all anyways)...

There is no 100% awesome way of solving the floating point issue, but you, the engine developer, need to figure out the "best" way forward with some strategy and stick with it.  If you want a 100% accurate way of always having 100% accurate real number representation, you may wish to look in to scientific computing as opposed to game engine development as you will need a decent set of math understanding (and raw CPU power) to figure all that out.

Some caveats to watch out for are constant Epsilons and always thinking straight equality comparisons of floats are bad.

Example of a straight equality that is a Good Thing (tm).

float x = 1.0f;
if (x == 1.0f)
	hurray();

or even

double x = 1.0;
if (x == 1.0)
	hurray();

With no math involved after the initial assignment, this conditional should be fine. However, notice that I use the character 'f' on my number in the first example.  This forces single point precision, as opposed to double representation.  Depending on the system this is running on, if you leave the 'f' off, this comparison may or may not be equivalent!!!!  Do NOT mix having an 'f' on a float constant and then comparing it to one without! Example of messing up:

float x = 1.0f;
if (x == 1.0)
	hurray();

What about Epsilon? This is a reasonable strategy, albeit not very accurate, sometimes.  Epsilon is some margin of error.  The trick is that this margin of error grows as each float gets further away from 0 and sadly shrinks as they get closer to true 0.

It is a tad tricky to pin down exactly how much Epsilon should be given as it depends on the magnitude of the float number in question.  There is seemingly no tried and true solution here - or at least a provable superior solution given program execution performance as a requirement. For our engine, we combined an absolute Epsilon (for numbers near 0) and relative Epsilons (for numbers not near 0).

If you're making your own engine, this is a topic worthy of extensive research and testing because all games made on top of the engine will suffer from your failings to deal with this issue "in the most correct way".
Oh, and if you think this is fun now, just wait, there are more woes to talk about with floating point issues with game engine development, not just precision, but we'll get to those soon enough!

No comments:

Post a Comment