Physics - Game Programming Algorithms and Techniques: A Platform-Agnostic Approach (2014)

Game Programming Algorithms and Techniques: A Platform-Agnostic Approach (2014)

Chapter 7. Physics

Although not every game needs physics, the ones that do implement collision detection and movement at a minimum. Collision detection enables the game to determine whether or not two game objects intersect with each other. A variety of different algorithms can be used to detect these collisions, and the first part of this chapter covers some of the more basic ones.

The movement part of physics takes into account force, acceleration, mass, and the other properties inherit in classical mechanics to determine where objects go from frame to frame. The only way this can be done is by using calculus, specifically the numeric integration methods covered in the second part of this chapter.

Planes, Rays, and Line Segments

Before we broach the topic of how we might represent characters and other game objects in the world for the purposes of collision detection, we need to discuss a couple of additional mathematical topics useful for physics systems. You may have learned about these objects in a high school algebra course, but now that you have familiarity with linear algebra we can approach them from a slightly different perspective.

Planes

A plane is a flat, two-dimensional surface that extends infinitely, much like a line is a one-dimensional object that extends infinitely. In games, we may commonly use planes as abstractions for the ground or walls, though there are also other uses. A plane can be represented in a few different ways, but game programmers prefer

Image

where P is any arbitrary point on the plane, Image is the normal to the plane, and the absolute value of d is the minimum distance between the plane and the origin.

Recall that a triangle is guaranteed to lie on a single plane. One reason the preceding plane representation is popular is because given a triangle, it is possible to quickly construct the equation of the plane that triangle is on. Once we’ve computed both Image and d, we can then test if any arbitrary point, P, also satisfies the equation, in which case this arbitrary point is also on the plane.

Suppose we have the triangle ΔABC expressed in a clockwise winding order (see Figure 7.1). In order to create the equation of the plane the triangle is on, we first need to calculate the normal of the triangle. Hopefully you remember that you can take the cross product between two edge vectors to determine the normal of the triangle—if you don’t, you should review Chapter 3, “Linear Algebra for Games,” before you go any further.

Image

Figure 7.1 Triangle ABC in a clockwise winding order.

Don’t forget that the order of the cross product matters, so it’s important to look at the winding order. Because ΔABC has a clockwise winding order, as illustrated in Figure 7.1, we want to construct vectors from A to B and B to C. Once we have these two vectors, we take the cross product between them and normalize the result to end up with Image.

Once we have Image, we need a point somewhere on the plane to solve for d, because

Image

Luckily, it turns out we have three points that we absolutely know are on the same plane as the triangle: A, B, and C. We can dot product any of these points with Image and we will end up with the same value of d.

All the points on the plane will yield the same d value because it’s a scalar projection: Image is normalized and P is unnormalized, so what we get is the minimum distance from the origin to the plane in the direction of Image. This minimum distance will be the same regardless of which vertex we select, because all of them are on the same plane.

Once we’ve solved for Image and d, we can then store these values in our Plane data structure:

struct Plane
Vector3 normal
float d
end

Rays and Line Segments

A ray starts at a specific point and extends infinitely in one particular direction. In games, it’s typical to represent a ray using a parametric function. Recall that a parametric function is one that is expressed in terms of an arbitrary parameter, generally by the name of t. For a ray, the parametric function is

Image

where R0 is the starting point of the plane and Image is the direction the ray is travelling in. Because a ray starts at a specific point and extends infinitely, for this representation of a ray to work properly, t must be greater than or equal to 0. This means that when t is 0, the parametric function will yield the starting point R0.

A line segment is similar to a ray, except it has both a start and end point. We can actually use the same exact parametric function to represent a line segment. The only difference is we now also have a maximum value of t, because a line segment has a discrete end point.

Technically, a ray cast involves firing a ray and checking whether or not the ray intersects with one or more objects. However, what is confusing is that most physics engines, including Havok and Box2D, use the term “ray cast” when in actuality they are using a line segment, not a ray. The reason for this is that game worlds usually have constraints, so it typically makes sense to use the more constrained line segment.

I admit that this terminology might be confusing. However, I wanted to be consistent with the terminology I’ve seen used in the game industry. So keep in mind as you continue reading the book that whenever you see the term “ray cast,” we are actually using a line segment.

So what are these ray casts used for anyways? It turns out they’re actually quite ubiquitous in 3D games. One very common example is firing a bullet that travels in a straight line. Although some games do simulate actual projectiles for their bullets, it may be appropriate to perform a ray cast instead, because the bullet travels so quickly. But there are other uses of a ray cast. Some examples include a reticule that changes red or green depending on friend or foe (covered in Chapter 10, “User Interfaces”), an AI that needs to determine whether an enemy is visible, Fresnel acoustic diffraction (covered in Chapter 6, “Sound”), and using the mouse to click objects in a 3D world (covered in Chapter 8, “Cameras”). All of these scenarios and many more use ray casts in one way or another.

Because a ray cast request uses a line segment, at a minimum we need two parameters—the start point and the end point of the ray cast:

