Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/saquer916/odyssey/llms.txt

Use this file to discover all available pages before exploring further.

Odyssey represents every path segment as a cubic Bézier curve — a smooth, C1-continuous polynomial that connects two field positions while letting you shape the curve through a pair of interior control-point handles. Because the curve is defined by a compact closed-form expression, evaluating a point on it is fast enough for the tight control loops FTC robots require, yet expressive enough to describe sweeping arcs, tight turns, and gentle S-bends with a single object.

The four control points

A cubic Bézier is fully described by four Vector2d control points:
PointRole
p0Start of the curve — the robot’s position when it enters this segment
p1First handle — pulls the curve away from p0 in a specific direction
p2Second handle — pulls the curve into p3 from a specific direction
p3End of the curve — the robot’s position when it leaves this segment
Moving p1 away from p0 along some direction sets the exit tangent of the curve; moving p2 away from p3 along some direction sets the entry tangent into the endpoint. That symmetry is what makes it straightforward to create smooth seams between consecutive segments. The cubic formula evaluated by getPoint(double t) is:
// B(t) = (1-t)³·p0 + 3t(1-t)²·p1 + 3t²(1-t)·p2 + t³·p3,   t ∈ [0, 1]
Vector2d point = curve.getPoint(t);

Constructing a BezierCurve

Pass the four control points and a HeadingInterpolator (which governs robot orientation — see Heading Interpolation):
BezierCurve curve = new BezierCurve(
    new Vector2d(0, 0),
    new Vector2d(0, 1500),
    new Vector2d(1500, 1500),
    new Vector2d(1500, 0),
    new TangentInterpolator()
);
All coordinates are in millimeters to match the FTC field coordinate system (the full field is approximately 3 658 mm × 3 658 mm). Using millimeters keeps curvature and velocity units consistent throughout the library.

Why arc-length parameterization matters

The raw parameter t does not correspond to equal distances along the curve. Near tight bends, equal steps in t produce small spatial steps; near straight sections the same t step covers much more distance. Driving the robot at constant increments of t would therefore cause uneven, jerky motion. Odyssey solves this in two stages:
1

Compute arc length with Gauss–Legendre integration

getLength(double b) integrates the magnitude of the tangent vector from 0 to b using Apache Commons Math’s IterativeLegendreGaussIntegrator (5-point rule, absolute and relative tolerances of 1e-9, up to 1 000 evaluations):
// How far along the curve is t = 0.6?
double lengthTo60pct = curve.getLength(0.6);

// Total arc length of the segment
double totalLength = curve.getLength(1);
2

Invert with a Brent solver

getTAtDistance(double dist) answers the inverse question: which t value corresponds to a given arc-length distance? It sets up the root-finding problem getLength(b) − dist = 0 and solves it with a BrentSolver (absolute and relative tolerances of 1e-9, up to 1 000 iterations):
// What is t when the robot has traveled 750 mm along this curve?
double t = curve.getTAtDistance(750);
With arc-length parameterization, the VelocityProfile can sample the path at uniform spatial steps and the Path can walk multi-segment routes by cumulative distance — all without ever thinking about t directly.

Closest-T projection

The Follower needs to know where on the curve the robot currently is. getClosestT(Vector2d pose) finds the parameter t whose point on the curve is nearest to the robot’s measured position. It runs a two-phase search:
1

Coarse scan (step = 0.01)

Sweeps t from 0 to 1 in steps of 0.01, evaluating the squared Euclidean distance at each step and recording the best t.
2

Fine refinement (step = 1e-5)

Narrows the search window to [bestT − 0.01, bestT + 0.01] and re-sweeps with a step of 1e-5 for sub-millimetre precision.
// Project robot position onto the curve
Vector2d robotPos = new Vector2d(robotX, robotY);
double t = curve.getClosestT(robotPos);
Vector2d closestPoint = curve.getPoint(t);
The overload getClosestT(Vector2d pose, double coarseStep, double fineStep) exposes both step sizes if you need to tune the trade-off between speed and accuracy.

Curvature for centripetal limiting

getCurvature(double t) computes the signed curvature κ at parameter t using the standard cross-product formula:
κ = |B′(t) × B″(t)| / |B′(t)|³
where B′ is the first derivative (tangent vector, from getTangentVector) and B″ is the second derivative (from getSecondDerivative). The VelocityProfile queries this value at every discretized distance step to cap robot speed at the centripetal limit sqrt(maxCentripetalAccel / κ), preventing the robot from sliding outward on tight bends.
// Curvature at the midpoint of the curve
double kappa = curve.getCurvature(0.5);

Build docs developers (and LLMs) love