TinyRobotics
Loading...
Searching...
No Matches
FrameMgr2D.h
1#pragma once
2#include <cmath>
3#include <vector>
4
5#include "TinyRobotics/coordinates/Coordinate.h"
6#include "TinyRobotics/coordinates/GPSCoordinate.h"
7#include "TinyRobotics/utils/Common.h"
8#include "TinyRobotics/utils/LoggerClass.h"
9
10namespace tinyrobotics {
11
12/**
13 * @class Transform2D
14 * @ingroup coordinates
15 * @brief Represents a 2D rigid body transform (SE(2)): translation and
16 * rotation.
17 *
18 * Transform2D models a pose in 2D space, consisting of a translation (x, y) in
19 * meters and a heading (rotation) in degrees. It supports:
20 * - Applying the transform to local coordinates (rotating and translating
21 * them into the global frame)
22 * - Composing transforms (chaining multiple poses)
23 * - Inverting the transform (computing the reverse transformation)
24 *
25 * The heading is in degrees, where 0 means facing along the positive x-axis,
26 * and positive values rotate counterclockwise (e.g., 90 degrees is along the
27 * positive y-axis).
28 *
29 * Typical usage:
30 * - Represent the pose of a robot, sensor, or frame in 2D space.
31 * - Transform points between local and global frames.
32 * - Chain multiple transforms for hierarchical coordinate systems.
33 *
34 * Example:
35 * @code
36 * Transform2D base_to_world(1.0, 2.0, 45); // 1m x, 2m y, 45 deg rotation
37 * Coordinate local_point(0.5, 0.0); // 0.5m ahead in base frame
38 * Coordinate global_point = base_to_world.apply(local_point);
39 * Transform2D world_to_base = base_to_world.inverse();
40 * @endcode
41 */
42class Transform2D {
43 public:
44 Transform2D() : pos(0, 0), heading_deg(0) {}
45 Transform2D(float x, float y, float heading_deg)
46 : pos(x, y), heading_deg(heading_deg) {}
47 Transform2D(const Coordinate<DistanceM>& coord, float heading_deg)
48 : pos(coord), heading_deg(heading_deg) {}
49
50 Coordinate<DistanceM> pos; ///< Position (x, y) in meters
51 float heading_deg; ///< Heading in degrees (0 = x axis)
52
53 /// Get heading in specified unit (degrees or radians)
54 float getHeading(AngleUnit unit) const {
55 return Angle(heading_deg, AngleUnit::DEG).getValue(unit);
56 }
57
58 /**
59 * @brief Applies this transform to a local coordinate, returning the
60 * coordinate in the global frame. The local coordinate is first rotated by
61 * the heading, then translated by the position of this transform.
62 */
63 Coordinate<DistanceM> apply(const Coordinate<DistanceM>& local) const {
64 float theta = heading_deg * static_cast<float>(M_PI) / 180.0f;
65 float x = std::cos(theta) * local.x - std::sin(theta) * local.y + pos.x;
66 float y = std::sin(theta) * local.x + std::cos(theta) * local.y + pos.y;
67 return Coordinate<DistanceM>(x, y);
68 }
69
70 /**
71 * @brief Compose this transform with another (this * other).
72 * Applies this transform, then the other.
73 */
74 Transform2D operator*(const Transform2D& other) const {
75 float theta_rad = heading_deg * static_cast<float>(M_PI) / 180.0f;
76 float cos_theta = std::cos(theta_rad);
77 float sin_theta = std::sin(theta_rad);
78 float nx = pos.x + cos_theta * other.pos.x - sin_theta * other.pos.y;
79 float ny = pos.y + sin_theta * other.pos.x + cos_theta * other.pos.y;
80 float nheading = heading_deg + other.heading_deg;
81 nheading = normalizeAngleDeg(nheading);
82 return Transform2D(nx, ny, nheading);
83 }
84
85 /**
86 * @brief Invert this transform.
87 */
88 Transform2D inverse() const {
89 float theta_rad = heading_deg * static_cast<float>(M_PI) / 180.0f;
90 float cos_theta = std::cos(theta_rad);
91 float sin_theta = std::sin(theta_rad);
92 float ix = -pos.x * cos_theta - pos.y * sin_theta;
93 float iy = pos.x * sin_theta - pos.y * cos_theta;
94 float iheading = -heading_deg;
95 iheading = normalizeAngleDeg(iheading);
96 return Transform2D(ix, iy, iheading);
97 }
98};
99
100/**
101 * @brief Represents a 2D coordinate frame in a hierarchical frame tree.
102 *
103 * Frame2D models a reference frame in 2D space, such as a robot base, sensor,
104 * or world frame. Each frame can have a parent frame, forming a tree structure
105 * for managing relative poses. The pose of the frame relative to its parent is
106 * described by a Transform2D (translation and heading).
107 *
108 * Features:
109 * - Stores frame type and index for identification (e.g., WORLD, BASE,
110 * SENSOR).
111 * - Maintains a pointer to its parent frame (if any).
112 * - Stores the SE(2) transform (Transform2D) from this frame to its parent.
113 * - Supports setting and retrieving the transform and parent.
114 * - Used by FrameMgr2D to compute transforms and manage coordinate
115 * conversions.
116 *
117 * Typical usage:
118 * - Define a hierarchy of frames for a robot (e.g., world → base → sensor).
119 * - Set the transform from each frame to its parent.
120 * - Use with FrameMgr2D to compute transforms between arbitrary frames and
121 * convert to GPS.
122 *
123 * Example:
124 * @code
125 * Frame2D world(FrameType::WORLD);
126 * Frame2D base(FrameType::BASE, 0, world, Transform2D(1.0, 2.0, 45));
127 * Frame2D lidar(FrameType::SENSOR, 0, base, Transform2D(0.2, 0.0, 0));
128 * @endcode
129 */
130struct Frame2D {
131 Frame2D(FrameType type, uint8_t idx = 0) : type(type), index(idx) {}
132 Frame2D(FrameType type, uint8_t idx, Frame2D& parent)
133 : type(type), index(idx), p_parent(&parent) {}
134 Frame2D(FrameType type, uint8_t idx, Frame2D& parent, const Transform2D& tf)
135 : type(type), index(idx), p_parent(&parent), tf(tf) {}
136
137 bool operator==(const Frame2D& o) const {
138 return type == o.type && index == o.index;
139 }
140
141 FrameType getType() const { return type; }
142 uint8_t getIndex() const { return index; }
143 const Frame2D* getParent() const { return p_parent; }
144 void setParent(Frame2D& parent) { p_parent = &parent; }
145 const Transform2D& getTransform() const { return tf; }
146 Transform2D& getTransform() { return tf; }
147 void setTransform(const Transform2D& transform) { tf = transform; }
148
149 protected:
150 FrameType type;
151 uint8_t index = 0;
152 Frame2D* p_parent = nullptr;
153 Transform2D tf;
154};
155
156/**
157 * @class FrameMgr2D
158 * @ingroup coordinates
159 * @brief Manages a hierarchy of 2D coordinate frames and enables SE(2)
160 * transforms and GPS conversion.
161 *
162 * FrameMgr2D is designed for robotics and navigation applications where
163 * multiple coordinate frames (such as world, robot base, and sensors) must be
164 * related and transformed. It supports:
165 * - Defining a tree of 2D frames, each with a pose (translation and heading)
166 * relative to its parent.
167 * - Computing the SE(2) transform (translation + rotation) between any two
168 * frames in the hierarchy.
169 * - Associating a GPS coordinate and orientation with any frame, enabling
170 * conversion of local coordinates to global GPS coordinates.
171 * - Converting local frame coordinates to GPS using the reference frame and
172 * orientation.
173 *
174 * Typical usage:
175 * - Model a robot's world, base, and sensor frames.
176 * - Set the GPS reference for the world or another frame.
177 * - Compute the transform between frames for sensor fusion, mapping, or
178 * control.
179 * - Convert robot or sensor positions to GPS for navigation or logging.
180 *
181 * Example:
182 * @code
183 * FrameMgr2D mgr;
184 * Frame2D world(FrameType::WORLD);
185 * Frame2D base(FrameType::BASE, 0, world, Transform2D(1.0, 2.0, 45));
186 * mgr.setGPS(world, GPSCoordinate(48.8584, 2.2945, 35)); // Eiffel Tower
187 * Coordinate robot_local(0.5, 0.0); // 0.5m ahead in base frame
188 * // Convert robot position in base frame to GPS
189 * GPSCoordinate robot_gps = mgr.toGPS(base);
190 * @endcode
191 *
192 * This class is suitable for embedded and desktop robotics systems that require
193 * flexible, hierarchical frame management and integration with GPS.
194 * @ingroup coordinates
195 */
196class FrameMgr2D {
197 public:
198 FrameMgr2D() = default;
199
200 /// Defines the GPI coordinate for the indicated parent frame. The GPS
201 /// coordinate is assumed to be facing north (90 degrees) by default, but a
202 /// different rotation can be specified if needed.
203 void setGPS(Frame2D& parent, const GPSCoordinate gps,
204 float rotationDeg = 90) {
205 p_gpsParent = &parent;
206 gpsCoordinate = gps;
207 gpsRotationDeg = rotationDeg;
208 }
209
210 /// Converts a local frame to a GPS coordinate
211 GPSCoordinate toGPS(const Frame2D& frame) const {
212 if (!p_gpsParent) {
213 TRLogger.error(
214 "FrameMgr2D: GPS parent frame not set, cannot convert to GPS");
215 return GPSCoordinate(); // Return default GPS coordinate
216 }
217 Transform2D tf = getTransform(frame, *p_gpsParent);
218
219 // Rotate the local (x, y) by gpsRotationDeg to align with GPS north
220 float theta_rad = gpsRotationDeg * static_cast<float>(M_PI) / 180.0f;
221 float cos_theta = std::cos(theta_rad);
222 float sin_theta = std::sin(theta_rad);
223 float dx = cos_theta * tf.pos.x - sin_theta * tf.pos.y;
224 float dy = sin_theta * tf.pos.x + cos_theta * tf.pos.y;
225
226 float distance = std::sqrt(dx * dx + dy * dy);
227 float bearing_deg = std::atan2(dx, dy) * 180.0f /
228 static_cast<float>(M_PI); // Bearing from north
229 bearing_deg = normalizeAngleDeg(bearing_deg);
230
231 return gpsCoordinate.navigate(distance, bearing_deg);
232 }
233
234 /**
235 * @brief Computes the transform from one frame to another using SE(2) math.
236 * @param from The starting frame.
237 * @param to The target frame.
238 * @return The composed transform from 'from' to 'to'.
239 */
240 Transform2D getTransform(const Frame2D& from, const Frame2D& to) const {
241 // Find paths from both frames to root
242 std::vector<const Frame2D*> from_path = getPathToRoot(from);
243 std::vector<const Frame2D*> to_path = getPathToRoot(to);
244
245 // Find lowest common ancestor (LCA)
246 int i = from_path.size() - 1;
247 int j = to_path.size() - 1;
248 //const Frame2D* lca = nullptr;
249 while (i >= 0 && j >= 0 && from_path[i] == to_path[j]) {
250 //lca = from_path[i];
251 --i;
252 --j;
253 }
254
255 // Compose transform from 'from' up to LCA (invert each step)
256 Transform2D tf_from;
257 for (int k = 0; k <= i; ++k) {
258 tf_from = from_path[k]->getTransform() * tf_from;
259 }
260 tf_from = tf_from.inverse();
261
262 // Compose transform from LCA down to 'to'
263 Transform2D tf_to;
264 for (int k = j; k >= 0; --k) {
265 tf_to = tf_to * to_path[k]->getTransform();
266 }
267
268 // Final transform: from → LCA (inverted) → to
269 return tf_to * tf_from;
270 }
271
272 protected:
273 Frame2D* p_gpsParent = nullptr;
274 GPSCoordinate gpsCoordinate;
275 float gpsRotationDeg;
276
277 /**
278 * @brief Returns the path from the given frame up to the root (including
279 * self). The path is verly small, so no special optimizations are needed.
280 */
281 std::vector<const Frame2D*> getPathToRoot(const Frame2D& frame) const {
282 std::vector<const Frame2D*> path;
283 const Frame2D* current = &frame;
284 while (current) {
285 path.push_back(current);
286 current = current->getParent();
287 }
288 return path;
289 }
290};
291
292} // namespace tinyrobotics
Represents an angle with unit conversion and wrap-around support.
Definition: Angle.h:42
A generic 3D coordinate class for robotics, navigation, and spatial calculations.
Definition: Coordinate.h:57
Manages a hierarchy of 2D coordinate frames and enables SE(2) transforms and GPS conversion.
Definition: FrameMgr2D.h:196
GPSCoordinate toGPS(const Frame2D &frame) const
Converts a local frame to a GPS coordinate.
Definition: FrameMgr2D.h:211
Transform2D getTransform(const Frame2D &from, const Frame2D &to) const
Computes the transform from one frame to another using SE(2) math.
Definition: FrameMgr2D.h:240
void setGPS(Frame2D &parent, const GPSCoordinate gps, float rotationDeg=90)
Definition: FrameMgr2D.h:203
std::vector< const Frame2D * > getPathToRoot(const Frame2D &frame) const
Returns the path from the given frame up to the root (including self). The path is verly small,...
Definition: FrameMgr2D.h:281
Represents a geodetic GPS coordinate with latitude, longitude, and optional altitude.
Definition: GPSCoordinate.h:52
GPSCoordinate navigate(float distance_m, float bearing_deg, float alt_diff_m=0) const
Calculate a new GPS coordinate given a distance (in meters) and bearing.
Definition: GPSCoordinate.h:114
Represents a 2D rigid body transform (SE(2)): translation and rotation.
Definition: FrameMgr2D.h:42
Transform2D inverse() const
Invert this transform.
Definition: FrameMgr2D.h:88
Transform2D operator*(const Transform2D &other) const
Compose this transform with another (this * other). Applies this transform, then the other.
Definition: FrameMgr2D.h:74
Coordinate< DistanceM > pos
Position (x, y) in meters.
Definition: FrameMgr2D.h:50
float heading_deg
Heading in degrees (0 = x axis)
Definition: FrameMgr2D.h:51
Coordinate< DistanceM > apply(const Coordinate< DistanceM > &local) const
Applies this transform to a local coordinate, returning the coordinate in the global frame....
Definition: FrameMgr2D.h:63
float getHeading(AngleUnit unit) const
Get heading in specified unit (degrees or radians)
Definition: FrameMgr2D.h:54
AngleUnit
Supported angle units for conversion and representation.
Definition: Angle.h:11
FrameType
Frame type for coordinate systems and reference frames.
Definition: Common.h:15
Represents a 2D coordinate frame in a hierarchical frame tree.
Definition: FrameMgr2D.h:130