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 static edu.wpi.first.units.Units.MetersPerSecond; 008import static edu.wpi.first.units.Units.RadiansPerSecond; 009 010import edu.wpi.first.math.MathSharedStore; 011import edu.wpi.first.math.MathUsageId; 012import edu.wpi.first.math.geometry.Rotation2d; 013import edu.wpi.first.math.geometry.Translation2d; 014import edu.wpi.first.math.geometry.Twist2d; 015import edu.wpi.first.units.Angle; 016import edu.wpi.first.units.Distance; 017import edu.wpi.first.units.Measure; 018import edu.wpi.first.units.Velocity; 019import java.util.Arrays; 020import org.ejml.simple.SimpleMatrix; 021 022/** 023 * Helper class that converts a chassis velocity (dx, dy, and dtheta components) into individual 024 * module states (speed and angle). 025 * 026 * <p>The inverse kinematics (converting from a desired chassis velocity to individual module 027 * states) uses the relative locations of the modules with respect to the center of rotation. The 028 * center of rotation for inverse kinematics is also variable. This means that you can set your 029 * center of rotation in a corner of the robot to perform special evasion maneuvers. 030 * 031 * <p>Forward kinematics (converting an array of module states into the overall chassis motion) is 032 * performs the exact opposite of what inverse kinematics does. Since this is an overdetermined 033 * system (more equations than variables), we use a least-squares approximation. 034 * 035 * <p>The inverse kinematics: [moduleStates] = [moduleLocations] * [chassisSpeeds] We take the 036 * Moore-Penrose pseudoinverse of [moduleLocations] and then multiply by [moduleStates] to get our 037 * chassis speeds. 038 * 039 * <p>Forward kinematics is also used for odometry -- determining the position of the robot on the 040 * field using encoders and a gyro. 041 */ 042public class SwerveDriveKinematics 043 implements Kinematics<SwerveDriveKinematics.SwerveDriveWheelStates, SwerveDriveWheelPositions> { 044 public static class SwerveDriveWheelStates { 045 public SwerveModuleState[] states; 046 047 /** 048 * Creates a new SwerveDriveWheelStates instance. 049 * 050 * @param states The swerve module states. This will be deeply copied. 051 */ 052 public SwerveDriveWheelStates(SwerveModuleState[] states) { 053 this.states = new SwerveModuleState[states.length]; 054 for (int i = 0; i < states.length; i++) { 055 this.states[i] = new SwerveModuleState(states[i].speedMetersPerSecond, states[i].angle); 056 } 057 } 058 } 059 060 private final SimpleMatrix m_inverseKinematics; 061 private final SimpleMatrix m_forwardKinematics; 062 063 private final int m_numModules; 064 private final Translation2d[] m_modules; 065 private Rotation2d[] m_moduleHeadings; 066 private Translation2d m_prevCoR = new Translation2d(); 067 068 /** 069 * Constructs a swerve drive kinematics object. This takes in a variable number of module 070 * locations as Translation2d objects. The order in which you pass in the module locations is the 071 * same order that you will receive the module states when performing inverse kinematics. It is 072 * also expected that you pass in the module states in the same order when calling the forward 073 * kinematics methods. 074 * 075 * @param moduleTranslationsMeters The locations of the modules relative to the physical center of 076 * the robot. 077 */ 078 public SwerveDriveKinematics(Translation2d... moduleTranslationsMeters) { 079 if (moduleTranslationsMeters.length < 2) { 080 throw new IllegalArgumentException("A swerve drive requires at least two modules"); 081 } 082 m_numModules = moduleTranslationsMeters.length; 083 m_modules = Arrays.copyOf(moduleTranslationsMeters, m_numModules); 084 m_moduleHeadings = new Rotation2d[m_numModules]; 085 Arrays.fill(m_moduleHeadings, new Rotation2d()); 086 m_inverseKinematics = new SimpleMatrix(m_numModules * 2, 3); 087 088 for (int i = 0; i < m_numModules; i++) { 089 m_inverseKinematics.setRow(i * 2 + 0, 0, /* Start Data */ 1, 0, -m_modules[i].getY()); 090 m_inverseKinematics.setRow(i * 2 + 1, 0, /* Start Data */ 0, 1, +m_modules[i].getX()); 091 } 092 m_forwardKinematics = m_inverseKinematics.pseudoInverse(); 093 094 MathSharedStore.reportUsage(MathUsageId.kKinematics_SwerveDrive, 1); 095 } 096 097 /** 098 * Reset the internal swerve module headings. 099 * 100 * @param moduleHeadings The swerve module headings. The order of the module headings should be 101 * same as passed into the constructor of this class. 102 */ 103 public void resetHeadings(Rotation2d... moduleHeadings) { 104 if (moduleHeadings.length != m_numModules) { 105 throw new IllegalArgumentException( 106 "Number of headings is not consistent with number of module locations provided in " 107 + "constructor"); 108 } 109 m_moduleHeadings = Arrays.copyOf(moduleHeadings, m_numModules); 110 } 111 112 /** 113 * Performs inverse kinematics to return the module states from a desired chassis velocity. This 114 * method is often used to convert joystick values into module speeds and angles. 115 * 116 * <p>This function also supports variable centers of rotation. During normal operations, the 117 * center of rotation is usually the same as the physical center of the robot; therefore, the 118 * argument is defaulted to that use case. However, if you wish to change the center of rotation 119 * for evasive maneuvers, vision alignment, or for any other use case, you can do so. 120 * 121 * <p>In the case that the desired chassis speeds are zero (i.e. the robot will be stationary), 122 * the previously calculated module angle will be maintained. 123 * 124 * @param chassisSpeeds The desired chassis speed. 125 * @param centerOfRotationMeters The center of rotation. For example, if you set the center of 126 * rotation at one corner of the robot and provide a chassis speed that only has a dtheta 127 * component, the robot will rotate around that corner. 128 * @return An array containing the module states. Use caution because these module states are not 129 * normalized. Sometimes, a user input may cause one of the module speeds to go above the 130 * attainable max velocity. Use the {@link #desaturateWheelSpeeds(SwerveModuleState[], double) 131 * DesaturateWheelSpeeds} function to rectify this issue. 132 */ 133 public SwerveModuleState[] toSwerveModuleStates( 134 ChassisSpeeds chassisSpeeds, Translation2d centerOfRotationMeters) { 135 var moduleStates = new SwerveModuleState[m_numModules]; 136 137 if (chassisSpeeds.vxMetersPerSecond == 0.0 138 && chassisSpeeds.vyMetersPerSecond == 0.0 139 && chassisSpeeds.omegaRadiansPerSecond == 0.0) { 140 for (int i = 0; i < m_numModules; i++) { 141 moduleStates[i] = new SwerveModuleState(0.0, m_moduleHeadings[i]); 142 } 143 144 return moduleStates; 145 } 146 147 if (!centerOfRotationMeters.equals(m_prevCoR)) { 148 for (int i = 0; i < m_numModules; i++) { 149 m_inverseKinematics.setRow( 150 i * 2 + 0, 151 0, /* Start Data */ 152 1, 153 0, 154 -m_modules[i].getY() + centerOfRotationMeters.getY()); 155 m_inverseKinematics.setRow( 156 i * 2 + 1, 157 0, /* Start Data */ 158 0, 159 1, 160 +m_modules[i].getX() - centerOfRotationMeters.getX()); 161 } 162 m_prevCoR = centerOfRotationMeters; 163 } 164 165 var chassisSpeedsVector = new SimpleMatrix(3, 1); 166 chassisSpeedsVector.setColumn( 167 0, 168 0, 169 chassisSpeeds.vxMetersPerSecond, 170 chassisSpeeds.vyMetersPerSecond, 171 chassisSpeeds.omegaRadiansPerSecond); 172 173 var moduleStatesMatrix = m_inverseKinematics.mult(chassisSpeedsVector); 174 175 for (int i = 0; i < m_numModules; i++) { 176 double x = moduleStatesMatrix.get(i * 2, 0); 177 double y = moduleStatesMatrix.get(i * 2 + 1, 0); 178 179 double speed = Math.hypot(x, y); 180 Rotation2d angle = new Rotation2d(x, y); 181 182 moduleStates[i] = new SwerveModuleState(speed, angle); 183 m_moduleHeadings[i] = angle; 184 } 185 186 return moduleStates; 187 } 188 189 /** 190 * Performs inverse kinematics. See {@link #toSwerveModuleStates(ChassisSpeeds, Translation2d)} 191 * toSwerveModuleStates for more information. 192 * 193 * @param chassisSpeeds The desired chassis speed. 194 * @return An array containing the module states. 195 */ 196 public SwerveModuleState[] toSwerveModuleStates(ChassisSpeeds chassisSpeeds) { 197 return toSwerveModuleStates(chassisSpeeds, new Translation2d()); 198 } 199 200 @Override 201 public SwerveDriveWheelStates toWheelSpeeds(ChassisSpeeds chassisSpeeds) { 202 return new SwerveDriveWheelStates(toSwerveModuleStates(chassisSpeeds)); 203 } 204 205 /** 206 * Performs forward kinematics to return the resulting chassis state from the given module states. 207 * This method is often used for odometry -- determining the robot's position on the field using 208 * data from the real-world speed and angle of each module on the robot. 209 * 210 * @param moduleStates The state of the modules (as a SwerveModuleState type) as measured from 211 * respective encoders and gyros. The order of the swerve module states should be same as 212 * passed into the constructor of this class. 213 * @return The resulting chassis speed. 214 */ 215 public ChassisSpeeds toChassisSpeeds(SwerveModuleState... moduleStates) { 216 if (moduleStates.length != m_numModules) { 217 throw new IllegalArgumentException( 218 "Number of modules is not consistent with number of module locations provided in " 219 + "constructor"); 220 } 221 var moduleStatesMatrix = new SimpleMatrix(m_numModules * 2, 1); 222 223 for (int i = 0; i < m_numModules; i++) { 224 var module = moduleStates[i]; 225 moduleStatesMatrix.set(i * 2, 0, module.speedMetersPerSecond * module.angle.getCos()); 226 moduleStatesMatrix.set(i * 2 + 1, module.speedMetersPerSecond * module.angle.getSin()); 227 } 228 229 var chassisSpeedsVector = m_forwardKinematics.mult(moduleStatesMatrix); 230 return new ChassisSpeeds( 231 chassisSpeedsVector.get(0, 0), 232 chassisSpeedsVector.get(1, 0), 233 chassisSpeedsVector.get(2, 0)); 234 } 235 236 @Override 237 public ChassisSpeeds toChassisSpeeds(SwerveDriveWheelStates wheelStates) { 238 return toChassisSpeeds(wheelStates.states); 239 } 240 241 /** 242 * Performs forward kinematics to return the resulting chassis state from the given module states. 243 * This method is often used for odometry -- determining the robot's position on the field using 244 * data from the real-world speed and angle of each module on the robot. 245 * 246 * @param moduleDeltas The latest change in position of the modules (as a SwerveModulePosition 247 * type) as measured from respective encoders and gyros. The order of the swerve module states 248 * should be same as passed into the constructor of this class. 249 * @return The resulting Twist2d. 250 */ 251 public Twist2d toTwist2d(SwerveModulePosition... moduleDeltas) { 252 if (moduleDeltas.length != m_numModules) { 253 throw new IllegalArgumentException( 254 "Number of modules is not consistent with number of module locations provided in " 255 + "constructor"); 256 } 257 var moduleDeltaMatrix = new SimpleMatrix(m_numModules * 2, 1); 258 259 for (int i = 0; i < m_numModules; i++) { 260 var module = moduleDeltas[i]; 261 moduleDeltaMatrix.set(i * 2, 0, module.distanceMeters * module.angle.getCos()); 262 moduleDeltaMatrix.set(i * 2 + 1, module.distanceMeters * module.angle.getSin()); 263 } 264 265 var chassisDeltaVector = m_forwardKinematics.mult(moduleDeltaMatrix); 266 return new Twist2d( 267 chassisDeltaVector.get(0, 0), chassisDeltaVector.get(1, 0), chassisDeltaVector.get(2, 0)); 268 } 269 270 @Override 271 public Twist2d toTwist2d(SwerveDriveWheelPositions start, SwerveDriveWheelPositions end) { 272 if (start.positions.length != end.positions.length) { 273 throw new IllegalArgumentException("Inconsistent number of modules!"); 274 } 275 var newPositions = new SwerveModulePosition[start.positions.length]; 276 for (int i = 0; i < start.positions.length; i++) { 277 var startModule = start.positions[i]; 278 var endModule = end.positions[i]; 279 newPositions[i] = 280 new SwerveModulePosition( 281 endModule.distanceMeters - startModule.distanceMeters, endModule.angle); 282 } 283 return toTwist2d(newPositions); 284 } 285 286 /** 287 * Renormalizes the wheel speeds if any individual speed is above the specified maximum. 288 * 289 * <p>Sometimes, after inverse kinematics, the requested speed from one or more modules may be 290 * above the max attainable speed for the driving motor on that module. To fix this issue, one can 291 * reduce all the wheel speeds to make sure that all requested module speeds are at-or-below the 292 * absolute threshold, while maintaining the ratio of speeds between modules. 293 * 294 * @param moduleStates Reference to array of module states. The array will be mutated with the 295 * normalized speeds! 296 * @param attainableMaxSpeedMetersPerSecond The absolute max speed that a module can reach. 297 */ 298 public static void desaturateWheelSpeeds( 299 SwerveModuleState[] moduleStates, double attainableMaxSpeedMetersPerSecond) { 300 double realMaxSpeed = 0; 301 for (SwerveModuleState moduleState : moduleStates) { 302 realMaxSpeed = Math.max(realMaxSpeed, Math.abs(moduleState.speedMetersPerSecond)); 303 } 304 if (realMaxSpeed > attainableMaxSpeedMetersPerSecond) { 305 for (SwerveModuleState moduleState : moduleStates) { 306 moduleState.speedMetersPerSecond = 307 moduleState.speedMetersPerSecond / realMaxSpeed * attainableMaxSpeedMetersPerSecond; 308 } 309 } 310 } 311 312 /** 313 * Renormalizes the wheel speeds if any individual speed is above the specified maximum. 314 * 315 * <p>Sometimes, after inverse kinematics, the requested speed from one or more modules may be 316 * above the max attainable speed for the driving motor on that module. To fix this issue, one can 317 * reduce all the wheel speeds to make sure that all requested module speeds are at-or-below the 318 * absolute threshold, while maintaining the ratio of speeds between modules. 319 * 320 * @param moduleStates Reference to array of module states. The array will be mutated with the 321 * normalized speeds! 322 * @param attainableMaxSpeed The absolute max speed that a module can reach. 323 */ 324 public static void desaturateWheelSpeeds( 325 SwerveModuleState[] moduleStates, Measure<Velocity<Distance>> attainableMaxSpeed) { 326 desaturateWheelSpeeds(moduleStates, attainableMaxSpeed.in(MetersPerSecond)); 327 } 328 329 /** 330 * Renormalizes the wheel speeds if any individual speed is above the specified maximum, as well 331 * as getting rid of joystick saturation at edges of joystick. 332 * 333 * <p>Sometimes, after inverse kinematics, the requested speed from one or more modules may be 334 * above the max attainable speed for the driving motor on that module. To fix this issue, one can 335 * reduce all the wheel speeds to make sure that all requested module speeds are at-or-below the 336 * absolute threshold, while maintaining the ratio of speeds between modules. 337 * 338 * @param moduleStates Reference to array of module states. The array will be mutated with the 339 * normalized speeds! 340 * @param desiredChassisSpeed The desired speed of the robot 341 * @param attainableMaxModuleSpeedMetersPerSecond The absolute max speed that a module can reach 342 * @param attainableMaxTranslationalSpeedMetersPerSecond The absolute max speed that your robot 343 * can reach while translating 344 * @param attainableMaxRotationalVelocityRadiansPerSecond The absolute max speed the robot can 345 * reach while rotating 346 */ 347 public static void desaturateWheelSpeeds( 348 SwerveModuleState[] moduleStates, 349 ChassisSpeeds desiredChassisSpeed, 350 double attainableMaxModuleSpeedMetersPerSecond, 351 double attainableMaxTranslationalSpeedMetersPerSecond, 352 double attainableMaxRotationalVelocityRadiansPerSecond) { 353 double realMaxSpeed = 0; 354 for (SwerveModuleState moduleState : moduleStates) { 355 realMaxSpeed = Math.max(realMaxSpeed, Math.abs(moduleState.speedMetersPerSecond)); 356 } 357 358 if (attainableMaxTranslationalSpeedMetersPerSecond == 0 359 || attainableMaxRotationalVelocityRadiansPerSecond == 0 360 || realMaxSpeed == 0) { 361 return; 362 } 363 double translationalK = 364 Math.hypot(desiredChassisSpeed.vxMetersPerSecond, desiredChassisSpeed.vyMetersPerSecond) 365 / attainableMaxTranslationalSpeedMetersPerSecond; 366 double rotationalK = 367 Math.abs(desiredChassisSpeed.omegaRadiansPerSecond) 368 / attainableMaxRotationalVelocityRadiansPerSecond; 369 double k = Math.max(translationalK, rotationalK); 370 double scale = Math.min(k * attainableMaxModuleSpeedMetersPerSecond / realMaxSpeed, 1); 371 for (SwerveModuleState moduleState : moduleStates) { 372 moduleState.speedMetersPerSecond *= scale; 373 } 374 } 375 376 /** 377 * Renormalizes the wheel speeds if any individual speed is above the specified maximum, as well 378 * as getting rid of joystick saturation at edges of joystick. 379 * 380 * <p>Sometimes, after inverse kinematics, the requested speed from one or more modules may be 381 * above the max attainable speed for the driving motor on that module. To fix this issue, one can 382 * reduce all the wheel speeds to make sure that all requested module speeds are at-or-below the 383 * absolute threshold, while maintaining the ratio of speeds between modules. 384 * 385 * @param moduleStates Reference to array of module states. The array will be mutated with the 386 * normalized speeds! 387 * @param desiredChassisSpeed The desired speed of the robot 388 * @param attainableMaxModuleSpeed The absolute max speed that a module can reach 389 * @param attainableMaxTranslationalSpeed The absolute max speed that your robot can reach while 390 * translating 391 * @param attainableMaxRotationalVelocity The absolute max speed the robot can reach while 392 * rotating 393 */ 394 public static void desaturateWheelSpeeds( 395 SwerveModuleState[] moduleStates, 396 ChassisSpeeds desiredChassisSpeed, 397 Measure<Velocity<Distance>> attainableMaxModuleSpeed, 398 Measure<Velocity<Distance>> attainableMaxTranslationalSpeed, 399 Measure<Velocity<Angle>> attainableMaxRotationalVelocity) { 400 desaturateWheelSpeeds( 401 moduleStates, 402 desiredChassisSpeed, 403 attainableMaxModuleSpeed.in(MetersPerSecond), 404 attainableMaxTranslationalSpeed.in(MetersPerSecond), 405 attainableMaxRotationalVelocity.in(RadiansPerSecond)); 406 } 407}