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}