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.geometry.Pose2d; 011import edu.wpi.first.math.geometry.Rotation2d; 012import edu.wpi.first.math.geometry.Translation2d; 013import edu.wpi.first.math.geometry.Twist2d; 014import edu.wpi.first.math.kinematics.proto.ChassisSpeedsProto; 015import edu.wpi.first.math.kinematics.struct.ChassisSpeedsStruct; 016import edu.wpi.first.units.measure.AngularVelocity; 017import edu.wpi.first.units.measure.LinearVelocity; 018import edu.wpi.first.util.protobuf.ProtobufSerializable; 019import edu.wpi.first.util.struct.StructSerializable; 020import java.util.Objects; 021 022/** 023 * Represents the speed of a robot chassis. Although this class contains similar members compared to 024 * a Twist2d, they do NOT represent the same thing. Whereas a Twist2d represents a change in pose 025 * w.r.t to the robot frame of reference, a ChassisSpeeds object represents a robot's velocity. 026 * 027 * <p>A strictly non-holonomic drivetrain, such as a differential drive, should never have a dy 028 * component because it can never move sideways. Holonomic drivetrains such as swerve and mecanum 029 * will often have all three components. 030 */ 031public class ChassisSpeeds implements ProtobufSerializable, StructSerializable { 032 /** Velocity along the x-axis in meters per second. (Fwd is +) */ 033 public double vx; 034 035 /** Velocity along the y-axis in meters per second. (Left is +) */ 036 public double vy; 037 038 /** Angular velocity of the robot frame in radians per second. (CCW is +) */ 039 public double omega; 040 041 /** ChassisSpeeds protobuf for serialization. */ 042 public static final ChassisSpeedsProto proto = new ChassisSpeedsProto(); 043 044 /** ChassisSpeeds struct for serialization. */ 045 public static final ChassisSpeedsStruct struct = new ChassisSpeedsStruct(); 046 047 /** Constructs a ChassisSpeeds with zeros for dx, dy, and theta. */ 048 public ChassisSpeeds() {} 049 050 /** 051 * Constructs a ChassisSpeeds object. 052 * 053 * @param vx Forward velocity in meters per second. 054 * @param vy Sideways velocity in meters per second. 055 * @param omega Angular velocity in radians per second. 056 */ 057 public ChassisSpeeds(double vx, double vy, double omega) { 058 this.vx = vx; 059 this.vy = vy; 060 this.omega = omega; 061 } 062 063 /** 064 * Constructs a ChassisSpeeds object. 065 * 066 * @param vx Forward velocity. 067 * @param vy Sideways velocity. 068 * @param omega Angular velocity. 069 */ 070 public ChassisSpeeds(LinearVelocity vx, LinearVelocity vy, AngularVelocity omega) { 071 this(vx.in(MetersPerSecond), vy.in(MetersPerSecond), omega.in(RadiansPerSecond)); 072 } 073 074 /** 075 * Creates a Twist2d from ChassisSpeeds. 076 * 077 * @param dt The duration of the timestep in seconds. 078 * @return Twist2d. 079 */ 080 public Twist2d toTwist2d(double dt) { 081 return new Twist2d(vx * dt, vy * dt, omega * dt); 082 } 083 084 /** 085 * Discretizes a continuous-time chassis speed. 086 * 087 * <p>This function converts this continuous-time chassis speed into a discrete-time one such that 088 * when the discrete-time chassis speed is applied for one timestep, the robot moves as if the 089 * velocity components are independent (i.e., the robot moves v_x * dt along the x-axis, v_y * dt 090 * along the y-axis, and omega * dt around the z-axis). 091 * 092 * <p>This is useful for compensating for translational skew when translating and rotating a 093 * holonomic (swerve or mecanum) drivetrain. However, scaling down the ChassisSpeeds after 094 * discretizing (e.g., when desaturating swerve module speeds) rotates the direction of net motion 095 * in the opposite direction of rotational velocity, introducing a different translational skew 096 * which is not accounted for by discretization. 097 * 098 * @param dt The duration of the timestep in seconds the speeds should be applied for. 099 * @return Discretized ChassisSpeeds. 100 */ 101 public ChassisSpeeds discretize(double dt) { 102 // Construct the desired pose after a timestep, relative to the current pose. The desired pose 103 // has decoupled translation and rotation. 104 var desiredDeltaPose = new Pose2d(vx * dt, vy * dt, new Rotation2d(omega * dt)); 105 106 // Find the chassis translation/rotation deltas in the robot frame that move the robot from its 107 // current pose to the desired pose 108 var twist = Pose2d.kZero.log(desiredDeltaPose); 109 110 // Turn the chassis translation/rotation deltas into average velocities 111 return new ChassisSpeeds(twist.dx / dt, twist.dy / dt, twist.dtheta / dt); 112 } 113 114 /** 115 * Converts this field-relative set of speeds into a robot-relative ChassisSpeeds object. 116 * 117 * @param robotAngle The angle of the robot as measured by a gyroscope. The robot's angle is 118 * considered to be zero when it is facing directly away from your alliance station wall. 119 * Remember that this should be CCW positive. 120 * @return ChassisSpeeds object representing the speeds in the robot's frame of reference. 121 */ 122 public ChassisSpeeds toRobotRelative(Rotation2d robotAngle) { 123 // CW rotation into chassis frame 124 var rotated = new Translation2d(vx, vy).rotateBy(robotAngle.unaryMinus()); 125 return new ChassisSpeeds(rotated.getX(), rotated.getY(), omega); 126 } 127 128 /** 129 * Converts this robot-relative set of speeds into a field-relative ChassisSpeeds object. 130 * 131 * @param robotAngle The angle of the robot as measured by a gyroscope. The robot's angle is 132 * considered to be zero when it is facing directly away from your alliance station wall. 133 * Remember that this should be CCW positive. 134 * @return ChassisSpeeds object representing the speeds in the field's frame of reference. 135 */ 136 public ChassisSpeeds toFieldRelative(Rotation2d robotAngle) { 137 // CCW rotation out of chassis frame 138 var rotated = new Translation2d(vx, vy).rotateBy(robotAngle); 139 return new ChassisSpeeds(rotated.getX(), rotated.getY(), omega); 140 } 141 142 /** 143 * Adds two ChassisSpeeds and returns the sum. 144 * 145 * <p>For example, ChassisSpeeds{1.0, 0.5, 0.75} + ChassisSpeeds{2.0, 1.5, 0.25} = 146 * ChassisSpeeds{3.0, 2.0, 1.0} 147 * 148 * @param other The ChassisSpeeds to add. 149 * @return The sum of the ChassisSpeeds. 150 */ 151 public ChassisSpeeds plus(ChassisSpeeds other) { 152 return new ChassisSpeeds(vx + other.vx, vy + other.vy, omega + other.omega); 153 } 154 155 /** 156 * Subtracts the other ChassisSpeeds from the current ChassisSpeeds and returns the difference. 157 * 158 * <p>For example, ChassisSpeeds{5.0, 4.0, 2.0} - ChassisSpeeds{1.0, 2.0, 1.0} = 159 * ChassisSpeeds{4.0, 2.0, 1.0} 160 * 161 * @param other The ChassisSpeeds to subtract. 162 * @return The difference between the two ChassisSpeeds. 163 */ 164 public ChassisSpeeds minus(ChassisSpeeds other) { 165 return new ChassisSpeeds(vx - other.vx, vy - other.vy, omega - other.omega); 166 } 167 168 /** 169 * Returns the inverse of the current ChassisSpeeds. This is equivalent to negating all components 170 * of the ChassisSpeeds. 171 * 172 * @return The inverse of the current ChassisSpeeds. 173 */ 174 public ChassisSpeeds unaryMinus() { 175 return new ChassisSpeeds(-vx, -vy, -omega); 176 } 177 178 /** 179 * Multiplies the ChassisSpeeds by a scalar and returns the new ChassisSpeeds. 180 * 181 * <p>For example, ChassisSpeeds{2.0, 2.5, 1.0} * 2 = ChassisSpeeds{4.0, 5.0, 1.0} 182 * 183 * @param scalar The scalar to multiply by. 184 * @return The scaled ChassisSpeeds. 185 */ 186 public ChassisSpeeds times(double scalar) { 187 return new ChassisSpeeds(vx * scalar, vy * scalar, omega * scalar); 188 } 189 190 /** 191 * Divides the ChassisSpeeds by a scalar and returns the new ChassisSpeeds. 192 * 193 * <p>For example, ChassisSpeeds{2.0, 2.5, 1.0} / 2 = ChassisSpeeds{1.0, 1.25, 0.5} 194 * 195 * @param scalar The scalar to divide by. 196 * @return The scaled ChassisSpeeds. 197 */ 198 public ChassisSpeeds div(double scalar) { 199 return new ChassisSpeeds(vx / scalar, vy / scalar, omega / scalar); 200 } 201 202 @Override 203 public final int hashCode() { 204 return Objects.hash(vx, vy, omega); 205 } 206 207 @Override 208 public boolean equals(Object o) { 209 return o == this 210 || o instanceof ChassisSpeeds c && vx == c.vx && vy == c.vy && omega == c.omega; 211 } 212 213 @Override 214 public String toString() { 215 return String.format( 216 "ChassisSpeeds(Vx: %.2f m/s, Vy: %.2f m/s, Omega: %.2f rad/s)", vx, vy, omega); 217 } 218}