使用CAMediaTimingFunction计算时间点(t)的数值

15
在Cocoa/Touch中,CAMediaTimingFunction代表了四个控制点,这些点指定了一个时间函数的三次贝塞尔曲线。我正在编写一个应用程序,希望能够在任意时间t(0 -> 1)提取该贝塞尔曲线的结果。令我困惑的是,当我查找如何执行此操作时,结果也应该是一个点,而不是标量:

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

然而,苹果公司的实现结果是标量值(您可以在此图中看到它们绘制的 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 )。那么,苹果公司是否只忽略结果的 y 坐标并处理 x?这似乎很奇怪,因为你不需要传入控制点,而只需传入控制标量,因为 y 不会影响结果。

如果您能扩展问题的背景,我认为我可能更好地回答您的问题。您是否正在使用CAMediaTimingFunction来执行动画?如果是这样,它是关键帧动画吗? - jin
4个回答

13

CAMediaTimingFunction可以实现你想要的效果,但它不支持多功能(动画)使用中获取给定'x'的'y'值,而是将已解决的值不透明地传递给后台的动画系统。

我自己也需要它,所以构建了一个具有与CAMediaTimingFunction完全相同接口和功能的类,但具有所需的-valueForX:方法。以下是用法示例:

RSTimingFunction *heavyEaseInTimingFunction = [RSTimingFunction timingFunctionWithControlPoint1:CGPointMake(0.8, 0.0) controlPoint2:CGPointMake(1.0, 1.0)];
CGFloat visualProgress = [heavyEaseInTimingFunction valueForX:progress];
您可以创建缓入、缓出、缓入缓出或任何可以用三次贝塞尔曲线描述的曲线。实现的数学基础是基于WebCore(WebKit),这也可能是CoreAnimation在内部使用的东西。
祝好, Raphael

+1 干得好 :) - 你有没有想过 iOS 7 物理动画的实现原理?(animate...usingSpringWithDamping:..) - Robert
1
他们使用一个物理引擎——与SpriteKit共享——这个特定计算可能使用流行的RK4积分器。在这里,RK4积分器得到了很好的解释http://gafferongames.com/game-physics/integration-basics/,并提供了C++的优秀代码示例。 - Raphael Schaad
1
不确定你的意思,@Andy -- https://gist.github.com/raphaelschaad/6739676 在任何当前版本的iOS上都可以正常工作。 - Raphael Schaad
@RaphaelSchaad,我认为当我测试这个时,在iOS 7上出现了崩溃。 - pronebird
@RaphaelSchaad Core Animation实际上并没有使用物理引擎。弹簧动画只是使用了CASpringAnimation,这是一个简单的弹簧求解器。UIKit Dynamics、SpriteKit和SceneKit使用实际的实时物理引擎,但Core Animation只是提前计算所有关键帧值。 - CIFilter
显示剩余3条评论

6

很遗憾,但是Core Animation没有暴露其动画定时的内部计算模型。然而,对我来说非常有效的方法是使用Core Animation来完成工作!

  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
    }
    
基本上,您正在使用核心动画来运行不可见层的动画。但是该层的速度是0.0,因此它不会推进动画。使用timeOffset,我们可以手动调整动画的当前位置,然后获取其表示层的x位置。这代表由动画驱动的属性的当前感知值。
这有点不寻常,但没有任何非法行为。这是一个最忠实的CAMediaTimingFunction输出值的表示,因为Core Animation实际上正在使用它。
唯一需要注意的是呈现层接近于屏幕上呈现的值。Core Animation不保证其准确性,但在我多年使用核心动画的经验中,从未看到过不准确的情况。不过,如果您的应用程序需要绝对精度,则可能此技术可能不是最佳选择。

简单而聪明。喜欢它。 - mattsven
1
这种方法存在一个问题:表示层是异步更新的,因此根据您停止/开始从中请求值的时间,可能会得到意外的结果。 - mattsven
是的,绝对没错。这个解决方案并不完美,而且你要依赖于一堆不同的跨进程异步通信(你的应用程序和渲染服务器)。因此,在某些情况下,你可能会得到奇怪的结果。 - CIFilter

5

注意: 我不是CoreAnimation的专家。这只是我从阅读您提供的文档中所理解的。

苹果在这里混合了坐标系,这造成了一些混淆。

示例图中的 x(t) 表示沿某条路径的标量进展,而不是物理坐标。

CAMediaTimingFunction 中使用的控制点用于描述此进度,而不是几何点。为了增加混淆,在控制点中,x 实际上映射到绘图中的 t,而 y 则映射到绘图中的 x(t)

kCAMediaTimingFunctionEaseInEaseOut 的绘图为例,该绘图大致由控制点 (0,0), (0.5,0), (0.5,1), (1,1) 描述。


您的确是正确的,问题在于t代表x,而x(t)代表y。然而,现在的问题是如何在给定一些x的情况下找到y(目前似乎需要解决三次方程,这是一个相当困难的任务)。 - Francisco Ryan Tolmasky I
2
@Fransisco,你将其标记为答案,但正如你在评论中所述,真正的问题仍然是针对给定的x获取y。你解决了吗?干杯。 - epologee
1
@jin:实际上,我刚刚使用了CAMediaTimingFunction方法getControlPointAtIndex来查询kCAMediaTimingFunctionEaseInEaseOut时间函数的控制点,而控制点是(.42, 0)和(.58, 1)。(第一个和最后一个点始终为0,0和1,1) - Duncan C

1

最好的提示可能是 WebKit 源代码中的 UnitBezier.h。实际上,我猜 Apple 在 Core Animation 中使用相同的代码。

更详细地说,他们使用计算参数 t(与时间无关)给定值 x,该值实际上包含时间值。然后他们使用 Newton-Raphson 方法附加说明和示例)。由于它可能失败,他们会多次迭代到使用二分法(“分而治之”)。请参见 solveCurveX() 方法。

在获取参数 t(这是必要的,因为三次贝塞尔曲线是参数定义的)之后,他们只需使用它来计算 y。请参见 solve() 方法。

顺便说一句,我是 Cappuccino 的忠实粉丝,仍然希望 Atlas 能够发布 :-)


2020年1月更新:我在2012年实现Core Animation APIs时完成了这项工作。随意参考。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接