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}