7
7
import edu .wpi .first .math .Nat ;
8
8
import edu .wpi .first .math .geometry .Pose2d ;
9
9
import edu .wpi .first .math .geometry .Rotation2d ;
10
+ import edu .wpi .first .math .geometry .Transform2d ;
10
11
import edu .wpi .first .math .geometry .Translation2d ;
11
12
import edu .wpi .first .math .numbers .N1 ;
12
13
import edu .wpi .first .math .numbers .N3 ;
18
19
import frc .robot .Constants ;
19
20
import frc .subsystem .BlinkinLED .BlinkinLedMode ;
20
21
import frc .subsystem .BlinkinLED .LedStatus ;
22
+ import frc .utility .CircleFitter ;
21
23
import frc .utility .Limelight ;
22
24
import frc .utility .Limelight .LedMode ;
23
25
import frc .utility .Limelight .LimelightResolution ;
26
+ import frc .utility .geometry .GeometryUtils ;
24
27
import frc .utility .net .editing .LiveEditableValue ;
25
28
import org .apache .commons .math3 .geometry .euclidean .threed .Rotation ;
26
29
import org .apache .commons .math3 .geometry .euclidean .threed .RotationConvention ;
30
33
import org .jetbrains .annotations .NotNull ;
31
34
import org .jetbrains .annotations .Nullable ;
32
35
33
- import java .util .HashSet ;
34
- import java .util .Objects ;
35
- import java .util .Optional ;
36
- import java .util .Set ;
36
+ import java .util .*;
37
37
import java .util .concurrent .atomic .AtomicInteger ;
38
38
import java .util .concurrent .locks .ReentrantReadWriteLock ;
39
39
@@ -122,10 +122,11 @@ public void logData() {
122
122
private static final MatBuilder <N3 , N1 > THREE_BY_ONE_MAT_BUILDER = new MatBuilder <>(Nat .N3 (), Nat .N1 ());
123
123
124
124
private final LiveEditableValue <Rotation > cameraRotation ;
125
-
125
+ private final LiveEditableValue < Rotation2d > cameraRotation2d ;
126
126
private final LiveEditableValue <Double > hOffset ;
127
127
private final LiveEditableValue <Double > depthOffset ;
128
128
private final LiveEditableValue <Vector3D > centerOffset ;
129
+ private final LiveEditableValue <Transform2d > centerOffsetTranslation2d ;
129
130
130
131
{
131
132
NetworkTable guiTable = limelight .limelightGuiTable ;
@@ -135,6 +136,13 @@ public void logData() {
135
136
guiTable .getEntry ("centerOffset" ),
136
137
(value ) -> new Vector3D (0 , 0 , (Double ) value ),
137
138
Vector3D ::getZ );
139
+
140
+ centerOffsetTranslation2d = new LiveEditableValue <>(
141
+ new Transform2d (new Translation2d (IS_PRACTICE ? 6.9 : 18 , 0 ), new Rotation2d ()),
142
+ guiTable .getEntry ("centerOffsetNew" ),
143
+ (value ) -> new Transform2d (new Translation2d ((Double ) value , 0 ), new Rotation2d ()),
144
+ Transform2d ::getX );
145
+
138
146
cameraRotation = new LiveEditableValue <>(
139
147
new Rotation (RotationOrder .XYZ , RotationConvention .VECTOR_OPERATOR ,
140
148
Math .toRadians (IS_PRACTICE ? -37.5 : -34.5 ), 0 , 0 ),
@@ -146,6 +154,13 @@ public void logData() {
146
154
(value ) ->
147
155
Math .toDegrees (value .getAngles (RotationOrder .XYZ , RotationConvention .VECTOR_OPERATOR )[0 ])
148
156
);
157
+
158
+ cameraRotation2d = new LiveEditableValue <>(
159
+ new Rotation2d (Math .toRadians (IS_PRACTICE ? -37.5 : -34.5 )),
160
+ guiTable .getEntry ("angle" ),
161
+ (value ) -> Rotation2d .fromDegrees ((Double ) value ),
162
+ Rotation2d ::getDegrees
163
+ );
149
164
}
150
165
151
166
private Vector3D getPositionOfTargetRelativeToRobot () {
@@ -277,6 +292,28 @@ public void update() {
277
292
logData ("Angle To Target" , angleToTarget );
278
293
279
294
295
+ Optional <Pose2d > circleFitVisionPoseOptional = processFrame ();
296
+
297
+ if (circleFitVisionPoseOptional .isPresent ()) {
298
+ Pose2d circleFitVisionPose = circleFitVisionPoseOptional .get ();
299
+
300
+ if (DriverStation .isTeleopEnabled ()) {
301
+ robotTracker .addVisionMeasurement (circleFitVisionPose .getTranslation (), getLimelightTime (), false );
302
+ }
303
+ RobotPositionSender .addRobotPosition (new RobotState (
304
+ circleFitVisionPose .getX (),
305
+ circleFitVisionPose .getY (),
306
+ circleFitVisionPose .getRotation ().getRadians (),
307
+ getLimelightTime (),
308
+ "Circle Fit Vision Pose"
309
+ ));
310
+
311
+ logData ("Circle Fit Vision Pose X" , circleFitVisionPose .getX ());
312
+ logData ("Circle Fit Vision Pose Y" , circleFitVisionPose .getY ());
313
+ logData ("Circle Fit Vision Pose Angle" , circleFitVisionPose .getRotation ().getRadians ());
314
+ logData ("Circle Fit Vision Pose Time" , getLimelightTime ());
315
+ }
316
+
280
317
Optional <Translation2d > robotTranslationOptional = getVisionTranslation ();
281
318
if (robotTranslationOptional .isPresent ()) {
282
319
Translation2d robotTranslation = robotTranslationOptional .get ();
@@ -306,7 +343,7 @@ public void update() {
306
343
if (dist2 (robotTracker .getLatencyCompedPoseMeters ().getTranslation (),
307
344
robotTranslation ) < Constants .VISION_MANAGER_DISTANCE_THRESHOLD_SQUARED ) {
308
345
if (DriverStation .isTeleopEnabled ()) {
309
- robotTracker .addVisionMeasurement (robotTranslation , getLimelightTime (), false );
346
+ // robotTracker.addVisionMeasurement(robotTranslation, getLimelightTime(), false);
310
347
}
311
348
312
349
logData ("Using Vision Info" , "Using Vision Info" );
@@ -351,4 +388,248 @@ public void shootBallsUntilBeamBreakHasBroken(double shootTime) throws Interrupt
351
388
public void close () throws Exception {
352
389
353
390
}
391
+
392
+ double lastCaptureTimestamp = 0 ;
393
+ private static final int MIN_TARGET_COUNT = 3 ; // For calculating odometry
394
+ private double lastTranslationsTimestamp ;
395
+ private List <Translation2d > lastTranslations ;
396
+
397
+ public static final double VISION_TARGET_HEIGHT_LOWER =
398
+ Units .inchesToMeters (8.0 * 12.0 + 5.625 ); // Bottom of tape
399
+ public static final double VISION_TARGET_HEIGHT_UPPER = VISION_TARGET_HEIGHT_LOWER + Units .inchesToMeters (2.0 ); // Top of tape
400
+ public static final double VISION_TARGET_DIAMETER = Units .inchesToMeters (4.0 * 12.0 + 5.375 );
401
+ private static final double CIRCLE_FIT_PRECISION = 0.01 ;
402
+
403
+ public static final double FIELD_LENGTH = Units .inchesToMeters (54.0 * 12.0 );
404
+ public static final double FIELD_WIDTH = Units .inchesToMeters (27.0 * 12.0 );
405
+ public static final Translation2d HUB_CENTER = new Translation2d (FIELD_LENGTH / 2.0 , FIELD_WIDTH / 2.0 );
406
+
407
+ /**
408
+ * Process the current vision data
409
+ *
410
+ * @return The robot's position relative to the field
411
+ * @author jonahb55
412
+ * @author Aum-P
413
+ * @author V-RA
414
+ * @author Ayushk2023
415
+ * @author Mechanical Advantage 6328
416
+ */
417
+ private Optional <Pose2d > processFrame () {
418
+ // noinspection FloatingPointEquality
419
+ if (Limelight .getInstance ().getTimestamp () == lastCaptureTimestamp ) {
420
+ // Exit if no new frame
421
+ return Optional .empty ();
422
+ }
423
+
424
+ Limelight .getInstance ().getTimestamp ();
425
+ lastCaptureTimestamp = Limelight .getInstance ().getTimestamp ();
426
+
427
+ CameraPosition cameraPosition = new CameraPosition (hOffset .get (), cameraRotation2d .get (),
428
+ centerOffsetTranslation2d .get ());
429
+ Vector2d [] cornersRaw = Limelight .getInstance ().getCorners ();
430
+
431
+ int targetCount = cornersRaw .length / 4 ;
432
+ // Calculate camera to target translation
433
+ if (targetCount >= MIN_TARGET_COUNT && cornersRaw .length % 4 == 0 ) {
434
+ // Calculate individual corner translations
435
+ List <Translation2d > cameraToTargetTranslations = new ArrayList <>();
436
+ for (int targetIndex = 0 ; targetIndex < targetCount ; targetIndex ++) {
437
+ List <VisionPoint > corners = new ArrayList <>();
438
+ double totalX = 0.0 , totalY = 0.0 ;
439
+ for (int i = targetIndex * 4 ; i < (targetIndex * 4 ) + 4 ; i ++) {
440
+ if (i < cornersRaw .length ) {
441
+ corners .add (new VisionPoint (cornersRaw [i ].x , cornersRaw [i ].y ));
442
+ totalX += cornersRaw [i ].x ;
443
+ totalY += cornersRaw [i ].y ;
444
+ }
445
+ }
446
+
447
+ VisionPoint targetAvg = new VisionPoint (totalX / 4 , totalY / 4 );
448
+ corners = sortCorners (corners , targetAvg );
449
+
450
+ for (int i = 0 ; i < corners .size (); i ++) {
451
+ Translation2d translation = solveCameraToTargetTranslation (
452
+ corners .get (i ), i < 2 ? VISION_TARGET_HEIGHT_LOWER : VISION_TARGET_HEIGHT_UPPER , cameraPosition );
453
+ if (translation != null ) {
454
+ cameraToTargetTranslations .add (translation );
455
+ }
456
+ }
457
+ }
458
+
459
+ // Save individual translations
460
+ lastTranslationsTimestamp = Timer .getFPGATimestamp ();
461
+ lastTranslations = cameraToTargetTranslations ;
462
+
463
+ // Combine corner translations to full target translation
464
+ if (cameraToTargetTranslations .size () >= MIN_TARGET_COUNT * 4 ) {
465
+ Translation2d cameraToTargetTranslation =
466
+ CircleFitter .fit (VISION_TARGET_DIAMETER / 2.0 ,
467
+ cameraToTargetTranslations , CIRCLE_FIT_PRECISION );
468
+
469
+ // Calculate field to robot translation
470
+ Rotation2d robotRotation = RobotTracker .getInstance ().getGyroRotation (lastCaptureTimestamp );
471
+ Rotation2d cameraRotation = robotRotation
472
+ .rotateBy (cameraPosition .vehicleToCamera .getRotation ());
473
+ Transform2d fieldToTargetRotated =
474
+ new Transform2d (HUB_CENTER , cameraRotation );
475
+ Transform2d fieldToCamera = fieldToTargetRotated .plus (
476
+ GeometryUtils .transformFromTranslation (cameraToTargetTranslation .unaryMinus ()));
477
+ Pose2d fieldToVehicle =
478
+ GeometryUtils .transformToPose (fieldToCamera .plus (cameraPosition .vehicleToCamera .inverse ()));
479
+
480
+ if (fieldToVehicle .getX () > FIELD_LENGTH
481
+ || fieldToVehicle .getX () < 0.0
482
+ || fieldToVehicle .getY () > FIELD_WIDTH
483
+ || fieldToVehicle .getY () < 0.0 ) {
484
+ return Optional .empty ();
485
+ }
486
+ return Optional .of (fieldToVehicle );
487
+ }
488
+ }
489
+ return Optional .empty ();
490
+ }
491
+
492
+ /**
493
+ * @author jonahb55
494
+ * @author Aum-P
495
+ * @author V-RA
496
+ * @author Ayushk2023
497
+ * @author Mechanical Advantage 6328
498
+ */
499
+ private List <VisionPoint > sortCorners (List <VisionPoint > corners ,
500
+ VisionPoint average ) {
501
+
502
+ // Find top corners
503
+ Integer topLeftIndex = null ;
504
+ Integer topRightIndex = null ;
505
+ double minPosRads = Math .PI ;
506
+ double minNegRads = Math .PI ;
507
+ for (int i = 0 ; i < corners .size (); i ++) {
508
+ VisionPoint corner = corners .get (i );
509
+ double angleRad =
510
+ new Rotation2d (corner .x - average .x , average .y - corner .y )
511
+ .minus (Rotation2d .fromDegrees (90 )).getRadians ();
512
+ if (angleRad > 0 ) {
513
+ if (angleRad < minPosRads ) {
514
+ minPosRads = angleRad ;
515
+ topLeftIndex = i ;
516
+ }
517
+ } else {
518
+ if (Math .abs (angleRad ) < minNegRads ) {
519
+ minNegRads = Math .abs (angleRad );
520
+ topRightIndex = i ;
521
+ }
522
+ }
523
+ }
524
+
525
+ // Find lower corners
526
+ Integer lowerIndex1 = null ;
527
+ Integer lowerIndex2 = null ;
528
+ for (int i = 0 ; i < corners .size (); i ++) {
529
+ boolean alreadySaved = false ;
530
+ if (topLeftIndex != null ) {
531
+ if (topLeftIndex .equals (i )) {
532
+ alreadySaved = true ;
533
+ }
534
+ }
535
+ if (topRightIndex != null ) {
536
+ if (topRightIndex .equals (i )) {
537
+ alreadySaved = true ;
538
+ }
539
+ }
540
+ if (!alreadySaved ) {
541
+ if (lowerIndex1 == null ) {
542
+ lowerIndex1 = i ;
543
+ } else {
544
+ lowerIndex2 = i ;
545
+ }
546
+ }
547
+ }
548
+
549
+ // Combine final list
550
+ List <VisionPoint > newCorners = new ArrayList <>();
551
+ if (topLeftIndex != null ) {
552
+ newCorners .add (corners .get (topLeftIndex ));
553
+ }
554
+ if (topRightIndex != null ) {
555
+ newCorners .add (corners .get (topRightIndex ));
556
+ }
557
+ if (lowerIndex1 != null ) {
558
+ newCorners .add (corners .get (lowerIndex1 ));
559
+ }
560
+ if (lowerIndex2 != null ) {
561
+ newCorners .add (corners .get (lowerIndex2 ));
562
+ }
563
+ return newCorners ;
564
+ }
565
+
566
+ public static final Rotation2d FOV_HORIZONTAL = Rotation2d .fromDegrees (59.6 );
567
+ public static final Rotation2d FOV_VERTICAL = Rotation2d .fromDegrees (49.7 );
568
+
569
+ private static final double vpw = 2.0 * Math .tan (FOV_HORIZONTAL .getRadians () / 2.0 );
570
+ private static final double vph = 2.0 * Math .tan (FOV_VERTICAL .getRadians () / 2.0 );
571
+
572
+ /**
573
+ * Camera offsets in pixels?
574
+ */
575
+ public static final double CROSSHAIR_X = 0 ;
576
+ public static final double CROSSHAIR_Y = 0 ;
577
+
578
+ /**
579
+ * @author jonahb55
580
+ * @author Aum-P
581
+ * @author V-RA
582
+ * @author Ayushk2023
583
+ * @author Mechanical Advantage 6328
584
+ */
585
+ private Translation2d solveCameraToTargetTranslation (VisionPoint corner , double goalHeight , CameraPosition cameraPosition ) {
586
+
587
+ double halfWidthPixels = limelight .getCameraResolution ().width / 2.0 ;
588
+ double halfHeightPixels = limelight .getCameraResolution ().height / 2.0 ;
589
+ double nY = -((corner .x - halfWidthPixels - CROSSHAIR_X ) / halfWidthPixels );
590
+ double nZ = -((corner .y - halfHeightPixels - CROSSHAIR_Y ) / halfHeightPixels );
591
+
592
+ Translation2d xzPlaneTranslation = new Translation2d (1.0 , vph / 2.0 * nZ ).rotateBy (cameraPosition .verticalRotation );
593
+ double x = xzPlaneTranslation .getX ();
594
+ double y = vpw / 2.0 * nY ;
595
+ double z = xzPlaneTranslation .getY ();
596
+
597
+ double differentialHeight = cameraPosition .cameraHeight - goalHeight ;
598
+ if ((z < 0.0 ) == (differentialHeight > 0.0 )) {
599
+ double scaling = differentialHeight / -z ;
600
+ double distance = Math .hypot (x , y ) * scaling ;
601
+ Rotation2d angle = new Rotation2d (x , y );
602
+ return new Translation2d (distance * angle .getCos (), distance * angle .getSin ());
603
+ }
604
+ return null ;
605
+ }
606
+
607
+ /**
608
+ * @author jonahb55
609
+ */
610
+ public static class VisionPoint {
611
+ public final double x ;
612
+ public final double y ;
613
+
614
+ public VisionPoint (double x , double y ) {
615
+ this .x = x ;
616
+ this .y = y ;
617
+ }
618
+ }
619
+
620
+ /**
621
+ * @author jonahb55
622
+ */
623
+ public static final class CameraPosition {
624
+ public final double cameraHeight ;
625
+ public final Rotation2d verticalRotation ;
626
+ public final Transform2d vehicleToCamera ;
627
+
628
+ public CameraPosition (double cameraHeight , Rotation2d verticalRotation ,
629
+ Transform2d vehicleToCamera ) {
630
+ this .cameraHeight = cameraHeight ;
631
+ this .verticalRotation = verticalRotation ;
632
+ this .vehicleToCamera = vehicleToCamera ;
633
+ }
634
+ }
354
635
}
0 commit comments