Last updated: 2026-09-26

M
Masters level

Projective Geometric Algebra: Points, Planes, and Motors for Graphics

11.1 One Algebra Instead of Five Toolkits

A graphics programmer's toolbox holds several separate mathematical systems. Matrices carry transformations. The dot and cross products measure angles and find normals. Quaternions handle rotation, dual quaternions add translation to it, and homogeneous coordinates let a matrix express translation at all. Each tool solves one problem well. Passing work from one to the next is where the bugs live.

Projective Geometric Algebra (PGA) replaces that patchwork with a single algebra. Points, lines and planes are elements of it, and so are the rigid motions that move them. The same small set of products computes joins, intersections, angles and transformations, in any dimension[1][2]. This chapter builds the 3D version from the blades and rotors of Chapter 9 in four steps: the algebra itself (11.2 and 11.3), the objects it contains (11.4), the two products that relate them (11.5), and the motors that move them (11.6 to 11.8). It closes with what the software libraries do (11.9), where PGA sits beside the conformal model of Chapter 8 (11.10), and a runnable version of the worked examples (11.11).

11.2 What You Already Have

Everything in Chapter 9 carries over. A blade is a product of orthogonal vectors, and its grade counts how many. In three dimensions:

  • Grade 0, scalar. A plain number. It isn't a point; where points live depends on the model, and PGA answers that below.
  • Grade 1, vector. A directed line segment through the origin, in the ordinary vector model.
  • Grade 2, bivector. An oriented area element, formed as the outer product of two vectors.
  • Grade 3, trivector. An oriented volume element.

A multivector is any sum of blades of mixed grade. The geometric product of two vectors combines their metric and orientation information in one expression:

\[ \mathbf{a}\mathbf{b} = \mathbf{a}\cdot\mathbf{b} + \mathbf{a}\wedge\mathbf{b} \]

The dot product is a scalar measuring alignment; the wedge is a bivector recording the plane the two vectors span. Two facts about the ordinary vector model matter for what follows. Every vector starts at the origin, so it describes a displacement from the origin, and a line or plane that misses the origin has no direct representation. And rotors turn things about the origin, so translation has to be added from outside, by a matrix or a separate vector. PGA fixes both with one extra dimension.

11.3 A Degenerate Extra Dimension

To describe 3D Euclidean space, PGA works in a four-dimensional algebra. Three basis vectors, \(\mathbf{e}_1,\mathbf{e}_2,\mathbf{e}_3\), square to \(+1\) and behave like the usual axes. The fourth, \(\mathbf{e}_0\), squares to zero:

\[ \mathbf{e}_1^2 = \mathbf{e}_2^2 = \mathbf{e}_3^2 = 1, \qquad \mathbf{e}_0^2 = 0 \]

That is a degenerate metric, written \(\mathbb{R}_{3,0,1}\): three positive dimensions, no negative ones, one null. In general, PGA embeds \(n\)-dimensional Euclidean space in an \((n+1)\)-dimensional algebra of this form[1]. The null direction stands for "infinity", and it's what turns translation into an ordinary algebraic operation, as 11.6 shows. The full 3D algebra has \(2^4 = 16\) basis elements: one scalar, four vectors, six bivectors, four trivectors and one four-vector.

The convention here is the plane-based one. The grade-1 vectors of the algebra represent planes, not points. Gunn, and Gunn and De Keninck, develop this formulation for Euclidean geometry[1][2], and Dorst and De Keninck's tutorial covers it in full[3]. Older texts, including Chapter 4 of this series and Dorst, Fontijne and Mann[4], use a point-based convention where points are vectors. The geometry is identical, but the roles of the two products swap between conventions, and 11.5 spells out exactly how. Gunn writes the algebra \(\mathcal{P}(\mathbb{R}^*_{3,0,1})\), the star marking the plane-based (dual) formulation. Sign conventions for individual basis blades also differ between texts and libraries, so check them before mixing code from two sources.

11.4 Planes, Lines, and Points as Blades

Each geometric object sits at a fixed grade, and its coordinates are the coefficients of the blade:

