Skip to content

Commit

Permalink
add a straightforward translation of the HelloWorld.cpp sample app
Browse files Browse the repository at this point in the history
  • Loading branch information
stephengold committed Jun 26, 2024
1 parent 40709f0 commit 6fd970c
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 0 deletions.
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Gradle script to build jolt-jni Maven artifacts and desktop native libraries

plugins {
id 'application' // to build JVM applications
id 'cpp' // to compile C++ code and link native libraries
id 'java-library' // to build JVM libraries
alias(libs.plugins.download) // to retrieve files from URLs
Expand Down Expand Up @@ -167,6 +168,15 @@ tasks.withType(JavaCompile) { // Java compile-time options:
options.headerOutputDirectory = new File('src/main/native/auto')
}

application {
mainClass = 'HelloWorld'
}
tasks.withType(JavaExec).configureEach { // Java runtime options:
classpath sourceSets.test.runtimeClasspath
enableAssertions true
}
run.dependsOn('assemble')

test.dependsOn('assemble')
javadoc.dependsOn('compileTestJava')

Expand Down
197 changes: 197 additions & 0 deletions src/test/java/HelloWorld.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
Copyright (c) 2024 Stephen Gold
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

import com.github.stephengold.joltjni.*;

/**
* A straightforward Java translation of the Jolt Physics "hello world" sample
* application.
* <p>
* Derived from HelloWorld/HelloWorld.cpp by Jorrit Rouwe.
*
* @author Stephen Gold [email protected]
*/
public class HelloWorld {
public static final int cMaxPhysicsJobs = 2048;
public static final int cMaxPhysicsBarriers = 8;

// 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).
private static final int OBJ_LAYER_NON_MOVING = 0;
private static final int OBJ_LAYER_MOVING = 1;
private static final int OBJ_NUM_LAYERS = 2;

// 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.
private static final int BP_LAYER_NON_MOVING = 0;
private static final int BP_LAYER_MOVING = 1;
private static final int BP_NUM_LAYERS = 2;

// Program entry point
public static void main(String[] argv)
{
Utils.loadNativeLibrary();

// Register allocation hook. In this example we'll just let Jolt use malloc / free but you can override these if you want (see Memory.h).
// This needs to be done before any other Jolt function is called.
Jolt.registerDefaultAllocator();

// Install trace and assert callbacks
Jolt.installDefaultTraceCallback();
Jolt.installDefaultAssertCallback();

// Create a factory, this class is responsible for creating instances of classes based on their name or hash and is mainly used for deserialization of saved data.
// It is not directly used in this example but still required.
Jolt.newFactory();

// Register all physics types with the factory and install their collision handlers with the CollisionDispatch class.
// If you have your own custom shape types you probably need to register their handlers with the CollisionDispatch before calling this function.
// If you implement your own default material (PhysicsMaterial::sDefault) make sure to initialize it before this function or else this function will create one for you.
Jolt.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 = new TempAllocatorImpl(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 = new JobSystemThreadPool(cMaxPhysicsJobs, cMaxPhysicsBarriers, Utils.numThreads());

// 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.
final int cMaxBodies = 1024;

// This determines how many mutexes to allocate to protect rigid bodies from concurrent access. Set it to 0 for the default settings.
final int 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.
final int 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.
final int cMaxContactConstraints = 1024;

// Create mapping table from object layer to broadphase layer
// Note: As this is an interface, PhysicsSystem will take a reference to this so this instance needs to stay alive!
MapObj2Bp broad_phase_layer_interface = new MapObj2Bp(OBJ_NUM_LAYERS, BP_NUM_LAYERS)
.add(OBJ_LAYER_NON_MOVING, BP_LAYER_NON_MOVING)
.add(OBJ_LAYER_MOVING, BP_LAYER_MOVING);

// Create class that filters object vs broadphase layers
// Note: As this is an interface, PhysicsSystem will take a reference to this so this instance needs to stay alive!
ObjVsBpFilter object_vs_broadphase_layer_filter = new ObjVsBpFilter(OBJ_NUM_LAYERS, BP_NUM_LAYERS)
.disablePair(OBJ_LAYER_NON_MOVING, BP_LAYER_NON_MOVING);

// Create class that filters object vs object layers
// Note: As this is an interface, PhysicsSystem will take a reference to this so this instance needs to stay alive!
ObjVsObjFilter object_vs_object_layer_filter = new ObjVsObjFilter(OBJ_NUM_LAYERS)
.disablePair(OBJ_LAYER_NON_MOVING, OBJ_LAYER_NON_MOVING);

// Now we can create the actual physics system.
PhysicsSystem physics_system = new PhysicsSystem(cMaxBodies, cNumBodyMutexes, cMaxBodyPairs, cMaxContactConstraints, broad_phase_layer_interface, object_vs_broadphase_layer_filter, object_vs_object_layer_filter);

// 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 = new BoxShapeSettings(new Vec3(100.0f, 1.0f, 100.0f));

// Create the settings for the body itself. Note that here you can also set other properties like the restitution / friction.
BodyCreationSettings floorBodySettings = new BodyCreationSettings(floor_shape_settings, new RVec3(0.0, -1.0, 0.0), new Quat(), EMotionType.Static, OBJ_LAYER_NON_MOVING);

// Create the actual rigid body
Body floor = body_interface.createBody(floorBodySettings); // 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 BodyCreationSettings(new SphereShape(0.5f), new RVec3(0f, 2f, 0f), new Quat(), EMotionType.Dynamic, OBJ_LAYER_MOVING);
Body sphere = body_interface.createBody(sphere_settings);
BodyId sphere_id = sphere.getId();
body_interface.addBody(sphere_id, 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, new 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.
final 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.
physics_system.optimizeBroadPhase();

// Now we're ready to simulate the body, keep simulating until it goes to sleep
int step = 0;
while (body_interface.isActive(sphere_id))
{
// Next step
++step;

// Output current position and velocity of the sphere
RVec3 position = body_interface.getCenterOfMassPosition(sphere_id);
Vec3 velocity = body_interface.getLinearVelocity(sphere_id);
System.out.println("Step " + step + ": Position = " + position + ", Velocity = " + velocity);

// 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).
final int cCollisionSteps = 1;

// Step the world
physics_system.update(cDeltaTime, cCollisionSteps, 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());

// Unregisters all types with the factory and cleans up the default material
Jolt.unregisterTypes();

// Destroy the factory
Jolt.destroyFactory();
}
}

0 comments on commit 6fd970c

Please sign in to comment.