-
Notifications
You must be signed in to change notification settings - Fork 19
Integrating with Three.js
By itself Goblin physics doesn't have any kind of graphical output, it only simulates how objects move and react to collisions. To visualize the system you need a way to render the physics world; this example shows how to do so with Three.js. You are not limited to using Three.js, but it is a popular and easy to use WebGL library making it a good choice.
Before starting, make sure you have read the Getting Started part of the Goblin wiki.
To integrate Goblin & Three.js we will need a few things:
- physics world
- renderable scene
- mapping of rigid bodies in Goblin to renderable objects in Three.js
- method that runs the physics simulation, updates the Three.js scene, and performs a render
We'll reuse the setup created in Getting Started, with one dynamic box and a static box.
var world = new Goblin.World( new Goblin.BasicBroadphase(), new Goblin.NarrowPhase(), new Goblin.IterativeSolver() );
var box_shape = new Goblin.BoxShape( 0.5, 0.5, 0.5 ), // dimensions are half width, half height, and half depth, or a box 1x1x1
mass = 5,
dynamic_box = new Goblin.RigidBody( box_shape, mass );
world.addRigidBody( dynamic_box );
var static_box = new Goblin.RigidBody( box_shape, Infinity ); // Mass of Infinity means the box cannot move
static_box.position.set( 0, -5, 0 ); // Set the static box's position 5 units down
world.addRigidBody( static_box );
A scene in Three.js requires a couple things: a scene object containing all of the objects, a camera, a renderer, and a light to make things look nice. The following code setups up a scene to mimic the physics world created above.
var scene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 100 ), // FOV, aspect, near, far
renderer = new THREE.WebGLRenderer({ antialias: true });
var box_geometry = new THREE.BoxGeometry( 1, 1, 1 ), // note that the `BoxGeometry` arguments are the box's full width, height, and depth, while the parameters for `Goblin.BoxShape` are expressed as half sizes
box_material = new THREE.MeshLambertMaterial({ color: 0xaa8833 }),
dynamic_mesh = new THREE.Mesh( box_geometry, box_material ),
static_mesh = new THREE.Mesh( box_geometry, box_material );
// Add both mesh's to the scene
scene.add( dynamic_mesh );
static_mesh.position.set( 0, -5, 0 );
scene.add( static_mesh );
// Setup the camera
camera.position.set( 5, 5, 5 );
camera.lookAt( static_mesh.position );
// Setup the renderer
renderer.setSize( window.innerWidth, window.innerHeight);
document.body.appendChild( renderer.domElement );
// Setup the light
var light = new THREE.PointLight({ color: 0xffffff });
light.position.copy( camera.position );
scene.add( light );
We need a way to keep track of which mesh each rigid body corresponds to, otherwise there is no way to update the meshes' position. Each rigid body has a unique id
property that can be used as a key to point to a mesh.
var body_to_mesh_map = {};
body_to_mesh_map[dynamic_box.id] = dynamic_mesh;
body_to_mesh_map[static_box.id] = static_mesh;
If you are wondering why the static_box -> static_mesh map is included, it doesn't need to be as the static_mesh is already in the correct position. It's included as an example.
The update & render loop will target 60fps and process everything as part of the same loop. It is encouraged that bigger projects separate the physics and render loops for performance and usability purposes.
function updateAndRender() {
window.requestAnimationFrame( updateAndRender );
// run physics simulation
world.step( 1 / 60 );
// update mesh positions / rotations
for ( var i = 0; i < world.rigid_bodies.length; i++ ) {
var body = world.rigid_bodies[i],
mesh = body_to_mesh_map[ body.id ];
// update position
mesh.position.x = body.position.x;
mesh.position.y = body.position.y;
mesh.position.z = body.position.z;
// update rotation
mesh.quaternion._x = body.rotation.x;
mesh.quaternion._y = body.rotation.y;
mesh.quaternion._z = body.rotation.z;
mesh.quaternion._w = body.rotation.w;
}
// render
renderer.render( scene, camera );
}
window.requestAnimationFrame( updateAndRender );
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="goblin.min.js"></script>
<script type="text/javascript" src="three.min.js"></script>
</head>
<body>
<script type="text/javascript">
var world = new Goblin.World( new Goblin.BasicBroadphase(), new Goblin.NarrowPhase(), new Goblin.IterativeSolver() );
var box_shape = new Goblin.BoxShape( 0.5, 0.5, 0.5 ), // dimensions are half width, half height, and half depth, or a box 1x1x1
mass = 5,
dynamic_box = new Goblin.RigidBody( box_shape, mass );
world.addRigidBody( dynamic_box );
var static_box = new Goblin.RigidBody( box_shape, Infinity ); // Mass of Infinity means the box cannot move
static_box.position.set( 0, -5, 0 ); // Set the static box's position 5 units down
world.addRigidBody( static_box );
var scene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 100 ), // FOV, aspect, near, far
renderer = new THREE.WebGLRenderer({ antialias: true });
var box_geometry = new THREE.BoxGeometry( 1, 1, 1 ), // note that the `BoxGeometry` arguments are the box's full width, height, and depth, while the parameters for `Goblin.BoxShape` are expressed as half sizes
box_material = new THREE.MeshLambertMaterial({ color: 0xaa8833 }),
dynamic_mesh = new THREE.Mesh( box_geometry, box_material ),
static_mesh = new THREE.Mesh( box_geometry, box_material );
// Add both mesh's to the scene
scene.add( dynamic_mesh );
static_mesh.position.set( 0, -5, 0 );
scene.add( static_mesh );
// Setup the camera
camera.position.set( 8, 0, 8 );
camera.lookAt( static_mesh.position );
// Setup the renderer
renderer.setSize( window.innerWidth, window.innerHeight);
renderer.setClearColor( 0xffffff );
document.body.appendChild( renderer.domElement );
// Setup the light
var light = new THREE.PointLight({ color: 0xffffff });
light.position.copy( camera.position );
scene.add( light );
var body_to_mesh_map = {};
body_to_mesh_map[dynamic_box.id] = dynamic_mesh;
body_to_mesh_map[static_box.id] = static_mesh;
function updateAndRender() {
window.requestAnimationFrame( updateAndRender );
// run physics simulation
world.step( 1 / 60 );
// update mesh positions / rotations
for ( var i = 0; i < world.rigid_bodies.length; i++ ) {
var body = world.rigid_bodies[i],
mesh = body_to_mesh_map[ body.id ];
// update position
mesh.position.x = body.position.x;
mesh.position.y = body.position.y;
mesh.position.z = body.position.z;
// update rotation
mesh.quaternion._x = body.rotation.x;
mesh.quaternion._y = body.rotation.y;
mesh.quaternion._z = body.rotation.z;
mesh.quaternion._w = body.rotation.w;
}
// render
renderer.render( scene, camera );
}
window.requestAnimationFrame( updateAndRender );
</script>
</body>
</html>