Exploring Collision Detection in Three.js
Introduction
Three.js has emerged as one of the most versatile Javascript libraries for creating interactive 3D graphics on the web. It finds applications in developing games, simulations, and data visualizations, ranging from crafting 3D representations of sensor data (such as LiDAR, radar, and camera input for Advanced Driver-Assistance Systems or ADAS) to Apple’s recent venture into the AR/VR space with the launch of Vision Pro for spatial computing and 3D mapping. Understanding collision detection is now more crucial than ever for creating immersive and responsive experiences. In this blog post, we will delve into the world of collision detection in Three.js, exploring how to set up a Three.js app and the detection logic to infuse realism and interactivity into your 3D scenes.
Understanding Collision Detection
Collision detection is the process of determining when two or more objects intersect in a 3D space. A simple example is “throwing a dart at a dartboard.” In the context of Three.js, this involves checking whether two or more meshes (objects) in your scene are overlapping. Different types of collisions in Three.js include bounding box collisions, sphere collisions, and mesh collisions. In this blog, we will consider an example of bounding box collision, but the concept behind different types of collisions remains the same.
Setting up Three.js with React and Vite.js
For our example of bounding box collision detection, let’s set up a React app with Vite.js and install the Three.js package as a dependency. You can find more information about setting up a React app with Vite on their official documentation site here. After establishing the initial app, we can begin adding the main components in our app.jsx
file for setting up any Three.js project.
1. Create a Scene which will hold all our 3D objects.
const scene = new THREE.Scene();
2. Setting up the Camera.
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
3. Create a renderer, which will render our scene.
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
sceneRef.current.appendChild(renderer.domElement);
4. Adding Geometry, since we are detecting collision we will add two Geometrical Cube (black and Red).
// black cube
const blackCubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const blackCubeMaterial = new THREE.MeshBasicMaterial({ color: "black"});
const blackCube = new THREE.Mesh(blackCubeGeometry, blackCubeMaterial);
blackCube.position.set(0, 0, 0);
scene.add(blackCube)
// Red cube
const redCubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const redCubeMaterial = new THREE.MeshBasicMaterial({ color: "red"});
const redCube = new THREE.Mesh(redCubeGeometry, redCubeMaterial);
redCube.position.set(2, 0, 0); // set position different from black cube
scene.add(redCube)
5. Let’s keep one object stationary (i.e. black cube) and let’s animate red cube, this will be helpful is visualizing collision detection. We will animate red cube, by moving it with up, down, left and right arrows key.
// Adding event listener to keyPressed event and changing position of red cube
document.addEventListener("keydown", onDocumentKeyDown, false);
function onDocumentKeyDown(event) {
var keyCode = event.which;
if (keyCode == 38) { // up
redCube.position.z -= 1;
} else if (keyCode == 40) { // down
redCube.position.z += 1;
} else if (keyCode == 37) { // left
redCube.position.x -= 1;
} else if (keyCode == 39) { // right
redCube.position.x += 1;
}
}
// Adding animations to continously update our scene
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
Adding Collision detections logic
In order to detect collisions between two objects, we’ll use bounding box collisions, which involve employing axis-aligned bounding boxes — a Vector3
point — to approximate the volume occupied by a 3D object.
Let’s add bounding boxes to our geometry objects, the black and red cubes, like this:
// Adding bounding box to our black box
const blackCubeBB = new THREE.Box3(new THREE.Vector3(), new THREE.Vector3());
blackCubeBB.setFromObject(blackCube);
// Adding bounding box to our red box
const redCubeBB = new THREE.Box3(new THREE.Vector3(), new THREE.Vector3());
redCubeBB.setFromObject(redCube);
Now, utilizing the Three.js Box3
class, representing a bounding box in three-dimensional space, we can compare the bounding boxes of two objects to quickly determine if they intersect. To visualize collisions, update the color and reduce the opacity of the black cube. Don't forget to include the checkCollision()
method in the animate()
function, ensuring continuous scene updates when a collision occurs.
function checkCollision() {
if (redCubeBB.intersectsBox(blackCubeBB)) {
blackCube.material.transparent = true;
blackCube.material.opacity = 0.5;
blackCube.material.color = new THREE.Color(Math.random * 0xffffff);
} else {
blackCube.material.opacity = 1;
}
}
// Adding checkCollision() method in our animate() function
function animate() {
checkCollision()
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
This setup will help you visualize and respond to collisions in your Three.js scene. All the code for this collision detection can be found in this gist.
Conclusion
In conclusion, collision detection is a fundamental aspect of creating realistic and interactive 3D environments with Three.js. Whether you opt for bounding box collisions, ray-casting, or custom mesh collisions, choosing the right technique depends on the specific requirements of your project. Experiment with different approaches, optimize performance, and bring your 3D scenes to life with accurate collision detection.
By mastering collision detection in Three.js, you’ll unlock new possibilities for creating engaging and immersive user experiences in the world of web-based 3D graphics. Happy coding!