Babylongjs - Animation advanced API

Posted by simon13 on Sat, 25 Dec 2021 17:44:48 +0100

From Babylon js v3. 3, you can use promise to wait for the animation to end:

var anim = scene.beginAnimation(box1, 0, 100, false);

console.log("before");
await anim.waitAsync();
console.log("after");

Control animation

Each Animation has an attribute called currentFrame that indicates the current Animation key.

For advanced keyframe animation, you can also define functions to insert (transition) between keyframes. By default, these functions are as follows:

BABYLON.Animation.prototype.floatInterpolateFunction = function (startValue, endValue, gradient) {
  return startValue + (endValue - startValue) * gradient;
};

BABYLON.Animation.prototype.quaternionInterpolateFunction = function (startValue, endValue, gradient) {
  return BABYLON.Quaternion.Slerp(startValue, endValue, gradient);
};

BABYLON.Animation.prototype.vector3InterpolateFunction = function (startValue, endValue, gradient) {
  return BABYLON.Vector3.Lerp(startValue, endValue, gradient);
};

I don't quite understand here. Some people use these later to fill the pit.

Auxiliary function

You can use extension functions to create quick animations:

Animation.CreateAndStartAnimation = function(name, mesh, targetProperty, framePerSecond, totalFrame, from, to, loopMode);

In order to be able to use this feature, you need to know:

  • Your animation will have predefined keyframes (only 2 keyframes are generated: start and end)
  • Animation applies only to AbstractMesh objects.
  • The animation starts immediately after the method call.

This is a simple example of using the CreateAndStartAnimation() function:

BABYLON.Animation.CreateAndStartAnimation("boxscale", box1, "scaling.x", 30, 120, 1.0, 1.5);

Animation blending

You can start the animation with enableBlending = true to enable blending mode. This blend animation interpolates from the current object's state. This is very convenient for user controlled walking roles or responding to changes in values from input devices.

In the following playground demonstration, each time you click the FPS tag, the new animation blends with the current position of the box:

Babylon.js PlaygroundBabylon.js playground is a live editor for Babylon.js WebGL 3D sceneshttps://playground.babylonjs.com/#IQN716%239 Animation weights:

Start an animation with a specific weight. This means that you can use this API to run multiple animations simultaneously on the same target. The final value will be a blend of all animations weighted based on the weight value.

To start the animation with weights, you can use the new scene beginWeightedAnimationAPI:

/ Will have a weight of 1.0
var idleAnim = scene.beginWeightedAnimation(skeleton, 0, 89, 1.0, true);
// Will have a weight of 0
var walkAnim = scene.beginWeightedAnimation(skeleton, 90, 124, 0, true);
// Will have a weight of 0
var runAnim = scene.beginWeightedAnimation(skeleton, 125, 146, 0, true);

This API has the following parameters:

target:

from: start frame

to: end frame

Weight: the weight of this animation. Default 1.0

Loop: if true, the animation will loop (depending on BABYLON.Animation.ANIMATIONLOOPMODE)

speedRatio: default : 1. This animation is faster than

onAnimationEnd: a function triggered at the end of the animation, even if the animation is stopped manually (also dependent on ANIMATIONLOOPMODE)

animatable: an optional specific animation

Like beginAnimation, this function returns an animation, but this time its weight property is set to a value.

You can also set the value of weight any Animatable at any time to switch to weighting mode. The value must be between 0 and 1. Similarly, you can set it to - 1 to turn off weight mode. If you set the weight to 0, the animation is considered paused.

var idleAnim = scene.beginWeightedAnimation(skeleton, 0, 89, 1.0, true);
var runAnim = scene.beginWeightedAnimation(skeleton, 125, 146, 0, true);

idleAnim.weight = 0.5;
runAnim.weight = 0.5

If you have different animation sizes (the same distance from and to keys), you need to turn on animation synchronization using the following code:

idleAnim.syncWith(runAnim);

To disable animation synchronization, simply call animation syncWith(null).

Add blend animation

