根据贝塞尔曲线插值间隔,插值之间的值

Rak*_*aks 5 math animation bezier core-animation

为了实现2D动画,我正在寻找两个关键帧之间的插值,其中变化的速度由贝塞尔曲线定义.问题是贝塞尔曲线以参数形式表示,而要求是能够评估特定时间的值.

详细说来,假设10和40的值将在4秒内插值,其值不是经常变化,而是由表示为0,0 0.2,0.3 0.5,0.5 1,1的贝塞尔曲线定义.现在如果我以每秒24帧的速度绘制,我需要评估每帧的值.我怎样才能做到这一点 ?我看了De Casteljau算法并且认为将曲线分成24*4个片段4秒将解决我的问题但是这听起来是错误的,因为时间沿着"x"轴而不是沿着曲线.

进一步简化如果我在平面中绘制曲线,x轴表示时间,y轴表示我正在寻找的值.我实际需要的是能够找出对应于"x"的"y".然后我可以在24个分区中划分x并知道每个帧的值

Dan*_*olf 5

我面临着同样的问题:每个动画包似乎都使用 B\xc3\xa9zier 曲线来控制随时间变化的值,但没有关于如何将 B\xc3\xa9zier 曲线实现为 ay(x) 的信息功能。这就是我的想法。

\n\n

二维空间中的标准三次 B\xc3\xa9zier 曲线可以由四个点P 0 =(x 0 , y 0 ) .. P 3 =(x 3 , y 3 )定义。
\nP 0和 P 3是曲线的端点,而 P 1和 P 2是影响其形状的控制柄。使用参数 t \xcf\xb5 [0, 1],可以使用以下方程确定曲线上任何给定点的 x 和 y 坐标
\nA) x = (1-t) 3 x 0 + 3t(1 -t) 2 x 1 + 3t 2 (1-t)x 2 + t 3 x 3
\nB) y = (1-t) 3 y 0 + 3t(1-t) 2 y 1 + 3t 2 (1 -t)y 2 + t 3 y 3

\n\n

我们想要的是一个函数 y(x),给定 x 坐标,它将返回曲线相应的 y 坐标。为此,曲线必须从左向右单调移动,这样它就不会在不同的 y 位置上多次占据相同的 x 坐标。确保这一点的最简单方法是限制输入点,使x 0 < x 3x 1 , x 2 \xcf\xb5 [x 0 , x 3 ]。换句话说,P 0必须位于 P 3的左侧,两个手柄位于它们之间。

\n\n

为了计算给定 x 的 y,我们必须首先根据 x 确定 t。将 t 代入方程 B 即可简单地从 t 得到 y。

\n\n

我看到有两种方法可以确定给定 y 的 t。

\n\n

首先,您可以尝试对 t 进行二分搜索。从下限 0 和上限 1 开始,通过方程 A 计算 t 的这些值的 x。继续平分间隔,直到获得相当接近的近似值。虽然这应该可以正常工作,但它既不会特别快,也不会非常精确(至少不会同时两者)。

\n\n

第二种方法是实际求解方程 A 的 t。这实施起来有点困难,因为方程是三次的。另一方面,计算变得非常快并产生精确的结果。

\n\n

方程 A 可以重写为
\n (-x 0 +3x 1 -3x 2 +x 3 )t 3 + (3x 0 -6x 1 +3x 2 )t 2 + (-3x 0 +3x 1 )t + (x 0 -x) = 0\n插入 x 0 ..x 3
的实际值,我们得到一个形式为a t 3 + b t 2 + c*t + d = 0的三次方程,我们知道 [0, 1 内只有一个解]。现在,我们可以使用Stack Overflow 答案中发布的算法来求解该方程。

\n\n

下面是一个演示此方法的 C# 小类。它应该足够简单,可以将其转换为您选择的语言。