struct RayCast
Vector3 startPoint
Vector3 endPoint
end

To convert startPoint and endPoint to the parametric form of a line segment is fairly straightforward. R0 is identical to startPoint, and Image is endPoint - startPoint. When the conversion is done in this manner, the t values from 0 to 1 will correspond to the range of the ray cast. That is to say, a t of 0 will yield startPoint whereas a t of 1 will yield endPoint.

Collision Geometry

In modern 3D games, it’s very common for human characters to be composed of 15,000+ polygons. When a game needs to determine whether or not two characters collide, it wouldn’t be terribly efficient to check against all these triangles. For this reason, most games utilize simplifiedcollision geometry, such as spheres and boxes, to test for collisions. This collision geometry isn’t drawn to the screen, but only used to check for collisions efficiently. In this section we cover some of the most common collision geometries used in games.

It’s also worth noting that it’s not uncommon for games to have multiple levels of collision geometry per object. This way, a simple collision check (such as with spheres) can be performed first to check whether there’s any possibility whatsoever that two objects collided. If the simple collision check says there might be a collision, we can then perform calculations with more complex collision geometry.

Bounding Sphere

The simplest type of collision geometry is the bounding sphere (or in 2D games, a bounding circle). A sphere can be defined with only two variables—a vector representing the center of the sphere, and a scalar for the radius of the sphere:

class BoundingSphere
Vector3 center
float radius
end

As illustrated in Figure 7.2, certain objects, such as an asteroid, fit well with bounding spheres. But other types of objects, including humanoid characters, end up with a lot of empty space between the edge of the character and the sphere. This means that for many types of objects, bounding spheres can have a large number of false positives. This happens when two game objects have intersecting collision geometry, yet the game objects themselves are not actually intersecting. False positives can be really frustrating for the player, because it will be apparent if the two game objects aren’t actually intersecting when a collision is detected.

Image

Figure 7.2 Bounding spheres for different objects.

Because of the relative inaccuracy of bounding spheres for most game objects, it usually is not appropriate to use bounding spheres as the only collision geometry. But the big advantage of bounding spheres is that instantaneous collision detection between two spheres is very simple, so this representation may be a great candidate for a first-line collision check.

Axis-Aligned Bounding Box

For a 2D game, an axis-aligned bounding box (or AABB) is a rectangle where every edge is parallel to either the x-axis or y-axis. Similarly, in 3D an AABB is a rectangular prism where every side of the prism is parallel to one of the coordinate axis planes. In both 2D and 3D, an AABB can be represented by two points: a minimum and a maximum point. In 2D, the minimum corresponds to the bottom-left point whereas the maximum corresponds to the top-right point.

class AABB2D
Vector2 min
Vector2 max
end

Because an AABB must keep its sides parallel to the coordinate axes, if an object is rotated the AABB may have to stretch, as demonstrated in Figure 7.3. But for 3D games, humanoid characters will typically only rotate about the up axis, and with these types of rotations the bounding box for the character may not change at all. Because of this, it’s very common to use AABBs as a basic form of collision representation for humanoid characters, especially because collision detection between AABBs, as with spheres, is not particularly expensive.

Image

Figure 7.3 Axis-aligned bounding boxes for different orientations of a character.

Oriented Bounding Box

An oriented bounding box is similar to an axis-aligned bounding box, but with the parallel restrictions removed. That is to say, it is a rectangle (in 2D) or a rectangular prism (in 3D) with sides that may not necessarily be parallel to the coordinate axes/planes. The big advantage of an OBB is that it can rotate as the game object rotates; therefore, regardless of the orientation of the game object, the OBB will have the same degree of accuracy. But this increased accuracy comes with a price; compared to an AABB, the collision calculations for an OBB are far more complicated.

An OBB can be represented in a game in multiple ways, including eight vertexes or six planes. But the OBB calculations are complex enough that this book does not outline any algorithms related to them. However, if you’d like to use them in your game, take a look at Real-time Collision Detection in the references at the end of this chapter.

Capsule

In 2D, a capsule can be thought of as an AABB with two semicircles (half circles) attached to the top and bottom. It’s called a capsule because it looks very similar to a medicine capsule. If we expand the capsule to 3D, it becomes a cylinder with two hemispheres attached to the top and bottom. Capsules are also a popular form of collision representation for humanoid characters because they are slightly more accurate than an AABB, as illustrated in Figure 7.4(a).

Image

Figure 7.4 Humanoid surrounded by a capsule (a), and a chair surrounded by a convex polygon (b).

Another way to think of a capsule is as a line segment that has a radius, which actually corresponds to the representation of it in a game engine:

struct Capsule2D
Vector2 startPoint
Vector2 endPoint
float radius
end

Convex Polygons

Another option is to use an arbitrary convex polygon (or in 3D, a convex hull) to represent the collision geometry. As you might expect, using an arbitrary convex polygon will be less efficient than the other options, but it also can be more accurate. Figure 7.4(b) shows a chair represented by a convex polygon. Although there still will be many false positives, the number of false positives is less than in alternative representations.

