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.geometry.Translation2d;
009import edu.wpi.first.math.geometry.Twist2d;
010import edu.wpi.first.math.kinematics.proto.MecanumDriveKinematicsProto;
011import edu.wpi.first.math.kinematics.struct.MecanumDriveKinematicsStruct;
012import edu.wpi.first.util.protobuf.ProtobufSerializable;
013import edu.wpi.first.util.struct.StructSerializable;
014import org.ejml.simple.SimpleMatrix;
015
016/**
017 * Helper class that converts a chassis velocity (dx, dy, and dtheta components) into individual
018 * wheel speeds.
019 *
020 * <p>The inverse kinematics (converting from a desired chassis velocity to individual wheel speeds)
021 * uses the relative locations of the wheels with respect to the center of rotation. The center of
022 * rotation for inverse kinematics is also variable. This means that you can set your center of
023 * rotation in a corner of the robot to perform special evasion maneuvers.
024 *
025 * <p>Forward kinematics (converting an array of wheel speeds into the overall chassis motion) is
026 * performs the exact opposite of what inverse kinematics does. Since this is an overdetermined
027 * system (more equations than variables), we use a least-squares approximation.
028 *
029 * <p>The inverse kinematics: [wheelSpeeds] = [wheelLocations] * [chassisSpeeds] We take the
030 * Moore-Penrose pseudoinverse of [wheelLocations] and then multiply by [wheelSpeeds] to get our
031 * chassis speeds.
032 *
033 * <p>Forward kinematics is also used for odometry -- determining the position of the robot on the
034 * field using encoders and a gyro.
035 */
036public class MecanumDriveKinematics
037    implements Kinematics<MecanumDriveWheelSpeeds, MecanumDriveWheelPositions>,
038        ProtobufSerializable,
039        StructSerializable {
040  private final SimpleMatrix m_inverseKinematics;
041  private final SimpleMatrix m_forwardKinematics;
042
043  private final Translation2d m_frontLeftWheel;
044  private final Translation2d m_frontRightWheel;
045  private final Translation2d m_rearLeftWheel;
046  private final Translation2d m_rearRightWheel;
047
048  private Translation2d m_prevCoR = Translation2d.kZero;
049
050  /** MecanumDriveKinematics protobuf for serialization. */
051  public static final MecanumDriveKinematicsProto proto = new MecanumDriveKinematicsProto();
052
053  /** MecanumDriveKinematics struct for serialization. */
054  public static final MecanumDriveKinematicsStruct struct = new MecanumDriveKinematicsStruct();
055
056  /**
057   * Constructs a mecanum drive kinematics object.
058   *
059   * @param frontLeftWheel The location of the front-left wheel relative to the physical center of
060   *     the robot, in meters.
061   * @param frontRightWheel The location of the front-right wheel relative to the physical center of
062   *     the robot, in meters.
063   * @param rearLeftWheel The location of the rear-left wheel relative to the physical center of the
064   *     robot, in meters.
065   * @param rearRightWheel The location of the rear-right wheel relative to the physical center of
066   *     the robot, in meters.
067   */
068  public MecanumDriveKinematics(
069      Translation2d frontLeftWheel,
070      Translation2d frontRightWheel,
071      Translation2d rearLeftWheel,
072      Translation2d rearRightWheel) {
073    m_frontLeftWheel = frontLeftWheel;
074    m_frontRightWheel = frontRightWheel;
075    m_rearLeftWheel = rearLeftWheel;
076    m_rearRightWheel = rearRightWheel;
077
078    m_inverseKinematics = new SimpleMatrix(4, 3);
079
080    setInverseKinematics(frontLeftWheel, frontRightWheel, rearLeftWheel, rearRightWheel);
081    m_forwardKinematics = m_inverseKinematics.pseudoInverse();
082
083    MathSharedStore.reportUsage("MecanumDriveKinematics", "");
084  }
085
086  /**
087   * Performs inverse kinematics to return the wheel speeds from a desired chassis velocity. This
088   * method is often used to convert joystick values into wheel speeds.
089   *
090   * <p>This function also supports variable centers of rotation. During normal operations, the
091   * center of rotation is usually the same as the physical center of the robot; therefore, the
092   * argument is defaulted to that use case. However, if you wish to change the center of rotation
093   * for evasive maneuvers, vision alignment, or for any other use case, you can do so.
094   *
095   * @param chassisSpeeds The desired chassis speed.
096   * @param centerOfRotation The center of rotation. For example, if you set the center of rotation
097   *     at one corner of the robot and provide a chassis speed that only has a dtheta component,
098   *     the robot will rotate around that corner.
099   * @return The wheel speeds. Use caution because they are not normalized. Sometimes, a user input
100   *     may cause one of the wheel speeds to go above the attainable max velocity. Use the {@link
101   *     MecanumDriveWheelSpeeds#desaturate(double)} function to rectify this issue.
102   */
103  public MecanumDriveWheelSpeeds toWheelSpeeds(
104      ChassisSpeeds chassisSpeeds, Translation2d centerOfRotation) {
105    // We have a new center of rotation. We need to compute the matrix again.
106    if (!centerOfRotation.equals(m_prevCoR)) {
107      var fl = m_frontLeftWheel.minus(centerOfRotation);
108      var fr = m_frontRightWheel.minus(centerOfRotation);
109      var rl = m_rearLeftWheel.minus(centerOfRotation);
110      var rr = m_rearRightWheel.minus(centerOfRotation);
111
112      setInverseKinematics(fl, fr, rl, rr);
113      m_prevCoR = centerOfRotation;
114    }
115
116    var chassisSpeedsVector = new SimpleMatrix(3, 1);
117    chassisSpeedsVector.setColumn(0, 0, chassisSpeeds.vx, chassisSpeeds.vy, chassisSpeeds.omega);
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, Translation2d.kZero);
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.frontLeft,
154        wheelSpeeds.frontRight,
155        wheelSpeeds.rearLeft,
156        wheelSpeeds.rearRight);
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.frontLeft - start.frontLeft,
172        end.frontRight - start.frontRight,
173        end.rearLeft - start.rearLeft,
174        end.rearRight - start.rearRight);
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.frontLeft,
193        wheelDeltas.frontRight,
194        wheelDeltas.rearLeft,
195        wheelDeltas.rearRight);
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  /**
217   * Returns the front-left wheel translation.
218   *
219   * @return The front-left wheel translation.
220   */
221  public Translation2d getFrontLeft() {
222    return m_frontLeftWheel;
223  }
224
225  /**
226   * Returns the front-right wheel translation.
227   *
228   * @return The front-right wheel translation.
229   */
230  public Translation2d getFrontRight() {
231    return m_frontRightWheel;
232  }
233
234  /**
235   * Returns the rear-left wheel translation.
236   *
237   * @return The rear-left wheel translation.
238   */
239  public Translation2d getRearLeft() {
240    return m_rearLeftWheel;
241  }
242
243  /**
244   * Returns the rear-right wheel translation.
245   *
246   * @return The rear-right wheel translation.
247   */
248  public Translation2d getRearRight() {
249    return m_rearRightWheel;
250  }
251
252  @Override
253  public MecanumDriveWheelPositions copy(MecanumDriveWheelPositions positions) {
254    return new MecanumDriveWheelPositions(
255        positions.frontLeft, positions.frontRight, positions.rearLeft, positions.rearRight);
256  }
257
258  @Override
259  public void copyInto(MecanumDriveWheelPositions positions, MecanumDriveWheelPositions output) {
260    output.frontLeft = positions.frontLeft;
261    output.frontRight = positions.frontRight;
262    output.rearLeft = positions.rearLeft;
263    output.rearRight = positions.rearRight;
264  }
265
266  @Override
267  public MecanumDriveWheelPositions interpolate(
268      MecanumDriveWheelPositions startValue, MecanumDriveWheelPositions endValue, double t) {
269    return startValue.interpolate(endValue, t);
270  }
271}