Gold Medal Games
Gold Medal Games logo
Gameplay Made Great
Written: 1/22/2026

GMGE Physics Part 3 of 3 - Collision Responses: a simplified look

Summary

I saved the best (or perhaps worst) for last. Where movement (Part 1) and collision detection (Part 2) were fairly easy, I found collision resolution to be a lot more complicated. Since a physics engine is a simulation or approximation of real world physics, some compromises have to be made. Those all have implications that pop up somewhere, often unexpectedly.

In this last part I'll try to thoroughly describe the choices made, and why they were made. By the end, we should have a good model for handling the kind of physics for games that I plan to build will likely require.

Conventions and Notation

This article uses some basic vector math, like the dot product, some trigonometry like the pythagorean theorem, geometry, algebra and of course some physics (like conservation of momentum). Each will be explained the first time it is used and why. Part of the reason for this article is to document how GMGE physics works, because the code is fairly complex. It is easy enough to see what it does, but this gives more complete reasoning around why.

Since there isn't a web-safe math font, and I don't want to make everything math-related an image, I'll use the following notations.


Inputs

When the collision resolver is called, we have a list of all of the collisions currently taking place. It is an array of collision objects that contain references to both objects involved, as well as to their physics colliders (shape and size). Additionally, collision objects contain the surface normal of the second object, and the depth of the collision. We pass one collision at a time to the resolver.

So we get object1, object2, object1Collider, and object2Collider references passed in via a collision object. At this point we already know that the two objects are in fact colliding, and that it is a physics (not a trigger) collision that needs to be resolved. The objects have velocity vectors which we will use to determine the reflection angle, based on the surface normal of the collision. Objects also have mass, elasticity and drag which is used to determine the magnitude of the new velocities post collision.

We also have the depth of the collision (see Part 2) which we will use to determine an impulse for each object to immediately get them out of collision with each other.


Constraints (Reminder)


High-Level Process

  1. Make sure the two objects are still colliding
  2. See if one object is resting on another
  3. Determine if the objects are simply falling together
  4. Calculate position adjustment impulses
  5. Solve for the reflection magnitude
  6. Impulse determination and update velocities


Still Colliding?

Think of a case where a falling player object lands on a platform. The platform is made up of several rectangle objects side by side. It is possible that the player lands right where the two tiles meet, colliding with both simultaneously. We resolve all the collisions for the frame one at a time. When we push the player up out of collision during the first player-platform resolution, it eliminates the player's collision with the other platform object as shown below.

Player falls from P0 to P1, colliding with A and B
A B C P 1 P 0

The player fell from P0 to P1, colliding with platform objects A & B simultaneously.

Player is pushed up out of collision with A, also resolving collision with B
A B C P 1 P 2

Since collisions are resolved one at a time, let's assume the Player vs B collision is resolved first. Since B is a static platform object, the Player is pushed up out of collision from P1 to P2. By doing so, it also resolves the collision between Player vs A. Since there is no longer a collision we exit out of collision resolution immediately. This is important because when a collision is resolved, velocities are updated. When the Player's first collision is resolved its downward velocity is reversed, so it ends up in an upward direction, still to the right. If we resolved the prior, but no longer colliding, collision, the upward velocity of the Player would be reversed again and end up pointing back down incorrectly.


Resting Objects

If we think of a stack of boxes just sitting there minding its own business, we would expect it to literally just sit there. Nice and boring. The reality though is that in a physics engine, all the objects are pushing down on each other due to gravity, and we have to push them back out of collision every frame. Rounding point errors can accumlate, and then objects appear to vibrate. This is not what we want. Below we have boxes A, B and C, resting on the ground box D which is static. Each has a downward velocity due to gravity, as shown by the downward arrows.

A B C D

If we can determine that an object is at rest, then we can clear its velocity and push it up out of collision. Up in this case really means the opposite direction of gravity. If gravity is set to the right, then "up" would be to the left. Hey, it is a game world, you can do what you want as a game designer.

