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 org.wpilib.math.kinematics;
006
007import org.ejml.simple.SimpleMatrix;
008import org.wpilib.math.geometry.Translation2d;
009import org.wpilib.math.geometry.Twist2d;
010import org.wpilib.math.kinematics.proto.MecanumDriveKinematicsProto;
011import org.wpilib.math.kinematics.struct.MecanumDriveKinematicsStruct;
012import org.wpilib.math.util.MathSharedStore;
013import org.wpilib.util.protobuf.ProtobufSerializable;
014import org.wpilib.util.struct.StructSerializable;
015
016/**
017 * Helper class that converts a chassis velocity (dx, dy, and dtheta components) into individual
018 * wheel velocities.
019 *
020 * <p>The inverse kinematics (converting from a desired chassis velocity to individual wheel
021 * velocities) uses the relative locations of the wheels with respect to the center of rotation. The
022 * center of rotation for inverse kinematics is also variable. This means that you can set your
023 * center of rotation in a corner of the robot to perform special evasion maneuvers.
024 *
025 * <p>Forward kinematics (converting an array of wheel velocities into the overall chassis motion)
026 * is 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: [wheelVelocities] = [wheelLocations] * [chassisVelocities] We take the
030 * Moore-Penrose pseudoinverse of [wheelLocations] and then multiply by [wheelVelocities] to get our
031 * chassis velocities.
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<
038            MecanumDriveWheelPositions,
039            MecanumDriveWheelVelocities,
040            MecanumDriveWheelAccelerations>,
041        ProtobufSerializable,
042        StructSerializable {
043  private final SimpleMatrix m_inverseKinematics;
044  private final SimpleMatrix m_forwardKinematics;
045
046  private final Translation2d m_frontLeftWheel;
047  private final Translation2d m_frontRightWheel;
048  private final Translation2d m_rearLeftWheel;
049  private final Translation2d m_rearRightWheel;
050
051  private Translation2d m_prevCoR = Translation2d.kZero;
052
053  /** MecanumDriveKinematics protobuf for serialization. */
054  public static final MecanumDriveKinematicsProto proto = new MecanumDriveKinematicsProto();
055
056  /** MecanumDriveKinematics struct for serialization. */
057  public static final MecanumDriveKinematicsStruct struct = new MecanumDriveKinematicsStruct();
058
059  /**
060   * Constructs a mecanum drive kinematics object.
061   *
062   * @param frontLeftWheel The location of the front-left wheel relative to the physical center of
063   *     the robot, in meters.
064   * @param frontRightWheel The location of the front-right wheel relative to the physical center of
065   *     the robot, in meters.
066   * @param rearLeftWheel The location of the rear-left wheel relative to the physical center of the
067   *     robot, in meters.
068   * @param rearRightWheel The location of the rear-right wheel relative to the physical center of
069   *     the robot, in meters.
070   */
071  public MecanumDriveKinematics(
072      Translation2d frontLeftWheel,
073      Translation2d frontRightWheel,
074      Translation2d rearLeftWheel,
075      Translation2d rearRightWheel) {
076    m_frontLeftWheel = frontLeftWheel;
077    m_frontRightWheel = frontRightWheel;
078    m_rearLeftWheel = rearLeftWheel;
079    m_rearRightWheel = rearRightWheel;
080
081    m_inverseKinematics = new SimpleMatrix(4, 3);
082
083    setInverseKinematics(frontLeftWheel, frontRightWheel, rearLeftWheel, rearRightWheel);
084    m_forwardKinematics = m_inverseKinematics.pseudoInverse();
085
086    MathSharedStore.reportUsage("MecanumDriveKinematics", "");
087  }
088
089  /**
090   * Performs inverse kinematics to return the wheel velocities from a desired chassis velocity.
091   * This method is often used to convert joystick values into wheel velocities.
092   *
093   * <p>This function also supports variable centers of rotation. During normal operations, the
094   * center of rotation is usually the same as the physical center of the robot; therefore, the
095   * argument is defaulted to that use case. However, if you wish to change the center of rotation
096   * for evasive maneuvers, vision alignment, or for any other use case, you can do so.
097   *
098   * @param chassisVelocities The desired chassis velocity.
099   * @param centerOfRotation The center of rotation. For example, if you set the center of rotation
100   *     at one corner of the robot and provide a chassis velocity that only has a dtheta component,
101   *     the robot will rotate around that corner.
102   * @return The wheel velocities. Use caution because they are not normalized. Sometimes, a user
103   *     input may cause one of the wheel velocities to go above the attainable max velocity. Use
104   *     the {@link MecanumDriveWheelVelocities#desaturate(double)} function to rectify this issue.
105   */
106  public MecanumDriveWheelVelocities toWheelVelocities(
107      ChassisVelocities chassisVelocities, Translation2d centerOfRotation) {
108    // We have a new center of rotation. We need to compute the matrix again.
109    if (!centerOfRotation.equals(m_prevCoR)) {
110      var fl = m_frontLeftWheel.minus(centerOfRotation);
111      var fr = m_frontRightWheel.minus(centerOfRotation);
112      var rl = m_rearLeftWheel.minus(centerOfRotation);
113      var rr = m_rearRightWheel.minus(centerOfRotation);
114
115      setInverseKinematics(fl, fr, rl, rr);
116      m_prevCoR = centerOfRotation;
117    }
118
119    var chassisVelocitiesVector = new SimpleMatrix(3, 1);
120    chassisVelocitiesVector.setColumn(
121        0, 0, chassisVelocities.vx, chassisVelocities.vy, chassisVelocities.omega);
122
123    var wheelsVector = m_inverseKinematics.mult(chassisVelocitiesVector);
124    return new MecanumDriveWheelVelocities(
125        wheelsVector.get(0, 0),
126        wheelsVector.get(1, 0),
127        wheelsVector.get(2, 0),
128        wheelsVector.get(3, 0));
129  }
130
131  /**
132   * Performs inverse kinematics. See {@link #toWheelVelocities(ChassisVelocities, Translation2d)}
133   * for more information.
134   *
135   * @param chassisVelocities The desired chassis velocity.
136   * @return The wheel velocities.
137   */
138  @Override
139  public MecanumDriveWheelVelocities toWheelVelocities(ChassisVelocities chassisVelocities) {
140    return toWheelVelocities(chassisVelocities, Translation2d.kZero);
141  }
142
143  /**
144   * Performs forward kinematics to return the resulting chassis state from the given wheel
145   * velocities. This method is often used for odometry -- determining the robot's position on the
146   * field using data from the real-world velocity of each wheel on the robot.
147   *
148   * @param wheelVelocities The current mecanum drive wheel velocities.
149   * @return The resulting chassis velocity.
150   */
151  @Override
152  public ChassisVelocities toChassisVelocities(MecanumDriveWheelVelocities wheelVelocities) {
153    var wheelVelocitiesVector = new SimpleMatrix(4, 1);
154    wheelVelocitiesVector.setColumn(
155        0,
156        0,
157        wheelVelocities.frontLeft,
158        wheelVelocities.frontRight,
159        wheelVelocities.rearLeft,
160        wheelVelocities.rearRight);
161    var chassisVelocitiesVector = m_forwardKinematics.mult(wheelVelocitiesVector);
162
163    return new ChassisVelocities(
164        chassisVelocitiesVector.get(0, 0),
165        chassisVelocitiesVector.get(1, 0),
166        chassisVelocitiesVector.get(2, 0));
167  }
168
169  /**
170   * Performs inverse kinematics to return the wheel accelerations from a desired chassis
171   * acceleration. This method is often used for dynamics calculations -- converting desired robot
172   * accelerations into individual wheel accelerations.
173   *
174   * <p>This function also supports variable centers of rotation. During normal operations, the
175   * center of rotation is usually the same as the physical center of the robot; therefore, the
176   * argument is defaulted to that use case. However, if you wish to change the center of rotation
177   * for evasive maneuvers, vision alignment, or for any other use case, you can do so.
178   *
179   * @param chassisAccelerations The desired chassis accelerations.
180   * @param centerOfRotation The center of rotation. For example, if you set the center of rotation
181   *     at one corner of the robot and provide a chassis acceleration that only has a dtheta
182   *     component, the robot will rotate around that corner.
183   * @return The wheel accelerations.
184   */
185  public MecanumDriveWheelAccelerations toWheelAccelerations(
186      ChassisAccelerations chassisAccelerations, Translation2d centerOfRotation) {
187    // We have a new center of rotation. We need to compute the matrix again.
188    if (!centerOfRotation.equals(m_prevCoR)) {
189      var fl = m_frontLeftWheel.minus(centerOfRotation);
190      var fr = m_frontRightWheel.minus(centerOfRotation);
191      var rl = m_rearLeftWheel.minus(centerOfRotation);
192      var rr = m_rearRightWheel.minus(centerOfRotation);
193
194      setInverseKinematics(fl, fr, rl, rr);
195      m_prevCoR = centerOfRotation;
196    }
197
198    var chassisAccelerationsVector = new SimpleMatrix(3, 1);
199    chassisAccelerationsVector.setColumn(
200        0, 0, chassisAccelerations.ax, chassisAccelerations.ay, chassisAccelerations.alpha);
201
202    var wheelsVector = m_inverseKinematics.mult(chassisAccelerationsVector);
203    return new MecanumDriveWheelAccelerations(
204        wheelsVector.get(0, 0),
205        wheelsVector.get(1, 0),
206        wheelsVector.get(2, 0),
207        wheelsVector.get(3, 0));
208  }
209
210  /**
211   * Performs inverse kinematics. See {@link #toWheelAccelerations(ChassisAccelerations,
212   * Translation2d)} for more information.
213   *
214   * @param chassisAccelerations The desired chassis accelerations.
215   * @return The wheel accelerations.
216   */
217  @Override
218  public MecanumDriveWheelAccelerations toWheelAccelerations(
219      ChassisAccelerations chassisAccelerations) {
220    return toWheelAccelerations(chassisAccelerations, Translation2d.kZero);
221  }
222
223  /**
224   * Performs forward kinematics to return the resulting chassis accelerations from the given wheel
225   * accelerations. This method is often used for dynamics calculations -- determining the robot's
226   * acceleration on the field using data from the real-world acceleration of each wheel on the
227   * robot.
228   *
229   * @param wheelAccelerations The current mecanum drive wheel accelerations.
230   * @return The resulting chassis accelerations.
231   */
232  @Override
233  public ChassisAccelerations toChassisAccelerations(
234      MecanumDriveWheelAccelerations wheelAccelerations) {
235    var wheelAccelerationsVector = new SimpleMatrix(4, 1);
236    wheelAccelerationsVector.setColumn(
237        0,
238        0,
239        wheelAccelerations.frontLeft,
240        wheelAccelerations.frontRight,
241        wheelAccelerations.rearLeft,
242        wheelAccelerations.rearRight);
243    var chassisAccelerationsVector = m_forwardKinematics.mult(wheelAccelerationsVector);
244
245    return new ChassisAccelerations(
246        chassisAccelerationsVector.get(0, 0),
247        chassisAccelerationsVector.get(1, 0),
248        chassisAccelerationsVector.get(2, 0));
249  }
250
251  @Override
252  public Twist2d toTwist2d(MecanumDriveWheelPositions start, MecanumDriveWheelPositions end) {
253    var wheelDeltasVector = new SimpleMatrix(4, 1);
254    wheelDeltasVector.setColumn(
255        0,
256        0,
257        end.frontLeft - start.frontLeft,
258        end.frontRight - start.frontRight,
259        end.rearLeft - start.rearLeft,
260        end.rearRight - start.rearRight);
261    var twist = m_forwardKinematics.mult(wheelDeltasVector);
262    return new Twist2d(twist.get(0, 0), twist.get(1, 0), twist.get(2, 0));
263  }
264
265  /**
266   * Performs forward kinematics to return the resulting Twist2d from the given wheel deltas. This
267   * method is often used for odometry -- determining the robot's position on the field using
268   * changes in the distance driven by each wheel on the robot.
269   *
270   * @param wheelDeltas The distances driven by each wheel.
271   * @return The resulting Twist2d.
272   */
273  public Twist2d toTwist2d(MecanumDriveWheelPositions wheelDeltas) {
274    var wheelDeltasVector = new SimpleMatrix(4, 1);
275    wheelDeltasVector.setColumn(
276        0,
277        0,
278        wheelDeltas.frontLeft,
279        wheelDeltas.frontRight,
280        wheelDeltas.rearLeft,
281        wheelDeltas.rearRight);
282    var twist = m_forwardKinematics.mult(wheelDeltasVector);
283    return new Twist2d(twist.get(0, 0), twist.get(1, 0), twist.get(2, 0));
284  }
285
286  /**
287   * Construct inverse kinematics matrix from wheel locations.
288   *
289   * @param fl The location of the front-left wheel relative to the physical center of the robot.
290   * @param fr The location of the front-right wheel relative to the physical center of the robot.
291   * @param rl The location of the rear-left wheel relative to the physical center of the robot.
292   * @param rr The location of the rear-right wheel relative to the physical center of the robot.
293   */
294  private void setInverseKinematics(
295      Translation2d fl, Translation2d fr, Translation2d rl, Translation2d rr) {
296    m_inverseKinematics.setRow(0, 0, 1, -1, -(fl.getX() + fl.getY()));
297    m_inverseKinematics.setRow(1, 0, 1, 1, fr.getX() - fr.getY());
298    m_inverseKinematics.setRow(2, 0, 1, 1, rl.getX() - rl.getY());
299    m_inverseKinematics.setRow(3, 0, 1, -1, -(rr.getX() + rr.getY()));
300  }
301
302  /**
303   * Returns the front-left wheel translation.
304   *
305   * @return The front-left wheel translation.
306   */
307  public Translation2d getFrontLeft() {
308    return m_frontLeftWheel;
309  }
310
311  /**
312   * Returns the front-right wheel translation.
313   *
314   * @return The front-right wheel translation.
315   */
316  public Translation2d getFrontRight() {
317    return m_frontRightWheel;
318  }
319
320  /**
321   * Returns the rear-left wheel translation.
322   *
323   * @return The rear-left wheel translation.
324   */
325  public Translation2d getRearLeft() {
326    return m_rearLeftWheel;
327  }
328
329  /**
330   * Returns the rear-right wheel translation.
331   *
332   * @return The rear-right wheel translation.
333   */
334  public Translation2d getRearRight() {
335    return m_rearRightWheel;
336  }
337
338  @Override
339  public MecanumDriveWheelPositions copy(MecanumDriveWheelPositions positions) {
340    return new MecanumDriveWheelPositions(
341        positions.frontLeft, positions.frontRight, positions.rearLeft, positions.rearRight);
342  }
343
344  @Override
345  public void copyInto(MecanumDriveWheelPositions positions, MecanumDriveWheelPositions output) {
346    output.frontLeft = positions.frontLeft;
347    output.frontRight = positions.frontRight;
348    output.rearLeft = positions.rearLeft;
349    output.rearRight = positions.rearRight;
350  }
351
352  @Override
353  public MecanumDriveWheelPositions interpolate(
354      MecanumDriveWheelPositions startValue, MecanumDriveWheelPositions endValue, double t) {
355    return startValue.interpolate(endValue, t);
356  }
357}