diff --git a/wpimath/src/main/java/edu/wpi/first/math/estimator/DifferentialDrivePoseEstimator3d.java b/wpimath/src/main/java/edu/wpi/first/math/estimator/DifferentialDrivePoseEstimator3d.java
new file mode 100644
index 00000000000..470a05aa373
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/estimator/DifferentialDrivePoseEstimator3d.java
@@ -0,0 +1,159 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.math.estimator;
+
+import edu.wpi.first.math.Matrix;
+import edu.wpi.first.math.VecBuilder;
+import edu.wpi.first.math.geometry.Pose2d;
+import edu.wpi.first.math.geometry.Pose3d;
+import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.geometry.Rotation3d;
+import edu.wpi.first.math.geometry.Translation2d;
+import edu.wpi.first.math.geometry.Translation3d;
+import edu.wpi.first.math.kinematics.DifferentialDriveKinematics;
+import edu.wpi.first.math.kinematics.DifferentialDriveOdometry3d;
+import edu.wpi.first.math.kinematics.DifferentialDriveWheelPositions;
+import edu.wpi.first.math.numbers.N1;
+import edu.wpi.first.math.numbers.N4;
+
+/**
+ * This class wraps {@link DifferentialDriveOdometry3d Differential Drive Odometry} to fuse
+ * latency-compensated vision measurements with differential drive encoder measurements. It is
+ * intended to be a drop-in replacement for {@link DifferentialDriveOdometry3d}; in fact, if you
+ * never call {@link DifferentialDrivePoseEstimator3d#addVisionMeasurement} and only call {@link
+ * DifferentialDrivePoseEstimator3d#update} then this will behave exactly the same as
+ * DifferentialDriveOdometry3d. It is also intended to be an easy replacement for {@link
+ * DifferentialDrivePoseEstimator}, only requiring the addition of a standard deviation for Z and
+ * appropriate conversions between 2D and 3D versions of geometry classes. (See {@link
+ * Pose3d#Pose3d(Pose2d)}, {@link Rotation3d#Rotation3d(Rotation2d)}, {@link
+ * Translation3d#Translation3d(Translation2d)}, and {@link Pose3d#toPose2d()}.)
+ *
+ *
{@link DifferentialDrivePoseEstimator3d#update} should be called every robot loop.
+ *
+ *
{@link DifferentialDrivePoseEstimator3d#addVisionMeasurement} can be called as infrequently as
+ * you want; if you never call it then this class will behave exactly like regular encoder odometry.
+ */
+public class DifferentialDrivePoseEstimator3d
+ extends PoseEstimator3d {
+ /**
+ * Constructs a DifferentialDrivePoseEstimator3d with default standard deviations for the model
+ * and vision measurements.
+ *
+ * The default standard deviations of the model states are 0.02 meters for x, 0.02 meters for
+ * y, 0.02 meters for z, and 0.01 radians for angle. The default standard deviations of the vision
+ * measurements are 0.1 meters for x, 0.1 meters for y, 0.1 meters for z, and 0.1 radians for
+ * angle.
+ *
+ * @param kinematics A correctly-configured kinematics object for your drivetrain.
+ * @param gyroAngle The current gyro angle.
+ * @param leftDistanceMeters The distance traveled by the left encoder.
+ * @param rightDistanceMeters The distance traveled by the right encoder.
+ * @param initialPoseMeters The starting pose estimate.
+ */
+ public DifferentialDrivePoseEstimator3d(
+ DifferentialDriveKinematics kinematics,
+ Rotation3d gyroAngle,
+ double leftDistanceMeters,
+ double rightDistanceMeters,
+ Pose3d initialPoseMeters) {
+ this(
+ kinematics,
+ gyroAngle,
+ leftDistanceMeters,
+ rightDistanceMeters,
+ initialPoseMeters,
+ VecBuilder.fill(0.02, 0.02, 0.02, 0.01),
+ VecBuilder.fill(0.1, 0.1, 0.1, 0.1));
+ }
+
+ /**
+ * Constructs a DifferentialDrivePoseEstimator3d.
+ *
+ * @param kinematics A correctly-configured kinematics object for your drivetrain.
+ * @param gyroAngle The gyro angle of the robot.
+ * @param leftDistanceMeters The distance traveled by the left encoder.
+ * @param rightDistanceMeters The distance traveled by the right encoder.
+ * @param initialPoseMeters The estimated initial pose.
+ * @param stateStdDevs Standard deviations of the pose estimate (x position in meters, y position
+ * in meters, and heading in radians). Increase these numbers to trust your state estimate
+ * less.
+ * @param visionMeasurementStdDevs Standard deviations of the vision pose measurement (x position
+ * in meters, y position in meters, and heading in radians). Increase these numbers to trust
+ * the vision pose measurement less.
+ */
+ public DifferentialDrivePoseEstimator3d(
+ DifferentialDriveKinematics kinematics,
+ Rotation3d gyroAngle,
+ double leftDistanceMeters,
+ double rightDistanceMeters,
+ Pose3d initialPoseMeters,
+ Matrix stateStdDevs,
+ Matrix visionMeasurementStdDevs) {
+ super(
+ kinematics,
+ new DifferentialDriveOdometry3d(
+ gyroAngle, leftDistanceMeters, rightDistanceMeters, initialPoseMeters),
+ stateStdDevs,
+ visionMeasurementStdDevs);
+ }
+
+ /**
+ * Resets the robot's position on the field.
+ *
+ * The gyroscope angle does not need to be reset here on the user's robot code. The library
+ * automatically takes care of offsetting the gyro angle.
+ *
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param leftPositionMeters The distance traveled by the left encoder.
+ * @param rightPositionMeters The distance traveled by the right encoder.
+ * @param poseMeters The position on the field that your robot is at.
+ */
+ public void resetPosition(
+ Rotation3d gyroAngle,
+ double leftPositionMeters,
+ double rightPositionMeters,
+ Pose3d poseMeters) {
+ resetPosition(
+ gyroAngle,
+ new DifferentialDriveWheelPositions(leftPositionMeters, rightPositionMeters),
+ poseMeters);
+ }
+
+ /**
+ * Updates the pose estimator with wheel encoder and gyro information. This should be called every
+ * loop.
+ *
+ * @param gyroAngle The current gyro angle.
+ * @param distanceLeftMeters The total distance travelled by the left wheel in meters.
+ * @param distanceRightMeters The total distance travelled by the right wheel in meters.
+ * @return The estimated pose of the robot in meters.
+ */
+ public Pose3d update(
+ Rotation3d gyroAngle, double distanceLeftMeters, double distanceRightMeters) {
+ return update(
+ gyroAngle, new DifferentialDriveWheelPositions(distanceLeftMeters, distanceRightMeters));
+ }
+
+ /**
+ * Updates the pose estimator with wheel encoder and gyro information. This should be called every
+ * loop.
+ *
+ * @param currentTimeSeconds Time at which this method was called, in seconds.
+ * @param gyroAngle The current gyro angle.
+ * @param distanceLeftMeters The total distance travelled by the left wheel in meters.
+ * @param distanceRightMeters The total distance travelled by the right wheel in meters.
+ * @return The estimated pose of the robot in meters.
+ */
+ public Pose3d updateWithTime(
+ double currentTimeSeconds,
+ Rotation3d gyroAngle,
+ double distanceLeftMeters,
+ double distanceRightMeters) {
+ return updateWithTime(
+ currentTimeSeconds,
+ gyroAngle,
+ new DifferentialDriveWheelPositions(distanceLeftMeters, distanceRightMeters));
+ }
+}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/estimator/MecanumDrivePoseEstimator.java b/wpimath/src/main/java/edu/wpi/first/math/estimator/MecanumDrivePoseEstimator.java
index 59bbb3235cd..e33b30d09ca 100644
--- a/wpimath/src/main/java/edu/wpi/first/math/estimator/MecanumDrivePoseEstimator.java
+++ b/wpimath/src/main/java/edu/wpi/first/math/estimator/MecanumDrivePoseEstimator.java
@@ -18,7 +18,7 @@
* This class wraps {@link MecanumDriveOdometry Mecanum Drive Odometry} to fuse latency-compensated
* vision measurements with mecanum drive encoder distance measurements. It will correct for noisy
* measurements and encoder drift. It is intended to be a drop-in replacement for {@link
- * edu.wpi.first.math.kinematics.MecanumDriveOdometry}.
+ * MecanumDriveOdometry}.
*
*
{@link MecanumDrivePoseEstimator#update} should be called every robot loop.
*
diff --git a/wpimath/src/main/java/edu/wpi/first/math/estimator/MecanumDrivePoseEstimator3d.java b/wpimath/src/main/java/edu/wpi/first/math/estimator/MecanumDrivePoseEstimator3d.java
new file mode 100644
index 00000000000..e2f7acfdb0c
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/estimator/MecanumDrivePoseEstimator3d.java
@@ -0,0 +1,92 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.math.estimator;
+
+import edu.wpi.first.math.Matrix;
+import edu.wpi.first.math.VecBuilder;
+import edu.wpi.first.math.geometry.Pose2d;
+import edu.wpi.first.math.geometry.Pose3d;
+import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.geometry.Rotation3d;
+import edu.wpi.first.math.geometry.Translation2d;
+import edu.wpi.first.math.geometry.Translation3d;
+import edu.wpi.first.math.kinematics.MecanumDriveKinematics;
+import edu.wpi.first.math.kinematics.MecanumDriveOdometry3d;
+import edu.wpi.first.math.kinematics.MecanumDriveWheelPositions;
+import edu.wpi.first.math.numbers.N1;
+import edu.wpi.first.math.numbers.N4;
+
+/**
+ * This class wraps {@link MecanumDriveOdometry3d Mecanum Drive Odometry} to fuse
+ * latency-compensated vision measurements with mecanum drive encoder distance measurements. It will
+ * correct for noisy measurements and encoder drift. It is intended to be a drop-in replacement for
+ * {@link MecanumDriveOdometry3d}. It is also intended to be an easy replacement for {@link
+ * MecanumDrivePoseEstimator}, only requiring the addition of a standard deviation for Z and
+ * appropriate conversions between 2D and 3D versions of geometry classes. (See {@link
+ * Pose3d#Pose3d(Pose2d)}, {@link Rotation3d#Rotation3d(Rotation2d)}, {@link
+ * Translation3d#Translation3d(Translation2d)}, and {@link Pose3d#toPose2d()}.)
+ *
+ *
{@link MecanumDrivePoseEstimator3d#update} should be called every robot loop.
+ *
+ *
{@link MecanumDrivePoseEstimator3d#addVisionMeasurement} can be called as infrequently as you
+ * want; if you never call it, then this class will behave mostly like regular encoder odometry.
+ */
+public class MecanumDrivePoseEstimator3d extends PoseEstimator3d {
+ /**
+ * Constructs a MecanumDrivePoseEstimator3d with default standard deviations for the model and
+ * vision measurements.
+ *
+ * The default standard deviations of the model states are 0.1 meters for x, 0.1 meters for y,
+ * 0.1 meters for z, and 0.1 radians for angle. The default standard deviations of the vision
+ * measurements are 0.45 meters for x, 0.45 meters for y, 0.45 meters for z, and 0.45 radians for
+ * angle.
+ *
+ * @param kinematics A correctly-configured kinematics object for your drivetrain.
+ * @param gyroAngle The current gyro angle.
+ * @param wheelPositions The distances driven by each wheel.
+ * @param initialPoseMeters The starting pose estimate.
+ */
+ public MecanumDrivePoseEstimator3d(
+ MecanumDriveKinematics kinematics,
+ Rotation3d gyroAngle,
+ MecanumDriveWheelPositions wheelPositions,
+ Pose3d initialPoseMeters) {
+ this(
+ kinematics,
+ gyroAngle,
+ wheelPositions,
+ initialPoseMeters,
+ VecBuilder.fill(0.1, 0.1, 0.1, 0.1),
+ VecBuilder.fill(0.45, 0.45, 0.45, 0.45));
+ }
+
+ /**
+ * Constructs a MecanumDrivePoseEstimator3d.
+ *
+ * @param kinematics A correctly-configured kinematics object for your drivetrain.
+ * @param gyroAngle The current gyro angle.
+ * @param wheelPositions The distance measured by each wheel.
+ * @param initialPoseMeters The starting pose estimate.
+ * @param stateStdDevs Standard deviations of the pose estimate (x position in meters, y position
+ * in meters, and heading in radians). Increase these numbers to trust your state estimate
+ * less.
+ * @param visionMeasurementStdDevs Standard deviations of the vision pose measurement (x position
+ * in meters, y position in meters, and heading in radians). Increase these numbers to trust
+ * the vision pose measurement less.
+ */
+ public MecanumDrivePoseEstimator3d(
+ MecanumDriveKinematics kinematics,
+ Rotation3d gyroAngle,
+ MecanumDriveWheelPositions wheelPositions,
+ Pose3d initialPoseMeters,
+ Matrix stateStdDevs,
+ Matrix visionMeasurementStdDevs) {
+ super(
+ kinematics,
+ new MecanumDriveOdometry3d(kinematics, gyroAngle, wheelPositions, initialPoseMeters),
+ stateStdDevs,
+ visionMeasurementStdDevs);
+ }
+}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/estimator/PoseEstimator3d.java b/wpimath/src/main/java/edu/wpi/first/math/estimator/PoseEstimator3d.java
new file mode 100644
index 00000000000..9ce96f0243b
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/estimator/PoseEstimator3d.java
@@ -0,0 +1,424 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.math.estimator;
+
+import edu.wpi.first.math.MathSharedStore;
+import edu.wpi.first.math.MathUtil;
+import edu.wpi.first.math.Matrix;
+import edu.wpi.first.math.Nat;
+import edu.wpi.first.math.VecBuilder;
+import edu.wpi.first.math.geometry.Pose2d;
+import edu.wpi.first.math.geometry.Pose3d;
+import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.geometry.Rotation3d;
+import edu.wpi.first.math.geometry.Translation2d;
+import edu.wpi.first.math.geometry.Translation3d;
+import edu.wpi.first.math.geometry.Twist3d;
+import edu.wpi.first.math.interpolation.TimeInterpolatableBuffer;
+import edu.wpi.first.math.kinematics.Kinematics;
+import edu.wpi.first.math.kinematics.Odometry3d;
+import edu.wpi.first.math.numbers.N1;
+import edu.wpi.first.math.numbers.N4;
+import edu.wpi.first.math.numbers.N6;
+import java.util.NavigableMap;
+import java.util.Optional;
+import java.util.TreeMap;
+
+/**
+ * This class wraps {@link Odometry3d} to fuse latency-compensated vision measurements with encoder
+ * measurements. Robot code should not use this directly- Instead, use the particular type for your
+ * drivetrain (e.g., {@link DifferentialDrivePoseEstimator3d}). It is intended to be a drop-in
+ * replacement for {@link Odometry3d}; in fact, if you never call {@link
+ * PoseEstimator3d#addVisionMeasurement} and only call {@link PoseEstimator3d#update} then this will
+ * behave exactly the same as Odometry3d. It is also intended to be an easy replacement for {@link
+ * PoseEstimator}, only requiring the addition of a standard deviation for Z and appropriate
+ * conversions between 2D and 3D versions of geometry classes. (See {@link Pose3d#Pose3d(Pose2d)},
+ * {@link Rotation3d#Rotation3d(Rotation2d)}, {@link Translation3d#Translation3d(Translation2d)},
+ * and {@link Pose3d#toPose2d()}.)
+ *
+ * {@link PoseEstimator3d#update} should be called every robot loop.
+ *
+ *
{@link PoseEstimator3d#addVisionMeasurement} can be called as infrequently as you want; if you
+ * never call it then this class will behave exactly like regular encoder odometry.
+ *
+ * @param Wheel positions type.
+ */
+public class PoseEstimator3d {
+ private final Odometry3d m_odometry;
+ private final Matrix m_q = new Matrix<>(Nat.N4(), Nat.N1());
+ private final Matrix m_visionK = new Matrix<>(Nat.N6(), Nat.N6());
+
+ private static final double kBufferDuration = 1.5;
+ // Maps timestamps to odometry-only pose estimates
+ private final TimeInterpolatableBuffer m_odometryPoseBuffer =
+ TimeInterpolatableBuffer.createBuffer(kBufferDuration);
+ // Maps timestamps to vision updates
+ // Always contains one entry before the oldest entry in m_odometryPoseBuffer, unless there have
+ // been no vision measurements after the last reset
+ private final NavigableMap m_visionUpdates = new TreeMap<>();
+
+ private Pose3d m_poseEstimate;
+
+ /**
+ * Constructs a PoseEstimator3d.
+ *
+ * @param kinematics A correctly-configured kinematics object for your drivetrain.
+ * @param odometry A correctly-configured odometry object for your drivetrain.
+ * @param stateStdDevs Standard deviations of the pose estimate (x position in meters, y position
+ * in meters, z position in meters, and angle in radians). Increase these numbers to trust
+ * your state estimate less.
+ * @param visionMeasurementStdDevs Standard deviations of the vision pose measurement (x position
+ * in meters, y position in meters, z position in meters, and angle in radians). Increase
+ * these numbers to trust the vision pose measurement less.
+ */
+ @SuppressWarnings("PMD.UnusedFormalParameter")
+ public PoseEstimator3d(
+ Kinematics, T> kinematics,
+ Odometry3d odometry,
+ Matrix stateStdDevs,
+ Matrix visionMeasurementStdDevs) {
+ m_odometry = odometry;
+
+ m_poseEstimate = m_odometry.getPoseMeters();
+
+ for (int i = 0; i < 4; ++i) {
+ m_q.set(i, 0, stateStdDevs.get(i, 0) * stateStdDevs.get(i, 0));
+ }
+ setVisionMeasurementStdDevs(visionMeasurementStdDevs);
+ }
+
+ /**
+ * Sets the pose estimator's trust of global measurements. This might be used to change trust in
+ * vision measurements after the autonomous period, or to change trust as distance to a vision
+ * target increases.
+ *
+ * @param visionMeasurementStdDevs Standard deviations of the vision measurements. Increase these
+ * numbers to trust global measurements from vision less. This matrix is in the form [x, y, z,
+ * theta]ᵀ, with units in meters and radians.
+ */
+ public final void setVisionMeasurementStdDevs(Matrix visionMeasurementStdDevs) {
+ var r = new double[4];
+ for (int i = 0; i < 4; ++i) {
+ r[i] = visionMeasurementStdDevs.get(i, 0) * visionMeasurementStdDevs.get(i, 0);
+ }
+
+ // Solve for closed form Kalman gain for continuous Kalman filter with A = 0
+ // and C = I. See wpimath/algorithms.md.
+ for (int row = 0; row < 4; ++row) {
+ if (m_q.get(row, 0) == 0.0) {
+ m_visionK.set(row, row, 0.0);
+ } else {
+ m_visionK.set(
+ row, row, m_q.get(row, 0) / (m_q.get(row, 0) + Math.sqrt(m_q.get(row, 0) * r[row])));
+ }
+ }
+ // Fill in the gains for the other components of the rotation vector
+ double angle_gain = m_visionK.get(3, 3);
+ m_visionK.set(4, 4, angle_gain);
+ m_visionK.set(5, 5, angle_gain);
+ }
+
+ /**
+ * Resets the robot's position on the field.
+ *
+ * The gyroscope angle does not need to be reset here on the user's robot code. The library
+ * automatically takes care of offsetting the gyro angle.
+ *
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param wheelPositions The current encoder readings.
+ * @param poseMeters The position on the field that your robot is at.
+ */
+ public void resetPosition(Rotation3d gyroAngle, T wheelPositions, Pose3d poseMeters) {
+ // Reset state estimate and error covariance
+ m_odometry.resetPosition(gyroAngle, wheelPositions, poseMeters);
+ m_odometryPoseBuffer.clear();
+ m_visionUpdates.clear();
+ m_poseEstimate = m_odometry.getPoseMeters();
+ }
+
+ /**
+ * Resets the robot's pose.
+ *
+ * @param pose The pose to reset to.
+ */
+ public void resetPose(Pose3d pose) {
+ m_odometry.resetPose(pose);
+ m_odometryPoseBuffer.clear();
+ m_visionUpdates.clear();
+ m_poseEstimate = m_odometry.getPoseMeters();
+ }
+
+ /**
+ * Resets the robot's translation.
+ *
+ * @param translation The pose to translation to.
+ */
+ public void resetTranslation(Translation3d translation) {
+ m_odometry.resetTranslation(translation);
+ m_odometryPoseBuffer.clear();
+ m_visionUpdates.clear();
+ m_poseEstimate = m_odometry.getPoseMeters();
+ }
+
+ /**
+ * Resets the robot's rotation.
+ *
+ * @param rotation The rotation to reset to.
+ */
+ public void resetRotation(Rotation3d rotation) {
+ m_odometry.resetRotation(rotation);
+ m_odometryPoseBuffer.clear();
+ m_visionUpdates.clear();
+ m_poseEstimate = m_odometry.getPoseMeters();
+ }
+
+ /**
+ * Gets the estimated robot pose.
+ *
+ * @return The estimated robot pose in meters.
+ */
+ public Pose3d getEstimatedPosition() {
+ return m_poseEstimate;
+ }
+
+ /**
+ * Return the pose at a given timestamp, if the buffer is not empty.
+ *
+ * @param timestampSeconds The pose's timestamp in seconds.
+ * @return The pose at the given timestamp (or Optional.empty() if the buffer is empty).
+ */
+ public Optional sampleAt(double timestampSeconds) {
+ // Step 0: If there are no odometry updates to sample, skip.
+ if (m_odometryPoseBuffer.getInternalBuffer().isEmpty()) {
+ return Optional.empty();
+ }
+
+ // Step 1: Make sure timestamp matches the sample from the odometry pose buffer. (When sampling,
+ // the buffer will always use a timestamp between the first and last timestamps)
+ double oldestOdometryTimestamp = m_odometryPoseBuffer.getInternalBuffer().firstKey();
+ double newestOdometryTimestamp = m_odometryPoseBuffer.getInternalBuffer().lastKey();
+ timestampSeconds =
+ MathUtil.clamp(timestampSeconds, oldestOdometryTimestamp, newestOdometryTimestamp);
+
+ // Step 2: If there are no applicable vision updates, use the odometry-only information.
+ if (m_visionUpdates.isEmpty() || timestampSeconds < m_visionUpdates.firstKey()) {
+ return m_odometryPoseBuffer.getSample(timestampSeconds);
+ }
+
+ // Step 3: Get the latest vision update from before or at the timestamp to sample at.
+ double floorTimestamp = m_visionUpdates.floorKey(timestampSeconds);
+ var visionUpdate = m_visionUpdates.get(floorTimestamp);
+
+ // Step 4: Get the pose measured by odometry at the time of the sample.
+ var odometryEstimate = m_odometryPoseBuffer.getSample(timestampSeconds);
+
+ // Step 5: Apply the vision compensation to the odometry pose.
+ return odometryEstimate.map(odometryPose -> visionUpdate.compensate(odometryPose));
+ }
+
+ /** Removes stale vision updates that won't affect sampling. */
+ private void cleanUpVisionUpdates() {
+ // Step 0: If there are no odometry samples, skip.
+ if (m_odometryPoseBuffer.getInternalBuffer().isEmpty()) {
+ return;
+ }
+
+ // Step 1: Find the oldest timestamp that needs a vision update.
+ double oldestOdometryTimestamp = m_odometryPoseBuffer.getInternalBuffer().firstKey();
+
+ // Step 2: If there are no vision updates before that timestamp, skip.
+ if (m_visionUpdates.isEmpty() || oldestOdometryTimestamp < m_visionUpdates.firstKey()) {
+ return;
+ }
+
+ // Step 3: Find the newest vision update timestamp before or at the oldest timestamp.
+ double newestNeededVisionUpdateTimestamp = m_visionUpdates.floorKey(oldestOdometryTimestamp);
+
+ // Step 4: Remove all entries strictly before the newest timestamp we need.
+ m_visionUpdates.headMap(newestNeededVisionUpdateTimestamp, false).clear();
+ }
+
+ /**
+ * Adds a vision measurement to the Kalman Filter. This will correct the odometry pose estimate
+ * while still accounting for measurement noise.
+ *
+ * This method can be called as infrequently as you want, as long as you are calling {@link
+ * PoseEstimator3d#update} every loop.
+ *
+ *
To promote stability of the pose estimate and make it robust to bad vision data, we
+ * recommend only adding vision measurements that are already within one meter or so of the
+ * current pose estimate.
+ *
+ * @param visionRobotPoseMeters The pose of the robot as measured by the vision camera.
+ * @param timestampSeconds The timestamp of the vision measurement in seconds. Note that if you
+ * don't use your own time source by calling {@link
+ * PoseEstimator3d#updateWithTime(double,Rotation3d,Object)} then you must use a timestamp
+ * with an epoch since FPGA startup (i.e., the epoch of this timestamp is the same epoch as
+ * {@link edu.wpi.first.wpilibj.Timer#getFPGATimestamp()}.) This means that you should use
+ * {@link edu.wpi.first.wpilibj.Timer#getFPGATimestamp()} as your time source or sync the
+ * epochs.
+ */
+ public void addVisionMeasurement(Pose3d visionRobotPoseMeters, double timestampSeconds) {
+ // Step 0: If this measurement is old enough to be outside the pose buffer's timespan, skip.
+ if (m_odometryPoseBuffer.getInternalBuffer().isEmpty()
+ || m_odometryPoseBuffer.getInternalBuffer().lastKey() - kBufferDuration
+ > timestampSeconds) {
+ return;
+ }
+
+ // Step 1: Clean up any old entries
+ cleanUpVisionUpdates();
+
+ // Step 2: Get the pose measured by odometry at the moment the vision measurement was made.
+ var odometrySample = m_odometryPoseBuffer.getSample(timestampSeconds);
+
+ if (odometrySample.isEmpty()) {
+ return;
+ }
+
+ // Step 3: Get the vision-compensated pose estimate at the moment the vision measurement was
+ // made.
+ var visionSample = sampleAt(timestampSeconds);
+
+ if (visionSample.isEmpty()) {
+ return;
+ }
+
+ // Step 4: Measure the twist between the old pose estimate and the vision pose.
+ var twist = visionSample.get().log(visionRobotPoseMeters);
+
+ // Step 5: We should not trust the twist entirely, so instead we scale this twist by a Kalman
+ // gain matrix representing how much we trust vision measurements compared to our current pose.
+ var k_times_twist =
+ m_visionK.times(
+ VecBuilder.fill(twist.dx, twist.dy, twist.dz, twist.rx, twist.ry, twist.rz));
+
+ // Step 6: Convert back to Twist3d.
+ var scaledTwist =
+ new Twist3d(
+ k_times_twist.get(0, 0),
+ k_times_twist.get(1, 0),
+ k_times_twist.get(2, 0),
+ k_times_twist.get(3, 0),
+ k_times_twist.get(4, 0),
+ k_times_twist.get(5, 0));
+
+ // Step 7: Calculate and record the vision update.
+ var visionUpdate = new VisionUpdate(visionSample.get().exp(scaledTwist), odometrySample.get());
+ m_visionUpdates.put(timestampSeconds, visionUpdate);
+
+ // Step 8: Remove later vision measurements. (Matches previous behavior)
+ m_visionUpdates.tailMap(timestampSeconds, false).entrySet().clear();
+
+ // Step 9: Update latest pose estimate. Since we cleared all updates after this vision update,
+ // it's guaranteed to be the latest vision update.
+ m_poseEstimate = visionUpdate.compensate(m_odometry.getPoseMeters());
+ }
+
+ /**
+ * Adds a vision measurement to the Kalman Filter. This will correct the odometry pose estimate
+ * while still accounting for measurement noise.
+ *
+ *
This method can be called as infrequently as you want, as long as you are calling {@link
+ * PoseEstimator3d#update} every loop.
+ *
+ *
To promote stability of the pose estimate and make it robust to bad vision data, we
+ * recommend only adding vision measurements that are already within one meter or so of the
+ * current pose estimate.
+ *
+ *
Note that the vision measurement standard deviations passed into this method will continue
+ * to apply to future measurements until a subsequent call to {@link
+ * PoseEstimator3d#setVisionMeasurementStdDevs(Matrix)} or this method.
+ *
+ * @param visionRobotPoseMeters The pose of the robot as measured by the vision camera.
+ * @param timestampSeconds The timestamp of the vision measurement in seconds. Note that if you
+ * don't use your own time source by calling {@link #updateWithTime}, then you must use a
+ * timestamp with an epoch since FPGA startup (i.e., the epoch of this timestamp is the same
+ * epoch as {@link edu.wpi.first.wpilibj.Timer#getFPGATimestamp()}). This means that you
+ * should use {@link edu.wpi.first.wpilibj.Timer#getFPGATimestamp()} as your time source in
+ * this case.
+ * @param visionMeasurementStdDevs Standard deviations of the vision pose measurement (x position
+ * in meters, y position in meters, z position in meters, and angle in radians). Increase
+ * these numbers to trust the vision pose measurement less.
+ */
+ public void addVisionMeasurement(
+ Pose3d visionRobotPoseMeters,
+ double timestampSeconds,
+ Matrix visionMeasurementStdDevs) {
+ setVisionMeasurementStdDevs(visionMeasurementStdDevs);
+ addVisionMeasurement(visionRobotPoseMeters, timestampSeconds);
+ }
+
+ /**
+ * Updates the pose estimator with wheel encoder and gyro information. This should be called every
+ * loop.
+ *
+ * @param gyroAngle The current gyro angle.
+ * @param wheelPositions The current encoder readings.
+ * @return The estimated pose of the robot in meters.
+ */
+ public Pose3d update(Rotation3d gyroAngle, T wheelPositions) {
+ return updateWithTime(MathSharedStore.getTimestamp(), gyroAngle, wheelPositions);
+ }
+
+ /**
+ * Updates the pose estimator with wheel encoder and gyro information. This should be called every
+ * loop.
+ *
+ * @param currentTimeSeconds Time at which this method was called, in seconds.
+ * @param gyroAngle The current gyro angle.
+ * @param wheelPositions The current encoder readings.
+ * @return The estimated pose of the robot in meters.
+ */
+ public Pose3d updateWithTime(double currentTimeSeconds, Rotation3d gyroAngle, T wheelPositions) {
+ var odometryEstimate = m_odometry.update(gyroAngle, wheelPositions);
+
+ m_odometryPoseBuffer.addSample(currentTimeSeconds, odometryEstimate);
+
+ if (m_visionUpdates.isEmpty()) {
+ m_poseEstimate = odometryEstimate;
+ } else {
+ var visionUpdate = m_visionUpdates.get(m_visionUpdates.lastKey());
+ m_poseEstimate = visionUpdate.compensate(odometryEstimate);
+ }
+
+ return getEstimatedPosition();
+ }
+
+ /**
+ * Represents a vision update record. The record contains the vision-compensated pose estimate as
+ * well as the corresponding odometry pose estimate.
+ */
+ private static final class VisionUpdate {
+ // The vision-compensated pose estimate.
+ private final Pose3d visionPose;
+
+ // The pose estimated based solely on odometry.
+ private final Pose3d odometryPose;
+
+ /**
+ * Constructs a vision update record with the specified parameters.
+ *
+ * @param visionPose The vision-compensated pose estimate.
+ * @param odometryPose The pose estimate based solely on odometry.
+ */
+ private VisionUpdate(Pose3d visionPose, Pose3d odometryPose) {
+ this.visionPose = visionPose;
+ this.odometryPose = odometryPose;
+ }
+
+ /**
+ * Returns the vision-compensated version of the pose. Specifically, changes the pose from being
+ * relative to this record's odometry pose to being relative to this record's vision pose.
+ *
+ * @param pose The pose to compensate.
+ * @return The compensated pose.
+ */
+ public Pose3d compensate(Pose3d pose) {
+ var delta = pose.minus(this.odometryPose);
+ return this.visionPose.plus(delta);
+ }
+ }
+}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/estimator/SwerveDrivePoseEstimator.java b/wpimath/src/main/java/edu/wpi/first/math/estimator/SwerveDrivePoseEstimator.java
index 42901670045..495cd893fc6 100644
--- a/wpimath/src/main/java/edu/wpi/first/math/estimator/SwerveDrivePoseEstimator.java
+++ b/wpimath/src/main/java/edu/wpi/first/math/estimator/SwerveDrivePoseEstimator.java
@@ -17,7 +17,7 @@
/**
* This class wraps {@link SwerveDriveOdometry Swerve Drive Odometry} to fuse latency-compensated
* vision measurements with swerve drive encoder distance measurements. It is intended to be a
- * drop-in replacement for {@link edu.wpi.first.math.kinematics.SwerveDriveOdometry}.
+ * drop-in replacement for {@link SwerveDriveOdometry}.
*
* {@link SwerveDrivePoseEstimator#update} should be called every robot loop.
*
diff --git a/wpimath/src/main/java/edu/wpi/first/math/estimator/SwerveDrivePoseEstimator3d.java b/wpimath/src/main/java/edu/wpi/first/math/estimator/SwerveDrivePoseEstimator3d.java
new file mode 100644
index 00000000000..668f45c410e
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/estimator/SwerveDrivePoseEstimator3d.java
@@ -0,0 +1,107 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.math.estimator;
+
+import edu.wpi.first.math.Matrix;
+import edu.wpi.first.math.VecBuilder;
+import edu.wpi.first.math.geometry.Pose2d;
+import edu.wpi.first.math.geometry.Pose3d;
+import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.geometry.Rotation3d;
+import edu.wpi.first.math.geometry.Translation2d;
+import edu.wpi.first.math.geometry.Translation3d;
+import edu.wpi.first.math.kinematics.SwerveDriveKinematics;
+import edu.wpi.first.math.kinematics.SwerveDriveOdometry3d;
+import edu.wpi.first.math.kinematics.SwerveModulePosition;
+import edu.wpi.first.math.numbers.N1;
+import edu.wpi.first.math.numbers.N4;
+
+/**
+ * This class wraps {@link SwerveDriveOdometry3d Swerve Drive Odometry} to fuse latency-compensated
+ * vision measurements with swerve drive encoder distance measurements. It is intended to be a
+ * drop-in replacement for {@link SwerveDriveOdometry3d}. It is also intended to be an easy
+ * replacement for {@link SwerveDrivePoseEstimator}, only requiring the addition of a standard
+ * deviation for Z and appropriate conversions between 2D and 3D versions of geometry classes. (See
+ * {@link Pose3d#Pose3d(Pose2d)}, {@link Rotation3d#Rotation3d(Rotation2d)}, {@link
+ * Translation3d#Translation3d(Translation2d)}, and {@link Pose3d#toPose2d()}.)
+ *
+ *
{@link SwerveDrivePoseEstimator3d#update} should be called every robot loop.
+ *
+ *
{@link SwerveDrivePoseEstimator3d#addVisionMeasurement} can be called as infrequently as you
+ * want; if you never call it, then this class will behave as regular encoder odometry.
+ */
+public class SwerveDrivePoseEstimator3d extends PoseEstimator3d {
+ private final int m_numModules;
+
+ /**
+ * Constructs a SwerveDrivePoseEstimator3d with default standard deviations for the model and
+ * vision measurements.
+ *
+ * The default standard deviations of the model states are 0.1 meters for x, 0.1 meters for y,
+ * 0.1 meters for z, and 0.1 radians for angle. The default standard deviations of the vision
+ * measurements are 0.9 meters for x, 0.9 meters for y, 0.9 meters for z, and 0.9 radians for
+ * angle.
+ *
+ * @param kinematics A correctly-configured kinematics object for your drivetrain.
+ * @param gyroAngle The current gyro angle.
+ * @param modulePositions The current distance measurements and rotations of the swerve modules.
+ * @param initialPoseMeters The starting pose estimate.
+ */
+ public SwerveDrivePoseEstimator3d(
+ SwerveDriveKinematics kinematics,
+ Rotation3d gyroAngle,
+ SwerveModulePosition[] modulePositions,
+ Pose3d initialPoseMeters) {
+ this(
+ kinematics,
+ gyroAngle,
+ modulePositions,
+ initialPoseMeters,
+ VecBuilder.fill(0.1, 0.1, 0.1, 0.1),
+ VecBuilder.fill(0.9, 0.9, 0.9, 0.9));
+ }
+
+ /**
+ * Constructs a SwerveDrivePoseEstimator3d.
+ *
+ * @param kinematics A correctly-configured kinematics object for your drivetrain.
+ * @param gyroAngle The current gyro angle.
+ * @param modulePositions The current distance and rotation measurements of the swerve modules.
+ * @param initialPoseMeters The starting pose estimate.
+ * @param stateStdDevs Standard deviations of the pose estimate (x position in meters, y position
+ * in meters, and heading in radians). Increase these numbers to trust your state estimate
+ * less.
+ * @param visionMeasurementStdDevs Standard deviations of the vision pose measurement (x position
+ * in meters, y position in meters, and heading in radians). Increase these numbers to trust
+ * the vision pose measurement less.
+ */
+ public SwerveDrivePoseEstimator3d(
+ SwerveDriveKinematics kinematics,
+ Rotation3d gyroAngle,
+ SwerveModulePosition[] modulePositions,
+ Pose3d initialPoseMeters,
+ Matrix stateStdDevs,
+ Matrix visionMeasurementStdDevs) {
+ super(
+ kinematics,
+ new SwerveDriveOdometry3d(kinematics, gyroAngle, modulePositions, initialPoseMeters),
+ stateStdDevs,
+ visionMeasurementStdDevs);
+
+ m_numModules = modulePositions.length;
+ }
+
+ @Override
+ public Pose3d updateWithTime(
+ double currentTimeSeconds, Rotation3d gyroAngle, SwerveModulePosition[] wheelPositions) {
+ if (wheelPositions.length != m_numModules) {
+ throw new IllegalArgumentException(
+ "Number of modules is not consistent with number of wheel locations provided in "
+ + "constructor");
+ }
+
+ return super.updateWithTime(currentTimeSeconds, gyroAngle, wheelPositions);
+ }
+}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/kinematics/DifferentialDriveOdometry3d.java b/wpimath/src/main/java/edu/wpi/first/math/kinematics/DifferentialDriveOdometry3d.java
new file mode 100644
index 00000000000..7a5769a5777
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/kinematics/DifferentialDriveOdometry3d.java
@@ -0,0 +1,149 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.math.kinematics;
+
+import static edu.wpi.first.units.Units.Meters;
+
+import edu.wpi.first.math.MathSharedStore;
+import edu.wpi.first.math.MathUsageId;
+import edu.wpi.first.math.geometry.Pose2d;
+import edu.wpi.first.math.geometry.Pose3d;
+import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.geometry.Rotation3d;
+import edu.wpi.first.math.geometry.Translation2d;
+import edu.wpi.first.math.geometry.Translation3d;
+import edu.wpi.first.units.measure.Distance;
+
+/**
+ * Class for differential drive odometry. Odometry allows you to track the robot's position on the
+ * field over the course of a match using readings from 2 encoders and a gyroscope.
+ *
+ * This class is meant to be an easy replacement for {@link DifferentialDriveOdometry}, only
+ * requiring the addition of appropriate conversions between 2D and 3D versions of geometry classes.
+ * (See {@link Pose3d#Pose3d(Pose2d)}, {@link Rotation3d#Rotation3d(Rotation2d)}, {@link
+ * Translation3d#Translation3d(Translation2d)}, and {@link Pose3d#toPose2d()}.)
+ *
+ *
Teams can use odometry during the autonomous period for complex tasks like path following.
+ * Furthermore, odometry can be used for latency compensation when using computer-vision systems.
+ *
+ *
It is important that you reset your encoders to zero before using this class. Any subsequent
+ * pose resets also require the encoders to be reset to zero.
+ */
+public class DifferentialDriveOdometry3d extends Odometry3d {
+ /**
+ * Constructs a DifferentialDriveOdometry3d object.
+ *
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param leftDistanceMeters The distance traveled by the left encoder.
+ * @param rightDistanceMeters The distance traveled by the right encoder.
+ * @param initialPoseMeters The starting position of the robot on the field.
+ */
+ public DifferentialDriveOdometry3d(
+ Rotation3d gyroAngle,
+ double leftDistanceMeters,
+ double rightDistanceMeters,
+ Pose3d initialPoseMeters) {
+ super(
+ new DifferentialDriveKinematics(1),
+ gyroAngle,
+ new DifferentialDriveWheelPositions(leftDistanceMeters, rightDistanceMeters),
+ initialPoseMeters);
+ MathSharedStore.reportUsage(MathUsageId.kOdometry_DifferentialDrive, 1);
+ }
+
+ /**
+ * Constructs a DifferentialDriveOdometry3d object.
+ *
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param leftDistance The distance traveled by the left encoder.
+ * @param rightDistance The distance traveled by the right encoder.
+ * @param initialPoseMeters The starting position of the robot on the field.
+ */
+ public DifferentialDriveOdometry3d(
+ Rotation3d gyroAngle,
+ Distance leftDistance,
+ Distance rightDistance,
+ Pose3d initialPoseMeters) {
+ this(gyroAngle, leftDistance.in(Meters), rightDistance.in(Meters), initialPoseMeters);
+ }
+
+ /**
+ * Constructs a DifferentialDriveOdometry3d object.
+ *
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param leftDistanceMeters The distance traveled by the left encoder.
+ * @param rightDistanceMeters The distance traveled by the right encoder.
+ */
+ public DifferentialDriveOdometry3d(
+ Rotation3d gyroAngle, double leftDistanceMeters, double rightDistanceMeters) {
+ this(gyroAngle, leftDistanceMeters, rightDistanceMeters, Pose3d.kZero);
+ }
+
+ /**
+ * Constructs a DifferentialDriveOdometry3d object.
+ *
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param leftDistance The distance traveled by the left encoder.
+ * @param rightDistance The distance traveled by the right encoder.
+ */
+ public DifferentialDriveOdometry3d(
+ Rotation3d gyroAngle, Distance leftDistance, Distance rightDistance) {
+ this(gyroAngle, leftDistance, rightDistance, Pose3d.kZero);
+ }
+
+ /**
+ * Resets the robot's position on the field.
+ *
+ * The gyroscope angle does not need to be reset here on the user's robot code. The library
+ * automatically takes care of offsetting the gyro angle.
+ *
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param leftDistanceMeters The distance traveled by the left encoder.
+ * @param rightDistanceMeters The distance traveled by the right encoder.
+ * @param poseMeters The position on the field that your robot is at.
+ */
+ public void resetPosition(
+ Rotation3d gyroAngle,
+ double leftDistanceMeters,
+ double rightDistanceMeters,
+ Pose3d poseMeters) {
+ super.resetPosition(
+ gyroAngle,
+ new DifferentialDriveWheelPositions(leftDistanceMeters, rightDistanceMeters),
+ poseMeters);
+ }
+
+ /**
+ * Resets the robot's position on the field.
+ *
+ *
The gyroscope angle does not need to be reset here on the user's robot code. The library
+ * automatically takes care of offsetting the gyro angle.
+ *
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param leftDistance The distance traveled by the left encoder.
+ * @param rightDistance The distance traveled by the right encoder.
+ * @param poseMeters The position on the field that your robot is at.
+ */
+ public void resetPosition(
+ Rotation3d gyroAngle, Distance leftDistance, Distance rightDistance, Pose3d poseMeters) {
+ resetPosition(gyroAngle, leftDistance.in(Meters), rightDistance.in(Meters), poseMeters);
+ }
+
+ /**
+ * Updates the robot position on the field using distance measurements from encoders. This method
+ * is more numerically accurate than using velocities to integrate the pose and is also
+ * advantageous for teams that are using lower CPR encoders.
+ *
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param leftDistanceMeters The distance traveled by the left encoder.
+ * @param rightDistanceMeters The distance traveled by the right encoder.
+ * @return The new pose of the robot.
+ */
+ public Pose3d update(
+ Rotation3d gyroAngle, double leftDistanceMeters, double rightDistanceMeters) {
+ return super.update(
+ gyroAngle, new DifferentialDriveWheelPositions(leftDistanceMeters, rightDistanceMeters));
+ }
+}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/kinematics/MecanumDriveOdometry3d.java b/wpimath/src/main/java/edu/wpi/first/math/kinematics/MecanumDriveOdometry3d.java
new file mode 100644
index 00000000000..00d66c0ab1b
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/kinematics/MecanumDriveOdometry3d.java
@@ -0,0 +1,59 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.math.kinematics;
+
+import edu.wpi.first.math.MathSharedStore;
+import edu.wpi.first.math.MathUsageId;
+import edu.wpi.first.math.geometry.Pose2d;
+import edu.wpi.first.math.geometry.Pose3d;
+import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.geometry.Rotation3d;
+import edu.wpi.first.math.geometry.Translation2d;
+import edu.wpi.first.math.geometry.Translation3d;
+
+/**
+ * Class for mecanum drive odometry. Odometry allows you to track the robot's position on the field
+ * over a course of a match using readings from your mecanum wheel encoders.
+ *
+ *
This class is meant to be an easy replacement for {@link MecanumDriveOdometry}, only requiring
+ * the addition of appropriate conversions between 2D and 3D versions of geometry classes. (See
+ * {@link Pose3d#Pose3d(Pose2d)}, {@link Rotation3d#Rotation3d(Rotation2d)}, {@link
+ * Translation3d#Translation3d(Translation2d)}, and {@link Pose3d#toPose2d()}.)
+ *
+ *
Teams can use odometry during the autonomous period for complex tasks like path following.
+ * Furthermore, odometry can be used for latency compensation when using computer-vision systems.
+ */
+public class MecanumDriveOdometry3d extends Odometry3d {
+ /**
+ * Constructs a MecanumDriveOdometry3d object.
+ *
+ * @param kinematics The mecanum drive kinematics for your drivetrain.
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param wheelPositions The distances driven by each wheel.
+ * @param initialPoseMeters The starting position of the robot on the field.
+ */
+ public MecanumDriveOdometry3d(
+ MecanumDriveKinematics kinematics,
+ Rotation3d gyroAngle,
+ MecanumDriveWheelPositions wheelPositions,
+ Pose3d initialPoseMeters) {
+ super(kinematics, gyroAngle, wheelPositions, initialPoseMeters);
+ MathSharedStore.reportUsage(MathUsageId.kOdometry_MecanumDrive, 1);
+ }
+
+ /**
+ * Constructs a MecanumDriveOdometry3d object with the default pose at the origin.
+ *
+ * @param kinematics The mecanum drive kinematics for your drivetrain.
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param wheelPositions The distances driven by each wheel.
+ */
+ public MecanumDriveOdometry3d(
+ MecanumDriveKinematics kinematics,
+ Rotation3d gyroAngle,
+ MecanumDriveWheelPositions wheelPositions) {
+ this(kinematics, gyroAngle, wheelPositions, Pose3d.kZero);
+ }
+}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/kinematics/Odometry3d.java b/wpimath/src/main/java/edu/wpi/first/math/kinematics/Odometry3d.java
new file mode 100644
index 00000000000..c1ccd3c9a16
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/kinematics/Odometry3d.java
@@ -0,0 +1,148 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.math.kinematics;
+
+import edu.wpi.first.math.geometry.Pose2d;
+import edu.wpi.first.math.geometry.Pose3d;
+import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.geometry.Rotation3d;
+import edu.wpi.first.math.geometry.Translation2d;
+import edu.wpi.first.math.geometry.Translation3d;
+import edu.wpi.first.math.geometry.Twist3d;
+
+/**
+ * Class for odometry. Robot code should not use this directly- Instead, use the particular type for
+ * your drivetrain (e.g., {@link DifferentialDriveOdometry3d}). Odometry allows you to track the
+ * robot's position on the field over the course of a match using readings from encoders and a
+ * gyroscope.
+ *
+ * This class is meant to be an easy replacement for {@link Odometry}, only requiring the
+ * addition of appropriate conversions between 2D and 3D versions of geometry classes. (See {@link
+ * Pose3d#Pose3d(Pose2d)}, {@link Rotation3d#Rotation3d(Rotation2d)}, {@link
+ * Translation3d#Translation3d(Translation2d)}, and {@link Pose3d#toPose2d()}.)
+ *
+ *
Teams can use odometry during the autonomous period for complex tasks like path following.
+ * Furthermore, odometry can be used for latency compensation when using computer-vision systems.
+ *
+ * @param Wheel positions type.
+ */
+public class Odometry3d {
+ private final Kinematics, T> m_kinematics;
+ private Pose3d m_poseMeters;
+
+ private Rotation3d m_gyroOffset;
+ private Rotation3d m_previousAngle;
+ private final T m_previousWheelPositions;
+
+ /**
+ * Constructs an Odometry3d object.
+ *
+ * @param kinematics The kinematics of the drivebase.
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param wheelPositions The current encoder readings.
+ * @param initialPoseMeters The starting position of the robot on the field.
+ */
+ public Odometry3d(
+ Kinematics, T> kinematics,
+ Rotation3d gyroAngle,
+ T wheelPositions,
+ Pose3d initialPoseMeters) {
+ m_kinematics = kinematics;
+ m_poseMeters = initialPoseMeters;
+ m_gyroOffset = m_poseMeters.getRotation().minus(gyroAngle);
+ m_previousAngle = m_poseMeters.getRotation();
+ m_previousWheelPositions = m_kinematics.copy(wheelPositions);
+ }
+
+ /**
+ * Resets the robot's position on the field.
+ *
+ * The gyroscope angle does not need to be reset here on the user's robot code. The library
+ * automatically takes care of offsetting the gyro angle.
+ *
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param wheelPositions The current encoder readings.
+ * @param poseMeters The position on the field that your robot is at.
+ */
+ public void resetPosition(Rotation3d gyroAngle, T wheelPositions, Pose3d poseMeters) {
+ m_poseMeters = poseMeters;
+ m_previousAngle = m_poseMeters.getRotation();
+ m_gyroOffset = m_poseMeters.getRotation().minus(gyroAngle);
+ m_kinematics.copyInto(wheelPositions, m_previousWheelPositions);
+ }
+
+ /**
+ * Resets the pose.
+ *
+ * @param poseMeters The pose to reset to.
+ */
+ public void resetPose(Pose3d poseMeters) {
+ m_gyroOffset = m_gyroOffset.plus(poseMeters.getRotation().minus(m_poseMeters.getRotation()));
+ m_poseMeters = poseMeters;
+ m_previousAngle = m_poseMeters.getRotation();
+ }
+
+ /**
+ * Resets the translation of the pose.
+ *
+ * @param translation The translation to reset to.
+ */
+ public void resetTranslation(Translation3d translation) {
+ m_poseMeters = new Pose3d(translation, m_poseMeters.getRotation());
+ }
+
+ /**
+ * Resets the rotation of the pose.
+ *
+ * @param rotation The rotation to reset to.
+ */
+ public void resetRotation(Rotation3d rotation) {
+ m_gyroOffset = m_gyroOffset.plus(rotation.minus(m_poseMeters.getRotation()));
+ m_poseMeters = new Pose3d(m_poseMeters.getTranslation(), rotation);
+ m_previousAngle = m_poseMeters.getRotation();
+ }
+
+ /**
+ * Returns the position of the robot on the field.
+ *
+ * @return The pose of the robot (x, y, and z are in meters).
+ */
+ public Pose3d getPoseMeters() {
+ return m_poseMeters;
+ }
+
+ /**
+ * Updates the robot's position on the field using forward kinematics and integration of the pose
+ * over time. This method takes in an angle parameter which is used instead of the angular rate
+ * that is calculated from forward kinematics, in addition to the current distance measurement at
+ * each wheel.
+ *
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param wheelPositions The current encoder readings.
+ * @return The new pose of the robot.
+ */
+ public Pose3d update(Rotation3d gyroAngle, T wheelPositions) {
+ var angle = gyroAngle.plus(m_gyroOffset);
+ var angle_difference = angle.minus(m_previousAngle).getQuaternion().toRotationVector();
+
+ var twist2d = m_kinematics.toTwist2d(m_previousWheelPositions, wheelPositions);
+ var twist =
+ new Twist3d(
+ twist2d.dx,
+ twist2d.dy,
+ 0,
+ angle_difference.get(0),
+ angle_difference.get(1),
+ angle_difference.get(2));
+
+ var newPose = m_poseMeters.exp(twist);
+
+ m_kinematics.copyInto(wheelPositions, m_previousWheelPositions);
+ m_previousAngle = angle;
+ m_poseMeters = new Pose3d(newPose.getTranslation(), angle);
+
+ return m_poseMeters;
+ }
+}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/kinematics/SwerveDriveOdometry3d.java b/wpimath/src/main/java/edu/wpi/first/math/kinematics/SwerveDriveOdometry3d.java
new file mode 100644
index 00000000000..35fc6fbc3a8
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/kinematics/SwerveDriveOdometry3d.java
@@ -0,0 +1,86 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.math.kinematics;
+
+import edu.wpi.first.math.MathSharedStore;
+import edu.wpi.first.math.MathUsageId;
+import edu.wpi.first.math.geometry.Pose2d;
+import edu.wpi.first.math.geometry.Pose3d;
+import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.geometry.Rotation3d;
+import edu.wpi.first.math.geometry.Translation2d;
+import edu.wpi.first.math.geometry.Translation3d;
+
+/**
+ * Class for swerve drive odometry. Odometry allows you to track the robot's position on the field
+ * over a course of a match using readings from your swerve drive encoders and swerve azimuth
+ * encoders.
+ *
+ *
This class is meant to be an easy replacement for {@link SwerveDriveOdometry}, only requiring
+ * the addition of appropriate conversions between 2D and 3D versions of geometry classes. (See
+ * {@link Pose3d#Pose3d(Pose2d)}, {@link Rotation3d#Rotation3d(Rotation2d)}, {@link
+ * Translation3d#Translation3d(Translation2d)}, and {@link Pose3d#toPose2d()}.)
+ *
+ *
Teams can use odometry during the autonomous period for complex tasks like path following.
+ * Furthermore, odometry can be used for latency compensation when using computer-vision systems.
+ */
+public class SwerveDriveOdometry3d extends Odometry3d {
+ private final int m_numModules;
+
+ /**
+ * Constructs a SwerveDriveOdometry3d object.
+ *
+ * @param kinematics The swerve drive kinematics for your drivetrain.
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param modulePositions The wheel positions reported by each module.
+ * @param initialPose The starting position of the robot on the field.
+ */
+ public SwerveDriveOdometry3d(
+ SwerveDriveKinematics kinematics,
+ Rotation3d gyroAngle,
+ SwerveModulePosition[] modulePositions,
+ Pose3d initialPose) {
+ super(kinematics, gyroAngle, modulePositions, initialPose);
+
+ m_numModules = modulePositions.length;
+
+ MathSharedStore.reportUsage(MathUsageId.kOdometry_SwerveDrive, 1);
+ }
+
+ /**
+ * Constructs a SwerveDriveOdometry3d object with the default pose at the origin.
+ *
+ * @param kinematics The swerve drive kinematics for your drivetrain.
+ * @param gyroAngle The angle reported by the gyroscope.
+ * @param modulePositions The wheel positions reported by each module.
+ */
+ public SwerveDriveOdometry3d(
+ SwerveDriveKinematics kinematics,
+ Rotation3d gyroAngle,
+ SwerveModulePosition[] modulePositions) {
+ this(kinematics, gyroAngle, modulePositions, Pose3d.kZero);
+ }
+
+ @Override
+ public void resetPosition(
+ Rotation3d gyroAngle, SwerveModulePosition[] modulePositions, Pose3d pose) {
+ if (modulePositions.length != m_numModules) {
+ throw new IllegalArgumentException(
+ "Number of modules is not consistent with number of wheel locations provided in "
+ + "constructor");
+ }
+ super.resetPosition(gyroAngle, modulePositions, pose);
+ }
+
+ @Override
+ public Pose3d update(Rotation3d gyroAngle, SwerveModulePosition[] modulePositions) {
+ if (modulePositions.length != m_numModules) {
+ throw new IllegalArgumentException(
+ "Number of modules is not consistent with number of wheel locations provided in "
+ + "constructor");
+ }
+ return super.update(gyroAngle, modulePositions);
+ }
+}
diff --git a/wpimath/src/main/native/cpp/estimator/DifferentialDrivePoseEstimator.cpp b/wpimath/src/main/native/cpp/estimator/DifferentialDrivePoseEstimator.cpp
index 6ddc916a69e..e8ac341e4ce 100644
--- a/wpimath/src/main/native/cpp/estimator/DifferentialDrivePoseEstimator.cpp
+++ b/wpimath/src/main/native/cpp/estimator/DifferentialDrivePoseEstimator.cpp
@@ -21,9 +21,8 @@ DifferentialDrivePoseEstimator::DifferentialDrivePoseEstimator(
units::meter_t leftDistance, units::meter_t rightDistance,
const Pose2d& initialPose, const wpi::array& stateStdDevs,
const wpi::array& visionMeasurementStdDevs)
- : PoseEstimator(
- kinematics, m_odometryImpl, stateStdDevs, visionMeasurementStdDevs),
+ : PoseEstimator(kinematics, m_odometryImpl, stateStdDevs,
+ visionMeasurementStdDevs),
m_odometryImpl{gyroAngle, leftDistance, rightDistance, initialPose} {
- ResetPose(m_odometryImpl.GetPose());
+ ResetPose(initialPose);
}
diff --git a/wpimath/src/main/native/cpp/estimator/DifferentialDrivePoseEstimator3d.cpp b/wpimath/src/main/native/cpp/estimator/DifferentialDrivePoseEstimator3d.cpp
new file mode 100644
index 00000000000..baa53b3eb44
--- /dev/null
+++ b/wpimath/src/main/native/cpp/estimator/DifferentialDrivePoseEstimator3d.cpp
@@ -0,0 +1,27 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "frc/estimator/DifferentialDrivePoseEstimator3d.h"
+
+using namespace frc;
+
+DifferentialDrivePoseEstimator3d::DifferentialDrivePoseEstimator3d(
+ DifferentialDriveKinematics& kinematics, const Rotation3d& gyroAngle,
+ units::meter_t leftDistance, units::meter_t rightDistance,
+ const Pose3d& initialPose)
+ : DifferentialDrivePoseEstimator3d{
+ kinematics, gyroAngle, leftDistance,
+ rightDistance, initialPose, {0.02, 0.02, 0.02, 0.01},
+ {0.1, 0.1, 0.1, 0.1}} {}
+
+DifferentialDrivePoseEstimator3d::DifferentialDrivePoseEstimator3d(
+ DifferentialDriveKinematics& kinematics, const Rotation3d& gyroAngle,
+ units::meter_t leftDistance, units::meter_t rightDistance,
+ const Pose3d& initialPose, const wpi::array& stateStdDevs,
+ const wpi::array& visionMeasurementStdDevs)
+ : PoseEstimator3d(kinematics, m_odometryImpl, stateStdDevs,
+ visionMeasurementStdDevs),
+ m_odometryImpl{gyroAngle, leftDistance, rightDistance, initialPose} {
+ ResetPose(initialPose);
+}
diff --git a/wpimath/src/main/native/cpp/estimator/MecanumDrivePoseEstimator.cpp b/wpimath/src/main/native/cpp/estimator/MecanumDrivePoseEstimator.cpp
index 5db1df74c06..f5d5ba4ffc4 100644
--- a/wpimath/src/main/native/cpp/estimator/MecanumDrivePoseEstimator.cpp
+++ b/wpimath/src/main/native/cpp/estimator/MecanumDrivePoseEstimator.cpp
@@ -24,8 +24,8 @@ frc::MecanumDrivePoseEstimator::MecanumDrivePoseEstimator(
const MecanumDriveWheelPositions& wheelPositions, const Pose2d& initialPose,
const wpi::array& stateStdDevs,
const wpi::array& visionMeasurementStdDevs)
- : PoseEstimator(
- kinematics, m_odometryImpl, stateStdDevs, visionMeasurementStdDevs),
+ : PoseEstimator(kinematics, m_odometryImpl, stateStdDevs,
+ visionMeasurementStdDevs),
m_odometryImpl(kinematics, gyroAngle, wheelPositions, initialPose) {
- ResetPose(m_odometryImpl.GetPose());
+ ResetPose(initialPose);
}
diff --git a/wpimath/src/main/native/cpp/estimator/MecanumDrivePoseEstimator3d.cpp b/wpimath/src/main/native/cpp/estimator/MecanumDrivePoseEstimator3d.cpp
new file mode 100644
index 00000000000..644b3da8dfc
--- /dev/null
+++ b/wpimath/src/main/native/cpp/estimator/MecanumDrivePoseEstimator3d.cpp
@@ -0,0 +1,32 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "frc/estimator/MecanumDrivePoseEstimator3d.h"
+
+#include
+
+#include "frc/StateSpaceUtil.h"
+#include "frc/estimator/AngleStatistics.h"
+#include "wpimath/MathShared.h"
+
+using namespace frc;
+
+frc::MecanumDrivePoseEstimator3d::MecanumDrivePoseEstimator3d(
+ MecanumDriveKinematics& kinematics, const Rotation3d& gyroAngle,
+ const MecanumDriveWheelPositions& wheelPositions, const Pose3d& initialPose)
+ : MecanumDrivePoseEstimator3d{
+ kinematics, gyroAngle,
+ wheelPositions, initialPose,
+ {0.1, 0.1, 0.1, 0.1}, {0.45, 0.45, 0.45, 0.45}} {}
+
+frc::MecanumDrivePoseEstimator3d::MecanumDrivePoseEstimator3d(
+ MecanumDriveKinematics& kinematics, const Rotation3d& gyroAngle,
+ const MecanumDriveWheelPositions& wheelPositions, const Pose3d& initialPose,
+ const wpi::array& stateStdDevs,
+ const wpi::array& visionMeasurementStdDevs)
+ : PoseEstimator3d(kinematics, m_odometryImpl, stateStdDevs,
+ visionMeasurementStdDevs),
+ m_odometryImpl(kinematics, gyroAngle, wheelPositions, initialPose) {
+ ResetPose(initialPose);
+}
diff --git a/wpimath/src/main/native/cpp/estimator/SwerveDrivePoseEstimator3d.cpp b/wpimath/src/main/native/cpp/estimator/SwerveDrivePoseEstimator3d.cpp
new file mode 100644
index 00000000000..7f36471b259
--- /dev/null
+++ b/wpimath/src/main/native/cpp/estimator/SwerveDrivePoseEstimator3d.cpp
@@ -0,0 +1,12 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "frc/estimator/SwerveDrivePoseEstimator3d.h"
+
+namespace frc {
+
+template class EXPORT_TEMPLATE_DEFINE(WPILIB_DLLEXPORT)
+ SwerveDrivePoseEstimator3d<4>;
+
+} // namespace frc
diff --git a/wpimath/src/main/native/cpp/kinematics/DifferentialDriveOdometry.cpp b/wpimath/src/main/native/cpp/kinematics/DifferentialDriveOdometry.cpp
index 346a232e75c..d41a1c077f6 100644
--- a/wpimath/src/main/native/cpp/kinematics/DifferentialDriveOdometry.cpp
+++ b/wpimath/src/main/native/cpp/kinematics/DifferentialDriveOdometry.cpp
@@ -11,9 +11,8 @@ using namespace frc;
DifferentialDriveOdometry::DifferentialDriveOdometry(
const Rotation2d& gyroAngle, units::meter_t leftDistance,
units::meter_t rightDistance, const Pose2d& initialPose)
- : Odometry(
- m_kinematicsImpl, gyroAngle, {leftDistance, rightDistance},
- initialPose) {
+ : Odometry(m_kinematicsImpl, gyroAngle, {leftDistance, rightDistance},
+ initialPose) {
wpi::math::MathSharedStore::ReportUsage(
wpi::math::MathUsageId::kOdometry_DifferentialDrive, 1);
}
diff --git a/wpimath/src/main/native/cpp/kinematics/DifferentialDriveOdometry3d.cpp b/wpimath/src/main/native/cpp/kinematics/DifferentialDriveOdometry3d.cpp
new file mode 100644
index 00000000000..93fcddbdf00
--- /dev/null
+++ b/wpimath/src/main/native/cpp/kinematics/DifferentialDriveOdometry3d.cpp
@@ -0,0 +1,18 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "frc/kinematics/DifferentialDriveOdometry3d.h"
+
+#include "wpimath/MathShared.h"
+
+using namespace frc;
+
+DifferentialDriveOdometry3d::DifferentialDriveOdometry3d(
+ const Rotation3d& gyroAngle, units::meter_t leftDistance,
+ units::meter_t rightDistance, const Pose3d& initialPose)
+ : Odometry3d(m_kinematicsImpl, gyroAngle, {leftDistance, rightDistance},
+ initialPose) {
+ wpi::math::MathSharedStore::ReportUsage(
+ wpi::math::MathUsageId::kOdometry_DifferentialDrive, 1);
+}
diff --git a/wpimath/src/main/native/cpp/kinematics/MecanumDriveOdometry.cpp b/wpimath/src/main/native/cpp/kinematics/MecanumDriveOdometry.cpp
index 55055be744e..f3431e469ec 100644
--- a/wpimath/src/main/native/cpp/kinematics/MecanumDriveOdometry.cpp
+++ b/wpimath/src/main/native/cpp/kinematics/MecanumDriveOdometry.cpp
@@ -11,8 +11,7 @@ using namespace frc;
MecanumDriveOdometry::MecanumDriveOdometry(
MecanumDriveKinematics kinematics, const Rotation2d& gyroAngle,
const MecanumDriveWheelPositions& wheelPositions, const Pose2d& initialPose)
- : Odometry(
- m_kinematicsImpl, gyroAngle, wheelPositions, initialPose),
+ : Odometry(m_kinematicsImpl, gyroAngle, wheelPositions, initialPose),
m_kinematicsImpl(kinematics) {
wpi::math::MathSharedStore::ReportUsage(
wpi::math::MathUsageId::kOdometry_MecanumDrive, 1);
diff --git a/wpimath/src/main/native/cpp/kinematics/MecanumDriveOdometry3d.cpp b/wpimath/src/main/native/cpp/kinematics/MecanumDriveOdometry3d.cpp
new file mode 100644
index 00000000000..bd6954006fe
--- /dev/null
+++ b/wpimath/src/main/native/cpp/kinematics/MecanumDriveOdometry3d.cpp
@@ -0,0 +1,18 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "frc/kinematics/MecanumDriveOdometry3d.h"
+
+#include "wpimath/MathShared.h"
+
+using namespace frc;
+
+MecanumDriveOdometry3d::MecanumDriveOdometry3d(
+ MecanumDriveKinematics kinematics, const Rotation3d& gyroAngle,
+ const MecanumDriveWheelPositions& wheelPositions, const Pose3d& initialPose)
+ : Odometry3d(m_kinematicsImpl, gyroAngle, wheelPositions, initialPose),
+ m_kinematicsImpl(kinematics) {
+ wpi::math::MathSharedStore::ReportUsage(
+ wpi::math::MathUsageId::kOdometry_MecanumDrive, 1);
+}
diff --git a/wpimath/src/main/native/cpp/kinematics/SwerveDriveOdometry3d.cpp b/wpimath/src/main/native/cpp/kinematics/SwerveDriveOdometry3d.cpp
new file mode 100644
index 00000000000..2325081c0e6
--- /dev/null
+++ b/wpimath/src/main/native/cpp/kinematics/SwerveDriveOdometry3d.cpp
@@ -0,0 +1,12 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "frc/kinematics/SwerveDriveOdometry3d.h"
+
+namespace frc {
+
+template class EXPORT_TEMPLATE_DEFINE(WPILIB_DLLEXPORT)
+ SwerveDriveOdometry3d<4>;
+
+} // namespace frc
diff --git a/wpimath/src/main/native/include/frc/estimator/DifferentialDrivePoseEstimator.h b/wpimath/src/main/native/include/frc/estimator/DifferentialDrivePoseEstimator.h
index 89dcd35cb06..ccd09a5d0a9 100644
--- a/wpimath/src/main/native/include/frc/estimator/DifferentialDrivePoseEstimator.h
+++ b/wpimath/src/main/native/include/frc/estimator/DifferentialDrivePoseEstimator.h
@@ -89,9 +89,8 @@ class WPILIB_DLLEXPORT DifferentialDrivePoseEstimator
*/
void ResetPosition(const Rotation2d& gyroAngle, units::meter_t leftDistance,
units::meter_t rightDistance, const Pose2d& pose) {
- PoseEstimator::
- ResetPosition(gyroAngle, {leftDistance, rightDistance}, pose);
+ PoseEstimator::ResetPosition(gyroAngle, {leftDistance, rightDistance},
+ pose);
}
/**
@@ -106,10 +105,7 @@ class WPILIB_DLLEXPORT DifferentialDrivePoseEstimator
*/
Pose2d Update(const Rotation2d& gyroAngle, units::meter_t leftDistance,
units::meter_t rightDistance) {
- return PoseEstimator<
- DifferentialDriveWheelSpeeds,
- DifferentialDriveWheelPositions>::Update(gyroAngle,
- {leftDistance, rightDistance});
+ return PoseEstimator::Update(gyroAngle, {leftDistance, rightDistance});
}
/**
@@ -127,11 +123,8 @@ class WPILIB_DLLEXPORT DifferentialDrivePoseEstimator
const Rotation2d& gyroAngle,
units::meter_t leftDistance,
units::meter_t rightDistance) {
- return PoseEstimator<
- DifferentialDriveWheelSpeeds,
- DifferentialDriveWheelPositions>::UpdateWithTime(currentTime, gyroAngle,
- {leftDistance,
- rightDistance});
+ return PoseEstimator::UpdateWithTime(currentTime, gyroAngle,
+ {leftDistance, rightDistance});
}
private:
diff --git a/wpimath/src/main/native/include/frc/estimator/DifferentialDrivePoseEstimator3d.h b/wpimath/src/main/native/include/frc/estimator/DifferentialDrivePoseEstimator3d.h
new file mode 100644
index 00000000000..371536b2980
--- /dev/null
+++ b/wpimath/src/main/native/include/frc/estimator/DifferentialDrivePoseEstimator3d.h
@@ -0,0 +1,139 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include
+#include
+
+#include "frc/estimator/PoseEstimator3d.h"
+#include "frc/geometry/Pose2d.h"
+#include "frc/geometry/Rotation2d.h"
+#include "frc/kinematics/DifferentialDriveKinematics.h"
+#include "frc/kinematics/DifferentialDriveOdometry3d.h"
+#include "units/time.h"
+
+namespace frc {
+/**
+ * This class wraps Differential Drive Odometry to fuse latency-compensated
+ * vision measurements with differential drive encoder measurements. It will
+ * correct for noisy vision measurements and encoder drift. It is intended to be
+ * an easy drop-in for DifferentialDriveOdometry3d. In fact, if you never call
+ * AddVisionMeasurement(), and only call Update(), this will behave exactly the
+ * same as DifferentialDriveOdometry3d. It is also intended to be an easy
+ * replacement for PoseEstimator, only requiring the addition of a standard
+ * deviation for Z and appropriate conversions between 2D and 3D versions of
+ * geometry classes. (See Pose3d(Pose2d), Rotation3d(Rotation2d),
+ * Translation3d(Translation2d), and Pose3d.ToPose2d().)
+ *
+ * Update() should be called every robot loop (if your robot loops are faster or
+ * slower than the default of 20 ms, then you should change the nominal delta
+ * time via the constructor).
+ *
+ * AddVisionMeasurement() can be called as infrequently as you want; if you
+ * never call it, then this class will behave like regular encoder odometry.
+ */
+class WPILIB_DLLEXPORT DifferentialDrivePoseEstimator3d
+ : public PoseEstimator3d {
+ public:
+ /**
+ * Constructs a DifferentialDrivePoseEstimator3d with default standard
+ * deviations for the model and vision measurements.
+ *
+ * The default standard deviations of the model states are
+ * 0.02 meters for x, 0.02 meters for y, 0.02 meters for z, and 0.01 radians
+ * for angle. The default standard deviations of the vision measurements are
+ * 0.1 meters for x, 0.1 meters for y, 0.1 meters for z, and 0.1 radians for
+ * angle.
+ *
+ * @param kinematics A correctly-configured kinematics object for your
+ * drivetrain.
+ * @param gyroAngle The gyro angle of the robot.
+ * @param leftDistance The distance traveled by the left encoder.
+ * @param rightDistance The distance traveled by the right encoder.
+ * @param initialPose The estimated initial pose.
+ */
+ DifferentialDrivePoseEstimator3d(DifferentialDriveKinematics& kinematics,
+ const Rotation3d& gyroAngle,
+ units::meter_t leftDistance,
+ units::meter_t rightDistance,
+ const Pose3d& initialPose);
+
+ /**
+ * Constructs a DifferentialDrivePoseEstimator3d.
+ *
+ * @param kinematics A correctly-configured kinematics object for your
+ * drivetrain.
+ * @param gyroAngle The gyro angle of the robot.
+ * @param leftDistance The distance traveled by the left encoder.
+ * @param rightDistance The distance traveled by the right encoder.
+ * @param initialPose The estimated initial pose.
+ * @param stateStdDevs Standard deviations of the pose estimate (x position in
+ * meters, y position in meters, z position in meters, and angle in
+ * radians). Increase these numbers to trust your state estimate less.
+ * @param visionMeasurementStdDevs Standard deviations of the vision pose
+ * measurement (x position in meters, y position in meters, z position in
+ * meters, and angle in radians). Increase these numbers to trust the vision
+ * pose measurement less.
+ */
+ DifferentialDrivePoseEstimator3d(
+ DifferentialDriveKinematics& kinematics, const Rotation3d& gyroAngle,
+ units::meter_t leftDistance, units::meter_t rightDistance,
+ const Pose3d& initialPose, const wpi::array& stateStdDevs,
+ const wpi::array& visionMeasurementStdDevs);
+
+ /**
+ * Resets the robot's position on the field.
+ *
+ * @param gyroAngle The current gyro angle.
+ * @param leftDistance The distance traveled by the left encoder.
+ * @param rightDistance The distance traveled by the right encoder.
+ * @param pose The estimated pose of the robot on the field.
+ */
+ void ResetPosition(const Rotation3d& gyroAngle, units::meter_t leftDistance,
+ units::meter_t rightDistance, const Pose3d& pose) {
+ PoseEstimator3d::ResetPosition(gyroAngle, {leftDistance, rightDistance},
+ pose);
+ }
+
+ /**
+ * Updates the pose estimator with wheel encoder and gyro information. This
+ * should be called every loop.
+ *
+ * @param gyroAngle The current gyro angle.
+ * @param leftDistance The distance traveled by the left encoder.
+ * @param rightDistance The distance traveled by the right encoder.
+ *
+ * @return The estimated pose of the robot.
+ */
+ Pose3d Update(const Rotation3d& gyroAngle, units::meter_t leftDistance,
+ units::meter_t rightDistance) {
+ return PoseEstimator3d::Update(gyroAngle, {leftDistance, rightDistance});
+ }
+
+ /**
+ * Updates the pose estimator with wheel encoder and gyro information. This
+ * should be called every loop.
+ *
+ * @param currentTime The time at which this method was called.
+ * @param gyroAngle The current gyro angle.
+ * @param leftDistance The distance traveled by the left encoder.
+ * @param rightDistance The distance traveled by the right encoder.
+ *
+ * @return The estimated pose of the robot.
+ */
+ Pose3d UpdateWithTime(units::second_t currentTime,
+ const Rotation3d& gyroAngle,
+ units::meter_t leftDistance,
+ units::meter_t rightDistance) {
+ return PoseEstimator3d::UpdateWithTime(currentTime, gyroAngle,
+ {leftDistance, rightDistance});
+ }
+
+ private:
+ DifferentialDriveOdometry3d m_odometryImpl;
+};
+
+} // namespace frc
diff --git a/wpimath/src/main/native/include/frc/estimator/MecanumDrivePoseEstimator3d.h b/wpimath/src/main/native/include/frc/estimator/MecanumDrivePoseEstimator3d.h
new file mode 100644
index 00000000000..bb1c6ea6936
--- /dev/null
+++ b/wpimath/src/main/native/include/frc/estimator/MecanumDrivePoseEstimator3d.h
@@ -0,0 +1,90 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include
+
+#include
+#include
+
+#include "frc/estimator/PoseEstimator3d.h"
+#include "frc/geometry/Pose2d.h"
+#include "frc/geometry/Rotation2d.h"
+#include "frc/interpolation/TimeInterpolatableBuffer.h"
+#include "frc/kinematics/MecanumDriveKinematics.h"
+#include "frc/kinematics/MecanumDriveOdometry3d.h"
+#include "units/time.h"
+
+namespace frc {
+/**
+ * This class wraps Mecanum Drive Odometry to fuse latency-compensated
+ * vision measurements with mecanum drive encoder velocity measurements. It will
+ * correct for noisy measurements and encoder drift. It is intended to be an
+ * easy drop-in for MecanumDriveOdometry3d. It is also intended to be an easy
+ * replacement for PoseEstimator, only requiring the addition of a standard
+ * deviation for Z and appropriate conversions between 2D and 3D versions of
+ * geometry classes. (See Pose3d(Pose2d), Rotation3d(Rotation2d),
+ * Translation3d(Translation2d), and Pose3d.ToPose2d().)
+ *
+ * Update() should be called every robot loop. If your loops are faster or
+ * slower than the default of 20 ms, then you should change the nominal delta
+ * time by specifying it in the constructor.
+ *
+ * AddVisionMeasurement() can be called as infrequently as you want; if you
+ * never call it, then this class will behave mostly like regular encoder
+ * odometry.
+ */
+class WPILIB_DLLEXPORT MecanumDrivePoseEstimator3d
+ : public PoseEstimator3d {
+ public:
+ /**
+ * Constructs a MecanumDrivePoseEstimator3d with default standard deviations
+ * for the model and vision measurements.
+ *
+ * The default standard deviations of the model states are
+ * 0.1 meters for x, 0.1 meters for y, 0.1 meters for z, and 0.1 radians for
+ * angle. The default standard deviations of the vision measurements are 0.45
+ * meters for x, 0.45 meters for y, 0.45 meters for z, and 0.45 radians for
+ * angle.
+ *
+ * @param kinematics A correctly-configured kinematics object for your
+ * drivetrain.
+ * @param gyroAngle The current gyro angle.
+ * @param wheelPositions The distance measured by each wheel.
+ * @param initialPose The starting pose estimate.
+ */
+ MecanumDrivePoseEstimator3d(MecanumDriveKinematics& kinematics,
+ const Rotation3d& gyroAngle,
+ const MecanumDriveWheelPositions& wheelPositions,
+ const Pose3d& initialPose);
+
+ /**
+ * Constructs a MecanumDrivePoseEstimator3d.
+ *
+ * @param kinematics A correctly-configured kinematics object for your
+ * drivetrain.
+ * @param gyroAngle The current gyro angle.
+ * @param wheelPositions The distance measured by each wheel.
+ * @param initialPose The starting pose estimate.
+ * @param stateStdDevs Standard deviations of the pose estimate (x position in
+ * meters, y position in meters, z position in meters, and angle in
+ * radians). Increase these numbers to trust your state estimate less.
+ * @param visionMeasurementStdDevs Standard deviations of the vision pose
+ * measurement (x position in meters, y position in meters, z position in
+ * meters, and angle in radians). Increase these numbers to trust the vision
+ * pose measurement less.
+ */
+ MecanumDrivePoseEstimator3d(
+ MecanumDriveKinematics& kinematics, const Rotation3d& gyroAngle,
+ const MecanumDriveWheelPositions& wheelPositions,
+ const Pose3d& initialPose, const wpi::array& stateStdDevs,
+ const wpi::array& visionMeasurementStdDevs);
+
+ private:
+ MecanumDriveOdometry3d m_odometryImpl;
+};
+
+} // namespace frc
diff --git a/wpimath/src/main/native/include/frc/estimator/PoseEstimator3d.h b/wpimath/src/main/native/include/frc/estimator/PoseEstimator3d.h
new file mode 100644
index 00000000000..bc8bdb08a66
--- /dev/null
+++ b/wpimath/src/main/native/include/frc/estimator/PoseEstimator3d.h
@@ -0,0 +1,451 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include