F-curve (timeline with keyframes)
Monkey Forums/Monkey Programming/F-curve (timeline with keyframes)
| ||
Hi, I'm trying to create a Curve class that returns a value for any arbitrary time, like this: GetValue(time). It contains Keyframe objects which are associated with frames, like this: (time=0, value=20) ----------- (time=15, value=50) ---------- (time=30, value=100) ----------- etc. If I use a IntMap<Keyframe> where the map's key is the keyframe's time, I can easily retrieve the values for existing keyframes, but.... how can I figure out the two closest existing keyframes to any arbitrary time? Maybe using a Map isn't the best approach? Stack? List? Help!!! Here's some code that doesn't work. The GetNext(time) and GetPrevious(time) methods return null, causing it to fail. Those methods are supposed to find the closest keyframes to 'time'. Class Curve Field timeline:= New IntMap<Keyframe> Method GetValue:Float(time:Float) If timeline.Contains(time) Return timeline.Get(time).value Else Local prv:Keyframe = GetPrevious(time) Local nxt:Keyframe = GetNext(time) Return Interpolate( prv.value, nxt.value, (time - prv.time) / (nxt.time - prv.time) ) Endif End Method Method GetPrevious:Keyframe(time) 'How do i figure out the closest Keyframe to 'time' ????????? 'This will yeld a NULL object, causing the program to fail at GetValue() End Method Method GetNext:Keyframe(time) 'How do i figure out the closest Keyframe to 'time' ????????? 'This will yeld a NULL object, causing the program to fail at GetValue() End Method Method AddKey(value:Float, time:Int) Local keyframe := New Keyframe keyframe.value = value keyframe.time = time timeline.Set(time, keyframe) End Method Method Interpolate:Float(a:Float, b:Float, x:Float) Return a * ( 1 - x ) + ( b * x ) End Method End Class Class Keyframe Field value:Float Field time:Int End Class Function Main() Local curve := New Curve() curve.AddKey(10,0) curve.AddKey(8,20) curve.AddKey(15,50) Print curve.GetValue(20) 'This is an existing keyframe time, so it returns the correct value Print curve.GetValue(40) 'Tries to get the value for frame 40 and fails miserably End Function |
| ||
you may have to iterate through the map to find the closest keyframes look into the map docs to find the iterator |
| ||
Hi, I'm doing something like that, but the problem is that it's awfully inefficient... it works fine for a handful of keyframes, but the more you add the longer it takes to find the nearest keys, since it has to basically iterate through the whole list. A long scene with a few full characters with multiple child parts each can be hundreds, if not thousands, of keyframes that need to be processed per Update... well, I'll go with this for now, and if I run into performance issues I can always "bake" or cache the animation - pre-calculate the whole curve, which would eat more memory but would also process faster. Here's my most recent example... run it to see a couple sample curves plotted. I can add this to the code archives, but I'm sure you guys can improve it before I do so! :-) Meanwhile, if anyone knows a good algorithm for this would be awesome! There's gotta be a more efficient way... Cheers! |
| ||
Some suggestions: Use a dynamic array of KeyFrames rather than a Map. Define the array fairly large, then in AddKey, double the size using array.Resize() if you need extra space. Get rid of the DegToRad and RadToDeg methods, and instead multiply by a constant float. Two reasons for this: 1) Putting degrees/radians calculations in a separate method is just that: another method call. The less method calls the better if you're compiling for Android (or any platform, really). 2) Division is expensive, and if you can cache the result, all the better. Precalculating 180/PI and PI/180 as separate Const definitions will be much faster as (correct me if I'm wrong, Mark) constants will be calculated and substituted at compile time. You're using calculations such as last.time/width and amplitude/height multiple times. Consider calculating them once and store them in local variables. Cache DeviceWidth() in Draw() before entering the Repeat loop. |