|
@@ -0,0 +1,276 @@
|
|
|
|
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
|
|
|
+// SPDX-License-Identifier: MIT
|
|
|
|
+
|
|
|
|
+// Jolt includes
|
|
|
|
+#include <Jolt.h>
|
|
|
|
+#include <RegisterTypes.h>
|
|
|
|
+#include <Core/TempAllocator.h>
|
|
|
|
+#include <Core/JobSystemThreadPool.h>
|
|
|
|
+#include <Physics/PhysicsSettings.h>
|
|
|
|
+#include <Physics/PhysicsSystem.h>
|
|
|
|
+#include <Physics/Collision/Shape/BoxShape.h>
|
|
|
|
+#include <Physics/Collision/Shape/SphereShape.h>
|
|
|
|
+#include <Physics/Body/BodyCreationSettings.h>
|
|
|
|
+#include <Physics/Body/BodyActivationListener.h>
|
|
|
|
+
|
|
|
|
+// STL includes
|
|
|
|
+#include <iostream>
|
|
|
|
+#include <cstdarg>
|
|
|
|
+#include <thread>
|
|
|
|
+
|
|
|
|
+// All Jolt symbols are in the JPH namespace
|
|
|
|
+using namespace JPH;
|
|
|
|
+
|
|
|
|
+// We're also using STL classes in this example
|
|
|
|
+using namespace std;
|
|
|
|
+
|
|
|
|
+// Callback for traces, connect this to your own trace function if you have one
|
|
|
|
+static void TraceImpl(const char *inFMT, ...)
|
|
|
|
+{
|
|
|
|
+ // Format the message
|
|
|
|
+ va_list list;
|
|
|
|
+ va_start(list, inFMT);
|
|
|
|
+ char buffer[1024];
|
|
|
|
+ vsnprintf(buffer, sizeof(buffer), inFMT, list);
|
|
|
|
+
|
|
|
|
+ // Print to the TTY
|
|
|
|
+ cout << buffer << 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, uint inLine)
|
|
|
|
+{
|
|
|
|
+ // Print to the TTY
|
|
|
|
+ cout << inFile << ":" << inLine << ": (" << inExpression << ") " << (inMessage != nullptr? inMessage : "") << 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 NON_MOVING = 0;
|
|
|
|
+ static constexpr uint8 MOVING = 1;
|
|
|
|
+ static constexpr uint8 NUM_LAYERS = 2;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// Function that determines if two object layers can collide
|
|
|
|
+bool MyObjectCanCollide(ObjectLayer inObject1, ObjectLayer inObject2)
|
|
|
|
+{
|
|
|
|
+ switch (inObject1)
|
|
|
|
+ {
|
|
|
|
+ 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 BroadPhaseLayer NON_MOVING(0);
|
|
|
|
+ static constexpr BroadPhaseLayer MOVING(1);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// Function that determines if two broadphase layers can collide
|
|
|
|
+bool MyBroadPhaseCanCollide(ObjectLayer inLayer1, BroadPhaseLayer inLayer2)
|
|
|
|
+{
|
|
|
|
+ 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 ContactListener
|
|
|
|
+{
|
|
|
|
+public:
|
|
|
|
+ // See: ContactListener
|
|
|
|
+ virtual ValidateResult OnContactValidate(const Body &inBody1, const Body &inBody2, const CollideShapeResult &inCollisionResult) override
|
|
|
|
+ {
|
|
|
|
+ cout << "Contact validate callback" << endl;
|
|
|
|
+
|
|
|
|
+ // Allows you to ignore a contact before it is created (using layers to not make objects collide is cheaper!)
|
|
|
|
+ return ValidateResult::AcceptAllContactsForThisBodyPair;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
|
|
|
|
+ {
|
|
|
|
+ cout << "A contact was added" << endl;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
|
|
|
|
+ {
|
|
|
|
+ cout << "A contact was persisted" << endl;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ virtual void OnContactRemoved(const SubShapeIDPair &inSubShapePair) override
|
|
|
|
+ {
|
|
|
|
+ cout << "A contact was removed" << endl;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// An example activation listener
|
|
|
|
+class MyBodyActivationListener : public BodyActivationListener
|
|
|
|
+{
|
|
|
|
+public:
|
|
|
|
+ virtual void OnBodyActivated(const BodyID &inBodyID, void *inBodyUserData) override
|
|
|
|
+ {
|
|
|
|
+ cout << "A body got activated" << endl;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ virtual void OnBodyDeactivated(const BodyID &inBodyID, void *inBodyUserData) override
|
|
|
|
+ {
|
|
|
|
+ cout << "A body went to sleep" << endl;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// Program entry point
|
|
|
|
+int main(int argc, char** argv)
|
|
|
|
+{
|
|
|
|
+ // Install callbacks
|
|
|
|
+ Trace = TraceImpl;
|
|
|
|
+ JPH_IF_ENABLE_ASSERTS(AssertFailed = AssertFailedImpl;)
|
|
|
|
+
|
|
|
|
+ // Register all Jolt physics types
|
|
|
|
+ RegisterTypes();
|
|
|
|
+
|
|
|
|
+ // We need a temp allocator for temporary allocations during the physics update. We're
|
|
|
|
+ // pre-allocating 10 MB to avoid having to do allocations during the physics update.
|
|
|
|
+ // B.t.w. 10 MB is way too much for this example but it is a typical value you can use.
|
|
|
|
+ // If you don't want to pre-allocate you can also use TempAllocatorMalloc to fall back to
|
|
|
|
+ // malloc / free.
|
|
|
|
+ TempAllocatorImpl temp_allocator(10 * 1024 * 1024);
|
|
|
|
+
|
|
|
|
+ // We need a job system that will execute physics jobs on multiple threads. Typically
|
|
|
|
+ // you would implement the JobSystem interface yourself and let Jolt Physics run on top
|
|
|
|
+ // of your own job scheduler. JobSystemThreadPool is an example implementation.
|
|
|
|
+ JobSystemThreadPool job_system(cMaxPhysicsJobs, cMaxPhysicsBarriers, thread::hardware_concurrency() - 1);
|
|
|
|
+
|
|
|
|
+ // 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.
|
|
|
|
+ const uint 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 uint 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.
|
|
|
|
+ const uint 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.
|
|
|
|
+ const uint cMaxContactConstraints = 1024;
|
|
|
|
+
|
|
|
|
+ // Create mapping table from object layer to broadphase layer
|
|
|
|
+ ObjectToBroadPhaseLayer object_to_broadphase;
|
|
|
|
+ object_to_broadphase.resize(Layers::NUM_LAYERS);
|
|
|
|
+ object_to_broadphase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
|
|
|
|
+ object_to_broadphase[Layers::MOVING] = BroadPhaseLayers::MOVING;
|
|
|
|
+
|
|
|
|
+ // Now we can create the actual physics system.
|
|
|
|
+ PhysicsSystem physics_system;
|
|
|
|
+ physics_system.Init(cMaxBodies, cNumBodyMutexes, cMaxBodyPairs, cMaxContactConstraints, object_to_broadphase, 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.
|
|
|
|
+ MyBodyActivationListener body_activation_listener;
|
|
|
|
+ physics_system.SetBodyActivationListener(&body_activation_listener);
|
|
|
|
+
|
|
|
|
+ // 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.
|
|
|
|
+ MyContactListener contact_listener;
|
|
|
|
+ physics_system.SetContactListener(&contact_listener);
|
|
|
|
+
|
|
|
|
+ // 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)
|
|
|
|
+ BodyInterface &body_interface = physics_system.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.
|
|
|
|
+ BoxShapeSettings floor_shape_settings(Vec3(100.0f, 1.0f, 100.0f));
|
|
|
|
+
|
|
|
|
+ // Create the shape
|
|
|
|
+ ShapeSettings::ShapeResult floor_shape_result = floor_shape_settings.Create();
|
|
|
|
+ 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.
|
|
|
|
+ BodyCreationSettings floor_settings(floor_shape, Vec3(0.0f, -1.0f, 0.0f), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
|
|
|
|
+
|
|
|
|
+ // Create the actual rigid body
|
|
|
|
+ Body *floor = body_interface.CreateBody(floor_settings); // Note that if we run out of bodies this can return nullptr
|
|
|
|
+
|
|
|
|
+ // Add it to the world
|
|
|
|
+ body_interface.AddBody(floor->GetID(), EActivation::DontActivate);
|
|
|
|
+
|
|
|
|
+ // Now create a dynamic body to bounce on the floor
|
|
|
|
+ // Note that this uses the shorthand version of creating and adding a body to the world
|
|
|
|
+ BodyCreationSettings sphere_settings(new SphereShape(0.5f), Vec3(0.0f, 2.0f, 0.0f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
|
|
|
|
+ BodyID sphere_id = body_interface.CreateAndAddBody(sphere_settings, 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)
|
|
|
|
+ body_interface.SetLinearVelocity(sphere_id, 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;
|
|
|
|
+
|
|
|
|
+ // Now we're ready to simulate the body, keep simulating until it goes to sleep
|
|
|
|
+ uint step = 0;
|
|
|
|
+ while (body_interface.IsActive(sphere_id))
|
|
|
|
+ {
|
|
|
|
+ // Next step
|
|
|
|
+ ++step;
|
|
|
|
+
|
|
|
|
+ // Output current position and velocity of the sphere
|
|
|
|
+ Vec3 position = body_interface.GetCenterOfMassPosition(sphere_id);
|
|
|
|
+ Vec3 velocity = body_interface.GetLinearVelocity(sphere_id);
|
|
|
|
+ cout << "Step " << step << ": Position = (" << position.GetX() << ", " << position.GetY() << ", " << position.GetZ() << "), Velocity = (" << velocity.GetX() << ", " << velocity.GetY() << ", " << velocity.GetZ() << ")" << 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
|
|
|
|
+ physics_system.Update(cDeltaTime, cCollisionSteps, cIntegrationSubSteps, &temp_allocator, &job_system);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Remove the sphere from the physics system. Note that the sphere itself keeps all of its state and can be re-added at any time.
|
|
|
|
+ body_interface.RemoveBody(sphere_id);
|
|
|
|
+
|
|
|
|
+ // Destroy the sphere. After this the sphere ID is no longer valid.
|
|
|
|
+ body_interface.DestroyBody(sphere_id);
|
|
|
|
+
|
|
|
|
+ // Remove and destroy the floor
|
|
|
|
+ body_interface.RemoveBody(floor->GetID());
|
|
|
|
+ body_interface.DestroyBody(floor->GetID());
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|