· Create the world is committed to creating the first “cloud CAD” collaborative design platform integrating viewing, modeling, assembly and rendering in China.

At the request of readers, we hope to set up a professional WEBGL and Threejs industry QQ communication group for front-end developers in Chengdu-Chongqing area to facilitate discussion. There are webGL and Threejs in the group, welcome to join! — Click the link to join the group chat [three.js/ webGL Chongqing Union Group] : jq.qq.com/?_wv=1027&k…

The authors introduce

Xiao Gang, a big front end r&d engineer of Cloud map, is responsible for the development of 3d front end of cloud map.

preface

In previous articles, we introduced basic concepts such as geometry, materials, and light. We can use this knowledge to create some simple scenes. In this article, we will share with you how to animate our scenes.

The body of the

First, animation introduction

In Threejs we use the renderer.render method to draw the scene, which takes the scene and camera as input, and a single still image into the HTML< Canvas element >. The output is a purple box that you can see is not moving.

render() {
  // draw a single frame
  renderer.render(scene, camera);
}
Copy the code

This time, we will add some simple rotation animations to the cube. Think about the process of adding animations

  • callrender.render(...)
  • Wait until the next frame is drawn
  • Rotate the cube a bit
  • callrender.render(...)
  • Wait until the next frame is drawn
  • Rotate the cube a bit
  • .

In an infinite loop in the animation loop, set up the cycle is very simple, because threejs by the renderer. SetAnimationLoop method finished all the work for us.

We’ll also look at Clock, a simple stopwatch class that we can use to keep animations in sync, in milliseconds (ms).

Once we set up the loop, our goal is to generate a steady stream of frames at 60 frames per second, which means we need.render to be called about every 16 milliseconds. In other words, we need to make sure that everything we do in a frame takes less than 16 milliseconds. So before you need to update animations, perform any other tasks that require cross-frame computation, and render frames in less than 16 milliseconds on the lowest spec hardware we intend to support. We’ll discuss how best to do this in a later section, when we set up the loop and create a simple rotation animation for the cube.

Create an animation loop using THREEJS

1. Loop. Js module

Create a new Loop class that will handle all of the Loop logic and animation systems, first importing Clock, using it to keep animations in sync, and then using Renderer.render (scene,camera) to generate frames. Finally, create methods start and stop to start/stop the loop

import { Clock } from 'three';
class Loop {
  constructor(camera, scene, renderer) {
    this.camera = camera;
    this.scene = scene;
    this.renderer = renderer;
  }
  start() {}
  stop(){}}export { Loop }
Copy the code

In World, import Loop

import { createCamera } from './components/camera.js';
import { createCube } from './components/cube.js';
import { createLights } from './components/lights.js';
import { createScene } from './components/scene.js';

import { createRenderer } from './systems/renderer.js';
import { Resizer } from './systems/Resizer.js';
import { Loop } from './systems/Loop.js';
Copy the code

Make the loop a property of the World, accessible throughout the scene

let camera;
let renderer;
let scene;
let loop;

class World {
  constructor(container) {
    camera = createCamera();
    renderer = createRenderer();
    scene = createScene();
    loop = newLoop(camera, scene, renderer); container.append(renderer.domElement); . }Copy the code

Finally add.start and.stop to World

render() {
  // draw a single frame
  renderer.render(scene, camera);
}
start() {
  loop.start();
}
stop() {
  loop.stop();
}
Copy the code

Call world.render and world.start in main.js

function main() {
  // Get a reference to the container element
  const container = document.querySelector('#scene-container');
  // create a new world
  const world = new World(container);
  // draw the scene
  world.render();
 // start the animation loop
  world.start();
}
Copy the code

At this point, the whole scene will go black, but don’t worry, it will come back to life once we finish creating the loop.

2. Create a loop.setAnimationLoop

Use of threejs WebGLRenderer setAnimationLoop

import { WebGLRenderer } from 'three';
const renderer = new WebGLRenderer();
// start the loop
renderer.setAnimationLoop(() = > {
  renderer.render(scene, camera);
});
Copy the code

Renderer. Render is called over and over again to generate a stream of frames, and null can be used as a callback to cancel the running loop

// stop the loop
renderer.setAnimationLoop(null);
Copy the code

Internally, the loop uses.requestAnimationFrame, a built-in browser method that intelligently synchronizes frames with the monitor’s refresh rate and smoothly lowers the frame rate if your hardware can’t keep up. Since.setAnimationLoop is a recent addition, older three.js examples and tutorials usually use.requestAnimationFrame directly to set the loop, which is very simple to do.

Loop.start and loop. stop methods

Now to create the loop, use setAnimationLoop

start() {
  this.renderer.setAnimationLoop(() = > {
    // render a frame
    this.renderer.render(this.scene, this.camera);
  });
}
Copy the code

Create the corresponding stop method

stop() {
  this.renderer.setAnimationLoop(null);
}
Copy the code

At this point, the scene will start to output frames at about 60fps, but you won’t see any difference. Why? Review what we did earlier

