Game Math

What, you didn’t think games did math?

Subsections of Game Math

Introduction

Math plays a very large role in computer games, as math is used both in the process of simulating game worlds (i.e. to calculate game physics) and in the rendering of the same (i.e. to represent and rasterize 3D objects). Algebra and trigonometry play a vital role in nearly every game’s logic. However, vectors, matricies, and quaternions play an especially important role in hardware-accelerated 3D rendering.

Coordinate Systems

Computer games almost universally take place in a simulated 2D or 3D world. We use coordinate systems to represent the position of various objects within these worlds. Perhaps the simplest coordinate system is one you encountered in elementary school - the number line:

Number Line Number Line

The number line represents a 1-dimensional coordinate system - its coordinates are a single value that range from $ -\infty $ to $ \infty $. We normally express this as a single number. Adding a second number line running perpendicular to the first gives us a 2-dimensional coordinate system. The coordinate system you are probably most familiar with is the planar Cartesian Coordinate System:

Planar Cartesian Coordinates Planar Cartesian Coordinates

While 2D games sometimes use this coordinate system, most instead adopt the screen coordinate system. It labels its axes X and Y as does the Cartesian Coordinate System, but the Y-axis increases in the downwards direction. This arrangement derives from the analog video signals sent to Cathode Ray Tube computer monitors (which were adapted from the earlier television technology), and this legacy has lived on in modern computer monitors.

Points in both coordinate systems are represented by two values, an x-coordinate (distance along the x-axis), and a y-coordinate (distance along the y-axis). These are usually expressed as a tuple: $ (x, y) $. MonoGame provides the Point struct to represent this, however we more commonly use a Vector2 as it can embody the same information but has additional, desirable mathematical properties.

For three dimensions, we add a third axis, the z-axis, perpendicular to both the x-axis and y-axis. In Cartesian coordinates, the z-axis is typically drawn as being vertical. However, the two most common 3D rendering hardware libraries (DirectX and OpenGL), have the x-axis horizontal, the y-axis vertical, and the z-axis coming out of or into the screen. These two approaches are often referred to as left-hand or right-hand coordinate systems, as when you curl the fingers of your right hand from the x-axis to the y-axis, your thumb will point in the direction of the z-axis:

Left and Right-hand coordinate systems Left and Right-hand coordinate systems

DirectX adopted the left-hand coordinate system, while OpenGL adopted the right-hand system. There are some implications for this choice involved in matrix math, which we will discuss later, and for importing 3D models. A 3D model drawn for a left-hand system will have its z-coordinates reflected when drawn in a right-hand system. This can be reversed by scaling the model by $ -1 $ in the z-axis. When importing models through the content pipeline, this transformation can be specified so that the imported model is already reversed.

Coordinates in a 3D system can be represented as a tuple of three coordinates along each of the axes: $ (x, y, z) $. However, video games universally represent coordinates in 3d space with vectors; MonoGame provides the Vector3 for this purpose.

Trigonometry

Trigonometry - the math that deals with the side lengths and angles of triangles, plays an important role in many games. The trigonometric functions Sine, Cosine, and Tangent relate to the ratios of sides in a right triangle:

The trigonometry triangle The trigonometry triangle

$$ \sin{A} = \frac{opposite}{hypotenuse} = \frac{a}{c} \tag{0} $$ $$ \cos{A} = \frac{adjacent}{hypotenuse} = \frac{b}{c} \tag{1} $$ $$ \tan{A} = \frac{opposite}{adjacent} = \frac{a}{b} \tag{2} $$

You can use the System.MathF library to compute $ \sin $, $ \cos $ using float values:

  • MathF.Sin(float radians) computes the $ \sin $ of the supplied angle
  • MathF.Cos(float radians) computes the $ \cos $ of the supplied angle
  • MathF.Tan(float radians) computes the $ \tan $ of the supplied angle

