Configuration Space I: Obstacles in the Space of Configurations
What a C-obstacle is — the set of configurations at which a function of q meets the obstacle — computed exactly for a disc, vertex by vertex for a polygon, and pixel by pixel for an arm on a torus; the star algorithm, GJK, and Reach's Jacobian with its singularities.
Although the example in figure 3.5 is quite simple, the main point is that it is easier to think about points moving around than bodies with volume.
In this chapter
Chapter 1 promised that the piano is a point. This chapter pays. It answers the four questions Choset opens his Chapter 3 with — how much information specifies a robot's position, how to represent it, what its mathematical properties are, and how obstacles in the workspace become constraints on it — for every case that can be computed exactly, and it opens the collision box that Chapter 2 deliberately sealed.
The idea the whole chapter turns on is one sentence long and easy to say wrong. A configuration space obstacle is not a picture of the workspace obstacle. It is the set of configurations at which a function of the configuration, , meets the obstacle. For a disc that set happens to look like the obstacle, grown by the radius — and Choset warns on his page 44 that this is the one case where it does. For a polygon that rotates, it is a Minkowski difference computed one vertex at a time, and the vertices re-pair every time the robot turns. For a two-joint arm it is a blob on a torus that nobody can draw by hand and that a planner must nonetheless search. The widget at the top of this page draws it live; the mathematics below says why it has the shape it has; the Rust at the end computes it, and the tests hold every printed number to the code.
Two things from Choset's §3.8 ride along, because every later planner for Reach — the potential fields of Chapter 7, the dynamics of Chapter 17, the steering of Chapter 21 — needs them already: the Jacobian that turns joint velocities into tip velocities, and the configurations where it loses rank. The topology that makes the torus wrap, and why the square is only a chart of it, waits for Chapter 5.
The problem: a block, and its shadow
Below, a slate block slides across the Workbench on a seeded path while Reach, the two-joint arm, sweeps through its configurations. Beside it, the square of joint angles — the torus cut open and flattened — with an amber region that stretches, splits, and rejoins as the block moves. Before reading on: what is the amber region?
It is the set of joint-angle pairs at which some point of the arm lies inside the block. Choset calls it the configuration space obstacle , and three things about it are the whole chapter.
It is not the block. The block is a square in the plane. Its shadow on the torus is a curved blob whose shape depends on where the block is, how long the links are, and which link can reach it. Slide the block toward the base and a vertical band appears — link 1 now hits it for every , so a whole column of the square is forbidden. Slide it the other way and the blob shrinks toward a point. Drag it in one direction and the blob can move in the other. The misconception this widget exists to kill is that the C-obstacle "looks like" the obstacle. It is a different shape in a different space.
It is the preimage of a function. Write for the set of workspace points the arm occupies
at configuration . The amber region is , the set of
inputs for which a function of meets a fixed set. Every property of the blob — its convexity or
lack of it, its connectedness, whether it wraps across the seam — is a property of that function.
This is also why the same code draws the disc's C-space in the next section: nothing about the
rasterizer knows what an arm is. It asks a Collision checker one question per cell.
It is a picture, not a certificate. The amber cells are the configurations the raster tested. A path through the white cells passes through configurations no one tested. Choset's §3.2.2 says this plainly — the grid "only encodes collision information for the discrete set of points lying on the grid" — and offers the remedy the widget's link thickness slider implements: thicken the robot so that a free sample certifies a free neighborhood. The planner is then conservative and only resolution complete. We will come back to this.
The readout under the torus counts cells and components. Load the micro-example square preset and the row shows exactly occupied cells. That number is the chapter's handshake between hand, raster and code: the next section derives it with a protractor, the Rust at the end prints it, and a test asserts it.
Building intuition
A disc only grows
Start where the picture is easy. Choset's figure 3.5 puts three circular robots of increasing radius in one room with a doorway and asks each to cross it. The widget below is that figure with numbers: a m room, a m doorway in the dividing wall, one convex crate, and three discs — a point, Rusty at m, and a third whose radius you set.
For a disc translating in the plane the configuration is the center , so and it is tempting to think of configuration space as the workspace. It is not — they are different spaces that happen to have the same coordinates — but the C-obstacle of a disc does have a closed form. The disc of radius at meets the crate iff some crate point lies within of , so is the crate with every point pushed outward by : edges slide out by , corners become quarter-circles, and walls — zero-width obstacles — become capsules of width . Choset's phrase is that "we 'grow' the polygon outward and the walls inward." The point robot then plans in the grown map.
Three numbers to check against the readouts. The point robot's free area is the room less the crate,
. For Rusty the crate's C-obstacle has area — the original, a strip of width
along the perimeter , and corner arcs that together make exactly one disc. And the doorway,
m wide, narrows to : Rusty passes with m to spare on each side, the free space
is one component, and the purple path goes through. Raise the third radius past m and the two
capsules meet in the doorway; falls into two components and the query has no answer —
which is exactly what Choset's figure 3.5(c) shows and what no amount of clever search can fix. The
check doorway pins the component counts for and the free area
.
Now the honesty item this widget is really about. The disc inflation is correct — "inflate by the radius and plan for a point" is exactly right — and it is right only for discs. A disc has no orientation, so its footprint is the same set translated; for any body that rotates, changes shape with , and the "grow the obstacle" picture fails at the first corner. The Minkowski Studio in the mathematics section shows what replaces it.
An arm's C-obstacle is a shape in a different space
Here is the micro-example that runs through the rest of the chapter, chosen so that every number can be checked with a protractor. Reach has , zero-thickness links, base at the origin; the only obstacle is a small square — side , centered at . Link 1 has length and the square begins at , so link 1 can never touch it: the C-obstacle comes from link 2 alone.
Five configurations by hand.
- (a) . Link 2 runs from the elbow to the tip , straight through the square's center. Collision.
- (b) . Link 2 leaves at ; at its height is , inside the square's left edge. Collision.
- (c) . Height at is : the link clears the corner . Free.
- (d) . Elbow at , link 2 at absolute heading , tip at — inside the square. Collision.
- (e) for any . Elbow at ; the nearest point of the square is the corner at distance . Free for every .
So the slice of at is exactly rad , the angle the near corner subtends from the elbow. On a raster whose
cells are centered at integer degrees, that is the cells : cells out of — the number the widget's readout shows for the preset. Generalizing (e),
the -extent of the blob is the set of elbow positions within of the square:
, i.e. , i.e. rad . On the raster, the rows
hold one cell each and the rows are empty; the blob is one connected region of
cells ( of ) and touches no seam. The check cspace asserts all of it, and bisects the
exact segment test along the row to recover to .
Compare this with the square itself: a box. Its shadow on the torus is a curved lens about wide and tall at its waist, with no straight edges and no right angles. That is the picture to carry into the mathematics.
Why "grow the obstacle" fails for a polygon
One more experiment before the definitions. Suppose the robot is a convex polygon that translates at a fixed heading among a convex obstacle. Its C-obstacle is again convex, and it is again built from the obstacle and the robot — but not by growing the obstacle: it is the obstacle with the reflected robot swept along its boundary, the Minkowski difference . Turn the robot and the sweep changes; the C-obstacle's vertex count can change with it. The Minkowski Studio, placed beside the star algorithm below, computes it one vertex at a time. The misconception it kills is that Minkowski sums are expensive. For convex polygons they are a merge of two sorted lists.
The mathematics
| Symbol | Meaning |
|---|---|
| closure, interior, boundary of a set (Choset App. B) | |
| Minkowski sum {a + b} and Minkowski difference {a − b} | |
| mobility; ambient DOF (3 planar, 6 spatial); links; joints; DOF of joint i (Grübler) | |
| a holonomic constraint; a nonholonomic (velocity) constraint | |
| a line with unit normal (a, b) and its negative / positive half-planes (App. F.1) | |
| robot vertices, edges, outward normals (θ-dependent); obstacle vertices, edges, normals | |
| the half-plane constraints of a Type A and a Type B contact (eqs. F.4, F.6) | |
| the Jacobian of the forward kinematics; ẋ = J(q) q̇ | |
| GJK support value max z·x and support point of a polytope Z in direction x (eqs. F.11–F.12) |
Configuration, configuration space, degrees of freedom
For the disc, and , so
. For the two-joint arm, with each ,
so , the torus. Choset's figure 3.2 cuts the torus along and
and flattens it to a square whose opposite edges are identified — the square the
widgets draw, with its seams marked. The workspace of the arm, in the sense of the set of points
the end effector reaches, is an annulus; it is not a configuration space, because every interior
point is reached by two configurations (elbow-up and elbow-down), so a tip position does not specify
where every point of the robot is. Chapter 2's Reach::ik returns both.
C-obstacles, free space, and the two kinds of path
Hover a tinted term and every figure on the page mutes the other roles: the amber of the C-obstacle
is the amber of the torus, the slate of the workspace obstacle is the slate of the block. The
definition is a preimage: is the set of that the map sends into the
sets that meet . Two consequences follow at once and both matter for code. First, obstacles
union: , because meets a union iff it meets a member
(Choset problem 3.5) — so a rasterizer may ask one checker about a whole Scene rather than one
obstacle at a time. Second, for a convex robot translating against a convex obstacle, is
convex (problem 3.4; the star algorithm below is its constructive proof).
DerivationDerivation 1 — The disc's C-obstacle is a Minkowski inflation (Choset §3.2.1)
Statement. For a disc of radius translating in the plane, with configuration its center , . For a convex polygon with perimeter the inflated area is .
Step 1 — the preimage, unpacked. . Then iff there is an with iff for some , , iff . Nothing about the disc's shape entered except that it is a ball about its reference point: the sum is exactly the obstacle "grown" by .
Step 2 — the boundary. The boundary of for a convex polygon consists of each edge
offset outward by along its normal, joined at each vertex by a circular arc of radius
sweeping from the incoming edge's normal to the outgoing edge's normal. inflateDisc builds exactly
this: arcSegments chords per full turn, so the chord polygon approaches the true inflation from
the inside.
Step 3 — the area. The offset strips contribute times the perimeter . The arcs at the
vertices have angles that sum to the total turning of a convex polygon, , so together they make
one full disc, . For and : . The check inflate asserts the closed form and that the chord polygon's area
climbs toward it from below — with chords, with .
Choset's warning (his page 44): "although both the workspace and the configuration space for this system can be represented by , and the obstacles appear to simply 'grow' in this example, the configuration space and workspace are different spaces, and the transformation from workspace obstacles to configuration space obstacles is not always so simple." A rotating body's footprint is not a ball about any point; Derivations 3 and 4 are what "not so simple" costs.
Dimension: counting by constraints
DerivationDerivation 2 — Dimension by point counting (Choset §3.3)
Statement. A planar rigid body has three degrees of freedom and a spatial one six; a system of coordinates with independent holonomic constraints has .
Step 1 — place freely. Fix three non-collinear points on the body. 's position is free: coordinates in the plane, in space.
Step 2 — is constrained to a circle (sphere). Rigidity fixes , so lies on a circle about in the plane — one angle remains — or on a sphere in space, where two angles remain.
Step 3 — follows, up to a discrete choice. Two fixed distances , place in the plane up to a reflection: zero continuous freedoms remain. In space, three distances leave on a circle about the axis : one angle remains. Every other point of the body is then fixed by its three distances. Totals: and , with configuration spaces and .
Reach, counted the indirect way (Choset's own example). Treat each link as a spatial rigid body: coordinates. Six constraints confine the two links to the table plane, two pin the first joint to a point, and — once link 1's angle is chosen — two pin the second joint to the end of link 1: . The direct count is shorter: one revolute joint, one degree of freedom, twice. A closed chain: Choset's figure 3.9 has six links and seven revolute joints in the plane, so — a one-parameter mechanism, like the four-bar linkage of problem 3.7.
Choset's table 3.1 collects the configuration spaces this book plans in, and the inequalities beside it are the ones a planner written against coordinates cannot see:
| Robot | |
|---|---|
| Mobile robot translating in the plane (Rusty as a disc) | |
| Mobile robot translating and rotating in the plane (Rusty, Hitch's chassis) | or |
| Rigid body translating in space | |
| A spacecraft | or |
| An -joint revolute arm (Reach) | |
| A planar mobile robot with an attached -joint arm |
; ; ; . The torus is compact and the plane is not; this chapter uses the difference (the blob wraps, the seam is a lie of the chart) and Chapter 5 proves it.
Polygons as intersections of half-planes
The convention is what makes the rest of the appendix mechanical. List a convex polygon's vertices
counterclockwise; edge runs from to ; its outward unit normal is the edge
direction rotated clockwise by ; its half-plane is .
A point is inside iff every . Choset's figure F.2 is the triangle ,
, , whose negative half-planes intersect in the region , ,
— wait: . Read the signs: is the left half-plane. The
convention is easy to say and easy to get backwards, which is why halfPlaneOfEdge fixes it once and
every polygon in the module reads the same way.
Contacts, and the half-planes they define
DerivationDerivation 3 — Contact half-planes characterize collision for convex polygons (App. F.2)
Statement. For fixed , is a convex polygon, and iff every applicable contact's half-plane inequality holds. A Type A pair is applicable (eq. F.3) when
equivalently when the negated robot normal lies between the normals of the two obstacle edges meeting at ; its half-plane is (eq. F.4) . Symmetrically a Type B pair is applicable (F.5) when and , with half-plane (F.6) .
Step 1 — the boundary is where the bodies touch. If the interiors overlap at , every nearby is also in , so is interior to . Boundary configurations are therefore exactly those satisfying F.2: contact without overlap.
Step 2 — only Type A and Type B realize contact. Two convex polygons whose interiors are disjoint meet in a point or a segment of their boundaries; the meeting set contains a vertex of one body lying on an edge of the other (or an edge along an edge, which is both). That is the dichotomy.
Step 3 — each applicable pair gives a supporting half-plane. Take an applicable Type A pair. The condition F.3 says both obstacle edges leaving point into or along the robot normal's half-space, so the whole obstacle lies on the side ; the robot edge can touch the obstacle only at . Translating the robot, the contact persists while — a line in the -plane — and the robot overlaps the obstacle exactly on the side . So and the line is a supporting line of . Type B is the same with roles swapped. Non-applicable pairs give nothing: a robot edge whose normal does not point at cannot touch it from outside.
Step 4 — a convex set is the intersection of its supporting half-planes. is convex (it is a Minkowski difference of convex sets, Derivation 4), and every edge of its boundary is a contact line from Step 3, so equals the intersection of the applicable half-planes. Hence iff all applicable inequalities hold — and one failing inequality is a certificate of separation.
Why depends on only. The robot's normals rotate with it and do not move with it; applicability is a function of alone, and the star algorithm exploits this by sorting normals once per heading.
Nonconvex bodies. Decompose both into convex pieces , and test every pair; the union of the pairwise 's is the C-obstacle (the union property above).
Choset's figure F.7 shows a triangle robot and a four-sided obstacle in two placements and tabulates
seven inequalities: all satisfied in (a), one violated in (b). His figure carries no coordinates, so
here is the same table with numbers the check contacts pins. Robot , , ; obstacle the square with
counterclockwise; heading . Three Type A pairs and four Type B pairs are applicable:
| Contact pair | half-plane | at | at |
|---|---|---|---|
| yes | yes | ||
| yes | yes | ||
| yes | yes | ||
| yes | yes | ||
| yes | yes | ||
| yes | yes | ||
| yes | no |
At all seven hold: collision. At the obstacle's left edge (normal , through ) separates the robot's vertex from the square by : no collision, and that one line is the proof. The same check runs this test against Chapter 2's separating-axis box on seeded poses of random convex pairs — collisions among them — and finds no disagreement. The black box is now a verified box.
The star algorithm
DerivationDerivation 4 — The star algorithm computes QO(θ) = WO ⊖ R(0,0,θ) in linear time after sorting (App. F.3)
Statement. Negate the robot's edge normals; merge-sort them with the obstacle's by angle; scan the merged list once. Each time a negated robot normal falls between adjacent obstacle normals, a Type A contact of with the vertex between those normals is applicable, and it contributes the vertices and ; each time an obstacle normal falls between adjacent negated robot normals, a Type B contact of with is applicable and contributes and . The emitted points, in scan order, are the vertices of counterclockwise, and equals the Minkowski difference (eq. F.7) .
Step 1 — contacts persist along edges, and their extremes are vertices. Fix an applicable Type A pair. Slide the robot so that stays on edge : the configuration traces a segment of the boundary of . At one extreme coincides with , at the other with ; the reference point is then at and . These are two vertices of , and the edge between them is a translate of — with outward normal , since the robot is reflected through its reference point. Symmetrically a Type B pair gives the vertices and , joined by a translate of with normal .
Step 2 — angular order of normals is boundary order. The boundary of a convex polygon, walked counterclockwise, has outward normals that rotate monotonically through . The boundary of is made of translates of robot edges (normals ) and obstacle edges (normals ), so walking it counterclockwise visits those edges in the angular order of . Merging the two already-sorted lists is that walk.
Step 3 — the merged scan visits every applicable pair exactly once. Keep a current robot vertex and obstacle vertex , starting from the pair extreme in the direction. Consuming a robot entry means walking the (reflected) robot edge while stays put — a Type A contact — and advancing ; consuming an obstacle entry means walking while stays put — Type B — and advancing . Each entry is consumed once, so each applicable contact is emitted once and the scan closes on its start after steps. Cost: for a general sort, here because each list is already in order.
Step 4 — equality with the hull of all pairwise differences.
is a Minkowski sum of convex polygons, hence convex, and its extreme points are differences of
extreme points, so it is the convex hull of the points . The star algorithm's
vertices are among these points and its edges have the correct normals, so the two polygons coincide.
That hull is minkowskiBruteForce, the oracle the check star holds the scan to on seeded
convex pairs at random headings: identical polygons, worst area gap .
The preset, worked. Robot at ; obstacle . The
merged list is at , at , at , at , at
, at , at — seven entries, seven steps. The emitted points are
; two of them, and , lie on edges because a robot edge is parallel to an obstacle edge, and dropping collinear
vertices leaves the five the design doc names — —
with area : the square, two strips, and the reflected triangle
in the corner. Turn the robot to and no edges are parallel: seven vertices. The
check star pins all of it.
: the C-obstacle as stacked slices
A polygon that also rotates has , three-dimensional, and its C-obstacle is a solid. Choset's
App. F.4 visualizes it the only honest way there is: fix , compute the convex slice
by the star algorithm, and stack the slices along a vertical -axis — his figure
F.8, a triangle against a five-sided obstacle. The stack toggle of the Minkowski Studio does this
with slices. Each slice is exact; the stack is a picture. The gap between two slices is the same
honesty item as the arm's raster: a path that passes between and visits
configurations no slice tested. Chapter 8's visibility graphs and Chapter 10's trapezoidal
decomposition need exactly these polygonal slices, and import se2Slices for them.
GJK: distance as the distance from a Minkowski difference to the origin
DerivationDerivation 5 — GJK computes the distance between convex polytopes from support functions alone (App. F.5, Algorithm 23)
Statement. with (eqs. F.8–F.10); the minimizer is unique; and Gilbert, Johnson and Keerthi's iteration finds it using only the support value and support point (F.11–F.12), which never require to be built.
Step 1 — rewrite over the Minkowski difference. Every is a point of and every point of is some , so the two minima are the same number (F.10). The distance between two bodies is the distance from one convex set to the origin.
Step 2 — uniqueness. is convex (Derivation 4, Step 4) and is a strictly convex function away from the origin, so is a single point . The witness pair with need not be unique — two parallel edges facing each other have a whole segment of nearest pairs — and Choset says so after F.10. Uniqueness is of the difference.
Step 3 — the simplex iteration. Keep a working set of at most three points of (a point, a segment or a triangle in the plane) and let be the point of nearest the origin. Ask for its support point in direction : . If — no point of projects farther toward the origin than itself — then and the iteration stops (Algorithm 23, step 4). Otherwise is strictly closer to the origin in the direction , so contains points nearer than ; keep the edge of the old simplex that contained plus (step 6) and repeat. The distance to the origin strictly decreases, the working set never revisits a vertex of in exact arithmetic, and has at most vertices, so the loop terminates in at most that many steps. If the origin lands inside the triangle, and the bodies intersect.
Step 4 — support functions compose, so is never built. (F.13), and if , attain those maxima then (F.14). For a polygon the support point is the vertex with the largest dot product — a scan of vertices — so one GJK step costs and the whole query is linear with a tiny constant.
What the check pins. gjk runs the two-dimensional iteration against Chapter 2's brute-force
polygon distance on seeded convex pairs ( of them intersecting): the distances agree to
, the witnesses realize the distance, the intersection verdict matches the
separating-axis test, and no query needs more than iterations — well under the bound.
What parry2d adds. The engine the Rust crate delegates to runs this same loop with a
bounding-volume hierarchy to skip far pairs, EPA (expanding polytope algorithm) to recover the
penetration depth when the origin is inside , and shape-casting — GJK along a motion — for the
swept query of Chapter 2's Collision::swept. Choset's one-sentence extension to polyhedra is to
start from four points and keep a face instead of an edge.
Reach's Jacobian and its singularities
Chapter 2 gave Reach's forward kinematics , . Choset's §3.8 asks how velocities transform: as the arm moves, , where is the Jacobian of , also called its differential — the linear map that Chapter 5 will show is independent of the chart.
DerivationDerivation 6 — Reach's Jacobian, det J = L₁L₂ sin θ₂, and Example 3.8.1 (Choset §3.8)
Statement. The matrix above is ; its determinant is ; the arm is singular exactly when — straight or folded. Choset's numbers: , , give , , and .
Step 1 — differentiate entrywise. ; ;
;
. Read the columns: column is
the tip velocity when joint alone turns at unit rate — every link beyond joint swings about
it, so the column is the sum over those links of times the link's heading rotated by . That
sentence is the implementation of jacobian, and it serves any number of links.
Step 2 — the determinant. Write , , , . Then by the sine difference identity.
Step 3 — what rank loss means. At the two columns are parallel (both are multiples of when the arm is straight), so the image of is a line perpendicular to the arm. Motion along the arm is instantaneously impossible — the tip is at the edge of the annulus and can only move tangentially. The arm has not broken; one direction is unavailable, and a planner that asks for it asks for with : infinite joint speed. Chapters 17–18 meet this as a velocity limit that goes to zero; Chapter 21 as a rank condition.
Step 4 — Choset's numbers. At : , , , . So ,
, , ; ; and — the tip, at
, moves straight left when the shoulder alone turns, as his figure 3.21 draws. The check
jacobian asserts the four entries, the determinant and to , and holds jacobian
to central differences of Reach::fk on seeded arms to .
The velocity ellipse. The Jacobian Lens maps the unit circle of joint velocities through : an ellipse with semi-axes the singular values of and area — Yoshikawa's manipulability. At (any , any ): , , so , and the ellipse is 28.6 times longer than it is wide; at it is a segment. Exercise 4 asks for this number before the widget shows it.
Example 3.8.2, the polygon. A point fixed at on a body with configuration sits at , with Jacobian
a matrix of rank always: a rigid body in the plane has no singular configurations,
and is one-to-many because . The third column is the lever arm
rotated by , the velocity of the point under pure rotation. bodyPointJacobian implements it and
the same check differences bodyPointFk against it.
is the force map. Power is coordinate-free: for every , so . The widget's toggle shows a unit tip force becoming joint torques; Chapter 7 lifts workspace potentials through this map and Chapter 17 makes it dynamics.
The algorithm
Four algorithms, each exact for the bodies it covers, in the order the chapter met them. Choset numbers only the last; the others are App. F.2, F.3 and §3.2.2 written as procedures.
- In
- a Collision checker for the robot in its scene; a 2-D chart of Q with n₁ × n₂ cell centers q(i, j) and a flag per axis saying whether it wraps
- Out
- a BitGrid: cell (i, j) set iff q(i, j) ∈ QO; its connected components on the torus
- for , do
- — one query per cell center; the checker decides what "the robot" is
- components flood fill over 4-neighbors, wrapping across the seams the chart marks periodic — connectivity measured, not assumed
- return grid, components — a free cell is a free point: thicken the footprint by the cell's reach if free cells must certify paths between them
- In
- convex robot R (vertices r_i CCW, normals n^R_i), configuration q = (x, y, θ), convex obstacle W (vertices o_j, normals n^W_j)
- Out
- true iff R(q) ∩ W ≠ ∅
- for each robot edge and obstacle vertex : if and — applicable Type A (F.3)
- if return false — one violated half-plane (F.4) separates the bodies
- for each obstacle edge and robot vertex : if and — applicable Type B (F.5)
- if return false — (F.6)
- return true — every applicable inequality holds: (Derivation 3)
- In
- convex robot R with reference point at the origin, heading θ, convex obstacle W, both CCW
- Out
- the vertices of QO(θ) counterclockwise, each tagged with the contact that produced it
- rotate by ; compute normals ,
- the angles of ; the angles of ; each cyclically sorted — rotate so each starts at its smallest angle
- merge and by angle into one list of entries
- the robot edge first in , the obstacle edge first in ; emit — the pair extreme in
- for each entry of the merged list, in order:
- if it is robot edge : ; emit — Type A: slid along (Derivation 4, Step 1)
- else it is obstacle edge : ; emit — Type B: slid along
- drop the closing duplicate and any collinear vertex; return the list — equals , the brute-force oracle
- In
- convex polygons A, B through their support functions h, s; a tolerance
- Out
- d(A, B) = min_{z ∈ Z} ‖z‖, the unique minimizer z*, and a witness pair (a*, b*)
- for any direction — Choset seeds with three vertices of ; one support point works equally and never needs listed
- — nearest point of a point, segment or triangle; if the origin is inside the triangle return
- if , i.e. (within tolerance): return with the witnesses from 's barycentric weights
- — (F.14); the point of farthest toward the origin
- the edge (or point) of containing , plus
- ; go to 3
Line 4 of GJK is the step worth a sentence. ; if that maximum is attained at itself the value is , and no point of lies on the origin's side of the supporting line through perpendicular to . That line is a certificate — the same kind of certificate as the single violated half-plane in POLYGON-INTERSECTS, and the same geometry: a supporting half-plane of a convex set.
Implementation in Rust
Crates. nalgebra 0.35 for Point2, Vector2, Matrix2, Isometry2; parry2d 0.30 as
parry2d-f64, still the black box for GJK/EPA and shape-casting, now explained; collide and
robots from Chapter 2; rand 0.9 SmallRng for seeded polygon fields; proptest as a dev
dependency for the star-versus-brute-force property test; k and urdf-rs as test-only
dependencies that load a URDF of Reach and cross-check jacobian().
crates/cspace/
src/lib.rs
src/polygon.rs # App. F.1: CCW convex polygons, half-planes, normals derived never stored
src/contact.rs # App. F.2: applicable contacts, the half-plane test, the F.7 table
src/star.rs # App. F.3–F.4: star algorithm, brute-force oracle, inflate_disc, se2_slices
src/raster.rs # §3.2.2: FootprintAt<Q>, TorusGrid, BitGrid, rasterize_cobstacle
src/jacobian.rs # §3.8: jacobian, det_jacobian, body_point_jacobian
examples/reach_square.rs # the micro-example, printed
examples/workbench_map.rs # the integration lab
tests/{star_vs_brute.rs, jacobian_vs_k.rs, contact_table_f7.rs, black_box_agrees.rs}
web/lib/cspace/{polygon,star,gjk,raster,jacobian,examples}.ts # the port the widgets runPolygons and contacts
The polygon type enforces the one convention everything depends on — counterclockwise, convex — in its constructor, and derives normals on demand so they can never go stale after a transform.
use nalgebra::{Point2, Vector2};
use geom::Pose2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NotConvexOrNotCcw;
/// A convex polygon as Choset's App. F.1 writes it: an intersection of negative
/// half-planes, stored as its counterclockwise vertex list. Edge i runs r_i → r_{i+1}.
#[derive(Debug, Clone, PartialEq)]
pub struct Polygon { verts: Vec<Point2<f64>> }
impl Polygon {
/// The only constructor. Rejecting clockwise or nonconvex input here means no
/// function below ever has to re-check the winding it relies on.
pub fn new_ccw(verts: Vec<Point2<f64>>) -> Result<Self, NotConvexOrNotCcw> {
if verts.len() < 3 || signed_area(&verts) <= 0.0 || !is_convex(&verts) {
return Err(NotConvexOrNotCcw);
}
Ok(Polygon { verts })
}
pub fn len(&self) -> usize { self.verts.len() }
pub fn vertex(&self, i: usize) -> Point2<f64> { self.verts[i % self.verts.len()] }
/// Outward unit normal of edge i: the edge direction rotated clockwise by 90°.
/// Derived, never stored — a stored normal is stale after the next `transformed`.
pub fn edge_normal(&self, i: usize) -> Vector2<f64> {
let e = self.vertex(i + 1) - self.vertex(i);
Vector2::new(e.y, -e.x).normalize()
}
/// h_i(x, y) = n_i · ((x, y) − r_i): negative inside, by the F.1 convention.
pub fn half_plane(&self, i: usize, p: &Point2<f64>) -> f64 {
self.edge_normal(i).dot(&(p - self.vertex(i)))
}
/// r_i(x, y, θ) = R(θ) r_i + (x, y): the robot's vertices at configuration q.
pub fn transformed(&self, pose: &Pose2) -> Polygon {
Polygon { verts: self.verts.iter().map(|v| pose.act(v)).collect() }
}
}The contact test is written out so the reader sees the box's insides: applicability first (F.3, F.5),
then one half-plane value per applicable pair (F.4, F.6). It is and makes no attempt to
be fast; its job is to make parry2d's answer a verified answer.
use super::polygon::Polygon;
use geom::Pose2;
/// The two ways convex bodies touch without overlapping (Choset F.2, figure F.4).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContactKind {
/// Robot edge E^R_i contains obstacle vertex o_j.
TypeA { robot_edge: usize, obs_vertex: usize },
/// Obstacle edge E^W_j contains robot vertex r_i.
TypeB { obs_edge: usize, robot_vertex: usize },
}
/// Eqs. F.3 and F.5. Only θ enters: the robot's normals rotate with it, its position does not.
pub fn applicable_contacts(robot: &Polygon, theta: f64, obs: &Polygon) -> Vec<ContactKind> {
let r = robot.transformed(&Pose2::new(0.0, 0.0, theta));
let mut out = Vec::new();
for i in 0..r.len() {
let n = r.edge_normal(i);
for j in 0..obs.len() {
let (o, prev, next) = (obs.vertex(j), obs.vertex(j + obs.len() - 1), obs.vertex(j + 1));
// Both obstacle edges leaving o_j point along or away from the robot normal:
// −n^R_i lies between the normals of the edges adjacent to o_j.
if (prev - o).dot(&n) >= 0.0 && (next - o).dot(&n) >= 0.0 {
out.push(ContactKind::TypeA { robot_edge: i, obs_vertex: j });
}
}
}
for j in 0..obs.len() {
let n = obs.edge_normal(j);
for i in 0..r.len() {
let (v, prev, next) = (r.vertex(i), r.vertex(i + r.len() - 1), r.vertex(i + 1));
if (prev - v).dot(&n) >= 0.0 && (next - v).dot(&n) >= 0.0 {
out.push(ContactKind::TypeB { obs_edge: j, robot_vertex: i });
}
}
}
out
}
/// f^R_ij(q) = n^R_i(θ) · (o_j − r_i(q)) or f^W_ij(q) = n^W_j · (r_i(q) − o_j) (F.4, F.6).
pub fn contact_value(c: ContactKind, robot: &Polygon, q: &Pose2, obs: &Polygon) -> f64 {
let r = robot.transformed(q);
match c {
ContactKind::TypeA { robot_edge: i, obs_vertex: j } => r.edge_normal(i).dot(&(obs.vertex(j) - r.vertex(i))),
ContactKind::TypeB { obs_edge: j, robot_vertex: i } => obs.edge_normal(j).dot(&(r.vertex(i) - obs.vertex(j))),
}
}
/// App. F.2: q ∈ QO(θ) iff every applicable half-plane constraint holds. One violation
/// is a certificate of separation — the supporting line that keeps the bodies apart.
pub fn polygon_intersects(robot: &Polygon, q: &Pose2, obs: &Polygon) -> bool {
applicable_contacts(robot, q.theta, obs)
.into_iter()
.all(|c| contact_value(c, robot, q, obs) <= 0.0)
}The star algorithm, and its oracle
This is the chapter's owned artifact — Chapters 8 and 10 import it and never reimplement it — so it is written to be checked, not merely used. Every emitted vertex carries the contact that produced it, and the brute-force hull sits beside it in the same file.
use nalgebra::{Point2, Vector2};
use super::polygon::{convex_hull, Polygon};
use geom::Pose2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Source { Robot, Obstacle }
/// One entry of the merged normal list: whose edge, which edge, at what angle in [0, 2π).
#[derive(Debug, Clone, Copy)]
struct Normal { source: Source, edge: usize, angle: f64 }
fn angle_of(v: Vector2<f64>) -> f64 {
let a = v.y.atan2(v.x);
if a < 0.0 { a + std::f64::consts::TAU } else { a }
}
/// Cyclic rotation so a monotone-but-wrapped list starts at its smallest angle.
fn start_at_min(mut list: Vec<Normal>) -> Vec<Normal> {
let k = (0..list.len()).min_by(|&a, &b| list[a].angle.total_cmp(&list[b].angle)).unwrap_or(0);
list.rotate_left(k);
list
}
/// QO(θ) = WO ⊖ R(0, 0, θ) by Choset's star algorithm (App. F.3).
///
/// WO ⊖ R = WO ⊕ (−R), and the boundary of a Minkowski sum of convex polygons is
/// made of both bodies' edges in order of outward normal. −R's normals are R's
/// negated, so: negate, merge by angle, walk the merged list. Consuming a robot
/// entry slides E^R_i along the current obstacle vertex (Type A) and advances i;
/// consuming an obstacle entry slides E^W_j along the current robot vertex (Type B)
/// and advances j. Both lists are already sorted, so this is one merge: O(n_R + n_W).
pub fn star_algorithm(robot: &Polygon, theta: f64, obs: &Polygon) -> Polygon {
let r = robot.transformed(&Pose2::new(0.0, 0.0, theta));
let rs = start_at_min((0..r.len()).map(|i| Normal { source: Source::Robot, edge: i, angle: angle_of(-r.edge_normal(i)) }).collect());
let os = start_at_min((0..obs.len()).map(|j| Normal { source: Source::Obstacle, edge: j, angle: angle_of(obs.edge_normal(j)) }).collect());
// The merge.
let mut merged = Vec::with_capacity(rs.len() + os.len());
let (mut a, mut b) = (0, 0);
while a < rs.len() || b < os.len() {
if b >= os.len() || (a < rs.len() && rs[a].angle <= os[b].angle) { merged.push(rs[a]); a += 1; }
else { merged.push(os[b]); b += 1; }
}
// The scan. (i, j) start at the vertex pair extreme in +x; o_j − r_i is a vertex of QO(θ).
let (mut i, mut j) = (rs[0].edge, os[0].edge);
let diff = |j: usize, i: usize| Point2::from(obs.vertex(j) - r.vertex(i).coords);
let mut verts = vec![diff(j, i)];
for e in &merged {
match e.source {
// Type A: E^R_i slides along o_j, from o_j − r_i to o_j − r_{i+1}.
Source::Robot => { i = (e.edge + 1) % r.len(); verts.push(diff(j, i)); }
// Type B: E^W_j slides along r_i, from o_j − r_i to o_{j+1} − r_i.
Source::Obstacle => { j = (e.edge + 1) % obs.len(); verts.push(diff(j, i)); }
}
}
verts.pop(); // the scan closes on its start
Polygon::new_ccw(remove_collinear(verts)).expect("the merge-scan of convex inputs is convex and CCW")
}
/// Eq. F.7 read literally: the convex hull of all n_R · n_W pairwise differences o_j − r_i.
/// O(n_R n_W log) — wasteful in a planner, exactly right as the oracle `star_algorithm` must equal.
pub fn minkowski_bruteforce(robot: &Polygon, theta: f64, obs: &Polygon) -> Polygon {
let r = robot.transformed(&Pose2::new(0.0, 0.0, theta));
let pts: Vec<Point2<f64>> = (0..obs.len()).flat_map(|j| (0..r.len()).map(move |i| (j, i)))
.map(|(j, i)| Point2::from(obs.vertex(j) - r.vertex(i).coords)).collect();
convex_hull(&pts)
}
/// §3.2.1: WO ⊕ B_r for a convex polygon — edges offset by r, vertices replaced by arcs.
pub fn inflate_disc(obs: &Polygon, r: f64, arc_segments: usize) -> Polygon { /* as in the TypeScript port */ }
/// App. F.4: QO ⊂ SE(2) as n_θ exact slices QO(θ_k). The stack is a picture; each slice is a theorem.
pub fn se2_slices(robot: &Polygon, obs: &Polygon, n_theta: usize) -> Vec<(f64, Polygon)> {
(0..n_theta).map(|k| {
let theta = -std::f64::consts::PI + std::f64::consts::TAU * k as f64 / n_theta as f64;
(theta, star_algorithm(robot, theta, obs))
}).collect()
}The rasterizer: any footprint, any chart
Choset's pixel method, generic over the configuration type. The rasterizer never learns what an arm
is; it asks a Collision checker one question per cell. thicken is his §3.2.2 footnote made a
parameter — zero for the micro-example's exact segments, the capsule radius for the Workbench.
use collide::{Collision, Footprint};
/// Any robot that can produce a footprint from a chart point. Reach<2> implements it
/// over T2; a disc over R2; nothing in this file knows which.
pub trait FootprintAt<Q> {
fn footprint_at(&self, q: &Q) -> Footprint;
}
/// Cells centered on a uniform grid of [−π, π)²: with n = 360 the centers sit at integer
/// degrees, which is what makes "29 cells in the θ₁ = 0° row" a statement about this grid.
pub struct TorusGrid { pub n1: usize, pub n2: usize }
impl TorusGrid {
pub fn point(&self, i: usize, j: usize) -> T2 {
T2::new(-PI + i as f64 * TAU / self.n1 as f64, -PI + j as f64 * TAU / self.n2 as f64)
}
}
/// One bit per cell: set iff the cell's configuration is in QO.
pub struct BitGrid { pub n1: usize, pub n2: usize, bits: Vec<u64> }
impl BitGrid {
pub fn get(&self, i: usize, j: usize) -> bool { let k = i * self.n2 + j; self.bits[k / 64] >> (k % 64) & 1 == 1 }
pub fn row_count(&self, i: usize) -> usize { (0..self.n2).filter(|&j| self.get(i, j)).count() }
/// 4-connected components of the occupied (or free) cells with both seams identified:
/// connectivity *measured* before Chapter 5 states what the torus is.
pub fn components_torus(&self, occupied: bool) -> usize { /* flood fill, wrapping both axes */ }
}
/// Choset's pixel method. A free cell is a free *point*, not a free square: `thicken` > 0
/// inflates the footprint so a free cell certifies a free neighborhood. Conservative —
/// the planner is then resolution complete, never complete (his footnote 3, p. 47).
pub fn rasterize_cobstacle<Q>(
robot: &dyn FootprintAt<Q>, scene: &dyn Collision, grid: &TorusGrid,
chart: impl Fn(usize, usize) -> Q, thicken: f64,
) -> BitGrid {
let mut out = BitGrid::new(grid.n1, grid.n2);
for i in 0..grid.n1 {
for j in 0..grid.n2 {
let fp = robot.footprint_at(&chart(i, j)).thickened(thicken);
if scene.intersects(&fp) { out.set(i, j); }
}
}
out
}The Jacobian
use nalgebra::{Matrix2, Matrix2x3, Vector2};
use robots::{Reach, T2};
/// J(q) = ∂φ/∂q for the 2R arm (Choset Example 3.8.1). Column i is the tip velocity when
/// joint i alone turns at unit rate: every link beyond joint i swings about it, so the
/// column is Σ_{k ≥ i} L_k · (heading of link k rotated by 90°).
pub fn jacobian(reach: &Reach<2>, q: &T2) -> Matrix2<f64> {
let [l1, l2] = reach.lengths;
let (s1, c1) = q.th1().sin_cos();
let (s12, c12) = (q.th1() + q.th2()).sin_cos();
Matrix2::new(
-l1 * s1 - l2 * s12, -l2 * s12,
l1 * c1 + l2 * c12, l2 * c12,
)
}
/// det J = L₁ L₂ sin θ₂ — computed from the matrix; the identity is a test, not an assumption.
pub fn det_jacobian(reach: &Reach<2>, q: &T2) -> f64 { jacobian(reach, q).determinant() }
/// Singular iff J loses rank: for 2R, sin θ₂ = 0 — arm straight or folded.
pub fn is_singular(reach: &Reach<2>, q: &T2, tol: f64) -> bool { det_jacobian(reach, q).abs() <= tol }
/// Example 3.8.2: a point r on a planar rigid body. Rank 2 always — never singular.
pub fn body_point_jacobian(r: Vector2<f64>, q3: f64) -> Matrix2x3<f64> {
let (s, c) = q3.sin_cos();
Matrix2x3::new(1.0, 0.0, -r.x * s - r.y * c,
0.0, 1.0, r.x * c - r.y * s)
}The worked example as a program
use collide::Scene;
use cspace::{jacobian, rasterize_cobstacle, TorusGrid};
use nalgebra::{Point2, Vector2};
use robots::{Reach, T2};
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4};
fn main() {
let arm = Reach::<2>::choset(); // L₁ = L₂ = 1, link_radius = 0
let scene = Scene::empty().with_rect("the square", 1.4, -0.1, 1.6, 0.1);
let verdict = |q: T2| if scene.intersects(&arm.footprint_at(&q)) { "collision" } else { "free" };
let deg = |d: f64| d.to_radians();
println!("q=(0°,0°) {} q=(0°,13°) {} q=(0°,15°) {}", verdict(T2::new(0.0, 0.0)), verdict(T2::new(0.0, deg(13.0))), verdict(T2::new(0.0, deg(15.0))));
let tip = arm.fk(&T2::new(FRAC_PI_4, -FRAC_PI_2)).tip();
println!("q=(45°,-90°) {} (tip ({:.4}, {:.4}) inside) q=(90°,*) {} (nearest {:.3} > L2)",
verdict(T2::new(FRAC_PI_4, -FRAC_PI_2)), tip.x, tip.y, verdict(T2::new(FRAC_PI_2, 1.0)), (1.4f64.powi(2) + 0.9f64.powi(2)).sqrt());
let grid = TorusGrid { n1: 360, n2: 360 }; // cells centered at integer degrees
let bits = rasterize_cobstacle(&arm, &scene, &grid, |i, j| grid.point(i, j), 0.0);
let half = (0.1f64 / 0.4).atan();
println!("slice θ1=0°: {} occupied cells; analytic half-width atan(1/4) = {:.4} rad = {:.2}°", bits.row_count(180), half, half.to_degrees());
let rows: Vec<i64> = (0..360).filter(|&i| bits.row_count(i) > 0).map(|i| i as i64 - 180).collect();
println!("θ1 extent: rows 0..±{}° occupied, ±{}° empty; analytic bound {:.3} rad = {:.1}°", rows.iter().max().unwrap(), rows.iter().max().unwrap() + 1, cspace::THETA1_EXTENT, cspace::THETA1_EXTENT.to_degrees());
println!("occupied: {} of {} cells ({:.2} %)", bits.count(), 360 * 360, 100.0 * bits.count() as f64 / 129_600.0);
println!("components on T²: {} ({})", bits.components_torus(true), if bits.touches_seam() { "wraps" } else { "does not wrap" });
let q = T2::new(FRAC_PI_4, FRAC_PI_2);
let j = jacobian(&arm, &q);
let xdot = j * Vector2::new(1.0, 0.0);
println!("Jacobian at (45°,90°): [[{:.4}, {:.4}],[{:.0}, {:.4}]] det = {:.4} J·(1,0) = ({:.4}, {:.0})", j[(0,0)], j[(0,1)], j[(1,0)], j[(1,1)], j.determinant(), xdot.x, xdot.y);
}q=(0°,0°) collision q=(0°,13°) collision q=(0°,15°) free
q=(45°,-90°) collision (tip (1.4142, 0.0000) inside) q=(90°,*) free (nearest 1.664 > L2)
slice θ1=0°: 29 occupied cells; analytic half-width atan(1/4) = 0.2450 rad = 14.04°
θ1 extent: rows 0..±49° occupied, ±50° empty; analytic bound 0.864 rad = 49.5°
occupied: 2015 of 129600 cells (1.55 %)
components on T²: 1 (does not wrap)
Jacobian at (45°,90°): [[-1.4142, -0.7071],[0, -0.7071]] det = 1.0000 J·(1,0) = (-1.4142, 0)The tests beside the example are the chapter's contract. reach_square_samples asserts the five
hand-checked cells; slice_row_count_is_29 and extent_rows assert the raster counts;
slice_halfwidth_analytic bisects the exact segment test along and asserts
to ; jacobian_matches_choset_example asserts , and
to ; jacobian_is_fk_derivative checks central differences of
Reach::fk; jacobian_vs_k loads reach.urdf through the k crate and asserts agreement at
seeded to ; star_equals_bruteforce is a proptest over seeded convex
pairs and headings; star_triangle_square asserts the five vertices and area ; inflate_area
asserts ; contact_table_f7 reproduces the two seven-row tables above; and
black_box_agrees asserts Scene::intersects — parry2d's GJK — equals polygon_intersects on
seeded poses. The TypeScript port in web/lib/cspace/ runs the same seventeen checks;
every widget on this page is that port, and every number printed above was produced by it.
The web port hand-rolls GJK (gjk.ts) because the browser has no parry2d; the Rust crate does
not, because it has. Both are held to the same brute-force polygon distance. That is the division of
labor this book keeps throughout: geometry engines are delegated and explained, planners are
written. The cspace crate is the explanation.
Putting it together: a Workbench whose free space is in two pieces
The integration lab asks one question of the full Workbench: is Reach's free space connected? Load the
lab preset of the C-Space Morph. It takes the Workbench of Chapter 2 — block, post, shelf — with the
block slid toward the base to so that link 1 can reach it, and adds a clamp, a
m square at on the far side of the base. The arm has its cm capsule links,
and the raster runs at in the check (workbenchLabRaster) and in the widget.
Two things appear on the torus that the micro-example never showed. First, each near obstacle cuts a vertical band: link 1 collides with the block for and with the clamp for , and a link-1 collision does not care about , so those columns are forbidden in full. Second, the two bands together do what one band cannot: a single band removes an annulus from the torus and leaves a connected annulus behind, but two bands cut that annulus into two strips. The readout confirms it — has components ( of the torus), and has 2. Switch the lab obstacles off and is 1 again; the block's band alone, the post's and the shelf's blobs, all merge into one free component.
The lab's query is and , both free, both drawn, and
in different components. No planner in this book will connect them. Not Chapter 6's A*, which is
complete and will say so after expanding every free cell; not Chapter 7's potential field, which
will sit in a local minimum against a band; not Chapter 11's PRM, whose roadmap will have two
components forever. The first thing a planner must respect is the connectivity of , and this
page has now measured it, on a raster, before Chapter 5 says what connectivity means on a torus. The
check lab pins the band edges, the component counts with and without the clamp, the start and goal
labels, and the occupancy.
A last honesty item, because the lab makes it concrete. The Workbench as Chapter 2 ships it — block at , post, shelf — keeps every obstacle beyond link 1's reach: its nearest corner is m from the base and . Its is connected (the default Workbench preset shows one free component), and a design that promised a disconnected Workbench would have been wrong by exactly the amount this chapter teaches: whether a C-space is in one piece is not visible from the workspace picture. You have to compute it.
Exercises
- Foundation exerciseDifficulty 2 of 3Convexity, and why one checker serves a whole scene
Prove that the C-obstacle of a convex robot translating in the plane against a convex obstacle is convex (Choset problem 3.4), and that the union operator propagates from the workspace to the configuration space, (problem 3.5). Then say in one sentence why the second fact lets
rasterize_cobstacletake a wholeScenerather than one obstacle at a time, and why the first fact does not extend to a rotating robot. - Foundation exerciseDifficulty 2 of 3Count the degrees of freedom
Give for (a) two planar rigid bodies tied together by a taut rope of fixed length; (b) two planar rigid bodies connected rigidly by a bar; (c) a train on tracks, with and without the wheel angles, where the wheels roll without slipping; (d) a sheet of paper (Choset problem 3.2, parts b, c, e, i). For (c), name the constraint that is nonholonomic and explain why it removes no dimension.
- Conceptual exerciseDifficulty 2 of 3Predict the band, then verifyPredict first
In the C-Space Morph, load the micro-example preset (zero-thickness links, 1° raster) and drag the square to be centered at (0.95, 0). Before releasing: what appears on the torus, and how wide is it in θ₁?
- Conceptual exerciseDifficulty 2 of 3How flat is the ellipse?
In the Jacobian Lens set with . The velocity ellipse has semi-axes , the singular values of . Predict their ratio from two facts you can compute by hand — and — then read it off the widget. Then switch on and find a configuration where a unit tip force needs the largest joint torque; explain why it is the same at which the ellipse is longest.
σ₁ / σ₂ at θ₂ = 10°, L₁ = L₂ = 1 (any θ₁), to one decimal
- Practical exerciseDifficulty 3 of 3A three-dimensional raster
Implement
rasterize_cobstacleforReach<3>on — aBitGrid3withcomponents_toruswrapping all three axes — and render three -slices of the Workbench as images. Verify with a test that setting and taking the slice reproduces theReach<2>grid of this chapter bit for bit. Then measure: how many collision queries does on cost, and how long doesparry2dtake per query on your machine? That product is the reason Part III exists. - Practical exerciseDifficulty 3 of 3Choset problem 3.18: translation paths at some headings, none at others
Write a program that reads a convex robot (counterclockwise vertices, reference point at the origin) and a set of convex obstacles from files, takes a heading , computes for each obstacle with
star_algorithm, unions the slices, and flood-fills the complement to decide whether a translation path exists between two given points. Show one heading at which the path exists and one at which it does not. Cross-check the union againstrasterize_cobstacleon with aFootprintCheckerat the same heading: every occupied cell center must lie in some slice, and every cell center in a slice must be occupied.
References
- Choset, H., Lynch, K. M., Hutchinson, S., Kantor, G., Burgard, W., Kavraki, L. E., and Thrun, S. (2005) Principles of Robot Motion: Theory, Algorithms, and Implementations. MIT Press.link to Principles of Robot Motion: Theory, Algorithms, and Implementations (opens in a new tab)
§3.1–3.3 for configuration, C-obstacles, the pixel method and the dimension count; §3.7 for Table 3.1; §3.8 for the Jacobian and Examples 3.8.1–3.8.2; Appendix F in full — half-planes, Type A/B contacts, the star algorithm, SE(2) slices and GJK as Algorithm 23.
- Lozano-Pérez, T. (1983) Spatial Planning: A Configuration Space Approach. IEEE Transactions on Computers C-32(2), 108–120.doi:10.1109/TC.1983.1676196 (opens in a new tab)
The paper that made configuration space the planner's space and computed polygonal C-obstacles from vertex–edge contacts; the star algorithm and the SE(2) slices descend from it.
- Gilbert, E. G., Johnson, D. W., and Keerthi, S. S. (1988) A fast procedure for computing the distance between complex objects in three-dimensional space. IEEE Journal on Robotics and Automation 4(2), 193–203.doi:10.1109/56.2083 (opens in a new tab)
GJK — Choset's Algorithm 23 and Derivation 5: distance as the distance from the Minkowski difference to the origin, computed from support functions alone. What parry2d runs inside every distance query.
- Yoshikawa, T. (1985) Manipulability of Robotic Mechanisms. International Journal of Robotics Research 4(2), 3–9.doi:10.1177/027836498500400201 (opens in a new tab)
The velocity ellipsoid and the manipulability measure |det J| = σ₁σ₂ the Jacobian Lens draws; singularities as the ellipsoid's collapse.
- Latombe, J.-C. (1991) Robot Motion Planning. Kluwer Academic Publishers.doi:10.1007/978-1-4615-4022-9 (opens in a new tab)
Chapter 3 is the fullest classical treatment of configuration space obstacles for translating and rotating polygons, including the contact-based boundary construction this chapter follows.
- Lynch, K. M. and Park, F. C. (2017) Modern Robotics: Mechanics, Planning, and Control. Cambridge University Press.link to Modern Robotics: Mechanics, Planning, and Control (opens in a new tab)
Chapters 2 and 5: degrees of freedom by Grübler's formula and the manipulator Jacobian, manipulability ellipsoids and singularities, by one of Choset's co-authors.
- Montaut, L., Le Lidec, Q., Petrik, V., Sivic, J., and Carpentier, J. (2022) Collision Detection Accelerated: An Optimization Perspective. Robotics: Science and Systems (RSS).link to Collision Detection Accelerated: An Optimization Perspective (opens in a new tab)
GJK re-read as a Frank–Wolfe method on the Minkowski difference, with Nesterov acceleration — the modern view of Algorithm 23, and why its support-function formulation is the right one.
- Dimforge (2024) parry: 2D and 3D collision-detection library for the Rust programming language. Documentation and source.link to parry: 2D and 3D collision-detection library for the Rust programming language (opens in a new tab)
The engine behind crates/collide: GJK, EPA for penetration depth, a Bvh broad phase and shape-casting — what this chapter's Appendix F explains and the black_box_agrees test verifies.
