3D SPHERE
COLLISION EVENTS

Detect when two 3D spheres collide in the browser with Three.js — bounce them off each other and the walls, and flash a new random color on every impact.

Three.js 3D Sphere Collision Events

LIVE DEMO

The two spheres bouncing and colliding behind this page are the live demo. Click and drag with the mouse to orbit the camera around them. Watch them change color each time they collide. Scroll to keep reading — on mobile just swipe to scroll.

Introduction

Collision detection is the foundation of almost every game and physics effect — knowing when two objects touch so you can react. For spheres it is beautifully simple: because a sphere is just a center point and a radius, you can detect a collision with a single distance check.

This free tutorial builds two spheres that drift around a lit ground plane. Each frame you move them along a direction vector, check the distance between them, and when they touch you reverse their directions and flash a random color. You will also bounce them off the edges of the plane like invisible walls. No physics engine required.

Frequently Asked Questions

How do you detect collisions between spheres in Three.js?

For spheres, the simplest and fastest method is a distance check. Each frame, measure the distance between the two sphere centers with ball1.position.distanceTo(ball2.position). If that distance is less than or equal to the sum of their radii, the spheres are touching — a collision. This works because a sphere is defined entirely by its center and radius.

How do you make objects bounce after a collision in Three.js?

Store each object's movement as a direction vector and add it to the position every frame. When a collision is detected, reverse the relevant components of the direction vectors (for example negate x and z) so the objects move apart. Adding a small random nudge to the reversed direction keeps the motion lively and prevents the objects from getting stuck together.

How do you keep objects inside a boundary in Three.js?

Check each object's position against the edges of your ground plane every frame. If it passes an edge threshold, flip the direction component pointing outward (using -Math.sign of the position) and re-normalize the direction vector. This bounces the object back into the play area like an invisible wall.

Should I use a physics engine for collisions in Three.js?

Not always. For simple shapes like spheres and boxes, manual distance and bounding-box checks are fast and easy. For realistic mass, friction, stacking, and complex shapes, use a physics engine such as Cannon-es, Rapier, or Ammo.js and let it handle collisions while Three.js renders the result. Start simple and add a physics engine only when you need real physics.

Is this Three.js collision tutorial free?

Yes. This is a completely free tutorial. The full source code is on the page, you can copy it, and the two colliding spheres rotating behind this page are the live demo. No sign-in or membership is required.

How The Collision Detection Works

  1. 1Give each sphere a direction vector. Store a small THREE.Vector3 velocity for each ball and add it to the ball's position every frame so it drifts.
  2. 2Measure the distance between centers. Each frame call ball1.position.distanceTo(ball2.position). If it is ≤ the sum of the two radii, they are touching.
  3. 3Bounce on collision. Reverse the x and z of each direction vector (plus a small random nudge) so the balls separate instead of overlapping.
  4. 4Flash a color. On impact, pick a random color from an array and set each ball's material.color so the collision is visible.
  5. 5Bounce off the walls. Compare each ball's position to the edges of the ground plane; if it passes the edge, flip the outward direction component and re-normalize.
  6. 6Render in a loop. Run all of the above inside requestAnimationFrame and call renderer.render(scene, camera) each frame.

The Full Code

Paste this into a file named index.html and open it in any modern browser.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Three.js Sphere Collision</title>
  <style> body { margin: 0; background: #000; } </style>
  <script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.160/build/three.module.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160/examples/jsm/"
    }
  }
  </script>
</head>
<body>
<script type="module">
  import * as THREE from 'three';
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(
    75, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.set(0, 25, 50);

  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.shadowMap.enabled = true;
  document.body.appendChild(renderer.domElement);

  const controls = new OrbitControls(camera, renderer.domElement);
  controls.autoRotate = true;

  // Lights
  scene.add(new THREE.AmbientLight(0xffffff, 0.6));
  const spot = new THREE.SpotLight(0xffffff, 10);
  spot.position.set(0, 60, 0);
  spot.castShadow = true;
  scene.add(spot);

  // Ground plane
  const plane = new THREE.Mesh(
    new THREE.PlaneGeometry(100, 100),
    new THREE.MeshPhongMaterial({ color: 0x1e40af, side: THREE.DoubleSide })
  );
  plane.rotation.x = Math.PI / 2;
  plane.position.y = -3;
  plane.receiveShadow = true;
  scene.add(plane);
  scene.add(new THREE.GridHelper(100, 30));

  // Two spheres
  const RADIUS = 5;
  const geo = new THREE.SphereGeometry(RADIUS, 32, 32);
  const ball1 = new THREE.Mesh(geo, new THREE.MeshPhongMaterial({ color: 'white' }));
  const ball2 = new THREE.Mesh(geo, new THREE.MeshPhongMaterial({ color: 'white' }));
  ball1.position.set(-15, 1, 0);
  ball2.position.set(15, 1, 0);
  ball1.castShadow = ball2.castShadow = true;
  scene.add(ball1, ball2);

  // Direction vectors (velocity)
  const speed = 0.4;
  const dir1 = new THREE.Vector3(speed, 0, speed * 0.6);
  const dir2 = new THREE.Vector3(-speed, 0, -speed * 0.4);

  const colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffffff, 0x800080, 0xffa500];
  const rand = () => Math.random() * 2 - 1;
  const half = 50, edge = RADIUS + 2;

  function animate() {
    requestAnimationFrame(animate);
    controls.update();

    // Move
    ball1.position.add(dir1);
    ball2.position.add(dir2);

    // Sphere-to-sphere collision: distance check
    if (ball1.position.distanceTo(ball2.position) <= RADIUS * 2) {
      dir1.set(-dir1.x + rand(), 0, -dir1.z + rand());
      dir2.set(-dir2.x + rand(), 0, -dir2.z + rand());
      ball1.material.color.set(colors[Math.floor(Math.random() * colors.length)]);
      ball2.material.color.set(colors[Math.floor(Math.random() * colors.length)]);
    }

    // Bounce off the plane edges (invisible walls)
    for (const [ball, dir] of [[ball1, dir1], [ball2, dir2]]) {
      if (Math.abs(ball.position.x) > half - edge) { dir.x = -Math.sign(ball.position.x) * speed; }
      if (Math.abs(ball.position.z) > half - edge) { dir.z = -Math.sign(ball.position.z) * speed; }
    }

    renderer.render(scene, camera);
  }
  animate();

  window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  });
</script>
</body>
</html>

What To Try Next

SEE 3D AND 2D BROWSER GAMES IN ACTION

Built with Three.js and JavaScript — free, no download, plays right in your browser.

More Tutorials