So far, the type of animation blending we have discussed is overlay blending. This means that adding influence to an animation affects other animations that are playing. The results are always standardized, so the more animations you play at the same time, the less impact each individual animation has on the final result. All keys in the overlay animation are stored relative to the object's parent. For example, suppose you have an object with 2 overlay animations. The first animation has a translation value of [0,0,0] at frame 0, then interpolates it into [0,2,0] upper frame 30, and then returns to [0,0,0]. The translation value of the second animation of frame 60 is [0,0,0], at frame 0, inserted into [0,0,2] at frame 30, and then returns to [0,0,0] at frame 60. If you play these animations at full weight at the same time, frame 30 will produce [0,1,1] of the translation value. Neither the Y-axis nor the Z-axis can fully reach the value of 2 when two animations are played. This behavior is very suitable for switching between animations, such as mixing from walking to running, but what if you want the actions to be based on each other? This is where additional animation becomes useful.

Additional animation is unique because it does not use that type of normalization logic. You can play N additional animations at the same time, and each animation has a specified exact amount of influence. To achieve this, the additional animation values are relative to the current result of the overlay animation, not the parent animation. Therefore, if the second animation in the above example is to be superimposed, the value of frame 30 will be [0,2,2], because the value of the second animation is added on the basis of the first animation.

There are several ways to specify an additional evaluation of the animation. First, an optional Boolean isAdditive parameter has been added to all scene methods used to start animation. see Scene API documentation To view the latest list of parameters for each method. By default, this parameter is false and will set the value generated by isAdditive Animatable New Boolean property for. This isAdditive property controls whether an evaluation Animatable should be attached and can be changed at any time. AnimationGroups Now there is also an accessor whose isAdditive defaults to false. Setting this accessor will set the properties of all Animatables controlled by isAdditive group.

One problem of additional animation is the creation of hierarchy. Because additional animations are evaluated relative to the results of other animations rather than the object's parents, it is not intuitive to create them directly. To alleviate this burden, make automation additive has added static methods to AnimationGroup,Skeleton and Animation Class. These methods allow you to specify a frame in an existing animation and subtract it from the rest of the keyframes in the animation so that they are all related to that particular pose.

The following example demonstrates how to convert animations to additional animations and blend them over overlay animations. The UI button allows you to blend between multiple overlay animations, and the slider blends in the additional animation at the top.

Override properties

When you have a mesh with multiple animations or a skeleton (all bones can be animated), you can use animationPropertiesOverride to specify some common properties for all sub animations. These properties override the local animation properties:

var overrides = new BABYLON.AnimationPropertiesOverride();

overrides.enableBlending = true;
overrides.blendingSpeed = 0.1;

skeleton.animationPropertiesOverride = overrides;

The following is a list of attributes that can be overridden:

  • Enable blending
  • Mixing speed
  • Cycle mode

Note that if the animation target does not contain one, scene. Is used animationPropertiesOverride.

Jog function

You can use the jog function to add some behavior to the animation. If you want to know more about the jog function, here are some useful links:

All these jogging functions are implemented in BABYLON, which allows you to apply custom mathematical formulas to animation.

The following are predefined jog functions that you can use:

  • BABYLON.CircleEase()
  • BABYLON.BackEase(amplitude)
  • BABYLON.BounceEase(bounces, bounciness)
  • BABYLON.CubicEase()
  • BABYLON.ElasticEase(oscillations, springiness)
  • BABYLON.ExponentialEase(exponent)
  • BABYLON.PowerEase(power)
  • BABYLON.QuadraticEase()
  • BABYLON.QuarticEase()
  • BABYLON.QuinticEase()
  • BABYLON.SineEase()
  • BABYLON.BezierCurveEase()

You can use the EasingMode property to change the behavior of the jog function, that is, to change the interpolation of the animation. You can provide three possible values for EasingMode:

  • BABYLON.EasingFunction.EASINGMODE_EASEIN: the interpolation follows the mathematical formula related to the slow motion function.
  • BABYLON.EasingFunction.EASINGMODE_EASEOUT: interpolation follows 100% interpolation minus the output of the formula associated with the jog function.
  • BABYLON.EasingFunction.EASINGMODE_EASEINOUT: Interpolation uses EaseIn in the first half of the animation and EaseOut in the second half.

This is a simple example of animating a torus in the CircleEase jog function:

//Create a Vector3 animation at 30 FPS
var animationTorus = new BABYLON.Animation("torusEasingAnimation", "position", 30, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);

