TinyRobotics
Loading...
Searching...
No Matches
FrameMgr3D.h
1#pragma once
2#include <array>
3#include <cmath>
4#include <cstdint>
5#include <string>
6#include <vector>
7
8#include "TinyRobotics/coordinates/Coordinates.h"
9#include "TinyRobotics/utils/Common.h"
10#include "TinyRobotics/utils/LoggerClass.h"
11
12namespace tinyrobotics {
13
14/**
15 * @brief Represents a 3D rigid body transform: translation (tx, ty, tz) and
16 * quaternion rotation (qx, qy, qz, qw).
17 */
18struct Transform3D {
19 float tx = 0, ty = 0, tz = 0;
20 float qx = 0, qy = 0, qz = 0, qw = 1;
21
22 static Transform3D identity() { return {}; }
23
24 // Quaternion multiplication and translation composition
25 Transform3D operator*(const Transform3D& other) const {
26 Transform3D r;
27 // Quaternion multiplication
28 r.qw = qw * other.qw - qx * other.qx - qy * other.qy - qz * other.qz;
29 r.qx = qw * other.qx + qx * other.qw + qy * other.qz - qz * other.qy;
30 r.qy = qw * other.qy - qx * other.qz + qy * other.qw + qz * other.qx;
31 r.qz = qw * other.qz + qx * other.qy - qy * other.qx + qz * other.qw;
32
33 // Rotate other's translation by this quaternion and add to this translation
34 float x = other.tx, y = other.ty, z = other.tz;
35 float ix = qw * x + qy * z - qz * y;
36 float iy = qw * y + qz * x - qx * z;
37 float iz = qw * z + qx * y - qy * x;
38 float iw = -qx * x - qy * y - qz * z;
39 r.tx = tx + (ix * qw + iw * -qx + iy * -qz - iz * -qy);
40 r.ty = ty + (iy * qw + iw * -qy + iz * -qx - ix * -qz);
41 r.tz = tz + (iz * qw + iw * -qz + ix * -qy - iy * -qx);
42
43 return r;
44 }
45
46 // Inverse of the transform
47 Transform3D inverse() const {
48 Transform3D r;
49 // Inverse rotation (conjugate)
50 r.qx = -qx;
51 r.qy = -qy;
52 r.qz = -qz;
53 r.qw = qw;
54 // Inverse translation
55 float x = -tx, y = -ty, z = -tz;
56 float ix = r.qw * x + r.qy * z - r.qz * y;
57 float iy = r.qw * y + r.qz * x - r.qx * z;
58 float iz = r.qw * z + r.qx * y - r.qy * x;
59 float iw = -r.qx * x - r.qy * y - r.qz * z;
60 r.tx = (ix * r.qw + iw * -r.qx + iy * -r.qz - iz * -r.qy);
61 r.ty = (iy * r.qw + iw * -r.qy + iz * -r.qx - ix * -r.qz);
62 r.tz = (iz * r.qw + iw * -r.qz + ix * -qy - iy * -qx);
63 return r;
64 }
65
66 /**
67 * @brief Applies this transform to a 3D point (x, y, z).
68 * Rotates the point by the quaternion and then translates it.
69 * @param point Array of 3 floats: {x, y, z}
70 * @return Transformed point as std::array<float, 3>
71 */
72 std::array<float, 3> apply(const std::array<float, 3>& point) const {
73 // Quaternion rotation
74 float x = point[0], y = point[1], z = point[2];
75 // Quaternion-vector multiplication (q * v * q^-1)
76 float qx2 = qx + qx, qy2 = qy + qy, qz2 = qz + qz;
77 float xx = qx * qx2, yy = qy * qy2, zz = qz * qz2;
78 float xy = qx * qy2, xz = qx * qz2, yz = qy * qz2;
79 float wx = qw * qx2, wy = qw * qy2, wz = qw * qz2;
80 float rx = (1 - (yy + zz)) * x + (xy - wz) * y + (xz + wy) * z;
81 float ry = (xy + wz) * x + (1 - (xx + zz)) * y + (yz - wx) * z;
82 float rz = (xz - wy) * x + (yz + wx) * y + (1 - (xx + yy)) * z;
83 return {rx + tx, ry + ty, rz + tz};
84 }
85};
86
87/**
88 * @class Frame3D
89 * @ingroup coordinates
90 * @brief Represents a 3D coordinate frame in a hierarchical frame tree.
91 *
92 * Frame3D models a reference frame in 3D space, such as a robot base, sensor,
93 * or world frame. Each frame can have a parent frame, forming a tree structure
94 * for managing relative poses. The pose of the frame relative to its parent is
95 * described by a Transform3D (translation and quaternion rotation).
96 */
97class Frame3D {
98 public:
99 Frame3D(FrameType type, int index = 0, Frame3D* parent = nullptr,
100 const Transform3D& tf = Transform3D::identity())
101 : type(type), index(index), parent(parent), tf(tf) {}
102
103 void setParent(Frame3D* p) { parent = p; }
104 void setTransform(const Transform3D& t) { tf = t; }
105 Frame3D* getParent() const { return parent; }
106 const Transform3D& getTransform() const { return tf; }
107 FrameType getType() const { return type; }
108 int getIndex() const { return index; }
109
110 protected:
111 FrameType type;
112 int index;
113 Frame3D* parent;
114 Transform3D tf;
115};
116
117/**
118 * @class FrameMgr3D
119 * @ingroup coordinates
120 * @brief Manages a hierarchy of 3D coordinate frames and enables SE(3)
121 * transforms.
122 *
123 * FrameMgr3D is designed for robotics and navigation applications where
124 * multiple coordinate frames (such as world, robot base, and sensors) must be
125 * related and transformed. It supports:
126 * - Defining a tree of 3D frames, each with a pose (translation and
127 * quaternion rotation) relative to its parent.
128 * - Computing the SE(3) transform between any two frames in the hierarchy.
129 *
130 * Example:
131 * @code
132 * FrameMgr3D mgr;
133 * Frame3D world(FrameType3D::WORLD);
134 * Frame3D base(FrameType3D::BASE_LINK, 0, &world, Transform3D{1,2,0,0,0,0,1});
135 * Frame3D camera(FrameType3D::CAMERA, 0, &base,
136 * Transform3D{0.1,0,0.2,0,0,0,1}); Transform3D tf = mgr.getTransform(camera,
137 * world);
138 * @endcode
139 * @ingroup coordinates
140 */
141class FrameMgr3D {
142 public:
143 // Returns the transform from 'from' to 'to'
144 Transform3D getTransform(const Frame3D& from, const Frame3D& to) const {
145 std::vector<const Frame3D*> fromPath = getPathToRoot(&from);
146 std::vector<const Frame3D*> toPath = getPathToRoot(&to);
147
148 // Find common ancestor
149 int i = fromPath.size() - 1;
150 int j = toPath.size() - 1;
151 while (i >= 0 && j >= 0 && fromPath[i] == toPath[j]) {
152 i--;
153 j--;
154 }
155 i++;
156 j++;
157
158 // Compose transforms up from 'from' to ancestor
159 Transform3D tf_from = Transform3D::identity();
160 for (int k = 0; k < i; ++k) {
161 tf_from = fromPath[k]->getTransform() * tf_from;
162 }
163 // Compose transforms up from 'to' to ancestor
164 Transform3D tf_to = Transform3D::identity();
165 for (int k = 0; k < j; ++k) {
166 tf_to = toPath[k]->getTransform() * tf_to;
167 }
168 // The transform from 'from' to 'to' is tf_to.inverse() * tf_from
169 return tf_to.inverse() * tf_from;
170 }
171
172 // GPS/geodetic integration
173 protected:
174 Frame3D* p_gpsParent = nullptr;
175 GPSCoordinate gpsCoordinate;
176 float gpsRotationDeg = 0.0f; // Rotation from local x to GPS north (degrees)
177
178 public:
179 /**
180 * @brief Sets the GPS reference for a frame.
181 * @param gpsParent The frame to associate with the GPS reference.
182 * @param gps The GPS coordinate at the origin of gpsParent.
183 * @param rotationDeg Rotation from local x to GPS north (degrees).
184 */
185 void setGPS(Frame3D& gpsParent, const GPSCoordinate& gps,
186 float rotationDeg = 0.0f) {
187 p_gpsParent = &gpsParent;
188 gpsCoordinate = gps;
189 gpsRotationDeg = rotationDeg;
190 }
191
192 /**
193 * @brief Converts a coordinate in the given frame to a GPS coordinate.
194 * @param frame The frame in which the coordinate is expressed.
195 * @param local The coordinate in the local frame (meters).
196 * @return The corresponding GPS coordinate.
197 */
198 GPSCoordinate toGPS(const Frame3D& frame,
199 const std::array<float, 3>& local) const {
200 if (!p_gpsParent) return gpsCoordinate;
201 // Get transform from local frame to GPS parent frame
202 Transform3D tf = getTransform(frame, *p_gpsParent);
203 // Apply the transform to the local coordinate to get position in GPS parent
204 // frame
205 std::array<float, 3> xyz = tf.apply(local);
206 // Rotate by gpsRotationDeg to align with GPS north (in x/y plane)
207 float theta_rad = gpsRotationDeg * static_cast<float>(M_PI) / 180.0f;
208 float cos_theta = std::cos(theta_rad);
209 float sin_theta = std::sin(theta_rad);
210 float dx = cos_theta * xyz[0] - sin_theta * xyz[1];
211 float dy = sin_theta * xyz[0] + cos_theta * xyz[1];
212 float dz = xyz[2];
213 // Use GPSCoordinate::navigate for conversion (distance in meters, bearing
214 // in degrees)
215 float distance = std::sqrt(dx * dx + dy * dy);
216 float bearing_deg = std::atan2(dx, dy) * 180.0f / static_cast<float>(M_PI);
217 bearing_deg = normalizeAngleDeg(bearing_deg);
218 return gpsCoordinate.navigate(distance, bearing_deg, dz);
219 }
220
221 protected:
222 // Returns the path from the given frame up to the root (including self)
223 std::vector<const Frame3D*> getPathToRoot(const Frame3D* frame) const {
224 std::vector<const Frame3D*> path;
225 while (frame) {
226 path.push_back(frame);
227 frame = frame->getParent();
228 }
229 return path;
230 }
231};
232
233} // namespace tinyrobotics
Represents a 3D rigid body transform: translation (tx, ty, tz) and quaternion rotation (qx,...
Definition: FrameMgr3D.h:18