ObjectGradeComponentsWhat they mean
Plane14\(a\,\mathbf{e}_1 + b\,\mathbf{e}_2 + c\,\mathbf{e}_3 + d\,\mathbf{e}_0\) is the plane \(ax+by+cz+d=0\). \((a,b,c)\) is its normal and \(d\) its offset.
Line26The \(\mathbf{e}_{23},\mathbf{e}_{31},\mathbf{e}_{12}\) parts give the direction. The \(\mathbf{e}_{10},\mathbf{e}_{20},\mathbf{e}_{30}\) parts encode where the line sits relative to the origin. These are Plücker coordinates, with one constraint: direction and moment must be orthogonal.
Point34\(\mathbf{e}_{123} - x\,\mathbf{e}_{230} - y\,\mathbf{e}_{310} - z\,\mathbf{e}_{120}\) is the point \((x,y,z)\). The \(\mathbf{e}_{123}\) coefficient is a weight, 1 for an ordinary point.
Pseudoscalar41\(\mathbf{e}_{1230}\). Wedging a plane with a point produces a multiple of it.

A point is the intersection of three planes, and that's exactly how it's built. The point \((x,y,z)\) is the wedge of the planes \(x' = x\), \(y' = y\) and \(z' = z\):

\[ P = (\mathbf{e}_1 - x\,\mathbf{e}_0)\wedge(\mathbf{e}_2 - y\,\mathbf{e}_0)\wedge(\mathbf{e}_3 - z\,\mathbf{e}_0) \]

Expanding for \((1,2,3)\) gives \(\mathbf{e}_{123} - \mathbf{e}_{230} - 2\,\mathbf{e}_{310} - 3\,\mathbf{e}_{120}\). Setting the weight to zero leaves a point at infinity, which is a direction. Directions need no special case in PGA: they're points with a vanishing weight, and they flow through every formula unchanged. Applying a translation to one leaves it exactly as it was, which is what a direction should do.

11.5 Join and Meet: Two Products, Not One

Relating objects to each other takes two operations. The meet finds what two objects share, and the join builds the smallest object containing both. In the plane-based convention, the outer product is the meet:

  • Two planes wedge to their line of intersection; three planes wedge to a point.
  • A plane wedged with a point is zero exactly when the point lies on the plane, and a multiple of \(\mathbf{e}_{1230}\) otherwise. Take the plane \(z=3\). The point \((0,0,3)\) gives zero, \((0,0,5)\) gives \(-2\,\mathbf{e}_{1230}\), and \((0,0,0)\) gives \(+3\,\mathbf{e}_{1230}\), so its size is the distance from the plane.
  • Two parallel planes still wedge to something, a line at infinity. Parallel lines and planes need no exception handling, which is the "robust, parallel-safe" property the graphics literature highlights[2].

The join needs the regressive product, written \(\vee\). It's the outer product seen through duality: take the dual of each operand, wedge the results, and take the dual back. Joining two points gives the line through them, and its magnitude scales with the distance between the points. Joining three points gives their plane; the three points \((0,0,2)\), \((1,0,2)\) and \((0,1,2)\) join to \(\mathbf{e}_3 - 2\,\mathbf{e}_0\), the plane \(z=2\). Joining a point to itself gives zero, because the "line through one point" isn't defined.

Watch the convention. In the point-based model of Chapter 4, points are vectors, so the wedge joins and the regressive product meets. In the plane-based model, planes are vectors, so the wedge meets and the regressive product joins. Duality swaps the two roles; the geometry underneath doesn't change. Whichever text you read, ask which grade holds the points.

The inner product does something else again. It measures. For two unit planes, the scalar part of their geometric product is the cosine of the angle between their normals. A pair of planes at a half-radian angle gives \(\cos 0.5 \approx 0.878\). The inner product doesn't intersect or span anything; it's the tool for angles, projections and distances.

11.6 Reflections Build Every Rigid Motion

Reflecting a point in a plane is a sandwich: the plane multiplies the point from both sides, up to a sign that depends on the grade of what's being reflected. Reflect in two planes one after the other and the sign issue vanishes, because the product of the two planes acts as a single even element. That product is a motor.

  • Two planes meeting in a line give a rotation about that line. The angle of rotation is twice the angle between the planes. Take \(\mathbf{p}_1 = \mathbf{e}_1\) and \(\mathbf{p}_2 = \cos\alpha\,\mathbf{e}_1 + \sin\alpha\,\mathbf{e}_2\). Their product is \(\cos\alpha + \sin\alpha\,\mathbf{e}_{21}\), a rotation by \(2\alpha\) about the \(z\)-axis. In general, a rotation by \(\theta\) is \(R = \cos\tfrac{\theta}{2} + \sin\tfrac{\theta}{2}\,\mathbf{e}_{21}\), counter-clockwise viewed from \(+z\).
  • Two parallel planes give a translation. Reflect in \(z=0\), then in \(z=h\), and every point moves by \(2h\) along \(z\). The motor is \(1 + h\,\mathbf{e}_{30}\). For a translation by a general vector \(\mathbf{t}\), the motor is \(T = 1 + \tfrac12\left(t_x\,\mathbf{e}_{10} + t_y\,\mathbf{e}_{20} + t_z\,\mathbf{e}_{30}\right)\).

Here \(\mathbf{e}_{i0}\) means \(\mathbf{e}_i\mathbf{e}_0\). Translation works because \(\mathbf{e}_{i0}^2 = 0\), so the exponential of \(\tfrac12 t\,\mathbf{e}_{i0}\) stops after its first-order term. The degenerate metric is doing the work: it makes translation a finite, exact operation instead of a limit.

Any object transforms under a motor \(M\) by the sandwich product:

\[ x' = M\,x\,M^{-1} \]

For a unit motor, \(M^{-1}\) is the reverse \(\widetilde{M}\), which flips the order of every blade's factors, so the formula is \(x' = M\,x\,\widetilde{M}\). It works identically on points, lines and planes. You don't need one routine for transforming a point and another for a normal, or a rotate-then-translate order to remember. Motors also compose by plain multiplication: \(M_2M_1\) is "\(M_1\), then \(M_2\)".

11.7 A Worked Example: Rotating About an Offset Axis

Rotations about the origin are easy in any system. The awkward case is an axis that misses the origin, which in matrix code means translate, rotate, translate back. Here it's three motors multiplied together. Take the axis parallel to \(z\) through \((1,0,0)\), and rotate by \(90^\circ\).

The translation to the axis is \(T = 1 + \tfrac12\mathbf{e}_{10}\). The rotation is \(R = \tfrac{1}{\sqrt2}(1 - \mathbf{e}_{12})\), since \(\mathbf{e}_{21} = -\mathbf{e}_{12}\). The combined motor is:

\[ M = T\,R\,\widetilde{T} = \tfrac{1}{\sqrt2}\left(1 - \mathbf{e}_{12} - \mathbf{e}_{20}\right) \]

Applying the sandwich to \((2,0,0)\), a point one unit from the axis, gives \((1,1,0)\), which is correct. A point on the axis, \((1,0,5)\), stays where it is. And \(M\widetilde{M} = 1\), so \(M\) is a valid unit motor.

Add a translation of 2 along \(z\) to the same \(90^\circ\) rotation about \(z\), and you have a screw motion: \(S = (1 + \mathbf{e}_{30})\,R\). It sends \((1,0,0)\) to \((0,1,2)\) in a single sandwich. Rotation plus translation along the axis is a single element of the algebra, with no separate rotation and offset to keep in step.

11.8 Motors, Quaternions, and Dual Quaternions

Motors are the even-grade elements of the algebra: one scalar, six bivectors and one pseudoscalar, eight components in all. Two subsets are already familiar. The scalar plus the three bivectors \(\mathbf{e}_{23},\mathbf{e}_{31},\mathbf{e}_{12}\) form the quaternions, which are the rotors of Chapter 9. Add the other three bivectors, which carry the translation, and the pseudoscalar, and the whole even subalgebra is isomorphic to the dual quaternions of rigid-body kinematics[1].

So PGA doesn't compete with quaternions and dual quaternions. It contains both, and puts the extra machinery (points, lines, planes and their joins and meets) in the same algebra.

Not every even element is a motor. A rigid motion has six degrees of freedom, and eight numbers need two constraints to get there: normalisation, and the Study condition, which says \(M\widetilde{M}\) must have no pseudoscalar part. The element \(1 + 0.7\,\mathbf{e}_{1230}\) fails it. Its product with its own reverse is \(1 + 1.4\,\mathbf{e}_{1230}\), which isn't a scalar, and it doesn't describe any rigid motion.

Motors inherit the practical benefits of unit quaternions. There are no Euler angles to lock up, since the gimbal-lock problem of Chapter 9 is a fault of the parameterisation, not of rotation itself. A general rigid motion, a screw, is the exponential of a general bivector, which is why motors can be interpolated and blended the way rotors are. Character animation uses the dual-quaternion form of this idea for skinning, for example in Kavan and colleagues' 2008 method[6]. The algebra above is the same mathematics with more of the geometry attached.

11.9 In Code: Elegance Versus Throughput

A naive multivector implementation stores all 16 coefficients of the 3D algebra and multiplies them generically, even though a point uses four and a motor eight. That overhead is the usual objection to GA in real-time work, and the practical answer is specialisation. Libraries generate or hand-write code that collapses each operation to flat arithmetic on only the coefficients that can be non-zero.

  • ganja.js is a code generator. It produces algebra classes for any signature and can emit implementations with a flat multivector layout for JavaScript, C++, C#, Rust and Python[7]. It favours generality: change the signature, get a new algebra.
  • Klein goes the other way. It implements only the 3D algebra and uses SSE vector instructions for throughput, aimed at animation and kinematics workloads[8]. It gives up generality to buy speed.

The same trade-off runs through this series. Chapter 9 weighs matrix backends against native blade engines, and the Ruby implementation makes a deliberate choice of sparse representation. Which end to pick depends on whether you're exploring geometry or shipping a frame budget. Section 11.11 runs this chapter's examples in ganja.js so you can try the exploratory end yourself.

11.10 Where PGA Sits Beside CGA

Chapter 8's conformal model uses a five-dimensional algebra to represent spheres and circles as well as points, lines and planes. PGA is smaller and covers the flat elements and rigid motions that most of graphics runs on. It sits between plain vector algebra and conformal GA, as a natural step up from the first and a simpler relative of the second[1]. Which to use depends on the objects. If a problem is planes, lines, points and rigid transforms, PGA is the lighter tool. If it needs round objects, CGA's extra dimension pays for itself. The null-blade difficulties of Chapter 3's inverse and dual show up in both, since both have a degenerate or null direction.

11.11 Try It: The Worked Examples in ganja.js

The examples from 11.5 and 11.7 run as they stand in ganja.js[7], which is released under the MIT licence. Press Run and the page fetches the library from the jsDelivr CDN and executes the code below in your browser. The source is editable in place: change a number and press Run again.

Two notation differences are worth knowing before you compare it with the chapter. ganja.js writes some blades in a different order, so its \(\mathbf{e}_{01}\) is this chapter's \(\mathbf{e}_{10}\) with the sign flipped, and the translator carries a minus sign to compensate. For the same reason its rotation about \(z\) is \(\cos\tfrac{\theta}{2} - \sin\tfrac{\theta}{2}\,\mathbf{e}_{12}\), which is the chapter's \(\cos\tfrac{\theta}{2} + \sin\tfrac{\theta}{2}\,\mathbf{e}_{21}\). The point form is the same. Literals such as 1e12 are algebraic blades, not numbers, because ganja.js rewrites the code inside the function you pass to Algebra.

// Worked examples from Chapter 11, in ganja.js (plane-based 3D PGA).
Algebra(3,0,1, () => {
  // Points are trivectors; planes are vectors (a e1 + b e2 + c e3 + d e0 is ax+by+cz+d = 0).
  const point = (x, y, z) => !(1e0 + x*1e1 + y*1e2 + z*1e3);
  const plane = (a, b, c, d) => a*1e1 + b*1e2 + c*1e3 + d*1e0;
  const xyz   = P => [-P.e023, P.e013, -P.e012].map(c => +(c / P.e123).toFixed(4));

  // ganja writes e01 where the chapter writes e10 (e01 = -e10), hence the minus sign.
  const translator = (x, y, z) => 1 - 0.5*(x*1e01 + y*1e02 + z*1e03);
  // Rotation about z by theta, counter-clockwise viewed from +z: cos + sin*e21 = cos - sin*e12.
  const rotor = th => Math.cos(th/2) - Math.sin(th/2)*1e12;
  const apply = (M, X) => M * X * ~M;                      // the sandwich product

  // 1. An axis parallel to z through (1,0,0): rotate 90 degrees about it.
  const T = translator(1, 0, 0), R = rotor(Math.PI/2);
  const M = T * R * ~T;                                    // one motor from three
  console.log("M =", "" + M);
  console.log("(2,0,0) ->", xyz(apply(M, point(2, 0, 0))));   // expect [1,1,0]
  console.log("(1,0,5) ->", xyz(apply(M, point(1, 0, 5))));   // on the axis: unchanged

  // 2. A screw motion: the same 90-degree turn plus a translation of 2 along z.
  const S = translator(0, 0, 2) * R;
  console.log("(1,0,0) ->", xyz(apply(S, point(1, 0, 0))));   // expect [0,1,2]

  // 3. Meet (wedge) and join (regressive product, & in ganja.js).
  console.log("plane z=3 ^ (0,0,5):", "" + (plane(0, 0, 1, -3) ^ point(0, 0, 5)));  // nonzero: off the plane
  console.log("plane z=3 ^ (0,0,3):", "" + (plane(0, 0, 1, -3) ^ point(0, 0, 3)));  // 0: on the plane
  console.log("join of 3 points:", "" + (point(0, 0, 2) & point(1, 0, 2) & point(0, 1, 2)));  // the plane z = 2
});

The output should show the point \((2,0,0)\) landing on \((1,1,0)\), the on-axis point \((1,0,5)\) unchanged, and the screw sending \((1,0,0)\) to \((0,1,2)\). The plane wedged with an off-plane point gives a non-zero multiple of \(\mathbf{e}_{0123}\), and with an on-plane point gives zero. The three-point join prints as \(2\mathbf{e}_0 - \mathbf{e}_3\), the plane \(z=2\) up to overall sign. ganja.js stores coefficients in 32-bit floats, so an unrounded value can print as 0.99999994 instead of 1, which is why the code rounds the coordinates it reports.

11.12 Key Terms

  • Clifford algebra. The algebraic structure Geometric Algebra lives in: a graded, associative algebra generated by a vector space and a metric.
  • Bivector. An oriented plane element. Bivectors generate rotations, and in physics the electromagnetic field is a single bivector in spacetime algebra[5].
  • Degenerate metric. A metric with at least one basis vector that squares to zero. PGA's \(\mathbf{e}_0\) is one.
  • Join / meet. The smallest object containing two others, and the largest object contained in both. Which product computes which depends on whether points or planes are the vectors.
  • Point at infinity (ideal point). A point of zero weight. It represents a direction.
  • Motor. An even-grade element representing a rigid motion: rotation, translation, or a screw combining both.
  • Sandwich product. The transformation \(x' = M\,x\,\widetilde{M}\) (with \(\widetilde{M} = M^{-1}\) for a unit motor).
  • Study condition. The constraint that \(M\widetilde{M}\) has no pseudoscalar part, which separates genuine motors from arbitrary even elements.

References

  1. Gunn, C. G. (2017). Geometric algebras for Euclidean geometry. Advances in Applied Clifford Algebras, 27, 185–208. https://doi.org/10.1007/s00006-016-0647-0
  2. Gunn, C. G., & De Keninck, S. (2019). Geometric algebra and computer graphics. ACM SIGGRAPH 2019 Courses. https://doi.org/10.1145/3305366.3328099 (course notes: arXiv:2002.04509)
  3. Dorst, L., & De Keninck, S. (2022). A guided tour to the plane-based geometric algebra PGA (version 2.0, 14 March 2022; first released 2020). University of Amsterdam. https://bivector.net/PGA4CS.html
  4. Dorst, L., Fontijne, D., & Mann, S. (2007). Geometric Algebra for Computer Science. Morgan Kaufmann.
  5. Doran, C., & Lasenby, A. (2003). Geometric Algebra for Physicists. Cambridge University Press.
  6. Kavan, L., Collins, S., Žára, J., & O'Sullivan, C. (2008). Geometric skinning with approximate dual quaternion blending. ACM Transactions on Graphics, 27(4). https://doi.org/10.1145/1409625.1409627
  7. De Keninck, S. ganja.js: Geometric algebra generator for JavaScript, C++, C#, Rust and Python. https://github.com/enkimute/ganja.js
  8. Ong, J. Klein: a specialised SIMD geometric algebra library for P(R*3,0,1). https://github.com/jeremyong/klein