Feb. 4, 2021

# Rectangular Collision Detection in D3 Force Layouts

Collision detection is a problem that comes up a lot in **game development**, and also in designing **physical simulations**, where the goal is to detect **intersecting objects**.

## Collision detection in D3.js

In **D3**, a **force layout** graph works as a physical simulation.

I'm assuming **some familiarity** with force layouts in D3 here - check out some of the posts in the sidebar if you're totally unfamiliar with them.

In this post we're going to adapt a built-in force in D3 that handles collision detection with circles for use with rectangles.

- You can find the code here.

### D3 force layout

You might have worked with bubble charts, or a **basic network graph** where the nodes are circles.

Setting up a force layout can be pretty straightforward:

- Start with your data for the graph nodes and sometimes links (we're ignoring links today).
- Initialize a
**simulation**.
- Assign some
**forces** to it.
- Define a
**tick** function to update the position of the circles as the simulation progresses.

Often a **combination of forces** work together to **repel** and/or **attract** nodes from one another to create the desired layout.

One such force works to correct **overlapping** nodes by detecting when two nodes are **colliding** and then **correcting** the overlap.

### forceCollide

D3 has a built-in force for collision detection, forceCollide, which works on **circles**.

But what if you're working with **rectangles** instead of circles?

**forceCollide** uses the **radius** of the circle to detect a collision and correct it.

In this post we will adapt **forceCollide** to work on rectangles.

The new force will be called **rectCollide** and can be used in a simulation like:

var simulation = d3.forceSimulation()
.force('collision', rectCollide().size(function(d){return [d.width,d.height]}))
.nodes(nodes);

But let's not get ahead of ourselves quite yet.

## How does collision detection work?

Two basic shapes in 2-dimensional collision detection problems are circles and rectangles.

Let's have a quick overview of circle collisions.

### Detect if two circles are colliding

The basic idea is to look at each pair of circles.

- Each circle has a
**radius**.
- If the
**sum of their radii** is **less than** the **distance** between the circles, then they are **colliding**.

#### How do we find the distance between two circles?

First draw a line between their **center x-coordinates**, and then a line between their **center y-coordinates**.

This creates a **right triangle** where the **hypotenuse** will be the distance between the two circles.

A refresher of the pythagorean theorem...

If the hypotenuse is **smaller** than the sum of each circle's radius, the circles must be colliding.

Compare the yellow hypotenuse for each circle pair with their radii to see how this is true.

### Detect if two rectangles are colliding

- Specifically, we're looking at axis-aligned, or
**unrotated** rectangles in 2 dimensions (x and y) in this post.
- Also known as
**axis-aligned bounding boxes**, or **AABBs** for short.

A rectangle is described by an **x** and **y** coordinate, along with a **width** and a **height**.

We'll use the **center point** of the rectangle for collision detection purposes.

Instead of a radius, we will use the **half-width** and **half-height** of the rectangle, measured from the center point.

- If the
**x-distance** between two rectangles is **less** than the **sum of their half-widths**, there is overlap.
- If the
**y-distance** between two rectangles is **less** than the **sum of their half-heights**, there is overlap.

Rectangles are colliding if there is **both** x and y overlap.

Here we are concerned with two rectangles **r1** and **r2**, which are colliding.

Notice that the **red rectangle** has **y-axis** overlap with **r1**, and **x-axis** overlap with **r2**.

But it is **not** colliding with anyone because if it were it would have both x and y overlap with its collision partner.

Here is pseudocode for detecting the collision between r1 and r2:

r1 = {centerX: 10, centerY: 14, , width: 14, height: 14}
r2 = {centerX: 19, centerY: 21, width: 12, height: 12}
xSize = (r1.width + r2.width)/2
ySize = (r1.height + r2.height)/2
dx = r1.centerX - r2.centerX
dy = r1.centerY - r2.centerY
absX = Math.abs(dx)
absY = Math.abs(dy)
//x overlap
xDiff = absX - xSize
//y overlap
yDiff = absY - ySize
if(xDiff < 0 and yDiff < 0){
//collision has occurred
}

## Responding to a collision

Once we've detected a collision, we will respond by adjusting the **velocity** of the two rectangles to separate them.

To do this, we need to find the **direction** and **speed** of the collision, which we will use to adjust the velocities of the rectangles and push them apart.

### Velocity

If you remember your physics, velocity is a **vector quantity**, defined by a **magnitude** and a **direction**.

The magnitude of velocity is **speed**, and velocity represents an object's speed in a certain direction.

Nodes in a force layout have a **velocity** in both the x and y direction, with attributes **vx** and **vy**.

### Pushing the rectangles apart

- We're only going to push the rectangles apart along
**one** axis.
- It will be the axis with the
**least** amount of overlap.

#### Find the separation vector

The separation vector will tell us a couple of things:

- Which axis has the least amount of overlap, and how much overlap there is.
- The direction of the collision.

We will use this to calculate the **collision normal**, which is a unit vector that has a magnitude of 1 and gives us the collision direction.

Initialize the vector components.

sx = xSize - absX;
sy = ySize - absY;

In the example collision, you can see that there is **less overlap** on the **x-axis** than on the y-axis, so the rectangles will be pushed apart along the x axis until they are separated, which will also resolve the overlap on the y-axis.

The initial separation vector is:

Then do the rest of the calculations.

if(sx < sy){
//x overlap is smaller than y overlap
if(sx > 0){
//only going to worry about x velocity, so set sy to zero
sy = 0;
}
}else{
//y overlap is smaller than x overlap
if(sy > 0){
//only going to worry about y velocity, so set sx to zero
sx = 0;
}
}
if (dx < 0){
//change sign of sx
sx = -sx;
}
if(dy < 0){
//change sign of sy
sy = -sy;
}

- The x overlap is smaller than y, so set
**sy = 0**.
**dx** is less than zero, so change the sign **sx = -sx**
- (dy is also less than zero, but sy is already 0 and would be set to -0)

And the final separation vector is:

#### Collision normal

To get the collision normal, we divide the separation vector components by the distance, which is the hypotenuse of the triangle created by them - the yellow line in the graphic.

distance = Math.sqrt(sx*sx + sy*sy)

Notice that **sy** was previously set to zero, since the x-axis has the smallest overlap and we are therefore ignoring the y-axis, so here it breaks down to finding the square root of **sx** squared, which is just (the absolute value of) **sx**.

Now to get the collision normal, just divide each component of the separation vector by the distance.

collisionNormalX = sx/distance = -4/4 = -1
collisionNormalY = sy/distance = 0/4 = 0

The collision normal vector is [-1,0].

In general, the collision normal for each direction would be:

- left = [-1,0]
- right = [1,0]
- bottom = [0,-1]
- top = [0,1]

#### Relative velocity

Next calculate the relative velocity of the rectangles.

- The velocity of
**r1 relative to r2** is the velocity that **r1** would appear to have from the **perspective of r2**.
- This allows us to treat
**r2** as **stationary**.

We will calculate the **speed** of the collision from the relative velocity and the collision normal.

rVelocityX = r1.vx - r2.vx
rVelocityY = r1.vy -r2.vy

Simply subtract r2's velocity from r1, for each component **vx** and **vy**.

## Calculate the speed of the collision

The relative velocity and the collision normal are both vectors.

We can take the dot product of these two vectors to get the speed of the collision.

speed = rVelocityX * collisionNormalX + rVelocityY * collisionNormalY

If the speed is **positive**, that means the rectangles are moving **towards** each other, so we will need to resolve the collision.

A **negative** speed means they're moving **away** from each other and the collision will resolve itself.

We want to take the rectangle **mass** into consideration, so that rectangles with smaller mass are pushed more than rectangles with larger mass.

Calculate the impulse which is then used to calculate momentum, which is the product of mass and velocity.

impulse = 2*speed / (r1.mass + r2.mass);

Then use this to update the velocity of each rectangle - remember that we're only pushing the rectangles apart along the **one** axis with the least amount of overlap, so we only need to update one component of the velocity.

In this example it's the x-axis velocity, so we will update the **vx** attribute for each rectangle.

r2.vx -= (impulse * r1.mass * collisionNormalX)
r1.vx += (impulse * r2.mass * collisionNormalX)

This will push the rectangles away from each other.

If you're interested in learning more about the physics of collisions, this Khan Academy article is a good starting point.

## Checking all object pairs for collisions

We've covered detecting and responding to a collision between **two objects**.

But in a D3 simulation we need to expand this to check for collisions between **all** object pairs.

One brute-force solution would be to use **nested loops** to compare all objects to all others, but that would be very **slow**.

### Quadtrees

**forceCollide** uses quadtrees to search for colliding node pairs in a much more efficient way.

A quadtree is a data structure that is used to partition a 2-D space by dividing it recursively into 4 regions.

If you're familiar with binary trees where each node has two children, in a quadtree each node has up to 4 children.

In a quadtree, each node is either a rectangle or a point, and will have up to 4 children that are rectangles and/or points.

The points are the **leaf** nodes, meaning they have no children.

We can efficiently determine if two nodes might be colliding by checking to see if they are located in the **same quadrant** in the quadtree, and continuing to traverse the tree until either:

- They are
**no longer** in the same quadrant.
- Or until we get to the leaves.

So if we are looking at a pair of nodes where one is located in the **top left** quadrant, and the other is in the **bottom right**, then we can **immediately dismiss** them since they are not located near each other to begin with and definitely are **not colliding**.

If the two nodes are somewhat close, maybe they are both located in the top left quadrant, then we continue to traverse down until they are not in the same rectangle.

Or if we get all the way to the leaf nodes(the points), then we check to see if they are colliding with the calculations from earlier.

## Thanks for reading!

My physics is pretty rusty (and to be completely honest, it was not one of my best subjects in school to begin with...) so I used a few sources to work out the calculations in this post.

The main sources I used were:

For more about quadtrees, check out this interactive visualization.

Reach out below or on Twitter @LVNGD with any questions or comments!