List of Collision Geometries

One last option to increase the accuracy of collision checks is to have an actual list of collision geometries to test against. So in the case of a human, we could have a sphere for the head, an AABB for the torso, and maybe some convex polygons for the hands/legs, and so on. By using several different types of collision geometries, we can almost completely eliminate false positives.

Although testing against a list of collision geometries is going to be faster than testing against the actual triangles of a model, it’s slow enough that you may not want to do it as a primary collision check. In the case of a humanoid, you should do a first-pass collision check against an AABB or capsule. Then only if that collision check passes should you bother checking against the list of collision objects. This, of course, assumes you even need that level of accuracy—you might need such accuracy to test for bullets colliding with the player, but you don’t need it to see whether or not the player is trying to walk into the wall.

Collision Detection

Now that we’ve covered some of the most common types of collision geometries used in games, we can take a look at actually testing for intersections between said objects. Any game that needs to respond to two objects colliding with each other must use some type of collision detection algorithm. Although some might find the amount of math in this section a bit off-putting, know that it is absolutely something that is used in a large number of games. Therefore, it’s very important to understand this math—it’s not covered just for the sake of being covered. This section won’t cover every permutation of every geometry colliding with every other geometry, but it does go over the most common ones you might use.

Sphere versus Sphere Intersection

Two spheres intersect if the distance between their center points is less than the sum of their radii, as shown in Figure 7.5. However, computing a distance requires the use of a square root, so to avoid the square root, it is common instead to check the distance squared against the sum of the radii squared.

Image

Figure 7.5 Two spheres intersect (a) and do not intersect (b).

The algorithm for this is only a couple of lines of code, as shown in Listing 7.1. It’s also extremely efficient, which is what makes using spheres a very popular basic collision detection option.

Listing 7.1 Sphere versus Sphere Intersection


function SphereIntersection(BoundingSphere a, BoundingSphere b)
// Construct a vector between centers, and get length squared
Vector3 centerVector = b.center - a.center
// Recall that the length squared of v is the same as v dot v
float distSquared = DotProduct(centerVector, centerVector)

// Is distSquared < sum of radii squared?
if distSquared < ((a.radius + b.radius) * (a.radius + b.radius))
return true
else
return false
end
end


AABB versus AABB Intersection

As with spheres, AABB intersection is not very expensive, even for 3D games. It’s easier to visualize the algorithm with 2D AABBs, though, so that’s what is covered here.

When checking for intersection between two 2D AABBs, rather than trying to test the cases where the two AABBs do intersect, it’s easier to test the four cases where two AABBs definitely cannot intersect. These four cases are illustrated in Figure 7.6.

Image

Figure 7.6 The four cases where two 2D AABBs definitely cannot intersect.

If any of the four cases are true, it means the AABBs do not intersect, and therefore the intersection function should return false. This means that the return statement should be the logical negation of the four comparison statements, as in Listing 7.2.

Listing 7.2 AABB versus AABB Intersection


function AABBIntersection(AABB2D a, AABB2D b)
bool test = (a.max.x < b.min.x) || (b.max.x < a.min.x) ||
(a.max.y < b.min.y) || (b.max.y < a.min.y)

return !test
end


It’s worth mentioning that AABB-AABB intersection is actually a very special application of an algorithm known as the separating axis theorem. In the general form, this theorem can actually be used on any type of convex polygon, though the details of it are beyond the scope of this book.

Line Segment versus Plane Intersection

Checking whether a line segment collides with a plane is a relatively common collision test in games. In order to understand how the algorithm for this works, it is useful to derive the formula using some linear algebra. First, we have the separate equations of a line segment and a plane:

Image

We want to find out if there is a value t such that R(t) is a point on the plane. In other words, we want to determine if there is a possible value of t where R(t) satisfies the P part of the plane equation. This can be done by substituting R(t) for P:

Image

Then it is just a matter of solving for t:

Image

Remember that for a line segment, the start point corresponds to t = 0, whereas the end point corresponds to t = 1. So when we solve for t, if we get a value that’s outside of that range, it should be ignored. Specifically, a negative value means that the line segment is actually pointing awayfrom the plane, as in Figure 7.7(a).

Image

Figure 7.7 Line segment pointing away from the plane (a) and parallel to the plane (b).

Also notice how if the dot product between Image and Image is zero, an illegal division by zero will occur. Recall that a dot product of zero means the two vectors are perpendicular. In this particular case, this means that Image is parallel to the plane and therefore cannot intersect with it, as in Figure 7.7(b). The only exception to this would be if the line segment is on the plane itself. In any event, this possibility must be taken into account to prevent division by zero.

As an added bonus, if the line segment and plane do intersect, we can simply substitute the t value to get the actual point of intersection, as shown in Listing 7.3.

Listing 7.3 Line Segment vs. Plane Intersection


// Return value is this struct
struct LSPlaneReturn
bool intersects
Vector3 point
end

