Vulcan
Aerospace Engineering Utilities Built on Janus
Loading...
Searching...
No Matches
FrameRegistry.hpp
Go to the documentation of this file.
1// Vulcan Frame Registry
2// Tree topology and LCA path finding for coordinate frame transforms
3#pragma once
4
7
8#include <cstdint>
9#include <string>
10#include <vector>
11
12namespace vulcan {
13
15struct FramePath {
16 std::vector<FrameID> frames;
20
21 [[nodiscard]] bool is_valid() const { return !frames.empty(); }
22
23 [[nodiscard]] int length() const {
24 if (frames.empty()) {
25 return -1;
26 }
27 return static_cast<int>(frames.size()) - 1;
28 }
29};
30
33 public:
35 : next_user_id_(static_cast<uint32_t>(BuiltinFrame::_BuiltinCount)) {
36 nodes_.reserve(static_cast<size_t>(BuiltinFrame::_BuiltinCount));
37
38 // Root
39 nodes_.emplace_back();
40
41 // Built-ins are registered in enum order for contiguous indexing.
42 add_builtin(BuiltinFrame::ECEF, FRAME_ECI, "ECEF");
43 add_builtin(BuiltinFrame::NED, FRAME_ECEF, "NED");
44 add_builtin(BuiltinFrame::ENU, FRAME_ECEF, "ENU");
45 add_builtin(BuiltinFrame::Body, FRAME_NED, "Body");
46 add_builtin(BuiltinFrame::Wind, FRAME_BODY, "Wind");
47 add_builtin(BuiltinFrame::Stability, FRAME_BODY, "Stability");
48 add_builtin(BuiltinFrame::Geocentric, FRAME_ECEF, "Geocentric");
49 add_builtin(BuiltinFrame::Rail, FRAME_ECEF, "Rail");
50 add_builtin(BuiltinFrame::CDA, FRAME_ECEF, "CDA");
51 }
52
54 FrameID register_frame(const std::string &name, FrameID parent_id) {
55 if (name.empty()) {
56 throw CoordinateError("Frame name cannot be empty");
57 }
58 if (!has_frame(parent_id)) {
59 throw CoordinateError("Parent frame is not registered");
60 }
61
62 const int depth = depth_of(parent_id) + 1;
63 if (depth > MAX_FRAME_DEPTH) {
64 throw CoordinateError("Frame tree exceeded MAX_FRAME_DEPTH");
65 }
66
67 FrameID id(next_user_id_++);
68 nodes_.emplace_back(id, parent_id, name, depth);
69 return id;
70 }
71
73 [[nodiscard]] FramePath find_path(FrameID from, FrameID to) const {
74 if (!has_frame(from) || !has_frame(to)) {
75 return {};
76 }
77
78 const std::vector<FrameID> path_a = path_to_root(from);
79 const std::vector<FrameID> path_b = path_to_root(to);
80 if (path_a.empty() || path_b.empty()) {
81 return {};
82 }
83
84 int ia = static_cast<int>(path_a.size()) - 1;
85 int ib = static_cast<int>(path_b.size()) - 1;
86 FrameID lca = FRAME_ECI;
87 bool found_lca = false;
88
89 while (ia >= 0 && ib >= 0 && path_a[ia] == path_b[ib]) {
90 lca = path_a[ia];
91 found_lca = true;
92 --ia;
93 --ib;
94 }
95
96 if (!found_lca) {
97 return {};
98 }
99
100 FramePath result;
101 result.lca = lca;
102 result.ascending_count = ia + 1;
103 result.descending_count = ib + 1;
104
105 // Source -> ... -> LCA
106 for (int i = 0; i <= ia + 1; ++i) {
107 result.frames.push_back(path_a[static_cast<size_t>(i)]);
108 }
109
110 // LCA child -> ... -> target
111 for (int i = ib; i >= 0; --i) {
112 result.frames.push_back(path_b[static_cast<size_t>(i)]);
113 }
114
115 return result;
116 }
117
118 [[nodiscard]] const FrameNode &get_node(FrameID id) const {
119 if (!has_frame(id)) {
120 throw CoordinateError("FrameID is not registered");
121 }
122 return nodes_[id.id];
123 }
124
125 [[nodiscard]] bool has_frame(FrameID id) const {
126 return id.id < nodes_.size();
127 }
128
129 [[nodiscard]] FrameID parent_of(FrameID id) const {
130 return get_node(id).parent_id;
131 }
132
133 [[nodiscard]] std::vector<FrameID> children_of(FrameID id) const {
134 if (!has_frame(id)) {
135 throw CoordinateError("FrameID is not registered");
136 }
137
138 std::vector<FrameID> children;
139 children.reserve(nodes_.size());
140 for (const auto &node : nodes_) {
141 if (node.id == id) {
142 continue;
143 }
144 if (node.parent_id == id) {
145 children.push_back(node.id);
146 }
147 }
148 return children;
149 }
150
151 [[nodiscard]] int depth_of(FrameID id) const { return get_node(id).depth; }
152
153 [[nodiscard]] size_t size() const { return nodes_.size(); }
154
155 [[nodiscard]] static FrameRegistry default_aerospace() {
156 return FrameRegistry();
157 }
158
159 private:
160 std::vector<FrameNode> nodes_;
161 uint32_t next_user_id_ = 0;
162
163 void add_builtin(BuiltinFrame frame, FrameID parent, const char *name) {
164 FrameID id(frame);
165 const auto expected = static_cast<uint32_t>(nodes_.size());
166 if (id.id != expected) {
167 throw CoordinateError(
168 "Builtin frame enum values must be contiguous");
169 }
170
171 const int depth = get_node(parent).depth + 1;
172 if (depth > MAX_FRAME_DEPTH) {
173 throw CoordinateError(
174 "Builtin frame tree exceeded MAX_FRAME_DEPTH");
175 }
176
177 nodes_.emplace_back(id, parent, name, depth);
178 }
179
180 [[nodiscard]] std::vector<FrameID> path_to_root(FrameID id) const {
181 if (!has_frame(id)) {
182 return {};
183 }
184
185 std::vector<FrameID> path;
186 path.reserve(static_cast<size_t>(MAX_FRAME_DEPTH + 1));
187
188 FrameID current = id;
189 for (int i = 0; i <= MAX_FRAME_DEPTH; ++i) {
190 path.push_back(current);
191 const auto &node = get_node(current);
192 if (node.is_root || node.parent_id == current) {
193 return path;
194 }
195 if (!has_frame(node.parent_id)) {
196 throw CoordinateError("Frame tree contains missing parent");
197 }
198 current = node.parent_id;
199 }
200
201 throw CoordinateError("Frame tree exceeded MAX_FRAME_DEPTH");
202 }
203};
204
205} // namespace vulcan
Exception hierarchy for Vulcan aerospace library.
Coordinate system errors (invalid LLA, singularities).
Definition VulcanError.hpp:53
FrameID register_frame(const std::string &name, FrameID parent_id)
Register a user-defined frame as a child of an existing frame.
Definition FrameRegistry.hpp:54
static FrameRegistry default_aerospace()
Definition FrameRegistry.hpp:155
FrameRegistry()
Definition FrameRegistry.hpp:34
const FrameNode & get_node(FrameID id) const
Definition FrameRegistry.hpp:118
size_t size() const
Definition FrameRegistry.hpp:153
int depth_of(FrameID id) const
Definition FrameRegistry.hpp:151
std::vector< FrameID > children_of(FrameID id) const
Definition FrameRegistry.hpp:133
FramePath find_path(FrameID from, FrameID to) const
Find path between two frames using lowest common ancestor.
Definition FrameRegistry.hpp:73
FrameID parent_of(FrameID id) const
Definition FrameRegistry.hpp:129
bool has_frame(FrameID id) const
Definition FrameRegistry.hpp:125
Definition Aerodynamics.hpp:11
BuiltinFrame
Built-in frame identifiers.
Definition FrameID.hpp:10
@ ENU
Definition FrameID.hpp:14
@ Wind
Definition FrameID.hpp:16
@ Rail
Definition FrameID.hpp:19
@ Stability
Definition FrameID.hpp:17
@ Body
Definition FrameID.hpp:15
@ _BuiltinCount
Definition FrameID.hpp:21
@ CDA
Definition FrameID.hpp:20
@ NED
Definition FrameID.hpp:13
@ ECEF
Definition FrameID.hpp:12
@ Geocentric
Definition FrameID.hpp:18
constexpr FrameID FRAME_ECI
Definition FrameID.hpp:49
constexpr FrameID FRAME_BODY
Definition FrameID.hpp:53
constexpr FrameID FRAME_ECEF
Definition FrameID.hpp:50
constexpr int MAX_FRAME_DEPTH
Maximum supported frame tree depth.
Definition FrameNode.hpp:13
constexpr FrameID FRAME_NED
Definition FrameID.hpp:51
Universal frame identifier.
Definition FrameID.hpp:25
A node in the frame tree (setup-time structure only).
Definition FrameNode.hpp:16
int depth
Definition FrameNode.hpp:20
FrameID parent_id
Definition FrameNode.hpp:18
Ordered path between frames from source to target.
Definition FrameRegistry.hpp:15
std::vector< FrameID > frames
Definition FrameRegistry.hpp:16
int descending_count
Definition FrameRegistry.hpp:19
bool is_valid() const
Definition FrameRegistry.hpp:21
int ascending_count
Definition FrameRegistry.hpp:18
int length() const
Definition FrameRegistry.hpp:23
FrameID lca
Definition FrameRegistry.hpp:17