001// Copyright (c) Choreo contributors
002
003package choreo.util;
004
005import edu.wpi.first.math.geometry.Pose2d;
006import edu.wpi.first.math.geometry.Pose3d;
007import edu.wpi.first.math.geometry.Rotation2d;
008import edu.wpi.first.math.geometry.Rotation3d;
009import edu.wpi.first.math.geometry.Translation2d;
010import edu.wpi.first.math.geometry.Translation3d;
011import edu.wpi.first.wpilibj.DriverStation;
012import edu.wpi.first.wpilibj.DriverStation.Alliance;
013import java.util.HashMap;
014
015/**
016 * A utility to standardize flipping of coordinate data based on the current alliance across
017 * different years.
018 *
019 * <p>If every vendor used this, the user would be able to specify the year and no matter the year
020 * the vendor's code is from, the user would be able to flip as expected.
021 *
022 * <p>This API still allows vendors and users to match case against the flipping variant as a way to
023 * specially handle cases or throw errors if a variant is explicitly not supported.
024 */
025public class AllianceFlipUtil {
026  /** The flipper to use for flipping coordinates. */
027  public static enum Flipper {
028    /**
029     * X becomes fieldLength - x, leaves the y coordinate unchanged, and heading becomes PI -
030     * heading.
031     */
032    MIRRORED {
033      public double flipX(double x) {
034        return activeYear.fieldLength - x;
035      }
036
037      public double flipY(double y) {
038        return y;
039      }
040
041      public double flipHeading(double heading) {
042        return Math.PI - heading;
043      }
044    },
045    /** X becomes fieldLength - x, Y becomes fieldWidth - y, and heading becomes PI - heading. */
046    ROTATE_AROUND {
047      public double flipX(double x) {
048        return activeYear.fieldLength - x;
049      }
050
051      public double flipY(double y) {
052        return activeYear.fieldWidth - y;
053      }
054
055      public double flipHeading(double heading) {
056        return Math.PI - heading;
057      }
058    };
059
060    /**
061     * Flips the X coordinate.
062     *
063     * @param x The X coordinate to flip.
064     * @return The flipped X coordinate.
065     */
066    public abstract double flipX(double x);
067
068    /**
069     * Flips the Y coordinate.
070     *
071     * @param y The Y coordinate to flip.
072     * @return The flipped Y coordinate.
073     */
074    public abstract double flipY(double y);
075
076    /**
077     * Flips the heading.
078     *
079     * @param heading The heading to flip.
080     * @return The flipped heading.
081     */
082    public abstract double flipHeading(double heading);
083  }
084
085  private static record YearInfo(Flipper flipper, double fieldLength, double fieldWidth) {}
086
087  // TODO: Update and expand this map
088  private static final HashMap<Integer, YearInfo> flipperMap =
089      new HashMap<Integer, YearInfo>() {
090        {
091          put(2020, new YearInfo(Flipper.ROTATE_AROUND, 16.5811, 8.19912));
092          put(2021, new YearInfo(Flipper.ROTATE_AROUND, 16.5811, 8.19912));
093          put(2022, new YearInfo(Flipper.ROTATE_AROUND, 16.5811, 8.19912));
094          put(2023, new YearInfo(Flipper.MIRRORED, 16.5811, 8.19912));
095          put(2024, new YearInfo(Flipper.MIRRORED, 16.5811, 8.19912));
096        }
097      };
098
099  private static YearInfo activeYear = flipperMap.get(2024);
100
101  /** Default constructor. */
102  private AllianceFlipUtil() {}
103
104  /**
105   * Get the flipper that is currently active for flipping coordinates. It's recommended not to
106   * store this locally as the flipper may change.
107   *
108   * @return The active flipper.
109   */
110  public static Flipper getFlipper() {
111    return activeYear.flipper;
112  }
113
114  /**
115   * Returns if you are on red alliance.
116   *
117   * @return If you are on red alliance.
118   */
119  public static boolean shouldFlip() {
120    return DriverStation.getAlliance().orElse(Alliance.Blue) == Alliance.Red;
121  }
122
123  /**
124   * Set the year to determine the Alliance Coordinate Flipper to use.
125   *
126   * @param year The year to set the flipper to. [2020 - 2024]
127   */
128  public static void setYear(int year) {
129    if (!flipperMap.containsKey(year)) {
130      throw new IllegalArgumentException("Year " + year + " is not supported.");
131    }
132    activeYear = flipperMap.get(year);
133  }
134
135  /**
136   * Flips the X coordinate.
137   *
138   * @param x The X coordinate to flip.
139   * @return The flipped X coordinate.
140   */
141  public static double flipX(double x) {
142    return activeYear.flipper.flipX(x);
143  }
144
145  /**
146   * Flips the Y coordinate.
147   *
148   * @param y The Y coordinate to flip.
149   * @return The flipped Y coordinate.
150   */
151  public static double flipY(double y) {
152    return activeYear.flipper.flipY(y);
153  }
154
155  /**
156   * Flips the heading.
157   *
158   * @param heading The heading to flip.
159   * @return The flipped heading.
160   */
161  public static double flipHeading(double heading) {
162    return activeYear.flipper.flipHeading(heading);
163  }
164
165  /**
166   * Flips the translation.
167   *
168   * @param translation The translation to flip.
169   * @return The flipped translation.
170   */
171  public static Translation2d flip(Translation2d translation) {
172    return new Translation2d(flipX(translation.getX()), flipY(translation.getY()));
173  }
174
175  /**
176   * Flips the rotation.
177   *
178   * @param rotation The rotation to flip.
179   * @return The flipped rotation.
180   */
181  public static Rotation2d flip(Rotation2d rotation) {
182    return switch (activeYear.flipper) {
183      case MIRRORED -> new Rotation2d(-rotation.getCos(), rotation.getSin());
184      case ROTATE_AROUND -> new Rotation2d(-rotation.getCos(), -rotation.getSin());
185    };
186  }
187
188  /**
189   * Flips the pose.
190   *
191   * @param pose The pose to flip.
192   * @return The flipped pose.
193   */
194  public static Pose2d flip(Pose2d pose) {
195    return new Pose2d(flip(pose.getTranslation()), flip(pose.getRotation()));
196  }
197
198  /**
199   * Flips the translation.
200   *
201   * @param translation The translation to flip.
202   * @return The flipped translation.
203   */
204  public static Translation3d flip(Translation3d translation) {
205    return new Translation3d(
206        flipX(translation.getX()), flipY(translation.getY()), translation.getZ());
207  }
208
209  /**
210   * Flips the rotation.
211   *
212   * @param rotation The rotation to flip.
213   * @return The flipped rotation.
214   */
215  public static Rotation3d flip(Rotation3d rotation) {
216    return new Rotation3d(
217        rotation.getX(), rotation.getY(), flip(rotation.toRotation2d()).getRadians());
218  }
219
220  /**
221   * Flips the pose.
222   *
223   * @param pose The pose to flip.
224   * @return The flipped pose.
225   */
226  public static Pose3d flip(Pose3d pose) {
227    return new Pose3d(flip(pose.getTranslation()), flip(pose.getRotation()));
228  }
229}