blob: 1eccf283b7a32768540f552bc2fd62d0fef4e2e6 [file] [log] [blame]
James Kuszmaul9f9676d2019-01-25 21:27:58 -08001#ifndef FRC971_CONTROL_LOOPS_POSE_H_
2#define FRC971_CONTROL_LOOPS_POSE_H_
3
James Kuszmaul090563a2019-02-09 14:43:20 -08004#include <vector>
5
James Kuszmaul9f9676d2019-01-25 21:27:58 -08006#include "Eigen/Dense"
Philipp Schrader790cb542023-07-05 21:06:52 -07007
James Kuszmaul9f9676d2019-01-25 21:27:58 -08008#include "aos/util/math.h"
9
10namespace frc971 {
11namespace control_loops {
12
James Kuszmaul3ca28612020-02-15 17:52:27 -080013// Constructs a homogeneous transformation matrix for rotating about the Z axis.
14template <typename Scalar>
15Eigen::Matrix<Scalar, 4, 4> TransformationMatrixForYaw(Scalar yaw) {
16 Eigen::Matrix<Scalar, 4, 4> matrix;
17 matrix.setIdentity();
18 const Scalar stheta = std::sin(yaw);
19 const Scalar ctheta = std::cos(yaw);
20 matrix(0, 0) = ctheta;
21 matrix(1, 1) = ctheta;
22 matrix(0, 1) = -stheta;
23 matrix(1, 0) = stheta;
24 return matrix;
25}
26
James Kuszmaul9f9676d2019-01-25 21:27:58 -080027// Provides a representation of a transformation on the field.
28// Currently, this is heavily geared towards things that occur in a 2-D plane.
29// The Z-axis is rarely used (but still relevant; e.g., in 2019 some of the
30// targets are at a different height).
31// For rotations, we currently just represent the yaw axis (the rotation about
32// the Z-axis).
33// As a convention, we use right-handed coordinate systems; the positive Z
34// axis will go up on the field, the positive X axis shall be "forwards" for
35// some relevant meaning of forwards, and the origin shall be chosen as
36// appropriate.
37// For 2019, at least, the global origin will be on the ground at the center
38// of the driver's station wall of your current alliance and the positive X-axis
39// will point straight into the field from the driver's station.
40// In future years this may need to change if the field's symmetry changes and
41// we can't interchangeably handle which side of the field we are on.
42// This means that if we had a Pose for the center of mass of the robot with a
43// position of (10, -5, 0) and a yaw of pi / 2, that suggests the robot is
44// facing straight to the left from the driver's perspective and is placed 10m
45// from the driver's station wall and 5m to the right of the center of the wall.
James Kuszmaula53c3ac2020-02-22 19:36:01 -080046// For 2020, we move the origin to be the center of the field and make positive
47// x always point towards the red alliance driver stations.
James Kuszmaul9f9676d2019-01-25 21:27:58 -080048//
49// Furthermore, Poses can be chained such that a Pose can be placed relative to
50// another Pose; the other Pose can dynamically update, thus allowing us to,
51// e.g., provide a Pose for a camera that is relative to the Pose of the robot.
52// Poses can also be in the global frame with no parent Pose.
53template <typename Scalar = double>
54class TypedPose {
55 public:
56 EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
57
58 // The type that contains the translational (x, y, z) component of the Pose.
59 typedef Eigen::Matrix<Scalar, 3, 1> Pos;
60
James Kuszmaul3ca28612020-02-15 17:52:27 -080061 // Provide a default constructor that creates a pose at the origin.
James Kuszmaul090563a2019-02-09 14:43:20 -080062 TypedPose() : TypedPose({0.0, 0.0, 0.0}, 0.0) {}
63
James Kuszmaul9f9676d2019-01-25 21:27:58 -080064 // Construct a Pose in the absolute frame with a particular position and yaw.
65 TypedPose(const Pos &abs_pos, Scalar theta) : pos_(abs_pos), theta_(theta) {}
66 // Construct a Pose relative to another Pose (base).
67 // If you provide a base of nullptr, then this will
68 // construct a Pose in the global frame.
69 // Note that the lifetime of base should be greater than the lifetime of
70 // the object being constructed.
71 TypedPose(const TypedPose<Scalar> *base, const Pos &rel_pos, Scalar rel_theta)
72 : base_(base), pos_(rel_pos), theta_(rel_theta) {}
73
James Kuszmaul3ca28612020-02-15 17:52:27 -080074 // Constructs a Pose from a homogeneous transformation matrix. Ignores the
75 // pitch/roll components of the rotation. Ignores the bottom row.
76 TypedPose(const Eigen::Matrix<Scalar, 4, 4> &H) {
77 pos_ = H.template block<3, 1>(0, 3);
James Kuszmauld478f872020-03-16 20:54:27 -070078 const Eigen::Matrix<Scalar, 3, 1> rotated_x =
79 H.template block<3, 3>(0, 0) * Eigen::Matrix<Scalar, 3, 1>::UnitX();
James Kuszmaul3ca28612020-02-15 17:52:27 -080080 theta_ = std::atan2(rotated_x.y(), rotated_x.x());
81 }
82
James Kuszmaul9f9676d2019-01-25 21:27:58 -080083 // Calculate the current global position of this Pose.
84 Pos abs_pos() const {
85 if (base_ == nullptr) {
86 return pos_;
87 }
88 Pos base_pos = base_->abs_pos();
89 Scalar base_theta = base_->abs_theta();
90 return base_pos + YawRotation(base_theta) * pos_;
91 }
92 // Calculate the absolute yaw of this Pose. Since we only have a single
93 // rotational axis, we can just sum the angle with that of the base Pose.
94 Scalar abs_theta() const {
95 if (base_ == nullptr) {
96 return theta_;
97 }
98 return aos::math::NormalizeAngle(theta_ + base_->abs_theta());
99 }
100 // Provide access to the position and yaw relative to the base Pose.
101 Pos rel_pos() const { return pos_; }
102 Scalar rel_theta() const { return theta_; }
James Kuszmaul090563a2019-02-09 14:43:20 -0800103 const TypedPose<Scalar> *base() const { return base_; }
James Kuszmaul9f9676d2019-01-25 21:27:58 -0800104
105 Pos *mutable_pos() { return &pos_; }
106 void set_theta(Scalar theta) { theta_ = theta; }
James Kuszmaul090563a2019-02-09 14:43:20 -0800107 // Swap out the base Pose, keeping the current relative position/angle.
108 void set_base(const TypedPose<Scalar> *new_base) { base_ = new_base; }
James Kuszmaul9f9676d2019-01-25 21:27:58 -0800109
110 // For 2-D calculation, provide the heading, which is distinct from the
111 // yaw/theta value. heading is the heading relative to the base Pose if you
112 // were to draw a line from the base to this Pose. i.e., if heading() is zero
113 // then you are directly in front of the base Pose.
114 Scalar heading() const { return ::std::atan2(pos_.y(), pos_.x()); }
115 // The 2-D distance from the base Pose to this Pose.
116 Scalar xy_norm() const { return pos_.template topRows<2>().norm(); }
117 // Return the absolute xy position.
118 Eigen::Matrix<Scalar, 2, 1> abs_xy() const {
119 return abs_pos().template topRows<2>();
120 }
121
James Kuszmaul3ca28612020-02-15 17:52:27 -0800122 // Returns a transformation matrix representing this pose--note that this
123 // explicitly does not include the base position, so this is equivalent to a
124 // translation and rotation by rel_pos and rel_theta.
125 Eigen::Matrix<Scalar, 4, 4> AsTransformationMatrix() const {
126 Eigen::Matrix<Scalar, 4, 4> matrix = TransformationMatrixForYaw(theta_);
127 matrix.template block<3, 1>(0, 3) = pos_;
128 return matrix;
129 }
130
James Kuszmaul9f9676d2019-01-25 21:27:58 -0800131 // Provide a copy of this that is set to have the same
132 // current absolute Pose as this, but have a different base.
133 // This can be used, e.g., to compute a Pose for a vision target that is
134 // relative to the camera instead of relative to the field. You can then
135 // access the rel_* variables to get what the position of the target is
136 // relative to the robot/camera.
137 // If new_base == nullptr, provides a Pose referenced to the global frame.
138 // Note that the lifetime of new_base should be greater than the lifetime of
139 // the returned object (unless new_base == nullptr).
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -0800140 [[nodiscard]] TypedPose Rebase(const TypedPose<Scalar> *new_base) const;
James Kuszmaul9f9676d2019-01-25 21:27:58 -0800141
James Kuszmaul3ca28612020-02-15 17:52:27 -0800142 // Convert this pose to the heading/distance/skew numbers that we
143 // traditionally use for EKF corrections.
144 Eigen::Matrix<Scalar, 3, 1> ToHeadingDistanceSkew() const {
145 const Scalar target_heading = heading();
146 return {target_heading, xy_norm(),
147 aos::math::NormalizeAngle(rel_theta() - target_heading)};
148 }
149
James Kuszmaul9f9676d2019-01-25 21:27:58 -0800150 private:
151 // A rotation-matrix like representation of the rotation for a given angle.
152 inline static Eigen::AngleAxis<Scalar> YawRotation(double theta) {
153 return Eigen::AngleAxis<Scalar>(theta, Pos::UnitZ());
154 }
155
156 // A pointer to the base Pose. If uninitialized, then this Pose is in the
157 // global frame.
158 const TypedPose<Scalar> *base_ = nullptr;
159 // Position and yaw relative to base_.
160 Pos pos_;
161 Scalar theta_;
Philipp Schrader790cb542023-07-05 21:06:52 -0700162}; // class TypedPose
James Kuszmaul9f9676d2019-01-25 21:27:58 -0800163
164typedef TypedPose<double> Pose;
165
166template <typename Scalar>
167TypedPose<Scalar> TypedPose<Scalar>::Rebase(
168 const TypedPose<Scalar> *new_base) const {
169 if (new_base == nullptr) {
170 return TypedPose<Scalar>(nullptr, abs_pos(), abs_theta());
171 }
172 // Calculate the absolute position/yaws of this and of the new_base, and then
173 // calculate where we are relative to new_base, essentially reversing the
174 // calculation in abs_*.
175 Pos base_pos = new_base->abs_pos();
176 Scalar base_theta = new_base->abs_theta();
177 Pos self_pos = abs_pos();
178 Scalar self_theta = abs_theta();
179 Scalar diff_theta = ::aos::math::DiffAngle(self_theta, base_theta);
180 Pos diff_pos = YawRotation(-base_theta) * (self_pos - base_pos);
181 return TypedPose<Scalar>(new_base, diff_pos, diff_theta);
182}
183
184// Represents a 2D line segment constructed from a pair of Poses.
185// The line segment goes between the two Poses, but for calculating
186// intersections we use the 2D projection of the Poses onto the global X-Y
187// plane.
188template <typename Scalar = double>
189class TypedLineSegment {
190 public:
James Kuszmaul090563a2019-02-09 14:43:20 -0800191 TypedLineSegment() {}
James Kuszmaul9f9676d2019-01-25 21:27:58 -0800192 TypedLineSegment(const TypedPose<Scalar> &pose1,
193 const TypedPose<Scalar> &pose2)
194 : pose1_(pose1), pose2_(pose2) {}
195 // Detects if two line segments intersect.
196 // When at least one end of one line segment is collinear with the other,
197 // the line segments are treated as not intersecting.
198 bool Intersects(const TypedLineSegment<Scalar> &other) const {
199 // Source for algorithm:
200 // https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
201 // Method:
202 // We will consider the four triangles that can be made out of any 3 points
203 // from the pair of line segments.
204 // Basically, if you consider one line segment the base of the triangle,
205 // then the two points of the other line segment should be on opposite
206 // sides of the first line segment (we use the PointsAreCCW function for
207 // this). This must hold when splitting off of both line segments.
208 Eigen::Matrix<Scalar, 2, 1> p1 = pose1_.abs_xy();
209 Eigen::Matrix<Scalar, 2, 1> p2 = pose2_.abs_xy();
210 Eigen::Matrix<Scalar, 2, 1> q1 = other.pose1_.abs_xy();
211 Eigen::Matrix<Scalar, 2, 1> q2 = other.pose2_.abs_xy();
212 return (::aos::math::PointsAreCCW<Scalar>(p1, q1, q2) !=
213 ::aos::math::PointsAreCCW<Scalar>(p2, q1, q2)) &&
214 (::aos::math::PointsAreCCW<Scalar>(p1, p2, q1) !=
215 ::aos::math::PointsAreCCW<Scalar>(p1, p2, q2));
216 }
James Kuszmaul090563a2019-02-09 14:43:20 -0800217
218 TypedPose<Scalar> pose1() const { return pose1_; }
219 TypedPose<Scalar> pose2() const { return pose2_; }
220 TypedPose<Scalar> *mutable_pose1() { return &pose1_; }
221 TypedPose<Scalar> *mutable_pose2() { return &pose2_; }
222
223 ::std::vector<TypedPose<Scalar>> PlotPoints() const {
224 return {pose1_, pose2_};
225 }
Philipp Schrader790cb542023-07-05 21:06:52 -0700226
James Kuszmaul9f9676d2019-01-25 21:27:58 -0800227 private:
James Kuszmaul090563a2019-02-09 14:43:20 -0800228 TypedPose<Scalar> pose1_;
229 TypedPose<Scalar> pose2_;
James Kuszmaul9f9676d2019-01-25 21:27:58 -0800230}; // class TypedLineSegment
231
232typedef TypedLineSegment<double> LineSegment;
233
234} // namespace control_loops
235} // namespace frc971
236
237#endif // FRC971_CONTROL_LOOPS_POSE_H_