001// Copyright (c) Choreo contributors
002
003package choreo.util;
004
005import static choreo.util.FieldDimensions.FIELD_LENGTH;
006import static choreo.util.FieldDimensions.FIELD_WIDTH;
007
008import choreo.trajectory.DifferentialSample;
009import choreo.trajectory.SwerveSample;
010import edu.wpi.first.math.geometry.Pose2d;
011import edu.wpi.first.math.geometry.Pose3d;
012import edu.wpi.first.math.geometry.Rotation2d;
013import edu.wpi.first.math.geometry.Rotation3d;
014import edu.wpi.first.math.geometry.Translation2d;
015import edu.wpi.first.math.geometry.Translation3d;
016import edu.wpi.first.wpilibj.DriverStation;
017import edu.wpi.first.wpilibj.DriverStation.Alliance;
018import java.util.Optional;
019import java.util.function.Supplier;
020
021/**
022 * A utility to standardize flipping of coordinate data based on the current alliance across
023 * different years.
024 *
025 * <p>If every vendor used this, the user would be able to specify the year and no matter the year
026 * the vendor's code is from, the user would be able to flip as expected.
027 *
028 * <p>This API still allows vendors and users to match case against the flipping variant as a way to
029 * specially handle cases or throw errors if a variant is explicitly not supported.
030 */
031public class ChoreoAllianceFlipUtil {
032  /** The flipper to use for flipping coordinates. */
033  public abstract static class Flipper {
034    /** Constructs a flipper. */
035    protected Flipper() {}
036
037    /**
038     * X becomes fieldLength - x, leaves the y coordinate unchanged, and heading becomes PI -
039     * heading.
040     */
041    static class MirroredX extends Flipper {
042      public MirroredX(double fieldLength, double fieldWidth) {
043        super(fieldLength, fieldWidth);
044      }
045
046      public double flipX(double x) {
047        return getFieldLength() - x;
048      }
049
050      public double flipY(double y) {
051        return y;
052      }
053
054      public double flipHeading(double heading) {
055        return Math.PI - heading;
056      }
057
058      public Rotation2d flip(Rotation2d rotation) {
059        return new Rotation2d(-rotation.getCos(), rotation.getSin());
060      }
061
062      public SwerveSample flip(SwerveSample sample) {
063        return new SwerveSample(
064            sample.t,
065            flipX(sample.x),
066            flipY(sample.y),
067            flipHeading(sample.heading),
068            -sample.vx,
069            sample.vy,
070            -sample.omega,
071            -sample.ax,
072            sample.ay,
073            -sample.alpha,
074            // FL, FR, BL, BR
075            // Mirrored
076            // -FR, -FL, -BR, -BL
077            new double[] {
078              -sample.moduleForcesX()[1],
079              -sample.moduleForcesX()[0],
080              -sample.moduleForcesX()[3],
081              -sample.moduleForcesX()[2]
082            },
083            // FL, FR, BL, BR
084            // Mirrored
085            // FR, FL, BR, BL
086            new double[] {
087              sample.moduleForcesY()[1],
088              sample.moduleForcesY()[0],
089              sample.moduleForcesY()[3],
090              sample.moduleForcesY()[2]
091            });
092      }
093
094      public DifferentialSample flip(DifferentialSample sample) {
095        return new DifferentialSample(
096            sample.t,
097            flipX(sample.x),
098            flipY(sample.y), // No-op for mirroring
099            flipHeading(sample.heading),
100            sample.vr,
101            sample.vl,
102            -sample.omega,
103            sample.ar,
104            sample.al,
105            -sample.alpha,
106            sample.fr,
107            sample.fl);
108      }
109    }
110
111    /**
112     * Creates a new flipper that mirrors across x=fieldLength/2. This is intended for
113     * alliance-based flipping in rotationally asymmetric games.
114     *
115     * @param fieldLength The length of the field.
116     * @param fieldWidth The width of the field.
117     * @return a new flipper.
118     */
119    public static MirroredX mirroredX(double fieldLength, double fieldWidth) {
120      return new MirroredX(fieldLength, fieldWidth);
121    }
122
123    /**
124     * More used for left-right variants on the same alliance. X is unchanged, Y becomes
125     * fieldWidth-y, and heading becomes -heading.
126     */
127    static class MirroredY extends Flipper {
128      public MirroredY(double fieldLength, double fieldWidth) {
129        super(fieldLength, fieldWidth);
130      }
131
132      public double flipX(double x) {
133        return x;
134      }
135
136      public double flipY(double y) {
137        return getFieldWidth() - y;
138      }
139
140      public double flipHeading(double heading) {
141        return -heading;
142      }
143
144      public Rotation2d flip(Rotation2d rotation) {
145        return new Rotation2d(rotation.getCos(), -rotation.getSin());
146      }
147
148      @Override
149      public SwerveSample flip(SwerveSample sample) {
150        return new SwerveSample(
151            sample.t,
152            flipX(sample.x),
153            flipY(sample.y),
154            flipHeading(sample.heading),
155            sample.vx,
156            -sample.vy,
157            -sample.omega,
158            sample.ax,
159            -sample.ay,
160            -sample.alpha,
161            // FL, FR, BL, BR
162            // Mirrored
163            // FR, FL, BR, BL
164            new double[] {
165              sample.moduleForcesX()[1],
166              sample.moduleForcesX()[0],
167              sample.moduleForcesX()[3],
168              sample.moduleForcesX()[2]
169            },
170            // FL, FR, BL, BR
171            // Mirrored
172            // -FR, -FL, -BR, -BL
173            new double[] {
174              -sample.moduleForcesY()[1],
175              -sample.moduleForcesY()[0],
176              -sample.moduleForcesY()[3],
177              -sample.moduleForcesY()[2]
178            });
179      }
180
181      @Override
182      public DifferentialSample flip(DifferentialSample sample) {
183        return new DifferentialSample(
184            sample.t,
185            flipX(sample.x),
186            flipY(sample.y), // No-op for mirroring
187            flipHeading(sample.heading),
188            sample.vr,
189            sample.vl,
190            -sample.omega,
191            sample.ar,
192            sample.al,
193            -sample.alpha,
194            sample.fr,
195            sample.fl);
196      }
197    }
198
199    /**
200     * Creates a new flipper that mirrors across y=fieldWidth/2. This keeps the positions on the
201     * same alliance half, but can be used to mirror left and right sides of the field, from driver
202     * perspective.
203     *
204     * @param fieldLength The length of the field.
205     * @param fieldWidth The width of the field.
206     * @return a new flipper.
207     */
208    public static MirroredY mirroredY(double fieldLength, double fieldWidth) {
209      return new MirroredY(fieldLength, fieldWidth);
210    }
211
212    /** X becomes fieldLength - x, Y becomes fieldWidth - y, and heading becomes PI + heading. */
213    static class RotatedAround extends Flipper {
214      public RotatedAround(double fieldLength, double fieldWidth) {
215        super(fieldLength, fieldWidth);
216        this.mirrorX = mirroredX(fieldLength, fieldWidth);
217        this.mirrorY = mirroredY(fieldLength, fieldWidth);
218      }
219
220      MirroredX mirrorX = mirroredX(getFieldLength(), getFieldWidth());
221      MirroredY mirrorY = mirroredY(getFieldLength(), getFieldWidth());
222
223      public double flipX(double x) {
224        return mirrorX.flipX(mirrorY.flipX(x));
225      }
226
227      public double flipY(double y) {
228        return mirrorX.flipY(mirrorY.flipY(y));
229      }
230
231      public double flipHeading(double heading) {
232        return mirrorX.flipHeading(mirrorY.flipHeading(heading));
233      }
234
235      public Rotation2d flip(Rotation2d rotation) {
236        return new Rotation2d(-rotation.getCos(), -rotation.getSin());
237      }
238
239      @Override
240      public SwerveSample flip(SwerveSample sample) {
241        return new SwerveSample(
242            sample.t,
243            flipX(sample.x),
244            flipY(sample.y),
245            flipHeading(sample.heading),
246            -sample.vx,
247            -sample.vy,
248            sample.omega,
249            -sample.ax,
250            -sample.ay,
251            sample.alpha,
252            new double[] {
253              -sample.moduleForcesX()[0],
254              -sample.moduleForcesX()[1],
255              -sample.moduleForcesX()[2],
256              -sample.moduleForcesX()[3]
257            },
258            new double[] {
259              -sample.moduleForcesY()[0],
260              -sample.moduleForcesY()[1],
261              -sample.moduleForcesY()[2],
262              -sample.moduleForcesY()[3]
263            });
264      }
265
266      @Override
267      public DifferentialSample flip(DifferentialSample sample) {
268        return new DifferentialSample(
269            sample.t,
270            flipX(sample.x),
271            flipY(sample.y),
272            flipHeading(sample.heading),
273            sample.vl,
274            sample.vr,
275            sample.omega,
276            sample.al,
277            sample.ar,
278            sample.alpha,
279            sample.fl,
280            sample.fr);
281      }
282    }
283    ;
284
285    /**
286     * Creates a new rotated flipper around the center of the field. This is intended for
287     * alliance-based flipping in rotationally symmetric games.
288     *
289     * @param fieldLength The length of the field.
290     * @param fieldWidth The width of the field.
291     * @return A new rotated flipper around the center of the field.
292     */
293    public static RotatedAround rotatedAround(double fieldLength, double fieldWidth) {
294      return new RotatedAround(fieldLength, fieldWidth);
295    }
296
297    // ***** Class Definition *****/
298
299    double fieldLength;
300    double fieldWidth;
301
302    /**
303     * Constructs a flipper with the given field dimensions.
304     *
305     * @param fieldLength The length of the field.
306     * @param fieldWidth The width of the field.
307     */
308    public Flipper(double fieldLength, double fieldWidth) {
309      this.fieldLength = fieldLength;
310      this.fieldWidth = fieldWidth;
311    }
312
313    /**
314     * Gets the length (X axis) of the field.
315     *
316     * @return the length (X axis) of the field.
317     */
318    public double getFieldLength() {
319      return fieldLength;
320    }
321
322    /**
323     * Gets the width (Y axis) of the field.
324     *
325     * @return the width (Y axis) of the field.
326     */
327    public double getFieldWidth() {
328      return fieldWidth;
329    }
330
331    /**
332     * Flips the X coordinate.
333     *
334     * @param x The X coordinate to flip.
335     * @return The flipped X coordinate.
336     */
337    public abstract double flipX(double x);
338
339    /**
340     * Flips the Y coordinate.
341     *
342     * @param y The Y coordinate to flip.
343     * @return The flipped Y coordinate.
344     */
345    public abstract double flipY(double y);
346
347    /**
348     * Flips the heading.
349     *
350     * @param heading The heading to flip.
351     * @return The flipped heading.
352     */
353    public abstract double flipHeading(double heading);
354
355    /**
356     * Flips a Rotation2d.
357     *
358     * @param rotation the Rotation2d to flip.
359     * @return The flipped Rotation2d.
360     */
361    public abstract Rotation2d flip(Rotation2d rotation);
362
363    /**
364     * Flips a SwerveSample.
365     *
366     * @param sample The SwerveSample to flip.
367     * @return The flipped SwerveSample.
368     */
369    public abstract SwerveSample flip(SwerveSample sample);
370
371    /**
372     * Flips a DifferentialSample.
373     *
374     * @param sample The DifferentialSample to flip.
375     * @return The flipped DifferentialSample.
376     */
377    public abstract DifferentialSample flip(DifferentialSample sample);
378
379    /**
380     * Flips a Translation2d.
381     *
382     * @param translation the Translation2d to flip.
383     * @return The flipped Translation2d.
384     */
385    public Translation2d flip(Translation2d translation) {
386      return new Translation2d(flipX(translation.getX()), flipY(translation.getY()));
387    }
388
389    /**
390     * Flips the pose.
391     *
392     * @param pose The pose to flip.
393     * @return The flipped pose.
394     */
395    public Pose2d flip(Pose2d pose) {
396      return new Pose2d(flip(pose.getTranslation()), flip(pose.getRotation()));
397    }
398
399    /**
400     * Flips the translation.
401     *
402     * @param translation The translation to flip.
403     * @return The flipped translation.
404     */
405    public Translation3d flip(Translation3d translation) {
406      return new Translation3d(
407          flipX(translation.getX()), flipY(translation.getY()), translation.getZ());
408    }
409
410    /**
411     * Flips the rotation.
412     *
413     * @param rotation The rotation to flip.
414     * @return The flipped rotation.
415     */
416    public Rotation3d flip(Rotation3d rotation) {
417      return new Rotation3d(
418          rotation.getX(), rotation.getY(), flip(rotation.toRotation2d()).getRadians());
419    }
420
421    /**
422     * Flips the pose.
423     *
424     * @param pose The pose to flip.
425     * @return The flipped pose.
426     */
427    public Pose3d flip(Pose3d pose) {
428      return new Pose3d(flip(pose.getTranslation()), flip(pose.getRotation()));
429    }
430
431    /** The default flipper for the current FRC year. */
432    public static Flipper FRC_CURRENT = rotatedAround(FIELD_LENGTH, FIELD_WIDTH);
433  }
434
435  private static Flipper activeAllianceFlip;
436  private static Flipper activeMirrorX;
437  private static Flipper activeMirrorY;
438  private static Flipper activeRotateAround;
439
440  static {
441    setFlipper(Flipper.FRC_CURRENT);
442  }
443
444  /** Default constructor. */
445  private ChoreoAllianceFlipUtil() {}
446
447  /**
448   * Get the flipper that is currently active for alliance-based flipping. It's recommended not to
449   * store this locally as the flipper may change.
450   *
451   * @return The active flipper.
452   */
453  public static Flipper getFlipper() {
454    return activeAllianceFlip;
455  }
456
457  /**
458   * Get the flipper that is currently active for mirroring to the other alliance. It's recommended
459   * not to store this locally as the flipper may change.
460   *
461   * @return The active alliance-mirroring flipper.
462   */
463  public static Flipper getMirrorX() {
464    return activeMirrorX;
465  }
466
467  /**
468   * Get the flipper that is currently active for mirroring left-to-right from the driver's
469   * perspective. It's recommended not to store this locally as the flipper may change.
470   *
471   * @return The active left-right mirror flipper.
472   */
473  public static Flipper getMirrorY() {
474    return activeMirrorY;
475  }
476
477  /**
478   * Get the flipper that is currently active for rotating around the field center. It's recommended
479   * not to store this locally as the flipper may change.
480   *
481   * @return The active rotate-around flipper.
482   */
483  public static Flipper getRotateAround() {
484    return activeRotateAround;
485  }
486
487  /**
488   * Returns if you are on red alliance.
489   *
490   * @return If you are on red alliance.
491   */
492  public static boolean shouldFlip() {
493    return DriverStation.getAlliance().orElse(Alliance.Blue) == Alliance.Red;
494  }
495
496  /**
497   * Sets the flipper to use for alliance-based flipping. This will also set the mirror flippers
498   * based on the new flipper. You should only need to do this if you want to change the flipping
499   * behavior from the default, which is set based on the field dimensions and symmetry for the
500   * current FRC year. It's recommended to call this in a static block in Robot or equivalent so
501   * that it's set before any flipping is done.
502   *
503   * @param flipper The new flipper to use for alliance-based flipping.
504   */
505  public static void setFlipper(Flipper flipper) {
506    activeAllianceFlip = flipper;
507    activeMirrorX = Flipper.mirroredX(flipper.getFieldLength(), flipper.getFieldWidth());
508    activeMirrorY = Flipper.mirroredY(flipper.getFieldLength(), flipper.getFieldWidth());
509    activeRotateAround = Flipper.rotatedAround(flipper.getFieldLength(), flipper.getFieldWidth());
510  }
511
512  /**
513   * Flips the X coordinate to the other alliance.
514   *
515   * @param x The X coordinate to flip.
516   * @return The flipped X coordinate.
517   */
518  public static double flipX(double x) {
519    return activeAllianceFlip.flipX(x);
520  }
521
522  /**
523   * Flips the Y coordinate to the other alliance.
524   *
525   * @param y The Y coordinate to flip.
526   * @return The flipped Y coordinate.
527   */
528  public static double flipY(double y) {
529    return activeAllianceFlip.flipY(y);
530  }
531
532  /**
533   * Flips the heading to the other alliance.
534   *
535   * @param heading The heading to flip.
536   * @return The flipped heading.
537   */
538  public static double flipHeading(double heading) {
539    return activeAllianceFlip.flipHeading(heading);
540  }
541
542  /**
543   * Flips the translation to the other alliance.
544   *
545   * @param translation The translation to flip.
546   * @return The flipped translation.
547   */
548  public static Translation2d flip(Translation2d translation) {
549    return activeAllianceFlip.flip(translation);
550  }
551
552  /**
553   * Flips the rotation to the other alliance.
554   *
555   * @param rotation The rotation to flip.
556   * @return The flipped rotation.
557   */
558  public static Rotation2d flip(Rotation2d rotation) {
559    return activeAllianceFlip.flip(rotation);
560  }
561
562  /**
563   * Flips the pose to the other alliance.
564   *
565   * @param pose The pose to flip.
566   * @return The flipped pose.
567   */
568  public static Pose2d flip(Pose2d pose) {
569    return activeAllianceFlip.flip(pose);
570  }
571
572  /**
573   * Flips the translation to the other alliance.
574   *
575   * @param translation The translation to flip.
576   * @return The flipped translation.
577   */
578  public static Translation3d flip(Translation3d translation) {
579    return activeAllianceFlip.flip(translation);
580  }
581
582  /**
583   * Flips the rotation to the other alliance.
584   *
585   * @param rotation The rotation to flip.
586   * @return The flipped rotation.
587   */
588  public static Rotation3d flip(Rotation3d rotation) {
589    return activeAllianceFlip.flip(rotation);
590  }
591
592  /**
593   * Flips the pose to the other alliance.
594   *
595   * @param pose The pose to flip.
596   * @return The flipped pose.
597   */
598  public static Pose3d flip(Pose3d pose) {
599    return activeAllianceFlip.flip(pose);
600  }
601
602  /**
603   * Flips the swerve sample to the other alliance.
604   *
605   * @param sample The swerve sample to flip.
606   * @return The flipped swerve sample.
607   */
608  public static SwerveSample flip(SwerveSample sample) {
609    return activeAllianceFlip.flip(sample);
610  }
611
612  /**
613   * Flips the differential sample to the other alliance.
614   *
615   * @param sample The differential sample to flip.
616   * @return The flipped differential sample.
617   */
618  public static DifferentialSample flip(DifferentialSample sample) {
619    return activeAllianceFlip.flip(sample);
620  }
621
622  /**
623   * Creates a Supplier&lt;Optional&lt;Pose2d&gt;&gt; based on a
624   * Supplier&lt;Optional&lt;Alliance&gt;&gt; and original Optional&lt;Pose2d&gt;
625   *
626   * @param poseOpt The pose to flip
627   * @param allianceOpt The current alliance
628   * @param doFlip Returns true if flipping based on the alliance should be done
629   * @return empty if the alliance is empty; the original pose optional if the alliance is blue or
630   *     doFlip is false; the flipped pose optional if the alliance is red and doFlip is true
631   */
632  public static Supplier<Optional<Pose2d>> optionalFlippedPose2d(
633      Optional<Pose2d> poseOpt, Supplier<Optional<Alliance>> allianceOpt, boolean doFlip) {
634    if (poseOpt.isEmpty()) {
635      return () -> Optional.empty();
636    }
637    Optional<Pose2d> flippedPose = poseOpt.map(ChoreoAllianceFlipUtil::flip);
638
639    return () ->
640        doFlip
641            ? allianceOpt.get().flatMap(ally -> ally == Alliance.Red ? flippedPose : poseOpt)
642            : poseOpt;
643  }
644
645  /**
646   * Creates a Supplier&lt;Optional&lt;Translation2d&gt;&gt; that is flipped based on a
647   * Supplier&lt;Optional&lt;Alliance&gt;&gt; and original Optional&lt;Translation2d&gt;
648   *
649   * @param translationOpt The translation to flip
650   * @param allianceOpt The current alliance
651   * @param doFlip Returns true if flipping based on the alliance should be done
652   * @return empty if the alliance is empty; the original translation optional if the alliance is
653   *     blue or doFlip is false; the flipped translation optional if the alliance is red and doFlip
654   *     is true
655   */
656  public static Supplier<Optional<Translation2d>> optionalFlippedTranslation2d(
657      Optional<Translation2d> translationOpt,
658      Supplier<Optional<Alliance>> allianceOpt,
659      boolean doFlip) {
660    if (translationOpt.isEmpty()) {
661      return () -> Optional.empty();
662    }
663    Optional<Translation2d> flippedTranslation = translationOpt.map(ChoreoAllianceFlipUtil::flip);
664
665    return () ->
666        doFlip
667            ? allianceOpt
668                .get()
669                .flatMap(ally -> ally == Alliance.Red ? flippedTranslation : translationOpt)
670            : translationOpt;
671  }
672}