Add a point at infinity for every direction. Now parallel lines meet. The payoff: no special cases. Points and lines become interchangeable (duality), and perspective projection becomes a linear map.
Homogeneous coordinates
Represent a point (x, y) as a triple (X, Y, W) where x = X/W and y = Y/W. The triple (2, 4, 2) and (1, 2, 1) are the same point. When W = 0, you get a point at infinity in the direction (X, Y). This trick makes all of projective geometry work.
Scheme
; Homogeneous coordinates; A point (x,y) becomes (X, Y, W) where x = X/W, y = Y/W
(define (to-homogeneous x y)
(list x y 1))
(define (from-homogeneous h)
(let ((w (caddr h)))
(if (= w 0)
(list 'infinity (car h) (cadr h))
(list (/ (car h) w) (/ (cadr h) w)))))
(display "Point (3,4) in homogeneous: ")
(display (to-homogeneous 34))
(newline)
(display "(6,8,2) back to Cartesian: ")
(display (from-homogeneous (list 682)))
(newline)
(display "(3,4,0) is a point at infinity: ")
(display (from-homogeneous (list 340)))
(newline)
; Scaling doesn't change the point
(display "(9,12,3) = same as (3,4,1): ")
(display (from-homogeneous (list 9123)))
Lines, intersection, and the cross product trick
In homogeneous coordinates, a line is also a triple (a, b, c) where ax + by + c = 0. The line through two points is their cross product. The intersection of two lines is also their cross product. Parallel lines intersect at a point at infinity.
Scheme
; Cross product of two homogeneous triples
(define (cross p q)
(list (- (* (cadr p) (caddr q)) (* (caddr p) (cadr q)))
(- (* (caddr p) (car q)) (* (car p) (caddr q)))
(- (* (car p) (cadr q)) (* (cadr p) (car q)))))
; Line through two points
(define (line-through p1 p2) (cross p1 p2))
; Intersection of two lines
(define (intersect l1 l2) (cross l1 l2))
; Two points: (1,0) and (0,1)
(define p1 (list 101))
(define p2 (list 011))
(define line1 (line-through p1 p2))
(display "Line through (1,0) and (0,1): ")
(display line1)
(newline)
; Parallel lines: y=1 is (0,1,-1), y=2 is (0,1,-2)
(define l1 (list 01-1))
(define l2 (list 01-2))
(define meeting (intersect l1 l2))
(display "Parallel lines y=1, y=2 meet at: ")
(display meeting)
(newline)
; W=0 means point at infinity (in x-direction)
(display "That is a point at infinity")
Python
def cross(p, q):
return (
p[1]*q[2] - p[2]*q[1],
p[2]*q[0] - p[0]*q[2],
p[0]*q[1] - p[1]*q[0],
)
def from_homogeneous(h):
if h[2] == 0:
returnf"point at infinity ({h[0]}, {h[1]})"return (h[0]/h[2], h[1]/h[2])
# Line through (1,0) and (0,1)
line = cross((1,0,1), (0,1,1))
print(f"Line through (1,0) and (0,1): {line}")
# Parallel lines y=1 and y=2 meet at infinity
meeting = cross((0,1,-1), (0,1,-2))
print(f"Parallel lines meet at: {from_homogeneous(meeting)}")
Duality
In projective geometry, every theorem about points and lines has a dual theorem where you swap "point" and "line." Two points determine a line; dually, two lines determine a point. This is because points and lines are both represented as triples, and the incidence relation (a point lies on a line) is symmetric: p . l = 0 iff l . p = 0.
Cross-ratio
The cross-ratio of four collinear points A, B, C, D is (AC * BD) / (BC * AD). It is the fundamental invariant of projective geometry: preserved under all projective transformations. Distances and angles change, but cross-ratios don't.
Scheme
; Cross-ratio of four collinear points (using their coordinates on the line)
(define (cross-ratio a b c d)
(/ (* (- a c) (- b d))
(* (- b c) (- a d))))
; Points on a line: 1, 2, 3, 5
(display "Cross-ratio of (1,2,3,5): ")
(display (cross-ratio 1235))
(newline)
; Projective transformation: f(x) = (2x+1)/(x+1)
(define (proj x) (/ (+ (* 2 x) 1) (+ x 1)))
(display "After projective transform: ")
(display (cross-ratio (proj 1) (proj 2) (proj 3) (proj 5)))
(newline)
; Cross-ratio is preserved!
(display "Preserved? ")
(display (= (cross-ratio 1235)
(cross-ratio (proj 1) (proj 2) (proj 3) (proj 5))))