gist-dot-product-katex
KaTeX files for "Practical applications of the dot product" blog post
Documentation
File structure
We are using BEM notation for our filenames where the "block" is our proof and "element" is the equation in the proof. For example:
- "projection__cosTheta-equals-axisLength.tex" has proof "projection" and step "cosTheta-equals-axisLength"
- We try to use camelCasing to associate relevant terms (e.g. length, operations, functions)
Compile images
To get our repo working locally, run the following:
# Clone our repo
git clone git@gist.github.com:207556d7ac0cd5d04fa283f6062841ab.git gist-dot-product-katex
cd gist-dot-product-katex
# Install our dependencies
npm install
# Compile our latest images
./build.sh
Proof
Here are ASCII based notes for the proof so we can easily modify/update/check ourselves
Projecting a vector onto a vector
Assume -pi <= theta <= pi. We have 3 scenarios
-pi/2 < theta < pi/2
+
/
/ theta
****-----+
theta = -pi/2, theta = pi/2
+
|
| theta
*--------+
-pi <= theta < -pi/2, pi/2 < theta <= pi
+
\
\ theta
****------+
Skipping over > pi and < -pi as they fall into the same buckets for theta
For -pi/2 < theta < pi/2, focus on 0 <= theta < pi/2, by the triangle sum theorem, we know for a given triangle with theta as an angle, we have:
alpha + beta + theta = pi
theta = pi - alpha - beta // Rearranged
0 <= pi - alpha - beta // Inequality substitution (1)
alpha + beta <= pi // Addition
pi - alpha - beta < pi/2 // Inequality substitution (2)
pi/2 - alpha - beta < 0 // Subtraction
pi/2 < alpha + beta // Addition
Let's build our triangle where:
axisProjectionVector = z * axisVector
such that the angle between (-axisProjectionVector) and (offshootVector - axisProjectionVector) is 90 degrees
+
/|
/ |
****-----+
// Handwaving over proof of triangle existence, I feel like we require a low-level Euclidean axiom but don't know which
Then by the trigonometric identity, we can state
cos(theta) = |axisProjectionVector| / |offshootVector|
For -pi/2 < theta < 0, we build a similar triangle and yield the same cos(θ) equation.
Else if theta = pi/2, then our offshoot vector is orthogonal to our axis and there's projection vector is the zero vector
Else if -pi <= theta < -pi/2 and pi/2 < theta <= pi, focus on pi/2 < theta < pi. Then we build a right triangle with phi similar to above and by cosine identity:
cos(phi) = |axisProjectionVector| / |offshootVector|
+
\
phi \ theta
****------+
phi = pi - theta
cos(pi - theta) = |axisProjectionVector| / |offshootVector|
cos(pi)cos(theta) + sin(pi)sin(theta) = |axisProjectionVector| / |offshootVector|
-1*cos(theta) + 0*sin(theta)
-cos(theta) = |axisProjectionVector| / |offshootVector|
cos(theta) = - |axisProjectionVector| / |offshootVector|
For -pi <= theta < -pi/2, we build a similar triangle and yield the same cos(θ) equation.
If cos(theta) >= 0, then let d = 1. Otherwise, let d = -1
This allows us to restate:
cos(theta) = d |axisProjectionVector| / |offshootVector|
If cos(theta) >= 0, then we have scenario where projection is positive with axis. Thus
axisProjectionUnitVector = 1 * axisUnitVector
Else (cos(theta) < 0), the nwe have scenario where projection is negative with axis. Thus
axisProjectionUnitVector = -1 * axisUnitVector
We can rewrite this via `d` to:
axisProjectionUnitVector = d * axisUnitVector
Knowns:
axisUnitVector = axisVector / |axisVector|
axisProjectionVector = axisProjectionUnitVector * |axisProjectionVector|
dot(axisVector, offshootVector) = |axisVector| * |offshootVector| * cos(theta)
Helpful derivation:
dot(axisVector, offshootVector) = |axisVector| * |offshootVector| * cos(theta)
= |axisVector| * |offshootVector| * (d * |axisProjectionVector|/|offshootVector|) // Substituion
= d * |axisVector| * |axisProjectionVector| // Cancellation
|axisProjectionVector| = dot(axisVector, offshootVector) / (d * |axisVector|) // Division
Goal: axisProjectionVector
axisProjectionVector = axisProjectionUnitVector * |axisProjectionVector|
= d * axisUnitVector * (dot(axisVector, offshootVector) / (d * |axisVector|)) // Subtitution
= axisUnitVector * dot(axisVector, offshootVector) / |axisVector| // Cancellation
= axisVector / |axisVector| * dot(axisVector, offshootVector) / |axisVector| // Substitution
= axisVector * dot(axisVector, offshootVector) / |axisVector|^2 // Consolidation
Finding the orthogonal component of a vector to another vector
offshootVector = axisProjectionVector + axisComplementVector // Resolved by right triangle
axisComplementVector = offshootVector - axisProjectionVector // Subtraction and rearranged sides
// Sanity explanation
offshootVector = c_axis*axisUnitVector + c_2*v_2 + ... + c_n*v_n
axisProjectionVector = c_axis * axisUnitVector + 0*v2 + ... + 0*v_n // By definition of component
Thus:
offshootVector - axisProjectionVector = 0*axisUnitVector + c_2*v2 + ... + c_n*v_n
Finding the shortest distance from a point to a segment
We have 3 scenarios
"Behind" v1
pt
*
v1--------v2
Between v1 and v2
pt
*
v1---*----v2
"Ahead of" v2
pt
*
v1--------v2
In the first/last, the shortest line is from our point to the closest vertex
In the middle, it's the shortest line to our segment (aka axisComplementVector)
To determine the scenario, we will resolve the axial component of offshootVector via the same calculation as `axisProjectionVector`. The key difference being we aren't multiplying by the axisVector
ratio = dot(axisVector, offshootVector) / |axisVector|^2
This gives us the ratio for how much of offshootVector is projecting onto axisVector
If it's negative, then we know it's "behind" v1
length = |offshootVector|
If it's greater than 1, then we know it's longer than our axis vector and ths "ahead" of v2
length = |offshootVector|
If it's in-between, then use vector addition to resolve axisComplementVector again and return its length
length = |offshootComplementVector| // Ideal definition
length = |offshootVector - axisProjectionVector| // Vector addition expansion
length = |offshootVector - axisVector * dot(axisVector, offshootVector) / |axisVector|^2|| // Substituion for axisProjectionVector calculation