Basic Collisions Detection, RayCasting with Three.Js

An other experiment with three.js, on character controls and raycasting for obstacles detection.

GitHub

Use the arrows on your keyboard to move the character around.

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 !