I’m not sure why you say this. The cubic Hermite interpolation in Babylon.js was added because of glTF which supports length. Where are you seeing Babylon.js not support tangent lengths?
If you look at this code, you can see that the animation processes tangent data with only one number (weight is missing)
The value of the tangent is the length of the tangent in 1D, so this is the weight.
It’s been a busy month for me personally, so I’m a little late to the discussion, sorry.
The first thing to discuss seems to be the difference between bezier and hermite.
We need to know the weight value to blend between two keyframes at a specific time in the animation.
So the interpolation must be defined as a function of the form
x = f(t)
(where x is the weight and t is the animation time)
So let’s look at the signature of the Scalar.Hermite function in Babylon.js.
public static Hermite(value1: number, tangent1: number, value2: number, tangent2: number, amount: number): number;
First of all, this function itself satisfies the form x = f(t), so we can use it for animation interpolation.
However, this function does not have the same degrees of freedom as bezier interpolation.
Let’s look at the signature of the BezierCurve.Interpolate function from Babylon.js.
public static Interpolate(t: number, x1: number, y1: number, x2: number, y2: number): number;
A BezierCurve has two-dimensional tangents in the form of x1, y1, x2, y2.
In Hermite, however, the tangent is represented by just one number.
The difference between the two functions is the presence or absence of length information for the tangent.
In order for Hermite to have the same number of degrees of freedom as Bezier, we need to do a separate Hermite Interpolation for x and t.
In other words
x = BezierCurve.Interpolate(t, tangentX1, tangentY1, tangentX2, tangentY2)
are the same as
x = Scalar.Hermite(0, tangentX1, 1, tangentX2, y), t = Scalar.Hermite(0, tangentY1, 1, tangentY2, y)
they will plot the same graph in the coordinate plane with x as the horizontal axis and t as the vertical axis
However, the expression using Hermite adds a new variable y and does not form a relationship between x and t in the form x = f(t)
.
This is why hermite interpolation can achieve the same results as bezier, but cannot be used for animation interpolation.
If you have any other feedback, I’d love to hear it.
I looked at BezierCurve.Interpolate
more carefully. The reason why this is different is because the curve is a 2D vector (like you said earlier) where the x component is time and the y component is the animation value. This part makes sense if that’s what creation tools are doing, which I am still confirming with @PatrickRyan.
This statement and pretty much all of the statements talking about tangent length is confusing to me. The length of a tangent is the magnitude of the tangent. The Hermite function is 1D. The length of a 1D tangent is the tangent value itself. I created a playground to show this: Babylon.js Playground (babylonjs.com). Change the tangentLength
variable to see a different curve.
I agree with this statement except for the word Bezier
. A cubic Bezier spline and cubic Hermite spline are different representations of a cubic spline. They have the same degrees of freedom. The distinction between the two ways of interpolating is whether the parametric parameter t
of the spline is being used for time and whether another spline is being used for time. I think this is where the confusion is coming from. I am interpreting the word Bezier to mean the mathematical term where I think you are using it for the 2D vector form of representing an animation curve.
After checking a few DCC tools (Maya, Max, Blender) with @PatrickRyan, we can see there are differences in how they approach animation curves. The simplest test we have come up with is trying to change the tangent to vertical. If the software is able to do this without creating massive values, then it is using a 2D vector. If it creates massive values when going vertical and can’t quite get to full vertical, then it is probably using a technique similar to glTF. We have found that Max matches glTF. Maya has two different modes, one that matches glTF, and one that uses 2D vectors. Blender seems to always use 2D vectors.
The arguments made above about being able to consume file formats that support this remains an issue, but it doesn’t seem like a requirement.
We also should try to do this efficiently at runtime. The current BezierCurve.Interpolate
has a hardcoded 5 iterations of Newton’s method to find the parametric t
value of the spline that corresponds to the requested time (the X component of the spline). I think we may be able to do better than this, perhaps by remembering the t
used in the previous frame. This can be done in a separate step though.
Certainly, this is a sentence that can be confusing.
I just assumed that hermite interpolation achieves similar results to bezier curves with fixed-length 2D vectors, so when I explained it in terms of bezier interpolation, the sentence became ambiguous
I can see how this optimization could be problematic if the results of the animation are (even slightly) inconsistent.
So, would you be positive about using bezier curves to animate with 2D tangent (a.k.a. weighted tangent) interpolation?
Now that the confusion is cleared up, I think I am okay with adding this, but I have some thoughts:
- What do other engines (three.js, PlayCanvas, Unity, Unreal, Godot) do for this? Do they support 2D vector Bezier curves at runtime?
- I was testing the implementation of our
BezierCurve.Interpolate
and there is an issue. If we take the playground I created earlier forScalar.Hermite
and change it to useBezierCurve.Interpolate
and use vertical tangents, it returnsNaN
for the start and end points: https://playground.babylonjs.com/#PLZV3Z#1. This looks like a bug in the math. - Given that
BezierCurve.Interpolate
always goes from0
to1
, the tangents being specified in theIAnimationKey
must also be normalized. Is that what we want? Answering question 1 might help with this.
-
-
Unreal Engine and Unity support 2D tangents.
-
three.js does not support them by default, but they are designed to allow users to override the interpolation implementation, so it is possible to use 2D tangents for interpolation.
https://github.com/mrdoob/three.js/blob/dev/examples/jsm/loaders/MMDLoader.js#L1955-L1961
-
PlayCanvas does not seem to support 2D tangent
-
-
This was actually an issue I was aware of. I was going to mention it later. I’m currently using a different bezier curve implementation for testing
-
I consider the tangents of animation keys to be normalized.
In practice, normalized and unnormalized tangents are interchangeable, regardless of whether they are normalized or not, so the user can have the same experience.However, the two benefits of normalized tangents make me think that normalization is the way to go.
- if two animation keys are zero distance from each other, the X dimension (time axis) of the tangent is lost.
- normalization is eventually needed for computation at animation runtime. If you already have normalized data, you won’t need to do any scaling, which means less costly animation evaluation
For this reason, I already implemented to handle normalized tangents in #14410.
This is the editor. What happens to this curve at runtime?
Where does the image show weights? Are you saying the tangents have a length? Having length on a tangent doesn’t mean it is using the 2D vector method we are discussing. This might be all moot if the runtime representation is different.
I don’t follow. I agree with this statement, but how is this relevant to whether the tangent is normalized?
I’m not sure this is true. The Bezier equations don’t have to be computed with ‘0’ and ‘1’.
That said, I am not saying using normalized tangents is the wrong thing to do. It would be good to know what other engines do.
@PatrickRyan helped me check this in Unity. Using their “weighted” mode, the tangents can have lengths, but it is not the 2D vector way as if you try to create a vertical tangent, the values will jump. I don’t think Unity has support for the 2D vector way.
I’m going to find out how the Unreal Engine is implemented…