A high-performance, header-only 2D and 3D physics engine designed for modern C++17. While optimized for Entity Component System (ECS) architectures via SFINAE trait validation, its generic design allows for seamless integration into OOP-based game engines.
- Strictly Decoupled: 2D and 3D pipelines are physically separated to optimize compile times and memory layout.
- ECS-Agnostic: Compatible with EnTT, Flecs, or custom registries via compile-time interface validation.
- Modern Math: Quaternion-based 3D rotations to avoid Gimbal Lock and 3D Impulse resolution with Baumgarte Stabilization.
- Data-Oriented: Components are POD (Plain Old Data) structs, ensuring cache-friendly iteration.
The engine uses SFINAE to verify that your Registry class provides a .view<T>() and .get<T>() member functions.
#include <entt/entt.hpp>
#include <physics/systems/PhysicsSystem2D.hpp>
#include <physics/components/RigidBody2D.hpp>
entt::registry registry;
physics::systems::PhysicsSystem2D<entt::registry, entt::entity> physics_system;
// Create an entity
auto entity = registry.create();
registry.emplace<physics::components::Transform2D>(entity, physics::math::Vector2{0.0F, 10.0F});
registry.emplace<physics::components::RigidBody2D>(entity).set_mass(1.0F);// Within your game loop
float dt = 0.016F;
physics_system.update(registry, dt);If you are using a standard GameObject architecture, you can still use the engine by creating a "Thin Wrapper" that mimics the Registry interface. This allows the physics engine to remain highly performant while managing your objects.
struct MyGameObject {
RigidBody3D rigidbody;
Transform3D transform;
AABBCollider3D collider;
};
struct ObjectRegistry {
std::vector<MyGameObject*> objects;
// Mimic the 'view' interface required by the Physics Engine
template<typename... Args>
std::span<MyGameObject*> view() { return objects; }
// Mimic the 'get' interface
template<typename T>
T& get(MyGameObject* obj) {
if constexpr (std::is_same_v<T, RigidBody3D>) return obj->rigidbody;
if constexpr (std::is_same_v<T, Transform3D>) return obj->transform;
if constexpr (std::is_same_v<T, AABBCollider3D>) return obj->collider;
}
};ObjectRegistry registry;
physics::systems::PhysicsSystem3D<ObjectRegistry, MyGameObject*> physics_system;
physics_system.update(registry, dt);- CMake 3.21+
- Ninja or MSVC
- C++17 Compatible Compiler
# Configure the project (Debug)
cmake --preset debug
# Build
cmake --build --preset debug
# Run Tests
ctest --preset debugRun doxygen Doxyfile to generate the full API reference in the docs/ folder.
In a "serious" project, performance is king. When you use the ECS integration (like with EnTT), all RigidBody and Transform components are stored in contiguous memory. When the PhysicsSystem iterates over them, the CPU can pre-fetch data efficiently, resulting in massive speedups when handling thousands of colliding objects.
Many legacy engines or smaller tools use a std::vector<std::unique_ptr<GameObject>>. By providing the "Wrapper" pattern in the README, you allow those users to adopt your high-quality physics math without forcing them to rewrite their entire engine architecture to an ECS.
- Templates: All systems are templated on
RegistryandEntityto maintain agnosticism.