James Kuszmaul | 9f9676d | 2019-01-25 21:27:58 -0800 | [diff] [blame] | 1 | #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 | |
| 7 | namespace frc971 { |
| 8 | namespace 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. |
| 34 | template <typename Scalar = double> |
| 35 | class 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 | |
| 113 | typedef TypedPose<double> Pose; |
| 114 | |
| 115 | template <typename Scalar> |
| 116 | TypedPose<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. |
| 137 | template <typename Scalar = double> |
| 138 | class 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 | |
| 170 | typedef TypedLineSegment<double> LineSegment; |
| 171 | |
| 172 | } // namespace control_loops |
| 173 | } // namespace frc971 |
| 174 | |
| 175 | #endif // FRC971_CONTROL_LOOPS_POSE_H_ |