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}