Three.js tutorial – building an atom

Three.js is a powerful open-source library for 3D modeling and animation using pure JavaScript.

This tutorial will cover the basics of working with three.js, and we’ll build an animated atom in the process.

Here’s what our end product will look like:

atomic

For the purposes of keeping this tutorial as simple as possible, we’ll use inline JavaScript in a single HTML file.

To begin, create an HTML file and name it atomic.html.

Before we dive into Three.js, we have a little bit of housekeeping. We’ll need some HTML in which to place our scene, so add an empty body tag. To save us the hassle of downloading the Three.js library, we can reference a copy of it online. The good folks at cdnjs have a service for just that. We’ll also create an inline script tag to place our JS in.

<body></body>
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r58/three.min.js">
</script>
<script>

// *** scene and camera ***

// *** objects ***

// *** renderer ***

// *** animation ***

</script>

First, create a scene in Three.js.

var scene = new THREE.Scene();

Next, create a camera and add it to the scene so that we can see what happens inside of it. We’ll make an aspect ratio and set it equal to the browser width / height. By moving our camera’s z positioning, we can increase or decrease how zoomed in our camera is on the objects we’re about to create.

var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
camera.position.z = 450;
scene.add(camera);

The last bit of housekeeping is to create a canvas renderer, set its size, add it to an HTML element on the page, and then tell it to render the scene and camera. We’ll render the scene into the body element we created earlier. A renderer will generate the image, drawing our models onto that part of the web page.

var renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.render(scene, camera);

Okay, the housekeeping is finished. We’re ready to start playing with objects.

Let’s create our atom nucleus. First, we’ll create a shape using the three.js SphereGeometry, and a Mesh to cover the sphere in. We can then create the nucleus and give it our shape and mesh covering. Lastly, we’ll add the nucleus to our scene.

// Nucleus
//                                      radius
//                                        | width segments
//                                        |    | height segments
//                                        |    |   |
//                                        v    v   v
var bigSphere = new THREE.SphereGeometry(100, 20, 20);
var cover = new THREE.MeshNormalMaterial();
var nucleus = new THREE.Mesh(bigSphere, cover);
scene.add(nucleus);

At this point, you should be able to open your file in a web browser and see the nucleus. Your code should look like this:

<body></body>
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r58/three.min.js">
</script>
<script>

// *** scene and camera ***
var scene = new THREE.Scene();
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
camera.position.z = 450;
scene.add(camera);

// *** objects ***
// Nucleus
//                                      radius
//                                        | width segments
//                                        |    | height segments
//                                        |    |   |
//                                        v    v   v
var bigSphere = new THREE.SphereGeometry(100, 20, 20);
var cover = new THREE.MeshNormalMaterial();
var nucleus = new THREE.Mesh(bigSphere, cover);
scene.add(nucleus);

// *** renderer ***
var renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.render(scene, camera);

// *** animation ***

</script>

Now, its time to add the electrons. Not only are you able to add objects to the scene, you can add objects to other objects, placing them in relative position to those objects.

// Electron 1
var smallSphere = new THREE.SphereGeometry(20, 20, 20);
var electron1 = new THREE.Mesh(smallSphere, cover);
nucleus.add(electron1);
//                        x,y,z
electron1.position.set(-150,150,0);

Do the same with electron #2 and #3.

// Electron 2
var electron2 = new THREE.Mesh(smallSphere, cover);
nucleus.add(electron2);
electron2.position.set(150,150,0);

// Electron 3
var electron3 = new THREE.Mesh(smallSphere, cover);
nucleus.add(electron3);
electron3.position.set(0,0,150);

Now, we’re ready to put the icing on the cake – animation. First, we’ll create a three.js Clock object to allow us to track time. Then, create a simple animate function, and include a timer variable that will use our new clock like a stopwatch. We’ll also need to move the renderer function out of the renderer section and inside our animate method, since we need to render every frame as it changes, rather than a single, one-time rendering.

var clock = new THREE.Clock();

function animate() {
  requestAnimationFrame(animate);

  var t = clock.getElapsedTime();
  renderer.render(scene, camera);
}

animate();

