Web Development

Getting Started with Three.js

April 20th, 2017 | By Daniela Grams | 9 min read

Three.js: Illumination Confirmed!

3D graphics in the browser have been a hot topic since they were first introduced. However, creating apps in pure WebGL takes ages.

This is why libraries have appeared, Three.js being one of the most popular. It is a nice and simple layer on top of WebGL that provides its users with plenty of well-written documentation.

Three.js may look complex at first, but it takes even more coding to write the same program in pure WebGL, mostly to write a rendering engine. With Three.js the heavy lifting is done without sacrificing much flexibility.

Three.js also does an excellent job of abstracting many of the details of WebGL, but it also gives you very clean, low-level access to all the rendering (projection, animation) capabilities.

If you want to do 3D models, textures, and render scenes, that look more realistic, Three.js is the way to go.

Countless well-documented examples on the website demonstrate this. You can draw inspiration from these creative demos and create your games based on them.

Many 3D WebGL projects have spun off and are propelling Three.js engine adoption.

We will create a scene where the user clicks away colorful blocks in an endless jumble while keeping track of the number of times the cubes were clicked. Find a working example.

Setting up Three.js

To install three.js you can click on the download button in JavaScript 3D library.

Once the zip has finished downloading, open it up and go to the build folder. Inside, you’ll find a file called three.min.js that you should copy into your local development directory. From here, you can include the library in your HTML file.

<script src="js/three.min.js"></script>


For this game you’ll also need the following files:

  • examples/js/libs/stats.min.js

  • examples/js/renderers/CanvasRenderer.js

  • examples/js/renderers/Projector.js

  • examples/js/libs/tween.min.js


Remember to add them to the HTML file, as shown in the three.min.js file.

Programming our game

To demonstrate how the game was built, we will start with our global variables, followed by the functions used to set up our cubes, header, scene, and score, followed by event listeners, and finally how they are all used to build the game.

Global variables

We will need to use the following variables.

var numCubes = 10;
var container, stats;
var camera, scene, renderer;
var textureLoader = new THREE.TextureLoader();
var raycaster;
var mouse;
var cubeTexture = textureLoader.load('http://i.imgur.com/U1DnhNv.png');
var isMouseDown = false,
	onMouseDownPosition, onMouseDownTheta = 45,
	onMouseDownPhi = 60,
	phi = 60,
	theta = 45,
	radious = 1600,
	count = 0;

We start by defining the number of cubes we want. From there, we have the container itself and stats.

The container will have all the cubes, titles, and stats. It represents the whole game scene. Stats will be used to monitor the game's performance (FPS - frames per second, and MS - milliseconds to render a frame).

We create our camera, scene, renderer, raycaster, mouse, and cube texture variables. Then we have some variables that will be used for camera rotation, a count variable used to count the number of times the cubes were clicked.

Creating a Cube

The following function demonstrates how to create cubes through BoxGeometry. To use its constructor we have to at least define the width, height, and depth.

function createGeometry() {
	var geometry = new THREE.BoxGeometry(100, 100, 100);
	var object = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
		color: Math.random() * 0xffffff,
		opacity: .75,
		map: cubeTexture
	}));
	object.position.x = Math.random() * 800 - 500;
	object.position.y = Math.random() * 800 - 600;
	object.position.z = Math.random() * 800 - 700;
	object.scale.x = Math.random() * 2 + 1;
	object.scale.y = Math.random() * 2 + 1;
	object.scale.z = Math.random() * 2 + 1;
	object.rotation.x = Math.random() * 2 * Math.PI;
	object.rotation.y = Math.random() * 2 * Math.PI;
	object.rotation.z = Math.random() * 2 * Math.PI;
	return object;
}

Here, boxes or cubes are created, and have a color, opacity, and texture applied, followed by a random position, scale, and rotation.

This means that although all the cubes have the same texture and opacity, they’ll be in different places, with different rotations, and different scales. Some cubes will look like rectangles due to the scaling.

Creating a Header, Score, and Scene

The following functions will be used later on inside the init function:

function setHeader() {
	var info = document.createElement('div');
	info.style.position = 'absolute';
	info.style.top = '10px';
	info.style.width = '100%';
	info.style.textAlign = 'center';
	info.innerHTML = '<a href="http://jscrambler.com" target="_blank">Jscrambler</a> - Three.js cube example';
	container.appendChild(info);
}