To determine if an object is at rest it must be on top of another object at rest, and the only force acting on it in the direction of gravity is gravity. The specific amount being how much it would have accelerated this frame from a complete stop. By that I mean an object would have to have been stationary (only in the direction of gravity) in the previous frame, and its only acceleration is gravity scaled by deltaTime. Please note, an object moving along a platform is considered to be at rest. We don't want it to vibrate or bounce along as it moves. To be considered on top, collisionNormal needs to equal gravityNormal if B is the top object, or -(gravityNormal) if A is the top object.

Gravity scaled by deltaTime is considered the restingVelocity. If an object's velocity, again in the direction of gravity, is identical to that, we assume it is at rest. If the two objects involved in this particular collision are considered to be at rest, then we are going to push the object that is on top of the other one up out of collision. Of course to do that, we need to know which object is on top of the other. For that determination, we'll use the dot product of 2 vectors. The first vector being gravity, and the second is a vector from one object to the other. Center to center.

Dot Product in a nutshell

The dot product is known as the scalar product of 2 vectors. Meaning 2 vectors in, a single numerical value out. The dot product represents how much one vector points in the direction of the other vector. It is calculated by adding the products of the x-values of both vectors and the y-values of both vectors, or (a.x * b.y) + (a.y * b.y). Here is an example based on the image above, where we'll assume the height of all boxes is 1, and D is centered at (0, 0):

Let's say the vector for gravity, g = [0, -9.8]
Let's also create a vector from object A to object B, so AB = [B.x - A.x, B.y - A.y]. Using the image above as an example, A = (0, 3), and B = (0, 2), therefore AB = [0, -1]. A vector from the center of A down to the center of B. (It just happens to be a length of 1 in this case since that is the height of the boxes)

g • AB = (g.x * AB.x) + (g.y * AB.y), therefore our dot product in this case is 9.8, but what does that mean to us?

With the dot product the resulting values are either postive, zero, or negative, and here is what those represent:

Dot products become even more useful if you first normalize one of the two vectors involved, but we'll discuss that later when calculating separating velocities. For now, it is good enough to know that since AB is in the same direction of g, that A must be on top of B. If the dot product had been negative, then B would have been on top.

Why not just check the value of gravity, and since the x-component is zero in this case, just compare both objects y-components? That is essentially what we are doing here with the dot product, because the zero x-component of g multiplied by anything is zero. Therefore, if the objects were side by side, then the y-component of AB is zero. Since everything is zero, neither is on top of the other. Again, if the dot product is positive, then the vector from the first object to the second object matches the direction of gravity, so it is on top. Conversely, if the dot product is negative, then the second object is on top of the first object in relation to the direction of gravity.

Is an object at rest?

Since we know A is on top, we check the velocity of B. If B's velocity in the direction of gravity is zero, and A's velocity in the direction of gravity is less than or equal to restingVelocity, then A is at rest. We will only push A up. The amount to push A is the difference between A and B's combined half-heights and their current distance from each other in the direction opposite of gravity. So in this particular case, it would be the absolute value of the difference between their y-coordinates. That difference is guaranteed to be less than their combined half-heights since we know the objects are colliding, and therefore have some amount of overlap. (Please note, that if gravity is in a sideways direction, then we would use their half-widths instead. If gravity is in some arbitrary diagonal direction, which seems a little odd, things will behave strangely. GMGE physics only supports gravity in orthogonal directions.)

At this point, we zero out A's velocity, only in the direction of gravity, and consider this collision resolved, moving on to the next one in our list. Had either object not been resting though, then we have more work to do.


Falling Together

If we got this far, we know the objects are not at rest. However, they may be falling together. If their velocities in the direction of gravity are identical, and the collision normal is not perpendicular to the direction of gravity, then the only reason a collision occurred at all was due to a slight floating point difference. In reality, the two objects are just barely touching.

We want to handle two objects falling together side by side, and moving toward each other as a normal collision. It is a collision that needs to be resolved. In that case the collision normal is perpendicular to gravity. However, if on object is on top of the other, the collision normal is parallel to gravity, and their velocities match in the direction of gravity then we want to handle it as a special case.

Since the objects aren't really colliding, the floating point precision at such a small difference needs to be ignored. If we don't, trying to figure out the collision depth and the impulse required to separate them is also very prone to floating point precision issues if we use the general case collision resolver.

Instead, we just push the top object up by the collision depth, and don't modify the velocities of either object. Remember, up just means in the opposite direction of gravity.

