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}