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.MathUsageId; 009import edu.wpi.first.math.geometry.Translation2d; 010import edu.wpi.first.math.geometry.Twist2d; 011import edu.wpi.first.math.kinematics.proto.MecanumDriveKinematicsProto; 012import edu.wpi.first.math.kinematics.struct.MecanumDriveKinematicsStruct; 013import org.ejml.simple.SimpleMatrix; 014 015/** 016 * Helper class that converts a chassis velocity (dx, dy, and dtheta components) into individual 017 * wheel speeds. 018 * 019 * <p>The inverse kinematics (converting from a desired chassis velocity to individual wheel speeds) 020 * uses the relative locations of the wheels with respect to the center of rotation. The center of 021 * rotation for inverse kinematics is also variable. This means that you can set your center of 022 * rotation in a corner of the robot to perform special evasion maneuvers. 023 * 024 * <p>Forward kinematics (converting an array of wheel speeds into the overall chassis motion) is 025 * performs the exact opposite of what inverse kinematics does. Since this is an overdetermined 026 * system (more equations than variables), we use a least-squares approximation. 027 * 028 * <p>The inverse kinematics: [wheelSpeeds] = [wheelLocations] * [chassisSpeeds] We take the 029 * Moore-Penrose pseudoinverse of [wheelLocations] and then multiply by [wheelSpeeds] to get our 030 * chassis speeds. 031 * 032 * <p>Forward kinematics is also used for odometry -- determining the position of the robot on the 033 * field using encoders and a gyro. 034 */ 035public class MecanumDriveKinematics 036 implements Kinematics<MecanumDriveWheelSpeeds, MecanumDriveWheelPositions> { 037 private final SimpleMatrix m_inverseKinematics; 038 private final SimpleMatrix m_forwardKinematics; 039 040 private final Translation2d m_frontLeftWheelMeters; 041 private final Translation2d m_frontRightWheelMeters; 042 private final Translation2d m_rearLeftWheelMeters; 043 private final Translation2d m_rearRightWheelMeters; 044 045 private Translation2d m_prevCoR = new Translation2d(); 046 047 public static final MecanumDriveKinematicsProto proto = new MecanumDriveKinematicsProto(); 048 public static final MecanumDriveKinematicsStruct struct = new MecanumDriveKinematicsStruct(); 049 050 /** 051 * Constructs a mecanum drive kinematics object. 052 * 053 * @param frontLeftWheelMeters The location of the front-left wheel relative to the physical 054 * center of the robot. 055 * @param frontRightWheelMeters The location of the front-right wheel relative to the physical 056 * center of the robot. 057 * @param rearLeftWheelMeters The location of the rear-left wheel relative to the physical center 058 * of the robot. 059 * @param rearRightWheelMeters The location of the rear-right wheel relative to the physical 060 * center of the robot. 061 */ 062 public MecanumDriveKinematics( 063 Translation2d frontLeftWheelMeters, 064 Translation2d frontRightWheelMeters, 065 Translation2d rearLeftWheelMeters, 066 Translation2d rearRightWheelMeters) { 067 m_frontLeftWheelMeters = frontLeftWheelMeters; 068 m_frontRightWheelMeters = frontRightWheelMeters; 069 m_rearLeftWheelMeters = rearLeftWheelMeters; 070 m_rearRightWheelMeters = rearRightWheelMeters; 071 072 m_inverseKinematics = new SimpleMatrix(4, 3); 073 074 setInverseKinematics( 075 frontLeftWheelMeters, frontRightWheelMeters, rearLeftWheelMeters, rearRightWheelMeters); 076 m_forwardKinematics = m_inverseKinematics.pseudoInverse(); 077 078 MathSharedStore.reportUsage(MathUsageId.kKinematics_MecanumDrive, 1); 079 } 080 081 /** 082 * Performs inverse kinematics to return the wheel speeds from a desired chassis velocity. This 083 * method is often used to convert joystick values into wheel speeds. 084 * 085 * <p>This function also supports variable centers of rotation. During normal operations, the 086 * center of rotation is usually the same as the physical center of the robot; therefore, the 087 * argument is defaulted to that use case. However, if you wish to change the center of rotation 088 * for evasive maneuvers, vision alignment, or for any other use case, you can do so. 089 * 090 * @param chassisSpeeds The desired chassis speed. 091 * @param centerOfRotationMeters The center of rotation. For example, if you set the center of 092 * rotation at one corner of the robot and provide a chassis speed that only has a dtheta 093 * component, the robot will rotate around that corner. 094 * @return The wheel speeds. Use caution because they are not normalized. Sometimes, a user input 095 * may cause one of the wheel speeds to go above the attainable max velocity. Use the {@link 096 * MecanumDriveWheelSpeeds#desaturate(double)} function to rectify this issue. 097 */ 098 public MecanumDriveWheelSpeeds toWheelSpeeds( 099 ChassisSpeeds chassisSpeeds, Translation2d centerOfRotationMeters) { 100 // We have a new center of rotation. We need to compute the matrix again. 101 if (!centerOfRotationMeters.equals(m_prevCoR)) { 102 var fl = m_frontLeftWheelMeters.minus(centerOfRotationMeters); 103 var fr = m_frontRightWheelMeters.minus(centerOfRotationMeters); 104 var rl = m_rearLeftWheelMeters.minus(centerOfRotationMeters); 105 var rr = m_rearRightWheelMeters.minus(centerOfRotationMeters); 106 107 setInverseKinematics(fl, fr, rl, rr); 108 m_prevCoR = centerOfRotationMeters; 109 } 110 111 var chassisSpeedsVector = new SimpleMatrix(3, 1); 112 chassisSpeedsVector.setColumn( 113 0, 114 0, 115 chassisSpeeds.vxMetersPerSecond, 116 chassisSpeeds.vyMetersPerSecond, 117 chassisSpeeds.omegaRadiansPerSecond); 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, new Translation2d()); 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.frontLeftMetersPerSecond, 154 wheelSpeeds.frontRightMetersPerSecond, 155 wheelSpeeds.rearLeftMetersPerSecond, 156 wheelSpeeds.rearRightMetersPerSecond); 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.frontLeftMeters - start.frontLeftMeters, 172 end.frontRightMeters - start.frontRightMeters, 173 end.rearLeftMeters - start.rearLeftMeters, 174 end.rearRightMeters - start.rearRightMeters); 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.frontLeftMeters, 193 wheelDeltas.frontRightMeters, 194 wheelDeltas.rearLeftMeters, 195 wheelDeltas.rearRightMeters); 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 public Translation2d getFrontLeft() { 217 return m_frontLeftWheelMeters; 218 } 219 220 public Translation2d getFrontRight() { 221 return m_frontRightWheelMeters; 222 } 223 224 public Translation2d getRearLeft() { 225 return m_rearLeftWheelMeters; 226 } 227 228 public Translation2d getRearRight() { 229 return m_rearRightWheelMeters; 230 } 231}