blob: 508a6a60d745b7c5e5e471fb3db2e95ff9df5798 [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
4#include "Eigen/Dense"
5#include "aos/util/math.h"
6
7namespace frc971 {
8namespace control_loops {
9
10// Provides a representation of a transformation on the field.
11// Currently, this is heavily geared towards things that occur in a 2-D plane.
12// The Z-axis is rarely used (but still relevant; e.g., in 2019 some of the
13// targets are at a different height).
14// For rotations, we currently just represent the yaw axis (the rotation about
15// the Z-axis).
16// As a convention, we use right-handed coordinate systems; the positive Z
17// axis will go up on the field, the positive X axis shall be "forwards" for
18// some relevant meaning of forwards, and the origin shall be chosen as
19// appropriate.
20// For 2019, at least, the global origin will be on the ground at the center
21// of the driver's station wall of your current alliance and the positive X-axis
22// will point straight into the field from the driver's station.
23// In future years this may need to change if the field's symmetry changes and
24// we can't interchangeably handle which side of the field we are on.
25// This means that if we had a Pose for the center of mass of the robot with a
26// position of (10, -5, 0) and a yaw of pi / 2, that suggests the robot is
27// facing straight to the left from the driver's perspective and is placed 10m
28// from the driver's station wall and 5m to the right of the center of the wall.
29//
30// Furthermore, Poses can be chained such that a Pose can be placed relative to
31// another Pose; the other Pose can dynamically update, thus allowing us to,
32// e.g., provide a Pose for a camera that is relative to the Pose of the robot.
33// Poses can also be in the global frame with no parent Pose.
34template <typename Scalar = double>
35class TypedPose {
36 public:
37 EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
38
39 // The type that contains the translational (x, y, z) component of the Pose.
40 typedef Eigen::Matrix<Scalar, 3, 1> Pos;
41
42 // Construct a Pose in the absolute frame with a particular position and yaw.
43 TypedPose(const Pos &abs_pos, Scalar theta) : pos_(abs_pos), theta_(theta) {}
44 // Construct a Pose relative to another Pose (base).
45 // If you provide a base of nullptr, then this will
46 // construct a Pose in the global frame.
47 // Note that the lifetime of base should be greater than the lifetime of
48 // the object being constructed.
49 TypedPose(const TypedPose<Scalar> *base, const Pos &rel_pos, Scalar rel_theta)
50 : base_(base), pos_(rel_pos), theta_(rel_theta) {}
51
52 // Calculate the current global position of this Pose.
53 Pos abs_pos() const {
54 if (base_ == nullptr) {
55 return pos_;
56 }
57 Pos base_pos = base_->abs_pos();
58 Scalar base_theta = base_->abs_theta();
59 return base_pos + YawRotation(base_theta) * pos_;
60 }
61 // Calculate the absolute yaw of this Pose. Since we only have a single
62 // rotational axis, we can just sum the angle with that of the base Pose.
63 Scalar abs_theta() const {
64 if (base_ == nullptr) {
65 return theta_;
66 }
67 return aos::math::NormalizeAngle(theta_ + base_->abs_theta());
68 }
69 // Provide access to the position and yaw relative to the base Pose.
70 Pos rel_pos() const { return pos_; }
71 Scalar rel_theta() const { return theta_; }
72
73 Pos *mutable_pos() { return &pos_; }
74 void set_theta(Scalar theta) { theta_ = theta; }
75
76 // For 2-D calculation, provide the heading, which is distinct from the
77 // yaw/theta value. heading is the heading relative to the base Pose if you
78 // were to draw a line from the base to this Pose. i.e., if heading() is zero
79 // then you are directly in front of the base Pose.
80 Scalar heading() const { return ::std::atan2(pos_.y(), pos_.x()); }
81 // The 2-D distance from the base Pose to this Pose.
82 Scalar xy_norm() const { return pos_.template topRows<2>().norm(); }
83 // Return the absolute xy position.
84 Eigen::Matrix<Scalar, 2, 1> abs_xy() const {
85 return abs_pos().template topRows<2>();
86 }
87
88 // Provide a copy of this that is set to have the same
89 // current absolute Pose as this, but have a different base.
90 // This can be used, e.g., to compute a Pose for a vision target that is
91 // relative to the camera instead of relative to the field. You can then
92 // access the rel_* variables to get what the position of the target is
93 // relative to the robot/camera.
94 // If new_base == nullptr, provides a Pose referenced to the global frame.
95 // Note that the lifetime of new_base should be greater than the lifetime of
96 // the returned object (unless new_base == nullptr).
97 TypedPose Rebase(const TypedPose<Scalar> *new_base) const;
98
99 private:
100 // A rotation-matrix like representation of the rotation for a given angle.
101 inline static Eigen::AngleAxis<Scalar> YawRotation(double theta) {
102 return Eigen::AngleAxis<Scalar>(theta, Pos::UnitZ());
103 }
104
105 // A pointer to the base Pose. If uninitialized, then this Pose is in the
106 // global frame.
107 const TypedPose<Scalar> *base_ = nullptr;
108 // Position and yaw relative to base_.
109 Pos pos_;
110 Scalar theta_;
111}; // class TypedPose
112
113typedef TypedPose<double> Pose;
114
115template <typename Scalar>
116TypedPose<Scalar> TypedPose<Scalar>::Rebase(
117 const TypedPose<Scalar> *new_base) const {
118 if (new_base == nullptr) {
119 return TypedPose<Scalar>(nullptr, abs_pos(), abs_theta());
120 }
121 // Calculate the absolute position/yaws of this and of the new_base, and then
122 // calculate where we are relative to new_base, essentially reversing the
123 // calculation in abs_*.
124 Pos base_pos = new_base->abs_pos();
125 Scalar base_theta = new_base->abs_theta();
126 Pos self_pos = abs_pos();
127 Scalar self_theta = abs_theta();
128 Scalar diff_theta = ::aos::math::DiffAngle(self_theta, base_theta);
129 Pos diff_pos = YawRotation(-base_theta) * (self_pos - base_pos);
130 return TypedPose<Scalar>(new_base, diff_pos, diff_theta);
131}
132
133// Represents a 2D line segment constructed from a pair of Poses.
134// The line segment goes between the two Poses, but for calculating
135// intersections we use the 2D projection of the Poses onto the global X-Y
136// plane.
137template <typename Scalar = double>
138class TypedLineSegment {
139 public:
140 TypedLineSegment(const TypedPose<Scalar> &pose1,
141 const TypedPose<Scalar> &pose2)
142 : pose1_(pose1), pose2_(pose2) {}
143 // Detects if two line segments intersect.
144 // When at least one end of one line segment is collinear with the other,
145 // the line segments are treated as not intersecting.
146 bool Intersects(const TypedLineSegment<Scalar> &other) const {
147 // Source for algorithm:
148 // https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
149 // Method:
150 // We will consider the four triangles that can be made out of any 3 points
151 // from the pair of line segments.
152 // Basically, if you consider one line segment the base of the triangle,
153 // then the two points of the other line segment should be on opposite
154 // sides of the first line segment (we use the PointsAreCCW function for
155 // this). This must hold when splitting off of both line segments.
156 Eigen::Matrix<Scalar, 2, 1> p1 = pose1_.abs_xy();
157 Eigen::Matrix<Scalar, 2, 1> p2 = pose2_.abs_xy();
158 Eigen::Matrix<Scalar, 2, 1> q1 = other.pose1_.abs_xy();
159 Eigen::Matrix<Scalar, 2, 1> q2 = other.pose2_.abs_xy();
160 return (::aos::math::PointsAreCCW<Scalar>(p1, q1, q2) !=
161 ::aos::math::PointsAreCCW<Scalar>(p2, q1, q2)) &&
162 (::aos::math::PointsAreCCW<Scalar>(p1, p2, q1) !=
163 ::aos::math::PointsAreCCW<Scalar>(p1, p2, q2));
164 }
165 private:
166 const TypedPose<Scalar> pose1_;
167 const TypedPose<Scalar> pose2_;
168}; // class TypedLineSegment
169
170typedef TypedLineSegment<double> LineSegment;
171
172} // namespace control_loops
173} // namespace frc971
174
175#endif // FRC971_CONTROL_LOOPS_POSE_H_