Notes from the book Game development with Three.js by Isaac Sukin

My notes from the book Game development with Three.js by Isaac Sukin:

Three.js is usually used with a new technology called WebGL, a JavaScript API for rendering graphics without plugins. The API is based on OpenGL, a desktop graphics API (GL stands for graphics library).

Because it uses the client’s graphics processing unit to accelerate rendering, WebGL is fast! However, many mobile browsers as well as Internet Explorer 10 and below do not support WebGL. Luckily, Three.js supports rendering with the HTML5 Canvas API as well as other technologies such as Scalable Vector Graphics instead.

<!DOCTYPE html>
<html>
	<head>
		<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r57/three.min.js"></script>
	</head>
	<body>
		
		<script>
			var camera, scene, renderer;
			var geometry, material, mesh;

			var init = function () {

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

				camera = new THREE.PerspectiveCamera( 95, window.innerWidth / window.innerHeight, 1, 5000 );
				camera.position.z = 900;

				scene = new THREE.Scene();

				geometry = new THREE.CubeGeometry( 100, 100, 100 );
				material = new THREE.MeshNormalMaterial( { color: 0x000000, wireframe: true, wireframeLinewidth: 2 } );

				mesh = new THREE.Mesh( geometry, material );
				scene.add( mesh );
			}

			var animate = function () {

				requestAnimationFrame( animate );

				mesh.rotation.x = Date.now() * 0.001;
				mesh.rotation.y = Date.now() * 0.001;

				renderer.render( scene, camera );

			}

			init();
			animate();
		</script>
	</body>
</html>

The renderer creates a new <canvas> element by default that should be added to the DOM. Avoid changing the canvas’ size with CSS; use the renderer’s setSize method instead, which sets the width and height HTML attributes on the canvas element.

This is because CSS describes the display size but not the render size. That is, if the canvas is rendered at 800 x 600, but the CSS shows it at 1024 x 768, the rendering will be stretched to fill the space just like if you specified the CSS size of an image to be larger than its true size. This can result in distortion and difficulty converting between “screen space” and “canvas space.”

The one last thing we need is a camera object as shown in the following code snippet, which is something Three.js uses to tell the renderer from what perspective the scene should be displayed. If the player was standing in your virtual world and their screen represented what they could see, camera would be their eyes, renderer would be their brain, and scene would be their universe.

All objects are initialized at the position (0, 0, 0), also called the origin. The key here is requestAnimationFrame(), which executes the function passed to it when the browser is ready to paint a new frame. Geometries are instances of THREE.Geometry that define the shape of an object in a scene. They are made up of vertices and faces (which are themselves objects and are accessible through the vertices and faces array properties). Vertices are the THREE.Vector3 objects representing points in three-dimensional space, while faces are the THREE.Face3 objects representing triangular surfaces.

Triangle:

var geo = new THREE.Geometry();
geo.vertices = [
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(0, 100, 0),
    new THREE.Vector3(0, 0, 100)
];

geo.faces.push(new THREE.Face3(0, 1, 2));
geo.computeBoundingSphere();

3D:

THREE.Sphere(radius, horizontalSegments = 8, verticalSegments = 6)

THREE.Icosahedron(radius, detail = 0);
THREE.Octahedron(radius, detail = 0);
THREE.Tetrahedron(radius, detail = 0);

THREE.CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments = 8, heightSegments = 1, openEnded= false)

THREE.TorusGeometry(radius, tubeWidth = 40, radialSegments = 8, tubularSegments = 6)

THREE.TorusKnotGeometry(radius, tubeWidth = 40, radialSegments, tubularSegments, p = 2, q = 3, heightScale = 1)

2D:

Plane THREE.PlaneGeometry(width, height, widthSegments = 1, heightSegments = 1)

Circle THREE.CircleGeometry(radius, numberOfSides = 8)

Ring THREE.RingGeometry(innerRadius, outerRadius, radialSegments = 8, ringSegments = 8)

Extruding:

var triangle = new THREE.Shape([
new THREE.Vector2 (0, 50),
new THREE.Vector2 (50, 50),
new THREE.Vector2 (50, 0)
]);
var geometry = new THREE.ExtrudeGeometry(triangle, {
bevelEnabled: false,
amount: 30
});

