diff --git a/ChangeLog.md b/ChangeLog.md index d12b740ab..847ddc992 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -188,6 +188,8 @@ Nazara Development Kit: - Force parent parameter to be present in widgets constructor - Added the possibility to write only specific characters with a predicate in TextAreaWidget - It is now possible to disable object culling in the RenderSystem +- Make Nz::PhysWorld2D& Ndk::PhysicsSystem2D::GetWorld private and rename it into GetPhysWorld +- Make Ndk::PhysicsSystem2D an interface of Nz::PhysWorld2D # 0.4: diff --git a/SDK/include/NDK/Systems/PhysicsSystem2D.hpp b/SDK/include/NDK/Systems/PhysicsSystem2D.hpp index 66c8b1c61..95b6767c4 100644 --- a/SDK/include/NDK/Systems/PhysicsSystem2D.hpp +++ b/SDK/include/NDK/Systems/PhysicsSystem2D.hpp @@ -16,24 +16,112 @@ namespace Ndk { class NDK_API PhysicsSystem2D : public System { + friend class CollisionComponent2D; + friend class PhysicsComponent2D; + + using ContactEndCallback = std::function; + using ContactPreSolveCallback = std::function; + using ContactPostSolveCallback = std::function; + using ContactStartCallback = std::function; + + using DebugDrawCircleCallback = std::function; + using DebugDrawDotCallback = std::function; + using DebugDrawPolygonCallback = std::function; + using DebugDrawSegmentCallback = std::function; + using DebugDrawTickSegmentCallback = std::function; + using DebugDrawGetColorCallback = std::function; + public: + struct Callback; + struct DebugDrawOptions; + struct NearestQueryResult; + struct RaycastHit; + PhysicsSystem2D(); PhysicsSystem2D(const PhysicsSystem2D& system); ~PhysicsSystem2D() = default; - Nz::PhysWorld2D& GetWorld(); - const Nz::PhysWorld2D& GetWorld() const; + void DebugDraw(const DebugDrawOptions& options, bool drawShapes = true, bool drawConstraints = true, bool drawCollisions = true); + + inline float GetDamping() const; + inline Nz::Vector2f GetGravity() const; + inline std::size_t GetIterationCount() const; + inline std::size_t GetMaxStepCount() const; + inline float GetStepSize() const; + + bool NearestBodyQuery(const Nz::Vector2f& from, float maxDistance, Nz::UInt32 collisionGroup, Nz::UInt32 categoryMask, Nz::UInt32 collisionMask, EntityHandle* nearestBody = nullptr); + bool NearestBodyQuery(const Nz::Vector2f& from, float maxDistance, Nz::UInt32 collisionGroup, Nz::UInt32 categoryMask, Nz::UInt32 collisionMask, NearestQueryResult* result); + + bool RaycastQuery(const Nz::Vector2f& from, const Nz::Vector2f& to, float radius, Nz::UInt32 collisionGroup, Nz::UInt32 categoryMask, Nz::UInt32 collisionMask, std::vector* hitInfos); + bool RaycastQueryFirst(const Nz::Vector2f& from, const Nz::Vector2f& to, float radius, Nz::UInt32 collisionGroup, Nz::UInt32 categoryMask, Nz::UInt32 collisionMask, RaycastHit* hitInfo = nullptr); + + void RegionQuery(const Nz::Rectf& boundingBox, Nz::UInt32 collisionGroup, Nz::UInt32 categoryMask, Nz::UInt32 collisionMask, std::vector* bodies); + + void RegisterCallbacks(unsigned int collisionId, const Callback& callbacks); + void RegisterCallbacks(unsigned int collisionIdA, unsigned int collisionIdB, const Callback& callbacks); + + inline void SetDamping(float dampingValue); + inline void SetGravity(const Nz::Vector2f& gravity); + inline void SetIterationCount(std::size_t iterationCount); + inline void SetMaxStepCount(std::size_t maxStepCount); + inline void SetStepSize(float stepSize); + + inline void UseSpatialHash(float cellSize, std::size_t entityCount); + + struct Callback + { + ContactEndCallback endCallback = nullptr; + ContactPreSolveCallback preSolveCallback = nullptr; + ContactPostSolveCallback postSolveCallback = nullptr; + ContactStartCallback startCallback = nullptr; + void* userdata; + }; + + struct DebugDrawOptions + { + Nz::Color constraintColor; + Nz::Color collisionPointColor; + Nz::Color shapeOutlineColor; + + DebugDrawCircleCallback circleCallback; + DebugDrawGetColorCallback colorCallback; + DebugDrawDotCallback dotCallback; + DebugDrawPolygonCallback polygonCallback; + DebugDrawSegmentCallback segmentCallback; + DebugDrawTickSegmentCallback thickSegmentCallback; + + void* userdata; + }; + + struct NearestQueryResult + { + EntityHandle nearestBody; + Nz::Vector2f closestPoint; + Nz::Vector2f fraction; + float distance; + }; + + struct RaycastHit + { + EntityHandle body; + Nz::Vector2f hitPos; + Nz::Vector2f hitNormal; + float fraction; + }; static SystemIndex systemIndex; private: void CreatePhysWorld() const; + const EntityHandle& GetEntityFromBody(const Nz::RigidBody2D& body) const; + Nz::PhysWorld2D& GetPhysWorld(); + const Nz::PhysWorld2D& GetPhysWorld() const; void OnEntityValidation(Entity* entity, bool justAdded) override; void OnUpdate(float elapsedTime) override; EntityList m_dynamicObjects; EntityList m_staticObjects; - mutable std::unique_ptr m_world; ///TODO: std::optional (Should I make a Nz::Optional class?) + mutable std::unique_ptr m_physWorld; ///TODO: std::optional (Should I make a Nz::Optional class?) }; } diff --git a/SDK/include/NDK/Systems/PhysicsSystem2D.inl b/SDK/include/NDK/Systems/PhysicsSystem2D.inl index 5f0755cff..cc1010c1f 100644 --- a/SDK/include/NDK/Systems/PhysicsSystem2D.inl +++ b/SDK/include/NDK/Systems/PhysicsSystem2D.inl @@ -4,17 +4,94 @@ namespace Ndk { + inline float PhysicsSystem2D::GetDamping() const + { + NazaraAssert(m_physWorld, "Invalid physics world"); + + return m_physWorld->GetDamping(); + } + + inline Nz::Vector2f PhysicsSystem2D::GetGravity() const + { + NazaraAssert(m_physWorld, "Invalid physics world"); + + return m_physWorld->GetGravity(); + } + + inline std::size_t PhysicsSystem2D::GetIterationCount() const + { + NazaraAssert(m_physWorld, "Invalid physics world"); + + return m_physWorld->GetIterationCount(); + } + + inline std::size_t PhysicsSystem2D::GetMaxStepCount() const + { + NazaraAssert(m_physWorld, "Invalid physics world"); + + return m_physWorld->GetMaxStepCount(); + } + + inline float PhysicsSystem2D::GetStepSize() const + { + NazaraAssert(m_physWorld, "Invalid physics world"); + + return m_physWorld->GetStepSize(); + } + + inline void PhysicsSystem2D::SetDamping(float dampingValue) + { + NazaraAssert(m_physWorld, "Invalid physics world"); + + m_physWorld->SetDamping(dampingValue); + } + + inline void PhysicsSystem2D::SetGravity(const Nz::Vector2f& gravity) + { + NazaraAssert(m_physWorld, "Invalid physics world"); + + m_physWorld->SetGravity(gravity); + } + + inline void PhysicsSystem2D::SetIterationCount(std::size_t iterationCount) + { + NazaraAssert(m_physWorld, "Invalid physics world"); + + m_physWorld->SetIterationCount(iterationCount); + } + + inline void PhysicsSystem2D::SetMaxStepCount(std::size_t maxStepCount) + { + NazaraAssert(m_physWorld, "Invalid physics world"); + + m_physWorld->SetMaxStepCount(maxStepCount); + } + + inline void PhysicsSystem2D::SetStepSize(float stepSize) + { + NazaraAssert(m_physWorld, "Invalid physics world"); + + m_physWorld->SetStepSize(stepSize); + } + + inline void PhysicsSystem2D::UseSpatialHash(float cellSize, std::size_t entityCount) + { + NazaraAssert(m_physWorld, "Invalid physics world"); + + m_physWorld->UseSpatialHash(cellSize, entityCount); + } + /*! * \brief Gets the physical world * \return A reference to the physical world */ - inline Nz::PhysWorld2D& PhysicsSystem2D::GetWorld() + inline Nz::PhysWorld2D& PhysicsSystem2D::GetPhysWorld() { - if (!m_world) + if (!m_physWorld) CreatePhysWorld(); - return *m_world; + return *m_physWorld; } /*! @@ -22,11 +99,11 @@ namespace Ndk * \return A constant reference to the physical world */ - inline const Nz::PhysWorld2D& PhysicsSystem2D::GetWorld() const + inline const Nz::PhysWorld2D& PhysicsSystem2D::GetPhysWorld() const { - if (!m_world) + if (!m_physWorld) CreatePhysWorld(); - return *m_world; + return *m_physWorld; } } diff --git a/SDK/src/NDK/Components/CollisionComponent2D.cpp b/SDK/src/NDK/Components/CollisionComponent2D.cpp index 9e88f396b..fd78e6c97 100644 --- a/SDK/src/NDK/Components/CollisionComponent2D.cpp +++ b/SDK/src/NDK/Components/CollisionComponent2D.cpp @@ -56,7 +56,7 @@ namespace Ndk NazaraAssert(entityWorld, "Entity must have world"); NazaraAssert(entityWorld->HasSystem(), "World must have a physics system"); - Nz::PhysWorld2D& physWorld = entityWorld->GetSystem().GetWorld(); + Nz::PhysWorld2D& physWorld = entityWorld->GetSystem().GetPhysWorld(); m_staticBody = std::make_unique(&physWorld, 0.f, m_geom); m_staticBody->SetUserdata(reinterpret_cast(static_cast(m_entity->GetId()))); diff --git a/SDK/src/NDK/Components/PhysicsComponent2D.cpp b/SDK/src/NDK/Components/PhysicsComponent2D.cpp index e2e199a22..62cd922a4 100644 --- a/SDK/src/NDK/Components/PhysicsComponent2D.cpp +++ b/SDK/src/NDK/Components/PhysicsComponent2D.cpp @@ -29,7 +29,7 @@ namespace Ndk World* entityWorld = m_entity->GetWorld(); NazaraAssert(entityWorld->HasSystem(), "World must have a 2D physics system"); - Nz::PhysWorld2D& world = entityWorld->GetSystem().GetWorld(); + Nz::PhysWorld2D& world = entityWorld->GetSystem().GetPhysWorld(); Nz::Collider2DRef geom; if (m_entity->HasComponent()) diff --git a/SDK/src/NDK/Systems/PhysicsSystem2D.cpp b/SDK/src/NDK/Systems/PhysicsSystem2D.cpp index 95c3f426b..ec615b041 100644 --- a/SDK/src/NDK/Systems/PhysicsSystem2D.cpp +++ b/SDK/src/NDK/Systems/PhysicsSystem2D.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -33,9 +34,108 @@ namespace Ndk void PhysicsSystem2D::CreatePhysWorld() const { - NazaraAssert(!m_world, "Physics world should not be created twice"); + NazaraAssert(!m_physWorld, "Physics world should not be created twice"); - m_world = std::make_unique(); + m_physWorld = std::make_unique(); + } + + void PhysicsSystem2D::DebugDraw(const DebugDrawOptions& options, bool drawShapes, bool drawConstraints, bool drawCollisions) + { + Nz::PhysWorld2D::DebugDrawOptions worldOptions{ options.constraintColor, options.collisionPointColor, options.shapeOutlineColor }; + + if (options.colorCallback) + { + worldOptions.colorCallback = [&options, this](Nz::RigidBody2D& body, std::size_t shapeIndex, void* userdata) + { + return options.colorCallback(GetEntityFromBody(body), shapeIndex, userdata); + }; + } + + worldOptions.circleCallback = options.circleCallback; + worldOptions.dotCallback = options.dotCallback; + worldOptions.polygonCallback = options.polygonCallback; + worldOptions.segmentCallback = options.segmentCallback; + worldOptions.thickSegmentCallback = options.thickSegmentCallback; + + worldOptions.userdata = options.userdata; + + m_physWorld->DebugDraw(worldOptions, drawShapes, drawConstraints, drawCollisions); + } + + const EntityHandle& PhysicsSystem2D::GetEntityFromBody(const Nz::RigidBody2D& body) const + { + auto entityId = static_cast(reinterpret_cast(body.GetUserdata())); + + auto& world = GetWorld(); + + NazaraAssert(world.IsEntityIdValid(entityId), "All Bodies of this world must be part of the physics world by using PhysicsComponent"); + + return world.GetEntity(entityId); + } + + bool PhysicsSystem2D::NearestBodyQuery(const Nz::Vector2f& from, float maxDistance, Nz::UInt32 collisionGroup, Nz::UInt32 categoryMask, Nz::UInt32 collisionMask, EntityHandle* nearestBody) + { + Nz::RigidBody2D* body; + bool res = m_physWorld->NearestBodyQuery(from, maxDistance, collisionGroup, categoryMask, collisionMask, &body); + + (*nearestBody) = GetEntityFromBody(*body); + + return res; + } + + bool PhysicsSystem2D::NearestBodyQuery(const Nz::Vector2f& from, float maxDistance, Nz::UInt32 collisionGroup, Nz::UInt32 categoryMask, Nz::UInt32 collisionMask, NearestQueryResult* result) + { + Nz::PhysWorld2D::NearestQueryResult queryResult; + bool res = m_physWorld->NearestBodyQuery(from, maxDistance, collisionGroup, categoryMask, collisionMask, &queryResult); + + result->nearestBody = GetEntityFromBody(*queryResult.nearestBody); + result->closestPoint = std::move(queryResult.closestPoint); + result->fraction = std::move(queryResult.fraction); + result->distance = queryResult.distance; + + return res; + } + + bool PhysicsSystem2D::RaycastQuery(const Nz::Vector2f& from, const Nz::Vector2f& to, float radius, Nz::UInt32 collisionGroup, Nz::UInt32 categoryMask, Nz::UInt32 collisionMask, std::vector* hitInfos) + { + std::vector queryResult; + bool res = m_physWorld->RaycastQuery(from, to, radius, collisionGroup, categoryMask, collisionMask, &queryResult); + + for (auto& hitResult : queryResult) + { + hitInfos->push_back({ + GetEntityFromBody(*hitResult.nearestBody), + std::move(hitResult.hitPos), + std::move(hitResult.hitNormal), + hitResult.fraction + }); + } + + return res; + } + + bool PhysicsSystem2D::RaycastQueryFirst(const Nz::Vector2f& from, const Nz::Vector2f& to, float radius, Nz::UInt32 collisionGroup, Nz::UInt32 categoryMask, Nz::UInt32 collisionMask, RaycastHit* hitInfo) + { + Nz::PhysWorld2D::RaycastHit queryResult; + bool res = m_physWorld->RaycastQueryFirst(from, to, radius, collisionGroup, categoryMask, collisionMask, &queryResult); + + hitInfo->body = GetEntityFromBody(*queryResult.nearestBody); + hitInfo->hitPos = std::move(queryResult.hitPos); + hitInfo->hitNormal = std::move(queryResult.hitNormal); + hitInfo->fraction = queryResult.fraction; + + return res; + } + + void PhysicsSystem2D::RegionQuery(const Nz::Rectf& boundingBox, Nz::UInt32 collisionGroup, Nz::UInt32 categoryMask, Nz::UInt32 collisionMask, std::vector* bodies) + { + std::vector queryResult; + m_physWorld->RegionQuery(boundingBox, collisionGroup, categoryMask, collisionMask, &queryResult); + + for (auto& body : queryResult) + { + bodies->emplace_back(GetEntityFromBody(*body)); + } } /*! @@ -58,7 +158,7 @@ namespace Ndk auto& entities = (entity->HasComponent()) ? m_dynamicObjects : m_staticObjects; entities.Insert(entity); - if (!m_world) + if (!m_physWorld) CreatePhysWorld(); } @@ -70,10 +170,10 @@ namespace Ndk void PhysicsSystem2D::OnUpdate(float elapsedTime) { - if (!m_world) + if (!m_physWorld) return; - m_world->Step(elapsedTime); + m_physWorld->Step(elapsedTime); for (const Ndk::EntityHandle& entity : m_dynamicObjects) { @@ -124,5 +224,81 @@ namespace Ndk } } + void PhysicsSystem2D::RegisterCallbacks(unsigned int collisionId, const Callback& callbacks) + { + Nz::PhysWorld2D::Callback worldCallbacks; + + if (callbacks.endCallback) + { + worldCallbacks.endCallback = [&callbacks, this](Nz::PhysWorld2D& world, Nz::Arbiter2D& arbiter, Nz::RigidBody2D& bodyA, Nz::RigidBody2D& bodyB, void* userdata) + { + callbacks.endCallback(*this, arbiter, GetEntityFromBody(bodyA), GetEntityFromBody(bodyB), userdata); + }; + } + if (callbacks.preSolveCallback) + { + worldCallbacks.preSolveCallback = [&callbacks, this](Nz::PhysWorld2D& world, Nz::Arbiter2D& arbiter, Nz::RigidBody2D& bodyA, Nz::RigidBody2D& bodyB, void* userdata) + { + return callbacks.preSolveCallback(*this, arbiter, GetEntityFromBody(bodyA), GetEntityFromBody(bodyB), userdata); + }; + } + if (callbacks.postSolveCallback) + { + worldCallbacks.postSolveCallback = [&callbacks, this](Nz::PhysWorld2D& world, Nz::Arbiter2D& arbiter, Nz::RigidBody2D& bodyA, Nz::RigidBody2D& bodyB, void* userdata) + { + callbacks.preSolveCallback(*this, arbiter, GetEntityFromBody(bodyA), GetEntityFromBody(bodyB), userdata); + }; + } + if (callbacks.startCallback) + { + worldCallbacks.startCallback = [&callbacks, this](Nz::PhysWorld2D& world, Nz::Arbiter2D& arbiter, Nz::RigidBody2D& bodyA, Nz::RigidBody2D& bodyB, void* userdata) + { + return callbacks.startCallback(*this, arbiter, GetEntityFromBody(bodyA), GetEntityFromBody(bodyB), userdata); + }; + } + + worldCallbacks.userdata = callbacks.userdata; + + m_physWorld->RegisterCallbacks(collisionId, worldCallbacks); + } + + void PhysicsSystem2D::RegisterCallbacks(unsigned int collisionIdA, unsigned int collisionIdB, const Callback& callbacks) + { + Nz::PhysWorld2D::Callback worldCallbacks{}; + + if (callbacks.endCallback) + { + worldCallbacks.endCallback = [&callbacks, this](Nz::PhysWorld2D& world, Nz::Arbiter2D& arbiter, Nz::RigidBody2D& bodyA, Nz::RigidBody2D& bodyB, void* userdata) + { + callbacks.endCallback(*this, arbiter, GetEntityFromBody(bodyA), GetEntityFromBody(bodyB), userdata); + }; + } + if (callbacks.preSolveCallback) + { + worldCallbacks.preSolveCallback = [&callbacks, this](Nz::PhysWorld2D& world, Nz::Arbiter2D& arbiter, Nz::RigidBody2D& bodyA, Nz::RigidBody2D& bodyB, void* userdata) + { + return callbacks.preSolveCallback(*this, arbiter, GetEntityFromBody(bodyA), GetEntityFromBody(bodyB), userdata); + }; + } + if (callbacks.postSolveCallback) + { + worldCallbacks.postSolveCallback = [&callbacks, this](Nz::PhysWorld2D& world, Nz::Arbiter2D& arbiter, Nz::RigidBody2D& bodyA, Nz::RigidBody2D& bodyB, void* userdata) + { + callbacks.preSolveCallback(*this, arbiter, GetEntityFromBody(bodyA), GetEntityFromBody(bodyB), userdata); + }; + } + if (callbacks.startCallback) + { + worldCallbacks.startCallback = [&callbacks, this](Nz::PhysWorld2D& world, Nz::Arbiter2D& arbiter, Nz::RigidBody2D& bodyA, Nz::RigidBody2D& bodyB, void* userdata) + { + return callbacks.startCallback(*this, arbiter, GetEntityFromBody(bodyA), GetEntityFromBody(bodyB), userdata); + }; + } + + worldCallbacks.userdata = callbacks.userdata; + + m_physWorld->RegisterCallbacks(collisionIdA, collisionIdB, worldCallbacks); + } + SystemIndex PhysicsSystem2D::systemIndex; } diff --git a/tests/SDK/NDK/Systems/PhysicsSystem2D.cpp b/tests/SDK/NDK/Systems/PhysicsSystem2D.cpp index 53808dc42..3cda3f0f1 100644 --- a/tests/SDK/NDK/Systems/PhysicsSystem2D.cpp +++ b/tests/SDK/NDK/Systems/PhysicsSystem2D.cpp @@ -21,7 +21,7 @@ SCENARIO("PhysicsSystem2D", "[NDK][PHYSICSSYSTEM2D]") Ndk::PhysicsComponent2D& physicsComponent2D = movingEntity->AddComponent(); world.GetSystem().SetMaximumUpdateRate(0.f); - world.GetSystem().GetWorld().SetMaxStepCount(std::numeric_limits::max()); + world.GetSystem().SetMaxStepCount(std::numeric_limits::max()); WHEN("We update the world") { @@ -100,7 +100,7 @@ SCENARIO("PhysicsSystem2D", "[NDK][PHYSICSSYSTEM2D]") WHEN("We put a force on it") { - float stepSize = world.GetSystem().GetWorld().GetStepSize(); + float stepSize = world.GetSystem().GetStepSize(); Nz::Vector2f velocity = Nz::Vector2f::UnitX(); physicsComponent2D.AddForce(velocity / stepSize); world.Update(1.f);