Body

BodyProxy Design Pattern

The simulation uses a Structure-of-Arrays (SoA) layout for its core physics data (implemented in the src.body.Bodies class) to maximize cache efficiency and enable highly vectorized computations with NumPy. While this is excellent for performance, directly manipulating individual body properties in an SoA can sometimes be less intuitive than an Array-of-Structures (AoS) approach, where each body is a distinct object with its own attributes.

To bridge this gap, the src.body.BodyProxy class implements the Proxy design pattern.

Purpose of BodyProxy:

The src.body.BodyProxy acts as a convenient, object-oriented wrapper around a single body’s data within the src.body.Bodies container. It provides an AoS-like interface, allowing developers to access and modify properties of an individual body (e.g., body.x for position, body.v for velocity) as if it were a standalone object.

How it Works:

  1. Reference to SoA: Each src.body.BodyProxy instance holds a reference to the main src.body.Bodies container and the _index of the specific body it represents.

  2. Property Accessors: All properties (like m, r, x, v, q, etc.) are implemented as Python properties that directly access the corresponding slice of the NumPy arrays within the src.body.Bodies container using the stored _index.

  3. Mutable Operations: Methods like set_acceleration, accumulate_acceleration, and apply_impulse directly modify the underlying NumPy arrays in the src.body.Bodies container at the specified index.

Benefits:

  • Readability and Ergonomics: Provides a more natural, object-oriented way to interact with individual bodies, improving code readability and developer experience.

  • Performance Preservation: Crucially, it does so without sacrificing the performance benefits of the underlying SoA layout, as the actual data remains contiguous and accessible for vectorized operations.

  • Encapsulation: It encapsulates the details of the SoA implementation, allowing the rest of the codebase to work with individual bodies more abstractly.

This pattern allows the simulation to leverage the best aspects of both data organization strategies: high-performance vectorized operations on the src.body.Bodies container and intuitive, object-oriented access to individual bodies via src.body.BodyProxy.

Module Overview

Inheritance diagram of src.body

Provides a Structure-of-Arrays (SoA) container for simulation bodies and a proxy for Array-of-Structures (AoS)-like access.

The Bodies class uses an SoA layout (e.g., all masses in one array, all positions in another) for cache-efficient computations. The BodyProxy class provides a convenient AoS-like interface (body.x, body.v) for a single body, combining the best of both worlds.

module:

body

author:

Le Bars, Yoann

class src.body.Bodies(n: int)[source]

Bases: object

Structure-of-Arrays (SoA) container for all bodies in the simulation. This layout improves cache performance by storing related data contiguously.

Variables:
  • m (np.ndarray) – Mass of each body.

  • inv_m (np.ndarray) – Inverse mass of each body.

  • r (np.ndarray) – Radius of each body.

  • i_inv (np.ndarray) – Inverse moment of inertia of each body.

  • x (np.ndarray) – Position of each body (n, 3).

  • v (np.ndarray) – Velocity of each body.

  • a (np.ndarray) – Acceleration of each body.

  • q (np.ndarray) – Orientation of each body.

  • omega (np.ndarray) – Angular velocity of each body.

  • alpha (np.ndarray) – Angular acceleration of each body.

  • torque (np.ndarray) – Net torque applied to each body in a frame.

  • _size (int) – Number of bodies.

a: ndarray
alpha: ndarray
apply_impulses(indices: ndarray, impulses: ndarray, contact_vectors: ndarray) None[source]

Applies a batch of impulses to multiple bodies in a fully vectorized manner.

Parameters:
  • indices (np.ndarray) – The indices of the bodies to apply impulses to.

  • impulses (np.ndarray) – An array of impulse vectors (J) for each body.

  • contact_vectors (np.ndarray) – An array of vectors (r) from each body’s centre of mass to the point of impulse application.

emplace_back(m: float, r: float, x0: ndarray, v0: ndarray) None[source]

Adds a new body to the data structure.

Parameters:
  • m (float) – Body mass.

  • r (float) – Body radius.

  • x0 (np.ndarray) – Body position.

  • v0 (np.ndarray) – Body velocity.

i_inv: ndarray
integrate_part1(dt: float) None[source]

Velocity Verlet: Part 1 (Vectorised). Updates positions and half-updates velocities for all bodies.

Parameters:

dt (float) – Time step.

integrate_part2(dt: float) None[source]

Velocity Verlet: Part 2 (Vectorised). Completes the velocity updates for all bodies.

Parameters:

dt (float) – Time step.

inv_m: ndarray
m: ndarray
omega: ndarray
q: ndarray
r: ndarray
set_size(size: int) None[source]

Sets the number of bodies in the container.

This method should be used with caution and is primarily for initialization scenarios where bodies are placed in bulk.

Parameters:

size (int) – The new size value.

torque: ndarray
v: ndarray
x: ndarray
class src.body.BodyProxy(bodies: Bodies, index: int)[source]

Bases: object

A proxy object that provides an AoS-like interface to a body stored in the Bodies SoA container.

The Bodies class uses a Structure-of-Arrays (SoA) layout for performance, where all positions are in one array, all velocities in another, etc. This is ideal for vectorised physics calculations.

However, it can be inconvenient to work with a single body’s properties. This BodyProxy class solves that problem by implementing the Proxy design pattern. It provides a classic Array-of-Structures (AoS) interface (e.g., body.x, body.v) for a single body, while the underlying data remains in the efficient SoA format. This gives us the best of both worlds: high-performance vectorised operations on the Bodies container and intuitive, object-oriented access to individual bodies.

Variables:
  • _bodies (Bodies) – The main SoA container.

  • _index (int) – The index of the body this proxy refers to.

property a: ndarray

Body acceleration.

accumulate_acceleration(accel: ndarray) None[source]

Accumulate an acceleration.

Parameters:

accel (np.ndarray) – Acceleration vector.

accumulate_angular_acceleration(angular_accel: ndarray) None[source]

Accumulate an angular acceleration.

Parameters:

angular_accel (np.ndarray) – Angular acceleration vector.

accumulate_torque(torque_vec: ndarray) None[source]

Accumulate a torque vector.

Parameters:

torque_vec (np.ndarray) – Torque vector to add.

property alpha: ndarray

Body angular acceleration.

apply_impulse(j_total: ndarray, r_vec: ndarray) ndarray[source]

Applies a linear impulse to the body and returns the resulting torque.

Based on the impulse-momentum theorem, this method updates the body’s linear velocity and calculates the torque generated by the impulse, which will later be used to update the angular velocity.

Parameters:
  • j_total (np.ndarray) – Total impulse vector.

  • r_vec (np.ndarray) – Vector from the body’s centre of mass to the point of impulse application.

Returns:

The torque vector generated by the impulse.

Return type:

np.ndarray

dampen_velocity(linear_damping: float, angular_damping: float) None[source]

Applies damping to linear and angular velocities to stabilise the simulation.

Parameters:
  • linear_damping (float) – Damping factor for linear velocity.

  • angular_damping (float) – Damping factor for angular velocity.

property i_inv: float

Inverse of the moment of inertia.

property inv_m: float

Inverse of the body mass.

property m: float

Body mass.

property omega: ndarray

Body angular velocity.

property q: Rotation

Body orientation.

property r: float

Body radius.

property torque: ndarray

Net torque on the body for the current frame.

property v: ndarray

Body velocity.

property x: ndarray

Body position.