Skip to content

Commit 18355d3

Browse files
authored
feat: physics integration (jolt physics)
# Summary of changes - Add a new system PhysicsSystem inheriting from `ecs::QuerySystem`, handling rigid body update - Bound the physics system to Jolt Physics through our existing ECS structure. - Exposed 2 main API methods to create bodies: `createStaticBody(entity, transform)` `createDynamicBody(entity, transform)` - Bodies are automatically tracked via the PhysicsBodyComponent, storing the BodyID and motion type. - Physics system automatically syncs Jolt positions back into the TransformComponent every frame. # New components - `TransformComponent`: used to compute position, size and rotation for body creation. - `PhysicsBodyComponent`: stores the Jolt `BodyID` and its type (`Static` or `Dynamic`). # Usage To assign physics to an entity: ```cpp auto entity = app.createEntity(); components::TransformComponent transform{ /* init pos, rot, size */ }; app.m_coordinator->addComponent(entity, transform); // For static objects app.getPhysicsSystem()->createStaticBody(entity, transform); // For dynamic objects app.getPhysicsSystem()->createDynamicBody(entity, transform); ``` No need to manually update the transform after simulation; it's done by the system at the end of each frame. # Testing Initial test coverage includes: - Validation of rigid body creation. - System-side updates and ECS sync. - Regression tests for TransformComponent integration with the physics update loop. # Example PS: Actually i have a exemple scene that have been push that replace the default editor scene, the scene include: - A ground (static body) - 2 cubes falling from the air (dynamic body) and colliding with each other while falling - **New Features** - Introduced a physics system powered by Jolt Physics, enabling simulation of dynamic and static bodies within scenes. - Added default scene entities (ground and cubes) with physics properties for immediate interaction. - Provided a default window layout configuration for the graphical user interface. - **Bug Fixes** - Improved exception handling with richer, formatted messages and detailed source location information. - **Chores** - Integrated Jolt Physics as a dependency, including build scripts and configuration updates. - **Tests** - Added unit tests for the new physics system and enhanced exception tests with debug output.
2 parents 7993ef8 + 22e2d52 commit 18355d3

17 files changed

Lines changed: 724 additions & 25 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
2020
set(NEXO_COMPILER_FLAGS_RELEASE -O3)
2121
set(NEXO_COVERAGE_FLAGS -O0 --coverage)
2222
elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
23-
set(NEXO_COMPILER_FLAGS_ALL /std:c++${CMAKE_CXX_STANDARD} /Zc:preprocessor)
23+
set(NEXO_COMPILER_FLAGS_ALL /std:c++${CMAKE_CXX_STANDARD} /Zc:preprocessor /utf-8)
2424
set(NEXO_COMPILER_FLAGS_DEBUG /Zi /Od /Zc:preprocessor /MDd /D_DEBUG /D_ITERATOR_DEBUG_LEVEL=2 /D_SECURE_SCL=1)
2525
set(NEXO_COMPILER_FLAGS_RELEASE /O2 /Zc:preprocessor)
2626
set(NEXO_COVERAGE_FLAGS "") # MSVC doesn't support coverage in the same way

common/Exception.cpp

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,41 @@
1414

1515
#include "Exception.hpp"
1616

17-
#include <sstream>
17+
#include <format>
1818

1919
namespace nexo {
20-
const char * Exception::what() const noexcept
20+
const char *Exception::what() const noexcept
2121
{
22-
formattedMessage = formatMessage();
23-
return formattedMessage.c_str();
22+
return m_formattedMessage.c_str();
2423
}
2524

26-
std::string Exception::formatMessage() const
25+
const std::string& Exception::getMessage() const noexcept
2726
{
28-
std::ostringstream oss;
29-
oss << "Exception occurred in " << file << ":" << line << " - " << message;
30-
return oss.str();
27+
return m_unformattedMessage;
3128
}
32-
}
29+
30+
const std::string& Exception::getFormattedMessage() const noexcept
31+
{
32+
return m_formattedMessage;
33+
}
34+
35+
const char* Exception::getFile() const noexcept
36+
{
37+
return m_location.file_name();
38+
}
39+
40+
unsigned int Exception::getLine() const noexcept
41+
{
42+
return m_location.line();
43+
}
44+
45+
const char* Exception::getFunction() const noexcept
46+
{
47+
return m_location.function_name();
48+
}
49+
50+
const std::source_location& Exception::getSourceLocation() const noexcept
51+
{
52+
return m_location;
53+
}
54+
}

