WARNING!!! This is a pre-alpha build of the library and highly subject to change. DO NOT use this for anything yet. It is under very high rate of change development and will likely break without notice
For contributions, please read the 🪧 Development Guide.
There are 4 phases planned for this library. We are currently in: Phase 0 (Pre-Alpha)
(See the Project Outline for more details)
The Jolt Physics Engine a highly capable, real-time Physics Engine designed for games and VR applications built for use in Horizon Forbidden West.
react-three/jolt
(or r3/jolt
) is a wrapper library designed to slot seamlessly into a react-three/fiber
pipeline.
The core library is written in C++ with active support in many platforms (Windows/Mac/Linux/Android/iOS) and engines such as Gadot as well as a dedicated WASM/JS Library. The WASM version also has many different options for building worth exploring.
The goal of this library is to allow quick and easy access to a world-class physics simulation without some of the complexity or pitfalls. Jolt is very powerful and flexible, sometimes at the cost of usability.
<Physics></Physics>
Jolt works like many other physics libraries where a <Physics>
component acts as the entrance point for the world.
Just like everything in R3F must be within the <Canvas>
Everything in R3/Jolt must be inside a <Physics>
<Physics gravity={10} debug={doDebug} paused={isPaused} defaultBodySettings={defaultBodySettings} >
</Physics>
One way it is different however is much of the logic actually lies in a PhysicsSystem class that has it’s own api so you can make changes to the entire system without directly adjusting the component.
<Physics>
takes a number of properties that will automatically be passed to the PhysicsSystem
Gravity can be a single number 20: that automatically gets turned into [0,-20, 0] and applied to the simulation. You can also pass a vector directly if you have a special gravity you want. 0 also works.
Debug triggers debugging on a global level. Any class or object that has debugging possible will start debugging. For things like RigidBody ‘s this will draw the shape. Some classes will begin logging more etc. One thing to note is Raycasters will not start or stop debugging based on this flag as they can be used for thousands of times per second and you may not want the screen filled up with lasers firing everywhere.
Paused is a simple flag that will block the update call in the core physics loop. This essentially stops the system including updating threeJS objects immediately. However, this IS NOT the rendering loop, so objects will continue to render, do shader effects, etc. The physics system will still operate and respond to requests, and the raycaster will still work.
This is a handy helper to pass to the BodySystem
which will pass properties when creating bodies. This lets you make higher level changes without needing to write your own components or interacting with the Jolt interface.
NOTE: most of the time we rewrite the names of properties for you, however this injects directly into the the BodySettings
pipeline so properties must be in the correct Jolt semanitcs. Normally that means at least starting with the letter 'm'.
// body settings so shapes bounce
const defaultBodySettings = { mRestitution: 0.5 });
<Physics defaultBodySettings={defaultBodySettings}>
<RigidBody position={[0, 20, 3]}>
<mesh>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial color="hotpink" />
</mesh>
</RigidBody>
</Physics>
// TODO: all the rapier like props
<RigidBody></RigidBody>
This is the most common component you’ll use. RigidBody wraps threeJS objects and automatically creates Jolt Shapes and Rigid Bodies. This will also automatically create a BodyState with the R3/Jolt BodySystem
<RigidBody position={[0, 1, 0]}>
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="green" />
</mesh>
</RigidBody>
You can however set the shape property directly with shape=’box’
etc.
If the system can’t determine the shape, it will unwrap the mesh and build a convex shape (giftwrapping) of the mesh. As of right now it’s fairly basic convex generation.
To get a Trimesh shape you must specify trimesh. We do this because trimesh is actually the most difficult shape and most likely to not work correctly. For example Jolt docs say Dynamic & Kinematic Trimeshes cannot collide with each other or heightmaps, doing so will throw errors.
To get a compound shape simply add multiple meshes and position them as if they are inside a group ( in local space).
<RigidBody position={[-2, 15, 4]}>
<mesh>
<cylinderGeometry args={[0.5, 0.5, 3, 32]} />
<meshStandardMaterial color="yellow" />
</mesh>
<mesh position={[0, -2, 0]}>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial color="yellow" />
</mesh>
<mesh position={[0, 2, 0]}>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial color="yellow" />
</mesh>
</RigidBody>
You can have meshes or items within the rigid body but not generate shapes by adding the ignore attribute.
(NOTE: this may change in the future as we look to add a <shape>
component to generate the shape but not display)
Many of the properties and options can be set at the component level. // example of rb props
-
type (Dynamic, static, kinetic, rig)
-
shape
-
position Setting position or rotation will teleport the body immediately
-
rotation
-
debug Debug can be set on a per-object basis and wont trigger the entire system to go into debug. However, changing this prop wont disable debug at the global level.
-
Events onContactAdded, onContactRemoved, onContactPersisted future
-
isSensor
-
onSleep
-
onWake
However some will need to be set on the BodyState directly. To get the BodyState for a body, you can either access it via the ref property, or request the body from the BodySystem.
As a ref:
const myBody = useRef<BodyState>();
useEffect(() => {
myBody.current.applyImpulse([2,3,1]);
},[]);
return (
<>
<RigidBody ref={myBody}></RigidBody>
</>
);
// accessing BodyState with bodySystem
const bodyHandle = props.bodyHandle;
const { bodySystem } = useJolt();
useEffect(() => {
bodySystem.dynamicBodies.forEach((body: BodyState) => body.applyImpulse([0,2,1]));
const myBody = bodySystem.getBody(bodyHandle);
if(myBody) myBody.position = new THREE.Vector3(0, 3, 2);
},[]);
// props and methods on the BodyState
Instancing lets you display many of the same meshes at a time with a single draw call. R3/Jolt supports that by creating bodies for each instance and automatically updating their positions. R3/Jolt works a little different than r3/rapier in how we handle instances. We replace the normal component with Anything inside is treated like RigidBody and creates the shape and mesh for the subsequent instances. The only other prop we need is the count of instances. Once setup, R3/Jolt automatically generates an array of all the BodyStates for each instance and this is what you’ll get on the ref // instances example
This may seem odd. But this actually gives you significantly more control over the instance than simply forcing the position and rotation. Or only passing them at creation.
There is some concern if you don’t specify a position, as it will try to put all the bodies in roughly the same position.(We actually add a slight jitter) and the physics system will push them out with force as the next item comes into position. (This may be what you want with a shape fountain)
In the future we may allow passing an initial position array. We also plan to allow passing an instance matrix directly. We know that when you use instancing with a GLTF that it automatically creates the matrix containing all position and color data automatically. It’s annoying to convert this to an array and would be easier to just pass directly. We’re working on it..
R3/Jolt actually has a pretty robust raycaster. It works similarly to ThreeJS’s raycaster with some additional options and built in debugging. At the moment to best see the raycaster see the raycasting demo. Each cast shows some of the features.
Raycasting also has a multicaster. Which allows you to setup multiple ray origins and/or destinations to cast many rays with a single request. This is common with sweeps and detections
Shapecasting is coming soon.
The heightfield component generates a plane and automatically creates a heightfield mesh and rigidbody. The heightfield can be an image URL, image, or texture // testing needed for other types And can be updated after creation // should be fine, test.
Heightfields are heavy, so don’t make them too big or use too many. It’s best to update and use other static bodies together to create the effect you want.
Be warry of contact events on heightfields. Honestly, best not to even use them as they fire for every single triangle in the mesh and can easily confuse the contact listener. Meaning you’ll never correctly detect when the item stops contacting.
It’s unclear if this is true from the perspective of the other shape, but know contact listening heightfields is currently buggy.
If you want to interface with any of R3/Jolts active systems or access the Jolt system directly use this hook anywhere inside the context.
This is the core physics system. All other systems link here and you can get to them through this system if you really needed. This also holds the active interfaces, and most of the api to manage the simulation. //TODO make a physicsSystem API docs
This is a shortener for physicsSystem.bodySystem. Many times you dont need to mess with the physicsSystem but the bodies. Doing // code example Makes access easier // TODO: api on bodySystem
This is the debug state of the system and is updated reactively. // TODO test if this is true, also test if it can be set directly as a setter
This is the paused state of the system and is updated reactively. // TODO test if this is true, also test if it can be set directly as a setter
This is the core WASM module. Be VERY CAREFUL when messing with this directly. R3/Jolt can’t save you if you mess it up.
This is the running Jolt Interface for this simulation. Be VERY CAREFUL when messing with this directly.
This is the running step call. Currently manual stepping isn’t setup, but when it is calling this function will progress the simulation a single step.
We have working four wheel and two wheel controllers. They need some minor attention before being released.
The controller actually works, but needs some attention
This goes hand in hand with the character controller. It’s pretty advanced but may need a refactor. Heightfield tools This will eventually allow you to generate heightfields in realtime from noise algorithms.
There are 4 phases planned for this library. We are currently in Phase 0 (Pre-Alpha)
This phase is to stabilize building, deployment, and planning among any interested devs while also providing the minimum level of usability and performance.
R3/Jolt is heavily inspired by sibling libraries R3/Rapier and useCannon/Cannon. While Jolt itself has many more features and capabilities, we should focus first on being comparable with these other libraries. We should also have plenty of documentation as well as examples (both functional and stylistic)
Jolt is a highly capable, powerful library. There are many features and usages we will want to include and provide. Pulleys, Buoyancy, etc.
Jolt provides a full skeleton animation system to control rigid body models. The most common use would be ragdoll/character models.