Tunnel animation (Part 2)

This is the second part of my article about tunnel animations.
If you didn't read the first one, please take a look over here :Tunnel animation (Part 1)

Welcome back explorer ! 🖖

Thanks for reaching this new part of the article, I assume that you enjoyed the first one and you want to learn more 😙
Like I said at the end of the previous part, we will now see how to generate a tunnel with particles instead of a plain TubeGeometry().

This is the kind of animation you'll be able to create at the end

Table of contents

1. Calculate the positions of the particles

To achieve the result we want, we will generate circles of particles all along our path. ThreeJs is using the exact same way to generate a tube geometry, with one difference that it adds faces to create a plain tube.
We will have to first choose the detail you want to give to your tube and its radius.

The detail will be set with two values :

// The amount of circles that will form the tube
var segments = 500;
// The amount of particles that will shape each circle
var circlesDetail = 10;
// The radius of the tube
var radius = 5;

Now that we know more or less the amount of particles we are gonna have (spoiler alert : segments * circlesDetail), we now have to calculate the Frenet frames.
I'm really not an expert in this domain but as far as we need to understand, those frenet frames are values calculated for all the segments of the tube. Each frame is made of a Tangent, a Normal and a Binormal. Roughly, those values are the rotations values for each segment and where it looks at.

Thanks to ThreeJs, we don't have to understand any of this to make our code work we can use a built in function from our path.

var frames = path.computeFrenetFrames(segments, true);
// True specify if the path is closed or not, in our case it must be

The result of this function is a set of three arrays of Vector3().

Now that we have all the info needed for each segment, we can start to generate the particles along each segment.
We will store each particle as a Vector3() in a Geometry() so that we can reuse it later.

// Create an empty Geometry where we will insert the particles
var geometry = new THREE.Geometry();

We now have to place particles along each segment. This is why we are gonna use a loop through all segments.
I'm not gonna explain here how the function works, check the code below, you will find all the details as comments ! ⬇️

// Loop through all segments
for (var i = 0; i < segments; i++) {
// Get the normal values of the segment from the Frenet frames
var normal = frames.normals[i];
// Get the binormal values of the segment from the Frenet frames
var binormal = frames.binormals[i];
// Calculate the index of the segment (from 0 to 1)
var index = i / segments;
// Get the coordinates of the point in the center of the segment
// We already used the function in the first part to move the camera along the path
var p = path.getPointAt(index);
// Loop for the amount of particles we want along each circle
for (var j = 0; j < circlesDetail; j++) {
// Clone the point in the center of the circle
var position = p.clone();
// We need to position every point based on an angle from 0 to Pi*2
// If you want only half a tube (like a water slide) you could calculate the angle from 0 to Pi.
var angle = (j / circlesDetail) * Math.PI * 2;
// Calculate the sine of the angle
var sin = Math.sin(angle);
// Calculate the negative cosine of the angle
var cos = -Math.cos(angle);
// Calculate the normal of each point based on its angle and the normal and binormal of the segment
var normalPoint = new THREE.Vector3(0,0,0);
normalPoint.x = (cos * normal.x + sin * binormal.x);
normalPoint.y = (cos * normal.y + sin * binormal.y);
normalPoint.z = (cos * normal.z + sin * binormal.z);
// Multiple the normal by the radius so that our tube is not a tube of 1 as radius
normalPoint.multiplyScalar(radius);
// Add the normal values to the center of the circle
position.add(normalPoint);
// Push the vector into our geometry
geometry.vertices.push(position);
}
}

Phew, this code is really not that easy to understand. I myself checked the source code of ThreeJs to create this code.

You can check this demo below to see the particles being calculate one by one. (Click Rerun if the tube is already fully visible)

2. Create the tube

We now have a Geometry object filled with vertices. With ThreeJs you can create nice particles demos by using the Points constructor that allows you to render with great performances simple points. You can customize those points with a texture or different colors.

The same way we create Mesh, we need two elements to create a Points object. We need a material & a geometry. Since we already have the geometry from step 1, we have to define the material.

var material = new THREE.PointsMaterial({
size: 1, // The size of each point
sizeAttenuation: true, // If we want the points to change size depending of distance with camera
color: 0xff0000 // The color of the points
});

Finally, we have to create our Points object and add it into the scene like so :

var tube = new THREE.Points(geometry, material);
scene.add(tube);

3. Make it move

To make everything move, we will reuse the same code we had in the previous demos.

4. Let's get crazy

From what you learned in this second part, you have now access to an unlimited amount of different tunnels ! You can find below three examples of custom tunnels always based on what you learned above.

Colorful tunnel

For this pen, I apply a custom color for each Vector. I also have applied a Fog on the scene to create some fade effect in the tunnel

// First create a new color based on the index of the vertice
var color = new THREE.Color("hsl(" + (index * 360 * 4) + ", 100%, 50%)");
// Push the color into the colors array in the Geometry object
geometry.colors.push(color);
var material = new THREE.PointsMaterial({
size: 0.2,
vertexColors: THREE.VertexColors // We specify that the colors must come from the Geometry
});
// Add some fog in the scene
scene.fog = new THREE.Fog(0x000000, 30, 150);

Squared cave

This tunnel is only made of Cubes. Instead of using a Points object, I'm creating a new Mesh at each position of the vertices. I also apply the colors based on a Perlin noise algorithm.

Octagonal tunnel

For this pen, I'm connecting the vertices of each circles together to create lines. I'm playing with the angle of the vertices and their colors to create that rotation illusion.

for (var i = 0; i < tubeDetail; i++) {
// Create a new geometry for each circle
var circle = new THREE.Geometry();
for (var j = 0; j < circlesDetail; j++) {
// Push the position of the vector
circle.vertices.push(position);
}
// Duplicate the first vector to make sure the circle is closed
circle.vertices.push(circle.vertices[0]);
// Create a new material with a custom color
var material = new THREE.LineBasicMaterial({
color: new THREE.Color("hsl("+(noise.simplex2(index*10,0)*60 + 300)+",50%,50%)")
});
// Create a Line object
var line = new THREE.Line(circle, material);
// Insert into the scene
scene.add(line);

🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄

Thanks for reading my posts about tunnel animations !

You can find all the demos from the pens into this collection if you want to see them together.

I hope you found this article interesting ! If you create some pretty tunnels, please share them with me --> Twitter. Please do not hesitate to poke me if you have any question 😉