If two objects were determined to be falling together, then we consider this collision to be resolved at this point, and move on to the next one in the list.


Resolving Collision Overlap

In looking at how different engines resolve collisions, and complaints about specific situations that didn't seem correct (or realistic), I decided to take a different approach. A common approach used, because it is quick and easy, and quite honestly given those engines handle rotations it may be computationally necessary to run with a reasonable frame-rate, is to push the objects away from each other in the direction that resolves the overlap with the shortest distance.

In the diagram below, the colliding objects would be pushed apart horizontally, and not vertically, since that is the shortest distance to get them out of collision. The box on the left would be pushed left, and the box on the right would be pushed right in proportion to their masses. Here is an example of what that would look like assuming they had equal masses.

That seems perfectly reasonable until you think about specific situations. For instance, what if the box on the right wasn't moving, and the box on the left was falling straight down? The solution on the left seemed wrong to me. GMGE opts for the solution on the right.

This becomes important in a couple situations. First, when you consider objects moving along platforms made up of multiple rectangles, and second when an object lands on a platform. Typically in a game, we don't want things to get caught on corners or bounce up off of them when they make up a larger platform.

Here is what happens when an object collides with another object at an angle. The initial down-to-the-right black line segment from the small box is its velocity. That is how far it moved this frame, causing it to collide with the larger box. The dashed red line is what should have happened if we were to model things realistically, a bounce at the point of collision. The solid black line that then moves straight up is the common technique to resolve the overlap. Then the proper speed and angle is applied to the box and it continues up and to the right. It just happens to be farther right than it seemingly should be.

However, the realistic approach causes problems in different situations, and the above example is exaggerated for illustration purposes. In reality, a little sliding it is not noticeable, and actually looks more realistic the faster that objects are moving. (It would be trivial to back things up to the point of collision, but we do not want to. It would lose the rest of its horizontal motion this frame, appearing to have a motion-jitter.)

Putting this all together, think of a platform made up of a series of boxes. Ideally you would just use 1 larger rectangle, but if your game needs destructable terrain, you probably want more granular platforms similar to the following example. We want the object landing on the platform to land and continue moving to the right. It would be jarring to see it catch a corner and reverse direction. This is what we want to avoid:

As you can see the falling object collides with 2 platform boxes simultaneously. If the one on the right is resolved first, and we were to only push to the shortest distance to get out of collision, it would be a side-hit with the platform, causing a reverse in horizontal direction. Also if we backed things up to the actual point of collision, you might notice a one-frame slow down as we back the falling object to the point of collision. It would lose the rest of its horizontal motion.

GMGE favors edge surface normals parallel to gravity, so objects are less likely to get caught on corners while moving. Again slightly exaggerated and slowed down it looks more like this:

The little bit of slide, even slowed down and elongated is still not easy to see, but it is there. Since the actual collision is resolved in a single frame, at normal speed it typically isn't noticeable.


Adjustment Impulses

We always want to move the two objects that are colliding out of collision. We have the collision depth, so now we have to make a decision of how much to push each object. There are different methods that we could use, and they all involve a compromise of sorts. Here are several common methods, and their consequences of doing so.

There are other methods different physics engines use to either prevent overlap, or move things out of collision. For example, technically when a box is sitting on the ground, according to the laws of physics, the ground is pushing up with a force equal to that of the gravity pushing down. Implementing something like that can get complicated quickly. Time-slicing is another method used that actually prevents penetration due to collisions. When collisions are detected, it backs things up to the point of the first collision, resolves it, then advances time by a small slice to the next collision. From a physics perspective, that probably produces the most accurate results. It also isn't suitable for most games with a fair number of objects moving around, because it is computationally expensive.

GMGE physics uses a velocity based impulse adjustment. This comes in handy because it does not generate new collisions from an object that wasn't moving in the first place. Well, at least not this frame. The object may incur a velocity change that puts it into a collision next frame, but that is so next frame.

It seems like a good compromise, as it is fairly realistic and performs well. So how does it work? We take each object, and check each component of its velocity. X and y in this 2D engine. We basically do the same thing with each component. Here is what happens for the x-component of the second object B.

