'),o.close()}(); /*]]>*/ Skip to main content

The pragmatical programmer’s guide to vectors

This blog post aims to be something like a cookbook for 2D Vectors. As such it will contain very practical recipes without diving into the theoretical background too much. There will be short introductions on how you can derive the formula, but you can also just skip those and go directly to the pseudo code, if that is all you need. Every recipe is nicely illustrated with interactive diagrams written in paper.js. You’ll need a reasonably modern browser for them to work, so if you don’t see them it really is time to update your browser!

Apart from vector addition/subtraction, which I assume you -my dear reader- have a good grasp of, there are two vector operations, which will be extensively used in this guide: normalization and the dot product. So let me just briefly explain both of them, before we dive right into the practical applications.

The basics

Normalization

Normalization of a vector scales it, so its length becomes 1 whithout changing its direction. A vector with a length of 1 is called unit vector and -as you’ll see in this guide- can be used for many vector operations. So: how do you scale the length of a vector to 1? Easy: just divide both x and y by the length of the vector. But how do you get the length of a vector? That involves the pythagorean theorem, which I won’t explain here, just trust me that the formula for the length of a vector is:

length = sqrt(vec.x * vec.x + vec.y * vec.y);

As you can see the formula contains a square root, which is not the most efficient operation. So as a rule of thumb the usage of the length operation and therefore also vector normalization should be minimized. And you can often get by without the square root, so you should also have the following operation at your disposal:

squaredLength = vec.x * vec.x + vec.y * vec.y;

If you want to compare the length of two vectors for example, there is no need for the square root: if the square root of one number is larger than the square root of another the same applies to the numbers themself! And if you want to know if a vector is longer than 5, simply compare its squared length to 25!

But now back to normalization! To normalize a vector you simply divide its x and y by the length of the vector, so it then has a length of 1:

 
float length = sqrt(vec.x*vec.x + vec.y*vec.y);
vector vecNormalized = new vector(vec.x = vec.x / length, vec.y = vec.y / length);

Dot Product


A dot product has two vectors as its input and just one number as its output. That number describes the relationship between those two vectors. You can think of it as the relationship between the directions of the vectors and the relationship between their length somehow mangled together.

You can already see that having one or both vectors normalized (length of 1) is of great value here because it essentially removes the length from that mangled number.

Again I won’t dive into the why (there’s wikipedia for that) and will only give you the formula:

dotproduct = vec1.x * vec2.x + vec1.y * vec2.y;

(no square root or other fancy operation here: this one is blazingly fast!)

From a practical point of view we can distinguish three different cases of dot products: one where both vectors are normalized, one where only one of the vectors is normalized and one where both vectors are not normalized.

Both vectors normalized

If both vectors have a length of 1 the only thing that remains in that mangled number is the relationship of the directions, which is basically the angle. But the dot product of two normalized vectors doesn’t directly spit out the angle, you’ll have to take the arc cosine of it to get the angle:

angle = acos(dotproduct(vec1, vec2));

(note: in most programming languages this angle will be in radian!)

Cosine and its cousins are expensive operations and should be used sparingly. So if you just need a general estimate of the angle between the two vectors just look at the dot product directly and let these numbers be your guide:

dotproduct 1: both vectors point in exactly the same direction
dotproduct 0.5: the angle between the two vectors is exactly 60°
dotproduct 0: the angle between the two vectors is exactly 90°
dotproduct -0.5: the angle between the two vectors is exactly 120°
dotproduct -1: the vectors point in opposite directions (180°)

As you can see the angle is not directed. When you only calculate the dot product you cannot know which vector is left or right, you only know by how much the direction is different. I’ll explain how you can also calculate the direction in the recipe “Directed angle”

Try it in the diagram: just keep both vectors on the circle marked with 1 (normalized, remember?) and move them around while watching the dot product and the angle.

Only one vector normalized

If only one of the vectors is normalized we still have the length of the other in our mangled number, but that is useful in its own way! Lets say Vector1 is normalized and Vector2 is not. Then the dot product of the two vectors results in the fraction of the length of vector2 that points in the same direction as vector1.

