Interpolation / Extrapolation of Animation Curves

Hi guys, does anyone have already have a code to interpolate / extrapolate curves for the given set of points and tangents to match values from Flame to share? I’m currently parsing Flame’s Timewarp node setups and need to calculate values from Timing / SpeedTiming animation curves. BSplines are fine with scipy but can’t quite get the math and difference between Natural and Hermite modes at the moment

If it were me, I would bake every frame before output in the animation editor, copy the resulting curve to an axis’ x position, select that channel and export raw from action.

That’s exactly what I’m going to do for the sake of time now, just to make users to bake it. I’m not aware if one can bake keyframes from python and especially in Timewarp FX, can’t see how do I get to the curve other then saving setup and parsing it. It is not the end of the world but if someone’d already been there would love to have a chat )

@kyleobley or @MikeV or @miles any ideas?

1 Like

Both Hermite and Natural are based on a predictive function based on the value and time of the keyframes with respect to the slope between them. The difference between them seems to be (all this I am deriving from experience, not any knowledge that has been passed to me) is that with Hermite, the slope of a keyframe that is inserted between 2 existing keyframes is always 1, where as with natural, its slope is calculated based on the arc of the curve(???). How well I’ve explained that may be dubious, but the end result is that when you insert a keyframe on an existing curve, it alters the curve in hermite, but keeps the existing curve in Natural (sort of). Whether keyframes are Hermite or Natural should make no difference when calculating the values anywhere on the curve. That said, the actual function that calculates the values is beyond my math skills.

1 Like

The first thing that comes to mind is if there’s a hotkey to bake keyframes, then that’d be the easiest way to do it as you can call those within a script. I vaguely recall a script somewhere, that would interpret timewarps from XMLs so perhaps there’s some good bits in there but I’ll have to see if I can find it. I think @julik wrote it…but I could be wrong.

EDIT: Have a look at GitHub - guerilla-di/flame_channel_parser: Extracts animation channels from Flame setups and computes their values

1 Like

Amazing, it is in Ruby but getting closer! Thank you!

There’s a python implementation if you go one-level up but I think it’s just to interface with the main bits which are Ruby.

1 Like

I wish you luck. I’ve been viciously burned in the past coming from final cut to flame. Mind this was in the early days of xmls where I discovered with discreet that different software have different interpolation methods.

1 Like

Julik’s stuff is great but largely based on pre-anniversary old school format with ‘new xml to old school’ converter. Looks like it has evolved a bit since then

Eventually it did work by feeding Julik’s Ruby script with modified setup that emulates old point-based handle values, thank you very much for the hint!

from xml.dom import minidom
xml = minidom.parse(args.setup)
keys = xml.getElementsByTagName(‘Key’)
for key in keys:
frame = key.getElementsByTagName(‘Frame’)
if frame:
frame = (frame[0].firstChild.nodeValue)
value = key.getElementsByTagName(‘Value’)
if value:
value = (value[0].firstChild.nodeValue)
rdx = key.getElementsByTagName(‘RHandle_dX’)
if rdx:
rdx = (rdx[0].firstChild.nodeValue)
rdy = key.getElementsByTagName(‘RHandle_dY’)
if rdy:
rdy = (rdy[0].firstChild.nodeValue)
ldx = key.getElementsByTagName(‘LHandle_dX’)
if ldx:
ldx = (ldx[0].firstChild.nodeValue)
ldy = key.getElementsByTagName(‘LHandle_dY’)
if ldy:
ldy = (ldy[0].firstChild.nodeValue)

lx = xml.createElement(‘LHandleX’)
lx.appendChild(xml.createTextNode(’{:.6f}’.format(float(frame) + float(ldx))))
key.appendChild(lx)
ly = xml.createElement(‘LHandleY’)
ly.appendChild(xml.createTextNode(’{:.6f}’.format(float(value) + float(ldy))))
key.appendChild(ly)
rx = xml.createElement(‘RHandleX’)
rx.appendChild(xml.createTextNode(’{:.6f}’.format(float(frame) + float(rdx))))
key.appendChild(rx)
ry = xml.createElement(‘RHandleY’)
ry.appendChild(xml.createTextNode(’{:.6f}’.format(float(value) + float(rdy))))
key.appendChild(ry)

xml_string = xml.toxml()

2 Likes

Glad it was helpful, this software is old but looks like it still can do the thing it was supposed to :blush: You can port them to python if you want, the stuff that is important is all in the segments.rb and you won’t need dependencies I think.

I think Natural just adjusts your tangents in such a way that the curves on both sides do not overshoot, on the level of the produced value it’s still Hermite.

1 Like

This code is of a great help, thank you so much!
Another thing I can’t quite get precise is solving the value at specific frame from Speed / SpeedTiming pair when doing vari-speed timewarps. SpeedTiming gives me anchor frame values in the beginning and the end of the curve. Then I calculate next frame value as: next_frame = anchor_frame + (speed(anchor_frame) + speed(anchor_frame +1)) / 200 and on to the last frame of the curve.
That brings me pretty close to what Flame does but not exactly there and error accumulates across long spans. I wonder if anyone may have any insights on how exactly Flame does it

Looks like something we should implement in one of the framecurve gems :slight_smile: we should go on screensharing sometime and try to fix it.

Speed timewarps == evil.

1 Like

That would be great. I’d love to do it once you have time!