First we create a combined velocity-magnitude-vector v. All this means is v.x = the absolute value of Av.x + the absolute value of Bv.x. That becomes the combined magnitude of x. (We do the same thing with the y-components.) The following code sets the adjustment impulse vector for B.

this.Badj.Set((this.v.x != 0 ? (Math.abs(this.Bv.x / this.v.x) * (col.depth + this.tolerance)) : 0) * (-col.collisionNormal.x), (this.v.y != 0 ? (Math.abs(this.Bv.y / this.v.y) * (col.depth + this.tolerance)) : 0) * (-col.collisionNormal.y));

Since we are illustrating things for Badj.x, we only need to look at the first line above. First check if v.x is non-zero, making sure there is some movement in x-direction. Well, that and we want to avoid dividing by zero. The next thing to happen is figure out what percentage of Bv.x is of the total x-velocity (or v.x). That is the division. We use the absolute value of the result because we just want the magnitude at this point. That percentage is then multiplied by the collision depth + a tolerance. That is then multiplied by the negative collision normal in the x direction.

The last 2 multiplications deserve some explanation. In the first case, what is the tolerance for, and in the second case why use the negative of the collision normal?

Floating point math

In JavaScript, what is 0.1 + 0.2? If you think it is 0.3, you would be close, but wrong. It is actually 0.30000000000000004. Why that is, is beyond the scope of this blog post. However, the reality is that whenever we are doing math with floating point numbers there can be a small error introduced. By adding in a tiny constant tolerance value, it makes sure that we use a value slightly larger than the collision depth. That guarantees that when we push the objects away from each other they won't still be colliding. It isn't noticeable visually, but when comparing numbers to detect a collision it is critical.

Negative collision normal

If you read Part 2, you might recall that the collision normal is with respect to the second object B involved in the collision. Since we are focussing on B and only its x push impulse, we use the negative of that so we push B away from A. We do the same thing for the y component of B. For adjusting A, we use the collision normal as-is to push A away from B when calculating its adjustment impulse.


Combined Velocities and Reflection Angles

If we have 2 objects A and B, and they are moving at velocities Av and Bv respectively, we can combine their velocities by subtracting one from the other.

If A and B are both moving directly to the right, their y-component will be 0. Perhaps Av = [4,0], and Bv = [1,0]. A is moving four times as fast to the right as B, so if A started to the left of B, it would be closing the distance over time.

If we look at things from the perspective of A only in regards to B, it is gaining on B at a speed of 3. [4,0] - [1,0] = [3,0]. If we look at it from B's perspective it would be the reverse, in that it is moving toward A at a speed of -3. While it isn't actually true, since both objects are in fact moving, why does it matter?

Here is a different example. On the left we have 2 moving objects that ultimately collide. On the right assume the rectangle is stationary, and let the circle move with their combined velocities. As you can see in regards to the actual collision (how they overlap), the result is the same.

A 0 A 1 B 0 B 1 A 0 A 1 B

On the left Av = [4,0], and Bv = [0,3]. On the right, we combine those velocities with respect to A, and get v = [4, -3]. As you can see, the overlap is identical.

Now we can use a single velocity to calculate the reflection angle. The equation to calculate a reflection vector is as follows:

reflection = v - 2(vsurfaceNormal) * surfaceNormal

reflection is a vector because we are ultimately scaling the surfaceNormal by the reflection magnitude. In other words if we drop the multiplication of the surface normal, we actually calculate the reflection magnitude which is a scalar value. This is the part we actually care about for now, the reflection magnitude.

Each object has an elasticity value, which we use to determine a coefficient of restitution. It ends up dampening the result of the collision in most cases. In a perfectly elastic collision, both objects have an elasticity value of 1.0, then the CoR has no effect. In normal circumstances though we multiply the CoR value by the reflection magnitude, and now we have the total magnitude to divy up amongst both objects. GMGE physics allows you to specify the type of CoR calculation you want to use in your game. You just select the one that works the best for your game. The default is CoR_min, so when two objects collide the object with the lower elasticity will be the CoR multiplier. (If CoR ends up being zero, then the objects would collide and not bounce apart. Just sort of a thud and stick together.)