Here, we can see our setHeader function. It’s just a simple text linking to Jscrambler, that says “Jscrambler - Three.js cube example”. After creating the element it is placed inside the container through container.appendChild(info).

function setScore() {
	var info = document.createElement('div');
	info.id = 'score';
	info.style.position = 'absolute';
	info.style.top = '30px';
	info.style.width = '100%';
	info.style.textAlign = 'center';
	info.innerHTML = 'Score: ' + count;
	container.appendChild(info);
}


Like in the setHeader function, setScore creates an element, this time giving it an Id, so we can later alter its inner HTML. Note that the inner HTML has our count variable, which will keep track of the number of times the cubes were clicked.

function createScene() {
	scene = new THREE.Scene();
	renderer = new THREE.WebGLRenderer({
		antialias: true
	});
	renderer.autoClear = true;
	renderer = new THREE.CanvasRenderer();
	renderer.setClearColor(0xf0f0f0);
	renderer.setPixelRatio(window.devicePixelRatio);
	renderer.setSize(window.innerWidth, window.innerHeight);
	container.appendChild(renderer.domElement);

	for (var i = 0; i < numCubes; i++) {
		var geometry = createGeometry();
		scene.add(geometry);
	}
}


As for the createScene, we have followed the creation of several cubes with var geometry=createGeometry(), according to the previously defined numCubes, and their addition to the scene.

Event Listeners

These event listeners will be triggered after being defined inside the init function.

onWindowResize

function onWindowResize() {
	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();
	renderer.setSize(window.innerWidth, window.innerHeight);
}


Here we resize our project, by altering the camera, and the renderer, whenever the window is resized.

onDocumentMouseDown

function onDocumentMouseDown(event) {
	isMouseDown = true;
	onMouseDownTheta = theta;
	onMouseDownPhi = phi;
	onMouseDownPosition.x = event.clientX;
	onMouseDownPosition.y = event.clientY;
	mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
	mouse.y = -(event.clientY / renderer.domElement.clientHeight) * 2 + 1;
	raycaster.setFromCamera(mouse, camera);
	var intersects = raycaster.intersectObjects(scene.children); //if mouse intersects a cube
	if (intersects.length > 0) {
		new TWEEN.Tween(intersects[0].object.position).to({
				x: Math.random() * 800 - 400,
				y: Math.random() * 800 - 432,
				z: Math.random() * 800 - 777
			}, 2000)
			.easing(TWEEN.Easing.Elastic.Out).start(); //moves cube
		new TWEEN.Tween(intersects[0].object.rotation).to({
				x: Math.random() * 2 * Math.PI,
				y: Math.random() * 2 * Math.PI,
				z: Math.random() * 2 * Math.PI
			}, 2000)
			.easing(TWEEN.Easing.Elastic.Out).start(); //rotates cube
		count++;
		document.getElementById("score").innerHTML = "Score: " + count;
	}

}


Once the mouse is clicked, the isMouseDown variable is set as true. Now we have Theta and Phi, which help with the camera rotation, and we record the mouse position, once in onMouseDownPosition and another time in the mouse.

The reason behind this is that the onMouseDownPosition is used to rotate the Camera, while the mouse is used with the raycaster to work out what objects the mouse is over. If the mouse is over a cube, the clicked cube will suffer a progressive rotation and position change.

The count is also incremented, and our scoring element will be altered as well, according to the new count value.

onDocumentMouseMove

function onDocumentMouseMove(event) {
	if (isMouseDown) {

		theta = -((event.clientX - onMouseDownPosition.x) * 0.5) + onMouseDownTheta;
		phi = ((event.clientY - onMouseDownPosition.y) * 0.5) + onMouseDownPhi;

		phi = Math.min(180, Math.max(0, phi));

		camera.position.x = radious * Math.sin(theta * Math.PI / 360) * Math.cos(phi * Math.PI / 360);
		camera.position.y = radious * Math.sin(phi * Math.PI / 360);
		camera.position.z = radious * Math.cos(theta * Math.PI / 360) * Math.cos(phi * Math.PI / 360);
		camera.updateMatrix();
	}
}

