Skip to main content

Physics Engine

C++ SDL CMake Engine Development
Table of Contents

Goal
#

The goal of this project was to develop a 2D rigid body physics subsystem that accurately simulates linear and angular motion, including reactions to forces and collisions.  I created my subsystem as a modification of XCube2D, a simple C++ game engine based on SDL2, and then built the engine as a library that could be included in a game. To manage the building of the engine library and the game executable as well as the SDL2 dependencies, I used the CMake build system, which I set up to make the build process as easy as possible across different platforms.

Outcome
#

Working on this project has greatly expanded my knowledge of the physics calculations used in games and have explored different methods such as the separating axis theorem, Euler’s method, and the restitution equation. I now understand that Euler’s method, which I have used in the past, is an approximation that becomes more accurate with faster updates. I have furthered my knowledge of C++ and CMake. I can now use the CMake build system to include external libraries and introduce dependencies between parts of my code (such as between the game and the engine). I have learnt how to use macros to include code in some builds and not others. I have improved my ability to write clear, concise, and well-structured C++ code, following best practices such as using descriptive variable names, not repeating code, and using smart pointers instead of legacy ones. I have also learnt to consider ownership of data to ensure memory is only used when it is needed. I made a particular effort in this project to have a well-documented API for my subsystem, and I will continue to do this in future projects. Overall, this project’s development has helped me to expand my knowledge in areas of physics such as linear and angular dynamics and collision resolution, as well as develop my ability to use C++ effectively.

Architecture
#

I designed the architecture of my subsystem to feature minimal coupling with a data-oriented approach. The subsystem has two main components: the physics engine class and the rigid body data structure, which form a one-way dependency tree. The physics engine is completely decoupled from any other subsystem and does not even provide any visual elements except for debugging purposes. This allows it to easily be used in combination with other subsystems to add physics to any object with an entity-component-system style approach. A rigid body component could be added to an entity that, for example, could have a graphical component as well. The physics engine class controls the rigid bodies and their interactions with each other, acting as the system.

Kinematics & Dynamics
#

In the physics engine update method, the position and velocity are calculated for each rigid body every frame. Velocity is derived from acceleration, and position from velocity, assuming constant acceleration. Forces act on the rigid body’s centre of mass, where total force divided by mass provides acceleration. Since force may vary during the frame, exact integration is unfeasible, so Euler’s method is used to approximate position and velocity, providing greater accuracy at smaller time steps. Angular acceleration is calculated by dividing the torque by the body’s moment of inertia, a constant defining resistance to changes in rotational velocity.

Moment of Inertia
#

The moment of inertia for each rigid body’s bounding box is calculated by dividing the polygon into triangles, splitting each into right-angled triangles, and summing their moments. Since the moment of inertia can be calculated around the point shared by all triangles, the moments of inertia can all be combined, with the “handedness” of each triangle being used to determine if it contributes positively or negatively. The separating axis theorem is then applied to shift the moment to the centre of mass.

Collision Detection
#

Every frame, pairs of rigid bodies are checked for collisions. First, with a broad phase where the axis-aligned bounding boxes are checked for an intersection. Any rigid bodies that collide in this phase proceed to a more precise check using the separating axis theorem (SAT), where the normals of each face serve as axes to check for collisions along.

Collision Resolution
#

When two objects collide, they must be separated, and an impulse must be applied to push them apart. The velocity component responsible for the collision is found by projecting the colliding bodies’ relative velocity onto the collision normal. The inverse of this velocity, scaled by the coefficient of restitution (which represents the elasticity of the collision), gives the post-collision velocity. Finally, by factoring in each object’s mass, the impulse magnitude is calculated and applied to each rigid body.

To simulate rotation resulting from a collision, the collision response equations also account for angular velocity. The velocity component responsible for the collision is found using the velocities of the colliding points on each body, and is used with the coefficient of restitution to calculate the post-collision velocity of the points. By factoring in both the masses and moments of inertia, the impulse magnitude is found for each rigid body at the collision point.