Building Shapes from Blades

Chapter 9 established what a blade is: the outer product of a set of vectors, encoding an oriented, extended geometric entity. This chapter is the workshop, following the construction Dorst, Fontijne, and Mann use throughout their treatment of blades as geometric primitives[1]: we build a point, a line, a plane, and a volume step by step, and at each stage write the same geometry three ways — as a vector, as a matrix, and as a GA blade — so the comparison is concrete rather than asserted.

10.1 Conventions We'll Use

Throughout, we work in \(\mathcal{G}(\mathbb{R}^3)\) with orthonormal basis vectors \(\mathbf{e}_1,\mathbf{e}_2,\mathbf{e}_3\) satisfying \(\mathbf{e}_i\cdot\mathbf{e}_j=\delta_{ij}\), the same algebra §9.5 and §9.8 worked in. §10.4 will need a fourth, Euclidean basis vector \(\mathbf{e}_4\) (\(\mathbf{e}_4^2=1\)) to fix a genuine limitation of plain 3D blades for representing planes.

!

This is not Chapter 8's CGA. The four-dimensional lift used below is a simple pedagogical device — one extra Euclidean dimension, nothing more. It is not Chapter 8's Conformal GA (two extra dimensions of mixed signature, built to represent spheres) and not Projective GA as usually defined in the literature (a degenerate extra dimension, \(\mathbf{e}_4^2=0\)). It's the simplest of the three lifts, and it's enough to fix the one problem this chapter actually has.

FrameworkWhat we call thingsMultiplication
Vectorposition \(\mathbf{p}\), direction \(\mathbf{d}\), normal \(\mathbf{n}\)dot \(\cdot\), cross \(\times\)
Matrixcolumn vectors; line/plane as a \(3\times4\) or \(4\times4\) homogeneous matrixmatrix multiply, transpose
GAvectors, bivectors (2-blades), trivectors (3-blades), pseudoscalargeometric product \(\mathbf{ab}=\mathbf{a}\cdot\mathbf{b}+\mathbf{a}\wedge\mathbf{b}\)
!

Why a 2-blade for a line? In vector algebra a line needs two things: a point on it (\(\mathbf{p}\)) and a direction (\(\mathbf{d}\)). In GA we wedge them: \(L=\mathbf{p}\wedge\mathbf{d}\). The resulting bivector is the line — one algebraic object, not a pair. §10.3 unpacks why that's not just notation.

10.2 A Point (a 1-blade)

A point is the simplest "shape": a location in space with no extent.

  • 1

    Pick coordinates. Say the point is at \((3,-2,1)\).

  • 2

    Form the linear combination: \(\mathbf{p}=3\mathbf{e}_1-2\mathbf{e}_2+\mathbf{e}_3\).

  • 3

    (GA only, and only when we need it — see §10.4.) In the homogeneous lift, append the homogenising coordinate: \(\mathbf{p}_h=3\mathbf{e}_1-2\mathbf{e}_2+\mathbf{e}_3+\mathbf{e}_4\). This is still a 1-blade, just living in 4D, so it can be pushed through the same rotor machinery as a line or plane.

VectorMatrix (homogeneous \(4\times4\))GA blade
Object\(\mathbf{p}=(3,-2,1)\)\((3,-2,1,1)^\top\), a \(4\times1\) column\(3\mathbf{e}_1-2\mathbf{e}_2+\mathbf{e}_3+\mathbf{e}_4\)
Grade— (just a tuple)— (just a column)1-blade (vector)
Distance to origin\(\sqrt{9+4+1}=\sqrt{14}\)\(\lVert\text{col}\rVert\) (dropping the last entry)\(\sqrt{\mathbf{p}\cdot\mathbf{p}}=\sqrt{14}\)

10.3 A Line (a 2-blade)

A line is the set of points \(\mathbf{p}+t\mathbf{d}\). It has a position and a direction, and it is oriented: swapping the order of the wedge flips its sign. We'll build the line through \(\mathbf{p}=\mathbf{e}_1\) with direction \(\mathbf{d}=\mathbf{e}_1+\mathbf{e}_2\) — the same pair used throughout the code comparison in §10.7.

  • 1

    Identify the two building vectors: \(\mathbf{p}=\mathbf{e}_1\), \(\mathbf{d}=\mathbf{e}_1+\mathbf{e}_2\).

  • 2

    Wedge them: \(L=\mathbf{p}\wedge\mathbf{d}=\mathbf{e}_1\wedge(\mathbf{e}_1+\mathbf{e}_2)\).

  • 3

    Distribute and simplify using \(\mathbf{e}_1\wedge\mathbf{e}_1=0\): \(L=(\mathbf{e}_1\wedge\mathbf{e}_1)+(\mathbf{e}_1\wedge\mathbf{e}_2)=\mathbf{e}_{12}\).

  • 4

    Check. \(\mathbf{p}\) and \(\mathbf{d}\) are not parallel, so the wedge is non-zero and the line is well-defined. Had we chosen \(\mathbf{d}=2\mathbf{e}_1\) instead, \(\mathbf{e}_1\wedge2\mathbf{e}_1=0\) — degenerate, as expected of a "direction" identical to the point itself.

