Browse Source

Added hello world example (#20)

jrouwe 3 years ago
parent
commit
0b73f96ce9
4 changed files with 299 additions and 1 deletions
  1. 9 0
      Build/CMakeLists.txt
  2. 11 0
      HelloWorld/HelloWorld.cmake
  3. 276 0
      HelloWorld/HelloWorld.cpp
  4. 3 1
      README.md

+ 9 - 0
Build/CMakeLists.txt

@@ -91,6 +91,15 @@ endif()
 enable_testing()
 enable_testing()
 add_test(UnitTests UnitTests)
 add_test(UnitTests UnitTests)
 
 
+# Example 'Hello World' application
+include(${PHYSICS_REPO_ROOT}/HelloWorld/HelloWorld.cmake)
+add_executable(HelloWorld ${HELLO_WORLD_SRC_FILES})
+target_include_directories(HelloWorld PUBLIC ${JOLT_PHYSICS_ROOT} ${HELLO_WORLD_ROOT})
+target_link_libraries (HelloWorld LINK_PUBLIC Jolt)
+if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
+	target_link_options(HelloWorld PUBLIC "/SUBSYSTEM:CONSOLE")
+endif()
+
 if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
 if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
 	# Windows only targets
 	# Windows only targets
 	include(${PHYSICS_REPO_ROOT}/TestFramework/TestFramework.cmake)
 	include(${PHYSICS_REPO_ROOT}/TestFramework/TestFramework.cmake)

+ 11 - 0
HelloWorld/HelloWorld.cmake

@@ -0,0 +1,11 @@
+# Root
+set(HELLO_WORLD_ROOT ${PHYSICS_REPO_ROOT}/HelloWorld)
+
+# Source files
+set(HELLO_WORLD_SRC_FILES
+	${HELLO_WORLD_ROOT}/HelloWorld.cpp
+	${HELLO_WORLD_ROOT}/HelloWorld.cmake
+)
+
+# Group source files
+source_group(TREE ${HELLO_WORLD_ROOT} FILES ${HELLO_WORLD_SRC_FILES})

+ 276 - 0
HelloWorld/HelloWorld.cpp

@@ -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;
+}

+ 3 - 1
README.md

@@ -14,6 +14,8 @@ A multi core friendly rigid body physics and collision detection library suitabl
 
 
 For more demos and [videos](https://www.youtube.com/watch?v=pwyCW0yNKMA&list=PLYXVwtOr1CBxbA50jVg2dKUQvHW_5OOom) go to the [Samples](Docs/Samples.md) section.
 For more demos and [videos](https://www.youtube.com/watch?v=pwyCW0yNKMA&list=PLYXVwtOr1CBxbA50jVg2dKUQvHW_5OOom) go to the [Samples](Docs/Samples.md) section.
 
 
+To get started, look at the [HelloWorld](HelloWorld/HelloWorld.cpp) example.
+
 ## Design Considerations
 ## Design Considerations
 
 
 So why create yet another physics engine? First of all, this has been a personal learning project and secondly I wanted to address some issues that I had with existing physics engines:
 So why create yet another physics engine? First of all, this has been a personal learning project and secondly I wanted to address some issues that I had with existing physics engines:
@@ -26,7 +28,7 @@ So why create yet another physics engine? First of all, this has been a personal
 * The simulation runs deterministically, so you could replicate a simulation to a remote client by merely replicating the inputs to the simulation.
 * The simulation runs deterministically, so you could replicate a simulation to a remote client by merely replicating the inputs to the simulation.
 * The simulation of this physics engine tries to simulate behavior of rigid bodies in the real world but makes approximations in the simulation so should mainly be used for games or VR simulations.
 * The simulation of this physics engine tries to simulate behavior of rigid bodies in the real world but makes approximations in the simulation so should mainly be used for games or VR simulations.
 
 
-For more information see the [Architecture](https://jrouwe.github.io/JoltPhysics/) section.
+For more information see the [Architecture and API documentation](https://jrouwe.github.io/JoltPhysics/) section.
 
 
 ## Features
 ## Features