Try it in the diagram: first keep the blue vector on (1, 0) and move the red vector around. The only fraction of red’s length that points in blue’s direction is its x component, so the dot product should equal reds x component and changing only reds y shouldn’t change the dot product! And then also try different angles of both vectors, just always keep one of them on the circle marked with 1.

Both vectors not normalized

It is hard to read anything out of this kind of dot product, without further calculations. Because the length of both vectors has an effect on the magnitude of the dot product, only the sign of this dot product tells us something: is it positive, is it negative or is it zero?

Positive means that the vectors don’t differ by more than 90°
Negative means that they differ by more than 90°
If the vectors are perpendicular the dot product will always be 0, no matter how long the two vectors are.

Try it in the diagram: move the two vectors around freely and observe how the dot product switches from positive to zero to negative when the vectors are passing perpendicular.

Recipies

With the basics out of the way we can start the with the main course of this guide and bring in the recipies!

Rotate a 2D Vector by 90°


As a starter, a quick but important recipe: how to rotate a vector by 90° clockwise or counter clockwise. It is easy to see how you can achieve that if you first think of a special case: a vector that lies on one of the coordinate axis, so that one component is always 0. Lets say we have the vector x:1 y:0 (points on the x axis to the right) and we want to rotate it 90° clockwise so that it points on the y axis down. So the new vector has to be x:0 y:-1. See what we did there? we took the x component, negated it and used it as the new y component. We also took the y component and used it as the new x component, but because it was 0, we don’t know if we have to negate it or not. So just rotate that new vector 90° counterclockwise one more time, so that it points on the x axis to the left, becoming x:-1 y:0. Same thing again: take the x component (0), negate it (still 0) and use it as the new y component and now you can see that we took the y component (-1) and used it as the new x component unchanged. And that’s really all there is to it!

Try to think the example above through for counter clockwise rotation. You’ll see that it is the same operation, only this time we have to negate the y component before using it as the new x component. The vector can be of any length for that to work, it doesn’t have to be a unit vector. And this operation is as fast as it gets!

Here is the pseudocode for clockwise (rotate90CW) and counter clockwise (rotate90CCW) rotation:

rotate90CW (vec)
	return new vector(vec.y, -vec.x);
rotate90CCW (vec)
	return new vector(-vec.y, vec.x);

Just a short sidenote: there is a third Vector operation that is sometimes mentioned that is called the cross product. The cross product is defined as an operation that takes two vectors and returns a vector which is perpendicular to both input vectors. And as such it isn’t really possible in 2D, but sometimes you see the 2D Cross product defined as:

cross = vec1.x * vec2.y - vec1.y * vec2.x

Now, if you think about that formula what does it remind you of? Exactly: the dot product, only that one of the vectors has its x and y components exchanged and there is a minus inbetween instead of a plus. And that is exactly what the rotate 90° clockwise operation does! So: a 2D cross product is nothing else than a dot product with one of the vectors rotated 90° clockwise. Thinking about the cross product that way makes many of the applications much more clear in my opionion. That’s why I won’t talk about the cross product in this guide and use the dot product with one vector rotated instead.

Directed Angle


As you already know you can get the angle between two vectors by normalizing both of them, taking the dot product and calculating the arcus cosine with the result. But as already mentioned the output of the arcus cosine operation will always be positive, so you won’t know in which direction the vector is rotated. You’ll need a second dot product calculation to find that out.

Just remember that the dot product of two vectors will be negative if they are further than 90° apart. So when you rotate vector1 by 90° counter clockwise (called vector1perp in the diagram) for the second dot product and it is negative, that means that vector2 is rotated clockwise to vector1. Have a look at the interactive diagram and you’ll quickly see the connection.

// both vectors have to be normalized!
float dotProduct = dotproduct(vector1, vector2);
float angle = acos(dotProduct);
vector vector1perp = rotate90CCW(vector1);
float perpDotProduct = dotproduct(vector1perp, vector2);
if(perpDotProduct > 0)
	angle = -1 * angle;

By the way: this is the typical case where you’ll normally find the cross product and also a good example of why I think the 90° rotated dot product makes things easier to understand.

Vector projection

This is a very important basic operation that has many applications. With this operation you can project a vector onto a unit vector. You can think of it like folding the first vector onto the second: the resulting vector has the direction of the unit vector and the fraction of the length of the first vector that had the same direction as the unit vector. Now go look at that diagram and play with it, it is easier shown than explained. Move the dark blue vector and watch the projected vector (light blue) being always parallel to the ProjectionVector (red) and having the length projected from the dark blue vector onto the red vector.

