We are experimenting with Keith Peters's Coding Math videos to see how they will fit into the egghead.io content.
This lesson explores the dot product method, two ways to calculate it and some useful examples.
This is Coding Math Episode 54, Dot Product.
Let's revisit vectors. I did some earlier videos on the subject, but to recap, a vector is a structure combining a direction and a magnitude. It's usually graphically represented by an arrow. The way the arrow points is the direction of the vector. The length of the arrow is its magnitude. Check out the earlier videos on the subject if you need to.
There are various operations you can perform on vectors. Adding them together, subtracting them, multiplying them by a single or a scalar value to scale them etc. Today we'll be looking at another operation you can do with vectors. It's called the dot product because it uses the dot symbol, like A.B. First we'll go into how to calculate the dot product, then we'll look at some of the uses of it.
There are a couple of ways of calculating dot product. If you just know the magnitudes and directions of the two vectors, you can get the dot product by multiplying the magnitude of both vectors by the cosine of the angle between them.
If the magnitude of A was 10, the magnitude of B was 5, and the angle between them was 30 degrees, then the dot product would be 10 x 5 x cosine 30, or 50 x 0.866, or about 43.301. I just want to throw that one out there first. We won't do anything with that right now, but keep it in mind because we'll come back to it.
Now remember that there's another way of representing vectors, by naming their component parts. For a 2D vector, that would be the X and Y components. The components are the distance between the head and the tail of the vector on each axis. These components can be represented by a matrix, like so.
You might have your two vectors like this, and we can represent the dot product operator as a dot between these two vectors. Calculating the dot product of these two is very easy, just multiply across the rows and add each product. You'd multiply both Xs, and then both Ys, and then add them together. We get 5 x 2 + 2 x 4 is 10 + 8, or 18.
For 3D you'd have a Z component in there as well. You just multiply the Z components the same way and add the results of those. Theoretically you could have vectors with any number of dimensions and it would all work the same way. If your vectors are in component form, like that, which they usually are when you're programming stuff, it's super easy. Let's code that one.
I've actually gone ahead and created a little test application here. It's a canvas and three points, zero, one and two, that get drawn to the screen with lines between them forming two vectors. There's some Morse [inaudible 02:53] code in here, so I can drag the points around and it updates the picture. You can go ahead and look through the code for all this later if you want. Mostly it's stuff that we've covered in earlier episodes.
The first thing we'll need to do is get some vectors out of these points. P0 in this case is the center point. We'll have on vector going from P0 to P1, and another vector going from P0 to P2. To get a vector between two points, you just get the difference between those two points on each axis, and those are your vector components.
I'll make a function called Vec. This accepts two points and returns a vector object. This is simple. It just subtracts the X and Y components of each point and returns an object with the difference. If P0 were at 100, 100, and P1 was at 150, 150, then the resulting vector will have X, Y components of 50, 50.
Down here on our draw function is where everything gets updated every time one of the points is moved. We'll create our two vectors here. P0 is the center point, P1 and P2 are the endpoints. V0 will be Vec P1, P0. V1 is Vec P2, P0. Now our dot product function. It takes two vectors, V0 and V1. All it has to do is return V0(X) x V1(X) + V0(Y) x V1(Y). See how simple dot products are?
On our draw function, we can call dot product passing in our two vectors and save the result to a variable, DP. Let's set the font to something large so we can see it, and then draw the result right on the canvas.
Now, as we move these points around, we can see the dot product that results from the two vectors. At first this just may look like a bunch of random numbers, but one thing to note is the angle formed by the two vectors and how that relates to the dot product. Notice that when the angle approaches 90 degrees, the dot product gets very small.
If you could get down to exactly 90 degrees, you'd see that the dot product would be zero. As the angle gets less than 90 degrees, the dot product goes very high. At greater than 90 degrees, it goes into negative numbers. The numbers are the largest, either positive or negative, when the two vectors are aligned with each other.
Also, the higher the magnitude of the vectors, the higher the dot product. The shorter they are, the smaller the dot product.
One problem with this is, beyond the patterns I've just mentioned, the actual numbers you get can seem rather arbitrary. You could be getting large numbers because the angle is close to 0 or 180, or because one or both of the vectors has a large magnitude. We can handle this by normalizing the vectors.
We covered normalization way back in one of the vector math episodes. To normalize a vector you divide each of its components by the magnitude. First we'll need a magnitude function. I'll call it Mag. This gets the squares of the X and Y components and takes the square root of their sum, basic Pythagorean Theorem.
Then I'll make a normalized function. This accepts a vector, gets its magnitude, then calculates and returns a new vector. The new vector's XY components will be the same as the old one divided by the magnitude we just got. The new vector will always have a magnitude of one.
When we call the dot product function, we just wrap each vector and we call it to normalize. Because the magnitudes of both vectors are always exactly one, the only variance is the angle here. Now you see that when the vectors are pointing in the same direction, we get values very close to one. Still zero at 90 degrees, and the dot product approaches minus one when we get to 180 degrees.
Well, all that's very neat, but what can we do with it? One big use is for 3D lighting. You'll have one vector that represents the normal of a surface, this is a vector that points directly away from that surface. Another vector that represents the light direction, the direction to a light source. If those two are exactly in line, then the light is hitting the surface directly. The dot product will be at its maximum, and the lighting will be at its strongest.
As those angles diverge towards 90 degrees, the dot product gets smaller and smaller and the light is hitting the surface at more and more of an angle. Eventually, when the light is at 90 degrees to the normal, it's not hitting the surface at all, but going right past it, and the dot product is zero.
There's a direct correlation here between the angles of the surfaces, the dot product and the strength of the lighting. That can be easily used in setting up 3D renderers. Another thing it can be used for is finding the exact angle between two vectors. This is where we jump back into the original formula for the dot product. Remember I told you to keep it in mind. Here it is.
We can reverse engineer this. We already have the dot product. What we want is that angle, A. We can easily figure out the magnitude of both vectors. If we divide both sides by those, we get DP divided by the magnitude of A divided by the magnitude of B equals cosine A.
Now we can remove the cosine by taking the arc cosine of both sides. We get A cosine of DP divided by magnitude of A, magnitude of B equals the angle. There's our angle. Let's code that up.
I'll create an angle between function that takes two vectors. We'll take this step by step for clarity. First we get the dot product as DP. Then Mag0 and Mag1, using the Mag function, finally we can return math, A cosine DP divided by Mag0 divided by Mag1.
Then, in draw, instead of just getting the dot product, I'll call angle between, storing the result in angle. I'll remove the calls to the norm here because we don't really need them the way this is currently coded. Although, I will say, with what you've just learned, you might be able to figure out alternate ways of getting the angle with normalized vectors.
This gives us an angle in radians. To be more human friendly, I'll convert that to degrees by saying, times math Pi divided by 180. Also, this is going to give us a number with a long decimal part in most cases. For display purposes, I'll wrap this in a math rounds call. Finally, we'll draw that value to the canvas. Run it, and now we see the angle in degrees between these two vectors.
There are other ways to get the angle between points like this, but you have to massage the values attained, as they can wind up as negative values, or wraparound, larger than 360 degrees.
Note that, no matter what I do here, we get a simple positive value for the angle. If we want, we can streamline this function too. Since dot product and Mag are both one line functions, I can remove the function calls and put them right in line here. DP becomes V0(X) x V1(X) + V0(Y) x V1(Y). Note the parentheses. Then both Mag calls can be replaced by direct calls to math square root.
You might be able to optimize this even more, but this works pretty well for me.