diff --git a/SDK/include/NDK/Components/PhysicsComponent3D.hpp b/SDK/include/NDK/Components/PhysicsComponent3D.hpp index 697f72632..98e36079a 100644 --- a/SDK/include/NDK/Components/PhysicsComponent3D.hpp +++ b/SDK/include/NDK/Components/PhysicsComponent3D.hpp @@ -54,6 +54,8 @@ namespace Ndk void SetLinearVelocity(const Nz::Vector3f& velocity); void SetMass(float mass); void SetMassCenter(const Nz::Vector3f& center); + void SetMaterial(const Nz::String& materialName); + void SetMaterial(int materialIndex); void SetPosition(const Nz::Vector3f& position); void SetRotation(const Nz::Quaternionf& rotation); diff --git a/SDK/include/NDK/Components/PhysicsComponent3D.inl b/SDK/include/NDK/Components/PhysicsComponent3D.inl index e81314316..22123ca52 100644 --- a/SDK/include/NDK/Components/PhysicsComponent3D.inl +++ b/SDK/include/NDK/Components/PhysicsComponent3D.inl @@ -396,7 +396,6 @@ namespace Ndk * * \remark Produces a NazaraAssert if the physics object is invalid */ - inline void PhysicsComponent3D::SetMassCenter(const Nz::Vector3f& center) { NazaraAssert(m_object, "Invalid physics object"); @@ -404,6 +403,34 @@ namespace Ndk m_object->SetMassCenter(center); } + /*! + * \brief Sets the material of the object, affecting how object does respond to collisions + * + * \param materialName Name of the material, previously registered to physics world + * + * \remark materialName must exists in PhysWorld before this call + */ + inline void PhysicsComponent3D::SetMaterial(const Nz::String& materialName) + { + NazaraAssert(m_object, "Invalid physics object"); + + m_object->SetMaterial(materialName); + } + + /*! + * \brief Sets the material of the object, affecting how object does respond to collisions + * + * \param materialIndex Id of the material, previously retrieved from a physics world + * + * \remark materialIndex must come from a call to in PhysWorld::CreateMaterial + */ + inline void PhysicsComponent3D::SetMaterial(int materialIndex) + { + NazaraAssert(m_object, "Invalid physics object"); + + m_object->SetMaterial(materialIndex); + } + /*! * \brief Sets the position of the physics object * diff --git a/SDK/include/NDK/Systems/PhysicsSystem3D.hpp b/SDK/include/NDK/Systems/PhysicsSystem3D.hpp index 3756f7247..07e4c7220 100644 --- a/SDK/include/NDK/Systems/PhysicsSystem3D.hpp +++ b/SDK/include/NDK/Systems/PhysicsSystem3D.hpp @@ -18,7 +18,6 @@ namespace Ndk { public: PhysicsSystem3D(); - PhysicsSystem3D(const PhysicsSystem3D& system); ~PhysicsSystem3D() = default; Nz::PhysWorld3D& GetWorld(); diff --git a/SDK/src/NDK/Components/CollisionComponent3D.cpp b/SDK/src/NDK/Components/CollisionComponent3D.cpp index faa95fa26..a147043d7 100644 --- a/SDK/src/NDK/Components/CollisionComponent3D.cpp +++ b/SDK/src/NDK/Components/CollisionComponent3D.cpp @@ -59,6 +59,7 @@ namespace Ndk m_staticBody = std::make_unique(&physWorld, m_geom); m_staticBody->EnableAutoSleep(false); + m_staticBody->SetUserdata(reinterpret_cast(static_cast(m_entity->GetId()))); } /*! diff --git a/SDK/src/NDK/Components/PhysicsComponent3D.cpp b/SDK/src/NDK/Components/PhysicsComponent3D.cpp index 643f88300..4615d3c43 100644 --- a/SDK/src/NDK/Components/PhysicsComponent3D.cpp +++ b/SDK/src/NDK/Components/PhysicsComponent3D.cpp @@ -41,6 +41,7 @@ namespace Ndk matrix.MakeIdentity(); m_object = std::make_unique(&world, geom, matrix); + m_object->SetUserdata(reinterpret_cast(static_cast(m_entity->GetId()))); if (m_pendingStates.valid) ApplyPhysicsState(*m_object); diff --git a/include/Nazara/Physics3D/PhysWorld3D.hpp b/include/Nazara/Physics3D/PhysWorld3D.hpp index 0566b97a6..76d4c09b4 100644 --- a/include/Nazara/Physics3D/PhysWorld3D.hpp +++ b/include/Nazara/Physics3D/PhysWorld3D.hpp @@ -8,23 +8,40 @@ #define NAZARA_PHYSWORLD_HPP #include +#include +#include #include #include +#include +class NewtonBody; +class NewtonJoint; +class NewtonMaterial; class NewtonWorld; namespace Nz { + class RigidBody3D; + class NAZARA_PHYSICS3D_API PhysWorld3D { public: + using BodyIterator = std::function; + using AABBOverlapCallback = std::function; + using CollisionCallback = std::function; + PhysWorld3D(); PhysWorld3D(const PhysWorld3D&) = delete; PhysWorld3D(PhysWorld3D&&) = delete; ///TODO ~PhysWorld3D(); + int CreateMaterial(Nz::String name = Nz::String()); + + void ForEachBodyInAABB(const Nz::Boxf& box, BodyIterator iterator); + Vector3f GetGravity() const; NewtonWorld* GetHandle() const; + int GetMaterial(const Nz::String& name); std::size_t GetMaxStepCount() const; float GetStepSize() const; @@ -33,12 +50,30 @@ namespace Nz void SetSolverModel(unsigned int model); void SetStepSize(float stepSize); + void SetMaterialCollisionCallback(int firstMaterial, int secondMaterial, AABBOverlapCallback aabbOverlapCallback, CollisionCallback collisionCallback); + void SetMaterialDefaultCollidable(int firstMaterial, int secondMaterial, bool collidable); + void SetMaterialDefaultElasticity(int firstMaterial, int secondMaterial, float elasticCoef); + void SetMaterialDefaultFriction(int firstMaterial, int secondMaterial, float staticFriction, float kineticFriction); + void SetMaterialDefaultSoftness(int firstMaterial, int secondMaterial, float softness); + void SetMaterialSurfaceThickness(int firstMaterial, int secondMaterial, float thickness); + void Step(float timestep); PhysWorld3D& operator=(const PhysWorld3D&) = delete; PhysWorld3D& operator=(PhysWorld3D&&) = delete; ///TODO private: + struct Callback + { + AABBOverlapCallback aabbOverlapCallback; + CollisionCallback collisionCallback; + }; + + static int OnAABBOverlap(const NewtonMaterial* const material, const NewtonBody* const body0, const NewtonBody* const body1, int threadIndex); + static void ProcessContact(const NewtonJoint* const contact, float timestep, int threadIndex); + + std::unordered_map> m_callbacks; + std::unordered_map m_materialIds; std::size_t m_maxStepCount; Vector3f m_gravity; NewtonWorld* m_world; diff --git a/include/Nazara/Physics3D/RigidBody3D.hpp b/include/Nazara/Physics3D/RigidBody3D.hpp index 719c0c997..28f53536f 100644 --- a/include/Nazara/Physics3D/RigidBody3D.hpp +++ b/include/Nazara/Physics3D/RigidBody3D.hpp @@ -47,9 +47,11 @@ namespace Nz Vector3f GetLinearVelocity() const; float GetMass() const; Vector3f GetMassCenter(CoordSys coordSys = CoordSys_Local) const; + int GetMaterial() const; const Matrix4f& GetMatrix() const; Vector3f GetPosition() const; Quaternionf GetRotation() const; + void* GetUserdata() const; PhysWorld3D* GetWorld() const; bool IsAutoSleepEnabled() const; @@ -65,8 +67,11 @@ namespace Nz void SetLinearVelocity(const Vector3f& velocity); void SetMass(float mass); void SetMassCenter(const Vector3f& center); + void SetMaterial(const Nz::String& materialName); + void SetMaterial(int materialIndex); void SetPosition(const Vector3f& position); void SetRotation(const Quaternionf& rotation); + void SetUserdata(void* ud); RigidBody3D& operator=(const RigidBody3D& object); RigidBody3D& operator=(RigidBody3D&& object); @@ -82,6 +87,7 @@ namespace Nz Vector3f m_torqueAccumulator; NewtonBody* m_body; PhysWorld3D* m_world; + void* m_userdata; float m_gravityFactor; float m_mass; }; diff --git a/src/Nazara/Physics3D/PhysWorld3D.cpp b/src/Nazara/Physics3D/PhysWorld3D.cpp index 3f722e93f..2a01a27e0 100644 --- a/src/Nazara/Physics3D/PhysWorld3D.cpp +++ b/src/Nazara/Physics3D/PhysWorld3D.cpp @@ -3,7 +3,9 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include #include +#include #include namespace Nz @@ -16,6 +18,8 @@ namespace Nz { m_world = NewtonCreate(); NewtonWorldSetUserData(m_world, this); + + m_materialIds.emplace("default", NewtonMaterialGetDefaultGroupID(m_world)); } PhysWorld3D::~PhysWorld3D() @@ -23,6 +27,28 @@ namespace Nz NewtonDestroy(m_world); } + int PhysWorld3D::CreateMaterial(Nz::String name) + { + NazaraAssert(m_materialIds.find(name) == m_materialIds.end(), "Material \"" + name + "\" already exists"); + + int materialId = NewtonMaterialCreateGroupID(m_world); + m_materialIds.emplace(std::move(name), materialId); + + return materialId; + } + + void PhysWorld3D::ForEachBodyInAABB(const Nz::Boxf& box, BodyIterator iterator) + { + auto NewtonCallback = [](const NewtonBody* const body, void* const userdata) -> int + { + BodyIterator& iterator = *static_cast(userdata); + RigidBody3D* nzBody = static_cast(NewtonBodyGetUserData(body)); + return iterator(*nzBody); + }; + + NewtonWorldForEachBodyInAABBDo(m_world, box.GetMinimum(), box.GetMaximum(), NewtonCallback, &iterator); + } + Vector3f PhysWorld3D::GetGravity() const { return m_gravity; @@ -33,6 +59,14 @@ namespace Nz return m_world; } + int PhysWorld3D::GetMaterial(const Nz::String& name) + { + auto it = m_materialIds.find(name); + NazaraAssert(it != m_materialIds.end(), "Material \"" + name + "\" does not exists"); + + return it->second; + } + std::size_t PhysWorld3D::GetMaxStepCount() const { return m_maxStepCount; @@ -63,6 +97,48 @@ namespace Nz m_stepSize = stepSize; } + void PhysWorld3D::SetMaterialCollisionCallback(int firstMaterial, int secondMaterial, AABBOverlapCallback aabbOverlapCallback, CollisionCallback collisionCallback) + { + static_assert(sizeof(Nz::UInt64) >= 2 * sizeof(int), "Oops"); + + auto callbackPtr = std::make_unique(); + callbackPtr->aabbOverlapCallback = std::move(aabbOverlapCallback); + callbackPtr->collisionCallback = std::move(collisionCallback); + + NewtonMaterialSetCollisionCallback(m_world, firstMaterial, secondMaterial, callbackPtr.get(), (callbackPtr->aabbOverlapCallback) ? OnAABBOverlap : nullptr, (callbackPtr->collisionCallback) ? ProcessContact : nullptr); + + Nz::UInt64 firstMaterialId(firstMaterial); + Nz::UInt64 secondMaterialId(secondMaterial); + + Nz::UInt64 callbackIndex = firstMaterialId << 32 | secondMaterialId; + m_callbacks[callbackIndex] = std::move(callbackPtr); + } + + void PhysWorld3D::SetMaterialDefaultCollidable(int firstMaterial, int secondMaterial, bool collidable) + { + NewtonMaterialSetDefaultCollidable(m_world, firstMaterial, secondMaterial, collidable); + } + + void PhysWorld3D::SetMaterialDefaultElasticity(int firstMaterial, int secondMaterial, float elasticCoef) + { + NewtonMaterialSetDefaultElasticity(m_world, firstMaterial, secondMaterial, elasticCoef); + } + + void PhysWorld3D::SetMaterialDefaultFriction(int firstMaterial, int secondMaterial, float staticFriction, float kineticFriction) + { + NewtonMaterialSetDefaultFriction(m_world, firstMaterial, secondMaterial, staticFriction, kineticFriction); + } + + void PhysWorld3D::SetMaterialDefaultSoftness(int firstMaterial, int secondMaterial, float softness) + { + NewtonMaterialSetDefaultSoftness(m_world, firstMaterial, secondMaterial, softness); + } + + void PhysWorld3D::SetMaterialSurfaceThickness(int firstMaterial, int secondMaterial, float thickness) + { + NewtonMaterialSetSurfaceThickness(m_world, firstMaterial, secondMaterial, thickness); + } + void PhysWorld3D::Step(float timestep) { m_timestepAccumulator += timestep; @@ -75,4 +151,46 @@ namespace Nz stepCount++; } } + + int PhysWorld3D::OnAABBOverlap(const NewtonMaterial* const material, const NewtonBody* const body0, const NewtonBody* const body1, int threadIndex) + { + Nz::RigidBody3D* bodyA = static_cast(NewtonBodyGetUserData(body0)); + Nz::RigidBody3D* bodyB = static_cast(NewtonBodyGetUserData(body1)); + assert(bodyA && bodyB); + + Callback* callbackData = static_cast(NewtonMaterialGetMaterialPairUserData(material)); + assert(callbackData); + assert(callbackData->aabbOverlapCallback); + + return callbackData->aabbOverlapCallback(*bodyA, *bodyB); + } + + void PhysWorld3D::ProcessContact(const NewtonJoint* const contactJoint, float timestep, int threadIndex) + { + Nz::RigidBody3D* bodyA = static_cast(NewtonBodyGetUserData(NewtonJointGetBody0(contactJoint))); + Nz::RigidBody3D* bodyB = static_cast(NewtonBodyGetUserData(NewtonJointGetBody1(contactJoint))); + assert(bodyA && bodyB); + + using ContactJoint = void*; + + // Query all joints first, to prevent removing a joint from the list while iterating on it + Nz::StackArray contacts = NazaraStackAllocationNoInit(ContactJoint, NewtonContactJointGetContactCount(contactJoint)); + std::size_t contactIndex = 0; + for (ContactJoint contact = NewtonContactJointGetFirstContact(contactJoint); contact; contact = NewtonContactJointGetNextContact(contactJoint, contact)) + { + assert(contactIndex < contacts.size()); + contacts[contactIndex++] = contact; + } + + for (ContactJoint contact : contacts) + { + NewtonMaterial* material = NewtonContactGetMaterial(contact); + Callback* callbackData = static_cast(NewtonMaterialGetMaterialPairUserData(material)); + assert(callbackData); + assert(callbackData->collisionCallback); + + if (!callbackData->collisionCallback(*bodyA, *bodyB)) + NewtonContactJointRemoveContact(contactJoint, contact); + } + } } diff --git a/src/Nazara/Physics3D/RigidBody3D.cpp b/src/Nazara/Physics3D/RigidBody3D.cpp index 2718f93f8..34e6ce12f 100644 --- a/src/Nazara/Physics3D/RigidBody3D.cpp +++ b/src/Nazara/Physics3D/RigidBody3D.cpp @@ -216,6 +216,11 @@ namespace Nz return center; } + int RigidBody3D::GetMaterial() const + { + return NewtonBodyGetMaterialGroupID(m_body); + } + const Matrix4f& RigidBody3D::GetMatrix() const { return m_matrix; @@ -231,6 +236,11 @@ namespace Nz return m_matrix.GetRotation(); } + void* RigidBody3D::GetUserdata() const + { + return m_userdata; + } + PhysWorld3D* RigidBody3D::GetWorld() const { return m_world; @@ -335,6 +345,16 @@ namespace Nz NewtonBodySetCentreOfMass(m_body, center); } + void RigidBody3D::SetMaterial(const Nz::String& materialName) + { + SetMaterial(m_world->GetMaterial(materialName)); + } + + void RigidBody3D::SetMaterial(int materialIndex) + { + NewtonBodySetMaterialGroupID(m_body, materialIndex); + } + void RigidBody3D::SetPosition(const Vector3f& position) { m_matrix.SetTranslation(position); @@ -349,6 +369,11 @@ namespace Nz UpdateBody(); } + void RigidBody3D::SetUserdata(void* ud) + { + m_userdata = ud; + } + RigidBody3D& RigidBody3D::operator=(const RigidBody3D& object) { RigidBody3D physObj(object);