Translations
So far we learnt how to do rotations using rotors. Another important operation is translation. Naively we could just use vector addition to achieve translation using some offset vector If we have two translation vectors and we can combine their action by adding them to get a single translation vector . This all seems very obvious and straightforward.
What if we wanted to do both translation and rotation? Using vector addition for translation and rotors for rotation our operation would look like this Now if we wanted to compose two translations and rotations, we would have two rotors and and two translation vectors and . First we would apply and according to the formula above. Then we would apply and on the result of the previous operation. We could multiply this out but we will get a lot of terms and the operations don't compose as nicely as they did when we had only rotations or translations. Is there a way we can do both rotation and translation with a single rotor? Projective Geometric Algebra (PGA) sets out to do this.
Projective Geometric Algebra
A new kind of basis vector
PGA starts with the familiar basis vectors that square to and also adds another basis vector one that squares to . In two dimensional PGA we thus have basis vectors , and . As a result there will also be three bivectors and one trivector .
We will see that this strange additional basis vector allows us to encode both rotations and translations in a single rotor and also many more things we couldn't easily do before.
Points
Another peculiarity of PGA is that points are not encoded as vectors anymore but as In the code below we display some points like before but this time using 2D PGA. The vector is denoted by as
e0
ande1
and e2
.// Render a point at x: 40, y: 60
renderPointPGA({
e02: -40, // -e_0y = e_y0
e01: 60,
e12: 1
}, "lime")
// Render a point at x: 20, y: 30
// Point coordinates divided by e_xy part
renderPointPGA({
e02: -40,
e01: 60,
e12: 2
}, "red")
In the code we can also see that the rendered point coordinate gets divided by the part of the multivector. Multiplying the multivector by a scalar thus won't have any effect on which point the multivector encodes as the final position gets divided by the part again which was also scaled by the same amount.
Translators
As promised this weird setup will allow us to perform translations using rotors. Rotors that only do translation are also called translators and we denote them by .
Just like with the rotors we use the exponential function to generate translators from our algebra. A translator that moves by in the X direction is given by . If we compare this to the point encoding we will notice that is the bivector related to the Y coordinate, so here the translators perform a translation that is orthogonal to the bivector's direction. As previously we will apply the translator using the sandwich product.
This time to calculate the result of the exponential we can not make use of Euler's formula as it only applies to elements that square to and the bivector squares to . The equivalent of Euler's formula for elements squaring to is fortunately very simple so all we picked up was the additional scalar .
var p = {
e02: -40,
e01: 60,
e12: 1
}
// Translator that moves by 80 along X.
// Same as exp(d/2 e_0y).
var t = {
scalar: 1,
e01: 40
}
var q = pga.sandwichProduct(p, t)
renderPointPGA(p, "lime")
renderPointPGA(q, "red")
Motors
We can also compose rotors and translators using multiplications. We call the resulting elements motors and denote them by . For example a motor will first perform the translation of the previous example followed by a rotation around the origin in the XY plane by CCW.
var p = {
e02: -40,
e01: 60,
e12: 1
}
var t = pga.exponential({
e01: 40
})
var r = pga.exponential({
// 90°/2; e_yx = -e_xy
e12: -Math.PI / 4
})
var m = pga.geometricProduct(r, t)
var q = pga.sandwichProduct(p, m)
renderPointPGA(p, "lime")
renderPointPGA(q, "red")
So far we have only been visualizing single points. With points we can not observe the effect that rotation has besides how it affects the position of the points. To visualize the rotation we will look at how a set of points gets transformed instead, such as a box. When applying a rotor that rotates we would expect the box to also rotate. We will use the provided
renderBoxPGA()
function for this purpose. The way it works is that it takes four points that are offset relative to the origin and transforms them with the given motor.var t = pga.exponential({
e01: 40
})
var r = pga.exponential({
e12: -Math.PI / 8
})
// Translate -80 in X, then rotate 45° CCW
var m = pga.geometricProduct(r, t)
// Identity motor to visualize the initial box
// at the origin
var identity = { scalar: 1 }
renderBoxPGA(identity, "lime")
renderBoxPGA(m, "red")
Our motor here produces a CCW rotation of (twice the amount written in the code) which indeed rotated our box by 45°.
Motor interpolation
A very useful property of PGA is its ability to smoothly interpolate between motors. Previously if we had separate translation and rotation (eg. when using vector addition for translation and rotors for rotation) it was not clear how one would interpolate between two of such transformations.
Interpolating translations and vectors is easy, for example with linear interpolation. If we are given two vectors and a blending factor the interpolated vector is given by
Interpolating rotations and rotors is a bit trickier but still relatively common, for example using quaternions and spherical linear interpolation.
So how do we interpolate between two motors and such as in the following example?
var a1 = {
e01: 40,
e02: 30
}
var a2 = {
e01: -40,
e02: -10,
e12: -Math.PI / 6
}
var m1 = pga.exponential(a1)
var m2 = pga.exponential(a2)
renderBoxPGA(m1, "black")
renderBoxPGA(m2, "red")
With known exponents
Thankfully our hard work of learning about motors will pay off here. Imagine we are given the exponents of two motors and which we denote by and (ie. ). To get the interpolated motor all we have to do is linearly interpolate between the exponents and then exponentiate
var a1 = {
e01: 40,
e02: 30
}
var a2 = {
e01: -40,
e02: -10,
e12: -Math.PI / 6
}
for (var alpha = 0; alpha <= 1; alpha += 0.1) {
var m = pga.exponential(pga.add(
pga.geometricProduct(a1, { scalar: 1 - alpha }),
pga.geometricProduct(a2, { scalar: alpha })
))
var c = "rgb(" + (255 * alpha).toString() + ", 0, 0)"
renderBoxPGA(m, c)
}
We can see the interpolation produces a curve. If we interpolated translation and rotation separately using linear interpolation we would have just gotten a straight line.
With unknown exponents
What if we don't know the exponents of the motors? This would happen for example when we keep composing motors. A very practical example where that occurs is if we used a motor to describe the position and rotation of a rigidbody in a physics simulation.
Just like in usual algebra, we can take the logarithm of an exponential to get its exponent. The logarithm of a motor in 2D PGA is given by where stands for the norm of the motor and stands for only keeping the grade parts (ie. all bivectors) of the result. can easily be calculated as which results in a scalar.
In the code we just take the previous example but instead of given exponents we will calculate them from given motors using the logarithm.
var m1 = { scalar: 1, e01: 20, e02: 40 }
var m2 = { scalar: 1, e01: -250, e12: -Math.PI * 1.3 }
function motorLog(m) {
var divisor = Math.sqrt(
pga.geometricProduct(
m,
pga.reversion(m)
).scalar
)
var allGrades = pga.div(m, divisor)
return {
e01: allGrades.e01,
e02: allGrades.e02,
e12: allGrades.e12
}
}
var a1 = motorLog(m1)
var a2 = motorLog(m2)
renderInfo("a1: " + pga.repr(a1))
renderInfo("a2: " + pga.repr(a2))
Summary
- 2D PGA basis vectors:
- Point at
: - Point coordinates
from PGA point : - Translator by
orthogonal to Y direction (ie. along X direction): - Motor: Rotor that both rotates and translates
- Motor logarithm:
- Interpolate between motors
and :
Conclusion
In this section we learnt about PGA where we have a new basis vector which squares to and also a different encoding for our points. This enabled us to perform translations using rotors. A rotor which does rotation and translation is also called a motor. This also enabled us to interpolate smoothly between motors.
In the next section we will take a look at how PGA allows us to represent "flat" geometric objects such as lines and planes, and how it allows us to easily do many operations that would classically look very distinct. We will also learn about the concept of duality and how the geometric product decomposes into two separate parts.