Now Loading..

The F-Curve Enigma

1619010779

A Blender struggle about timing

@blender_org @PabloVazquez_ @BlenderDev is there a reason the curves inside of the curve editor are called bezier curves but refuse to work like them? the fact that I can't tighten a curve between two keyframes like in any other curve editor is infuriating pic.twitter.com/vVGXhBG2wS

— RHEES (@grunku) June 22, 2020

I was informed of this peculiar behavior of animation curves in Blender by grunku in MMaker’s short lived server a while ago. Since I’m so accustomed to After Effects animation curves, this does seem to be very weird. At the time, I didn’t think much of it, "maybe I’ll do some testing later" and never did.

A couple weeks ago I was reminded by a dear CH FR the immediate consequences of the fact that After Effects expressions are evaluated frame-by-frame with no memory. I was directed to Dan Ebberts’ MotionScript website (Of course!) on controlling speed and frequency with frame-by-frame evaluated values. Part of the article explains that eased keyframes require a much less efficient method in order to integrate the curve. I understood immediately that After Effects doesn’t offer a way to fetch the data of the tangents to a keyframe, otherwise, it shouldn’t be as hard to calculate the integral (since Bézier curves we see in graphics software are just cubic). From here, I suddenly thought of the complaint grunku had for Blender again (tangents of Bézier curves!). So I went to do some testing.

The issue here is that 4-point (2 keyframes + 2 tangents in between) Bézier curves like to make loops and be multivalued on the y axis many times when the tangents are positioned in a certain ways.

Crude Demo

A (very crude) test in Photoshop, this configuration of tangents results in multivalued Y.

This is obviously unusable for animation curves, on that frame where the loop happens, what is the value even supposed to be?

A very cool page about everything Bézier I came across when doing research

So to avoid this, After Effects simply does not let the tangents go past the range of the keyframes. Suppose the first keyframe is an array of x and y position v1, its tangent v2, tangent of the second keyframe v3, and the second keyframe itself v4: x component of v2 and v3 just live in the range between the x component of v1 and v4. This simple method does indeed prevent any multivalued configurations.

In Blender and Cinema 4D, they do it in another way: v2 and v3 are scaled back to v1 and v4 respectively based on their x distance away from their host keyframe and the x difference between v1 and v4. In my humble opinion, this is stupid. Most importantly, they make Bézier curves not behave like Bézier curves anymore.

Fast forward to today, I had the audacity to set up a Blender build environment and tried to implement the AE way in myself.

I found the function responsible for handling Bézier sanity :

void correct_bezpart(float v1[2], float v2[2], float v3[2], float v4[2])
{
  float h1[2], h2[2], len1, len2, len, fac;
  /* calculate handle deltas */
  h1[0] = v1[0] - v2[0];
  h1[1] = v1[1] - v2[1];
  h2[0] = v4[0] - v3[0];
  h2[1] = v4[1] - v3[1];
  /* calculate distances:
   * - len  = span of time between keyframes
   * - len1 = length of handle of start key
   * - len2 = length of handle of end key
   */
  len = v4[0] - v1[0];
  len1 = fabsf(h1[0]);
  len2 = fabsf(h2[0]);
  /* if the handles have no length, no need to do any corrections */
  if ((len1 + len2) == 0.0f) {
    return;
  }
  /* the two handles cross over each other, so force them
   * apart using the proportion they overlap
   */
  if ((len1 + len2) > len) {
    fac = len / (len1 + len2);
    v2[0] = (v1[0] - fac * h1[0]);
    v2[1] = (v1[1] - fac * h1[1]);
    v3[0] = (v4[0] - fac * h2[0]);
    v3[1] = (v4[1] - fac * h2[1]);
  }
}

in the file /source/blender/blenkernel/intern/fcurve.c

So I quickly changed this to how it behaves in AE.

void correct_bezpart(const float v1[2], float v2[2], float v3[2], const float v4[2])
{
  float h1, h2, len1, len2, len;
  /* calculate handle deltas */
  h1 = v1[0] - v2[0];
  h2 = v4[0] - v3[0];
  /* calculate distances:
   * - len  = span of time between keyframes
   * - len1 = length of handle of start key
   * - len2 = length of handle of end key
   */
  len = v4[0] - v1[0];
  len1 = fabsf(h1);
  len2 = fabsf(h2);
   /* forces handles to be within the range of keyframes */
  if (len1 > len) {
    v2[0] = v4[0];
  }
  if (len2 > len) {
    v3[0] = v1[0];
  }
}

It’s a lot simpler.

And I compiled my own version of Blender. I really want to take note how fast it was built, only 2 to 3 mins and it’s done (without CUDA and Optix, thanks to mmaker for pointing this out, lach RTFM!).

Demo

Vanilla 2.8

Vanilla Blender

My Fork

My build

It works!

What would be really nice is to have a option in the menu to choose which method to use. But modifying the UI code is a little out of reach at this moment.

Note that a side effect of this is that this function is also used in color curves, as pointed out by the absolutely based MMaker, so it might break those.

Extra thanks to MMaker for compiling help on Windows.

Postmortem

Turns out we were blackpilled HARD. MMaker messaged me today this particular Blender diff. It’s posted just 1 day before when I had mine working. TonyG posted this related issue back in APRIL, while I have only been aware of this issue since early August.

I was not aware of any of this at all when I was trying to solve it on my own even if SOMEHOW the code I ended up having looks like a exact character-to character carbon copy of the first version of that diff.

The world really is a strange place.

in the end everyone gets bézierpilled by TonyG

— lachrymaL (@lachrymaL_F) September 6, 2020

So still a happy ending in the end, everybody will get to use the good animation curves in 2.91. :D