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;
008
009import java.util.Objects;
010import org.wpilib.math.geometry.Rotation2d;
011import org.wpilib.math.interpolation.Interpolatable;
012import org.wpilib.math.kinematics.proto.SwerveModuleVelocityProto;
013import org.wpilib.math.kinematics.struct.SwerveModuleVelocityStruct;
014import org.wpilib.units.measure.LinearVelocity;
015import org.wpilib.util.protobuf.ProtobufSerializable;
016import org.wpilib.util.struct.StructSerializable;
017
018/** Represents the velocity of one swerve module. */
019public class SwerveModuleVelocity
020    implements Interpolatable<SwerveModuleVelocity>,
021        Comparable<SwerveModuleVelocity>,
022        ProtobufSerializable,
023        StructSerializable {
024  /** Velocity of the wheel of the module in meters per second. */
025  public double velocity;
026
027  /** Angle of the module. */
028  public Rotation2d angle = Rotation2d.kZero;
029
030  /** SwerveModuleVelocity protobuf for serialization. */
031  public static final SwerveModuleVelocityProto proto = new SwerveModuleVelocityProto();
032
033  /** SwerveModuleVelocity struct for serialization. */
034  public static final SwerveModuleVelocityStruct struct = new SwerveModuleVelocityStruct();
035
036  /** Constructs a SwerveModuleVelocity with zeros for velocity and angle. */
037  public SwerveModuleVelocity() {}
038
039  /**
040   * Constructs a SwerveModuleVelocity.
041   *
042   * @param velocity The velocity of the wheel of the module in meters per second.
043   * @param angle The angle of the module.
044   */
045  public SwerveModuleVelocity(double velocity, Rotation2d angle) {
046    this.velocity = velocity;
047    this.angle = angle;
048  }
049
050  /**
051   * Constructs a SwerveModuleVelocity.
052   *
053   * @param velocity The velocity of the wheel of the module.
054   * @param angle The angle of the module.
055   */
056  public SwerveModuleVelocity(LinearVelocity velocity, Rotation2d angle) {
057    this(velocity.in(MetersPerSecond), angle);
058  }
059
060  @Override
061  public boolean equals(Object obj) {
062    return obj instanceof SwerveModuleVelocity other
063        && Math.abs(other.velocity - velocity) < 1E-9
064        && angle.equals(other.angle);
065  }
066
067  @Override
068  public int hashCode() {
069    return Objects.hash(velocity, angle);
070  }
071
072  /**
073   * Compares two swerve module velocities. One swerve module is "greater" than the other if its
074   * velocity is higher than the other.
075   *
076   * @param other The other swerve module.
077   * @return 1 if this is greater, 0 if both are equal, -1 if other is greater.
078   */
079  @Override
080  public int compareTo(SwerveModuleVelocity other) {
081    return Double.compare(this.velocity, other.velocity);
082  }
083
084  @Override
085  public String toString() {
086    return String.format("SwerveModuleVelocity(Velocity: %.2f m/s, Angle: %s)", velocity, angle);
087  }
088
089  /**
090   * Minimize the change in heading this swerve module velocity would require by potentially
091   * reversing the direction the wheel spins. If this is used with the PIDController class's
092   * continuous input functionality, the furthest a wheel will ever rotate is 90 degrees.
093   *
094   * @param currentAngle The current module angle.
095   */
096  public void optimize(Rotation2d currentAngle) {
097    var delta = angle.minus(currentAngle);
098    if (Math.abs(delta.getDegrees()) > 90.0) {
099      velocity *= -1;
100      angle = angle.rotateBy(Rotation2d.kPi);
101    }
102  }
103
104  /**
105   * Minimize the change in heading the desired swerve module velocity would require by potentially
106   * reversing the direction the wheel spins. If this is used with the PIDController class's
107   * continuous input functionality, the furthest a wheel will ever rotate is 90 degrees.
108   *
109   * @param desiredVelocity The desired velocity.
110   * @param currentAngle The current module angle.
111   * @return Optimized swerve module velocity.
112   * @deprecated Use the instance method instead.
113   */
114  @Deprecated
115  public static SwerveModuleVelocity optimize(
116      SwerveModuleVelocity desiredVelocity, Rotation2d currentAngle) {
117    var delta = desiredVelocity.angle.minus(currentAngle);
118    if (Math.abs(delta.getDegrees()) > 90.0) {
119      return new SwerveModuleVelocity(
120          -desiredVelocity.velocity, desiredVelocity.angle.rotateBy(Rotation2d.kPi));
121    } else {
122      return new SwerveModuleVelocity(desiredVelocity.velocity, desiredVelocity.angle);
123    }
124  }
125
126  /**
127   * Returns the linear interpolation of this SwerveModuleVelocity and another. The angle is
128   * interpolated using the shortest path between the two angles.
129   *
130   * @param endValue The end value for the interpolation.
131   * @param t How far between the two values to interpolate. This is clamped to [0, 1].
132   * @return The interpolated value.
133   */
134  @Override
135  public SwerveModuleVelocity interpolate(SwerveModuleVelocity endValue, double t) {
136    // Clamp t to [0, 1]
137    t = Math.max(0.0, Math.min(1.0, t));
138
139    // Interpolate velocity linearly
140    double interpolatedVelocity = velocity + t * (endValue.velocity - velocity);
141
142    // Interpolate angle using the shortest path
143    Rotation2d interpolatedAngle = angle.interpolate(endValue.angle, t);
144
145    return new SwerveModuleVelocity(interpolatedVelocity, interpolatedAngle);
146  }
147
148  /**
149   * Scales velocity by cosine of angle error. This scales down movement perpendicular to the
150   * desired direction of travel that can occur when modules change directions. This results in
151   * smoother driving.
152   *
153   * @param currentAngle The current module angle.
154   */
155  public void cosineScale(Rotation2d currentAngle) {
156    velocity *= angle.minus(currentAngle).getCos();
157  }
158}