// Remember "ray cast" actually refers to a line segment!
function LSPlaneIntersection(RayCast r, Plane p)
LSPlaneReturn retVal
retVal.intersects = false

// Calculate v in parametric line segment equation
Vector3 v = r.endPointr.startPoint

// Check if the line segment is parallel to the plane
float vDotn = DotProduct(v, p.normal)
if vDotn is not approximately 0
t = -1 * (DotProduct(r.startPoint, p.normal) + p.d)
t /= vDotn

// t should be between startPoint and endPoint (0 to 1)
if t >= 0 && t <= 1
retVal.intersects = true

// Calculate the point of intersection
retVal.point = r.startPoint + v * t
end
else
// Test if r.startPoint is on the plane
...
end

return retVal
end


Line Segment versus Triangle Intersection

Suppose you want to check if a line segment representing a bullet collides with a particular triangle. The first step to solve this problem is to calculate the plane that the triangle lies on. Once you have that plane, you can see whether or not the line segment intersects with it. If they do intersect, you will have a point of intersection on the plane of the triangle. However, because a plane is infinite, we need to determine whether this point of intersection is inside or outside a triangle.

If the triangle ΔABC is expressed in a clockwise winding order, the first step is to construct a vector from A to B. Then, construct a vector from point A to point P, the location that is being tested. If the rotation from vector Image to Image is clockwise, P is on the interior of that side of the triangle. This test can then be repeated for every other side (Image and Image), and as long as it is clockwise in every instance, it is interior of every side, and therefore on the interior of the polygon as a whole. This is process is shown in Figure 7.8.

Image

Figure 7.8 Point inside a triangle (a) and outside a triangle (b).

But how do we determine whether the rotation is clockwise or counterclockwise? Notice how in Figure 7.8(a), if we were to compute Image in a right-handed coordinate system, the resultant vector would go into the page. Recall that in a clockwise winding order in a right-handed coordinate system, the triangle’s normal also goes into the page. So in this case, the direction of the cross product result is the same as the triangle’s normal vector. If two normalized vectors have a positive dot product, that means they are facing in roughly the same direction. So if you normalize the result of Image and dot it with the normal, if it’s positive, then P is on the interior of Image.

This algorithm can then be applied to all the other sides of the triangle. It turns out that this works not only for triangles, but for any convex polygon that lies on a single plane. The pseudocode for this is provided in Listing 7.4.

Listing 7.4 Determining Whether a Point Is Inside a Convex Polygon


