Declarative animation is a big topic. We'll scratch the surface here.
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:
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.
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.
name
is required - the Scene
object will be passed for you
automatically.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.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:
Then, we need to alter it in the render loop:
To put everything together, we can create a Functional Component (FC) that wraps
the box
tag with our new behaviors.
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:
rpm
a component propLet's use the power of Typescript to create some spinning box props that accepts RPM (not needed in JavaScript projects):
This will be the props that we pass to the box. Let's update the box component to use it:
And then update the hook:
Now that's a nice reusable component! We use it like this:
Let's try it. We should see one rotation every 6 seconds when rpm
is 10
(60/10=6).
rpm
even more reactiveIn 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:
You can try it out by choosing the RPM here and seeing that it alters the rotation speed accordingly.
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.