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 <cassert>
14#include <Eigen/Dense>
15#include <Eigen/Geometry>
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 container.
139 * \ingroup PhysicsCore
140 */
141 class BodyProxy : public BodyProxyBase<Bodies> {
142 public:
143 /**
144 * \brief Construct a new Body Proxy object.
145 *
146 * \param in_bodies The `Bodies` container to be accessed.
147 * \param in_index The index of the body this proxy will access.
148 */
149 BodyProxy(Bodies& in_bodies, std::size_t in_index);
150
151 /**
152 * \brief Implicit conversion operator to a const proxy.
153 *
154 * \return A const proxy to the same body.
155 */
156 // NOLINTNEXTLINE(google-explicit-constructor)
157 operator ConstBodyProxy() const;
158
159 // --- Getters ---
160 /**
161 * \brief Access to body accelerations.
162 * \return A mutable reference to the body's linear acceleration
163 * vector.
164 */
165 [[nodiscard]] Vector3d& a();
166
167 /**
168 * \brief Access to body angular accelerations.
169 *
170 * \return A mutable reference to the body's angular acceleration
171 * vector.
172 */
173 [[nodiscard]] Vector3d& alpha();
174
175 /**
176 * \brief Access to body angular velocities.
177 * \return A mutable reference to the body's angular velocity vector.
178 */
179 [[nodiscard]] Vector3d& omega();
180
181 // --- Modifiers ---
182 /**
183 * \brief Add a displacement to a position.
184 *
185 * \param correction Correction to be applied.
186 */
187 void addToX(const Vector3d& correction);
188
189 /**
190 * \brief Apply an impulse to a body.
191 *
192 * \param in_J Impulse to be applied.
193 * \param in_r_vec Vector from the centre of mass to the contact point.
194 * \return The torque vector generated by the impulse on this body.
195 */
196 Vector3d applyImpulse(const Vector3d& in_J, const Vector3d& in_r_vec);
197
198 /**
199 * \brief Set the acceleration of a body.
200 *
201 * \param new_a The new linear acceleration vector.
202 */
203 void setAcceleration(const Vector3d& new_a);
204
205 /**
206 * \brief Set the angular acceleration of a body.
207 *
208 * \param new_alpha The new angular acceleration vector.
209 */
210 void setAngularAcceleration(const Vector3d& new_alpha);
211
212 /**
213 * \brief Accumulate an acceleration to a body.
214 *
215 * \param accel The linear acceleration vector to add.
216 */
217 void accumulateAcceleration(const Vector3d& accel);
218
219 /**
220 * \brief Accumulate an angular acceleration to a body.
221 *
222 * \param in_alpha The angular acceleration vector to add.
223 */
224 void accumulateAngularAcceleration(const Vector3d& in_alpha);
225
226 /// \brief Resets linear and angular accelerations to zero.
227 void resetAccelerations();
228
229 /// \brief Resets the accumulated torque to zero.
230 void resetTorque();
231
232 /// \brief Accumulates a torque vector to the body's total torque.
233 void accumulateTorque(const Vector3d& torque);
234
235 /**
236 * \brief Applies damping to linear and angular velocities.
237 *
238 * \param linear_damping The damping factor for linear velocity.
239 * \param angular_damping The damping factor for angular velocity.
240 */
241 void dampenVelocity(double linear_damping, double angular_damping);
242
243 /// \brief Updates angular acceleration based on the accumulated torque.
245 };
246
247 /**
248 * \brief A const proxy object that provides a read-only AoS-like interface
249 * to a body stored in the `Bodies` SoA container.
250 * \ingroup PhysicsCore
251 */
252 class ConstBodyProxy : public BodyProxyBase<const Bodies> {
253 public:
254 /**
255 * \brief Construct a new Const Body Proxy object.
256 *
257 * \param in_bodies The `Bodies` container to be accessed.
258 * \param in_index The index of the body this proxy will access.
259 */
260 ConstBodyProxy(const Bodies& in_bodies, std::size_t in_index);
261 };
262
263 /**
264 * \brief Structure-of-Arrays (SoA) container for all bodies in the
265 * simulation.
266 *
267 * This layout improves cache performance by storing related data
268 * contiguously.
269 */
270 /**
271 * \brief Structure of Arrays container for all simulation bodies.
272 * \ingroup PhysicsCore
273 */
274 class Bodies {
275 public:
276 // Grant access to the proxy base class template.
277 template <typename T>
278 friend class BodyProxyBase;
279 friend class BodyProxy;
280 friend class Space;
281 friend class ConstBodyProxy;
282 friend class BodiesAdaptor;
283
284 /**
285 * \brief Reserve memory space for the data structure.
286 *
287 * \param n Number of bodies in the simulation.
288 */
289 void reserve(std::size_t n) {
290 m_.reserve(n);
291 invM_.reserve(n);
292 r_.reserve(n);
293 iInv_.reserve(n);
294 x_.reserve(n);
295 v_.reserve(n);
296 a_.reserve(n);
297 q_.reserve(n);
298 omega_.reserve(n);
299 alpha_.reserve(n);
300 torque_.reserve(n);
301 }
302
303 /**
304 * \brief Add a new body at the back of the data structure.
305 *
306 * \param in_m New body mass.
307 * \param in_r New body radius.
308 * \param in_x0 New body initial position.
309 * \param in_v0 New body initial velocity.
310 */
311 void emplaceBack(double in_m, double in_r, const Vector3d& in_x0,
312 const Vector3d& in_v0, const Vector3d& in_omega0) {
313 m_.push_back(in_m);
314 invM_.push_back(in_m > 0 ? 1.0 / in_m : 0.0);
315 r_.push_back(in_r);
316 if (invM_.back() > 0 && in_r > 0) {
317 iInv_.push_back(1.0 / (0.4 * in_m * in_r * in_r));
318 } else {
319 iInv_.push_back(0.0);
320 }
321 x_.push_back(in_x0);
322 v_.push_back(in_v0);
323 a_.push_back(Vector3d::Zero());
324 q_.push_back(Quaterniond::Identity());
325 omega_.push_back(in_omega0);
326 alpha_.push_back(Vector3d::Zero());
327 torque_.push_back(Vector3d::Zero());
328 }
329
330 /**
331 * \brief Vectorized Velocity Verlet: Part 1.
332 *
333 * \param dt The time step for the current frame's velocity update.
334 * \param dt_pos The time step from the previous frame for the position
335 * update.
336 */
337 void integratePart1(double dt, double dt_pos) {
338#pragma omp parallel for schedule(static)
339 for (std::size_t i = 0; i < size(); ++i) {
340 /* In Velocity Verlet, positions are updated using the full
341 time step from the *previous* frame's dynamics. */
342 x_[i] += v_[i] * dt_pos + 0.5 * a_[i] * dt_pos * dt_pos;
343
344 /* Velocities and rotations are half-updated using the
345 *current* frame’s time step, `dt`. */
346 v_[i] += 0.5 * a_[i] * dt;
347 omega_[i] += 0.5 * alpha_[i] * dt;
348 }
349 // Update all rotations in a single vectorized pass.
351 }
352
353 /**
354 * \brief Vectorized Velocity Verlet: Part 2.
355 * \param dt The time step for the current frame.
356 */
357 void integratePart2(double dt) {
358#pragma omp parallel for
359 for (std::size_t i = 0; i < size(); ++i) {
360 // Complete the velocity update for this frame.
361 omega_[i] += 0.5 * alpha_[i] * dt;
362 v_[i] += 0.5 * a_[i] * dt;
363 }
364 }
365
366 /**
367 * \brief Get number of bodies in the simulation.
368 *
369 * \return The total number of bodies.
370 */
371 [[nodiscard]] std::size_t size() const { return m_.size(); }
372
373 /**
374 * \brief A proxy to access a given body.
375 *
376 * \param index Index of the body to be accessed.
377 *
378 * \return A mutable proxy to the specified body.
379 */
380 BodyProxy operator[](std::size_t index) {
381 assert(index < size() && "Index out of bounds");
382 return BodyProxy(*this, index);
383 }
384
385 /**
386 * \brief Array like access to given body.
387 *
388 * \param index Index of the body to be accessed.
389 *
390 * \return A const proxy to the specified body.
391 */
392 ConstBodyProxy operator[](std::size_t index) const {
393 assert(index < size() && "Index out of bounds");
394 return ConstBodyProxy(*this, index);
395 }
396
397 // --- Data Arrays (SoA) ---
398 private:
399 /**
400 * \brief Calculates the time derivative of a quaternion.
401 *
402 * \param omega The angular velocity vector.
403 * \param q_coeffs The quaternion coefficients [x, y, z, w].
404 * \return The quaternion derivative multiplied by 2.
405 */
406 [[nodiscard]] static Vector4d getQuaternionDerivative( // NOLINT
407 const Vector3d& omega, const Vector4d& q_coeffs) {
408 const Vector3d q_vec = q_coeffs.head<3>();
409 const double q_w = q_coeffs.w();
410
411 const Vector3d derivative_vec = q_w * omega + omega.cross(q_vec);
412 const double derivative_w = -omega.dot(q_vec);
413
414 return {derivative_vec.x(), derivative_vec.y(), derivative_vec.z(),
415 derivative_w};
416 }
417
418 /**
419 * \brief Vectorized update of orientations for all bodies using a
420 * 2nd-order Runge-Kutta method (RK2).
421 *
422 * This method mirrors the efficient, vectorized approach of the Python
423 * implementation, operating on all bodies at once.
424 *
425 * \param dt The time step for the integration.
426 */
427 void updateRotationsRK2(double dt) {
428#pragma omp parallel for schedule(static)
429 for (std::size_t i = 0; i < size(); ++i) {
430 // As an optimisation, skip bodies that are not rotating.
431 if (omega_[i].squaredNorm() < 1e-12) {
432 continue;
433 }
434
435 // RK2 Midpoint Method for quaternion integration
436 const Vector4d q_coeffs = q_[i].coeffs();
437
438 /* k1 = f(q_old)
439 The derivative returns 2*dq/dt, so we use 0.25*dt instead of
440 0.5*dt. */
441 const Vector4d k1 =
442 getQuaternionDerivative(omega_[i], q_coeffs);
443 // q_mid = q_old + 0.5 * dt * (0.5 * k1)
444 const Vector4d q_mid = (q_coeffs + 0.25 * dt * k1).normalized();
445
446 /* k2 = f(q_mid)
447 The derivative returns 2*dq/dt, so we use 0.5*dt instead of
448 dt. */
449 const Vector4d k2 = getQuaternionDerivative(omega_[i], q_mid);
450
451 // q_new = q_old + dt * (0.5 * k2)
452 q_[i].coeffs() += 0.5 * dt * k2;
453 q_[i].normalize();
454 }
455 }
456
457 /// \brief Bodies’ masses.
458 std::vector<double> m_;
459
460 /// \brief Bodies inverse masses.
461 std::vector<double> invM_;
462
463 /// \brief Bodies radii.
464 std::vector<double> r_;
465
466 /// \brief Bodies inverse moments of inertia.
467 std::vector<double> iInv_;
468
469 /// \brief Bodies' positions.
470 std::vector<Vector3d> x_;
471
472 /// \brief Bodies' velocities.
473 std::vector<Vector3d> v_;
474
475 /// \brief Bodies' accelerations.
476 std::vector<Vector3d> a_;
477
478 /// \brief Bodies' orientations (quaternions).
479 std::vector<Quaterniond> q_;
480
481 /// \brief Bodies' angular velocities.
482 std::vector<Vector3d> omega_;
483
484 /// \brief Bodies' angular accelerations.
485 std::vector<Vector3d> alpha_;
486
487 /// \brief Net torque applied to each body in a frame.
488 std::vector<Vector3d> torque_;
489 };
490
491 // --- Implementation of BodyProxy ---
492
493 inline BodyProxy::BodyProxy(Bodies& in_bodies, std::size_t in_index)
494 : BodyProxyBase<Bodies>(in_bodies, in_index) {}
495
496 inline BodyProxy::operator ConstBodyProxy() const {
497 return {bodies_, index_}; // Implicit conversion is fine here
498 }
499
500 inline Vector3d& BodyProxy::a() { return bodies_.a_[index_]; }
501
502 inline Vector3d& BodyProxy::alpha() { return bodies_.alpha_[index_]; }
503
504 inline Vector3d& BodyProxy::omega() {
505 return bodies_.omega_[index_]; // Return a mutable reference
506 }
507
508 // --- Implementation of BodyProxy ---
509
510 inline void BodyProxy::addToX(const Vector3d& correction) {
511 bodies_.x_[index_] += correction;
512 }
513
514 inline Vector3d BodyProxy::applyImpulse(const Vector3d& in_J,
515 const Vector3d& in_r_vec) {
516 bodies_.v_[index_] += in_J * getInverseMass();
517 return in_r_vec.cross(in_J);
518 }
519
520 inline void BodyProxy::setAcceleration(const Vector3d& new_a) {
521 bodies_.a_[index_] = new_a;
522 }
523
524 inline void BodyProxy::setAngularAcceleration(const Vector3d& new_alpha) {
525 bodies_.alpha_[index_] = new_alpha;
526 }
527
528 inline void BodyProxy::accumulateAcceleration(const Vector3d& accel) {
529 bodies_.a_[index_] += accel;
530 }
531
533 const Vector3d& in_alpha) {
534 bodies_.alpha_[index_] += in_alpha;
535 }
536
538 bodies_.a_[index_].setZero();
539 bodies_.alpha_[index_].setZero();
540 }
541
542 inline void BodyProxy::resetTorque() { bodies_.torque_[index_].setZero(); }
543
544 inline void BodyProxy::accumulateTorque(const Vector3d& torque) {
545 bodies_.torque_[index_] += torque;
546 }
547
550 }
551
552 inline void BodyProxy::dampenVelocity(double linear_damping,
553 double angular_damping) {
554 bodies_.v_[index_] *= (1.0 - linear_damping);
555 bodies_.omega_[index_] *= (1.0 - angular_damping);
556 }
557
558 // --- Implementation of ConstBodyProxy ---
559
560 inline ConstBodyProxy::ConstBodyProxy(const Bodies& in_bodies,
561 std::size_t in_index)
562 : BodyProxyBase<const Bodies>(in_bodies, in_index) {}
563
564} // namespace Model
565#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:274
std::vector< double > m_
Bodies’ masses.
Definition: body.hpp:458
std::vector< double > r_
Bodies radii.
Definition: body.hpp:464
std::vector< Vector3d > alpha_
Bodies' angular accelerations.
Definition: body.hpp:485
std::vector< Vector3d > omega_
Bodies' angular velocities.
Definition: body.hpp:482
std::vector< double > invM_
Bodies inverse masses.
Definition: body.hpp:461
void reserve(std::size_t n)
Reserve memory space for the data structure.
Definition: body.hpp:289
static Vector4d getQuaternionDerivative(const Vector3d &omega, const Vector4d &q_coeffs)
Calculates the time derivative of a quaternion.
Definition: body.hpp:406
std::vector< Quaterniond > q_
Bodies' orientations (quaternions).
Definition: body.hpp:479
std::size_t size() const
Get number of bodies in the simulation.
Definition: body.hpp:371
std::vector< Vector3d > torque_
Net torque applied to each body in a frame.
Definition: body.hpp:488
void updateRotationsRK2(double dt)
Vectorized update of orientations for all bodies using a 2nd-order Runge-Kutta method (RK2).
Definition: body.hpp:427
std::vector< Vector3d > v_
Bodies' velocities.
Definition: body.hpp:473
std::vector< Vector3d > a_
Bodies' accelerations.
Definition: body.hpp:476
std::vector< Vector3d > x_
Bodies' positions.
Definition: body.hpp:470
void integratePart2(double dt)
Vectorized Velocity Verlet: Part 2.
Definition: body.hpp:357
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:311
ConstBodyProxy operator[](std::size_t index) const
Array like access to given body.
Definition: body.hpp:392
BodyProxy operator[](std::size_t index)
A proxy to access a given body.
Definition: body.hpp:380
std::vector< double > iInv_
Bodies inverse moments of inertia.
Definition: body.hpp:467
void integratePart1(double dt, double dt_pos)
Vectorized Velocity Verlet: Part 1.
Definition: body.hpp:337
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:141
void setAcceleration(const Vector3d &new_a)
Set the acceleration of a body.
Definition: body.hpp:520
Vector3d & a()
Access to body accelerations.
Definition: body.hpp:500
void dampenVelocity(double linear_damping, double angular_damping)
Applies damping to linear and angular velocities.
Definition: body.hpp:552
void addToX(const Vector3d &correction)
Add a displacement to a position.
Definition: body.hpp:510
void accumulateAngularAcceleration(const Vector3d &in_alpha)
Accumulate an angular acceleration to a body.
Definition: body.hpp:532
void resetTorque()
Resets the accumulated torque to zero.
Definition: body.hpp:542
BodyProxy(Bodies &in_bodies, std::size_t in_index)
Construct a new Body Proxy object.
Definition: body.hpp:493
Vector3d & omega()
Access to body angular velocities.
Definition: body.hpp:504
void resetAccelerations()
Resets linear and angular accelerations to zero.
Definition: body.hpp:537
void updateAlphaFromTorque()
Updates angular acceleration based on the accumulated torque.
Definition: body.hpp:548
void setAngularAcceleration(const Vector3d &new_alpha)
Set the angular acceleration of a body.
Definition: body.hpp:524
Vector3d & alpha()
Access to body angular accelerations.
Definition: body.hpp:502
void accumulateTorque(const Vector3d &torque)
Accumulates a torque vector to the body's total torque.
Definition: body.hpp:544
Vector3d applyImpulse(const Vector3d &in_J, const Vector3d &in_r_vec)
Apply an impulse to a body.
Definition: body.hpp:514
void accumulateAcceleration(const Vector3d &accel)
Accumulate an acceleration to a body.
Definition: body.hpp:528
A const proxy object that provides a read-only AoS-like interface to a body stored in the Bodies SoA ...
Definition: body.hpp:252
ConstBodyProxy(const Bodies &in_bodies, std::size_t in_index)
Construct a new Const Body Proxy object.
Definition: body.hpp:560
Class describing a space in which move several bodies.
Definition: space.hpp:89