pure-cpp 1.0.0
A C++ physics simulation benchmark comparing performance with Python implementations
body.hpp
Go to the documentation of this file.
1#ifndef BODY_HPP
2#define BODY_HPP
3
4/**
5 * \file body.hpp
6 * \brief SoA container for simulation bodies and proxies for AoS-like access.
7 * \author Le Bars, Yoann
8 * \ingroup PhysicsCore
9 *
10 * This file is part of the pure C++ benchmark.
11 */
12
13#include <Eigen/Dense>
14#include <Eigen/Geometry>
15#include <cassert>
16#include <vector>
17
18namespace Model {
19
20 class Bodies; // Forward declaration
21 class ConstBodyProxy; // Forward declaration
22 class BodiesAdaptor; // Forward declaration for kdtree adaptor
23
24 using Eigen::Quaterniond;
25 using Eigen::Vector3d;
26 using Eigen::Vector4d;
27
28 /* We need to declare these types as metatypes so they can be used in
29 queued signal/slot connections across threads. */
30 using Vector3dVec =
31 std::vector<Eigen::Vector3d, Eigen::aligned_allocator<Eigen::Vector3d>>;
32 using QuaterniondVec =
33 std::vector<Eigen::Quaterniond,
34 Eigen::aligned_allocator<Eigen::Quaterniond>>;
35
36 /**
37 * \brief A template base class for body proxies to reduce code
38 * duplication.
39 * \tparam T Either `Bodies` or `const Bodies`.
40 * \ingroup PhysicsCore
41 */
42 template <typename T>
44 public:
45 /**
46 * \brief Construct a new Body Proxy Base object.
47 *
48 * \param in_bodies The `Bodies` container to be referenced.
49 * \param in_index The index of the body this proxy will access.
50 */
51 BodyProxyBase(T& in_bodies, std::size_t in_index)
52 : bodies_(in_bodies), index_(in_index) {}
53
54 // --- Common Getters ---
55 /**
56 * \brief Access to body masses.
57 *
58 * \return Mass of the current body.
59 */
60 [[nodiscard]] double m() const noexcept { return bodies_.m_[index_]; }
61
62 /**
63 * \brief Access to the inverse of the body masses.
64 *
65 * \return The inverse of the current body's mass.
66 */
67 [[nodiscard]] double getInverseMass() const {
68 return bodies_.invM_[index_]; // NOLINT
69 }
70
71 /**
72 * \brief Access to body radii.
73 *
74 * \return The radius of the current body.
75 */
76 [[nodiscard]] double r() const noexcept { return bodies_.r_[index_]; }
77
78 /**
79 * \brief Access to the inverse of the body's moment of inertia.
80 *
81 * \return The inverse of the current body's moment of inertia.
82 */
83 [[nodiscard]] double getInverseInertia() const {
84 return bodies_.iInv_[index_]; // NOLINT
85 }
86
87 /**
88 * \brief Access to body positions.
89 *
90 * \return A const reference to the current body's position vector.
91 */
92 [[nodiscard]] const Vector3d& x() const noexcept {
93 return bodies_.x_[index_];
94 }
95
96 /**
97 * \brief Access to body velocities.
98 *
99 * \return A const reference to the current body's velocity vector.
100 */
101 [[nodiscard]] const Vector3d& v() const noexcept {
102 return bodies_.v_[index_];
103 }
104
105 /**
106 * \brief Access to body orientation.
107 *
108 * \return A const reference to the current body's orientation
109 * quaternion.
110 */
111 [[nodiscard]] const Quaterniond& q() const noexcept {
112 return bodies_.q_[index_];
113 }
114
115 /**
116 * \brief Access to body angular velocities.
117 *
118 * \return A const reference to the current body's angular velocity
119 * vector.
120 */
121 [[nodiscard]] const Vector3d& omega() const noexcept {
122 return bodies_.omega_[index_];
123 }
124
125 protected:
126 /// \brief Reference to the main SoA container.
128
129 /// \brief The index of the body this proxy refers to.
130 std::size_t index_;
131 };
132
133 /**
134 * \brief A proxy object that provides an AoS-like interface to a body
135 * stored in the `Bodies` SoA container.
136 */
137 /**
138 * \brief Mutable proxy for accessing individual bodies in the Bodies
139 * container.
140 * \ingroup PhysicsCore
141 */
142 class BodyProxy : public BodyProxyBase<Bodies> {
143 public:
144 /**
145 * \brief Construct a new Body Proxy object.
146 *
147 * \param in_bodies The `Bodies` container to be accessed.
148 * \param in_index The index of the body this proxy will access.
149 */
150 BodyProxy(Bodies& in_bodies, std::size_t in_index);
151
152 /**
153 * \brief Implicit conversion operator to a const proxy.
154 *
155 * \return A const proxy to the same body.
156 */
157 // NOLINTNEXTLINE(google-explicit-constructor)
158 operator ConstBodyProxy() const;
159
160 // --- Getters ---
161 /**
162 * \brief Access to body accelerations.
163 * \return A mutable reference to the body's linear acceleration
164 * vector.
165 */
166 [[nodiscard]] Vector3d& a();
167
168 /**
169 * \brief Access to body angular accelerations.
170 *
171 * \return A mutable reference to the body's angular acceleration
172 * vector.
173 */
174 [[nodiscard]] Vector3d& alpha();
175
176 /**
177 * \brief Access to body angular velocities.
178 * \return A mutable reference to the body's angular velocity vector.
179 */
180 [[nodiscard]] Vector3d& omega();
181
182 // --- Modifiers ---
183 /**
184 * \brief Add a displacement to a position.
185 *
186 * \param correction Correction to be applied.
187 */
188 void addToX(const Vector3d& correction);
189
190 /**
191 * \brief Apply an impulse to a body.
192 *
193 * \param in_J Impulse to be applied.
194 * \param in_r_vec Vector from the centre of mass to the contact point.
195 * \return The torque vector generated by the impulse on this body.
196 */
197 Vector3d applyImpulse(const Vector3d& in_J, const Vector3d& in_r_vec);
198
199 /**
200 * \brief Set the acceleration of a body.
201 *
202 * \param new_a The new linear acceleration vector.
203 */
204 void setAcceleration(const Vector3d& new_a);
205
206 /**
207 * \brief Set the angular acceleration of a body.
208 *
209 * \param new_alpha The new angular acceleration vector.
210 */
211 void setAngularAcceleration(const Vector3d& new_alpha);
212
213 /**
214 * \brief Accumulate an acceleration to a body.
215 *
216 * \param accel The linear acceleration vector to add.
217 */
218 void accumulateAcceleration(const Vector3d& accel);
219
220 /**
221 * \brief Accumulate an angular acceleration to a body.
222 *
223 * \param in_alpha The angular acceleration vector to add.
224 */
225 void accumulateAngularAcceleration(const Vector3d& in_alpha);
226
227 /// \brief Resets linear and angular accelerations to zero.
228 void resetAccelerations();
229
230 /// \brief Resets the accumulated torque to zero.
231 void resetTorque();
232
233 /// \brief Accumulates a torque vector to the body's total torque.
234 void accumulateTorque(const Vector3d& torque);
235
236 /**
237 * \brief Applies damping to linear and angular velocities.
238 *
239 * \param linear_damping The damping factor for linear velocity.
240 * \param angular_damping The damping factor for angular velocity.
241 */
242 void dampenVelocity(double linear_damping, double angular_damping);
243
244 /// \brief Updates angular acceleration based on the accumulated torque.
246 };
247
248 /**
249 * \brief A const proxy object that provides a read-only AoS-like interface
250 * to a body stored in the `Bodies` SoA container.
251 * \ingroup PhysicsCore
252 */
253 class ConstBodyProxy : public BodyProxyBase<const Bodies> {
254 public:
255 /**
256 * \brief Construct a new Const Body Proxy object.
257 *
258 * \param in_bodies The `Bodies` container to be accessed.
259 * \param in_index The index of the body this proxy will access.
260 */
261 ConstBodyProxy(const Bodies& in_bodies, std::size_t in_index);
262 };
263
264 /**
265 * \brief Structure-of-Arrays (SoA) container for all bodies in the
266 * simulation.
267 *
268 * This layout improves cache performance by storing related data
269 * contiguously.
270 */
271 /**
272 * \brief Structure of Arrays container for all simulation bodies.
273 * \ingroup PhysicsCore
274 */
275 class Bodies {
276 public:
277 // Grant access to the proxy base class template.
278 template <typename T>
279 friend class BodyProxyBase;
280 friend class BodyProxy;
281 friend class Space;
282 friend class ConstBodyProxy;
283 friend class BodiesAdaptor;
284
285 /**
286 * \brief Reserve memory space for the data structure.
287 *
288 * \param n Number of bodies in the simulation.
289 */
290 void reserve(std::size_t n) {
291 m_.reserve(n);
292 invM_.reserve(n);
293 r_.reserve(n);
294 iInv_.reserve(n);
295 x_.reserve(n);
296 v_.reserve(n);
297 a_.reserve(n);
298 q_.reserve(n);
299 omega_.reserve(n);
300 alpha_.reserve(n);
301 torque_.reserve(n);
302 }
303
304 /**
305 * \brief Add a new body at the back of the data structure.
306 *
307 * \param in_m New body mass.
308 * \param in_r New body radius.
309 * \param in_x0 New body initial position.
310 * \param in_v0 New body initial velocity.
311 */
312 void emplaceBack(double in_m, double in_r, const Vector3d& in_x0,
313 const Vector3d& in_v0, const Vector3d& in_omega0) {
314 m_.push_back(in_m);
315 invM_.push_back(in_m > 0 ? 1.0 / in_m : 0.0);
316 r_.push_back(in_r);
317 if (invM_.back() > 0 && in_r > 0) {
318 iInv_.push_back(1.0 / (0.4 * in_m * in_r * in_r));
319 } else {
320 iInv_.push_back(0.0);
321 }
322 x_.push_back(in_x0);
323 v_.push_back(in_v0);
324 a_.push_back(Vector3d::Zero());
325 q_.push_back(Quaterniond::Identity());
326 omega_.push_back(in_omega0);
327 alpha_.push_back(Vector3d::Zero());
328 torque_.push_back(Vector3d::Zero());
329 }
330
331 /**
332 * \brief Vectorized Velocity Verlet: Part 1.
333 *
334 * \param dt The time step for the current frame's velocity update.
335 * \param dt_pos The time step from the previous frame for the position
336 * update.
337 */
338 void integratePart1(double dt, double dt_pos) {
339#pragma omp parallel for schedule(static)
340 for (std::size_t i = 0; i < size(); ++i) {
341 /* In Velocity Verlet, positions are updated using the full
342 time step from the *previous* frame's dynamics. */
343 x_[i] += v_[i] * dt_pos + 0.5 * a_[i] * dt_pos * dt_pos;
344
345 /* Velocities and rotations are half-updated using the
346 *current* frame’s time step, `dt`. */
347 v_[i] += 0.5 * a_[i] * dt;
348 omega_[i] += 0.5 * alpha_[i] * dt;
349
350 // RK2 Midpoint Method for quaternion integration
351 if (omega_[i].squaredNorm() >= 1e-12) {
352 const Vector4d q_coeffs = q_[i].coeffs();
353 const Vector4d k1 =
354 getQuaternionDerivative(omega_[i], q_coeffs);
355 const Vector4d q_mid =
356 (q_coeffs + 0.25 * dt * k1).normalized();
357 const Vector4d k2 =
359 q_[i].coeffs() += 0.5 * dt * k2;
360 q_[i].normalize();
361 }
362 }
363 }
364
365 /**
366 * \brief Get number of bodies in the simulation.
367 *
368 * \return The total number of bodies.
369 */
370 [[nodiscard]] std::size_t size() const { return m_.size(); }
371
372 /**
373 * \brief A proxy to access a given body.
374 *
375 * \param index Index of the body to be accessed.
376 *
377 * \return A mutable proxy to the specified body.
378 */
379 BodyProxy operator[](std::size_t index) {
380 assert(index < size() && "Index out of bounds");
381 return BodyProxy(*this, index);
382 }
383
384 /**
385 * \brief Array like access to given body.
386 *
387 * \param index Index of the body to be accessed.
388 *
389 * \return A const proxy to the specified body.
390 */
391 ConstBodyProxy operator[](std::size_t index) const {
392 assert(index < size() && "Index out of bounds");
393 return ConstBodyProxy(*this, index);
394 }
395
396 // --- Data Arrays (SoA) ---
397 private:
398 /**
399 * \brief Calculates the time derivative of a quaternion.
400 *
401 * \param omega The angular velocity vector.
402 * \param q_coeffs The quaternion coefficients [x, y, z, w].
403 * \return The quaternion derivative multiplied by 2.
404 */
405 [[nodiscard]] static Vector4d getQuaternionDerivative( // NOLINT
406 const Vector3d& omega, const Vector4d& q_coeffs) {
407 const Vector3d q_vec = q_coeffs.head<3>();
408 const double q_w = q_coeffs.w();
409
410 const Vector3d derivative_vec = q_w * omega + omega.cross(q_vec);
411 const double derivative_w = -omega.dot(q_vec);
412
413 return {derivative_vec.x(), derivative_vec.y(), derivative_vec.z(),
414 derivative_w};
415 }
416
417 /// \brief Bodies’ masses.
418 std::vector<double> m_;
419
420 /// \brief Bodies inverse masses.
421 std::vector<double> invM_;
422
423 /// \brief Bodies radii.
424 std::vector<double> r_;
425
426 /// \brief Bodies inverse moments of inertia.
427 std::vector<double> iInv_;
428
429 /// \brief Bodies' positions.
430 std::vector<Vector3d> x_;
431
432 /// \brief Bodies' velocities.
433 std::vector<Vector3d> v_;
434
435 /// \brief Bodies' accelerations.
436 std::vector<Vector3d> a_;
437
438 /// \brief Bodies' orientations (quaternions).
439 std::vector<Quaterniond> q_;
440
441 /// \brief Bodies' angular velocities.
442 std::vector<Vector3d> omega_;
443
444 /// \brief Bodies' angular accelerations.
445 std::vector<Vector3d> alpha_;
446
447 /// \brief Net torque applied to each body in a frame.
448 std::vector<Vector3d> torque_;
449 };
450
451 // --- Implementation of BodyProxy ---
452
453 inline BodyProxy::BodyProxy(Bodies& in_bodies, std::size_t in_index)
454 : BodyProxyBase<Bodies>(in_bodies, in_index) {}
455
456 inline BodyProxy::operator ConstBodyProxy() const {
457 return {bodies_, index_}; // Implicit conversion is fine here
458 }
459
460 inline Vector3d& BodyProxy::a() { return bodies_.a_[index_]; }
461
462 inline Vector3d& BodyProxy::alpha() { return bodies_.alpha_[index_]; }
463
464 inline Vector3d& BodyProxy::omega() {
465 return bodies_.omega_[index_]; // Return a mutable reference
466 }
467
468 // --- Implementation of BodyProxy ---
469
470 inline void BodyProxy::addToX(const Vector3d& correction) {
471 bodies_.x_[index_] += correction;
472 }
473
474 inline Vector3d BodyProxy::applyImpulse(const Vector3d& in_J,
475 const Vector3d& in_r_vec) {
476 bodies_.v_[index_] += in_J * getInverseMass();
477 return in_r_vec.cross(in_J);
478 }
479
480 inline void BodyProxy::setAcceleration(const Vector3d& new_a) {
481 bodies_.a_[index_] = new_a;
482 }
483
484 inline void BodyProxy::setAngularAcceleration(const Vector3d& new_alpha) {
485 bodies_.alpha_[index_] = new_alpha;
486 }
487
488 inline void BodyProxy::accumulateAcceleration(const Vector3d& accel) {
489 bodies_.a_[index_] += accel;
490 }
491
493 const Vector3d& in_alpha) {
494 bodies_.alpha_[index_] += in_alpha;
495 }
496
498 bodies_.a_[index_].setZero();
499 bodies_.alpha_[index_].setZero();
500 }
501
502 inline void BodyProxy::resetTorque() { bodies_.torque_[index_].setZero(); }
503
504 inline void BodyProxy::accumulateTorque(const Vector3d& torque) {
505 bodies_.torque_[index_] += torque;
506 }
507
510 }
511
512 inline void BodyProxy::dampenVelocity(double linear_damping,
513 double angular_damping) {
514 bodies_.v_[index_] *= (1.0 - linear_damping);
515 bodies_.omega_[index_] *= (1.0 - angular_damping);
516 }
517
518 // --- Implementation of ConstBodyProxy ---
519
520 inline ConstBodyProxy::ConstBodyProxy(const Bodies& in_bodies,
521 std::size_t in_index)
522 : BodyProxyBase<const Bodies>(in_bodies, in_index) {}
523
524} // namespace Model
525#endif // BODY_HPP
An adapter to allow nanoflann to work directly with our Bodies SoA container.
Definition: kdtree.hpp:29
Structure-of-Arrays (SoA) container for all bodies in the simulation.
Definition: body.hpp:275
std::vector< double > m_
Bodies’ masses.
Definition: body.hpp:418
std::vector< double > r_
Bodies radii.
Definition: body.hpp:424
std::vector< Vector3d > alpha_
Bodies' angular accelerations.
Definition: body.hpp:445
std::vector< Vector3d > omega_
Bodies' angular velocities.
Definition: body.hpp:442
std::vector< double > invM_
Bodies inverse masses.
Definition: body.hpp:421
void reserve(std::size_t n)
Reserve memory space for the data structure.
Definition: body.hpp:290
static Vector4d getQuaternionDerivative(const Vector3d &omega, const Vector4d &q_coeffs)
Calculates the time derivative of a quaternion.
Definition: body.hpp:405
std::vector< Quaterniond > q_
Bodies' orientations (quaternions).
Definition: body.hpp:439
std::size_t size() const
Get number of bodies in the simulation.
Definition: body.hpp:370
std::vector< Vector3d > torque_
Net torque applied to each body in a frame.
Definition: body.hpp:448
std::vector< Vector3d > v_
Bodies' velocities.
Definition: body.hpp:433
std::vector< Vector3d > a_
Bodies' accelerations.
Definition: body.hpp:436
std::vector< Vector3d > x_
Bodies' positions.
Definition: body.hpp:430
void emplaceBack(double in_m, double in_r, const Vector3d &in_x0, const Vector3d &in_v0, const Vector3d &in_omega0)
Add a new body at the back of the data structure.
Definition: body.hpp:312
ConstBodyProxy operator[](std::size_t index) const
Array like access to given body.
Definition: body.hpp:391
BodyProxy operator[](std::size_t index)
A proxy to access a given body.
Definition: body.hpp:379
std::vector< double > iInv_
Bodies inverse moments of inertia.
Definition: body.hpp:427
void integratePart1(double dt, double dt_pos)
Vectorized Velocity Verlet: Part 1.
Definition: body.hpp:338
A template base class for body proxies to reduce code duplication.
Definition: body.hpp:43
std::size_t index_
The index of the body this proxy refers to.
Definition: body.hpp:130
const Quaterniond & q() const noexcept
Access to body orientation.
Definition: body.hpp:111
const Vector3d & v() const noexcept
Access to body velocities.
Definition: body.hpp:101
BodyProxyBase(T &in_bodies, std::size_t in_index)
Construct a new Body Proxy Base object.
Definition: body.hpp:51
double getInverseInertia() const
Access to the inverse of the body's moment of inertia.
Definition: body.hpp:83
double m() const noexcept
Access to body masses.
Definition: body.hpp:60
T & bodies_
Reference to the main SoA container.
Definition: body.hpp:127
double getInverseMass() const
Access to the inverse of the body masses.
Definition: body.hpp:67
double r() const noexcept
Access to body radii.
Definition: body.hpp:76
const Vector3d & x() const noexcept
Access to body positions.
Definition: body.hpp:92
const Vector3d & omega() const noexcept
Access to body angular velocities.
Definition: body.hpp:121
A proxy object that provides an AoS-like interface to a body stored in the Bodies SoA container.
Definition: body.hpp:142
void setAcceleration(const Vector3d &new_a)
Set the acceleration of a body.
Definition: body.hpp:480
Vector3d & a()
Access to body accelerations.
Definition: body.hpp:460
void dampenVelocity(double linear_damping, double angular_damping)
Applies damping to linear and angular velocities.
Definition: body.hpp:512
void addToX(const Vector3d &correction)
Add a displacement to a position.
Definition: body.hpp:470
void accumulateAngularAcceleration(const Vector3d &in_alpha)
Accumulate an angular acceleration to a body.
Definition: body.hpp:492
void resetTorque()
Resets the accumulated torque to zero.
Definition: body.hpp:502
BodyProxy(Bodies &in_bodies, std::size_t in_index)
Construct a new Body Proxy object.
Definition: body.hpp:453
Vector3d & omega()
Access to body angular velocities.
Definition: body.hpp:464
void resetAccelerations()
Resets linear and angular accelerations to zero.
Definition: body.hpp:497
void updateAlphaFromTorque()
Updates angular acceleration based on the accumulated torque.
Definition: body.hpp:508
void setAngularAcceleration(const Vector3d &new_alpha)
Set the angular acceleration of a body.
Definition: body.hpp:484
Vector3d & alpha()
Access to body angular accelerations.
Definition: body.hpp:462
void accumulateTorque(const Vector3d &torque)
Accumulates a torque vector to the body's total torque.
Definition: body.hpp:504
Vector3d applyImpulse(const Vector3d &in_J, const Vector3d &in_r_vec)
Apply an impulse to a body.
Definition: body.hpp:474
void accumulateAcceleration(const Vector3d &accel)
Accumulate an acceleration to a body.
Definition: body.hpp:488
A const proxy object that provides a read-only AoS-like interface to a body stored in the Bodies SoA ...
Definition: body.hpp:253
ConstBodyProxy(const Bodies &in_bodies, std::size_t in_index)
Construct a new Const Body Proxy object.
Definition: body.hpp:520
Class describing a space in which move several bodies.
Definition: space.hpp:90