Calculating Dot Products
Calculating Dot Products
The dot product is an operation performed on two vectors in any dimension that returns a single number. It is also sometimes called the scalar product, the inner product, or rarely the projection product. The dot product is considered a way to multiply two vectors. Performing the dot product operation is simple enough, but in order to use it in code you will need a good understanding of the mathematics behind the operation. This article aims give a better understanding of the dot product so that you can use it in your games!
- Trigonometric functions
To start off let’s have a definition for the dot product given vectors A and B.
“The scalar projection of A onto B multiplied by the magnitude of B”
“The scalar projection of B onto A multiplied by the magnitude of A”
This definition may of course leave you wondering what a scalar projection is, and more importantly how to calculate it. A scalar projection is the amount that one vector travels in another vector’s direction. So if we say that we want the projection of A onto B we want to know how much of vector A is going in the same direction as vector B and vice versa for the projection of B onto A.
To calculate the projection we use the trigonometric functions alongside right triangles to solve for the adjacent edge.
So that takes care of the projection aspect of our geometric dot product. To finish the dot product calculation we take the magnitude of the vector we’re projecting onto and multiplying it by the projection. With that in mind we can start to define an equation for the dot product:
We can further expand this to:
That’s great, but we aren’t done yet. What about the projection of B onto A multiplied by the magnitude of A? Let’s draw it out and see what we get:
Once again we can take this projection value and plug it into our other dot product definition:
We can further expand this to:
Would you look at that! The dot product is commutative. It doesn’t matter if we dot A against B or B against A, we get the same result! So we know that given both vectors and the angle between them we can geometrically calculate the dot product like so:
function dot(a, b, theta) return a.magnitude * b.magnitude * math.cos(theta); end;
Another important thing this has shown us is that by using simple algebra on the dot product equation we can get both the scalar projection of A onto B and the scalar projection of B onto A.
function projab(b, dot) return dot / b.magnitude end function projba(a, dot) return dot / a.magnitude end
Assuming we already have the dot product and the magnitude of both vectors we can also easily solve for the angle between them.
-- this will work with vectors of any dimension function angle(a, b, dot) return math.acos(dot / (a.magnitude * b.magnitude)); end;
In the previous section we defined both an equation and a definition for the dot product. We also showed some of the possible/common uses of the dot product. The problem with this however is that the geometric equation for the dot product requires us to know the angle between two vector for us to be able to compute it. Here’s the good news, there’s another way! To figure out what it is let’s review the law of cosines.
We can take this definition and bring it over to the triangle created by two vectors:
Our first step will be to substitute in the dot product:
Now we will solve for A dot B:
We know that the magnitude of a vector can be found by using Pythagorean Theorem:
Since that is the case we know that the magnitude of a vector squared is equal to the sum of its components squared. As such we can rewrite the above as (assuming Vector3):
If we went through the same process with Vector2 we would find that:
If we kept doing this same process with any number of dimensions we would find a pattern for calculating the dot product.
Therefore we can write our two dot product functions for Vector3 and Vector2 as such:
-- dot product for vector2 function dot2d(a, b) return a.x * b.x + a.y * b.y; end; -- dot product for vector3 -- equivalent to a:Dot(b) method function dot3d(a, b) return a.x * b.x + a.y * b.y + a.z * b.z; end;
Case study: Reflecting lasers
To finish this article off we’re going to go how we can use the dot product to create a laser beam that reflects off things in our game. To do this calculation we will need two things:
- The vector we want to reflect
- The surface normal of what we want to reflect against
Getting the first part is simple enough, but how do we get the surface normal? For the sake of this example we will use the returned normal vector from the
So, let’s take a look at the situation. In the image below we have the vector we want to reflect (initial laser) and the surface normal. Our goal is to calculate the reflected laser’s direction.
The first thing to take note of is that as we have it drawn above the vectors aren’t drawn with their tails attached to the origin. To fix that let’s redraw the above image so we can get a better visualization of our vectors (at least in a mathematical sense). We’ll also draw our reflected laser as a dotted line to signify that we don’t actually know its value, rather it’s something we’re trying to solve for.
The next step we’ll take is to get the opposite vector to the initial laser and then find its scalar projection on the surface normal:
Finally if we double that scalar projection, convert it into a vector projection, and then finally add the initial laser to it (tail to tip) we’ll find we’re left with the reflected laser!
So with all this in mind we can easily write a vector reflection function:
function reflect(input, normal) return -2 * input:Dot(normal) * normal + input end
With that being said, this is not a lesson on creating tools. There are other articles on that. As such here is a quick example of a reflecting laser gun.
local tool = script.Parent local handle = tool:WaitForChild("Handle") function reflect(input, normal) -- using dot method b/c vector3 return -2 * input:Dot(normal) * normal + input end function drawray(ray, parent) local part = Instance.new("Part") part.Material = Enum.Material.Neon part.Size = Vector3.new(.2, .2, ray.Direction.magnitude) part.CFrame = CFrame.new(ray.Origin + ray.Direction/2, ray.Origin + ray.Direction) part.Anchored = true part.CanCollide = false part.BrickColor = BrickColor.new("Bright red") part.Parent = parent or game.Workspace.CurrentCamera return part end function fire(from, to, bounce) local bounce = bounce or 0 if bounce <= 5 then -- how many times can it reflect -- first ray is to calculate distance and normal local ray = Ray.new(from, (to - from).unit * 500) local hit, pos, normal = game.Workspace:FindPartOnRay(ray, tool.Parent) -- this is the actual ray we use and draw local ray2 = Ray.new(from, (pos - from)) local part = drawray(ray2, tool) -- throw out the laser beam later game:GetService("Debris"):AddItem(part, 2) -- calculate the reflected ray local ref = reflect((pos - from), normal) if hit then local hum = hit.Parent:FindFirstChild("Humanoid") if hum then hum:TakeDamage(math.random(10, 15)) end -- shoot a ray in the reflected position fire(pos, pos + ref, bounce + 1) end end end script.Parent.Equipped:connect(function(mouse) mouse.TargetFilter = game.Workspace script.Parent.Activated:connect(function() fire(handle.CFrame.p, mouse.Hit.p) end) end)