// the torus destination position
var nextPos = torus.position.add(new BABYLON.Vector3(-80, 0, 0));

// Animation keys
var keysTorus = [];
keysTorus.push({ frame: 0, value: torus.position });
keysTorus.push({ frame: 120, value: nextPos });
animationTorus.setKeys(keysTorus);

// Creating an easing function
var easingFunction = new BABYLON.CircleEase();

// For each easing function, you can choose between EASEIN (default), EASEOUT, EASEINOUT
easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);

// Adding the easing function to the animation
animationTorus.setEasingFunction(easingFunction);

// Adding animation to my torus animations collection
torus.animations.push(animationTorus);

//Finally, launch animations on torus, from key 0 to key 120 with loop activated
scene.beginAnimation(torus, 0, 120, true);

You can also use the BezierCurveEase(x1, y1, x2, y2) function to play the Bezier curve algorithm. For this purpose, this is a good reference for creating curve algorithm: http : //cubic-bezier.com

This is a very cool implementation using Bessel curve algorithm:

 

var bezierEase = new BABYLON.BezierCurveEase(0.32, -0.73, 0.69, 1.59);

Finally, you can extend EasingFunction base function to create your own jog function, as shown below:

var FunnyEase = (function (_super) {
  __extends(FunnyEase, _super);
  function FunnyEase() {
    _super.apply(this, arguments);
  }
  FunnyEase.prototype.easeInCore = function (gradient) {
    // Here is the core method you should change to make your own Easing Function
    // Gradient is the percent of value change
    return Math.pow(Math.pow(gradient, 4), gradient);
  };
  return FunnyEase;
})(BABYLON.EasingFunction);

 

Attach events to animation

From Babylon JS version 2.3, you can Animation event Attach to Animated Specific frame.

Events are functions that will be called at a given frame.

This is very simple:

// 3 parameters to create an event:
// - The frame at which the event will be triggered
// - The action to execute
// - A boolean if the event should execute only once (false by default)
var event1 = new BABYLON.AnimationEvent(
  50,
  function () {
    console.log("Yeah!");
  },
  true,
);
// Attach your event to your animation
animation.addEvent(event1);

Deterministic lock step

Sometimes it is important to ensure that animation, physics, and game logic code are synchronized and decoupled by frame rate differences. Given the same initial conditions and inputs, this may be useful for being able to replay how the scene evolves or minimize the differences between multiple clients in a multi-user environment.

The principle is to quantify the state execution time, update the state with fixed frequency and discrete time steps, and retain an accumulator to carry forward the exceeded time to the next frame update.

To do this, you need to create a Babylon engine through the following two options:

this.engine = new BABYLON.Engine(theCanvas, true, {
  deterministicLockstep: true,
  lockstepMaxSteps: 4,
});

In this way, the scene will render and quantify the physical and animation steps according to the discrete blocks of the timeStep amount set in the physics engine. For example:

var physEngine = new BABYLON.CannonJSPlugin(false);
newScene.enablePhysics(this.gravity, physEngine);
physEngine.setTimeStep(1 / 60);

Using the above code, the engine will run at a frequency of 60Hz (0.0166667s), and in the case of delayed frame rendering time, it will try to calculate up to 4 steps (lockstepMaxSteps) to recover the final cumulative delay, and then render the frame.

Note that when explicitly creating a cannon jsplugin, it is important to have false in its constructor_ The useDeltaForWorldStep parameter is passed to disable the cannon JS internal accumulator.

In order to run the logic code synchronously with the step, there are two observable objects in the scene:

newScene.onBeforeStepObservable.add(function (theScene) {
  console.log("Performing game logic, BEFORE animations and physics for stepId: " + theScene.getStepId());
});

newScene.onAfterStepObservable.add(function (theScene) {
  console.log("Performing game logic, AFTER animations and physics for stepId: " + theScene.getStepId());
});

Using them allows arbitrary logical code to run before and after animation and physical updates.

In the following example, you can see the stepId when the sphere is considered stationary and the rotation value of the rotation box in the console. Multiple runs will always produce the same value regardless of the frame rate.

The effect is similar to frame synchronization

Topics: Javascript Front-end