Ok, so how does this operation work? The maths behind it is actually quite simple!
First we need to find out how much of the first vector points in the direction of the second vector. As you learned in the explanation of the dot product: when one vector is normalized and the other one is not the dot product provides the fraction of the non normalized vector that points in the same direction as the normalized one. And that is exactly what we need here. So make sure that the vector that you want to project onto is a unit vector (has a length of one)! And then simple get the dot product of the two vectors.
Next we need to make a new vector with that fraction as a length pointing in the direction of our projection vector. Nothing easier than that: because our projection vector is a unit vector we can simply multiply it by that fraction and we are done!
That is really all there is to it, here is the pseudo code for the whole operation:

// the projection vector has to be normalized!
projectVector(vec, projectionVec)
	// you could normalize projectionVec here, just to be sure...
	float dotProduct = dotproduct(vec, projectionVec);
	vector result = dotProduct * projectionVec;
	return result;

So what can you do with this operation? A lot! You can split a vector into two perpendicular parts, so that adding those two vectors results in the original vector again. For example lets say you have a spaceship ramming an asteroid and you want to know how much damage the ship takes. The damage should only depend on the speed that is perpendicular to the surface of the asteroid. So you just project the velocity of the ship onto the normal of the asteroid surface. And if you want to calculate the new velocity after the bounce, first split the velocity into two vectors: perpendicular and parallel to the surface. Then multiply the perpendicular vector by -1, leave the parallel part as it is and add the two parts back together. Voila: you just bounced only one part of a velocity vector without changing the overall speed!

Line intersection

A Line can be defined as a positional vector and a directional vector. Let’s say the position vector of the first line is called P and the direction vector is called D, you can then describe every point on that line with the formula: P + t * D with t being a number between 0 and 1 (0 is the start of the line (=P) and 1 is the end).

Now we have a second line, lets say Q + u * E and we want to know for which u and which t those two equations have the same result. That result will be the Intersection point we are looking for. Now that is one equation with two unknowns, so we can’t just solve it. So we have to set up an equation for t and u seperately (and if you only need the intersection point one of them might be enough).

Okay, so let’s start with the formula for t! So by changing t we are moving a point on the first line forward and backward (let’s just call this new point A) and search for the intersection point. And at this intersection point the line from Q (the start of the second line) to A will point in the same direction as E! That also means the line from Q to A will be perpendicular to a 90° rotated E (direction of the second line), which is easier to check for since the dot product of two perpendicular vectors is 0 for any length. Move the point A in the diagram and watch the dot product to see what I mean, move it to the intersection point, marked with an X and note that the dot product between QA and Eperp will be 0.

With this condition we can set up the following equation: ((P + t * D) – Q) dot Eperp = 0 (with Eperp being E rotated 90°, clockwise or counter clockwise, doesn’t matter). Solving this equation for t gives us the first formula: t = (P – Q) dot Eperp / (-D dot Eperp)

If t is between 0 and 1 the intersectionpoint lies on the first line. You can then use this calculated t in the original line formula P + t * D and you’ll get the intersection point.

If you want to know whether it also lies on the second line, you’ll also have to calculate the corresponding u parameter of the second line: u = (Q – P) dot Dperp / (-E dot Dperp) Of course you should get exactly the same intersectionpoint when you input the calculated u in the second line.

But: the lines only intersect if BOTH parameters are between 0 and 1! If one parameter is larger than 1 the intersection point is in front of that line if it is smaller than 0 it is behind it. Depending on your usage it can be OK for one or both parameters to be larger than 1 or smaller than 0. One example being a laserpointer with infinite range: the parameter of that line can be larger than 1 and the intersectionpoint will still be the laserdot on the wall.

There is a special case to watch out for: when the lines are parallel there is no intersection point (only if the lines are parallel and overlap, but I won’t touch that case here). If you look at the equation for t = (-P dot Eperp) / (D dot Eperp) we are happily dividing by (D dot Eperp) even though that can also be 0! And that is exactly the special case of the lines being parallel: when the dot product of a vector with the perpendicular of another vector is 0 the original vectors have to be parallel. So we just check D dot Eperp before we calculate t and we are safe.