PropertyVector algebraMatrix algebraGA
Define the lineStore \(\mathbf{p}\) and \(\mathbf{d}\) separately (6 floats, two objects)\(3\times2\) matrix \([\mathbf{p}\,|\,\mathbf{d}]\)One object \(L=\mathbf{p}\wedge\mathbf{d}\) (a bivector, 3 floats in 3D)
Does point \(\mathbf{q}\) lie on the line?\(\mathbf{d}\times(\mathbf{q}-\mathbf{p})=\mathbf{0}\)\(\mathrm{rank}\big([\mathbf{d}\ |\ \mathbf{q}-\mathbf{p}]\big)<2\)\((\mathbf{q}-\mathbf{p})\wedge\mathbf{d}=0\)
Rotate the line by 90° about \(\mathbf{e}_3\)Rotate \(\mathbf{p}\) and \(\mathbf{d}\) separately with a rotation matrix \(R\); reassembleConjugate each embedded vector: \(R\mathbf{v}\)\(L'=RL\tilde R\) where \(R=\cos45^\circ-\sin45^\circ\,\mathbf{e}_{12}\) is the actual rotor for a 90° turn about \(\mathbf{e}_3\) (half-angle 45°, in the \(\mathbf{e}_{12}\) plane — that plane's dual axis is \(\mathbf{e}_3\), not \(\mathbf{e}_{23}\)'s). One line, one expression.
Area of the parallelogram spanned by \(\mathbf{p},\mathbf{d}\)\(\lvert\mathbf{d}\times\mathbf{p}\rvert=\lvert\mathbf{p}\rvert\lvert\mathbf{d}\rvert\sin\theta\)\(\sqrt{\det(M^\top M)}\)\(\lVert L\rVert=\sqrt{L\tilde L}\) — intrinsic, no cross product needed (note the reverse \(\tilde L\), not a raw \(L\cdot L\): a real bivector squares to a negative number, so \(L\tilde L=-L^2\) is what's actually non-negative)
!

The point-on-line test, and why it needs the subtraction. It's tempting to test incidence by wedging \(\mathbf{q}\) straight into the blade: \(\mathbf{q}\wedge\mathbf{p}\wedge\mathbf{d}=0\). Resist it — that tests something weaker than you want. Any three vectors lying in the 2D plane through the origin spanned by \(\mathbf{p}\) and \(\mathbf{d}\) wedge to zero, including points like \(2\mathbf{p}\) that are nowhere near the actual line \(\{\mathbf{p}+t\mathbf{d}\}\). Subtracting \(\mathbf{p}\) first, as the vector-algebra test already does, fixes it: \((\mathbf{q}-\mathbf{p})\wedge\mathbf{d}=0\) tests exactly what it should — that \(\mathbf{q}-\mathbf{p}\) is parallel to \(\mathbf{d}\). (The homogeneous lift from §10.4 gives a version with no subtraction needed at all: with \(\mathbf{p}_h,\mathbf{d}_h\) built the same way as §10.4's points, \(\mathbf{q}_h\wedge(\mathbf{p}_h\wedge\mathbf{d}_h)=0\) is the fully general, single-blade incidence test — the pattern the naive version above was reaching for.)

10.4 A Plane (a 3-blade)

A plane through the origin is spanned by two independent vectors; a general plane is the affine span of three points. Let the three points be \(A=\mathbf{e}_1\), \(B=\mathbf{e}_2\), \(C=\mathbf{e}_1+\mathbf{e}_2+\mathbf{e}_3\).

  • 1

    The direct construction is the wedge of all three points: \(\Pi=A\wedge B\wedge C\). Because \(A\wedge B\wedge A=A\wedge A\wedge C=0\) (any factor repeated in a wedge product vanishes), this equals \(A\wedge(B-A)\wedge(C-A)\) — the same trivector either way, so we can use whichever form is more convenient.

  • 2

    Expand directly: \(\Pi=\mathbf{e}_1\wedge\mathbf{e}_2\wedge(\mathbf{e}_1+\mathbf{e}_2+\mathbf{e}_3)\). The \(\mathbf{e}_1\) and \(\mathbf{e}_2\) terms inside the last factor each repeat an index already present and vanish, leaving \(\Pi=\mathbf{e}_1\wedge\mathbf{e}_2\wedge\mathbf{e}_3=\mathbf{e}_{123}\).

!

That's a clean trivector — so what's the problem? In plain 3D, the space of trivectors is one-dimensional: every non-zero trivector is some scalar multiple of \(\mathbf{e}_{123}\). \(\Pi\) can tell you the plane's orientation and how "big" the spanning triangle is, but it structurally cannot distinguish this plane from any parallel plane offset along its own normal — there's no room left in a 1-dimensional space to encode "which one." That's a real limitation of 3D blades, not an error to route around by more careful arithmetic.

The fix is the homogeneous lift from §10.1: embed each point with a \(+\mathbf{e}_4\) term, \(A_h=\mathbf{e}_1+\mathbf{e}_4\), \(B_h=\mathbf{e}_2+\mathbf{e}_4\), \(C_h=\mathbf{e}_1+\mathbf{e}_2+\mathbf{e}_3+\mathbf{e}_4\), and wedge in 4D: \(\Pi=A_h\wedge B_h\wedge C_h\). The trivector space of \(\mathcal{G}(\mathbb{R}^4)\) is four-dimensional (basis \(\mathbf{e}_{123},\mathbf{e}_{124},\mathbf{e}_{134},\mathbf{e}_{234}\)), which is exactly enough room to carry the plane's position along with its orientation. Expanding the wedge (most of the 27 terms vanish on a repeated index; the coefficients of what's left are the \(3\times3\) minors of the \(3\times4\) matrix of components) gives:

