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}