That gives us the following pseudo code:

struct Intersection {
	Vector intersectPoint;
	bool linesAreParallel;
	float parameter1, parameter2;
}

Intersection intersectVectors(Vector line1Start, Vector line1Dir, Vector line2Start, Vector line2Dir)
	Intersection returnValue;
	Vector line2DirPerp = rotate90CW(line2Dir);
	float line1DirDotLine2DirPerp = dotproduct(line1Dir, line2DirPerp);

	if(line1DirDotLine2DirPerp != 0) {
		returnValue.linesAreParallel = false;
		float line2StartToLine1StartDotLine2DirPerp = dotproduct(line1Start - line2Start, line2DirPerp);
		returnValue.parameter1 = line2StartToLine1StartDotLine2DirPerp / -line1DirDotLine2DirPerp;

		// now the same for line 2!
		Vector line1DirPerp = rotate90CW(line1Dir);
		float line2DirDotLine1DirPerp = dotproduct(line2Dir, line1DirPerp);
		// no need to check for parallel vectors, we already checked...
		float line1StartToLine2StartDotLine1DirPerp = dotproduct(line2Start - line1Start, line1DirPerp);
		returnValue.parameter2 = line1StartToLine2StartDotLine1DirPerp / -line2StartDotLine1DirPerp;

		// we can calculate the intersectionpoint with either line 1 or line 2
		returnValue.intersectPoint = line1Start + line1Dir * returnValue.parameter1;

		// you can also check if parameter1 and parameter2 are between 0 and 1
		// and input that information in the intersection struct here...
		return returnValue;
	}
	else {
		returnValue.linesAreParallel = true;
		// when the lines are parallel, there is no intersection point
		// and also no parameter1 and parameter2!
		return returnValue;
	}

Here is a neat trick how you can use this intersection function to check if two objects moving with constant velocity will hit each other: If you simply input the current position as the lineStart and the velocity as the lineDir parameter of both objects into the intersectVectors function the returned parameters will be the time to the intersection point! When both parameters are larger than 0 (the past isn’t of interest) and both parameters are reasonably close to each other the objects will hit each other. It depends on the size of the objects of course, so you’ll have to do some additional calculations if you need an exact intersection point but that trick provides a pretty good estimate at a relatively small cost.

Nearest point on Line

We are using the parametric form of the line again, so our line has the starting point P and the directional vector D: P + t * D with t being the parameter describing every point on the line. Now we also have a point, let’s call it A, and we want to find the nearest point to A on our line (and call that new one B).

If you take a look at the diagram and try to find the shortest distance from A to the line, you’ll quickly see that the distance line is perpendicular to the line and intersects A. And with that condition, we can formulate an equation where only the parameter t is unknown: ((P + t * D) – A) dot D = 0

P + t * D is the Point we are searching for (we call it B). We create a vector from that point to A: (P + t * D) – A. And that vector has to be perpendicular to D: (…) dot D = 0 The only unknown in that equation is t, so we solve for t: t = (A – P) dot D / (D dot D) That D dot D is equals to the length of D squared, so the equation for the parameter becomes: t = (A – P) dot D / lengthSquared( D )

The nearest point will only be on that line if the parameter t is between 0 and 1! If it is smaller than 0 the nearest point will be in front of the line and if it is larger than 1 it will be behind the line. Depending on your use case you can check for those conditions and in those special cases use the start of the line respectively the end of the line as your nearest point.

You can then input that parameter t back in the parametric line P + t * D and you’ll get the nearest point on that line.

And here is the pseudocode for that operation:

vector getNearestPointOnLine(vector lineStart, vector lineDir, vector point){
	float paramT = dotproduct(point - lineStart, lineDir) / lengthsquared(lineDir);
	if(paramT < 0){
		return lineStart;
	}
	else if(paramT > 1){
		return lineStart + lineDir;
	}
	return lineStart + paramT * lineDir;
}

So long and thanks for all the fish!

Thanks for reading, I hope you gained some new insight into 2D vector operations! If you have any suggestions or questions, just leave a comment below.

Dominik Schneider

Founder, Managing Director & Code

2 Comments

Leave a Reply