Graphics and projective geometry: equation reference
Sometimes I do some GLSL or similar: much graphics and geometry. I usually scribble down some back-of-envelope variant of the usual geometry equations, but I’ve never consistently decided what to call things or how to write them down.
Here I’m setting down the notation I’m going to use, and capturing the definitions. Reference pseudo-implementations are given too (Swift).
The equations and source given here have been unit tested; full source to come after tidying.
Glossary
- scalar absolute value: \( | \mathbf{a} | \)
- vector length:1 \( \| \mathbf{a} \| = \sqrt{ \mathbf{a}_0^2 + \mathbf{a}_1^2 + \dots } \)
- vector length squared: \( \| \mathbf{a} \|^2 = \mathbf{a}_0^2 + \mathbf{a}_1^2 + \dots \) (also equal to \( \mathbf{a} \cdot \mathbf{a} \))
- unit vector: \( \hat{\mathbf{a}} = \mathbf{a} / \| \mathbf{a} \| \). For larger expressions can write as \( unit( \dots ) \)
- angle between vectors: written as \(\angle (\mathbf{a}, \mathbf{b})\)
- orthogonal vectors: \(\angle (\mathbf{a}, \mathbf{b}) = 90^\circ\). Can be asserted with \(\mathbf{a} \perp \mathbf{b}\)
- orthogonal vector: in 2D, a vector \(\mathbf{a}\) has its orthogonal vector written as \(\mathbf{a}^{\perp}\). There are two possible orth vectors, we define \(\mathbf{a}^{\perp} = \operatorname{rot}_{90} \mathbf{a} \) (i.e. \(\mathbf{a}\) rotated 90 degrees counter-clockwise). The other orth vector is \(\mathbf{a}^{\top} = \operatorname{rot}_{-90} \mathbf{a} = -\mathbf{a}^{\perp} \)
- same direction vectors: \(\angle (\mathbf{a}, \mathbf{b}) < 90^\circ\).
- opposite direction vectors: \(\angle (\mathbf{a}, \mathbf{b}) > 90^\circ\).
- dot product: a scalar of \(\mathbf{a}\) and \(\mathbf{b}\), written \(\mathbf{a} \cdot \mathbf{b}\), computed as sum of all components of two vectors multiplied together. Is equal to \(|\mathbf{a}| |\mathbf{b}| \cos \theta\)
- determinant: written \(\det[\mathbf{a}, \mathbf{b}]\). In 2D defined as \(a_0 b_1 - a_1 b_0\). Is equal to \(|\mathbf{a}| |\mathbf{b}| \sin \theta\) and also \(\mathbf{a} \cdot \mathbf{b^{\perp}}\)
Projection length
The length of the projection of \(\mathbf{a}\) onto \(\mathbf{b}\).
$$ \begin{aligned} \| \operatorname{proj}_{\mathbf{b}} \mathbf{a} \, \| &= \mathbf{a} \cdot \hat{\mathbf{b}} \end{aligned} $$func projectionLength(a: Vec2, b: Vec2) -> Double {
let bUnit = b.unitVector
return a.dot(bUnit)
}
Projection
The projection of \(\mathbf{a}\) onto \(\mathbf{b}\), which can be in the same direction or opposite direction to \(\mathbf{b}\), or zero.
$$ \begin{aligned} \operatorname{proj}_{\mathbf{b}} \mathbf{a} &= (\mathbf{a} \cdot \hat{\mathbf{b}}) \hat{\mathbf{b}} \end{aligned} $$func projection(a: Vec2, b: Vec2) -> Vec2 {
let bUnit = b.unitVector
return a.dot(bUnit) * bUnit
}
Forward projection
The projection of \(\mathbf{a}\) onto \(\mathbf{b}\). It can be in the same direction as \(\mathbf{b}\) or zero.
$$ \begin{aligned} \operatorname{fproj}_{\mathbf{b}} \mathbf{a} &= |\mathbf{a} \cdot \hat{\mathbf{b}}| \hat{\mathbf{b}} \end{aligned} $$func projectionForward(a: Vec2, b: Vec2) -> Vec2 {
let bUnit = b.unitVector
return abs(a.dot(bUnit)) * bUnit
}
Rejection length (2D only)
The length of the rejection of \(\mathbf{a}\) onto \(\mathbf{b}^\perp\).
$$ \begin{aligned} \| \operatorname{rej}_{\mathbf{b}} \mathbf{a} \, \| &= \mathbf{a} \cdot \hat{\mathbf{b}}^\perp \end{aligned} $$func rejectionLength(a: Vec2, b: Vec2) -> Double {
let bUnitOrth = b.unitVector.rotated90CCW
return a.dot(bUnitOrth)
}
Rejection (2D only)
The rejection of \(\mathbf{a}\) onto \(\mathbf{b}\), which is defined as the projection of \(\mathbf{a}\) onto \(\mathbf{b}^\perp\).
$$ \begin{aligned} \operatorname{rej}_{\mathbf{b}} \mathbf{a} &= (\mathbf{a} \cdot \hat{\mathbf{b}}^\perp) \hat{\mathbf{b}}^\perp \end{aligned} $$func rejection(a: Vec2, b: Vec2) -> Vec2 {
let bUnitOrth = b.unitVector.rotated90CCW
return a.dot(bUnitOrth) * bUnitOrth
}
Right rejection (2D only)
The projection of \(\mathbf{a}\) onto \(\mathbf{b}^\perp\), fixed to the right of \(\mathbf{b}\).
$$ \begin{aligned} \operatorname{rrej}_{\mathbf{b}} \mathbf{a} &= | \mathbf{a} \cdot \hat{\mathbf{b}}^\perp | \hat{\mathbf{b}}^\perp \end{aligned} $$func rejection(a: Vec2, b: Vec2) -> Vec2 {
let bUnitOrth = b.unitVector.rotated90CCW
return abs(a.dot(bUnitOrth)) * bUnitOrth
}
Rejection (dimensions >= 2)
This equation also works for 2D, but if you’re specifically working in 2 dimensions use the simpler 2D version above.
$$ \begin{aligned} \operatorname{rej}_{\mathbf{b}} \mathbf{a} &= \mathbf{a} - \operatorname{proj}_{\mathbf{b}} \mathbf{a} \end{aligned} $$Is same direction
This equation2 returns 1 (true) if \(\angle (\mathbf{a}, \mathbf{b}) > 0\).
$$ \operatorname{same}_{\mathbf{b}} \mathbf{a} = [\mathbf{a} \cdot \mathbf{b} > 0] $$func isSameDirection(a: Vec2, b: Vec2) -> Bool {
a.dot(b) > 0
}
Is opposite direction
This equation returns 1 (true) if \(\angle (\mathbf{a}, \mathbf{b}) > 0\).
$$ \operatorname{opp}_{\mathbf{b}} \mathbf{a} = [\mathbf{a} \cdot \mathbf{b} < 0] $$func isOppositeDirection(a: Vec2, b: Vec2) -> Bool {
a.dot(b) < 0
}
Is to left (2D)
$$ \operatorname{left}_{\mathbf{b}} \mathbf{a} = [\mathbf{a^\perp} \cdot \mathbf{b} < 0] $$/// is A to left of B
func isToLeft(a: Vec2, b: Vec2) -> Bool {
a.rotated90CCW.dot(b) < 0
}
Is to right (2D)
$$ \operatorname{right}_{\mathbf{b}} \mathbf{a} = [\mathbf{a^\perp} \cdot \mathbf{b} > 0] $$/// is A to right of B
func isToRight(a: Vec2, b: Vec2) -> Bool {
a.rotated90CCW.dot(b) > 0
}
Rebase (2D)
Converts a 2D vector into a different basis with \(\mathbf{b}\) being the first (\(x\)) base vector in new basis:
$$ \operatorname{rebase}_{\mathbf{b}} \mathbf{a} = ( \|\operatorname{proj}_{\mathbf{b}} \mathbf{a}\|, \|\operatorname{rej}_{\mathbf{b}} \mathbf{a}\| ) $$Summary
Function | Operator | Definition | Result | Ref output |
---|---|---|---|---|
Angle between | \( \angle (\mathbf{a}, \mathbf{b}) \) | \( \operatorname{cos}^{-1} ( \hat{\mathbf{a}} \cdot \hat{\mathbf{b}} ) \) | Real | \( \approx 126.87^\circ \) |
Projection length | \( \| \operatorname{proj}_{\mathbf{b}} \mathbf{a} \, \| \) | \( \vert \mathbf{a} \cdot \hat{\mathbf{b}} \vert \) | Real | \( 3 \) |
Projection | \(\operatorname{proj}_{\mathbf{b}} \mathbf{a}\) | \((\mathbf{a} \cdot \hat{\mathbf{b}}) \hat{\mathbf{b}}\) | Vector | \( (3, 0) \) |
Forward projection | \( \operatorname{fproj}_{\mathbf{b}} \mathbf{a} \) | \( \vert \mathbf{a} \cdot \hat{\mathbf{b}} \vert \hat{\mathbf{b}}\) | Vector | \( (-3, 0) \) |
Rejection length (2D) | \( \|\operatorname{rej}_{\mathbf{b}} \mathbf{a} \, \| \) | \( \vert \mathbf{a} \cdot \hat{\mathbf{b}}^\perp \vert \) | Real | \( 4 \) |
Rejection (2D) | \( \operatorname{rej}_{\mathbf{b}} \mathbf{a} \) | \( (\mathbf{a} \cdot \hat{\mathbf{b}}^\perp) \hat{\mathbf{b}}^\perp \) | Vector | \( (0, 4) \) |
Right rejection (2D) | \( \operatorname{rrej}_{\mathbf{b}} \mathbf{a} \) | \( \vert \mathbf{a} \cdot \hat{\mathbf{b}}^\perp \vert \hat{\mathbf{b}}^\perp \) | Vector | \( (0, -4) \) |
Rejection (>=2D) | \( \operatorname{rej}_{\mathbf{b}} \mathbf{a} \) | \( \mathbf{a} - \operatorname{proj}_{\mathbf{b}} \mathbf{a} \) | Vector | \( (0, 4) \) |
Same direction | \( \operatorname{same}_{\mathbf{b}} \mathbf{a} \) | \( [\mathbf{a} \cdot \mathbf{b} > 0] \) | Bool | false |
Opposite direction | \( \operatorname{opp}_{\mathbf{b}} \mathbf{a} \) | \( [\mathbf{a} \cdot \mathbf{b} < 0] \) | Bool | true |
Is to left (2D) | \( \operatorname{left}_{\mathbf{b}} \mathbf{a} \) | \( [\mathbf{a^\perp} \cdot \mathbf{b} < 0] \) | Bool | false |
Is to right (2D) | \( \operatorname{right}_{\mathbf{b}} \mathbf{a} \) | \( [\mathbf{a^\perp} \cdot \mathbf{b} > 0] \) | Bool | true |
The Ref output is the result when applying the function to these values:
$$ \begin{aligned} \mathbf{a} &= (3, 4) \\ \mathbf{b} &= (-2, 0) \end{aligned} $$Think of it as a single-value unit test for validating an implementation of the funcs.
Note that any function using the orth operator (\( \perp \)) applies to 2D only.
Alternative to subscripts for bigger expressions
If you’re writing an operator and you have some unwieldy expression for the \( \mathbf{b} \) subscript, you can use brackets as an alternative form:
$$ \operatorname{proj}(\mathbf{b}, \mathbf{a}) = \operatorname{proj}_{\mathbf{b}} \mathbf{a} $$Notice that it’s the same order (\( \mathbf{b} \) then \( \mathbf{a} \)) as the \( \operatorname{proj}_{\mathbf{b}} \mathbf{a} \), to avoid confusion.
Applications of cos and sin
Cos is associated with deciding direction (forward versus back). This is because its zero points are at -90 and 90 degrees; it has unique values3 from 0 to 180 degrees.
Sin is associated with deciding handedness/side (left versus right). This is because its zero points are at 0 and 180 degrees; it has unique values from -90 to 90 degrees.
Document history (click to expand)
- 2025-03-25
- Fix mistake in rejection length (
||
->|
) - add unit tests mention
- add footnote about bar vs double bars
- add vec len squared
- change all \textbf to \mathbf (consistency)
- add section ‘alternative to subscripts’
- Fix mistake in rejection length (
- 2025-03-21
- published
note the standard usage of double-bars (\( \| \)) for length of a vector, versus single bars (\( | \)) for the absolute value of a scalar (real). This gives some context about what you’re taking the ‘size’ of ↩︎
the square brackets are Iverson notation , where \( [ \operatorname{statement} ] \) takes the value \( 1 \) when the statement is true (otherwise \( 0 \)). It’s a maths notation for boolean evaluation4 ↩︎
by ‘unique values’ I mean one-to-one functions ↩︎
the notation lends itself naturally to boolean and expressions (conjunction), for example: \( [ \operatorname{statement1} ] [ \operatorname{statement 2} ] \). More on Iverson boolean operations ↩︎