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<Optional<Pose2d>> based on a 624 * Supplier<Optional<Alliance>> and original Optional<Pose2d> 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<Optional<Translation2d>> that is flipped based on a 647 * Supplier<Optional<Alliance>> and original Optional<Translation2d> 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}