\[ \Pi = \mathbf{e}_{123} - \mathbf{e}_{124} - \mathbf{e}_{134} + \mathbf{e}_{234} \]
VectorMatrixGA
Store the planeNormal \(\mathbf{n}\) (3 floats) + offset \(d\) (1 float)Row of a \(4\times4\): \([n_x,n_y,n_z,d]\)One trivector \(\Pi\) (4 components in \(\mathcal{G}(\mathbb{R}^4)\))
Distance of point \(Q\) to plane\(d=\mathbf{n}\cdot(Q-A)/\lvert\mathbf{n}\rvert\)\([n_x\,n_y\,n_z\,d]\cdot[Q_x\,Q_y\,Q_z\,1]^\top\)proportional to \(Q_h\cdot\Pi^*\), the dual trivector \(\Pi^*=\Pi I_4^{-1}\) from Chapter 4's meet/join duality — \(\Pi^*\) is a vector, the plane's normal carried in the embedding
Rotate the planeRotate \(\mathbf{n}\); recompute \(d\) from the rotated pointConjugate the embedded row\(\Pi'=R\Pi\tilde R\) — one expression

10.5 A Volume (a 3-blade in 3D; a 4-blade in the homogeneous lift)

Tetrahedron vertices: \(O=\mathbf{0}\), \(A=\mathbf{e}_1\), \(B=\mathbf{e}_2\), \(C=\mathbf{e}_3\).

  • 1

    Edge vectors from \(O\): \(\mathbf{u}=\mathbf{e}_1,\ \mathbf{v}=\mathbf{e}_2,\ \mathbf{w}=\mathbf{e}_3\).

  • 2

    Wedge all three: \(V=\mathbf{e}_1\wedge\mathbf{e}_2\wedge\mathbf{e}_3=\mathbf{e}_{123}\).

  • 3

    Magnitude: \(\mathbf{e}_{123}\tilde{\mathbf{e}}_{123}=-\mathbf{e}_{123}^2=-(-1)=1\) (a grade-3 blade's reverse flips sign, so \(\tilde{\mathbf{e}}_{123}=-\mathbf{e}_{123}\)), giving \(\lVert V\rVert=\sqrt{V\tilde V}=1\). This is the parallelepiped's volume; the tetrahedron's is \(1/3! = 1/6\) of it.

  • 4

    In the homogeneous lift, \(V_4=O_h\wedge A_h\wedge B_h\wedge C_h\) (with \(O_h=\mathbf{e}_4\)) is proportional to the pseudoscalar \(I_4=\mathbf{e}_{1234}\) and encodes the full oriented tetrahedron together with its position relative to the origin — the same jump in expressive power §10.4 needed for planes, one grade up.

10.6 Rotating a Blade: Where GA Shines

This is the operation that costs the most bookkeeping in vector or matrix code. In GA it is a single conjugation: for a rotor \(R=\cos(\theta/2)-\sin(\theta/2)\mathbf{B}\) built from unit bivector \(\mathbf{B}\) (§9.5), any blade \(X\) of any grade rotates the same way — Hestenes's outer-morphism property, the fact that a rotor's action extends uniformly to every grade rather than needing a separate rule per shape[2] — as

\(X \to R X \tilde R\).

Take \(R=\cos(\theta/2)-\sin(\theta/2)\mathbf{e}_{12}\), rotation in the \(\mathbf{e}_{12}\) plane by \(\theta=45^\circ\). Because \(\mathbf{e}_3\) commutes with \(\mathbf{e}_{12}\) (it shares no index with it, so it slides past both factors freely), \(R\) leaves \(\mathbf{e}_3\) fixed and rotates \(\mathbf{e}_1,\mathbf{e}_2\) into each other in the ordinary way:

\[ R\mathbf{e}_1\tilde R = \cos\theta\,\mathbf{e}_1+\sin\theta\,\mathbf{e}_2, \qquad R\mathbf{e}_3\tilde R = \mathbf{e}_3. \]

That's all that's needed. Rotating the bivector \(M=\mathbf{e}_{13}=\mathbf{e}_1\wedge\mathbf{e}_3\) just rotates its two factors and re-wedges them:

\[ M' = R\mathbf{e}_1\tilde R \wedge R\mathbf{e}_3\tilde R = (\cos\theta\,\mathbf{e}_1+\sin\theta\,\mathbf{e}_2)\wedge\mathbf{e}_3 = \cos\theta\,\mathbf{e}_{13}+\sin\theta\,\mathbf{e}_{23}. \]

At \(\theta=45^\circ\): \(M'=\cos45^\circ\,\mathbf{e}_{13}+\sin45^\circ\,\mathbf{e}_{23}\) — the \(\mathbf{e}_1\mathbf{e}_3\) plane tips partway toward the \(\mathbf{e}_2\mathbf{e}_3\) plane, exactly as a physical plane containing the \(\mathbf{e}_3\)-axis would when the space is turned 45° about that same axis.

\(L=\mathbf{e}_{12}\) is the special case worth naming: since both of its factors rotate within the same plane the rotor is built from, \(RLR̃=L\) unchanged — a plane doesn't move when you rotate it within itself, only the vectors spanning it do.

!

Key insight. In vector algebra you'd rotate \(\mathbf{e}_1\) and \(\mathbf{e}_3\) separately with a \(3\times3\) matrix, then recompute the cross product to get the new normal. In GA the blade rotates as a whole under conjugation — the shape is the primary object, not the vectors that happen to span it.

10.7 Full Code Comparison (Python)

The three snippets below compute the same thing: build the line through \(\mathbf{p}=(1,0,0)\) with direction \(\mathbf{d}=(1,1,0)\), confirm that \(\mathbf{q}=(2,1,0)\) lies on it (it does — \(\mathbf{q}=\mathbf{p}+\mathbf{d}\)), rotate the line 45° about \(\mathbf{e}_3\), then check whether that same, unmoved point \(\mathbf{q}\) still lies on the rotated line. It doesn't — rotating the line doesn't drag along points that were never part of the rotation, which is exactly the kind of bookkeeping error GA's single-conjugation approach makes structurally harder to fall into.

10.7.1 Vector algebra

import numpy as np

def rot_z(theta):
    c, s = np.cos(theta), np.sin(theta)
    return np.array([[c, -s, 0],
                      [s,  c, 0],
                      [0,  0, 1]])

# --- build line ---
p = np.array([1.0, 0.0, 0.0])
d = np.array([1.0, 1.0, 0.0])
q = np.array([2.0, 1.0, 0.0])   # q = p + d, so q is on the original line

# --- point-on-line test, against the ORIGINAL line ---
on_original = np.allclose(np.cross(d, q - p), 0, atol=1e-10)
print("On original line?", on_original)               # True

# --- rotate the line 45 deg about z; q itself does not move ---
R = rot_z(np.pi / 4)
p_r, d_r = R @ p, R @ d

# --- test the same q against the ROTATED line ---
on_rotated = np.allclose(np.cross(d_r, q - p_r), 0, atol=1e-10)
print("Still on line after rotating the line?", on_rotated)   # False

10.7.2 Matrix (homogeneous \(4\times4\)) algebra

import numpy as np

def rot_z_mat(theta):
    c, s = np.cos(theta), np.sin(theta)
    M = np.eye(4)
    M[0, 0], M[0, 1] = c, -s
    M[1, 0], M[1, 1] = s,  c
    return M

def embed(v):
    return np.array([v[0], v[1], v[2], 1.0])

p = np.array([1.0, 0.0, 0.0])
d = np.array([1.0, 1.0, 0.0])
q = np.array([2.0, 1.0, 0.0])
R = rot_z_mat(np.pi / 4)

# --- rotate p and d by conjugating their embedded columns ---
p_r = (R @ embed(p))[:3]
d_r = (R @ embed(d))[:3] - (R @ embed(np.zeros(3)))[:3]  # direction: no translation part

# --- point-on-line test as a rank check: d and (q - p) parallel iff rank < 2 ---
def on_line(p_test, d_test):
    M = np.column_stack([d_test, q - p_test])
    return np.linalg.matrix_rank(M, tol=1e-10) < 2

print("On original line?", on_line(p, d))     # True
print("On rotated line?", on_line(p_r, d_r))   # False

10.7.3 Geometric Algebra (library-agnostic pseudocode)

# Adapt to your GA library's API; ^ is the outer (wedge) product,
# * the geometric product, and the basis is e1, e2, e3.

e1, e2, e3 = basis_vectors(3)        # from your GA library
p = e1
d = e1 + e2
q = 2*e1 + e2                        # q = p + d

# --- build the line as a 2-blade (bivector) ---
L = p ^ d                            # = e1^e2, since e1^e1 = 0

# --- point-on-line test: subtract p first, then wedge with d ---
def on_line(p_test, d_test):
    return is_zero((q - p_test) ^ d_test, tol=1e-12)

print("On original line?", on_line(p, d))       # True

# --- rotor for 45 deg in the e1e2 plane ---
theta = np.pi / 4
B = e1 ^ e2
R = cos(theta / 2) - sin(theta / 2) * B
R_tilde = reverse(R)

# --- rotate the LINE (blade), not the point q ---
L_rot = R * L * R_tilde              # single conjugation

# extract the rotated p, d back out for the same on_line test,
# or (equivalently, and more in the spirit of the chapter) test
# q directly against L_rot via the meet: q is on L_rot iff
# q ^ L_rot vanishes for a line through the origin, or, for a
# general line, iff (q - p_rot) ^ d_rot vanishes as above.
p_rot, d_rot = extract_point_and_direction(L_rot)  # library-specific
print("On rotated line?", on_line(p_rot, d_rot))   # False
!

What you just saw in code. Vector: two separate arrays, a rotation matrix, a cross product. Matrix: a \(4\times4\) conjugation and a rank check — the "line" is a pair of transformed columns, not a shape. GA: one bivector \(L\), one rotor \(R\), one conjugation \(RL\tilde R\) that moves the whole shape at once. The shape is the primary data type; in the other two columns it's reconstructed from parts every time you need it.

10.8 Summary Table

ShapeVectorMatrixGA bladeGrade
Point3-tuple \((x,y,z)\)\(4\times1\) homogeneous column\(x\mathbf{e}_1+y\mathbf{e}_2+z\mathbf{e}_3\ (+\,\mathbf{e}_4)\)1-blade
Line\(\mathbf{p}\) (3) + \(\mathbf{d}\) (3), two objects\(3\times2\) or \(4\times4\)\(\mathbf{p}\wedge\mathbf{d}\)2-blade (bivector)
PlaneNormal \(\mathbf{n}\) (3) + offset \(d\) (1)\(4\times4\) (or \(3\times4\))\(A_h\wedge B_h\wedge C_h\)3-blade (trivector)
VolumeScalar signed volume — loses orientation and shape\(\det(M)\) of a \(4\times4\)\(A_h\wedge B_h\wedge C_h\wedge D_h\)4-blade (pseudoscalar)
RotateRotate each stored vector with \(R\); reassembleConjugate each embedded column\(R X \tilde R\)
Incidence testCross product \(=0\), or dot with normal \(=0\)Rank/determinant of an assembled matrix \(=0\)Wedge in the (offset) test vector; check the blade \(=0\)

References

  1. Dorst, L., Fontijne, D., & Mann, S. (2007). Geometric Algebra for Computer Science. Morgan Kaufmann.
  2. Hestenes, D. (1999). New Foundations for Classical Mechanics (2nd ed.). Kluwer Academic.