You can inverse these operations (compute the angle whose $ \sin $, $ \cos $, or $ \tan $ matches what you supply) with:

  • MathF.Asin(float s) computes the angle which produces the supplied $ \sin $ value
  • MathF.Acos(float c) computes the angle which produces the supplied $ \cos $ value
  • MathF.Atan(float t) computes the angle which produces the supplied $ \tan $ value
  • MathF.Atan2(float x, float y) computes the angle with produces the supplied x/y ratio. This form can be helpful to avoid a division by 0 error if y is 0.

These angles are measured in radians - fractions of $ \pi $. Positive angles rotate counter-clockwise and negative ones clockwise. It can be helpful to consider radians in relation to the unit circle - a circle with radius 1 centered on the origin:

The unit circle The unit circle

The angle of $ 0 $ radians falls along the x-axis. MonoGame provides some helpful float constants for common measurements in radians:

  • MathHelper.TwoPi represents $ 2\pi $, a full rotation around the unit circle ( $ 360^{\circ} $).
  • MathHelper.Pi represents $ \pi $, a half-rotation around the unit circle ( $ 180^{\circ} $).
  • MathHelper.PiOver2 represents $ \frac{\pi}{2} $, a quarter rotation around the unit circle ( $ 90^{\circ} $).
  • MathHelper.PiOver4 represents $ \frac{\pi}{4} $, an eighth rotation around the unit circle ( $ 45^{\circ} $).

Inside the unit circle you can inscribe a right triangle with angle at the origin of $ \theta $. This triangle has a hypotenuse with length 1, so $ \sin{\theta} $ is the length of the opposite leg of the triangle, and $ \cos{\theta} $ is the length of the adjacent leg of the triangle. Of course $ \tan{\theta} $ will always equal $ 1 $.

Vectors

Games almost universally use vectors to represent coordinates rather than points, as these have additional mathematical properties which can be very helpful. In mathematical notation, vectors are expressed similar to points, but use angle brackets, i.e.: $ $ and $ $ for two- and three-element vectors. A vector represents both direction and magnitude, and relates to the trigonometric right triangle. Consider the case of a two-element vector - the vector is the hypotenuse of a right triangle with adjacent leg formed by its X component and opposite leg formed by its Y component. A three-element vector repeats this relationship in three dimensions.

MonoGame provides structs to represent 2-, 3-, and 4- element vectors:

  • Vector2 - two-element vectors, often used to represent coordinates in 2d space
  • Vector3 - three-element vectors, often used to represent coordinates in 3d space
  • Vector4 - four-element vectors, used for affine transformations (more on this soon)

Magnitude

The magnitude is the length of the vector. In XNA it can be computed using Vector2.Length() or Vector3.Length(), i.e.:

Vector2 a = new Vector2(10, 10);
float length = a.Length();

This is calculated using the distance formula:

$$ |\overline{v}| = \squareroot{(x_0 - x_1)^2 + (y_0 - y1)^2} \tag{0} $$ $$ |\overline{v}| = \squareroot{(x_0 - x_1)^2 + (y_0 - y1)^2 + (z_0 - z_1)^2} \tag{1} $$

In some instances, we may be able to compare the square of the distance, and avoid the computation of a square root. For these cases, the vector classes also define a LengthSquared() method:

Vector2 a = new Vector2(10, 10);
float lengthSquared = a.LengthSquared();

Normalization

In some cases, it can be useful to normalize (convert into a unit vector, i.e. one of length 1, preserving the side ratios) a vector. This can be done with Vector2.Normalize() or Vector3.Normalize(). When invoked on a vector object, it turns the current vector into its normalized form:

Vector2 a = new Vector2(10, 10);
a.Normalize(); 
// Now a is a normal vector <0.5, 0.5>

Alternatively, you can use the static version to return a new vector computed from the supplied one:

Vector2 a = new Vector2(10, 10);
b = Vector2.Normalize(); 
// Vector a is still <10, 10>
// Vector b is a normal vector <0.5, 0.5>

Mathematically, normalization is accomplished by

Addition

Subtraction

Multiplication

Division

Barycentric

Linear Interpolation

Reflection

Transformation