// This function only works if the vertices are provided in a
// clockwise winding order and are coplanar.
function PointInPolygon(Vector3[] verts, int numSides,
Vector3 point)
// Calculate the normal of the polygon
Vector3 normal = CrossProduct(Vector3(verts[1] – verts[0]),
Vector3(verts[2] – verts[1])
normal.Normalize()

// Temporary variables
Vector3 side, to, cross

for int i = 1, i < numSides, i++
// FROM previous vertex TO current vertex
side = verts[i] - verts[i – 1]
// FROM previous vertex TO point
to = pointverts[i – 1]

cross = CrossProduct(side, to)
cross.Normalize()

// This means it's outside the polygon
if DotProduct(cross, normal) < 0
return false
end
loop

// Have to test the last side, which is from last vertex to first
side = verts[0] – verts[numSides – 1]
to = pointverts[numSides – 1]
cross = CrossProduct(side, to)
cross.Normalize()

if DotProduct(cross, normal) < 0
return false
end

// We're inside all sides
return true
end


Sphere versus Plane Intersection

In a game where a ball can collide with a wall, in order to accurately model the collision it might be desirable to use sphere-plane intersection. Given that we only store Image and d for a plane, the easiest way to perform this collision check is to calculate d for a hypothetical second plane with the same Image that contains the center of the sphere. If the absolute value of the difference between this new plane’s d and the actual plane’s d is less than the sphere’s radius, it means they must intersect. This process is illustrated in Figure 7.9.

Image

Figure 7.9 Sphere-plane intersection in a case where they don’t intersect.

As with sphere-sphere intersection, the code for sphere-plane intersection is not that complex, and is outlined in Listing 7.5.

Listing 7.5 Sphere versus Plane Intersection


function SpherePlaneIntersection(BoundingSphere s, Plane p)
// Calculate d for the plane with normal p.normal and
// point on plane s.center
float dSphere = -DotProduct(p.normal, s.center)

// Check if we're within range
return (abs(ddSphere) < s.radius)
end


Swept Sphere Intersection

To this point, we have covered instantaneous collision detection algorithms. This means the algorithm checks to see if the two objects collide on the current frame. Although this can work in many cases, there are some instances where it won’t.

If a bullet is fired at a piece of paper, it’s unlikely there will be a precise frame where the bullet and the paper intersect with each other. That’s because the bullet is travelling so fast and the paper is so thin. This problem is fittingly known as the bullet-through-paper problem, and is illustrated in Figure 7.10. In order to solve this problem, a form of continuous collision detection (CCD) is necessary. Generic CCD is a topic that’s well beyond the scope of this book, but it’s worth covering at least one algorithm that does not have to worry about bullet-through-paper.

Image

Figure 7.10 Bullet-through-paper problem.

In swept sphere intersection, there are two moving spheres. The inputs are the positions of both spheres during the last frame (t = 0) and the current frame (t = 1). Given these values, we can determine whether or not the two spheres collided at any point between the two frames. So unlike the instantaneous sphere-sphere intersection, it won’t miss out on any intersections that occur between frames. This is shown in Figure 7.11.

Image

Figure 7.11 Swept sphere intersection.

You might notice that a swept sphere looks a lot like a capsule. That’s because a swept sphere is a capsule. A swept sphere has a start point, end point, and a radius, which is exactly like a capsule. So the algorithm discussed here can also be used for “capsule versus capsule” intersection.

As with “line segment versus plane” intersection, it’s useful to solve the equation first. It also turns out that solving the swept sphere equation is an extremely popular question during game programming interviews, because it requires a good grasp of many of the core linear algebra concepts that are expected from a game programmer.

Given a position of the sphere’s last frame and current frame, it’s possible to convert the position of the sphere into a parametric function. The process of converting it to a function equation ends up being identical to the process used for a ray cast. So given sphere P and sphere Q, we can express the position of both as separate parametric functions:

Image

What we want to solve for is the value of t, where the distance between the two spheres is equal to the sum of their radii, because that is the specific point where the intersection occurs. This can be represented mathematically as follows:

Image

The problem with this equation is we need some way to get rid of the length operation. The trick to this is remembering that the length squared of a vector Image is the same thing as the dot product between Image and itself:

Image

So if we were to square both sides of our sphere comparison statement, we would get the following:

Image

Now that we have this equation, we need to solve for t. This process is a little complicated. First, let’s substitute the full equations for P(t) and Q(t):

Image

Then we can do a little bit of factoring and grouping to make the equation a bit more palatable:

Image

To simplify it further, we can introduce a couple substitutions:

Image

Because the dot product is distributive over addition, we can then apply the FOIL (first, outside, inside, last) rule between the (A + Bt) terms:

Image

If we bring over the sum of the radii-squared terms to the left side of the equation and then apply a little more substitution, we will get an equation that may seem familiar:

Image

You might recall that this type of equation can be solved using the quadratic equation, which you may have not used in years:

Image

The value under the radical, known as the discriminant, is especially important. There are three ranges of note: less than zero, equal to zero, and greater than zero.

If the discriminant is less than zero, there is no real solution for t, which in this case means no intersection occurs. If the discriminant is equal to zero, it means that the spheres tangentially touch at exactly one value of t. If the discriminant is greater than zero, it means that the objects do fully intersect, and the smaller of the two quadratic solutions (the – in ±) is the t where the initial intersection occurs. All three of these scenarios are illustrated in Figure 7.12.

Image

Figure 7.12 Possible values of the discriminant in swept sphere intersection.

Once we have the value of t, we can then see if the value is between the 0 and 1 range that we care about for the code implementation. Remember that a t value greater than one is at a point after the current frame, and less than zero is prior to the current frame. Therefore, values outside the range are not something the function should concern itself with. The code implementation for this is provided in Listing 7.6.

Listing 7.6 Swept Sphere Intersection


// p0/q0 are the spheres last frame
// p1/q1 are the spheres this frame
function SweptSphere(BoundingSphere p0, BoundingSphere q0,
BoundingSphere p1, BoundingSphere q1)
// First calculate v vectors for parametric equations
Vector3 vp = p1.centerp0.center
Vector3 vq = q1.centerq0.center

// Calculate A and B
// A = P0 – Q0
Vector3 A = p0.centerq0.center
// B = vp – vq
Vector3 B = vpvq

// Calculate a, b, and c
// a = B dot B
float a = DotProduct(B, B)
// b = 2(A dot B)
float b = 2 * (DotProduct(A, B)
// c = (A dot A) - (rp + rq) * (rp + rq)
float c = DotProduct(A, A) – ((q0.radius + p0.radius) *
(q0.radius + p0.radius))

// Now calculate the discriminant (b^2 – 4ac)
float disc = b * b – 4 * a * c
if disc >= 0
// If we needed the value of t, we could calculate it with:
// t = (-b – sqrt(disc)) / (2a)
// However, this simplified function just returns true to say
// than an intersection occurred.
return true
else
// No real solutions, so they don't intersect
return false
end
end


Collision Response

We can use any of the preceding algorithms to determine whether or not two objects did, in fact, collide. But once they do collide, what should the game do? This is the response to the collision. In some instances that response might be very simple: One or both objects may die or be otherwise removed from the world. A slightly more complex response might be something like a missile reducing the health of an enemy on collision.

But what if the two objects need to bounce off of each other, such as two asteroids colliding in Asteroids? A tempting solution might be to simply negate the velocity of the asteroids upon collision. But a couple major issues crop up with this approach. One issue is the asteroids getting stuck. Suppose there are two asteroids that intersect on a particular frame, which results in their velocities being negated. But what if they’re travelling so slow that they still intersect on the next frame? Well, their velocities will get negated again and again, frame after frame, and they will be stuck in place. This is shown in Figure 7.13(a).

Image

Figure 7.13 Two asteroids get stuck (a), and the plane tangential to the point of intersection (b).

To solve this first issue, we need to find the precise point in time that the asteroids intersect, even if it occurs between frames. Because the asteroids use bounding spheres, we can use swept sphere intersection to find the exact time of intersection. Once we find the time of intersection, we must roll back the position of the two spheres to that point in time. We can then apply whatever change to the velocity we want (in our naïve case, negate them) and then complete the rest of the time step with the new velocity.

However, there’s still a big problem with our collision response: Negating the velocities is not the correct behavior. To see why this is the case, imagine you have two asteroids travelling in the same direction, one in front of the other. The front asteroid is travelling slower than the back asteroid, so eventually the back asteroid will catch up. This means when the two asteroids intersect, they will both suddenly start travelling in the opposite direction, which definitely is not correct.

So rather than negating the velocity, we actually want to reflect the velocity vector based on the normal of the plane of intersection. This uses the vector reflection concept discussed in Chapter 3. If an asteroid were colliding with a wall, it would be easy enough to figure out the plane of intersection because we can make a plane out of the wall. However, in the case of two spheres touching at precisely one point, we need to find the plane that’s tangential to the point of intersection, as in Figure 7.13(b).

To construct this tangential plane, we first need to determine the point of intersection. This can actually be done using linear interpolation. If we have two spheres that are touching at one point, the point ends up being somewhere on the line segment between the center of the two spheres. Where it is on that line segment depends on the radii of the spheres. If we have two instances of BoundingSphere A and B that are intersecting at precisely one point, we can calculate the point of intersection using linear interpolation:

Vector3 pointOfIntersection = Lerp(A.position, B.position,
A.radius / (A.radius + B.radius))

It turns out the normal of this tangential plane is simply the normalized vector from the center of one sphere to the center of the other. With both a point on the plane and a normal, we can then create the plane that’s tangential at the point of intersection. Although if our collision response is merely a reflection of the velocity, we only really need to compute the normal of the plane.

With this reflected velocity, our asteroid collision will look much better, though it still will seem weird because the asteroids will reflect and continue at the same speed as before. In reality, when two objects collide there is a coefficient of restitution, or a ratio between the speed after and before a collision:

Image

In an elastic collision (CR > 1), the relative speed afterward is actually higher than the relative speed beforehand. On the other hand, an inelastic collision (CR < 1) is one where the relative speed becomes lower. In the case of asteroids, we likely want an inelastic collision, unless they are magical asteroids.

A further consideration is angular dynamics, which are briefly covered at the end of the chapter. But hopefully this section gave you some idea on how to implement a more believable collision response.

Optimizing Collisions

All of the collision detection algorithms we covered allow for testing between pairs of objects to see if they collide. An optimization question that might come up is what can be done if there is a large number of objects to test against? Suppose there are 10,000 objects in the world, and we want to check whether the player collides with any of them. A naïve solution would perform 10,000 collision checks: one between the player and each of the objects. That’s not terribly efficient, especially given that the vast majority of the objects are going to be far away.

So what games must do is try to partition the world in some way such that the player only needs to test against a subset of the objects. One such type of partitioning for a 2D game is called a quadtree, because the world is recursively split into quarters until each node in the tree only references one particular object, as shown in Figure 7.14.

Image

Figure 7.14 A quadtree, where letters represent objects in the world.

When it’s time to do the collision check, the code would first check which of the original quarters intersect against the player, potentially eliminating three fourths of the objects instantly. Then a recursive algorithm could continue testing until it finds all of the nodes that potentially intersect with the player. Once there’s only a handful of objects left, they could each be tested against the player using the normal collision checks.

A quadtree isn’t the only partitioning method available; there are others, including binary spatial portioning (BSP) and octrees (which are the 3D version of quadtrees). Most of the algorithms partition the world spatially, but others partition it with some other grouping heuristic. Partitioning algorithms could easily be an entire chapter in a book, and it turns out there are multiple chapters on this topic in the aforementioned Real-time Collision Detection.

Physics-Based Movement

If a game requires objects to move around in the world, some form of physics will be used to simulate the movement. Newtonian physics (also known as classical mechanics) was formulated by Isaac Newton, among others, in the seventeenth century. The vast majority of games utilize Newtonian physics, because it is an excellent model so long as the objects are not moving close to the speed of light. Newtonian physics has several different components, but this section focuses on the most basic one: linear mechanics, which is movement without any rotational forces applied.

I want to forewarn you before going further into this topic that classical mechanics is an extremely comprehensive topic—that’s why there is an entire college-level class built around it. Of course, I can’t fit everything within the context of this book (and honestly, there are enough physics textbooks out there already). So I had to be very selective of which topics I covered—I wanted to talk about topics that had a high change of being included in a game written by someone who’s in the target audience for this book.

Linear Mechanics Overview

The two cornerstones of linear mechanics are force and mass. Force is an influence that can cause an object to move. Force has a magnitude and a direction, and therefore it is represented by a vector. Mass is a scalar that represents the quantity of matter contained in an object. For mechanics, the primary relevant property is that the higher the mass, the more difficult it is to move the object.

If enough force is applied to an object, in theory it would eventually start accelerating. This idea is encapsulated by Newton’s second law of motion:

F = m·a

Here, F is force, m is mass, and a is the acceleration. Because force is equal to mass times acceleration, it’s also true that acceleration is force divided by mass. Given a force, this equation is what a game will use in order to calculate the acceleration.

Typically, we will want to represent the acceleration as a function over time, a(t). Now acceleration understandably has a relationship between velocity and position. That relationship is that the derivative of the position function (r(t)) is the velocity function (v(t)) and the derivative of the velocity function is the acceleration function. Here it is in symbolic form:

Image

But this formulation is not terribly valuable for a game. In a game, we want to be able to apply a force to an object and from that force determine the acceleration over time. Once we have the acceleration, we then want to determine the velocity over time. Finally, with the velocity in hand, we can then determine the position of the object. All of these are need to be applied over the current time step.

So in other words, what we need is the opposite of the derivative, which you might recall is the integral. But it’s not just any type of integration we’re interested in. You are likely most familiar with symbolic integration, which looks something like this:

Image

But in a game, symbolic integration is not going to be very useful, because it’s not like we are going to have a symbolic equation to integrate in the first place. Rather, on an arbitrary frame we want to apply the acceleration over the time step to get the velocity and the position. This means using numeric integration, which is an approximation of the symbolic integral applied over a specific time step. If you ever were required to calculate the area under a curve using the midpoint or trapezoidal rule, you calculated a numeric integral. This section covers a few of the most common types of numeric integration used in games.

Issues with Variable Time Steps

Before we cover any of the numeric integration methods, it’s important to discuss one big gotcha for any physics-based movement. Once you are using numeric integration, you more or less cannot use variable time steps. That’s because the accuracy of numeric integration is wholly dependent on the size of the time step. The smaller the time step, the more accurate the approximation.

This means that if the time step changes from frame to frame, the accuracy of the approximation would also change from frame to frame. If the accuracy changes, the behavior will also change in very noticeable ways. Imagine you are playing a game where the character can jump, as in Super Mario Bros. You’re playing the game, and Mario is jumping at a constant speed. Suddenly, the frame rate drops low and you notice that Mario can jump much further. The reason this happens is that the percent error in numeric integration gets higher with a lower frame rate, so the jump arc becomes more exaggerated. This is demonstrated with the jumping character in Figure 7.15. This means a player on a slower machine will be able to jump further than a player on fast machine.

Image

Figure 7.15 Different jump arcs caused by different sized time steps.

For this reason, any game that uses physics to calculate the position should not use a variable time step for physics calculations. It’s certainly possible to code a game where the physics is running at a different time step than the rest of the game, but that’s a little bit more advanced. For now, you could instead utilize the frame rate limiting approach outlined in Chapter 1, “Game Programming Overview.”

Calculating the Force

Numeric integration will allow us to go from acceleration to velocity, and then velocity to position. But in order to compute the acceleration, we need force and mass. There are multiple types of forces to consider. Some forces, such as gravity, are constantly applied to an object. Other forces may instead be impulses, or forces applied for a single frame.

For example, a jump might be caused first by applying an impulse force to begin the jump. But once the jump starts, it’s the force of gravity that’ll bring the character back down to the ground. Because multiple forces can be acting on an object at the same time, the most common approach in a game is to sum up all the force vectors affecting an object and divide this by the mass to determine the current acceleration:

acceleration = sumOfForces / mass

Euler and Semi-Implicit Euler Integration

The simplest type of numeric integration is Euler integration, which is named after the famed Swiss mathematician. In Euler integration, first the new position is calculated by taking the old position and adding to it the old velocity multiplied by the time step. Then the velocity is similarly calculated using the acceleration. A simple physics object that uses this in its update function is shown in Listing 7.7

Listing 7.7 Euler Integration in Physics Object


class PhysicsObject
// List of all the force vectors active on this object
List forces
Vector3 acceleration, velocity, position
float mass

function Update(float deltaTime)
Vector3 sumOfForces = sum of forces in forces
acceleration = sumOfForces / mass

// Euler Integration
position += velocity * deltaTime
velocity += acceleration * deltaTime
end
end


Although Euler integration is very simple, it does not really exhibit very much accuracy. One big issue is that the position calculation is using the old velocity, not the new velocity after the time step. This results in a propagation of error that causes the approximation to diverge further and further as time continues.

A simple modification to basic Euler integration is to swap the order of the position and velocity calculations. What this means is that the position is now updating based on the new velocity, not the old velocity. This is semi-implicit Euler integration, and it ends up being reasonably more stable, to the point that respectable physics engines such as Box2D utilize it. However, if we want further accuracy we have to explore more complex numerical integration methods.

Velocity Verlet Integration

In velocity Verlet integration, first the velocity at the time step’s midpoint is calculated. This average velocity is then used to integrate the position over the entire time step. Next, the acceleration is updated based on the force and mass, and finally that new acceleration is applied to calculate the velocity at the end of the time step. The implementation of velocity Verlet is shown in Listing 7.8.

Listing 7.8 Velocity Verlet Integration


function Update(float deltaTime)
Vector3 sumOfForces = sum of forces in forces

// Velocity Verlet Integration
Vector3 avgVelocity = velocity + acceleration * deltaTime / 2.0f
// Position is integrated with the average velocity
position += avgVelocity * deltaTime
// Calculate new acceleration and velocity
acceleration = sumOfForces / mass
velocity = avgVelocity + acceleration * deltaTime / 2.0f
end


By essentially using both the average velocity and average accelerations when performing the integrations, the velocity Verlet method ends up with a much greater accuracy than both Euler methods. And the computational expense, although certainly greater than the Euler approaches, still is not that steep.

Other Integration Methods

A number of other integration methods might be used in a game, though they are a bit more complex. Perhaps one of the most popular advanced methods is the fourth-order Runge-Kutta method. The way this essentially works is by using Taylor approximation in order to approximate a solution to the differential equations representing the movement. This method is inarguably more accurate than both Euler and velocity Verlet, but it’s also slower. It might make sense for a game where very accurate integration is required (such as for car physics), but for most games it’s probably overkill.

Angular Mechanics

Angular mechanics is rotational physics. For example, you might need this type of physics when you have an object tied to a string and rotating around another object. Just as linear mechanics has mass, force, acceleration, velocity, and position, angular mechanics has moment of inertia, torque, angular acceleration, angular velocity, and the angular position (or angle). The equations for angular mechanics are a little bit more complex than linear mechanics, but not by much. As with linear mechanics, you will also have to numerically integrate if you want to simulate angular mechanics in your game. Some types of games that have movement, such as a billiards game, absolutely need angular mechanics to function properly. But this is not as prevalent as games that rely on linear mechanics, so I choose not to cover this topic in greater detail.

Physics Middleware

Recall from Chapter 1 that middleware is a code library that implements a solution to a common problem. Because of the level of complexity of physics and how vast the problem is, it is extremely common for game companies to utilize a middleware package for physics in lieu of implementing it themselves.

The most popular commercial physics middleware for 3D games without question is Havok Physics. If you go through a list of the top AAA games from the last 10 years, you’ll find that a staggering number of them utilize Havok. Although Havok is designed for commercial game engines, there is a version of it for smaller developers.

An alternative that’s considered industrial strength is PhysX, whose claim to fame is that it was selected as the default physics system used by Unreal Engine 3 (and newer versions of Unity). The library is available free as long as you have less than $100,000 in yearly revenue.

For 2D physics, the most popular solution by far is the open source Box2D (http://box2d.org). The core library is written in C++, but there are ports to many different languages, including C#, Java, and Python. It’s pretty much the de facto 2D physics engine, and many 2D physics-based games, including Angry Birds, use it.

Summary

This chapter was a lengthy overview of using physics in 2D and 3D games. Collision geometry, such as spheres and boxes, is used to simplify intersection calculations. There are many potential intersection tests, including AABB vs. AABB and line segment vs. plane, and we looked at the implementation of many of them. Remember that some intersection tests, such as swept sphere, are continuous in that they can detect intersections between frames. Finally, movement in games is most commonly implemented through the use of linear mechanics. This means that we must use numeric integration methods such as Euler integration to compute position from velocity and velocity from acceleration.

Review Questions

1. What is the difference between an axis-aligned bounding box (AABB) and an oriented bounding box (OBB)?

2. What plane equation is most commonly used in games? What does each variable in said equation represent?

3. What is a parametric equation? How can you represent a ray via a parametric equation?

4. How can you efficiently determine whether or not two spheres intersect?

5. What is the best approach to determine whether or not two 2D AABBs intersect?

6. In line segment vs. plane intersection, what does a negative t value represent?

7. What is the difference between instantaneous and continuous collision detection?

8. In the swept sphere solution, the discriminant can be negative, zero, or positive. What do all three of these different cases represent?

9. Why is it a bad idea to use a variable time step when using numeric integration methods?

10. How does velocity Verlet integration work conceptually?

Additional References

Ericson, Christer. Real-time Collision Detection. San Francisco: Morgan Kaufmann, 2005. This book is the end-all be-all book when it comes to collision detection. It covers many different types of collision geometry and all the possible permutations between them. Be warned that this book has a large amount of math, so you might want to make sure you are comfortable with the math in this book before picking it up.

Fielder, Glenn. Gaffer on Games – Game Physics. http://gafferongames.com/game-physics/. This blog covers several different topics, but the physics articles are of particular note. Fielder covers topics including how to implement fourth-order Runge-Kutta as well as angular dynamics and springs.