Avoiding Common Math Mistakes That Can Derail Game Projects
Underestimating Floating Point Precision
Floating point numbers have limited precision due to their binary representation. This can cause issues in game development when fractional values that appear identical can have slight differences at the binary level. These imprecisions may result in visual artifacts like gaps or jittering.
For example, representing the value 0.2 in binary floating point format results in the repeating fraction 0.19999999. When adding several values meant to equal 1.0, the result may differ slightly:
float a = 0.2f; float b = 0.2f; float c = 0.2f; float d = 0.2f; float e = 0.2f; float sum = a + b + c + d + e; // Equals 0.9999999 instead of 1.0
This imprecision could cause gaps to appear between game objects that should seamlessly connect or result in animations that jitter as objects are transformed across frames.
For applications requiring precise fixed point values, consider using fixed point math instead of floats. This represents values as integers that can exactly represent fractions with a fixed number of digits after the decimal point. This avoids the accumulation of rounding errors inherent in floating point math.
Forgetting to Normalize Vectors
Normalizing a vector means scaling its magnitude down to a length of 1 while retaining its direction. This is important for many graphics and physics calculations. For example, when computing lighting, normalizing the direction from the light source to the vertex ensures the brightness calculation depends only on the angle between the light and vertex normals, not the distance.
Vector3 Normalize(Vector3 v) { float mag = sqrt(v.x*v.x + v.y*v.y + v.z*v.z); v.x /= mag; v.y /= mag; v.z /= mag; return v; }
Forgetting to normalize can cause blocky graphics if lighting appears inconsistently bright on different object faces. It could also break collision detection if the velocity vector lengths used for projection aren’t normalized first. Always normalizing direction vectors consistently before use prevents subtle bugs down the line.
Mixing Up Coordinate Systems
Game worlds typically use a global world space coordinate system while models and textures rely on a local coordinate system tied to the orientation of that asset. Mixing these coordinate spaces up can lead to incorrect transformations.
For example, scaling a model’s local axes directly instead of its world transform will distort its shape. Conversely, animating models without converting bone joint positions from local space to world space first will result in incorrect vertex deformation.
Carefully separating code that operates in local vs. global coordinate spaces avoids these ambiguities. Use descriptor functions like LocalToWorld() and WorldToLocal() whenever translating between spaces to limit confusion.
Incorrect Bounding Volume Hierarchies
Bounding volumes encompass game objects to accelerate visibility culling, collision detection, and other performance critical operations. Organizing bounding shapes into hierarchies allows quickly rejecting large sections of geometry by testing only the outermost volume.
However, constructing bounding volume hierarchies requires balancing two competing goals: tighter fitting shapes provide more accurate culling while simpler structures with fewer levels are faster to test against. A poor design can negatively impact performance instead of improving it.
For example, encapsulating the entire world with one huge bounding box prevents almost no work while testing long chains of nested boxes throughout the scene hierarchy adds significant overhead. Analyzing the spatial distribution and movement of world geometry to construct an optimal bounding volume tree better optimizes testing and culling.
Improved Bounding Hierarchy: World - Outdoor Sections (6 bound boxes) - City Area (500 bound spheres) - Each building (4-8 bound boxes) - Indoor Sections (8 bound regions) Poor Bounding Hierarchy: World - Section 1 - Subsection 1A - Object 1 (bound sphere) ... - Section 2 ...
As a best practice, profiling various bounding volume configurations under actual game conditions guides settling on the right balance of simplicity and precision.
Lossy Physics Integrations
Simulating physics requires numerically estimating the results of dynamical systems like rigid bodies, cloth, vehicles, and particles over time. Using naive integration techniques that simplify calculations can artificially dampen or increase energy over many frames.
For example, the simple Euler method for approximating velocity and position changes over time steps leads to noticeable energy loss. Unchecked, objects may slow down or start tunneling through surfaces.
Vector3 EulerIntegrate(Vector3 pos, Vector3 vel) { float frameTime = 1.0 / 60.0; pos += vel * frameTime; return pos; // Energy steadily decreases }
Implementations using more complex, stable integrators like Verlet and Runge-Kutta avoid these pitfalls. While no numerical integrator offers perfect physical accuracy, guidelines exist for choosing schemes that balance performance vs. quality for common use cases.
Regardless of integrator, tracking conservation of energy and linear/angular momentum over tens of seconds assesses if unacceptable damping or exaggeration is occurring.
Overlooking Matrix Handedness
The coordinate system orientation standard differs between graphics programming and mathematical conventions. Graphics uses right-handed axes with +X horizontal to the right, +Y vertical up, and +Z pointing outward. Math fields often use left-handed systems with +Z pointing inwards.
These handedness differences extend to transform matrices. Swapping conventions but forgetting to transpose matrix column orders accordingly will result in mirrors transforms:
// Incorrect graphics transform with math matrix matrix4x4 gfxMat = Matrix4x4( 1, 0, 0, 0 0, 1, 0, 0 0, 0,-1, 0 0, 0, 0, 1 ); // Correct version matches conventions matrix4x4 gfxMat = Matrix4x4( 1, 0, 0, 0 0, 1, 0, 0 0, 0, 1, 0 0, 0, 0, 1 );
Effects from overlooked handedness mismatches may be subtle, like mirrors worlds, inside-out normal mappings, or impossible camera clipping configurations. Standardizing on convention per subsystem avoids incorrect matrix operations slipping through testing.