001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.math.kinematics;
006
007import edu.wpi.first.math.MathSharedStore;
008import edu.wpi.first.math.MathUsageId;
009import edu.wpi.first.math.geometry.Translation2d;
010import edu.wpi.first.math.geometry.Twist2d;
011import edu.wpi.first.math.kinematics.proto.MecanumDriveKinematicsProto;
012import edu.wpi.first.math.kinematics.struct.MecanumDriveKinematicsStruct;
013import org.ejml.simple.SimpleMatrix;
014
015/**
016 * Helper class that converts a chassis velocity (dx, dy, and dtheta components) into individual
017 * wheel speeds.
018 *
019 * <p>The inverse kinematics (converting from a desired chassis velocity to individual wheel speeds)
020 * uses the relative locations of the wheels with respect to the center of rotation. The center of
021 * rotation for inverse kinematics is also variable. This means that you can set your center of
022 * rotation in a corner of the robot to perform special evasion maneuvers.
023 *
024 * <p>Forward kinematics (converting an array of wheel speeds into the overall chassis motion) is
025 * performs the exact opposite of what inverse kinematics does. Since this is an overdetermined
026 * system (more equations than variables), we use a least-squares approximation.
027 *
028 * <p>The inverse kinematics: [wheelSpeeds] = [wheelLocations] * [chassisSpeeds] We take the
029 * Moore-Penrose pseudoinverse of [wheelLocations] and then multiply by [wheelSpeeds] to get our
030 * chassis speeds.
031 *
032 * <p>Forward kinematics is also used for odometry -- determining the position of the robot on the
033 * field using encoders and a gyro.
034 */
035public class MecanumDriveKinematics
036    implements Kinematics<MecanumDriveWheelSpeeds, MecanumDriveWheelPositions> {
037  private final SimpleMatrix m_inverseKinematics;
038  private final SimpleMatrix m_forwardKinematics;
039
040  private final Translation2d m_frontLeftWheelMeters;
041  private final Translation2d m_frontRightWheelMeters;
042  private final Translation2d m_rearLeftWheelMeters;
043  private final Translation2d m_rearRightWheelMeters;
044
045  private Translation2d m_prevCoR = new Translation2d();
046
047  public static final MecanumDriveKinematicsProto proto = new MecanumDriveKinematicsProto();
048  public static final MecanumDriveKinematicsStruct struct = new MecanumDriveKinematicsStruct();
049
050  /**
051   * Constructs a mecanum drive kinematics object.
052   *
053   * @param frontLeftWheelMeters The location of the front-left wheel relative to the physical
054   *     center of the robot.
055   * @param frontRightWheelMeters The location of the front-right wheel relative to the physical
056   *     center of the robot.
057   * @param rearLeftWheelMeters The location of the rear-left wheel relative to the physical center
058   *     of the robot.
059   * @param rearRightWheelMeters The location of the rear-right wheel relative to the physical
060   *     center of the robot.
061   */
062  public MecanumDriveKinematics(
063      Translation2d frontLeftWheelMeters,
064      Translation2d frontRightWheelMeters,
065      Translation2d rearLeftWheelMeters,
066      Translation2d rearRightWheelMeters) {
067    m_frontLeftWheelMeters = frontLeftWheelMeters;
068    m_frontRightWheelMeters = frontRightWheelMeters;
069    m_rearLeftWheelMeters = rearLeftWheelMeters;
070    m_rearRightWheelMeters = rearRightWheelMeters;
071
072    m_inverseKinematics = new SimpleMatrix(4, 3);
073
074    setInverseKinematics(
075        frontLeftWheelMeters, frontRightWheelMeters, rearLeftWheelMeters, rearRightWheelMeters);
076    m_forwardKinematics = m_inverseKinematics.pseudoInverse();
077
078    MathSharedStore.reportUsage(MathUsageId.kKinematics_MecanumDrive, 1);
079  }
080
081  /**
082   * Performs inverse kinematics to return the wheel speeds from a desired chassis velocity. This
083   * method is often used to convert joystick values into wheel speeds.
084   *
085   * <p>This function also supports variable centers of rotation. During normal operations, the
086   * center of rotation is usually the same as the physical center of the robot; therefore, the
087   * argument is defaulted to that use case. However, if you wish to change the center of rotation
088   * for evasive maneuvers, vision alignment, or for any other use case, you can do so.
089   *
090   * @param chassisSpeeds The desired chassis speed.
091   * @param centerOfRotationMeters The center of rotation. For example, if you set the center of
092   *     rotation at one corner of the robot and provide a chassis speed that only has a dtheta
093   *     component, the robot will rotate around that corner.
094   * @return The wheel speeds. Use caution because they are not normalized. Sometimes, a user input
095   *     may cause one of the wheel speeds to go above the attainable max velocity. Use the {@link
096   *     MecanumDriveWheelSpeeds#desaturate(double)} function to rectify this issue.
097   */
098  public MecanumDriveWheelSpeeds toWheelSpeeds(
099      ChassisSpeeds chassisSpeeds, Translation2d centerOfRotationMeters) {
100    // We have a new center of rotation. We need to compute the matrix again.
101    if (!centerOfRotationMeters.equals(m_prevCoR)) {
102      var fl = m_frontLeftWheelMeters.minus(centerOfRotationMeters);
103      var fr = m_frontRightWheelMeters.minus(centerOfRotationMeters);
104      var rl = m_rearLeftWheelMeters.minus(centerOfRotationMeters);
105      var rr = m_rearRightWheelMeters.minus(centerOfRotationMeters);
106
107      setInverseKinematics(fl, fr, rl, rr);
108      m_prevCoR = centerOfRotationMeters;
109    }
110
111    var chassisSpeedsVector = new SimpleMatrix(3, 1);
112    chassisSpeedsVector.setColumn(
113        0,
114        0,
115        chassisSpeeds.vxMetersPerSecond,
116        chassisSpeeds.vyMetersPerSecond,
117        chassisSpeeds.omegaRadiansPerSecond);
118
119    var wheelsVector = m_inverseKinematics.mult(chassisSpeedsVector);
120    return new MecanumDriveWheelSpeeds(
121        wheelsVector.get(0, 0),
122        wheelsVector.get(1, 0),
123        wheelsVector.get(2, 0),
124        wheelsVector.get(3, 0));
125  }
126
127  /**
128   * Performs inverse kinematics. See {@link #toWheelSpeeds(ChassisSpeeds, Translation2d)} for more
129   * information.
130   *
131   * @param chassisSpeeds The desired chassis speed.
132   * @return The wheel speeds.
133   */
134  @Override
135  public MecanumDriveWheelSpeeds toWheelSpeeds(ChassisSpeeds chassisSpeeds) {
136    return toWheelSpeeds(chassisSpeeds, new Translation2d());
137  }
138
139  /**
140   * Performs forward kinematics to return the resulting chassis state from the given wheel speeds.
141   * This method is often used for odometry -- determining the robot's position on the field using
142   * data from the real-world speed of each wheel on the robot.
143   *
144   * @param wheelSpeeds The current mecanum drive wheel speeds.
145   * @return The resulting chassis speed.
146   */
147  @Override
148  public ChassisSpeeds toChassisSpeeds(MecanumDriveWheelSpeeds wheelSpeeds) {
149    var wheelSpeedsVector = new SimpleMatrix(4, 1);
150    wheelSpeedsVector.setColumn(
151        0,
152        0,
153        wheelSpeeds.frontLeftMetersPerSecond,
154        wheelSpeeds.frontRightMetersPerSecond,
155        wheelSpeeds.rearLeftMetersPerSecond,
156        wheelSpeeds.rearRightMetersPerSecond);
157    var chassisSpeedsVector = m_forwardKinematics.mult(wheelSpeedsVector);
158
159    return new ChassisSpeeds(
160        chassisSpeedsVector.get(0, 0),
161        chassisSpeedsVector.get(1, 0),
162        chassisSpeedsVector.get(2, 0));
163  }
164
165  @Override
166  public Twist2d toTwist2d(MecanumDriveWheelPositions start, MecanumDriveWheelPositions end) {
167    var wheelDeltasVector = new SimpleMatrix(4, 1);
168    wheelDeltasVector.setColumn(
169        0,
170        0,
171        end.frontLeftMeters - start.frontLeftMeters,
172        end.frontRightMeters - start.frontRightMeters,
173        end.rearLeftMeters - start.rearLeftMeters,
174        end.rearRightMeters - start.rearRightMeters);
175    var twist = m_forwardKinematics.mult(wheelDeltasVector);
176    return new Twist2d(twist.get(0, 0), twist.get(1, 0), twist.get(2, 0));
177  }
178
179  /**
180   * Performs forward kinematics to return the resulting Twist2d from the given wheel deltas. This
181   * method is often used for odometry -- determining the robot's position on the field using
182   * changes in the distance driven by each wheel on the robot.
183   *
184   * @param wheelDeltas The distances driven by each wheel.
185   * @return The resulting Twist2d.
186   */
187  public Twist2d toTwist2d(MecanumDriveWheelPositions wheelDeltas) {
188    var wheelDeltasVector = new SimpleMatrix(4, 1);
189    wheelDeltasVector.setColumn(
190        0,
191        0,
192        wheelDeltas.frontLeftMeters,
193        wheelDeltas.frontRightMeters,
194        wheelDeltas.rearLeftMeters,
195        wheelDeltas.rearRightMeters);
196    var twist = m_forwardKinematics.mult(wheelDeltasVector);
197    return new Twist2d(twist.get(0, 0), twist.get(1, 0), twist.get(2, 0));
198  }
199
200  /**
201   * Construct inverse kinematics matrix from wheel locations.
202   *
203   * @param fl The location of the front-left wheel relative to the physical center of the robot.
204   * @param fr The location of the front-right wheel relative to the physical center of the robot.
205   * @param rl The location of the rear-left wheel relative to the physical center of the robot.
206   * @param rr The location of the rear-right wheel relative to the physical center of the robot.
207   */
208  private void setInverseKinematics(
209      Translation2d fl, Translation2d fr, Translation2d rl, Translation2d rr) {
210    m_inverseKinematics.setRow(0, 0, 1, -1, -(fl.getX() + fl.getY()));
211    m_inverseKinematics.setRow(1, 0, 1, 1, fr.getX() - fr.getY());
212    m_inverseKinematics.setRow(2, 0, 1, 1, rl.getX() - rl.getY());
213    m_inverseKinematics.setRow(3, 0, 1, -1, -(rr.getX() + rr.getY()));
214  }
215
216  public Translation2d getFrontLeft() {
217    return m_frontLeftWheelMeters;
218  }
219
220  public Translation2d getFrontRight() {
221    return m_frontRightWheelMeters;
222  }
223
224  public Translation2d getRearLeft() {
225    return m_rearLeftWheelMeters;
226  }
227
228  public Translation2d getRearRight() {
229    return m_rearRightWheelMeters;
230  }
231}