const CoR_min = (e1, e2) => Math.min(e1, e2); // Use minimum elasticity const CoR_max = (e1, e2) => Math.max(e1, e2); // Use maximum elasticity const CoR_product = (e1, e2) => (e1 * e2); // Multiply elasticities const CoR_average = (e1, e2) => ((e1 + e2) / 2); // Determine average elasticity

Impulse Determination and New Velocities

We add the inverse masses of the two objects together, and divide the reflection magnitude by that sum. That allows us to conserve the total momentum before and after the collision when we factor in their previous velocities. impulseMagnitude = reflectionMagnitude / ((1 / A.mass) + (1 / B.mass)) Before doing that though, we have to accomodate some game engine features.

In GMGE, objects can have either axis be frozen if desired, and static objects are also supported. A static object essentially has infinite mass, equivalent to both axes being frozen. No amount of force will move it. The same is true for a frozen axis. When an object cannot move on a particular axis, but it is involved in a collision, we drop its mass from the calculation along the frozen axis. Because it is kind of unpredictable to do math with infinity, we assume 1 / infinity is 0. That way we still conserve momentum, and the object that can move on a particular axis gets the full brunt of the resulting impulse on that axis.

It might help to think of this this way. If we bounce a ball on the ground, the ground is a static object in that it isn't going to move. We expect the ball to bounce back up. However, if we had 2 hockey pucks on ice, and we slid one into the other one that wasn't moving, upon colliding we would expect the sliding puck to stop, and the puck that was stationary to move away in the opposite direction of the collision normal. Unless of course, the stationary puck was up against the boards in a hockey rink, in which case we would expect the sliding puck to bounce backward while the stationary puck remains still. A final twist on the last situation. If the sliding puck comes in at an angle, the stationary puck is frozen on one axis, but not the other.

The end result is that the sliding puck would take all of the rebound force on one axis and be evenly split on the other axis, since the pucks are the same mass. The equation looks complicated, but really isn't. Here is a description of what it does:

We create separate impulse magnitudes for each axis independently. It is the reflection magnitude divided by the combined inverse masses involved on that axis. The impulse magnitude for each axis is multiplied by each object's inverse mass, but zeroed out if that axis is frozen or perpendicular to the collision surface normal. When done for both axes, we have an impulse vector that is then added to the original velocity of the object.

Here is an example of the calculation, but remember we do this separately for the x and y axes:
    reflectionMagnitude = 2 * (v)
    inverseMass = (A.inverseMass * A.axisFreedom) + (B.inverseMass * B.axisFreedom)
    impulseMagnitude = reflectionMagnitude / inverseMass
    A.velocityAdjustment = * A.axisFreedom * impulseMagnitude * A.inverseMass
    B.velocityAdjustment = - * B.axisFreedom * impulseMagnitude * B.inverseMass

axisFreedom is 0 if the axis is frozen, and 1 if not. The velocity adjustments are then added back to the original velocities of the objects. When we do all that, we have completed the reflection equation:

    reflection = v - 2(vsurfaceNormal) * surfaceNormal

... and things behave as we would expect them to.


Particle Physics

You don't need to read this section. It just explains how and why GMGE simplifies the physics. It is the logic behind why it works, not actually how it is implemented.

Because we are not worried about rotation, the physics engine can make the assumption that A is always a single point mass, colliding with a bigger object B. We have to do some manipulation though to make that work. Whatever we shrink A by in terms of its length and width (which are the same for a circle, just the circumference), we need to increase the size of B by the same amount.

Since the objects are colliding, once we shrink A and enlarge B, A will end up being inside of B, which is what we want. At least for now anyway. We'll be pushing them apart soon enough. Here is what the before (top 3 collisions) and after (bottom 3 collision) situations look like for the three possible types of collisions.

Before: A and B at their actual sizes
A B A B A B
After: A is shrunk to a single point, and B is grown by the same amount A was shrunk by
A B A B A B

In the third case (CvR), note the rounded corners of B after it is grown. This definitely complicates things, and requires some special consideration. It combines solutions from the first two cases. RvR for the straight sides, and CvC for the rounded corners.

The point is that because we don't care about rotation, and the colliders are convex, we can rely on particle physics. Point masses bouncing off of each other.