THREEJS_PDF1
THREEJS_PDF1
INTRODUCTION
- NOTE : WebGPU is a JavaScript API provided by a web browser that enables webpage
scripts to efficiently utilise a device's graphics processing unit (GPU).
SETTING IT UP
NOTE : If faced with any errors while installing this use a previous version of parcel or a
different bundler like webpack. Parcel 2 is preferred.
Parcel will bundle any used asset from the src folder and put it in the Dist folder if it gets
used in the project code.
- Step 3 : Create index.html in a folder named src (which is inside your project folder).
Create a folder called js inside which script.js is to be created.
- Step 4 : Run parcel ./src/index.html in terminal to make parcel bundle the files and
replace the page automatically.
NOTE: “Not digitally signed, you cannot run this script on the current system”, if you
encounter this error do the following:
1
1. Open PowerShell as Administrator by searching for "PowerShell" in the Start
Menu, right-clicking it, and selecting "Run as administrator."
2. Run the following command:
After changing the execution policy, try running your parcel command again.
- A dist folder should be created on running this command. This holds the distribution
version of the app including any asset located in the src folder and used in the code.
- Cmd + left click on Mac (ctrl + left click on windows) on the server url that parcel has
started to view what you’ve created.
2
HOW DOES THREEJS WORK?
- Coordinate system
- Make a scene by creating instance of the scene class
- Choose the right type of camera and create an instance of it
- There are 2 types of cameras : perspective camera (basically a real life camera) and
orthographic camera (used to render 2D scenes where depth does not matter)
NOTE :
1. Use the mentioned project for reference. Necessary information has been commented in the code.
2. Use the specified method to run the project.
3
USE PROJ1_BASICS For this. Run with parcel ./src/index.html
INDEX.HTML SCRIPT
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=dge">
<meta name="viewport" content="width=device-width, initial-
scale=1.0">
<title>Three.js Tutorial </title>
<style>
body{
margin:0
}
</style>
</head>
<body>
<script src="./js/script.js" type="module"></script>
</body>
</html>
4
box.rotation.y = 5 //about y axis
On visiting the running server, the page should look something like this
Make sure to save you code after making changes to observe the changes made.
Sometimes you may have to refresh your screen as well.
5
ADDING ANIMATIONS
- Speed of animation can be controlled by using the time argument. As times passes, the
faster the animation will be.
6
• Zoom in and out using the mouse scroll wheel or touchpad pinch gestures.
• Rotate the scene by clicking and dragging the mouse or using touch gestures.
• Pan the scene by holding the right mouse button and dragging or using a two-finger
gesture on a touchpad.
3. Cover the geometry with the material (can be done using Mesh).
7
NOTE:
While creating a scene with multiple elements, using trial and error to get the perfect
position, colour (colour code) etc is a tedious task
What is dat.gui?
- dat.gui is a lightweight GUI (graphical user interface) library that allows you to create a
real-time control panel for adjusting parameters like colors, positions, and sizes in your
Three.js scene without hardcoding or refreshing.
- You can bind properties (like color, size, rotation) to a GUI controller, and it will
automatically update the scene when those values are changed via the control panel.
To install this stop parcel (ctrl + C) and run the command :
Let’s create a basic element (a sphere, for example) in Three.js, which will be controlled by
dat.gui.
1. Create Geometry (Skeleton): This defines the shape of the element you want to add to
the scene.
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32); // A sphere with radius 1
2. Create Material (Skin): You can define the material's appearance using color or texture.
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x00FF00 });//Green sphere
3. Create Mesh: The mesh combines the geometry and material, which makes the object
visible in the scene.
8
4. Create an Object to Store the Sphere's Properties:
Create an object to store the sphere’s properties that can be modified via the GUI.
const sphereProperties = {
color: 0x00FF00, // Initial color of the sphere (green)
scale: 1, // Initial scale of the sphere
};
gui.addColor(sphereProperties, 'color').onChange(function(value) {
// When the color changes, update the sphere’s material color
sphere.material.color.set(value);
});
// Scene setup
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
9
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);
// GUI Setup
const gui = new dat.GUI(); // Create dat.GUI instance
// Animation loop
function animate(time) {
orbit.update();
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
OUTPUT:
Your interface on
doing this will
look something
like this. Using
this you can
change the
colour and size of
any object that
holds the
interface
elements.
10
LIGHTING
1. Ambient lighting : Light that comes from the environment. It is the result of other sources
of light such as sunlight.
2. Directional lighting : light that falls from a direction on all objects in the scene
3. Spot light : light source that emits light in the form of a cone. Further the light, bigger the
radius of the cone.
AMBIENT LIGHT
To create a light source in the scene, you have to create an instance of the light class and
pass the colour of the light as an argument.
NOTE : Keep in mind if the material of your object is MeshBasicMaterial you will not be able
to see any visible change in your scene after adding this code. This is because Basic
material is not affected by light.
Working with lights can prove to be tricky. Thankfully, threeJS has many inbuilt helpers to
deal with these issues. These helpers will indicate the direction of the light.
const dLightHelper = new THREE.DirectionalLightHelper (directionalLight);
scene.add(dLightHelper);
.
11
Complete code for the above image:
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// Renderer setup
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight); // Set renderer size to the
window dimensions
document.body.appendChild(renderer.domElement); // Append renderer to the DOM
// Scene setup
const scene = new THREE.Scene();
// Camera setup
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight,
0.1, 1000); // (FOV, aspect ratio, near, far)
camera.position.set(0, 2, 8); // Set the camera position
// Axes Helper
const axesHelper = new THREE.AxesHelper(5); // Axes helper to visualize the coordinate
system
scene.add(axesHelper); // Add helper to the scene
// Grid helper
const gridHelper = new THREE.GridHelper(10, 10); // 10x10 grid
scene.add(gridHelper);
12
const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00FF00 }); // Green color
for the square
const box = new THREE.Mesh(boxGeometry, boxMaterial);
box.position.set(0, 0.25, 0); // Adjust the height and position of the cube
scene.add(box);
// OrbitControls setup
const orbit = new OrbitControls(camera, renderer.domElement); // Attach OrbitControls
to the camera and renderer
orbit.update(); // Update controls on each render
// Animation loop
function animate() {
requestAnimationFrame(animate);
dLightHelper.update(); // Update the helper to visualize changes
orbit.update(); // Keep updating the controls
renderer.render(scene, camera); // Render the scene
}
13
SHADOWS
Shadows are not enabled by default in threeJS. Hence to enable shadows we need to set
the shadow map enabled property of the renderer to true in the beginning of your code.
renderer.shadowMap.enabled = true;
Hence, if you want to visualise the space that the shadow camera marks for the shadows to
be rendered on we can use another helper.
This will be an instance of the camera helper class and it takes the camera of the shadow
that belongs to our directional light as an argument
const dLightShadowHelper = new THREE.CameraHelper(directionalLight.shadow.camera); //
shadow camera helper
scene.add(dLightShadowHelper);
On adding the above lines of code you will notice 4 orange segments that go from the
camera and through the plane. The square formed by these segments is the only surface on
which the cast shadow can be rendered.
14
In this case we need to move the 2 orange lines on
the bottom, backward so that the entire shadow
can be rendered. To do that we need to
add the following line of code to your
directionalLight component
directionalLight.castShadow = true; //Light
that casts the shadow of the sphere onto the
plane
directionalLight.shadow.camera.bottom = -12;
SPOTLIGHT
To observe the spot light clearly, comment out the code for all your other lights.
const spotLight = new THREE.SpotLight(0xFFFFFF); //creating a
spotLight
scene.add(spotLight);
The above code will create a spotlight that shines at the centre of the plane.
To make it easier to work with a spotlight, we will create its very own helper.
spotLight.position.set(-50, 50, 0);
15
The segment going through the plane
represents the direction of the light while the
other 4 lines represents the boundaries of the
spotlight.
spotLight.castShadow = true;
On adding shadows to your spotlight you will notice that the shadow looks pixelated and not
natural. This is a result of the angle of the spotlight (if the angle of the 4 lines is too wide, the
shadow will be pixelated).
spotLight.angle = 0.15;
On doing this it can be observed that the shadow looks way more realistic.
To work better with the spotlight, you can add its properties as a guy component.
NOTE: You need to update the helper every time you change the properties of the light.
16
FOG
METHOD 1:
We can add Fog in ThreeJS by creating an instance of the fog class, where the constructor
takes in three arguments : colour, near, far of the space where the fog is.
As you can see, the further we get from 0 the less visible the scene is.
METHOD 2:
Instantiate the fog Exp2 class whose constructor takes 2 arguments : color and density. This
will give you the same output but with lesser number of arguments.
17
To do this, create an image folder in src folder. Then import the image in your code. On
doing this, you will notice that the image files are created in the
Dist folder with random names.
import clouds from '.../img/clouds.jpg'
The scene essentially is a 3D code and hence the background can be changed such that all
6 sides have different backgrounds, to make it appear as a 3D world.
To do this we will have to use a different type of loader called the cube texture loader.
This load method takes an array of image paths and each will serve as a texture to the face
of the cube (the scene).
18
const cubeTextureLoader = new THREE.CubeTextureLoader();
scene.background = cubeTextureLoader.load([
night, night, night, clouds, clouds, clouds]);
As you can see, you need to add your texture loader to your map property in your box
basicMaterial.
You can also add texture to your object later on by updating its map function directly.
If you want every face of your box to have a different material, create a material to every
face and each should have its own texture.
19
SELECTING OBJECTS FROM THE SCENE (MOUSE EVENTS)
Sometimes one would like their users to be able to select certain objects from the scene.
You can do this in threeJS we can do this by using something called a raycaster.
Ray casting is a rendering technique used in computer graphics and computational geometry.
In ThreeJS ray casting is used to identify mouse clicks. In Three.js, raycasting helps detect mouse clicks on
3D objects by transforming the 2D mouse coordinates from the screen into a 3D ray in the scene. This ray is
then used to determine which objects in the 3D scene are under the mouse cursor (identify which objects
the ray is passing through). To learn more about raycasting visit : Raycasting
Step 1 : Create a 2D vector which holds the x and y values of the cursor position. const
mousePosition = new THREE.Vector2();
Step 2 : Add an event listener to catch the position of the cursor and then update created
vector with the normalised values of the cursors coordinates.
window.addEventListener('mousemove', function(e) {
mousePosition.x = (e.clientX / window.innerWidth) * 2 - 1; //
e.clientX defines the x coordinate of the cursor
mousePosition.y = - (e.clientY / window.innerHeight ) * 2 + 1;
});
NOTE : These transformations are necessary to convert the mouse coordinates from the browser’s
coordinate system to WebGL’s normalised device coordinate system to enable accurate raycasting
and interaction within the 3D scene.
Step 4 : Set the 2 ends of the ray (camera and normalised mouse position) in the animate
function.
rayCaster.setFromCamera(mousePosition, camera);
Step 5 : Create a variable that will hold an object that is returned by the intersect object’s
method (this will contain any element from the scene that intersects with the ray)
const intersects = rayCaster.intersectObjects(scene.children);
console.log(intersects); //logs mouse position onto the wesite
console
When you look at your console log (inspect element) you can notice the position of your
mouse arrow on it. But how does threeJS identify which object the mouse is hovering over
when your scene has multiple meshes present on it?
ThreeJS assign unique ID’s to all objects in your scene. We will need to extract this data to
identify the object your mouse is hovering over.
const sphereId = sphere.id; //extract your spheres uuid
20
Step 5 : Do a loop over the intersects object in the animate function such that if the object
has the ID we’re looking for, a certain action can be performed.
For example, we can change the colour of the sphere every time we hover over it.
You can also perform recasting by assigning a name to your object id and using that every
time you want to perform a mouse event. box2.name = 'Box' //raycasting with
alias
IMPORTING 3D MODELS
Models created by 3D modelling programs (such as Blender) can be imported into your
threeJS.
To do this, you need to make sure to import the model in the right format : gltf, fbx, or obj.
Then add the downloaded file to a new folder called assets which is to be created in your
21
src folder. Then we need to tell parcel to use this asset so that it creates a copy in the Dist
folder.
const assetUrl = new URL.('../assets/3D.glb', import.meta.url);
Then wishing the callback function, we need to update the aspect property of the camera
window.addEventListener('resize', function() {
camera.aspect = this.window.innerWidth /
this.window.innerHeight;
camera.updateProjectionMatrix(); //called everytime theres any
change on the aspect ratio
renderer.setSize(window.innerWidth, window.innerHeight); })
Now when you resize your window your scene (canvas) will also adjust itself to the changes.
CAMERA MOVEMENT
USE PROJ2_CAMERA+TEXT FROM HERE - RUN SERVER USING COMMAND npx vite, (run
npm install - if there are any missing packages)
22
As you can see, in the above method we have set a certain camera position. Then we have
created 2 variables : z and zFinal. The animate function after listening to the mouse down
listener gradually increases the value of z until it reaches zFinal.
The above method is not recommended for complex animation since we will have to add a
lot more code and variables to deal with it. This will make your code very complex and
unclean.
To do this, we will have to first download the animation library (gsap) using the command:
The above lines of code perform the same animation using the gsap library without having
to use the animation function.
You can also combing 2 or more animations at the same time using gsap.
Ex:
23
window.addEventListener('mousedown', function(){
// z = camera.position.z ;
gsap.to(camera.position, {
z: 14,
duration: 1.5,
onUpdate : function() {
camera.lookAt(0, 0, 0); //maintains camera direction
towards the origin of the scene
}
});
gsap.to(camera.position, {
y: 14,
duration: 1.5,
onUpdate : function() {
camera.lookAt(0, 0, 0); //maintains camera direction
towards the origin of the scene
}
})
You can also use a timeline to perform more complex animations (animation in 3 phases).
const tl = gsap.timeline();
window.addEventListener('mousedown', function(){
// z = camera.position.z ;
tl.to(camera.position, {
z: 14,
duration: 1.5,
onUpdate : function() {
camera.lookAt(0, 0, 0); //maintains camera direction
towards the origin of the scene
}
})
.to(camera.position, {
y: 10,
duration: 1.5,
onUpdate : function() {
camera.lookAt(0, 0, 0); //maintains camera direction
towards the origin of the scene
}
})
.to(camera.position, {
x: 10,
y: 5,
z: 3,
duration: 1.5,
onUpdate : function() {
camera.lookAt(0, 0, 0); //maintains camera direction
24
towards the origin of the scene
}
});
});
25
DEALING WITH TEXT IN THREEJS
Start of by creating a font loader object. Then you need to pass the typeface json file to it
const fontLoader = new FontLoader();
fontLoader.load(
'node_modules/three/examples/fonts/droid/
droid_serif_regular.typeface.json', //Has some prebuilt fonts that
can be used to work with threeJS
(droidFont) =>{ //droid is a font family
const textGeometry = new TextGeometry('hello world',{ //2
arguments: your text and the style of your text
height : 2,
size : 10,
font : droidFont,
});
const textMaterial = new THREE.MeshNormalMaterial();
const text = new THREE.Mesh(textGeometry, textMaterial);
text.position.x = -30;
text.position.y = 0;
scene.add(text);
});
26
2. Loading custom fonts with TTFLoader
First, download the font that you want to use (you can use jetBrains.com to download your
custom fonts)
Create a folder called FontLoader (to easily manage your fonts) and place the .ttf files of
the fonts you want to use in this folder.
27
For more information to work with text visit : 3D typing effects with threeJS
Bump mapping is a texture mapping technique in computer graphics for simulating bumps
and wrinkles on the surface of an object.
Bump Map simulates geometry changes based on an image - the light and dark values of
an image imply height.
Step 1 : Get the original image of your object (texture) and add it to your project directory.
Step 2 : Desaturate the colour of your image completely and then invert the image colours
(invert black and white) in photoshop or any other photo editing app. You may want to
adjust the contrast to increase the effect. Save the file and put it in your project directory.
This is your bump map
Step 3 : Load your images into the scene using TextureLoader. Create a material using
MeshPhongMaterial() and your bump map in this. You can adjust texture of bump map
using bumpScale property.
28
You can now see the texture on your image is better but
it is still a 2D image. To make it a 3D image (more
realistic) you will need a displacement map.
Step 3 : to create displacement image, desaturate your original image. Then increase your
brightness and contrast as desired (light area = high altitude, Dark area = Low altitude).
Add this to your project directory.
Step 4 : Load it to your scene and assign the displacement map to the displacement
property of your material. You can adjust your displacement scale to increase the effect.
29
Brick image Bump image Displacement image
Position array : while dealing with multiple each of your particles need to have an x,y,z
attached to them and the Float32 array does that for you.
module.exports = merge(
commonConfiguration,
{
mode: 'development',
devServer: {
host: '0.0.0.0',
port: portFinderSync.getPort(8080),
static: './dist',
open: true,
liveReload: true,
onListening: function (server) {
const port = server.options.port;
const protocol = server.options.server.type === 'https' ? 'https' :
'http';
const address = server.options.host === '0.0.0.0' ? 'localhost' :
server.options.host;
SHADERS
A shader is a small program written in GLSL that runs on the GPU. You may want to use a
custom shader if you need to:
31
DEALING WITH ELEMENTS
REFER to PROJ5_ELEMENTS
Step 1: Get a transparent image of smoke. Use TextureLoader to load your image and
map the texture in your Material. Set transparent to True.
Step 2: Create a loop to load a lot of the smoke particles in your desired shape. Set the
particle position using the math equation of the shape you want the particles to render
themselves in (Google the equation). Set a random rotation to create a diversity.
You will now see a static shape rendered with its material as the image you have used.
Step 3: Animate it to make it look realistic. (Note: use a clock object to keep track of
rendering time, this is required to be able to control your animation). You then need to
create an array and add all you particles to it. This array should be then passed to your
animate function so as to render animations to each of your particles individually.
Step 4 : Add lighting (if desired) to make your animation look more interesting (Point lights
have been used in this project)
32
PHYSICS IN THREEJS
To start working with Rapier use : PROJ6_Physics as a reference (credits : https://github.com/
bobbyroe/physics-with-rapier-and-three)
Physics can be one of the coolest features you can add to a WebGL experience. People
enjoy playing with objects, see them collide, collapse, fall and bounce.
There are many ways of adding physics to your project, and it depends on what you want
to achieve. You can create your own physics with some mathematics and solutions like
Raycaster, but if you wish to get realistic physics with tension, friction, bouncing,
constraints, pivots, etc. and all that in 3D space, you better use a library.
The idea is simple. We are going to create a physics world. This physics world is purely
theoretical, and we cannot see it. But in this world, things fall, collide, rub, slide, etc.
When we create a Three.js mesh, we will also create a version of that mesh inside the
physics world. If we make a Box in Three.js, we also create a box in the physics world.
Then, on each frame, before rendering anything, we tell the physics world to update itself;
we take the coordinates (position and rotation) of the physics objects and apply them to the
corresponding Three.js mesh.
For 3D physics you can use the libraries : Rapier, Oimo.js, Cannon.js, Ammo.js.
Rapier is a set of 2D and 3D physics engines written using the Rust programming
language. It targets applications requiring real-time physics like video games, animation,
and robotics. It is designed to be fast, stable, and optionally cross-platform deterministic.
Rapier features include:
• Joint constraints.
• Snapshotting.
• JavaScript bindings.
• And more…
To start using Rapier, you need to import the Rapier physics engine into you file:
33
Note : If you are planning on using any other 3D library, make sure to follow the documentation for
necessary downloads/ imports.
Step 1 : initialise rapier and create a world with gravity (create the physics world).
await RAPIER.init(); //initialises Rapier physics engine const
gravity = { x: 0.0, y: 0, z: 0.0 }; //define your gravity const
world = new RAPIER.World(gravity); //create a world passing in
that gravity
Step 2 : Pass in Rapier and world to all the object that need physics based interaction in
your scene.
Code snippets:
const body = getBody(RAPIER, world);
const mouseBall = getMouseBall(RAPIER, world);
Step 3 : Set up a rigid body in the function where you’re getting your body. You can do this
by creating a RigidBodyDesc type. There are 4 types of rigid body descriptions.
Rigid-bodies are typically used to simulate the dynamics of non-deformable solids as well
as to integrate the trajectory of solids which velocities are controlled by the user.
Rigid-bodies are only responsible for the dynamics and kinematics of the solid. Colliders
can be attached to a rigid-body to specify its shape and enable collision-detection. A
rigidbody without collider attached to it will not be affected by contacts (because there is no
shape to compute contact against).
2 of them are :
1. Kinematic description : For interactive objects. Ex: objects that track the mouse.
// RIGID BODY
let bodyDesc =
RAPIER.RigidBodyDesc.kinematicPositionBased().setTranslation(0, 0, 0) //
kinematic description
let mouseRigid = world.createRigidBody(bodyDesc); //setting the
rigid body to the above description
let dynamicCollider = RAPIER.ColliderDesc.ball(mouseSize * 3.0); //
setting a dynamic collider that is scaled by 3
world.createCollider(dynamicCollider, mouseRigid);
34
2. Dynamic description : Not controlled by anything but the simulation.
let rigidBodyDesc = RAPIER.RigidBodyDesc.dynamic() //dynamic
description
.setTranslation(x, y, z);
let rigid = world.createRigidBody(rigidBodyDesc); //link rigid
body with the above description
let colliderDesc =
RAPIER.ColliderDesc.ball(size).setDensity(density);
//ball collider is created here.
world.createCollider(colliderDesc, rigid);
Colliders represent the geometric shapes that generate contacts and collision events when
they touch. Attaching one or multiple colliders to a rigid body allow the rigid-body to be
affected by contact forces.
Step 4 : Add an update function to your physics based function (get function in this case)
to define how you object updates based on certain movement (collisions in this case).
The below method sets the mouse rigid body to mouse position and updates the mesh.
35
The below method ensures that all displaced objects are attracted back to a defined
position.
function update () {
rigid.resetForces(true); //resets all the forces
let { x, y, z } = rigid.translation();
let pos = new THREE.Vector3(x, y, z); //definition of a point that
everything in the scene is attracted to. (defined at the enter of the
scene)
let dir = pos.clone().sub(sceneMiddle).normalize(); //calculate
posiiton of the current ball to the centre of the scene
rigid.addForce(dir.multiplyScalar(-0.5), true); //adding a force to
the result of the subtraction. -0.5 scales down the force.
mesh.position.set(x, y, z); //set position of the threeJS object.
}
return { mesh, rigid, update };
}
36
WEBXR
Note:
1. Units in WebXR are in metric quantities I.e., 1 unit = 1 metre.
2. MacOS devices do not have WebXR compatibility
First, you have to include VRButton.js into your project. You can do this by adding a simple
import statement.
import { VRButton } from 'three/addons/webxr/VRButton.js';
Next we need to add the createButton() method to our project. This creates a button for VR
compatibility and initiates a VR session if the user activates the button. To activate the you
need to add the following line of code.
Ensure that your animation function exists in which the following lines of code are present:
renderer.setAnimationLoop( function () {
} );
37
HOTSPOTS
Here are the specific steps for implementing the hotspot functionality in a Three.js
scene:
The raycaster is the key to detecting where the user clicks on the 3D object.
• Create a Raycaster:
const raycaster = new THREE.Raycaster();
You need to track when the user clicks on the object and check where the click happened.
Listen for mouse clicks and calculate the normalized mouse position.
• Mouse Normalization: This ensures that the mouse position is in a format that
works with Three.js (from -1 to 1).
38
Once you know the mouse position, you can cast a ray from the camera and check for
intersections with the 3D object.
raycaster.setFromCamera(mouse, camera);
If intersects.length > 0, that means the ray hit the object, and you can proceed
with checking which part was clicked.
Now that we know the user clicked on the cylinder, we need to determine which section
(top, middle, or bottom) was clicked.
function getHotspotFromIntersection(intersection) {
const y = intersection.point.y;
if (y > 1) {
return "top";
} else if (y >= -1 && y <= 1) {
return "middle";
} else if (y < -1) {
return "bottom";
}
return null;
}
39
Step 5: Show Popup or Trigger an Action
Once you have identified which part of the cylinder was clicked, you can show a popup or
trigger an action based on the identified hotspot.
• Show a Popup:
You can use a custom HTML element for the popup and position it dynamically
based on where the user clicked.
function showPopup(message, x, y) {
popup.innerHTML = message;
popup.style.left = `${x}px`;
popup.style.top = `${y}px`;
popup.style.display = 'block';
}
40
OUTPUT:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Cylinder Hotspots with Pop-ups</title>
<style>
body { margin: 0; }
canvas { display: block; }
.popup {
position: absolute;
background-color: rgba(255, 255, 255, 0.9);
padding: 10px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
display: none; /* Hidden by default */
transform: translate(-50%, -50%); /* Center the pop-up */
}
</style>
</head>
<body>
<!-- Pop-up Elements -->
<div id="popup" class="popup"></div>
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script>
// 1. Setup Scene, Camera, and Renderer
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth /
window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.z = 5;
41
// 2. Create a single cylindrical object
const geometry = new THREE.CylinderGeometry(1, 1, 3, 32); // Full cylinder
height 3
const material = new THREE.MeshBasicMaterial({ color: 0x0000ff });
const cylinder = new THREE.Mesh(geometry, material);
// 4. Function to determine which part of the cylinder was clicked (top, middle,
bottom)
function getHotspotFromIntersection(intersection) {
const y = intersection.point.y;
42
obj.updateMatrixWorld();
vector.setFromMatrixPosition(obj.matrixWorld);
vector.project(camera);
return {
x: vector.x,
y: vector.y
};
}
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObject(cylinder);
if (intersects.length > 0) {
const intersection = intersects[0];
const hotspot = getHotspotFromIntersection(intersection);
// 8. Animation Loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
43
// Hide pop-up when clicking outside of the object
window.addEventListener('click', (event) => {
const intersects = raycaster.intersectObject(cylinder);
if (intersects.length === 0) {
popup.style.display = 'none';
}
});
</script>
</body>
</html>
REFERNCES
[1] https://threejs.org/
[2] https://waelyasmina.net/
[3] https://redstapler.co/category/javascript/
[4] https://www.youtube.com/@flanniganable
[5] https://www.youtube.com/@DesignCourse
[6] https://github.com/bobbyroe/physics-with-rapier-and-three
44