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