I thought I would have to use a physics engine (like Cannon.Js or Ammo.Js), but Three.Js on its own is enough to sort us out with collisions, thanks to its Raycaster’s .intersectObjects() method.
This post follows a previous one, about setting up a scene and basic character controls with Three.Js, that maybe you should read if you need to understand a bit more those Character and World classes I’m playing with here.
Using the RayCaster
Even after a little read about raycasting, I’m not quite sure I can define its concept properly… But let’s put it like this : from one origin (the very center position of our character’s Object3D), we’re going to spread vectors in every direction we’re able to move. For each one of those “rays“, we’ll be able to test if it intersects with any given mesh, and if so, to disable any move in that direction.
Collecting the obstacles
So first of all, we need to collect every obstacle in an array : all the meshes that we’re not supposed to cross.
var World = Class.extend({
/* ... */
getObstacles: function () {
'use strict';
return this.obstacles.concat(this.walls);
}
});
Testing and prevent collisions
Our little character motions are based on its .direction vector. Now by testing every possible direction (with the rays), we’ll be able to update that vector for it not to drive us into an obstacle.
var Character = Class.extend({
// Class constructor
init: function (args) {
/* ... */
// Set the character modelisation object
this.mesh = new THREE.Object3D();
/* ... */
// Set the rays : one vector for every potential direction
this.rays = [
new THREE.Vector3(0, 0, 1),
new THREE.Vector3(1, 0, 1),
new THREE.Vector3(1, 0, 0),
new THREE.Vector3(1, 0, -1),
new THREE.Vector3(0, 0, -1),
new THREE.Vector3(-1, 0, -1),
new THREE.Vector3(-1, 0, 0),
new THREE.Vector3(-1, 0, 1)
];
// And the "RayCaster", able to test for intersections
this.caster = new THREE.Raycaster();
},
// Test and avoid collisions
collision: function () {
'use strict';
var collisions, i,
// Maximum distance from the origin before we consider collision
distance = 32,
// Get the obstacles array from our world
obstacles = basicScene.world.getObstacles();
// For each ray
for (i = 0; i < this.rays.length; i += 1) {
// We reset the raycaster to this direction
this.caster.set(this.mesh.position, this.rays[i]);
// Test if we intersect with any obstacle mesh
collisions = this.caster.intersectObjects(obstacles);
// And disable that direction if we do
if (collisions.length > 0 && collisions[0].distance <= distance) {
// Yep, this.rays[i] gives us : 0 => up, 1 => up-left, 2 => left, ...
if ((i === 0 || i === 1 || i === 7) && this.direction.z === 1) {
this.direction.setZ(0);
} else if ((i === 3 || i === 4 || i === 5) && this.direction.z === -1) {
this.direction.setZ(0);
}
if ((i === 1 || i === 2 || i === 3) && this.direction.x === 1) {
this.direction.setX(0);
} else if ((i === 5 || i === 6 || i === 7) && this.direction.x === -1) {
this.direction.setX(0);
}
}
}
},
// Process the character motions
motion: function () {
'use strict';
// Update the directions if we intersect with an obstacle
this.collision();
// If we're not static
if (this.direction.x !== 0 || this.direction.z !== 0) {
// Rotate the character
this.rotate();
// Move the character
this.move();
return true;
}
}
/* ... */
});
And that’s it. So of course this isn’t perfect : some meshes are not taken into account (the hands and feet for instance), and it’s possible for some obstacles to get through our rays (try to walk just a little bit too close beside the cube on the demo). One solution would be to add some more rays. But still, this is meant to stay basic, so I’ll keep it like this so far.
See ya folks !