3131import edu .wpi .first .math .Pair ;
3232import edu .wpi .first .math .geometry .Pose2d ;
3333import edu .wpi .first .math .geometry .Pose3d ;
34+ import edu .wpi .first .math .geometry .Rotation2d ;
3435import edu .wpi .first .math .geometry .Rotation3d ;
36+ import edu .wpi .first .math .geometry .Transform2d ;
3537import edu .wpi .first .math .geometry .Transform3d ;
38+ import edu .wpi .first .math .geometry .Translation2d ;
3639import edu .wpi .first .math .geometry .Translation3d ;
40+ import edu .wpi .first .math .interpolation .TimeInterpolatableBuffer ;
3741import edu .wpi .first .math .numbers .N1 ;
3842import edu .wpi .first .math .numbers .N3 ;
3943import edu .wpi .first .math .numbers .N8 ;
@@ -83,7 +87,18 @@ public enum PoseStrategy {
8387 * Use all visible tags to compute a single pose estimate. This runs on the RoboRIO, and can
8488 * take a lot of time.
8589 */
86- MULTI_TAG_PNP_ON_RIO
90+ MULTI_TAG_PNP_ON_RIO ,
91+
92+ /**
93+ * Use distance data from best visible tag to compute a Pose. This runs on the RoboRIO in order
94+ * to access the robot's yaw heading, and MUST have addHeadingData called every frame so heading
95+ * data is up-to-date.
96+ *
97+ * <p>Yields a Pose2d in estimatedRobotPose (0 for z, roll, pitch)
98+ *
99+ * <p>https://www.chiefdelphi.com/t/frc-6328-mechanical-advantage-2025-build-thread/477314/98
100+ */
101+ PNP_DISTANCE_TRIG_SOLVE
87102 }
88103
89104 private AprilTagFieldLayout fieldTags ;
@@ -97,6 +112,9 @@ public enum PoseStrategy {
97112 protected double poseCacheTimestampSeconds = -1 ;
98113 private final Set <Integer > reportedErrors = new HashSet <>();
99114
115+ private final TimeInterpolatableBuffer <Rotation2d > headingBuffer =
116+ TimeInterpolatableBuffer .createBuffer (1.0 );
117+
100118 /**
101119 * Create a new PhotonPoseEstimator.
102120 *
@@ -259,6 +277,30 @@ public void setLastPose(Pose2d lastPose) {
259277 setLastPose (new Pose3d (lastPose ));
260278 }
261279
280+ /**
281+ * Add robot heading data to buffer. Must be called periodically for the
282+ * <b>PNP_DISTANCE_TRIG_SOLVE</b> strategy.
283+ *
284+ * @param timestampSeconds timestamp of the robot heading data.
285+ * @param heading Field-relative robot heading at given timestamp. Standard WPILIB field
286+ * coordinates.
287+ */
288+ public void addHeadingData (double timestampSeconds , Rotation3d heading ) {
289+ addHeadingData (timestampSeconds , heading .toRotation2d ());
290+ }
291+
292+ /**
293+ * Add robot heading data to buffer. Must be called periodically for the
294+ * <b>PNP_DISTANCE_TRIG_SOLVE</b> strategy.
295+ *
296+ * @param timestampSeconds timestamp of the robot heading data.
297+ * @param heading Field-relative robot heading at given timestamp. Standard WPILIB field
298+ * coordinates.
299+ */
300+ public void addHeadingData (double timestampSeconds , Rotation2d heading ) {
301+ headingBuffer .addSample (timestampSeconds , heading );
302+ }
303+
262304 /**
263305 * @return The current transform from the center of the robot to the camera mount position
264306 */
@@ -374,6 +416,7 @@ private Optional<EstimatedRobotPose> update(
374416 case MULTI_TAG_PNP_ON_RIO ->
375417 multiTagOnRioStrategy (cameraResult , cameraMatrix , distCoeffs );
376418 case MULTI_TAG_PNP_ON_COPROCESSOR -> multiTagOnCoprocStrategy (cameraResult );
419+ case PNP_DISTANCE_TRIG_SOLVE -> pnpDistanceTrigSolveStrategy (cameraResult );
377420 };
378421
379422 if (estimatedPose .isPresent ()) {
@@ -383,6 +426,74 @@ private Optional<EstimatedRobotPose> update(
383426 return estimatedPose ;
384427 }
385428
429+ private Optional <EstimatedRobotPose > pnpDistanceTrigSolveStrategy (PhotonPipelineResult result ) {
430+ PhotonTrackedTarget bestTarget = result .getBestTarget ();
431+
432+ if (bestTarget == null ) return Optional .empty ();
433+
434+ Translation2d camToTagTranslation =
435+ new Pose3d (
436+ Translation3d .kZero ,
437+ new Rotation3d (
438+ 0 ,
439+ -Math .toRadians (bestTarget .getPitch ()),
440+ -Math .toRadians (bestTarget .getYaw ())))
441+ .transformBy (
442+ new Transform3d (
443+ new Translation3d (
444+ bestTarget .getBestCameraToTarget ().getTranslation ().getNorm (), 0 , 0 ),
445+ Rotation3d .kZero ))
446+ .getTranslation ()
447+ .rotateBy (
448+ new Rotation3d (
449+ getRobotToCameraTransform ().getRotation ().getX (),
450+ getRobotToCameraTransform ().getRotation ().getY (),
451+ 0 ))
452+ .toTranslation2d ();
453+
454+ if (headingBuffer .getSample (result .getTimestampSeconds ()).isEmpty ()) {
455+ return Optional .empty ();
456+ }
457+
458+ Rotation2d headingSample = headingBuffer .getSample (result .getTimestampSeconds ()).get ();
459+
460+ Rotation2d camToTagRotation =
461+ headingSample .plus (
462+ getRobotToCameraTransform ()
463+ .getRotation ()
464+ .toRotation2d ()
465+ .plus (camToTagTranslation .getAngle ()));
466+
467+ if (fieldTags .getTagPose (bestTarget .getFiducialId ()).isEmpty ()) return Optional .empty ();
468+ var tagPose2d = fieldTags .getTagPose (bestTarget .getFiducialId ()).get ().toPose2d ();
469+
470+ Translation2d fieldToCameraTranslation =
471+ new Pose2d (tagPose2d .getTranslation (), camToTagRotation .plus (Rotation2d .kPi ))
472+ .transformBy (new Transform2d (camToTagTranslation .getNorm (), 0 , Rotation2d .kZero ))
473+ .getTranslation ();
474+
475+ Pose2d robotPose =
476+ new Pose2d (
477+ fieldToCameraTranslation ,
478+ headingSample .plus (getRobotToCameraTransform ().getRotation ().toRotation2d ()))
479+ .transformBy (
480+ new Transform2d (
481+ new Pose3d (
482+ getRobotToCameraTransform ().getTranslation (),
483+ getRobotToCameraTransform ().getRotation ())
484+ .toPose2d (),
485+ Pose2d .kZero ));
486+
487+ robotPose = new Pose2d (robotPose .getTranslation (), headingSample );
488+
489+ return Optional .of (
490+ new EstimatedRobotPose (
491+ new Pose3d (robotPose ),
492+ result .getTimestampSeconds (),
493+ result .getTargets (),
494+ PoseStrategy .PNP_DISTANCE_TRIG_SOLVE ));
495+ }
496+
386497 private Optional <EstimatedRobotPose > multiTagOnCoprocStrategy (PhotonPipelineResult result ) {
387498 if (result .getMultiTagResult ().isEmpty ()) {
388499 return update (result , this .multiTagFallbackStrategy );
0 commit comments