Custom fonts must be in the typeface.js format (you can convert OpenType and TrueType fonts to Typeface format at http://typeface.neocracy.org/fonts.html). Use the following code to create text geometry:

new THREE.TextGeometry("Text message goes here", {
    size: 30,
    height: 20, // extrude thickness
    font: "helvetiker", // font family in lower case
    weight: "normal", // or e.g. bold
    style: "normal", // or e.g. italics
    bevelEnabled: false
});

Procedural city:

var camera, scene, renderer;

function setup() {
    document.body.style.backgroundColor = '#d7f0f7';
    setupThreeJS();
    setupWorld();
    requestAnimationFrame(function animate() {
        renderer.render(scene, camera);
        requestAnimationFrame(animate);
    });
}

function setupThreeJS() {
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
    camera.position.y = 400;
    camera.position.z = 400;
    camera.rotation.x = -45 * Math.PI / 180;
    renderer = new THREE.CanvasRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
}

function setupWorld() {
    // Floor
    var geo = new THREE.PlaneGeometry(2000, 2000, 20, 20);
    var mat = new THREE.MeshBasicMaterial({color: 0x9db3b5, overdraw:true});
    var floor = new THREE.Mesh(geo, mat);
    floor.rotation.x = -90 * Math.PI / 180;
    scene.add(floor);
    // Original building
    var geometry = new THREE.CubeGeometry(1, 1, 1);
    geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0, 0.5,0));

    var material = new THREE.MeshDepthMaterial({overdraw: true});
    // Cloned buildings
    for (var i = 0; i < 300; i++) {
        var building = new THREE.Mesh(geometry.clone(), material.clone());
        building.position.x = Math.floor(Math.random() * 200 - 100) * 4;
        building.position.z = Math.floor(Math.random() * 200 - 100) * 4;
        building.scale.x = Math.random() * 50 + 10;
        building.scale.y = Math.random() * building.scale.x * 8 + 8;
        building.scale.z = building.scale.x;
        scene.add(building);
    }
}

// Run it!
setup();

Fog:

scene.fog = new THREE.FogExp2(0x9db3b5, 0.002);

Only the DirectionalLight and PointLight objects can cast shadows. Casting shadows first requires that we enable shadows on the renderer:

renderer.shadowMapEnabled = true;

For our city scene, we’ll enable shadow receiving for our floor and both casting and receiving for our buildings:

floor.receiveShadow = true;
city.castShadow = true;
city.receiveShadow = true;

Finally, we configure our DirectionalLight object to use shadows:

light.castShadow = true;
light.shadowDarkness = 0.5;
light.shadowMapWidth = 2048;
light.shadowMapHeight = 2048;
light.position.set(500, 1500, 1000);
light.shadowCameraFar = 2500;

// DirectionalLight only; not necessary for PointLight
light.shadowCameraLeft = -1000;
light.shadowCameraRight = 1000;
light.shadowCameraTop = 1000;
light.shadowCameraBottom = -1000;

Earlier, we switched from CanvasRenderer to WebGLRenderer in order to support shadows and fog. As a rule of thumb, WebGLRenderer is faster and has the most features, while CanvasRenderer has fewer features but broader browser support. One particularly nice feature of WebGLRenderer is that it supports antialiasing to smooth out jagged edges. We can enable this for our cityscape by passing the option in to the renderer constructor:

renderer = new THREE.WebGLRenderer({antialias: true});

Already written navigation from examples:

<script src="FirstPersonControls.js"></script>

Add timer:

clock = new THREE.Clock();
controls = new THREE.FirstPersonControls(camera);
controls.movementSpeed = 100;
controls.lookSpeed = 0.1;

Add this to requestAnimationFrame:

controls.update(clock.getDelta());

Clicking on the screen in order to select or interact with something is a common requirement, but it’s somewhat harder than it sounds because of the need to project the location of the click in the 2D plane of your screen into the 3D world of Three.js. To do this, we draw an imaginary line, called a ray, from the camera toward the position where the mouse might be in 3D space and see if it intersects with anything. In order to project, we first need a projector:

projector = new THREE.Projector();

Then we need to register a listener on the click event for the canvas:

renderer.domElement.addEventListener('mousedown', function(event) {
var vector = new THREE.Vector3(
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) /
this.width * 2 - 1,
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) /
this.height * 2 + 1,
0
);
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(
camera.position,
vector.sub(camera.position).normalize()
);
var intersects = raycaster.intersectObjects(OBJECTS);
if (intersects.length) {
// intersects[0] describes the clicked object
}
}, false);

The last thing you want when your player is clicking madly to shoot at enemies is for the whole screen to suddenly turn blue because the browser thinks the user is trying to select something. To avoid this, you can either cancel the select event in JavaScript with document.

onselectstart = function() { return false; }

or disable it in CSS:

* {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

Colission libraries:

  • Ammo.js is a large but complete library compiled to JavaScript from C++. It is available at https://github.com/kripken/ammo.js/
  • Cannon.js is a smaller library written from scratch in JavaScript and inspired in part by concepts from Three.js. It is available at https://github.com/schteppe/cannon.js
  • Physi.js is a bridge between Ammo or Cannon and Three.js that also runs the physics simulation in a separate thread to avoid blocking the rendering. It is available at https://github.com/chandlerprall/Physijs

Well, tbh, stopped at page 61/100 as the examples from the book didn’t quite work for me so all in all very disappointed with the book in that department. But, as far as the writing and explanations up till this point go I think it was good. Grade: 2.7/5  

Written by Nikola Brežnjak