Whenever the mouse is moved this function is triggered if the isMouseDown variable is true, that means that the mouse is being clicked, and the camera will rotate, according to the mouse movement.

onDocumentMouseUp

function onDocumentMouseUp(event) {
	isMouseDown = false;
}

This function is triggered whenever the mouse stops being clicked. The isMouseDown variable is set as false, so the program knows the mouse isn’t being clicked anymore.

Building the game

To build our game, we need to call the init() and the animate() function which we’ll define below.

init();
animate();

This will lead to:

Init

function init() {
	container = document.createElement('div');
	document.body.appendChild(container);
	setHeader();
	setScore();
	camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
	camera.position.y = 360;
	camera.position.z = 555;
	raycaster = new THREE.Raycaster();
	mouse = new THREE.Vector2();
	stats = new Stats();
	container.appendChild(stats.dom);

	createScene();
	onMouseDownPosition = new THREE.Vector2();
	document.addEventListener('mousedown', onDocumentMouseDown, false);
	document.addEventListener('mousemove', onDocumentMouseMove, false);
	document.addEventListener('mouseup', onDocumentMouseUp, false);
	window.addEventListener('resize', onWindowResize, false);
}

In the init function, we set our container as a div. It’ll be filled with our cubes, header, and score, as shown previously.

Then we have setHeader(); and setScore() that, as we’ve demonstrated, are used to create the header text and the score tracker of our game.

From here we set our camera’s initial position, our raycaster, mouse, and stats. We have the createScene function, which handles our renderer initialization, and cube creation.

All that’s left is handling the mouse position, with the onMouseDownPosition, and our Event Listeners for when the mouse is moved, down, or up, and when the window is resized.

Animate and Render

We need to use an animation function. It may not seem like anything is really “animated” here in the traditional sense, but we do need to redraw when the camera orbits around the cubes.

This is done by calling the render function that will alter the camera’s position, to change the way it looks at the scene.

function animate() {
	requestAnimationFrame(animate);
	render();
	stats.update();
}

function render() {
	TWEEN.update();
	theta += 0.1;
	camera.position.x = radious * Math.sin(THREE.Math.degToRad(theta));
	camera.position.y = radious * Math.sin(THREE.Math.degToRad(theta));
	camera.position.z = radious * Math.cos(THREE.Math.degToRad(theta));
	camera.lookAt(scene.position);
	renderer.render(scene, camera);
}


Camera

Looking further into detail at the camera.

	camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
	camera.position.y = 360;
	camera.position.z = 555;


Our camera has a perspective projection, which is designed to mimic the human eye. We start by setting its field of view, aspect ratio, near plane, and far plane. The planes represent the distance at which you start seeing objects near and far, as they originate from the camera.

In other projects, it might be more fitting to employ an Orthographic perspective like the one shown above. For now, we’ll keep the PerspectiveCamera and position it to view a good amount of blocks without them blocking the line of sight.

The vast selection of plugins and community projects built on three.js lets anyone take advantage of the API. This doesn’t only expand the three.js community but also incentivizes new spin-off projects based on this library.

There’s also a three.js extension site known as threeX which provides several handy components which you can reuse when making your app. It could be a great way to deploy rapid experiments with some visual tweaks applied through components rather than rolling your own.

Remember that if you have a Three.js game or any other Javascript application you want to protect from prying eyes, Jscrambler is the best solution to do so.

Some good transformations for games would be Code Locks, where you can lock your code to dates, browsers, operating systems, and domains, and Self Defending, to prevent your code from being debugged and tampered with.

You can test Jscrambler and all of its functions in the Playground app.

Jscrambler

The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.

View All Articles

Must read next

PCI DSS

Three things you need to know about PCI DSS v4.0

Here are the top three things to know about the latest version of PCI DSS - v.4.0

March 14, 2023 | By Jscrambler | 3 min read

Web Development

Getting Started with Riot.js

Riot.js takes many of the good parts of React.js and makes it even simpler. In this blog post, learn how to get a functional application up and running.

May 2, 2017 | By Lamin Sanneh | 9 min read

Section Divider

Subscribe to Our Newsletter