Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using CAMediaTimingFunction to calculate value at time (t)

In Cocoa/Touch, CAMediaTimingFunction represents four control points that specify a cubic bezier curve of a timing function. For an application I am writing I would like to be able to extract the result of said bezier curve at an arbitrary time t (0 -> 1). What is confusing me is that when I look up how to do this, the result is supposed to be a point as well, not a scalar:

B(t) = (1 - t) ^ 3 * P0 + 3 * (1 - t) ^ 2 * t * P1 + 3 * (1 - t) * t ^ 2 * P2 + t ^ 2 * P3

However, Apple's implementation results in a scalar value (you can see on this graph they plot x(t) vs t: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Animation_Types_Timing/Articles/Timing.html#//apple_ref/doc/uid/TP40006670-SW1 )

So does Apple simply ignore the y coordinate of the result and only deal with the x? This seems strange because then you wouldn't need to pass in control points but rather control scalars as the y's wouldn't influence the result at all.

like image 957
Francisco Ryan Tolmasky I Avatar asked Apr 11 '12 01:04

Francisco Ryan Tolmasky I


2 Answers

CoreAnimation's CAMediaTimingFunction does what you want but doesn't expose getting 'y' for a given 'x' for versatile (animation) use but rather just feeds the solved values opaquely to the animation system under the hood.

I needed it myself so built a class with the interface and capabilities exactly like CAMediaTimingFunction but with the needed -valueForX: method; usage example:

RSTimingFunction *heavyEaseInTimingFunction = [RSTimingFunction timingFunctionWithControlPoint1:CGPointMake(0.8, 0.0) controlPoint2:CGPointMake(1.0, 1.0)];
CGFloat visualProgress = [heavyEaseInTimingFunction valueForX:progress];

You can create ease-in, ease-out, ease-in-ease-out or really any curves that can be described with a cubic Bézier curve. The implementation math is based on WebCore (WebKit), which is presumably what CoreAnimation is using under the hood too.

Enjoy, Raphael

like image 108
Raphael Schaad Avatar answered Oct 17 '22 08:10

Raphael Schaad


It's unfortunate, but Core Animation doesn't expose its internal computational model for its animation timing. However, what has worked really well for me is to use Core Animation to do the work!

  1. Create a CALayer to serve as an evaluator
  2. Set its frame to ((0.0, 0.0), (1.0, 1.0))
  3. Set isHidden to true
  4. Set speed to 0.0
  5. Add this layer to some container layer
  6. When you want to evaluate any CAMediaTimingFunction, create a reference animation:

    let basicAnimation = CABasicAnimation(keyPath: "bounds.origin.x")
    basicAnimation.duration = 1.0
    basicAnimation.timingFunction = timingFunction
    basicAnimation.fromValue = 0.0
    basicAnimation.toValue = containerLayer.bounds.width
    
    referenceLayer.add(basicAnimation, forKey: "evaluatorAnimation")
    
  7. Set the reference layer's timeOffset to whatever normalized input value (i.e., between 0.0 and 1.0) you want to evaluate:

    referenceLayer.timeOffset = 0.3    // 30% into the animation
    
  8. Ask for the reference layer's presentation layer, and get its current bounds origin x value:

    if let presentationLayer = referenceLayer.presentation() as CALayer? {
        let evaluatedValue = presentationLayer.bounds.origin.x / containerLayer.bounds.width
    }
    

Basically, you're using Core Animation to run an animation for an invisible layer. But the layer's speed is 0.0, so it won't progress the animation at all. Using timeOffset, we can manually adjust the current position of the animation then get its presentation layer's x position. This represents the current perceived value of that property as driven by the animation.

It's a little unconventional, but there's nothing hacky about it. It's as faithful a representation of the output value of a CAMediaTimingFunction as you can get because Core Animation is actually using it.

The only thing to be aware of is that presentation layers are close approximations of the values presented on screen. Core Animation makes no guarantees as to their accuracy, but in all my years of using Core Animation, I've never seen it be inaccurate. Still, if your application requires absolute accuracy, it's possible this technique might not be the best.

like image 5
CIFilter Avatar answered Oct 17 '22 08:10

CIFilter