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.

A Path is an ordered chain of BezierCurve segments that the robot traverses end-to-end as a single continuous route. Rather than commanding the robot to drive each segment in isolation, Path stitches them together by cumulative arc length, so higher-level components like VelocityProfile and the Follower can reason about the entire route with a single scalar: distance from path start.

Arc-length caching

Computing the arc length of a Bézier curve via Gauss–Legendre integration is accurate but not free. Path avoids repeating that work on every query by computing and storing each segment’s length once, at construction time:
// Path constructor — length of every segment is integrated exactly once
public Path(BezierCurve... curves) {
    this.curveLengths = new double[curves.length];
    double sum = 0;
    for (int i = 0; i < curves.length; i++) {
        curveLengths[i] = curves[i].getLength(1);  // cached here
        sum += curveLengths[i];
    }
    this.totalLength = sum;
}
After construction, getTotalLength() returns the sum in O(1) and all distance-walking loops read from curveLengths[] without touching the integrator again.

Building a two-segment path

BezierCurve seg1 = new BezierCurve(
    new Vector2d(0, 0),    new Vector2d(600, 0),
    new Vector2d(1200, 0), new Vector2d(1800, 0),
    new LinearInterpolator(0, Math.PI / 2)
);
BezierCurve seg2 = new BezierCurve(
    new Vector2d(1800, 0),    new Vector2d(1800, 600),
    new Vector2d(1800, 1200), new Vector2d(1800, 1800),
    new ConstantInterpolator(Math.PI / 2)
);
Path path = new Path(seg1, seg2);
Pass any number of BezierCurve objects — the varargs constructor accepts one or more segments.

Key API

getPointOnPath(double distance)

Returns a Pose2d (position + heading) at the given arc-length distance from the start of the path. Internally it walks the cached segment lengths to find which curve contains that distance, then calls getTAtDistance on that curve and delegates heading to the curve’s HeadingInterpolator:
// Where is the robot when it has traveled 2 400 mm along the path?
Pose2d target = path.getPointOnPath(2400);
double x       = target.getX();
double y       = target.getY();
double heading = target.getHeading(); // radians
The walk is O(n) in the number of segments. For the segment that contains distance, Odyssey calls curve.getTAtDistance(remainingDist) — the Brent-solver inversion — exactly once. Boundary behaviour:
  • distance ≤ 0 → returns the pose at the very start of the first segment.
  • distance ≥ totalLength → returns the pose at the very end of the last segment.

getDistanceOnPath(Vector2d pose)

Projects the robot’s measured position onto the path and returns a scalar distance from path start. This is the primary feedback signal used by the Follower. For each segment it calls getClosestT (two-phase coarse/fine search) to find the nearest point on that segment, then converts the winning t back to a cumulative arc-length distance using curve.getLength(t):
Vector2d robotPos = new Vector2d(robotX, robotY);
double distanceAlongPath = path.getDistanceOnPath(robotPos);
The segment that minimises squared Euclidean distance wins, so the projection is globally correct even when the robot strays off the path.

Additional queries

MethodDescription
getTotalLength()Total arc length of the entire path (mm)
getCurvatureFromPathDistance(double dist)Curvature κ at a given path distance — fed to VelocityProfile
getTangentFromPathDistance(double dist)Unit tangent vector at a given path distance
getCentripetalVectorPath(double dist)Centripetal acceleration direction at a given path distance

Heading seams between segments

Heading is controlled independently by each segment’s own HeadingInterpolator — there is no global heading interpolation across the whole Path. This means the robot’s commanded heading at the end of one segment and the start of the next are determined by two separate interpolator instances.
For a smooth heading transition at a seam, make sure the end heading of the outgoing segment matches the start heading of the incoming segment. For example, if seg1 uses new LinearInterpolator(0, Math.PI / 2) (ending at 90°), start seg2 with new ConstantInterpolator(Math.PI / 2) or new LinearInterpolator(Math.PI / 2, nextAngle) so the robot does not snap heading at the junction.
Because getPointOnPath always queries heading from the curve’s own interpolator, a well-matched seam produces a continuous heading profile with no discontinuity as the path crosses from one segment to the next.

Build docs developers (and LLMs) love