Want to learn more?

That's the end of the free part of this lesson

WebGPU & TSL Course new $45 VAT incl.
  • 21 lessons · 24 hours of video
  • Quizzes · Certificate
  • Members-only Discord server · Future updates
00:00/00:00
3:22
00:03:22

Shortcuts ⌨️

  • SPACE to play / pause
  • ARROW RIGHT or L to go forward
  • ARROW LEFT or J to go backward
  • ARROW UP to increase volume
  • ARROW DOWN to decrease volume
  • F to toggle fullscreen
  • M to toggle mute
  • 0 to 9 to go to the corresponding part of the video
  • SHIFT + , to decrease playback speed
  • SHIFT + . or ; to increase playback speed

Unlock content 🔓

To get access to 93 hours of video, a members-only Discord server, subtitles, lesson resources, future updates and much more join us for only $95!

Want to learn more? 👍

94%

That's the end of the free part of this lesson

WebGPU & TSL Course new $45 VAT incl.
  • 21 lessons · 24 hours of video
  • Quizzes · Certificate
  • Members-only Discord server · Future updates
Next lesson
WebGPU & TSL
Chapter 02 — Advanced Projects

84. Anvil

Difficulty: Hard01:27:28

Introduction 00:00

This exercise is the opportunity to mix many Three.js and TSL features.

We’re going to:

  • Animate the hammer on click
  • Animate a point light to brighten the scene near the impact
  • Add and animate an emissive area on the blade to simulate heat
  • Make hundreds of sparkles explode, and bounce on the floor

It’s also finally the opportunity to truly utilize that anvil model.

And before you say anything, this animation is not physically accurate. Hammering down a metal piece does generate heat, but not enough to turn such a massive chunk of metal red hot, even less white hot. But that’s the cool part with creative coding, there is no limit to your imagination.

Setup 01:16

The starter already contains the following:

  • OrbitControls to rotate around
  • The same floor we used in previous lessons
  • A model composed of an anvil, a blade, and a hammer, as separated objects
  • Some lighting with shadows
  • A SkyMesh to have something to look at in the background
  • The WebGPURenderer with the RenderPipeline to add a bloom pass
  • An instance of the Inspector
  • A Timer instance with a clamped delta time

The model 01:59

The Blender file for the model is provided with the lesson.

As you can see in the Outliner (the area with the scene graph), each part of the model has a specific name:

  • hammer
  • anvil
  • blade

Having those names will allow us to retrieve them in the JavaScript.

Also note that the hammer’s origin and rotation are set so that we can rotate it on its local x axis to create the animation.

Hammer impact 03:16

Speaking of the hammer, let’s animate it.

We are going to put everything in the script.js file, no class, just delicious spaghetti code. Don’t worry, most features are quite simple.

Before the Animate section, create a Hammer section:

/**
 * Hammer
 */

In there, retrieve the hammer object from the loaded model, using the getObjectByName() method:

const hammer = model.scene.getObjectByName('hammer')
console.log(hammer)

We can access the model.scene directly after loading the model because we used loadAsync() which will hold the execution of the script until the promise (loading the model) is resolved. No need for a callback function.

We have our hammer object, remove the console.log():

const hammer = model.scene.getObjectByName('hammer')
// console.log(hammer)

We are going to animate it on click, but first, let’s give it a try in the tick to see how it behaves.

Increment the rotation.x by deltaTime in the tick function:

const tick = () =>
{
    // ...

    // Hammer
    hammer.rotation.x += deltaTime

    // Render
    // ...
}

The hammer is rotating weirdly because of the rotation order.

To fix it, we need to change the order so that the rotation applies first on the y axis (the vertical one), then on the x axis (the one we want to animate), and we don’t care about the z axis.

Use the reorder() method to set the rotation order to 'YXZ':

const hammer = model.scene.getObjectByName('hammer')
hammer.rotation.reorder('YXZ')

The rotation is working, but this is not Thor’s hammer, and we don’t want it to swing all the time like that.

For now, comment the rotation in the tick function:

const tick = () =>
{
    // ...

    // Hammer
    // hammer.rotation.x += deltaTime

    // Render
    // ...
}

Instead, we want to rotate it on click, and since we’re going to have a lot more than just the hammer rotation, let’s create a function dedicated to this.

After the Hammer section, create an Impact section:

/**
 * Impact
 */

In there, create an impact function with a simple console.log():

const impact = () =>
{
    console.log('impact')
}

And listen to the pointerdown event on the canvas to trigger it:

const impact = () =>
{
    console.log('impact')
}

canvas.addEventListener('pointerdown', impact)

Using the pointerdown event means that the impact will occur when we try to rotate the camera by drag and dropping. Since it’s for the demo, we won’t bother with this behavior.

If we see the impact in the Console, it’s working, and we can replace that console.log() by a 90° rotation on the hammer:

const impact = () =>
{
    // Hammer
    hammer.rotation.x = Math.PI * 0.5
}

The down movement won’t be animated. But the movement back to the initial position will. This way, we get a fast and immediate feedback of the action, and we can still enjoy an animation.

And to move the hammer to its initial position, we need to animate the rotation back to 0°. To do that, we are going to use the usual ease technique to move it a little bit closer to the target on each frame.

In the tick, ease the rotation.x of the hammer back to the 0 angle, and use the deltaTime to get a similar result regardless of the frame rate:

const tick = () =>
{
    // ...

    // Hammer
    hammer.rotation.x += (0 - hammer.rotation.x) * deltaTime

    // Render
    // ...
}

And since the target is 0, the formula can be simplified:

const tick = () =>
{
    // ...

    // Hammer
    hammer.rotation.x += - hammer.rotation.x * deltaTime

    // Render
    // ...
}

Blade 10:46

Let’s take care of something more challenging and create the blade heat effect.

Want to learn more?

That's the end of the free part of this lesson

WebGPU & TSL Course new $45 VAT incl.
  • 21 lessons · 24 hours of video
  • Quizzes · Certificate
  • Members-only Discord server · Future updates

How to use it 🤔

  • Download the Starter pack or Final project
  • Unzip it
  • Open your terminal and go to the unzip folder
  • Run npm install to install dependencies
    (if your terminal warns you about vulnerabilities, ignore it)
  • Run npm run dev to launch the local server
    (project should open on your default browser automatically)
  • Start coding
  • The JS is located in src/script.js
  • The HTML is located in src/index.html
  • The CSS is located in src/style.css

If you get stuck and need help, join the members-only Discord server:

Discord