Declarative Animation

Declarative animation is a big topic. We'll scratch the surface here.

The declarative animation loop

One way you can achieve an animation with react-babylonjs is by using the Engine render loop, which runs many times per second. You can receive callbacks for this loop by using the useBeforeRender hook. As its name implies, it is called before each frame render and allows you to make any changes needed to simulate animations.

The beauty of Component based design is that each React component can register their own animation loop handler and take responsibility just for its small task.

Here's some basic usage:

const scene = useScene()
useBeforeRender(() => {
if (scene) {
// This gets called ~60 FPS or whatever your frame rate is.
// Don't do a lot in here, just essentials
}
})

Degrees of Freedom

Every object in 3D has a position (x, y, z) in space and a rotation (roll, pitch, yaw). As google states it:

Rotational movement is relative to the x, y, and z axes, commonly termed pitch, yaw, and roll. The position corresponds to translational movement along those axes, which can be thought of as moving forward or backward, moving left or right, and moving up or down.

You can read a lot about this elsewhere, so we won't cover it in much depth here. The main point to take away is that animation involves affecting one or more of these degrees of freedom on each object (animations can also be for colors, brightness, etc.).

Let's try useBeforeRender to adjust these properties.

Creating the famous rotating box

Using a box as an example - we will rotate it about the Y axis, known as yaw. And, we'll do it many times per second.

Here's how to make a box (<box ... /> (recall from previous guide how it uses the Babylon.js CreateBox). We need to make sure we are passing required constructor (or factory method) arguments.

  • Only name is required - the Scene object will be passed for you automatically.
  • Properties of the returned object of type Mesh can be set as well (ie: position, rotation). Changes to those properties will flow to the underling mesh - just like changing a DOM element attribute works.
<box name="box" size={2} position={new Vector3(0, 1, 0)} />

Now to make it spin. We will make the box spin about the Y axis. From Mesh rotation in Babylon we see that the Y axis is the vertical axis.

To achieve animation, we have to alter the box's Y rotation. Ok, let's do it. First let's try using state, so we need a state variable to hold the Y value:

const [y, setY] = useState(0)

Then, we need to alter it in the render loop:

useBeforeRender(() => {
setY((oldY) => oldY + 0.1)
})

To put everything together, we can create a Functional Component (FC) that wraps the box tag with our new behaviors.

import { Vector3 } from '@babylonjs/core/Maths/math.vector'
import React, { FC, useState } from 'react'
import { useBeforeRender } from 'react-babylonjs'
const RotatingBox: FC = () => {
const [y, setY] = useState(0)
useBeforeRender(() => {
setY((oldY) => oldY + 0.1)
})
return (
<box
name="box"
size={2}
position={new Vector3(0, 1, 0)}
rotation={new Vector3(0, y, 0)}
/>
)
}

Using real-world timing in animations

The rotating box is fun, but it would be nice to control rotation speed in terms of real-world time (ie: smooth animation). Also, different devices will rotate different speeds depending on frame rate this way!

Let's adjust the rotation to Revolutions Per Minute (RPM). To do that, we need to know that one complete revolution of yaw is Math.PI / 2.

We also need to know how many milliseconds have passed between animation frames (since it is not consistent). Fortunately Babylon.js provides a getDeltaTime() that provides that duration. Using all that information, we can do this instead:

const RPM = 5
setY((oldY) => oldY + (RPM / 60) * Math.PI * 2 * (deltaTimeInMillis / 1000))

Making rpm a component prop

Let's use the power of Typescript to create some spinning box props that accepts RPM (not needed in JavaScript projects):

type RotatingBoxProps = {
rpm: number
}

This will be the props that we pass to the box. Let's update the box component to use it:

const RotatingBox: FC<RotatingBoxProps> = ({ rpm }) => ...

And then update the hook:

useBeforeRender(() => {
// ...
setY((oldY) => oldY + (rpm / 60) * Math.PI * 2 * (deltaTimeInMillis / 1000))
})

Now that's a nice reusable component! We use it like this:

<RotatingBox rpm={10} />

Let's try it. We should see one rotation every 6 seconds when rpm is 10 (60/10=6).

Making rpm even more reactive

In the interests of completeness, you might notice the hook is referring to rpm. If rpm were to change, we would want to make sure that new value took effect. Therefore, just like useEffect, rpm is a dependency:

useBeforeRender(
() => {
// ...
},
undefined,
undefined,
undefined,
[rpm]
)

You can try it out by choosing the RPM here and seeing that it alters the rotation speed accordingly.

Direct object manipulation (not via state)

The state props will flow on the reconciler schedule and changing state in the render loop can have very noticeable performance implications.

Directly altering Babylon.js object properties via refs bypasses that overhead and will be a more accurate animation smoothing. Whereas the reconciler will schedule the updates using it's own algorithm, this method will immediately apply the update. It should be noted as well that this will not cause re-renders of the component itself, which was innefficiently creating new Vector3 objects every render in above examples.

There is also in Example->Basic->Animations that uses an Animation object that can be looped and has easing functions, etc.