This article is part of a web dev tech series from Microsoft. Thank you for supporting the partners who make SitePoint possible.
Today, I’d like to share with you the basics of collisions , physics and bounding boxes by playing with the WebGL babylon.js engine and a physics engine companion named oimo.js.
Here’s the demo we’re going to build together: babylon.js Espilit Physics demo with Oimo.js
You can launch it in a WebGL compatible browser —like IE11, Firefox, Chrome, Opera Safari 8, or Project Spartan in Windows 10 Technical Preview —then, move inside the scene like in an FPS game. Press the “s” key to launch some spheres/balls and the “b” key to launch some boxes. Using your mouse, you can click on one of the spheres or boxes to apply some impulse force on it also.
Understanding collisions
Looking at the Wikipedia Collision detection definition, we can read that:
“Collision detection typically refers to the computational problem of detecting the intersection of two or more objects. While the topic is most often associated with its use in video games and other physical simulations, it also has applications in robotics. In addition to determining whether two objects have collided, collision detection systems may also calculate time of impact (TOI), and report a contact manifold (the set of intersecting points). [1] Collision response deals with simulating what happens when a collision is detected (see physics engine, ragdoll physics). Solving collision detection problems requires extensive use of concepts from linear algebra and computational geometry.”
Let’s now unpack that definition into a cool 3D scene that will act as our starting base for this tutorial.
You can move in this great museum as you would in the real world. You won’t fall through the floor, walk through walls, or fly. We’re simulating gravity. All of that seems pretty obvious but requires a bunch of computation to simulate that in a 3D virtual world. The first question we need to resolve when we think about collisions detection is how complex it should be? Indeed, testing if 2 complex meshes are colliding could cost a lot of CPU, even more with a JavaScript engine where it’s complex to offload that on something other than the UI thread.
To better understand how we’re managing this complexity, navigate into the Espilit museum near this desk:
You’re blocked by the table even if there seem to be some space available on the right. Is it a bug in our collision algorithm? No, it’s not (babylon.js is free of bugs! ;-)). It’s because Michel Rousseau, the 3D artist who has built this scene, has done this by choice. To simplify the collision detection, he has used a specific collider.
What’s a collider?
Rather than testing the collisions against the complete detailed meshes, you can put them into simple invisible geometries. Those colliders will act as the mesh representation and will be used by the collision engine instead. Most of the time, you won’t see the differences but it will allow us to use much less CPU as the math behind that is much simpler to compute.
Every engine supports at least 2 types of colliders: the bounding box and the bounding sphere. You’ll better understand by looking at this picture:
Extracted from: Computer Visualization, Ray Tracing, Video Games, Replacement of Bounding Boxes
This beautiful yellow deck is the mesh to be displayed. Rather than testing the collisions against each of its faces, we can try to insert it into the best bounding geometry. In this case, a box seems a better choice than a sphere to act as the mesh impostor. But the choice really depends on the mesh itself.
Let’s go back to the Espilit scene and display the invisible bounding element in a semitransparent red color:
You can now understand why you can’t move by the right side of the desk. It’s because you’re colliding (well, the babylon.js camera is colliding) with this box. If you’d like to do so, simply change its size by lowering the width to perfectly fit the width of the desk.
Note: if you’d like to start learning babylon.js, you can follow our free training course at Microsoft Virtual Academy (MVA). For instance, you can jump directly to the “Introduction to WebGL 3D with HTML5 and Babylon.js – Using Babylon.js for Beginners” where we cover this collision part of Babylon.js. You can also have a look at the code inside our interactive playground tool: Babylon.js playground – Collisions sample.
Based on the complexity of the collision or physics engine, there are other types of colliders available: the capsule and the mesh for instance.
Extracted from: Getting Started with Unity – Colliders & UnityScript
Capsule is useful for humans or humanoids as it better fits our body than a box or a sphere. Mesh is almost never the complete mesh itself—rather it’s a simplified version of the original mesh you’re targeting—but is still much more precise than a box, a sphere or a capsule.
Loading the starting scene
To load our Espilit scene, you have various options:
Option 1 :
Download it from our GitHub repository, and then follow the Introduction to WebGL 3D with HTML5 and Babylon.js – Loading Assets module of our MVA course to learn how to load a .babylon scene. Basically, you need to host the assets and the Babylon.js engine into a web server and set the proper MIME types for the .babylon extension.
Option 2 :
Download this premade Visual Studio solution (.zip file).
Note: if you are unfamiliar with Visual Studio, take a look at this article: Web developers, Visual Studio could be a great free tool to develop with… Please note also that the Pro version is now free for a lot of different scenarios. It’s named Visual Studio 2013 Community Edition.
Of course, you can still follow this tutorial if you don’t want to use Visual Studio. Here’s the code to load our scene. Remember that while most browsers support WebGL now – you should test for Internet Explorer even on your Mac.
// <reference path="/scripts/babylon.js" />
var engine;
var canvas;
var scene;
document.addEventListener("DOMContentLoaded", startGame, false);
function startGame() {
if (BABYLON.Engine.isSupported()) {
canvas = document.getElementById("renderCanvas");
engine = new BABYLON.Engine(canvas, true);
BABYLON.SceneLoader.Load("Espilit/", "Espilit.babylon", engine, function (loadedScene) {
scene = loadedScene;
// Wait for textures and shaders to be ready
scene.executeWhenReady(function () {
// Attach camera to canvas inputs
scene.activeCamera.attachControl(canvas);
// Once the scene is loaded, just register a render loop to render it
engine.runRenderLoop(function () {
scene.render();
});
});
}, function (progress) {
// To do: give progress feedback to user
});
}
}
Using this material, you will only benefit from the embedded collision engine of Babylon.js. Indeed, we’re making a difference between our collision engine and a physics engine. The collision engine is mostly dedicated to the camera interacting with the scene. You can enable gravity or not on the camera, you can enable the checkCollision
option on the camera and on the various meshes. The collision engine can also help you to know if two meshes are colliding. But that’s all (this is already a lot in fact!). The collision engine won’t generate actions, force or impulse after two Babylon.js objects are colliding. You need a physics engine for that to bring life to the objects.
The way we’ve been integrating physics in Babylon.js is via a plug-in mechanism. You can read more about that here: Adding your own physics engine plugin to babylon.js. We’re supporting two open-source physics engine: cannon.js and oimo.js. Oimo is now the preferred default physics engine.
If you’ve chosen “option 1” to load the scene, you then need to download Oimo.js from our GitHub. It’s a slightly updated version we’ve made to better support Babylon.js. If you’ve chosen “option 2”, it’s already referenced and available in the VS solution under the scripts
folder.
Enabling physics support in the scene to transform colliders into “physics impostors”
The first thing to do is to enable physics on the scene. For that, please add this line of code:
scene.enablePhysics(new BABYLON.Vector3(0, -10, 0), new BABYLON.OimoJSPlugin());
You’re setting up the gravity level (-10 on the Y axis in this sample code, which is more or less like what we have on Earth) and the physics engine you’d like to use. We’ll use Oimo.js but the commented line shows how to use cannon.js.
Now, we need to iterate through all non-visible colliders used by the collision engine and activate physics properties on it. For that, you simply need to find all meshes where checkCollisions
is set to true but not visible in the scene:
for (var i = 1; i < scene.meshes.length; i++) {
if (scene.meshes[i].checkCollisions && scene.meshes[i].isVisible === false) {
scene.meshes[i].setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, { mass: 0,
friction: 0.5, restitution: 0.7 });
meshesColliderList.push(scene.meshes[i]);
}
}
Please declare the meshesColliderList also:
var meshesColliderList = [];
And we’re done! We’re ready to throw some objects in our scene and put a lot of mess in this beautiful but way-to-calm museum.
Creating spheres and boxes with physics states
We’re now going to add some spheres (with an Amiga texture) and some boxes (with a wood texture) to the scene. This meshes will have physics state set. For instance, this means that they will bounce on the floor if you launch them in the air, bounce between them after a collision has been detected and so on. The physics engine needs to know which kind of impostor you’d like to use for the mesh (plane, sphere or box today), the mass and friction properties.
If you’ve chosen “option 1“, you can download the two textures here: physicsassets.zip
Add this code to your project:
function CreateMaterials() {
materialAmiga = new BABYLON.StandardMaterial("amiga", scene);
materialAmiga.diffuseTexture = new BABYLON.Texture("assets/amiga.jpg", scene);
materialAmiga.emissiveColor = new BABYLON.Color3(0.5, 0.5, 0.5);
materialAmiga.diffuseTexture.uScale = 5;
materialAmiga.diffuseTexture.vScale = 5;
materialWood = new BABYLON.StandardMaterial("wood", scene);
materialWood.diffuseTexture = new BABYLON.Texture("assets/wood.jpg", scene);
materialWood.emissiveColor = new BABYLON.Color3(0.5, 0.5, 0.5);
}
function addListeners() {
window.addEventListener("keydown", function (evt) {
// s for sphere
if (evt.keyCode == 83) {
for (var index = 0; index < 25; index++) {
var sphere = BABYLON.Mesh.CreateSphere("Sphere0", 10, 0.5, scene);
sphere.material = materialAmiga;
sphere.position = new BABYLON.Vector3(0 + index / 10, 3, 5 + index / 10);
sphere.setPhysicsState(BABYLON.PhysicsEngine.SphereImpostor, { mass: 1 });
}
}
// b for box
if (evt.keyCode == 66) {
for (var index = 0; index < 10; index++) {
var box0 = BABYLON.Mesh.CreateBox("Box0", 0.5, scene);
box0.position = new BABYLON.Vector3(0 + index / 5, 3, 5 + index / 5);
box0.material = materialWood;
box0.setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, { mass: 4 });
}
}
});
}
You can see that boxes are heavier than the spheres by 4.
Note: if you need to understand how material works in babylon.js, watch to this module: Introduction to WebGL 3D with HTML5 and Babylon.js – Understanding Materials and Inputs or play with our dedicated Playground sample: Babylon.js Playground – Materials sample
Add these two lines of code after the scene.enablePhysics
line:
CreateMaterials();
addListeners();
And launch the web project. Navigate to the center of the museum and press rather the “s” or “b” keys. You’ll obtain this fun result:
Adding picking support to click on meshes
Let’s add another cool feature: the ability to click on one of the objects to throw them away. For that, you need to send a ray from the 2D coordinates of the mouse inside the 3D scene, check if this ray touches one of the interesting meshes and if so, apply an impulse force on it to try to move it.
Note: to understand how picking works, please view this MVA module: Introduction to WebGL 3D with HTML5 and Babylon.js – Advanced Features or play with our online sample: Babylon.js Playground – Picking sample.
Add this code into the addListeners()
function:
canvas.addEventListener("mousedown", function (evt) {
var pickResult = scene.pick(evt.clientX, evt.clientY, function (mesh) {
if (mesh.name.indexOf("Sphere0") !== -1 || mesh.name.indexOf("Box0") !== -1) {
return true;
}
return false;
});
if (pickResult.hit) {
var dir = pickResult.pickedPoint.subtract(scene.activeCamera.position);
dir.normalize();
pickResult.pickedMesh.applyImpulse(dir.scale(1), pickResult.pickedPoint);
}
});
Launch your code in your favorite browser. You can now click on your physic meshes to play with them.
Displaying the bounding boxes to better understand the whole story
Finally, we’re going to create a debug scene to let you display/hide the colliders and activate/deactivate the physics properties on them.
We’re going to inject the UI into this div:
<div id="lcContainer">
<ul id="listColliders">
</ul>
</div>
And we’ll use this function to handle the UI:
function CreateCollidersHTMLList() {
var listColliders = document.getElementById("listColliders");
for (var j = 0; j < meshesColliderList.length; j++) {
var newLi = document.createElement("li");
var chkVisibility = document.createElement('input');
chkVisibility.type = "checkbox";
chkVisibility.name = meshesColliderList[j].name;
chkVisibility.id = "colvis" + j;
var chkPhysics = document.createElement('input');
chkPhysics.type = "checkbox";
chkPhysics.name = meshesColliderList[j].name;
chkPhysics.id = "colphysx" + j;
(function (j) {
chkVisibility.addEventListener(
"click",
function (event) {
onChangeVisibility(j, event);
},
false
);
chkPhysics.addEventListener(
"click",
function (event) {
onChangePhysics(j, event);
},
false
);
})(j)
newLi.textContent = meshesColliderList[j].name + " visibility/physx ";
newLi.appendChild(chkVisibility);
newLi.appendChild(chkPhysics);
listColliders.appendChild(newLi);
}
function onChangeVisibility(id, event) {
if (!meshesColliderList[id].isVisible) {
meshesColliderList[id].isVisible = true;
meshesColliderList[id].material.alpha = 0.75;
meshesColliderList[id].material.ambientColor.r = 1;
}
else {
meshesColliderList[id].isVisible = false;
}
}
function onChangePhysics(id, event) {
if (!meshesColliderList[id].checkCollisions) {
meshesColliderList[id].checkCollisions = true;
meshesColliderList[id].setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, { mass: 0,
friction: 0.5, restitution: 0.7 });
}
else {
meshesColliderList[id].checkCollisions = false;
meshesColliderList[id].setPhysicsState(BABYLON.PhysicsEngine.NoImpostor);
}
}
}
I know, it generates a very ugly UI but I was too lazy to spend more time on it. Feel free to improve it! :-P
Call this new function and launch the web project. Now, for instance, display the colliders 12 & 17 :
You can also, with the second checkbox, enable/disable the physics properties. For instance if you disable the physics properties on collider 12 and launch the spheres, they will now go through this wall! This is shown in the following screenshot as the sphere surrounded by the red square:
Conclusion
You can play with this debugging sample directly in your browser here: babylon.js Espilit Physics debug demo.
Please also have a look at this awesome demo built by Samuel Girardin that also use Oimo.js on some funny characters:
Hope you’ve enjoyed this tutorial! Feel free to ping me on Twitter to comment on it.
This article is part of a web dev tech series from Microsoft. We’re excited to share Project Spartan and its new rendering engine with you. Get free virtual machines or test remotely on your Mac, iOS, Android, or Windows device with modern.IE.