Skip to content

x_drive.hpp

xDrive() — the symmetric 45° X-drive, as a MatrixKinematics preset (the hybrid backend §13 #15: a holonomic linear drive is just a coefficient table).

This header declares 1 free function.

Extracted from include/shulib/kinematics/x_drive.hpp — this page is that header's documentation, reformatted, so it cannot disagree with the code. Prose about how to think about the API lives in the user guide; worked recipes live in the cookbook; this page is the complete, mechanical list of what exists.

Contents

xDrive

[[nodiscard]] inline MatrixKinematics xDrive(units::Length driveRadius)

The symmetric 45° X-drive as a MatrixKinematics coefficient table: four omnis in the canonical order front-left, back-left, back-right, front-right (body angles 45°, 135°, 225°, 315° CCW from +X forward). driveRadius is the centre-to-wheel distance in INCHES and must be > 0; it is the only geometry input, because every row is [±√2/2, ±√2/2, driveRadius]. Using the SAME √2/2 magnitude on all four wheels makes the coefficient columns exactly orthogonal, so forward() is an exact inverse rather than a least-squares fit. Consequences worth knowing at the call site: strafeAuthority is 1.0 (strafe is symmetric with forward), and a forward command at V asks each wheel for only V/√2. Returned BY VALUE — the caller owns it, and it must outlive every IKinematics reference taken to it. Motor→index mapping and per-motor polarity are the HAL/config layer's; this fixes only the canonical order.

free function, declared at include/shulib/kinematics/x_drive.hpp:52.

Design commentary, from the header

The header opens with the reasoning behind these shapes. It is reproduced here in full because a reference that only lists signatures teaches nobody why.

The header’s own reasoning — 31 lines
 xDrive() — the symmetric 45° X-drive, as a MatrixKinematics preset (the hybrid
 backend §13 #15: a holonomic linear drive is just a coefficient table).

 Geometry — body frame per F1 (frame.hpp): +X = forward, +Y = left, ω CCW-positive.
 Canonical wheel order, by body angle φ measured CCW from +X (forward):
   wheel 0: front-left  (position angle  45°)
   wheel 1: back-left   (             135°)
   wheel 2: back-right  (             225°)
   wheel 3: front-right (             315°)

 Each omni's powered-roll direction is tangential, d̂ = (-sinφ, cosφ). The rigorous
 projection  wheel_i = d̂_i·(v_body + ω×r_i)  yields the row  [-sinφ, cosφ, R]  =
 [±c, ±c, R]  with c = √2/2 and R = driveRadius (center-to-wheel distance), where the
 first column weights vx (forward) and the second vy (left). Using the SAME magnitude
 c on every wheel makes the coefficient columns exactly orthogonal, so
 MatrixKinematics::forward() is an exact inverse.

 Pinned-by-test consequences of this geometry:
   * Forward translation at V needs wheel surface speed V/√2 — i.e. the body
     moves √2× faster forward than the wheels spin (the classic X-drive property).
   * Pure rotation at ω drives EVERY wheel at R·ω with the same sign (all-equal =
     spin in place — the X-drive signature).
   * Strafe (+Y) is symmetric with forward (+X), so strafeAuthority() = 1.0.

 The physical motor→index mapping and per-motor polarity live in the HAL/config
 layer; kinematics only defines this canonical order.

 That the BUILT robot matches this idealized geometry (true 45° symmetric mounts,
 equal radii) is an A4-registered assumption until a physical drivetrain exists:
 A4 register HA-17 (docs/hardware-assumptions.md); R3/R5 settle it.

Last updated 2026-08-15 — published from the main release branch, commit fa545ff. This site tracks releases, so between them it can lag the repository — that lag is deliberate, and it is what keeps the development log off the public site. For the current state of the code, see the repository.