common/Exception.hpp

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,38 @@
1313
///////////////////////////////////////////////////////////////////////////////
1414
#pragma once
1515

16+
#include <format>
1617
#include <string>
1718
#include <source_location>
19+
#include <exception>
1820

1921
namespace nexo {
2022
class Exception : public std::exception {
2123
public:
22-
explicit Exception(std::string message, const std::source_location loc)
23-
: message(std::move(message)), file(loc.file_name()), line(loc.line()) {}
24+
explicit Exception(const std::string& message, const std::source_location& loc)
25+
:
26+
m_unformattedMessage(message),
27+
m_formattedMessage(std::format("Exception occurred in {} : {} - {}", loc.file_name(), loc.line(), message)),
28+
m_location(loc)
29+
{
30+
}
2431

25-
const char *what() const noexcept override;
32+
~Exception() override = default;
2633

27-
const std::string &getMessage() const noexcept { return message; }
28-
const char *getFile() const noexcept { return file; }
29-
unsigned int getLine() const noexcept { return line; }
34+
[[nodiscard]] const char *what() const noexcept override;
3035

31-
protected:
32-
std::string formatMessage() const;
36+
[[nodiscard]] const std::string& getMessage() const noexcept;
37+
[[nodiscard]] const std::string& getFormattedMessage() const noexcept;
38+
39+
[[nodiscard]] const char *getFile() const noexcept;
40+
[[nodiscard]] unsigned int getLine() const noexcept;
41+
[[nodiscard]] const char *getFunction() const noexcept;
42+
[[nodiscard]] const std::source_location& getSourceLocation() const noexcept;
3343

3444
private:
35-
std::string message;
36-
const char *file;
37-
unsigned int line;
38-
mutable std::string formattedMessage;
45+
std::string m_unformattedMessage;
46+
std::string m_formattedMessage;
47+
std::source_location m_location;
3948
};
4049
}
4150

