Vulcan
Aerospace Engineering Utilities Built on Janus
Loading...
Searching...
No Matches
MassProperties.hpp
Go to the documentation of this file.
1// Vulcan Mass Properties
2// Full mass properties for rigid body components with aggregation support
3#pragma once
4
6
7#include <janus/janus.hpp>
8
9#include <type_traits>
10#include <vector>
11
12namespace vulcan::mass {
13
65template <typename Scalar> struct MassProperties {
66 Scalar mass;
67 Vec3<Scalar> cg;
68 Mat3<Scalar>
70
71 // =========================================================================
72 // Factory Constructors
73 // =========================================================================
74
81 static MassProperties<Scalar> point_mass(const Scalar &m,
82 const Vec3<Scalar> &position) {
84 .mass = m, .cg = position, .inertia = Mat3<Scalar>::Zero()};
85 }
86
94 static MassProperties<Scalar> diagonal(const Scalar &m,
95 const Vec3<Scalar> &cg_pos,
96 const Scalar &Ixx, const Scalar &Iyy,
97 const Scalar &Izz) {
98 Mat3<Scalar> I = Mat3<Scalar>::Zero();
99 I(0, 0) = Ixx;
100 I(1, 1) = Iyy;
101 I(2, 2) = Izz;
102 return MassProperties<Scalar>{.mass = m, .cg = cg_pos, .inertia = I};
103 }
104
119 from_components(const Scalar &m, const Vec3<Scalar> &cg_pos,
120 const Scalar &Ixx, const Scalar &Iyy, const Scalar &Izz,
121 const Scalar &Ixy, const Scalar &Ixz, const Scalar &Iyz) {
122 Mat3<Scalar> I;
123 I(0, 0) = Ixx;
124 I(0, 1) = -Ixy;
125 I(0, 2) = -Ixz;
126 I(1, 0) = -Ixy;
127 I(1, 1) = Iyy;
128 I(1, 2) = -Iyz;
129 I(2, 0) = -Ixz;
130 I(2, 1) = -Iyz;
131 I(2, 2) = Izz;
132 return MassProperties<Scalar>{.mass = m, .cg = cg_pos, .inertia = I};
133 }
134
135 // =========================================================================
136 // Shape Factories
137 // =========================================================================
138
147 solid_sphere(const Scalar &m, const Scalar &radius,
148 const Vec3<Scalar> &cg_pos = Vec3<Scalar>::Zero()) {
149 Scalar I_diag = Scalar(0.4) * m * radius * radius; // 2/5 = 0.4
150 return diagonal(m, cg_pos, I_diag, I_diag, I_diag);
151 }
152
163 solid_cylinder(const Scalar &m, const Scalar &radius, const Scalar &length,
164 const Vec3<Scalar> &cg_pos = Vec3<Scalar>::Zero()) {
165 Scalar r2 = radius * radius;
166 Scalar L2 = length * length;
167 Scalar Ixx = (Scalar(1) / Scalar(12)) * m * (Scalar(3) * r2 + L2);
168 Scalar Izz = Scalar(0.5) * m * r2;
169 return diagonal(m, cg_pos, Ixx, Ixx, Izz);
170 }
171
184 solid_box(const Scalar &m, const Scalar &dx, const Scalar &dy,
185 const Scalar &dz,
186 const Vec3<Scalar> &cg_pos = Vec3<Scalar>::Zero()) {
187 Scalar k = Scalar(1) / Scalar(12);
188 Scalar Ixx = k * m * (dy * dy + dz * dz);
189 Scalar Iyy = k * m * (dx * dx + dz * dz);
190 Scalar Izz = k * m * (dx * dx + dy * dy);
191 return diagonal(m, cg_pos, Ixx, Iyy, Izz);
192 }
193
194 // =========================================================================
195 // Backward Compatibility Factories
196 // =========================================================================
197
201 static MassProperties<Scalar> from_mass(const Scalar &m) {
202 // Return with identity-scaled inertia for legacy compatibility
203 Mat3<Scalar> I = Mat3<Scalar>::Identity();
204 I(0, 0) = m;
205 I(1, 1) = m;
206 I(2, 2) = m;
208 .mass = m, .cg = Vec3<Scalar>::Zero(), .inertia = I};
209 }
210
214 static MassProperties<Scalar> diagonal(const Scalar &m, const Scalar &Ixx,
215 const Scalar &Iyy,
216 const Scalar &Izz) {
217 return diagonal(m, Vec3<Scalar>::Zero(), Ixx, Iyy, Izz);
218 }
219
223 static MassProperties<Scalar> full(const Scalar &m, const Scalar &Ixx,
224 const Scalar &Iyy, const Scalar &Izz,
225 const Scalar &Ixy, const Scalar &Ixz,
226 const Scalar &Iyz) {
227 return from_components(m, Vec3<Scalar>::Zero(), Ixx, Iyy, Izz, Ixy, Ixz,
228 Iyz);
229 }
230
231 // =========================================================================
232 // Parallel Axis Theorem
233 // =========================================================================
234
247 Mat3<Scalar> inertia_about_point(const Vec3<Scalar> &point) const {
248 Vec3<Scalar> r = cg - point;
249 Scalar r_dot_r = janus::dot(r, r);
250
251 // J = I + m * (|r|² * I_3 - r ⊗ r)
252 Mat3<Scalar> J = inertia;
253 J(0, 0) += mass * (r_dot_r - r(0) * r(0));
254 J(1, 1) += mass * (r_dot_r - r(1) * r(1));
255 J(2, 2) += mass * (r_dot_r - r(2) * r(2));
256 J(0, 1) -= mass * r(0) * r(1);
257 J(1, 0) -= mass * r(0) * r(1);
258 J(0, 2) -= mass * r(0) * r(2);
259 J(2, 0) -= mass * r(0) * r(2);
260 J(1, 2) -= mass * r(1) * r(2);
261 J(2, 1) -= mass * r(1) * r(2);
262
263 return J;
264 }
265
266 // =========================================================================
267 // Aggregation Operators
268 // =========================================================================
269
275 operator+(const MassProperties<Scalar> &other) const {
276 Scalar total_mass = mass + other.mass;
277
278 // Combined CG (mass-weighted average)
279 Vec3<Scalar> combined_cg =
280 (mass * cg + other.mass * other.cg) / total_mass;
281
282 // Transform both inertias to combined CG and sum
283 Mat3<Scalar> I1_at_cg = inertia_about_point(combined_cg);
284 Mat3<Scalar> I2_at_cg = other.inertia_about_point(combined_cg);
285 Mat3<Scalar> combined_inertia = I1_at_cg + I2_at_cg;
286
288 .mass = total_mass, .cg = combined_cg, .inertia = combined_inertia};
289 }
290
295 operator-(const MassProperties<Scalar> &other) const {
296 return *this + (Scalar(-1) * other);
297 }
298
302 MassProperties<Scalar> operator*(const Scalar &factor) const {
304 .mass = mass * factor, .cg = cg, .inertia = inertia * factor};
305 }
306
308 MassProperties<Scalar> operator/(const Scalar &factor) const {
309 return *this * (Scalar(1) / factor);
310 }
311
314 *this = *this + other;
315 return *this;
316 }
317
318 // =========================================================================
319 // Accessors (for backward compatibility)
320 // =========================================================================
321
325 Vec3<Scalar> cg_offset() const { return cg; }
326};
327
329template <typename Scalar>
331 const MassProperties<Scalar> &props) {
332 return props * factor;
333}
334
335// =============================================================================
336// Free Functions
337// =============================================================================
338
346template <typename Scalar>
348 const std::vector<MassProperties<Scalar>> &components) {
349 if (components.empty()) {
350 return MassProperties<Scalar>{.mass = Scalar(0),
351 .cg = Vec3<Scalar>::Zero(),
352 .inertia = Mat3<Scalar>::Zero()};
353 }
354
355 MassProperties<Scalar> result = components[0];
356 for (size_t i = 1; i < components.size(); ++i) {
357 result = result + components[i];
358 }
359 return result;
360}
361
368template <typename Scalar>
369MassProperties<Scalar>
371 const Mat3<Scalar> &rotation,
372 const Vec3<Scalar> &translation) {
373 // Transform CG position
374 Vec3<Scalar> new_cg = rotation * (props.cg - translation);
375
376 // Transform inertia tensor: I' = R * I * R^T
377 Mat3<Scalar> new_inertia = rotation * props.inertia * rotation.transpose();
378
380 .mass = props.mass, .cg = new_cg, .inertia = new_inertia};
381}
382
383// =============================================================================
384// Numeric-Only Functions (require eigenvalue decomposition)
385// =============================================================================
386
395template <typename Scalar>
397 static_assert(std::is_same_v<Scalar, double>,
398 "is_physically_valid only works with double");
399
400 if (props.mass < 0) {
401 return false;
402 }
403
404 // Compute eigenvalues for principal moments
405 Eigen::SelfAdjointEigenSolver<Mat3<double>> solver(props.inertia);
406 Vec3<double> eigs = solver.eigenvalues();
407
408 // All principal moments must be non-negative
409 if (eigs(0) < 0 || eigs(1) < 0 || eigs(2) < 0) {
410 return false;
411 }
412
413 // Triangle inequality
414 if (eigs(0) + eigs(1) < eigs(2) || eigs(0) + eigs(2) < eigs(1) ||
415 eigs(1) + eigs(2) < eigs(0)) {
416 return false;
417 }
418
419 return true;
420}
421
423template <typename Scalar>
425 static_assert(std::is_same_v<Scalar, double>,
426 "is_point_mass only works with double");
427 return props.inertia.norm() < 1e-12;
428}
429
431template <typename Scalar>
432Vec3<Scalar> principal_moments(const MassProperties<Scalar> &props) {
433 static_assert(std::is_same_v<Scalar, double>,
434 "principal_moments only works with double");
435 Eigen::SelfAdjointEigenSolver<Mat3<double>> solver(props.inertia);
436 return solver.eigenvalues();
437}
438
440template <typename Scalar>
441Mat3<Scalar> principal_axes(const MassProperties<Scalar> &props) {
442 static_assert(std::is_same_v<Scalar, double>,
443 "principal_axes only works with double");
444 Eigen::SelfAdjointEigenSolver<Mat3<double>> solver(props.inertia);
445 return solver.eigenvectors();
446}
447
448} // namespace vulcan::mass
Definition MassProperties.hpp:12
MassProperties< Scalar > operator*(const Scalar &factor, const MassProperties< Scalar > &props)
Left-multiplication: factor * props.
Definition MassProperties.hpp:330
MassProperties< Scalar > transform_mass_properties(const MassProperties< Scalar > &props, const Mat3< Scalar > &rotation, const Vec3< Scalar > &translation)
Definition MassProperties.hpp:370
Mat3< Scalar > principal_axes(const MassProperties< Scalar > &props)
Compute principal axes rotation matrix (eigenvectors, numeric only).
Definition MassProperties.hpp:441
bool is_physically_valid(const MassProperties< Scalar > &props)
Definition MassProperties.hpp:396
bool is_point_mass(const MassProperties< Scalar > &props)
Check if effectively a point mass (zero inertia).
Definition MassProperties.hpp:424
MassProperties< Scalar > aggregate_mass_properties(const std::vector< MassProperties< Scalar > > &components)
Definition MassProperties.hpp:347
Vec3< Scalar > principal_moments(const MassProperties< Scalar > &props)
Compute principal moments of inertia (eigenvalues, numeric only).
Definition MassProperties.hpp:432
Definition MassProperties.hpp:65
static MassProperties< Scalar > solid_box(const Scalar &m, const Scalar &dx, const Scalar &dy, const Scalar &dz, const Vec3< Scalar > &cg_pos=Vec3< Scalar >::Zero())
Definition MassProperties.hpp:184
static MassProperties< Scalar > solid_sphere(const Scalar &m, const Scalar &radius, const Vec3< Scalar > &cg_pos=Vec3< Scalar >::Zero())
Definition MassProperties.hpp:147
Vec3< Scalar > cg
CG position in body frame [m].
Definition MassProperties.hpp:67
static MassProperties< Scalar > diagonal(const Scalar &m, const Vec3< Scalar > &cg_pos, const Scalar &Ixx, const Scalar &Iyy, const Scalar &Izz)
Definition MassProperties.hpp:94
static MassProperties< Scalar > point_mass(const Scalar &m, const Vec3< Scalar > &position)
Definition MassProperties.hpp:81
MassProperties< Scalar > operator*(const Scalar &factor) const
Definition MassProperties.hpp:302
Scalar mass
Total mass [kg].
Definition MassProperties.hpp:66
MassProperties< Scalar > operator-(const MassProperties< Scalar > &other) const
Definition MassProperties.hpp:295
MassProperties< Scalar > operator/(const Scalar &factor) const
Divide mass properties by a factor.
Definition MassProperties.hpp:308
MassProperties< Scalar > & operator+=(const MassProperties< Scalar > &other)
In-place addition.
Definition MassProperties.hpp:313
static MassProperties< Scalar > from_components(const Scalar &m, const Vec3< Scalar > &cg_pos, const Scalar &Ixx, const Scalar &Iyy, const Scalar &Izz, const Scalar &Ixy, const Scalar &Ixz, const Scalar &Iyz)
Definition MassProperties.hpp:119
static MassProperties< Scalar > solid_cylinder(const Scalar &m, const Scalar &radius, const Scalar &length, const Vec3< Scalar > &cg_pos=Vec3< Scalar >::Zero())
Definition MassProperties.hpp:163
static MassProperties< Scalar > diagonal(const Scalar &m, const Scalar &Ixx, const Scalar &Iyy, const Scalar &Izz)
Definition MassProperties.hpp:214
Mat3< Scalar > inertia_about_point(const Vec3< Scalar > &point) const
Definition MassProperties.hpp:247
MassProperties< Scalar > operator+(const MassProperties< Scalar > &other) const
Definition MassProperties.hpp:275
Mat3< Scalar > inertia
Inertia tensor about CG, in body-fixed axes [kg·m²].
Definition MassProperties.hpp:69
static MassProperties< Scalar > from_mass(const Scalar &m)
Definition MassProperties.hpp:201
static MassProperties< Scalar > full(const Scalar &m, const Scalar &Ixx, const Scalar &Iyy, const Scalar &Izz, const Scalar &Ixy, const Scalar &Ixz, const Scalar &Iyz)
Definition MassProperties.hpp:223
Vec3< Scalar > cg_offset() const
Definition MassProperties.hpp:325