\n\n
using System;\n\npublic class Point {\n    public Point(double x, double y) {\n        X = x;\n        Y = y;\n    }\n    public double X { get; private set; }\n    public double Y { get; private set; }\n}\n\npublic class BezierCurve {\n    public BezierCurve(Point p0, Point p1, Point p2, Point p3) {\n        P0 = p0;\n        P1 = p1;\n        P2 = p2;\n        P3 = p3;\n    }\n\n    public Point P0 { get; private set; }\n    public Point P1 { get; private set; }\n    public Point P2 { get; private set; }\n    public Point P3 { get; private set; }\n\n    public double? GetY(double x) {\n        // Determine t\n        double t;\n        if (x == P0.X) {\n            // Handle corner cases explicitly to prevent rounding errors\n            t = 0;\n        } else if (x == P3.X) {\n            t = 1;\n        } else {\n            // Calculate t\n            double a = -P0.X + 3 * P1.X - 3 * P2.X + P3.X;\n            double b = 3 * P0.X - 6 * P1.X + 3 * P2.X;\n            double c = -3 * P0.X + 3 * P1.X;\n            double d = P0.X - x;\n            double? tTemp = SolveCubic(a, b, c, d);\n            if (tTemp == null) return null;\n            t = tTemp.Value;\n        }\n\n        // Calculate y from t\n        return Cubed(1 - t) * P0.Y\n            + 3 * t * Squared(1 - t) * P1.Y\n            + 3 * Squared(t) * (1 - t) * P2.Y\n            + Cubed(t) * P3.Y;\n    }\n\n    // Solves the equation ax\xc2\xb3+bx\xc2\xb2+cx+d = 0 for x \xcf\xb5 \xe2\x84\x9d\n    // and returns the first result in [0, 1] or null.\n    private static double? SolveCubic(double a, double b, double c, double d) {\n        if (a == 0) return SolveQuadratic(b, c, d);\n        if (d == 0) return 0;\n\n        b /= a;\n        c /= a;\n        d /= a;\n        double q = (3.0 * c - Squared(b)) / 9.0;\n        double r = (-27.0 * d + b * (9.0 * c - 2.0 * Squared(b))) / 54.0;\n        double disc = Cubed(q) + Squared(r);\n        double term1 = b / 3.0;\n\n        if (disc > 0) {\n            double s = r + Math.Sqrt(disc);\n            s = (s < 0) ? -CubicRoot(-s) : CubicRoot(s);\n            double t = r - Math.Sqrt(disc);\n            t = (t < 0) ? -CubicRoot(-t) : CubicRoot(t);\n\n            double result = -term1 + s + t;\n            if (result >= 0 && result <= 1) return result;\n        } else if (disc == 0) {\n            double r13 = (r < 0) ? -CubicRoot(-r) : CubicRoot(r);\n\n            double result = -term1 + 2.0 * r13;\n            if (result >= 0 && result <= 1) return result;\n\n            result = -(r13 + term1);\n            if (result >= 0 && result <= 1) return result;\n        } else {\n            q = -q;\n            double dum1 = q * q * q;\n            dum1 = Math.Acos(r / Math.Sqrt(dum1));\n            double r13 = 2.0 * Math.Sqrt(q);\n\n            double result = -term1 + r13 * Math.Cos(dum1 / 3.0);\n            if (result >= 0 && result <= 1) return result;\n\n            result = -term1 + r13 * Math.Cos((dum1 + 2.0 * Math.PI) / 3.0);\n            if (result >= 0 && result <= 1) return result;\n\n            result = -term1 + r13 * Math.Cos((dum1 + 4.0 * Math.PI) / 3.0);\n            if (result >= 0 && result <= 1) return result;\n        }\n\n        return null;\n    }\n\n    // Solves the equation ax\xc2\xb2 + bx + c = 0 for x \xcf\xb5 \xe2\x84\x9d\n    // and returns the first result in [0, 1] or null.\n    private static double? SolveQuadratic(double a, double b, double c) {\n        double result = (-b + Math.Sqrt(Squared(b) - 4 * a * c)) / (2 * a);\n        if (result >= 0 && result <= 1) return result;\n\n        result = (-b - Math.Sqrt(Squared(b) - 4 * a * c)) / (2 * a);\n        if (result >= 0 && result <= 1) return result;\n\n        return null;\n    }\n\n    private static double Squared(double f) { return f * f; }\n\n    private static double Cubed(double f) { return f * f * f; }\n\n    private static double CubicRoot(double f) { return Math.Pow(f, 1.0 / 3.0); }\n}\n
Run Code Online (Sandbox Code Playgroud)\n


Eva*_*ers 0

您有几个选择:

假设您的曲线函数 F(t) 采用范围从 0 到 1 的参数 t,其中 F(0) 是曲线的起点,F(1) 是曲线的终点。

您可以通过以每单位时间的恒定变化增加 t 来制作沿着曲线的运动动画。所以 t 由函数 T(time) = Constant*time 定义

例如,如果您的帧为 1/24 秒,并且您希望以每秒 0.1 单位 t 的速率沿曲线移动,则每帧将 t 增加 0.1 (t/s) * 1/24 (秒/帧)。

这里的一个缺点是您的实际速度或每单位时间行驶的距离不会恒定。这取决于控制点的位置。

如果要沿曲线均匀缩放速度,可以修改每单位时间 t 的恒定变化。但是,如果您希望速度发生显着变化,您会发现很难控制曲线的形状。如果您希望一个端点处的速度更大,则必须将控制点移得更远,这反过来又会将曲线的形状拉向该点。如果这是一个问题,您可以考虑对 t 使用非常量函数。有多种方法和不同的权衡,我们需要了解有关您的问题的更多详细信息才能提出解决方案。例如,过去我允许用户定义每个关键帧的速度,并使用查找表将时间转换为参数 t,以便关键帧速度之间的速度存在线性变化(这很复杂)。

另一个常见的问题:如果您通过连接多条贝塞尔曲线来制作动画,并且希望在曲线之间移动时速度是连续的,那么您将需要约束控制点,使它们与相邻曲线对称。Catmull-Rom样条是一种常见的方法。