From f8643f1dcfc2996a0814aac88e48b09100f68b58 Mon Sep 17 00:00:00 2001 From: Antoine Pilote Date: Wed, 1 Mar 2023 22:05:23 -0500 Subject: [PATCH] Partial Jolt implementation and some model loader fixes. --- .../src/Core/Physics/CharacterController.cpp | 19 -- Nuake/src/Core/Physics/DynamicWorld.cpp | 266 +++++++++++++++++- Nuake/src/Core/Physics/DynamicWorld.h | 21 +- Nuake/src/Core/Physics/PhysicsManager.cpp | 22 +- Nuake/src/Core/Physics/PhysicsShapes.cpp | 14 +- Nuake/src/Core/Physics/PhysicsShapes.h | 10 - Nuake/src/Core/Physics/Rigibody.h | 9 +- Nuake/src/Resource/ModelLoader.cpp | 2 +- Nuake/src/Scene/Components/ModelComponent.cpp | 2 +- .../Scene/Components/TransformComponent.cpp | 3 + Nuake/src/Scene/Scene.cpp | 1 + 11 files changed, 282 insertions(+), 87 deletions(-) diff --git a/Nuake/src/Core/Physics/CharacterController.cpp b/Nuake/src/Core/Physics/CharacterController.cpp index f789a0eb..18e1eb1d 100644 --- a/Nuake/src/Core/Physics/CharacterController.cpp +++ b/Nuake/src/Core/Physics/CharacterController.cpp @@ -8,22 +8,7 @@ namespace Nuake { CharacterController::CharacterController(float height, float radius, float mass, Vector3 position) { - m_surfaceHitNormals = std::vector(); - m_bottomRoundedRegionYOffset = (height + radius) / 2.0f; - m_bottomYOffset = height / 2.0f + radius; - - - - //m_CollisionShape->calculateLocalInertia(mass, inertia); - - - //rigidBodyCI.m_additionalDamping = true; - //rigidBodyCI.m_additionalLinearDampingThresholdSqr= 1.0f; - //rigidBodyCI.m_additionalLinearDampingThresholdSqr = 0.5f; - - // Specify filters manually, otherwise ghost doesn't collide with statics for some reason - //m_pPhysicsWorld->m_pDynamicsWorld->addCollisionObject(m_pGhostObject, btBroadphaseProxy::KinematicFilter, btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter); } void CharacterController::SetEntity(Entity& ent) @@ -31,12 +16,8 @@ namespace Nuake } - - void CharacterController::MoveAndSlide(glm::vec3 velocity) { - - IsOnGround = false; ParseGhostContacts(); UpdatePosition(); diff --git a/Nuake/src/Core/Physics/DynamicWorld.cpp b/Nuake/src/Core/Physics/DynamicWorld.cpp index eb540aa2..0bdf1077 100644 --- a/Nuake/src/Core/Physics/DynamicWorld.cpp +++ b/Nuake/src/Core/Physics/DynamicWorld.cpp @@ -5,33 +5,258 @@ #include #include - +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace Nuake { - namespace Physics + // Callback for traces, connect this to your own trace function if you have one + static void TraceImpl(const char* inFMT, ...) { - DynamicWorld::DynamicWorld() + // Format the message + va_list list; + va_start(list, inFMT); + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), inFMT, list); + + // Print to the TTY + std::cout << buffer << std::endl; + } + +#ifdef JPH_ENABLE_ASSERTS + + // Callback for asserts, connect this to your own assert handler if you have one + static bool AssertFailedImpl(const char* inExpression, const char* inMessage, const char* inFile, uint32_t inLine) + { + // Print to the TTY + std::cout << inFile << ":" << inLine << ": (" << inExpression << ") " << (inMessage != nullptr ? inMessage : "") << std::endl; + + // Breakpoint + return true; + }; + +#endif // JPH_ENABLE_ASSERTS + + // Layer that objects can be in, determines which other objects it can collide with + // Typically you at least want to have 1 layer for moving bodies and 1 layer for static bodies, but you can have more + // layers if you want. E.g. you could have a layer for high detail collision (which is not used by the physics simulation + // but only if you do collision testing). + namespace Layers + { + static constexpr uint8_t NON_MOVING = 0; + static constexpr uint8_t MOVING = 1; + static constexpr uint8_t NUM_LAYERS = 2; + }; + + // Function that determines if two object layers can collide + static bool MyObjectCanCollide(JPH::ObjectLayer inObject1, JPH::ObjectLayer inObject2) + { + switch (inObject1) { - ///collision configuration contains default setup for memory, collision setup. Advanced users can create their own configuration. - - SetGravity(Vector3(0, -10000, 0)); + case Layers::NON_MOVING: + return inObject2 == Layers::MOVING; // Non moving only collides with moving + case Layers::MOVING: + return true; // Moving collides with everything + default: + //JPH_ASSERT(false); + return false; + } + }; + + // Each broadphase layer results in a separate bounding volume tree in the broad phase. You at least want to have + // a layer for non-moving and moving objects to avoid having to update a tree full of static objects every frame. + // You can have a 1-on-1 mapping between object layers and broadphase layers (like in this case) but if you have + // many object layers you'll be creating many broad phase trees, which is not efficient. If you want to fine tune + // your broadphase layers define JPH_TRACK_BROADPHASE_STATS and look at the stats reported on the TTY. + namespace BroadPhaseLayers + { + static constexpr JPH::BroadPhaseLayer NON_MOVING(0); + static constexpr JPH::BroadPhaseLayer MOVING(1); + static constexpr uint32_t NUM_LAYERS(2); + }; + + // BroadPhaseLayerInterface implementation + // This defines a mapping between object and broadphase layers. + class BPLayerInterfaceImpl final : public JPH::BroadPhaseLayerInterface + { + public: + BPLayerInterfaceImpl() + { + // Create a mapping table from object to broad phase layer + mObjectToBroadPhase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING; + mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING; } + virtual JPH::uint GetNumBroadPhaseLayers() const override + { + return BroadPhaseLayers::NUM_LAYERS; + } + + virtual JPH::BroadPhaseLayer GetBroadPhaseLayer(JPH::ObjectLayer inLayer) const override + { + using namespace JPH; + JPH_ASSERT(inLayer < Layers::NUM_LAYERS); + return mObjectToBroadPhase[inLayer]; + } + +#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED) + virtual const char* GetBroadPhaseLayerName(BroadPhaseLayer inLayer) const override + { + switch ((BroadPhaseLayer::Type)inLayer) + { + case (BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING: return "NON_MOVING"; + case (BroadPhaseLayer::Type)BroadPhaseLayers::MOVING: return "MOVING"; + default: JPH_ASSERT(false); return "INVALID"; + } + } +#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED + + private: + JPH::BroadPhaseLayer mObjectToBroadPhase[Layers::NUM_LAYERS]; + }; + + // Function that determines if two broadphase layers can collide + static bool MyBroadPhaseCanCollide(JPH::ObjectLayer inLayer1, JPH::BroadPhaseLayer inLayer2) + { + using namespace JPH; + switch (inLayer1) + { + case Layers::NON_MOVING: + return inLayer2 == BroadPhaseLayers::MOVING; + case Layers::MOVING: + return true; + default: + JPH_ASSERT(false); + return false; + } + } + + // An example contact listener + class MyContactListener : public JPH::ContactListener + { + public: + // See: ContactListener + virtual JPH::ValidateResult OnContactValidate(const JPH::Body& inBody1, const JPH::Body& inBody2, const JPH::CollideShapeResult& inCollisionResult) override + { + std::cout << "Contact validate callback" << std::endl; + + // Allows you to ignore a contact before it is created (using layers to not make objects collide is cheaper!) + return JPH::ValidateResult::AcceptAllContactsForThisBodyPair; + } + + virtual void OnContactAdded(const JPH::Body& inBody1, const JPH::Body& inBody2, const JPH::ContactManifold& inManifold, JPH::ContactSettings& ioSettings) override + { + std::cout << "A contact was added" << std::endl; + } + + virtual void OnContactPersisted(const JPH::Body& inBody1, const JPH::Body& inBody2, const JPH::ContactManifold& inManifold, JPH::ContactSettings& ioSettings) override + { + std::cout << "A contact was persisted" << std::endl; + } + + virtual void OnContactRemoved(const JPH::SubShapeIDPair& inSubShapePair) override + { + std::cout << "A contact was removed" << std::endl; + } + }; + + // An example activation listener + class MyBodyActivationListener : public JPH::BodyActivationListener + { + public: + virtual void OnBodyActivated(const JPH::BodyID& inBodyID, JPH::uint64 inBodyUserData) override + { + std::cout << "A body got activated" << std::endl; + } + + virtual void OnBodyDeactivated(const JPH::BodyID& inBodyID, JPH::uint64 inBodyUserData) override + { + std::cout << "A body went to sleep" << std::endl; + } + }; + JPH::BodyID sphere_id; + namespace Physics + { + DynamicWorld::DynamicWorld() : _stepCount(0) + { + const uint32_t MaxBodies = 1024; + const uint32_t NumBodyMutexes = 0; + const uint32_t MaxBodyPairs = 1024; + const uint32_t MaxContactConstraints = 1024; + + BPLayerInterfaceImpl broad_phase_layer_interface; + + _JoltPhysicsSystem = CreateRef(); + _JoltPhysicsSystem->Init(MaxBodies, NumBodyMutexes, MaxBodyPairs, MaxContactConstraints, broad_phase_layer_interface, MyBroadPhaseCanCollide, MyObjectCanCollide); + + // A body activation listener gets notified when bodies activate and go to sleep + // Note that this is called from a job so whatever you do here needs to be thread safe. + // Registering one is entirely optional. + _bodyActivationListener = CreateScope(); + _JoltPhysicsSystem->SetBodyActivationListener(_bodyActivationListener.get()); + + // A contact listener gets notified when bodies (are about to) collide, and when they separate again. + // Note that this is called from a job so whatever you do here needs to be thread safe. + // Registering one is entirely optional. + _contactListener = CreateScope(); + _JoltPhysicsSystem->SetContactListener(_contactListener.get()); + + // The main way to interact with the bodies in the physics system is through the body interface. There is a locking and a non-locking + // variant of this. We're going to use the locking version (even though we're not planning to access bodies from multiple threads) + JPH::BodyInterface& bodyInterface = _JoltPhysicsSystem->GetBodyInterface(); + + // Next we can create a rigid body to serve as the floor, we make a large box + // Create the settings for the collision volume (the shape). + // Note that for simple shapes (like boxes) you can also directly construct a BoxShape. + JPH::BoxShapeSettings floor_shape_settings(JPH::Vec3(100.0f, 1.0f, 100.0f)); + + // Create the shape + JPH::ShapeSettings::ShapeResult floor_shape_result = floor_shape_settings.Create(); + JPH::ShapeRefC floor_shape = floor_shape_result.Get(); // We don't expect an error here, but you can check floor_shape_result for HasError() / GetError() + + // Create the settings for the body itself. Note that here you can also set other properties like the restitution / friction. + JPH::BodyCreationSettings floor_settings(floor_shape, JPH::Vec3(0.0f, -1.0f, 0.0f), JPH::Quat::sIdentity(), JPH::EMotionType::Static, Layers::NON_MOVING); + + // Create the actual rigid body + JPH::Body* floor = bodyInterface.CreateBody(floor_settings); // Note that if we run out of bodies this can return nullptr + + bodyInterface.AddBody(floor->GetID(), JPH::EActivation::DontActivate); + + JPH::BodyCreationSettings sphere_settings(new JPH::SphereShape(0.5f), JPH::Vec3(0.0, 2.0, 0.0), JPH::Quat::sIdentity(), JPH::EMotionType::Dynamic, Layers::MOVING); + sphere_id = bodyInterface.CreateAndAddBody(sphere_settings, JPH::EActivation::Activate); + + // Now you can interact with the dynamic body, in this case we're going to give it a velocity. + // (note that if we had used CreateBody then we could have set the velocity straight on the body before adding it to the physics system) + bodyInterface.SetLinearVelocity(sphere_id, JPH::Vec3(0.0f, -5.0f, 0.0f)); + + // We simulate the physics world in discrete time steps. 60 Hz is a good rate to update the physics system. + const float cDeltaTime = 1.0f / 60.0f; + + // Optional step: Before starting the physics simulation you can optimize the broad phase. This improves collision detection performance (it's pointless here because we only have 2 bodies). + // You should definitely not call this every frame or when e.g. streaming in a new level section as it is an expensive operation. + // Instead insert all new objects in batches instead of 1 at a time to keep the broad phase efficient. + _JoltPhysicsSystem->OptimizeBroadPhase(); + _JoltJobSystem = new JPH::JobSystemThreadPool(JPH::cMaxPhysicsJobs, JPH::cMaxPhysicsBarriers, std::thread::hardware_concurrency() - 1); + } void DynamicWorld::DrawDebug() { } - void DynamicWorld::SetGravity(glm::vec3 g) { } - void DynamicWorld::AddRigidbody(Ref rb) { - } void DynamicWorld::AddGhostbody(Ref gb) @@ -40,11 +265,8 @@ namespace Nuake void DynamicWorld::AddCharacterController(Ref cc) { - - // Specify filters manually, otherwise ghost doesn't collide with statics for some reason } - RaycastResult DynamicWorld::Raycast(glm::vec3 from, glm::vec3 to) { Vector3 localNorm = glm::vec3(0,0,0); @@ -61,17 +283,31 @@ namespace Nuake return result; } - void DynamicWorld::StepSimulation(Timestep ts) { + // Next step + ++_stepCount; + const auto& bodyInterface = _JoltPhysicsSystem->GetBodyInterface(); + // Output current position and velocity of the sphere + JPH::Vec3 position = bodyInterface.GetCenterOfMassPosition(sphere_id); + JPH::Vec3 velocity = bodyInterface.GetLinearVelocity(sphere_id); + + std::cout << "Step " << _stepCount << ": Position = (" << position.GetX() << ", " << position.GetY() << ", " << position.GetZ() << "), Velocity = (" << velocity.GetX() << ", " << velocity.GetY() << ", " << velocity.GetZ() << ")" << std::endl; + + // If you take larger steps than 1 / 60th of a second you need to do multiple collision steps in order to keep the simulation stable. Do 1 collision step per 1 / 60th of a second (round up). + const int cCollisionSteps = 1; + + // If you want more accurate step results you can do multiple sub steps within a collision step. Usually you would set this to 1. + const int cIntegrationSubSteps = 1; + + // Step the world + _JoltPhysicsSystem->Update(ts, cCollisionSteps, cIntegrationSubSteps, new JPH::TempAllocatorMalloc(), _JoltJobSystem); } - void DynamicWorld::Clear() { } } - } diff --git a/Nuake/src/Core/Physics/DynamicWorld.h b/Nuake/src/Core/Physics/DynamicWorld.h index b4e9d4a1..3ed02b22 100644 --- a/Nuake/src/Core/Physics/DynamicWorld.h +++ b/Nuake/src/Core/Physics/DynamicWorld.h @@ -9,11 +9,28 @@ #include #include "CharacterController.h" +namespace JPH +{ + class PhysicsSystem; + class JobSystemThreadPool; + class ContactListener; + class BodyActivationListener; +} + namespace Nuake { + class MyContactListener; + class MyBodyActivationListener; + namespace Physics { class DynamicWorld { private: + uint32_t _stepCount; + Ref _JoltPhysicsSystem; + JPH::JobSystemThreadPool* _JoltJobSystem; + Scope _contactListener; + Scope _bodyActivationListener; + public: DynamicWorld(); @@ -23,13 +40,11 @@ namespace Nuake void AddRigidbody(Ref rb); void AddGhostbody(Ref gb); - - void AddCharacterController(Ref < CharacterController> cc); + void AddCharacterController(Ref cc); RaycastResult Raycast(glm::vec3 from, glm::vec3 to); void StepSimulation(Timestep ts); void Clear(); - }; } } diff --git a/Nuake/src/Core/Physics/PhysicsManager.cpp b/Nuake/src/Core/Physics/PhysicsManager.cpp index a9bdadfa..725876f4 100644 --- a/Nuake/src/Core/Physics/PhysicsManager.cpp +++ b/Nuake/src/Core/Physics/PhysicsManager.cpp @@ -40,9 +40,7 @@ namespace Nuake RaycastResult PhysicsManager::Raycast(glm::vec3 from, glm::vec3 to) { - return m_World->Raycast(from, to); - } void PhysicsManager::DrawDebug() @@ -53,31 +51,15 @@ namespace Nuake void PhysicsManager::Init() { + Logger::Log("Initializing Jolt physics."); JPH::RegisterDefaultAllocator(); + Logger::Log("Creating factory & registering types."); JPH::Factory::sInstance = new JPH::Factory(); - JPH::RegisterTypes(); // This is the max amount of rigid bodies that you can add to the physics system. If you try to add more you'll get an error. // Note: This value is low because this is a simple test. For a real project use something in the order of 65536. - const uint32_t cMaxBodies = 1024; - - // This determines how many mutexes to allocate to protect rigid bodies from concurrent access. Set it to 0 for the default settings. - const uint32_t cNumBodyMutexes = 0; - - // This is the max amount of body pairs that can be queued at any time (the broad phase will detect overlapping - // body pairs based on their bounding boxes and will insert them into a queue for the narrowphase). If you make this buffer - // too small the queue will fill up and the broad phase jobs will start to do narrow phase work. This is slightly less efficient. - // Note: This value is low because this is a simple test. For a real project use something in the order of 65536. - const uint32_t cMaxBodyPairs = 1024; - - // This is the maximum size of the contact constraint buffer. If more contacts (collisions between bodies) are detected than this - // number then these contacts will be ignored and bodies will start interpenetrating / fall through the world. - // Note: This value is low because this is a simple test. For a real project use something in the order of 10240. - const uint32_t cMaxContactConstraints = 1024; - - m_World = new Physics::DynamicWorld(); m_World->SetGravity(glm::vec3(0, -3, 0)); diff --git a/Nuake/src/Core/Physics/PhysicsShapes.cpp b/Nuake/src/Core/Physics/PhysicsShapes.cpp index ec773dd1..82a1dfc7 100644 --- a/Nuake/src/Core/Physics/PhysicsShapes.cpp +++ b/Nuake/src/Core/Physics/PhysicsShapes.cpp @@ -21,10 +21,7 @@ namespace Nuake m_Type = BOX; } - btCollisionShape* Box::GetBulletShape() - { - return bShape; - } + // Sphere Sphere::Sphere(float radius) { @@ -37,11 +34,6 @@ namespace Nuake Radius = radius; } - btCollisionShape* Sphere::GetBulletShape() - { - return bShape; - } - MeshShape::MeshShape(Ref mesh) { m_Mesh = mesh; @@ -59,9 +51,5 @@ namespace Nuake } - btCollisionShape* MeshShape::GetBulletShape() - { - return bShape; - } } } diff --git a/Nuake/src/Core/Physics/PhysicsShapes.h b/Nuake/src/Core/Physics/PhysicsShapes.h index 99a4d3ca..f4b4343f 100644 --- a/Nuake/src/Core/Physics/PhysicsShapes.h +++ b/Nuake/src/Core/Physics/PhysicsShapes.h @@ -2,8 +2,6 @@ #include "src/Core/Maths.h" #include "src/Rendering/Mesh/Mesh.h" -class btCollisionShape; - namespace Nuake { namespace Physics @@ -16,10 +14,8 @@ namespace Nuake class PhysicShape { protected: - btCollisionShape* bShape; RigidbodyShapes m_Type; public: - virtual btCollisionShape* GetBulletShape() = 0; RigidbodyShapes GetType() const { return m_Type; } }; @@ -28,42 +24,36 @@ namespace Nuake { private: glm::vec3 Size; - btCollisionShape* bShape; public: Box(); Box(glm::vec3 size); Box(float x, float y, float z); glm::vec3 GetSize() const { return Size; } - btCollisionShape* GetBulletShape() override; }; class Sphere : public PhysicShape { private: float Radius; - btCollisionShape* bShape; public: Sphere(float radius); float GetRadius() const { return Radius; } void SetRadius(float radius); - btCollisionShape* GetBulletShape() override; }; class MeshShape : public PhysicShape { private: Ref m_Mesh; - btCollisionShape* bShape; public: MeshShape(Ref mesh); void SetMesh(Mesh* mesh); Mesh* GetMesh(); - btCollisionShape* GetBulletShape() override; }; } diff --git a/Nuake/src/Core/Physics/Rigibody.h b/Nuake/src/Core/Physics/Rigibody.h index 4a4694e0..b6df4610 100644 --- a/Nuake/src/Core/Physics/Rigibody.h +++ b/Nuake/src/Core/Physics/Rigibody.h @@ -3,6 +3,7 @@ #include "../Core/Core.h" #include + namespace Nuake { class Entity; @@ -11,8 +12,7 @@ namespace Nuake private: bool m_IsDynamic = false; bool m_IsKinematic = false; - glm::vec3 m_InitialVel; - + Vector3 m_InitialVel; Ref m_CollisionShape; public: @@ -22,10 +22,9 @@ namespace Nuake RigidBody(glm::vec3 position, Entity handle); RigidBody(float mass, glm::vec3 position, Ref shape, glm::vec3 initialVel = glm::vec3(0, 0, 0)); - void UpdateTransform(); - glm::vec3 GetPosition() const; - glm::vec3 GetRotation() const; + Vector3 GetPosition() const; + Vector3 GetRotation() const; void SetEntityID(Entity ent); diff --git a/Nuake/src/Resource/ModelLoader.cpp b/Nuake/src/Resource/ModelLoader.cpp index e0fece67..de806c74 100644 --- a/Nuake/src/Resource/ModelLoader.cpp +++ b/Nuake/src/Resource/ModelLoader.cpp @@ -214,7 +214,7 @@ namespace Nuake return texture; } - std::string texturePath = FileSystem::Root + modelDir + path; + std::string texturePath = modelDir + path; if (!FileSystem::FileExists(texturePath, true)) { std::string textureNotFoundmsg = "Texture file couldn't be found: " + texturePath; diff --git a/Nuake/src/Scene/Components/ModelComponent.cpp b/Nuake/src/Scene/Components/ModelComponent.cpp index b1cc3ef7..12e8f6de 100644 --- a/Nuake/src/Scene/Components/ModelComponent.cpp +++ b/Nuake/src/Scene/Components/ModelComponent.cpp @@ -15,7 +15,7 @@ namespace Nuake { void ModelComponent::LoadModel() { auto loader = ModelLoader(); - this->ModelResource = loader.LoadModel(FileSystem::Root + "../" + ModelPath); + this->ModelResource = loader.LoadModel(FileSystem::Root + ModelPath); } } diff --git a/Nuake/src/Scene/Components/TransformComponent.cpp b/Nuake/src/Scene/Components/TransformComponent.cpp index 614d6234..d35c8d76 100644 --- a/Nuake/src/Scene/Components/TransformComponent.cpp +++ b/Nuake/src/Scene/Components/TransformComponent.cpp @@ -89,6 +89,8 @@ namespace Nuake void TransformComponent::SetGlobalTransform(const Matrix4& transform) { GlobalTransform = transform; + + GlobalTranslation = Vector3(transform[3]); } Matrix4 TransformComponent::GetLocalTransform() const @@ -99,5 +101,6 @@ namespace Nuake void TransformComponent::SetLocalTransform(const Matrix4& transform) { LocalTransform = transform; + Translation = Vector3(transform[3]); } } diff --git a/Nuake/src/Scene/Scene.cpp b/Nuake/src/Scene/Scene.cpp index 3f2cee3a..23dcf8c4 100644 --- a/Nuake/src/Scene/Scene.cpp +++ b/Nuake/src/Scene/Scene.cpp @@ -136,6 +136,7 @@ namespace Nuake { auto [transform, camera, parent] = view.get(e); cam = camera.CameraInstance; cam->Translation = transform.GetGlobalPosition(); + cam->SetDirection(Vector3(Vector4(0, 0, 1, 0) * transform.GetGlobalTransform())); break; } }