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 static org.wpilib.units.Units.MetersPerSecond;
008import static org.wpilib.units.Units.RadiansPerSecond;
009
010import java.util.Arrays;
011import org.ejml.simple.SimpleMatrix;
012import org.wpilib.annotation.NoDiscard;
013import org.wpilib.math.geometry.Rotation2d;
014import org.wpilib.math.geometry.Translation2d;
015import org.wpilib.math.geometry.Twist2d;
016import org.wpilib.math.kinematics.proto.SwerveDriveKinematicsProto;
017import org.wpilib.math.kinematics.struct.SwerveDriveKinematicsStruct;
018import org.wpilib.math.util.MathSharedStore;
019import org.wpilib.units.measure.AngularVelocity;
020import org.wpilib.units.measure.LinearVelocity;
021import org.wpilib.util.protobuf.ProtobufSerializable;
022import org.wpilib.util.struct.Struct;
023import org.wpilib.util.struct.StructSerializable;
024
025/**
026 * Helper class that converts a chassis velocity (dx, dy, and dtheta components) into individual
027 * module states (velocity and angle).
028 *
029 * <p>The inverse kinematics (converting from a desired chassis velocity to individual module
030 * states) uses the relative locations of the modules with respect to the center of rotation. The
031 * center of rotation for inverse kinematics is also variable. This means that you can set your
032 * center of rotation in a corner of the robot to perform special evasion maneuvers.
033 *
034 * <p>Forward kinematics (converting an array of module states into the overall chassis motion) is
035 * performs the exact opposite of what inverse kinematics does. Since this is an overdetermined
036 * system (more equations than variables), we use a least-squares approximation.
037 *
038 * <p>The inverse kinematics: [moduleVelocities] = [moduleLocations] * [chassisVelocities] We take
039 * the Moore-Penrose pseudoinverse of [moduleLocations] and then multiply by [moduleVelocities] to
040 * get our chassis velocities.
041 *
042 * <p>Forward kinematics is also used for odometry -- determining the position of the robot on the
043 * field using encoders and a gyro.
044 */
045@SuppressWarnings("overrides")
046public class SwerveDriveKinematics
047    implements Kinematics<
048            SwerveModulePosition[], SwerveModuleVelocity[], SwerveModuleAcceleration[]>,
049        ProtobufSerializable,
050        StructSerializable {
051  private final SimpleMatrix m_firstOrderInverseKinematics;
052  private final SimpleMatrix m_firstOrderForwardKinematics;
053  private final SimpleMatrix m_secondOrderInverseKinematics;
054  private final SimpleMatrix m_secondOrderForwardKinematics;
055
056  private final int m_numModules;
057  private final Translation2d[] m_modules;
058  private Rotation2d[] m_moduleHeadings;
059  private Translation2d m_prevCoR = Translation2d.kZero;
060
061  /**
062   * Constructs a swerve drive kinematics object. This takes in a variable number of module
063   * locations as Translation2d objects. The order in which you pass in the module locations is the
064   * same order that you will receive the module states when performing inverse kinematics. It is
065   * also expected that you pass in the module states in the same order when calling the forward
066   * kinematics methods.
067   *
068   * @param moduleTranslations The locations of the modules relative to the physical center of the
069   *     robot.
070   */
071  public SwerveDriveKinematics(Translation2d... moduleTranslations) {
072    if (moduleTranslations.length < 2) {
073      throw new IllegalArgumentException("A swerve drive requires at least two modules");
074    }
075    m_numModules = moduleTranslations.length;
076    m_modules = Arrays.copyOf(moduleTranslations, m_numModules);
077    m_moduleHeadings = new Rotation2d[m_numModules];
078    Arrays.fill(m_moduleHeadings, Rotation2d.kZero);
079    m_firstOrderInverseKinematics = new SimpleMatrix(m_numModules * 2, 3);
080    m_secondOrderInverseKinematics = new SimpleMatrix(m_numModules * 2, 4);
081
082    setInverseKinematics(Translation2d.kZero);
083
084    m_firstOrderForwardKinematics = m_firstOrderInverseKinematics.pseudoInverse();
085    m_secondOrderForwardKinematics = m_secondOrderInverseKinematics.pseudoInverse();
086
087    MathSharedStore.reportUsage("SwerveDriveKinematics", "");
088  }
089
090  /**
091   * Reset the internal swerve module headings.
092   *
093   * @param moduleHeadings The swerve module headings. The order of the module headings should be
094   *     same as passed into the constructor of this class.
095   */
096  public void resetHeadings(Rotation2d... moduleHeadings) {
097    if (moduleHeadings.length != m_numModules) {
098      throw new IllegalArgumentException(
099          "Number of headings is not consistent with number of module locations provided in "
100              + "constructor");
101    }
102    m_moduleHeadings = Arrays.copyOf(moduleHeadings, m_numModules);
103  }
104
105  /**
106   * Performs inverse kinematics to return the module states from a desired chassis velocity. This
107   * method is often used to convert joystick values into module velocities and angles.
108   *
109   * <p>This function also supports variable centers of rotation. During normal operations, the
110   * center of rotation is usually the same as the physical center of the robot; therefore, the
111   * argument is defaulted to that use case. However, if you wish to change the center of rotation
112   * for evasive maneuvers, vision alignment, or for any other use case, you can do so.
113   *
114   * <p>In the case that the desired chassis velocities are zero (i.e. the robot will be
115   * stationary), the previously calculated module angle will be maintained.
116   *
117   * @param chassisVelocities The desired chassis velocity.
118   * @param centerOfRotation The center of rotation. For example, if you set the center of rotation
119   *     at one corner of the robot and provide a chassis velocity that only has a dtheta component,
120   *     the robot will rotate around that corner.
121   * @return An array containing the module states. Use caution because these module states are not
122   *     normalized. Sometimes, a user input may cause one of the module velocities to go above the
123   *     attainable max velocity. Use the {@link #desaturateWheelVelocities(SwerveModuleVelocity[],
124   *     double) DesaturateWheelVelocities} function to rectify this issue.
125   */
126  public SwerveModuleVelocity[] toSwerveModuleVelocities(
127      ChassisVelocities chassisVelocities, Translation2d centerOfRotation) {
128    var moduleVelocities = new SwerveModuleVelocity[m_numModules];
129
130    if (chassisVelocities.vx == 0.0
131        && chassisVelocities.vy == 0.0
132        && chassisVelocities.omega == 0.0) {
133      for (int i = 0; i < m_numModules; i++) {
134        moduleVelocities[i] = new SwerveModuleVelocity(0.0, m_moduleHeadings[i]);
135      }
136
137      return moduleVelocities;
138    }
139
140    if (!centerOfRotation.equals(m_prevCoR)) {
141      setInverseKinematics(centerOfRotation);
142    }
143
144    var chassisVelocitiesVector = new SimpleMatrix(3, 1);
145    chassisVelocitiesVector.setColumn(
146        0, 0, chassisVelocities.vx, chassisVelocities.vy, chassisVelocities.omega);
147
148    var moduleVelocitiesMatrix = m_firstOrderInverseKinematics.mult(chassisVelocitiesVector);
149
150    for (int i = 0; i < m_numModules; i++) {
151      double x = moduleVelocitiesMatrix.get(i * 2, 0);
152      double y = moduleVelocitiesMatrix.get(i * 2 + 1, 0);
153
154      double velocity = Math.hypot(x, y);
155      Rotation2d angle = velocity > 1e-6 ? new Rotation2d(x, y) : m_moduleHeadings[i];
156
157      moduleVelocities[i] = new SwerveModuleVelocity(velocity, angle);
158      m_moduleHeadings[i] = angle;
159    }
160
161    return moduleVelocities;
162  }
163
164  /**
165   * Performs inverse kinematics. See {@link #toSwerveModuleVelocities(ChassisVelocities,
166   * Translation2d)} toSwerveModuleVelocities for more information.
167   *
168   * @param chassisVelocities The desired chassis velocity.
169   * @return An array containing the module states.
170   */
171  public SwerveModuleVelocity[] toSwerveModuleVelocities(ChassisVelocities chassisVelocities) {
172    return toSwerveModuleVelocities(chassisVelocities, Translation2d.kZero);
173  }
174
175  @Override
176  public SwerveModuleVelocity[] toWheelVelocities(ChassisVelocities chassisVelocities) {
177    return toSwerveModuleVelocities(chassisVelocities);
178  }
179
180  /**
181   * Performs forward kinematics to return the resulting chassis state from the given module states.
182   * This method is often used for odometry -- determining the robot's position on the field using
183   * data from the real-world velocity and angle of each module on the robot.
184   *
185   * @param moduleVelocities The state of the modules (as a SwerveModuleVelocity type) as measured
186   *     from respective encoders and gyros. The order of the swerve module states should be same as
187   *     passed into the constructor of this class.
188   * @return The resulting chassis velocity.
189   */
190  @Override
191  public ChassisVelocities toChassisVelocities(SwerveModuleVelocity... moduleVelocities) {
192    if (moduleVelocities.length != m_numModules) {
193      throw new IllegalArgumentException(
194          "Number of modules is not consistent with number of module locations provided in "
195              + "constructor");
196    }
197    var moduleVelocitiesMatrix = new SimpleMatrix(m_numModules * 2, 1);
198
199    for (int i = 0; i < m_numModules; i++) {
200      var module = moduleVelocities[i];
201      moduleVelocitiesMatrix.set(i * 2, 0, module.velocity * module.angle.getCos());
202      moduleVelocitiesMatrix.set(i * 2 + 1, module.velocity * module.angle.getSin());
203    }
204
205    var chassisVelocitiesVector = m_firstOrderForwardKinematics.mult(moduleVelocitiesMatrix);
206    return new ChassisVelocities(
207        chassisVelocitiesVector.get(0, 0),
208        chassisVelocitiesVector.get(1, 0),
209        chassisVelocitiesVector.get(2, 0));
210  }
211
212  /**
213   * Performs forward kinematics to return the resulting chassis state from the given module states.
214   * This method is often used for odometry -- determining the robot's position on the field using
215   * data from the real-world velocity and angle of each module on the robot.
216   *
217   * @param moduleDeltas The latest change in position of the modules (as a SwerveModulePosition
218   *     type) as measured from respective encoders and gyros. The order of the swerve module states
219   *     should be same as passed into the constructor of this class.
220   * @return The resulting Twist2d.
221   */
222  public Twist2d toTwist2d(SwerveModulePosition... moduleDeltas) {
223    if (moduleDeltas.length != m_numModules) {
224      throw new IllegalArgumentException(
225          "Number of modules is not consistent with number of module locations provided in "
226              + "constructor");
227    }
228    var moduleDeltaMatrix = new SimpleMatrix(m_numModules * 2, 1);
229
230    for (int i = 0; i < m_numModules; i++) {
231      var module = moduleDeltas[i];
232      moduleDeltaMatrix.set(i * 2, 0, module.distance * module.angle.getCos());
233      moduleDeltaMatrix.set(i * 2 + 1, 0, module.distance * module.angle.getSin());
234    }
235
236    var chassisDeltaVector = m_firstOrderForwardKinematics.mult(moduleDeltaMatrix);
237    return new Twist2d(
238        chassisDeltaVector.get(0, 0), chassisDeltaVector.get(1, 0), chassisDeltaVector.get(2, 0));
239  }
240
241  @Override
242  public Twist2d toTwist2d(SwerveModulePosition[] start, SwerveModulePosition[] end) {
243    if (start.length != end.length) {
244      throw new IllegalArgumentException("Inconsistent number of modules!");
245    }
246    var newPositions = new SwerveModulePosition[start.length];
247    for (int i = 0; i < start.length; i++) {
248      newPositions[i] = new SwerveModulePosition(end[i].distance - start[i].distance, end[i].angle);
249    }
250    return toTwist2d(newPositions);
251  }
252
253  /**
254   * Renormalizes the wheel velocities if any individual velocity is above the specified maximum.
255   *
256   * <p>Sometimes, after inverse kinematics, the requested velocity from one or more modules may be
257   * above the max attainable velocity for the driving motor on that module. To fix this issue, one
258   * can reduce all the wheel velocities to make sure that all requested module velocities are
259   * at-or-below the absolute threshold, while maintaining the ratio of velocities between modules.
260   *
261   * <p>Scaling down the module velocities rotates the direction of net motion in the opposite
262   * direction of rotational velocity, which makes discretizing the chassis velocities inaccurate
263   * because the discretization did not account for this translational skew.
264   *
265   * @param moduleVelocities The array of module velocities.
266   * @param attainableMaxVelocity The absolute max velocity in meters per second that a module can
267   *     reach.
268   * @return The array of desaturated module velocities.
269   */
270  @NoDiscard
271  public static SwerveModuleVelocity[] desaturateWheelVelocities(
272      SwerveModuleVelocity[] moduleVelocities, double attainableMaxVelocity) {
273    double realMaxVelocity = 0;
274    for (SwerveModuleVelocity moduleVelocity : moduleVelocities) {
275      realMaxVelocity = Math.max(realMaxVelocity, Math.abs(moduleVelocity.velocity));
276    }
277    var velocities = new SwerveModuleVelocity[moduleVelocities.length];
278    if (realMaxVelocity > attainableMaxVelocity) {
279      for (int i = 0; i < velocities.length; i++) {
280        velocities[i] =
281            new SwerveModuleVelocity(
282                moduleVelocities[i].velocity / realMaxVelocity * attainableMaxVelocity,
283                moduleVelocities[i].angle);
284      }
285    } else {
286      // Copy in the event someone wants to mutate the desaturated velocities but also wants the
287      // original velocities
288      for (int i = 0; i < velocities.length; i++) {
289        velocities[i] =
290            new SwerveModuleVelocity(moduleVelocities[i].velocity, moduleVelocities[i].angle);
291      }
292    }
293    return velocities;
294  }
295
296  /**
297   * Renormalizes the wheel velocities if any individual velocity is above the specified maximum.
298   *
299   * <p>Sometimes, after inverse kinematics, the requested velocity from one or more modules may be
300   * above the max attainable velocity for the driving motor on that module. To fix this issue, one
301   * can reduce all the wheel velocities to make sure that all requested module velocities are
302   * at-or-below the absolute threshold, while maintaining the ratio of velocities between modules.
303   *
304   * <p>Scaling down the module velocities rotates the direction of net motion in the opposite
305   * direction of rotational velocity, which makes discretizing the chassis velocities inaccurate
306   * because the discretization did not account for this translational skew.
307   *
308   * @param moduleVelocities The array of module velocities.
309   * @param attainableMaxVelocity The absolute max velocity in meters per second that a module can
310   *     reach.
311   * @return The array of desaturated module velocities.
312   */
313  @NoDiscard
314  public static SwerveModuleVelocity[] desaturateWheelVelocities(
315      SwerveModuleVelocity[] moduleVelocities, LinearVelocity attainableMaxVelocity) {
316    return desaturateWheelVelocities(moduleVelocities, attainableMaxVelocity.in(MetersPerSecond));
317  }
318
319  /**
320   * Renormalizes the wheel velocities if any individual velocity is above the specified maximum, as
321   * well as getting rid of joystick saturation at edges of joystick.
322   *
323   * <p>Sometimes, after inverse kinematics, the requested velocity from one or more modules may be
324   * above the max attainable velocity for the driving motor on that module. To fix this issue, one
325   * can reduce all the wheel velocities to make sure that all requested module velocities are
326   * at-or-below the absolute threshold, while maintaining the ratio of velocities between modules.
327   *
328   * <p>Scaling down the module velocities rotates the direction of net motion in the opposite
329   * direction of rotational velocity, which makes discretizing the chassis velocities inaccurate
330   * because the discretization did not account for this translational skew.
331   *
332   * @param moduleVelocities The array of module velocities
333   * @param desiredChassisVelocity The desired velocity of the robot
334   * @param attainableMaxModuleVelocity The absolute max velocity in meters per second that a module
335   *     can reach
336   * @param attainableMaxTranslationalVelocity The absolute max velocity in meters per second that
337   *     your robot can reach while translating
338   * @param attainableMaxRotationalVelocity The absolute max velocity in radians per second the
339   *     robot can reach while rotating
340   * @return The array of desaturated module velocities
341   */
342  @NoDiscard
343  public static SwerveModuleVelocity[] desaturateWheelVelocities(
344      SwerveModuleVelocity[] moduleVelocities,
345      ChassisVelocities desiredChassisVelocity,
346      double attainableMaxModuleVelocity,
347      double attainableMaxTranslationalVelocity,
348      double attainableMaxRotationalVelocity) {
349    double realMaxVelocity = 0;
350    for (SwerveModuleVelocity moduleVelocity : moduleVelocities) {
351      realMaxVelocity = Math.max(realMaxVelocity, Math.abs(moduleVelocity.velocity));
352    }
353
354    var velocities = new SwerveModuleVelocity[moduleVelocities.length];
355    if (attainableMaxTranslationalVelocity == 0
356        || attainableMaxRotationalVelocity == 0
357        || realMaxVelocity == 0) {
358      // Copy in the event someone wants to mutate the desaturated velocities but also wants the
359      // original velocities
360      for (int i = 0; i < velocities.length; i++) {
361        velocities[i] =
362            new SwerveModuleVelocity(moduleVelocities[i].velocity, moduleVelocities[i].angle);
363      }
364      return velocities;
365    }
366    double translationalK =
367        Math.hypot(desiredChassisVelocity.vx, desiredChassisVelocity.vy)
368            / attainableMaxTranslationalVelocity;
369    double rotationalK = Math.abs(desiredChassisVelocity.omega) / attainableMaxRotationalVelocity;
370    double k = Math.max(translationalK, rotationalK);
371    double scale = Math.min(k * attainableMaxModuleVelocity / realMaxVelocity, 1);
372    for (int i = 0; i < velocities.length; i++) {
373      velocities[i] =
374          new SwerveModuleVelocity(moduleVelocities[i].velocity * scale, moduleVelocities[i].angle);
375    }
376    return velocities;
377  }
378
379  /**
380   * Renormalizes the wheel velocities if any individual velocity is above the specified maximum, as
381   * well as getting rid of joystick saturation at edges of joystick.
382   *
383   * <p>Sometimes, after inverse kinematics, the requested velocity from one or more modules may be
384   * above the max attainable velocity for the driving motor on that module. To fix this issue, one
385   * can reduce all the wheel velocities to make sure that all requested module velocities are
386   * at-or-below the absolute threshold, while maintaining the ratio of velocities between modules.
387   *
388   * <p>Scaling down the module velocities rotates the direction of net motion in the opposite
389   * direction of rotational velocity, which makes discretizing the chassis velocities inaccurate
390   * because the discretization did not account for this translational skew.
391   *
392   * @param moduleVelocities The array of module velocities
393   * @param desiredChassisVelocity The desired velocity of the robot
394   * @param attainableMaxModuleVelocity The absolute max velocity that a module can reach
395   * @param attainableMaxTranslationalVelocity The absolute max velocity that your robot can reach
396   *     while translating
397   * @param attainableMaxRotationalVelocity The absolute max velocity the robot can reach while
398   *     rotating
399   * @return The array of desaturated module velocities
400   */
401  @NoDiscard
402  public static SwerveModuleVelocity[] desaturateWheelVelocities(
403      SwerveModuleVelocity[] moduleVelocities,
404      ChassisVelocities desiredChassisVelocity,
405      LinearVelocity attainableMaxModuleVelocity,
406      LinearVelocity attainableMaxTranslationalVelocity,
407      AngularVelocity attainableMaxRotationalVelocity) {
408    return desaturateWheelVelocities(
409        moduleVelocities,
410        desiredChassisVelocity,
411        attainableMaxModuleVelocity.in(MetersPerSecond),
412        attainableMaxTranslationalVelocity.in(MetersPerSecond),
413        attainableMaxRotationalVelocity.in(RadiansPerSecond));
414  }
415
416  @Override
417  public SwerveModulePosition[] copy(SwerveModulePosition[] positions) {
418    var newPositions = new SwerveModulePosition[positions.length];
419    for (int i = 0; i < positions.length; ++i) {
420      newPositions[i] = positions[i].copy();
421    }
422    return newPositions;
423  }
424
425  @Override
426  public void copyInto(SwerveModulePosition[] positions, SwerveModulePosition[] output) {
427    if (positions.length != output.length) {
428      throw new IllegalArgumentException("Inconsistent number of modules!");
429    }
430    for (int i = 0; i < positions.length; ++i) {
431      output[i].distance = positions[i].distance;
432      output[i].angle = positions[i].angle;
433    }
434  }
435
436  @Override
437  public SwerveModulePosition[] interpolate(
438      SwerveModulePosition[] startValue, SwerveModulePosition[] endValue, double t) {
439    if (endValue.length != startValue.length) {
440      throw new IllegalArgumentException("Inconsistent number of modules!");
441    }
442    var newPositions = new SwerveModulePosition[startValue.length];
443    for (int i = 0; i < startValue.length; ++i) {
444      newPositions[i] = startValue[i].interpolate(endValue[i], t);
445    }
446    return newPositions;
447  }
448
449  /**
450   * Gets the locations of the modules relative to the center of rotation.
451   *
452   * @return The locations of the modules relative to the center of rotation. This array should not
453   *     be modified.
454   */
455  @SuppressWarnings("PMD.MethodReturnsInternalArray")
456  public Translation2d[] getModules() {
457    return m_modules;
458  }
459
460  /** SwerveDriveKinematics protobuf for serialization. */
461  public static final SwerveDriveKinematicsProto proto = new SwerveDriveKinematicsProto();
462
463  /**
464   * Creates an implementation of the {@link Struct} interface for swerve drive kinematics objects.
465   *
466   * @param numModules The number of modules of the kinematics objects this serializer processes.
467   * @return The struct implementation.
468   */
469  public static final SwerveDriveKinematicsStruct getStruct(int numModules) {
470    return new SwerveDriveKinematicsStruct(numModules);
471  }
472
473  /**
474   * Performs inverse kinematics to return the module accelerations from a desired chassis
475   * acceleration. This method is often used for dynamics calculations -- converting desired robot
476   * accelerations into individual module accelerations.
477   *
478   * <p>This function also supports variable centers of rotation. During normal operations, the
479   * center of rotation is usually the same as the physical center of the robot; therefore, the
480   * argument is defaulted to that use case. However, if you wish to change the center of rotation
481   * for evasive maneuvers, vision alignment, or for any other use case, you can do so.
482   *
483   * @param chassisAccelerations The desired chassis accelerations.
484   * @param angularVelocity The desired robot angular velocity.
485   * @param centerOfRotation The center of rotation. For example, if you set the center of rotation
486   *     at one corner of the robot and provide a chassis acceleration that only has a dtheta
487   *     component, the robot will rotate around that corner.
488   * @return An array containing the module accelerations.
489   */
490  public SwerveModuleAcceleration[] toSwerveModuleAccelerations(
491      ChassisAccelerations chassisAccelerations,
492      double angularVelocity,
493      Translation2d centerOfRotation) {
494    // Derivation for second-order kinematics from "Swerve Drive Second Order Kinematics"
495    // by FRC Team 449 - The Blair Robot Project, Rafi Pedersen
496    // https://www.chiefdelphi.com/uploads/short-url/qzj4k2LyBs7rLxAem0YajNIlStH.pdf
497
498    var moduleAccelerations = new SwerveModuleAcceleration[m_numModules];
499
500    if (chassisAccelerations.ax == 0.0
501        && chassisAccelerations.ay == 0.0
502        && chassisAccelerations.alpha == 0.0) {
503      for (int i = 0; i < m_numModules; i++) {
504        moduleAccelerations[i] = new SwerveModuleAcceleration(0.0, Rotation2d.kZero);
505      }
506      return moduleAccelerations;
507    }
508
509    if (!centerOfRotation.equals(m_prevCoR)) {
510      setInverseKinematics(centerOfRotation);
511    }
512
513    var chassisAccelerationsVector = new SimpleMatrix(4, 1);
514    chassisAccelerationsVector.setColumn(
515        0,
516        0,
517        chassisAccelerations.ax,
518        chassisAccelerations.ay,
519        angularVelocity * angularVelocity,
520        chassisAccelerations.alpha);
521
522    var moduleAccelerationsMatrix = m_secondOrderInverseKinematics.mult(chassisAccelerationsVector);
523
524    for (int i = 0; i < m_numModules; i++) {
525      double x = moduleAccelerationsMatrix.get(i * 2, 0);
526      double y = moduleAccelerationsMatrix.get(i * 2 + 1, 0);
527
528      // For swerve modules, we need to compute both linear acceleration and angular acceleration
529      // The linear acceleration is the magnitude of the acceleration vector
530      double linearAcceleration = Math.hypot(x, y);
531
532      if (linearAcceleration <= 1e-6) {
533        moduleAccelerations[i] = new SwerveModuleAcceleration(linearAcceleration, Rotation2d.kZero);
534      } else {
535        moduleAccelerations[i] =
536            new SwerveModuleAcceleration(linearAcceleration, new Rotation2d(x, y));
537      }
538    }
539
540    return moduleAccelerations;
541  }
542
543  /**
544   * Performs inverse kinematics. See {@link #toSwerveModuleAccelerations(ChassisAccelerations,
545   * double, Translation2d)} toSwerveModuleAccelerations for more information.
546   *
547   * @param chassisAccelerations The desired chassis accelerations.
548   * @param angularVelocity The desired robot angular velocity.
549   * @return An array containing the module accelerations.
550   */
551  public SwerveModuleAcceleration[] toSwerveModuleAccelerations(
552      ChassisAccelerations chassisAccelerations, double angularVelocity) {
553    return toSwerveModuleAccelerations(chassisAccelerations, angularVelocity, Translation2d.kZero);
554  }
555
556  @Override
557  public SwerveModuleAcceleration[] toWheelAccelerations(
558      ChassisAccelerations chassisAccelerations) {
559    return toSwerveModuleAccelerations(chassisAccelerations, 0.0);
560  }
561
562  /**
563   * Performs forward kinematics to return the resulting chassis accelerations from the given module
564   * accelerations. This method is often used for dynamics calculations -- determining the robot's
565   * acceleration on the field using data from the real-world acceleration of each module on the
566   * robot.
567   *
568   * @param moduleAccelerations The accelerations of the modules as measured from respective
569   *     encoders and gyros. The order of the swerve module accelerations should be same as passed
570   *     into the constructor of this class.
571   * @return The resulting chassis accelerations.
572   */
573  @Override
574  public ChassisAccelerations toChassisAccelerations(
575      SwerveModuleAcceleration... moduleAccelerations) {
576    // Derivation for second-order kinematics from "Swerve Drive Second Order Kinematics"
577    // by FRC Team 449 - The Blair Robot Project, Rafi Pedersen
578    // https://www.chiefdelphi.com/uploads/short-url/qzj4k2LyBs7rLxAem0YajNIlStH.pdf
579
580    if (moduleAccelerations.length != m_numModules) {
581      throw new IllegalArgumentException(
582          "Number of modules is not consistent with number of module locations provided in "
583              + "constructor");
584    }
585    var moduleAccelerationsMatrix = new SimpleMatrix(m_numModules * 2, 1);
586
587    for (int i = 0; i < m_numModules; i++) {
588      var module = moduleAccelerations[i];
589
590      moduleAccelerationsMatrix.set(i * 2 + 0, 0, module.acceleration * module.angle.getCos());
591      moduleAccelerationsMatrix.set(i * 2 + 1, 0, module.acceleration * module.angle.getSin());
592    }
593
594    var chassisAccelerationsVector = m_secondOrderForwardKinematics.mult(moduleAccelerationsMatrix);
595
596    // the second order kinematics equation for swerve drive yields a state vector [aₓ, a_y, ω², α]
597    return new ChassisAccelerations(
598        chassisAccelerationsVector.get(0, 0),
599        chassisAccelerationsVector.get(1, 0),
600        chassisAccelerationsVector.get(3, 0));
601  }
602
603  /**
604   * Sets both inverse kinematics matrices based on the new center of rotation. This does not check
605   * if the new center of rotation is different from the previous one, so a check should be included
606   * before the call to this function.
607   *
608   * @param centerOfRotation new center of rotation
609   */
610  private void setInverseKinematics(Translation2d centerOfRotation) {
611    for (int i = 0; i < m_numModules; i++) {
612      var rx = m_modules[i].getX() - centerOfRotation.getX();
613      var ry = m_modules[i].getY() - centerOfRotation.getY();
614
615      m_firstOrderInverseKinematics.setRow(i * 2 + 0, 0, /* Start Data */ 1, 0, -ry);
616      m_firstOrderInverseKinematics.setRow(i * 2 + 1, 0, /* Start Data */ 0, 1, rx);
617
618      m_secondOrderInverseKinematics.setRow(i * 2 + 0, 0, /* Start Data */ 1, 0, -rx, -ry);
619      m_secondOrderInverseKinematics.setRow(i * 2 + 1, 0, /* Start Data */ 0, 1, -ry, +rx);
620    }
621    m_prevCoR = centerOfRotation;
622  }
623}