  • callrender.render(...)
  • Wait until the next frame is drawn
  • callrender.render(...)
  • Wait until the next frame is drawn

Do you notice something missing from the loop we described at the beginning of this article, that’s right, rotating the cube a little bit, so let’s do some preparatory work

4. Remove onResize hook

First, let’s tidy up. Now the loop is running, and every time we resize the window, a new frame is generated in the next iteration of the loop. There doesn’t seem to be any lag, so it’s no longer necessary to manually redraw the scene when resizing. Remove the resizer.onresize hook from the world

constructor(container) {
 camera = createCamera();
 scene = createScene();
 renderer = createRenderer();
 container.append(renderer.domElement);
 const cube = createCube();
 const light = createLights();
 updatables.push(cube);
 scene.add(cube, light);
 const resizer = new Resizer(container, camera, renderer);
 resizer.onResize = () = > {
   this.render();
 };
}
Copy the code

Animation system

Consider a simple game where the user can explore a map and pick an Apple. Here are some animated objects you can add to this game:

  • Heroine, has various animations such as walk/run/jump/climb/pick.
  • The apple tree. As the apple grows, its leaves flutter in the wind.
  • Some scary bees will try to drive you out of the garden.
  • An interesting environment containing objects such as water, wind, leaves and rocks.
  • Charge in the form of a rotating cube hovering over the ground.

… And so on. Each time the loop runs, we want to update all of these animations by moving them forward one frame. Just before we render every frame, we will make the heroine step forward a little, we will make every bee to her mobile, we will make the leaves move, apple grew up, props, each one is a little, tiny amount is almost naked eye cannot see, but with the passage of time will produce smooth animation effects.

1. Loop. Tick method

To handle the above, we need a function that updates all animations, and this function should be run once at the start of each frame. However, the word update is already in heavy use throughout three.js, so we will choose the word tick. Before drawing each frame, we move each animation forward one frame. Loop.tick adds the method Loop to the end of the class and calls it in the animation Loop:

start() {
  this.renderer.setAnimationLoop(() = > {
    // tell every animated object to tick forward one frame
    this.tick();
    // render a frame
    this.renderer.render(this.scene, this.camera);
  });
}
stop() {
  this.renderer.setAnimationLoop(null);
}
tick() {
  // Code to update animations will go here
}
Copy the code

When implementing the tick, we need to think about whether we want to call it in different places in our application or to have all the ticks in one place

2. Centralized or decentralized

Centralized — This might be ok if we only have a few animated objects in our scene. When you have fifty or a hundred animated objects, it can be very messy. It also breaks all kinds of software design principles, because Loop now has to dig deep into how each animated object works.

tick() {
  if(controls.state.run) {
    character.runAnimation.nextFrame();
  }

  beeA.moveTowards(character.position);
  beeB.moveTowards(character.position);
  beeC.moveTowards(character.position);

  powerupA.rotation.z += 0.01;
  powerupB.rotation.z += 0.01;
  powerupC.rotation.z += 0.01;

  leafA.rotation.y += 0.01;

  // ... and so on
}
Copy the code

Decentralized – The logic that updates each object is defined on the object itself. Each object exposes this logic using its own generic.tick method. For now, the loop. tick method will be simple. Every frame, we’re going to iterate over a list of animated objects and tell them each.tick forward one frame.

// somewhere in the Loop class:
this.updatables = [character, beeA, beeB, beeC, powerupA, powerupB, powerupC, leafA, ... ] .tick() {
  for(const object of this.updatables) { object.tick(); }}Copy the code

Obviously, decentralization is more in line with the modular philosophy of designing applications, where each object is designed as a separate entity, and then its behavior is encapsulated on that entity

3. Animate object list

We need a list of animated objects in the loop class. To do this, we’ll use a simple array, which we’ll call List Updatables.

constructor(camera, scene, renderer) {
  this.camera = camera;
  this.scene = scene;
  this.renderer = renderer;
  this.updatables = [];
}
Copy the code

Next, within loop. tick, iterates through the list and calls any object in.tick.

tick() { for (const object of this.updatables) { object.tick(); }}Copy the code

4. Cube. Tick method

Before adding cube to the Updatables list, it needs a.tick method, so go ahead and create one. In this. Tick method, the logic of the rotation cube is defined.

Each type of animated object has a different.tick method. Method such as the tick of the heroine will check she is walking, running, jumping, or stood motionless, then play a frame from one of the animation, and the tree of the tick method will check apple’s maturity and the leaves rustled, evil bees every tick method can check the location of the heroine, then the bees move a little to her. If she gets close enough, the bees will try to sting her.

Here, we will simply update the cube in X, yes, and Z a little per frame axis. This will make it look like it’s rolling randomly.

function createCube() {
  const geometry = new BoxBufferGeometry(2.2.2);
  const material = new MeshStandardMaterial({ color: 'purple' });
  const cube = new Mesh(geometry, material);

  cube.rotation.set(-0.5, -0.1.0.8);

  // this method will be called once per frame
  cube.tick = () = > {
    // increase the cube's rotation each frame
    cube.rotation.z += 0.01;
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;
  };

  return cube;
}
Copy the code

5. Add cube toLoop.updatables

In World, add the cube to the Loop. Updatables list

constructor(container) {
  camera = createCamera();
  renderer = createRenderer();
  scene = createScene();
  loop = new Loop(camera, scene, renderer);
  container.append(renderer.domElement);

  const cube = createCube();
  const light = createLights();

  loop.updatables.push(cube);

  scene.add(cube, light);

  const resizer = new Resizer(container, camera, renderer);
}
Copy the code

And then you can see that the cube is moving

Write in the last

Use animations in Threejs to liven up a scene and increase interaction. Here are some of the simplest ways to use animations in Threejs. If you’re interested, go ahead and try creating more fun and cool effects.