Next, we are ready to add the animation for each of our electrons. A little geometry allows us to create a smooth orbital path. Be sure to place them inside the animate function.

  // orbit from bottom right to top left
  //
  //                            movement speed
  //                               |
  //                               |  orbit distance
  //                               |      |
  //                               v      v
  electron1.position.x = Math.sin(5*t) * -150;
  electron1.position.y = Math.sin(5*t) * 150;
  electron1.position.z = Math.cos(5*t) * 150;

We can do the same thing for our other electrons. Unfortunately, our third electron will smash into the others, and that makes the animation not-so-cool. A timer offset will ensure they orbit in harmony.

// orbit from top right to bottom left
electron2.position.x = Math.cos(5*t) * 150;
electron2.position.y = Math.cos(5*t) * 150;
electron2.position.z = Math.sin(5*t) * 150;

var tOffset = 1.5 + clock.getElapsedTime();

// orbit from the bottom to the top
electron3.position.x = Math.sin(5*tOffset) * 0;
electron3.position.y = Math.sin(5*tOffset) * 150;
electron3.position.z = Math.cos(5*tOffset) * 150;

And… you’re done! Extra credit: See if you can figure out how to wrap your objects in a .jpg image or a hex color code. Hint: You’ll need a light source to be able to see the colors. Post a comment if you have any thoughts or if you’re able to pull off the extra credit. 😉

Here is the finished product – an amazingly short 100 lines of code.

<body></body>
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r58/three.min.js"></script>
<script>

// *** scene and camera ***
// Create a three.js scene
var scene = new THREE.Scene();

// Add a camera so that we can see our 3D objects.
// By moving our camera's z positioning, we can increase or decrease zoom.
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
camera.position.z = 350;
scene.add(camera);

// *** objects ***
// Nucleus
//                                  radius
//                                    | width segments
//                                    |    | height segments
//                                    |    |   |
//                                    v    v   v
var shape = new THREE.SphereGeometry(100, 20, 20);
var cover = new THREE.MeshNormalMaterial();
var nucleus = new THREE.Mesh(shape, cover);
scene.add(nucleus);

// Electron 1
var electronShape = new THREE.SphereGeometry(20, 20, 20);
var electron1 = new THREE.Mesh(electronShape, cover);
nucleus.add(electron1);
// When we add our electron geometry to the nucleus, we can statically position objects. 
// If the objects are dynamically moving, this has no effect.
//                        x, y, z
electron1.position.set(-150,150,0);

// Electron 2
var electron2 = new THREE.Mesh(electronShape, cover);
nucleus.add(electron2);
electron2.position.set(150,150,0);

// Electron 3
var electron3 = new THREE.Mesh(electronShape, cover);
nucleus.add(electron3);
electron3.position.set(0,0,150);

// *** renderer ***
// A canvas renderer will generate the image, drawing our models on the screen.
var renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);

// This places the CanvasRenderer onto the body element in our HTML.
document.body.appendChild(renderer.domElement);

// *** animation ***
// Animate motion using a clock timer.
var clock = new THREE.Clock();

// This function will handle animation of our atom
function animate() {
  requestAnimationFrame(animate);

  // This gives us a running timer for our orbiting electrons.
  var t = clock.getElapsedTime();

  // Display what the camera sees onto the browser screen.
  renderer.render(scene, camera);

  // orbit from bottom right to top left
  //
  //                            movement speed
  //                               |
  //                               |  orbit distance
  //                               |      |
  //                               v      v
  electron1.position.x = Math.sin(5*t) * -150;
  electron1.position.y = Math.sin(5*t) * 150;
  electron1.position.z = Math.cos(5*t) * 150;

  // orbit from top right to bottom left
  electron2.position.x = Math.cos(5*t) * 150;
  electron2.position.y = Math.cos(5*t) * 150;
  electron2.position.z = Math.sin(5*t) * 150;

  // Offset from our timer so the electrons don't smash into each other.
  var tOffset = 1.5 + clock.getElapsedTime();

  // orbit from the bottom to the top
  electron3.position.x = Math.sin(5*tOffset) * 0;
  electron3.position.y = Math.sin(5*tOffset) * 150;
  electron3.position.z = Math.cos(5*tOffset) * 150;
}

// Run the animation.
animate();
</script>

 

Back