editor/src/DocumentWindows/EditorScene/Init.cpp

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ namespace nexo::editor {
4646
framebufferSpecs.width = static_cast<unsigned int>(m_contentSize.x);
4747
framebufferSpecs.height = static_cast<unsigned int>(m_contentSize.y);
4848
const auto renderTarget = renderer::NxFramebuffer::create(framebufferSpecs);
49-
m_editorCamera = static_cast<int>(CameraFactory::createPerspectiveCamera({0.0f, 3.0f, -2.0f}, static_cast<unsigned int>(m_contentSize.x), static_cast<unsigned int>(m_contentSize.y), renderTarget));
50-
auto &cameraComponent = Application::m_coordinator->getComponent<components::CameraComponent>(m_editorCamera);
49+
m_editorCamera = CameraFactory::createPerspectiveCamera({0.0f, 4.0f, 10.0f}, static_cast<unsigned int>(m_contentSize.x), static_cast<unsigned int>(m_contentSize.y), renderTarget);
50+
auto &cameraComponent = app.m_coordinator->getComponent<components::CameraComponent>(m_editorCamera);
5151
cameraComponent.render = true;
5252
auto maskPass = std::make_shared<renderer::MaskPass>(m_contentSize.x, m_contentSize.y);
5353
auto outlinePass = std::make_shared<renderer::OutlinePass>(m_contentSize.x, m_contentSize.y);
@@ -177,6 +177,33 @@ namespace nexo::editor {
177177
// {0.0f, 0.0f, 0.0f});
178178

179179
app.getSceneManager().getScene(m_sceneId).addEntity(ModelAvocado);
180+
181+
const ecs::Entity ground = EntityFactory3D::createCube(
182+
{0.0f, 0.25f, 0.0f}, // position
183+
{20.0f, 0.5f, 20.0f}, // size
184+
{0.0f, 0.0f, 0.0f},
185+
{0.2f, 0.2f, 0.2f, 1.0f} // color
186+
);
187+
app.getPhysicsSystem()->createStaticBody(ground, app.m_coordinator->getComponent<components::TransformComponent>(ground));
188+
scene.addEntity(ground);
189+
190+
const ecs::Entity fallingCube = EntityFactory3D::createCube(
191+
{0.0f, 5.0f, 0.0f},
192+
{1.0f, 1.0f, 1.0f},
193+
{0.0f, 0.0f, 0.0f},
194+
{1.0f, 0.2f, 0.2f, 1.0f}
195+
);
196+
app.getPhysicsSystem()->createDynamicBody(fallingCube, app.m_coordinator->getComponent<components::TransformComponent>(fallingCube));
197+
scene.addEntity(fallingCube);
198+
199+
const ecs::Entity fallingCube2 = EntityFactory3D::createCube(
200+
{0.5f, 7.0f, 0.0f},
201+
{1.0f, 1.0f, 1.0f},
202+
{0.0f, 0.0f, 0.0f},
203+
{1.0f, 0.2f, 0.2f, 1.0f}
204+
);
205+
app.getPhysicsSystem()->createDynamicBody(fallingCube2, app.m_coordinator->getComponent<components::TransformComponent>(fallingCube2));
206+
scene.addEntity(fallingCube2);
180207
}
181208

182209
void EditorScene::setupWindow()

engine/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ set(COMMON_SOURCES
5757
engine/src/systems/RenderBillboardSystem.cpp
5858
engine/src/systems/LightSystem.cpp
5959
engine/src/systems/ScriptingSystem.cpp
60+
engine/src/systems/PhysicsSystem.cpp
6061
engine/src/systems/lights/AmbientLightSystem.cpp
6162
engine/src/systems/lights/PointLightsSystem.cpp
6263
engine/src/systems/lights/DirectionalLightsSystem.cpp
@@ -131,6 +132,10 @@ target_link_libraries(nexoRenderer PRIVATE assimp::assimp)
131132
find_package(Boost CONFIG REQUIRED COMPONENTS dll)
132133
target_link_libraries(nexoRenderer PRIVATE Boost::dll)
133134

135+
# joltphysics
136+
find_package(Jolt CONFIG REQUIRED)
137+
target_link_libraries(nexoRenderer PRIVATE Jolt::Jolt)
138+
134139
###########################################
135140
# Scripting
136141
###########################################

engine/src/Application.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ namespace nexo {
108108
m_coordinator->registerComponent<components::MaterialComponent>();
109109
m_coordinator->registerComponent<components::NameComponent>();
110110
m_coordinator->registerSingletonComponent<components::RenderContext>();
111+
112+
m_coordinator->registerComponent<components::PhysicsBodyComponent>();
111113
}
112114

113115
void Application::registerWindowCallbacks() const
@@ -205,6 +207,8 @@ namespace nexo {
205207
m_renderBillboardSystem = m_coordinator->registerGroupSystem<system::RenderBillboardSystem>();
206208
m_transformHierarchySystem = m_coordinator->registerGroupSystem<system::TransformHierarchySystem>();
207209
m_transformMatrixSystem = m_coordinator->registerQuerySystem<system::TransformMatrixSystem>();
210+
m_physicsSystem = m_coordinator->registerQuerySystem<system::PhysicsSystem>();
211+
m_physicsSystem->init();
208212

209213
auto pointLightSystem = m_coordinator->registerGroupSystem<system::PointLightsSystem>();
210214
auto directionalLightSystem = m_coordinator->registerGroupSystem<system::DirectionalLightsSystem>();
@@ -297,6 +301,9 @@ namespace nexo {
297301

298302
m_scriptingSystem->update();
299303

304+
constexpr float fixedTimestep = 1.0f / 60.0f;
305+
static float physicsAccumulator = 0.0f;
306+
300307
if (!m_isMinimized)
301308
{
302309
renderContext.sceneRendered = static_cast<int>(sceneInfo.id);
@@ -312,6 +319,12 @@ namespace nexo {
312319
m_transformHierarchySystem->update();
313320
m_cameraContextSystem->update();
314321
m_lightSystem->update();
322+
physicsAccumulator += m_worldState.time.deltaTime;
323+
324+
while (physicsAccumulator >= fixedTimestep) {
325+
m_physicsSystem->update(fixedTimestep);
326+
physicsAccumulator -= fixedTimestep;
327+
}
315328
m_renderCommandSystem->update();
316329
m_renderBillboardSystem->update();
317330
for (auto &camera : renderContext.cameras)

engine/src/Application.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@
2828
#include "Timer.hpp"
2929
#include "WorldState.hpp"
3030
#include "components/Light.hpp"
31+
#include "components/PhysicsBodyComponent.hpp"
3132

3233
#include "systems/CameraSystem.hpp"
3334
#include "systems/LightSystem.hpp"
3435
#include "systems/RenderCommandSystem.hpp"
3536
#include "systems/RenderBillboardSystem.hpp"
3637
#include "systems/TransformHierarchySystem.hpp"
3738
#include "systems/TransformMatrixSystem.hpp"
39+
#include "systems/PhysicsSystem.hpp"
3840

3941
#define NEXO_PROFILE(name) nexo::Timer timer##__LINE__(name, [&](ProfileResult profileResult) {m_profileResults.push_back(profileResult); })
4042

@@ -188,6 +190,10 @@ namespace nexo {
188190
*/
189191
ecs::Entity createEntity() const;
190192

193+
std::shared_ptr<system::PhysicsSystem> getPhysicsSystem() const {
194+
return m_physicsSystem;
195+
}
196+
191197
/**
192198
* @brief Deletes an existing entity.
193199
*
@@ -270,7 +276,9 @@ namespace nexo {
270276
std::shared_ptr<system::ScriptingSystem> m_scriptingSystem;
271277
std::shared_ptr<system::RenderCommandSystem> m_renderCommandSystem;
272278
std::shared_ptr<system::RenderBillboardSystem> m_renderBillboardSystem;
279+
std::shared_ptr<system::PhysicsSystem> m_physicsSystem;
273280

274281
std::vector<ProfileResult> m_profilesResults;
282+
275283
};
276284
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// PhysicsBodyComponent.hpp /////////////////////////////////////////////////
2+
//
3+
// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
4+
// zzzzzzz zzz zzzz zzzz zzzz zzzz
5+
// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
6+
// zzz zzz zzz z zzzz zzzz zzzz zzzz
7+
// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
8+
//
9+
// Author: Thomas PARENTEAU
10+
// Date: 21/03/2024
11+
// Description: Header file for the Physics Body components
12+
//
13+
///////////////////////////////////////////////////////////////////////////////
14+
15+
#pragma once
16+
17+
#include <Jolt/Jolt.h>
18+
#include <Jolt/Physics/Body/BodyID.h>
19+
20+
namespace nexo::components {
21+
struct PhysicsBodyComponent {
22+
enum class Type { Static, Dynamic };
23+
JPH::BodyID bodyID;
24+
Type type;
25+
};
26+
}

engine/src/ecs/Coordinator.hpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,36 @@ namespace nexo::ecs {
564564
void addComponentAny(Entity entity, const std::type_index& typeIndex, const std::any& component);
565565

566566
Entity duplicateEntity(Entity sourceEntity) const;
567+
568+
/**
569+
* @brief Retrieves all entities that have all the specified components.
570+
*
571+
* This method iterates over all entities and returns a list of those that possess
572+
* each of the given component types. It uses the current signature of each entity
573+
* to determine component ownership.
574+
*
575+
* @tparam ComponentTypes - A variadic list of component types to filter by.
576+
* @return std::vector<Entity> - A list of entities matching all specified component types.
577+
*/
578+
template<typename... ComponentTypes>
579+
std::vector<Entity> getEntitiesWithComponents() const
580+
{
581+
std::vector<Entity> result;
582+
std::span<const Entity> livingEntities = m_entityManager->getLivingEntities();
583+
result.reserve(livingEntities.size());
584+
for (Entity entity : livingEntities)
585+
{
586+
bool hasAll = (entityHasComponent<ComponentTypes>(entity) && ...);
587+
if (hasAll)
588+
{
589+
result.push_back(entity);
590+
}
591+
}
592+
return result;
593+
}
594+
595+
void updateSystemEntities() const;
596+
567597
private:
568598
template<typename Component>
569599
void processComponentSignature(Signature& required, Signature& excluded) const {

0 commit comments

Comments
 (0)