Theoretical Background: Trajectory Navigation
Module 5 Theory: Figure-Eight Trajectory and Pure Pursuit Path Following
Learning Objectives
- Derive the lemniscate parametric trajectory used in the figure-eight simulation
- Distinguish path-following Pure Pursuit from goal-seeking proportional control
- Understand arc-length parameterisation and lookahead-distance selection
- Interpret the navigation performance metrics produced by
useFigureEightNavigation - Analyse the
FigureEightTrajectorylibrary and its integration with the controller
5.5.1 The Lemniscate Trajectory
5.5.1.1 Parametric Definition
The figure-eight path used in the /figure-eight-nav page is a lemniscate of
Bernoulli scaled by amplitude . The standard Cartesian parametric form is:
where is the parameter and is the lobe half-amplitude in metres. As advances from to , the robot traces two complete lobes, returning exactly to the origin.
5.5.1.2 FigureEightConfig Parameters:
// useFigureEightNavigation.ts
interface FigureEightConfig {
amplitude: number // lobe half-width (m), default 3.0
speed: number // target cruise speed (m/s), default 0.4
waypointSpacing: number // arc-length between waypoints (m), default 0.1
loops: number // number of complete laps before stopping
}
5.5.1.3 Arc-Length Parameterisation
Raw parametric sampling produces uneven waypoint spacing because is
not constant along the lemniscate. FigureEightTrajectory.js corrects this by
numerically integrating arc length and resampling at uniform intervals :
This ensures the robot receives waypoints at constant geometric spacing regardless of the local curvature of the lemniscate.
5.5.1.4 Trajectory Metrics:
// FigureEightTrajectory.js
getLength() // total arc length (m) for the configured amplitude
getWaypoints() // array of {x, y, theta, curvature} objects
getEstimatedTime() // getLength() / config.speed (seconds)
5.5.2 Pure Pursuit for Curved Path Following
5.5.2.1 Distinction from Goal-Seeking Control
The proportional and PID controllers in useControlAlgorithms drive towards a
single static goal point. Pure Pursuit in the figure-eight context drives along a
precomputed path by continuously updating the target to a point ahead on the
path, producing smooth, anticipatory steering.
PurePursuitConfig Parameters:
// useFigureEightNavigation.ts
interface PurePursuitConfig {
lookaheadDistance: number // L — path lookahead (m), default 0.8
maxAngularVel: number // omega_max (rad/s), default 1.5
goalTolerance: number // waypoint switching radius (m), default 0.3
kp: number // proportional gain on heading error, default 1.2
}
5.5.2.2 Lookahead Point Selection
At each control cycle, the algorithm finds the waypoint on the precomputed trajectory that is closest to the lookahead distance ahead of the robot:
where is the -th waypoint. Unlike the single-goal version, always advances forward along the path and never regresses, preventing the robot from doubling back.
5.5.2.3 Steering Law
Given the lookahead target at world position , the heading error is:
The steering curvature and velocity commands are:
Equation (9) applies curvature-based speed reduction: the robot slows proportionally to the local path curvature , with damping coefficient . This prevents corner-cutting on the tight inner turns of the lemniscate.
5.5.2.4 Implementation in useFigureEightNavigation:
// useFigureEightNavigation.ts (simplified)
const updateNavigation = (robotPose: RobotPose) => {
const waypoints = pathWaypoints.value
const cfg = controllerConfig.value
// Find lookahead waypoint
let targetIdx = currentWaypointIndex.value
while (targetIdx < waypoints.length) {
const wp = waypoints[targetIdx]
const dx = wp.x - robotPose.x
const dy = wp.y - robotPose.y
const dist = Math.hypot(dx, dy)
if (dist >= cfg.lookaheadDistance) break
targetIdx++
}
// Wrap index for continuous looping
if (targetIdx >= waypoints.length) {
targetIdx = 0
completionCount.value++
}
const target = waypoints[targetIdx]
const dx = target.x - robotPose.x
const dy = target.y - robotPose.y
const theta_t = Math.atan2(dy, dx)
let e_theta = theta_t - robotPose.theta
while (e_theta > Math.PI) e_theta -= 2 * Math.PI
while (e_theta < -Math.PI) e_theta += 2 * Math.PI
// Curvature-scaled speed (Eqn 9)
const kappa = 2 * Math.sin(e_theta) / cfg.lookaheadDistance
const v = cfg.speed * Math.max(0.3, 1 - 0.4 * Math.abs(kappa))
const omega = Math.max(-cfg.maxAngularVel,
Math.min(cfg.maxAngularVel, cfg.kp * e_theta))
currentWaypointIndex.value = targetIdx
return { linear: v, angular: omega }
}
5.5.3 Lookahead Distance Selection
The lookahead distance is the primary tuning parameter:
| (m) | Behaviour | Suitable for |
|---|---|---|
| – | Tight tracking, corner-cutting | Low speed, high precision |
| (default) | Balanced smooth + accurate | General figure-eight use |
| – | Smooth, cuts sharp turns | High speed, large arenas |
The default m was chosen empirically for the 3 m amplitude lemniscate at m/s. As a rule of thumb: , which gives one second of forward anticipation.
5.5.4 Navigation Performance Metrics
useFigureEightNavigation accumulates a rich performance record throughout the
navigation run.
NavigationMetrics Interface:
// useFigureEightNavigation.ts
interface NavigationMetrics {
totalDistance: number // odometric path length (m)
averageSpeed: number // mean |v| (m/s)
maxDeviation: number // worst cross-track error (m)
averageDeviation: number // mean cross-track error (m)
completionTime: number // lap time in seconds
waypointsReached: number // total waypoint transitions
smoothnessScore: number // 0-1 acceleration smoothness
pathEfficiency: number // ideal_length / actual_distance
}
5.5.4.1 Cross-Track Error
The deviation from the ideal lemniscate path is computed as the perpendicular distance from the robot’s current position to the nearest path segment:
where is the unit tangent vector at waypoint . A smaller indicates tighter path adherence.
5.5.4.2 Path Efficiency
A value of means the robot travelled exactly the theoretical arc length. Values below indicate overshooting or oscillatory behaviour.
5.5.4.3 Smoothness Score
The smoothness score penalises rapid changes in angular velocity:
A score near 1 indicates consistent, gentle steering; a score near 0 indicates erratic steering caused by an overly aggressive or too-small .
5.5.5 Comparison: Goal-Seeking vs Path-Following Pure Pursuit
| Property | Goal-seeking (5_4) | Path-following (5_5) |
|---|---|---|
| Target type | Single static point | Moving lookahead on path |
| Path required | No | Yes (precomputed waypoints) |
| Handles curves | Poorly (stop-and-turn) | Well (anticipatory steering) |
| Lap counting | N/A | Native completion counter |
| Speed adaptation | Threshold-based | Curvature-proportional (Eqn 9) |
| Cross-track error | Not measured | Measured per cycle (Eqn 10) |
| Best suited to | /control page | /figure-eight-nav page |
Summary
Trajectory generation (Equations 1–3): the lemniscate provides a continuously curving path that exercises the full steering envelope of the differential-drive robot. Arc-length parameterisation ensures uniform waypoint spacing irrespective of local curvature.
Pure Pursuit path following (Equations 4–9): lookahead-distance selection anticipates the path shape; curvature-scaled speed reduction prevents corner-cutting; the single tuning parameter provides an interpretable accuracy-vs-smoothness trade-off.
Performance analysis (Equations 10–12): cross-track error, path efficiency, and smoothness score together characterise whether the controller is well-tuned for the given speed and amplitude combination.
Design Choices:
- Lemniscate over waypoint list: A parametric curve guarantees smoothness with a single amplitude parameter, avoiding the jagged paths that arise from manually placed waypoint lists on a grid.
- Default m: Two seconds of lookahead at 0.4 m/s provides enough anticipation to handle the tight inner turns without cutting across them.
- Curvature-scaled speed: Constant-speed Pure Pursuit cuts corners on high-curvature segments. Scaling by approximates the behaviour of a well-tuned trajectory planner without requiring a full kinodynamic solver.