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