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 vectord=dxex+dyey(1)v=v+dIf we have two translation vectorsaandbwe can combine their action by adding them to get a single translation vectord=a+b. 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(2)v=RvR~+dNow if we wanted to compose two translations and rotations, we would have two rotorsR1andR2and two translation vectorsd1andd2. First we would applyR1andd1according to the formula above. Then we would applyR2andd2on the result of the previous operation.v=R1vR1~+d1v=R2vR2~+d2=R2(R1vR1~+d1)R2~+d2We 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+1and also adds another basis vectore0one that squares to0. In two dimensional PGA we thus have basis vectorsex,eyande0. As a result there will also be three bivectorse0x,e0y,exyand one trivectore0xy.

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 vectorsxex+yeyanymore but as(3)P=xey0+ye0x+exyIn the code below we display some points like before but this time using 2D PGA. The vectore0is denoted bye0 andex,eyas e1 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")
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-100-80-60-40-2020406080100-100-80-60-40-20020406080100
In the code we can also see that the rendered point coordinate gets divided by theexypart 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 theexypart 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 byT.
Just like with the rotors we use the exponential function to generate translators from our algebra. A translator that moves bydin the X direction is given byT=ed2e0x. If we compare this to the point encoding we will notice thate0xis 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 to1and the bivectore0xsquares to0. The equivalent of Euler's formula for elements squaring to0is fortunately very simple(4)T=ed2e0x=1+d2e0xso all we picked up was the additional scalar1.
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")
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-100-80-60-40-2020406080100-100-80-60-40-20020406080100
Motors
We can also compose rotors and translators using multiplications. We call the resulting elements motors and denote them bym. For example a motor(5)m=eϕ2eyxed2e0xwill 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")
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-100-80-60-40-2020406080100-100-80-60-40-20020406080100
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")
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-100-80-60-40-2020406080100-100-80-60-40-20020406080100
Our motor here produces a CCW rotation ofπ4(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 vectorsv1,v2and a blending factorαthe interpolated vectorv(α)is given by(6)v(v1,v2,α)=(1α)v1+αv2
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 motorsm1andm2such 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")
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-100-80-60-40-2020406080100-100-80-60-40-20020406080100

With known exponents
Thankfully our hard work of learning about motors will pay off here. Imagine we are given the exponents of two motorsm1andm2which we denote bya1anda2(ie.m1=ea1,m2=ea2). To get the interpolated motorm(α)all we have to do is linearly interpolate between the exponents and then exponentiate(7)m(α)=e(1α)a1+αa2
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)
}
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-100-80-60-40-2020406080100-100-80-60-40-20020406080100
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(8)log(m)=m||m||2where||m||stands for the norm of the motor and...2stands for only keeping the grade2parts (ie. all bivectors) of the result.||m||can easily be calculated asmm~which results in a scalar.
In the code we just take the previous example but instead of given exponentsa1,a2we 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))
 
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-100-80-60-40-2020406080100-100-80-60-40-20020406080100

Summary

  • 2D PGA basis vectors:e02=0,ex2=1,ey2=1
  • Point at(x,y):P=xey0+ye0x+exy
  • Point coordinates(x,y)from PGA pointP:(X,Y)=(Py0,Px0)Pxy
  • Translator bydorthogonal to Y directione0x(ie. along X direction):T=ed2e0x
  • Motor: Rotor that both rotates and translates
  • Motor logarithm:log(m)=m||m||2
  • Interpolate between motorsm1andm2:m(m1,m2,α)=e(1α)log(m1)+αlog(m2)

Conclusion

In this section we learnt about PGA where we have a new basis vectore0which squares to0and 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.

Next: PGA Geometry