Vector projection

4 minute read

Previous - Angles Next - Matrices

Why vector projection

A projection of a vector onto another vector has many applications.

Imagine for example a cart on a slope. Given a vector made from two points on the slope and the gravity vector, we can use the projection of the gravitational acceleration vector onto the slope vector to calculate the direction and magnitude of the acceleration of the cart in a simple game physics setup.

Or think of the situation where a character jumps along an irregular polygon wall. When the collision circle of the character collides with the wall, we can calculate the closest point to the wall by projecting the circle’s center onto the wall.

If we want more precise collision detection and response, we can do this as well using vector projection, though we’ll need to learn a bit more about lines and polygons before we can get into that.

But before we can project points onto lines or start colliding polygons, we first need to get a basic understanding of how a vector can be projected onto another vector.

Calculating the projection of a vector onto another vector

If we look at the picture below, we see which is the projection of the vector onto . The goal is to calculate in terms of and .

proj_unrotated

To make this task easier, or at least more intuitive, we are going to rotate this setup. Let’s arrange the vector so that it is parallel with the x-axis.

proj_unscaled_equal

If we look at the vector now, we see that if . Now let’s scale our setup, so that is lying on the unit circle, a circle with as radius 1. In this case we see that is equal to the cosine of , which is the angle between and .

proj_cos

Of course in our second rotated but unscaled scenario, the vector doesn’t necessarily have a length equal to 1. Both and are scaled by the length of . This means that to go back to the second scenario, have to multiply by the length of to correct the scale.

As we stated before, we don’t want the angle in our equation, as we want a solution with just the two vectors and . We can use the dot product, since the cosine of an angle is equal to the dot product of the vectors that form the angle if the vectors are normalized

A normalized vector is one whose length is 1, thus it is obtained by dividing a vector by its length. Doing this with vectors and gives

Substituting the cosine in (1) by (2) gives

Or simplified, since we can ignore multiplying and dividing by the same factor

Our projection of vector in scenario 2 is was the vector or , substituting gives

We chose to be parallel with the x-axis to clearly see that was equal to the scaled cosine. But in our original scenario is not parallel to the x-axis. the only difference with scenario one and two is that we rotated everything. This rotation kept the sizes of the vectors as well as the angle between them intact. The only difference that affects the outcome is that is rotated. As we can see, the projected point always lies somewhere along . In scenario two, only lies along the x-axis because is parallel with it. The vector is nothing more than the normalized vector . Thus our projection of on in scenario one is

Since is equal to divided by it’s length we can write

And since the length is the square root of the dot product of with itself or we get

So the projection of on is the dot product of and divided by the dot product of with itself multiplied with .

function project(x1, y1, x2, y2)
    return mul(dot(x1, y1, x2, y2) / dot(x2, y2, x2, y2), x2, y2)
end

Projecting a point on a line

Now that we can project a vector onto another vector, it is easy to do things like projecting a point onto a line.

If we have a line AB defined by two points A and B on the line, and C the point we want to project, we build the vector , and project it onto the vector . From the resulting vector, we can then make the projected point C’ by adding A again.

Basically what happens is that first we translate everything so that A becomes the origin.

proj_move

Then we project AC onto AB.

proj_move_proj

And finally we translate everything back where it was.

proj_move_proj_move

function projectPoint(px, py, x1, y1, x2, y2)
    return add(x1, y1, project(px-x1, py-y1, x2-x1, y2-y1))
end

Distance of a point to a line

We can now calculate the distance from a point to a line, as the projected point is the point on the line which lies closest to our point.

function distancePointLine(px, py, x1, y1, x2, y2)
    return distance(px, py, projectPoint(px, py, x1, y1, x2, y2))
end

Distance of a circle to a line

Similarly we can calculate the distance from a circle to a line. We just measure the distance between the circle center and the line, and subtract the radius.

function distanceCircleLine(cx, cy, radius, x1, y1, x2, y2)
    return distancePointLine(cx, cy, x1, y1, x2, y2) - radius
end

While you might be tempted to use this for collision detection, by checking whether the distance is negative, don’t forget we’re using a square root in our distance calculation, which we don’t need. A more optimal way is using the squared distance, and looking whether that is smaller than the square of the radius.

function collidesCircleLine(cx, cy, radius, x1, y1, x2, y2)
    local x, y = projectPoint(cx, cy, x1, y1, x2, y2)
    return distance2(x, y, cx, cy) < radius * radius
end

Remember, only use distances when you actually need the distance, for comparing distances, use squared distances instead.

Previous - Angles Next - Matrices