blob: f1495bcbd2b809dc007bab9885708facdf1ab719 [file] [log] [blame]
Milind Upadhyayb67c6182022-10-22 13:45:45 -07001#ifndef FRC971_VISION_GEOMETRY_H_
2#define FRC971_VISION_GEOMETRY_H_
Milind Upadhyayda042bb2022-03-26 16:01:45 -07003
Austin Schuh86d980e2023-10-20 22:44:47 -07004#include <optional>
5
Austin Schuh99f7c6a2024-06-25 22:07:44 -07006#include "absl/log/check.h"
7#include "absl/log/log.h"
milind-udb98afa2022-03-01 19:54:57 -08008#include "opencv2/core/types.hpp"
9
Philipp Schrader790cb542023-07-05 21:06:52 -070010#include "aos/util/math.h"
11
Milind Upadhyayb67c6182022-10-22 13:45:45 -070012namespace frc971::vision {
milind-udb98afa2022-03-01 19:54:57 -080013
14// Linear equation in the form y = mx + b
15struct SlopeInterceptLine {
16 double m, b;
17
18 inline SlopeInterceptLine(cv::Point2d p, cv::Point2d q) {
19 if (p.x == q.x) {
20 CHECK_EQ(p.y, q.y) << "Can't fit line to infinite slope";
21
22 // If two identical points were passed in, give the slope 0,
23 // with it passing the point.
24 m = 0.0;
25 } else {
26 m = (p.y - q.y) / (p.x - q.x);
27 }
28 // y = mx + b -> b = y - mx
29 b = p.y - (m * p.x);
30 }
31
32 inline double operator()(double x) const { return (m * x) + b; }
33};
34
35// Linear equation in the form ax + by = c
36struct StdFormLine {
37 public:
38 double a, b, c;
39
40 inline std::optional<cv::Point2d> Intersection(const StdFormLine &l) const {
41 // Use Cramer's rule to solve for the intersection
42 const double denominator = Determinant(a, b, l.a, l.b);
43 const double numerator_x = Determinant(c, b, l.c, l.b);
44 const double numerator_y = Determinant(a, c, l.a, l.c);
45
46 std::optional<cv::Point2d> intersection = std::nullopt;
47 // Return nullopt if the denominator is 0, meaning the same slopes
48 if (denominator != 0) {
49 intersection =
50 cv::Point2d(numerator_x / denominator, numerator_y / denominator);
51 }
52
53 return intersection;
54 }
55
56 private: // Determinant of [[a, b], [c, d]]
57 static inline double Determinant(double a, double b, double c, double d) {
58 return (a * d) - (b * c);
59 }
60};
61
62struct Circle {
63 public:
64 cv::Point2d center;
65 double radius;
66
67 static inline std::optional<Circle> Fit(std::vector<cv::Point2d> points) {
68 CHECK_EQ(points.size(), 3ul);
69 // For the 3 points, we have 3 equations in the form
70 // (x - h)^2 + (y - k)^2 = r^2
71 // Manipulate them to solve for the center and radius
72 // (x1 - h)^2 + (y1 - k)^2 = r^2 ->
73 // x1^2 + h^2 - 2x1h + y1^2 + k^2 - 2y1k = r^2
74 // Also, (x2 - h)^2 + (y2 - k)^2 = r^2
75 // Subtracting these two, we get
76 // x1^2 - x2^2 - 2h(x1 - x2) + y1^2 - y2^2 - 2k(y1 - y2) = 0 ->
77 // h(x1 - x2) + k(y1 - y2) = (-x1^2 + x2^2 - y1^2 + y2^2) / -2
78 // Doing the same with equations 1 and 3, we get the second linear equation
79 // h(x1 - x3) + k(y1 - y3) = (-x1^2 + x3^2 - y1^2 + y3^2) / -2
80 // Now, we can solve for their intersection and find the center
81 const auto l =
82 StdFormLine{points[0].x - points[1].x, points[0].y - points[1].y,
83 (-std::pow(points[0].x, 2) + std::pow(points[1].x, 2) -
84 std::pow(points[0].y, 2) + std::pow(points[1].y, 2)) /
85 -2.0};
86 const auto m =
87 StdFormLine{points[0].x - points[2].x, points[0].y - points[2].y,
88 (-std::pow(points[0].x, 2) + std::pow(points[2].x, 2) -
89 std::pow(points[0].y, 2) + std::pow(points[2].y, 2)) /
90 -2.0};
91 const auto center = l.Intersection(m);
92
93 std::optional<Circle> circle = std::nullopt;
94 if (center) {
95 // Now find the radius
96 const double radius = cv::norm(points[0] - *center);
97 circle = Circle{*center, radius};
98 }
99 return circle;
100 }
101
102 inline double DistanceTo(cv::Point2d p) const {
103 const auto p_prime = TranslateToOrigin(p);
104 // Now, the distance is simply the difference between distance from the
105 // origin to p' and the radius.
106 return std::abs(cv::norm(p_prime) - radius);
107 }
108
109 inline double AngleOf(cv::Point2d p) const {
110 auto p_prime = TranslateToOrigin(p);
111 // Flip the y because y values go downwards.
112 p_prime.y *= -1;
113 return std::atan2(p_prime.y, p_prime.x);
114 }
115
Milind Upadhyay8e2582b2022-03-06 15:14:15 -0800116 // Expects all angles to be from 0 to 2pi
117 // TODO(milind): handle wrapping
118 static inline bool AngleInRange(double theta, double theta_min,
119 double theta_max) {
120 return (
121 (theta >= theta_min && theta <= theta_max) ||
122 (theta_min > theta_max && (theta >= theta_min || theta <= theta_max)));
123 }
124
milind-udb98afa2022-03-01 19:54:57 -0800125 inline bool InAngleRange(cv::Point2d p, double theta_min,
126 double theta_max) const {
Milind Upadhyay8e2582b2022-03-06 15:14:15 -0800127 return AngleInRange(AngleOf(p), theta_min, theta_max);
milind-udb98afa2022-03-01 19:54:57 -0800128 }
129
130 private:
131 // Translate the point on the circle
132 // as if the circle's center is the origin (0,0)
133 inline cv::Point2d TranslateToOrigin(cv::Point2d p) const {
134 return cv::Point2d(p.x - center.x, p.y - center.y);
135 }
136};
137
Milind Upadhyayb67c6182022-10-22 13:45:45 -0700138} // namespace frc971::vision
Milind Upadhyayda042bb2022-03-26 16:01:45 -0700139
Milind Upadhyayb67c6182022-10-22 13:45:45 -0700140#endif // FRC971_VISION_GEOMETRY_H_