2011-05-04 30 views
4

要實現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和認識價值

+0

這是否有幫助:http://www.gamedev.net/topic/313018-calculating-the-length-of-a-bezier-curve/? – 2011-05-04 15:49:50

+0

我看過這篇文章和很多其他文章,每個人似乎都有自己的解決方案。我認爲這個問題應該有一個標準的解決方案,因爲這是一個常見的問題。 – Raks 2011-05-05 05:37:01

+0

我在CG課程中使用過的書中查了幾個章節,有很多關於樣條的信息,但沒有提到一種標準的方法來確定曲線的長度並不幸福。 – 2011-05-05 10:21:35

回答

0

您有幾種選擇:

比方說,你的曲線函數F(t)取參數t的取值範圍從0到1,其中F (0)是曲線的起點,F(1)是曲線的終點。

您可以通過在單位時間內以不斷變化遞增t來使曲線沿着曲線動畫化。 所以t由函數T(時間)來定義=常數*時間

例如,如果你的幀是第二的1/24日,並且希望以每0.1個單位t的速率沿曲線移動第二,然後每個幀你增加t 0.1(t/s)* 1/24(秒/幀)。

這裏的一個缺點是您的實際速度或每單位時間行進的距離不會保持不變。這將取決於您的控制點的位置。

如果您想要沿着曲線均勻縮放速度,您可以修改每單位時間t的常數變化。但是,如果你想讓速度變化很大,你會發現很難控制曲線的形狀。如果您希望一個端點處的速度更大,則必須將控制點進一步移開,從而將曲線的形狀拉向該點。如果這是一個問題,你可以考慮對t使用一個非常量函數。有各種不同的折衷辦法,我們需要了解有關您的問題的更多細節以提出解決方案。例如,在過去,我允許用戶定義每個關鍵幀的速度,並使用查找表將時間轉換爲參數t,以便在關鍵幀速度之間存在線性變化(這很複雜)。另一個常見的掛斷:如果通過連接幾條貝塞爾曲線進行動畫處理,並且希望在曲線之間移動時速度是連續的,那麼您需要約束控制點,使它們與鄰近曲線對稱。 Catmull-Rom樣條是一種常用的方法。

3

我面臨同樣的問題:每個動畫軟件包似乎都使用Bézier曲線來控制隨時間變化的值,但是沒有關於如何實現Bézier曲線作爲y(x)函數的信息。所以這就是我想出的。

在二維空間中的標準三次貝塞爾曲線可以通過四個點P =被定義(X ,Y ).. P =(X ,Y )
P 和P 是曲線的結束點,而P 1 和P 是影響其形狀的手柄。使用參數tε[0,1],然後可以使用等式
A)確定沿曲線的任何給定的點的x和y座標X =(1-T) X + 3噸(1-T) X + 3T (1-叔)× +噸 X
B)Y =(1-t)的 y + 3T(1-T)ý + 3T (1-叔)Y +噸ý

我們想要的是一個函數y(x),給定一個x座標,將返回曲線的相應y座標。爲此,曲線必須從左向右單調移動,以便它不會在不同的y位置上多次使用同一個x座標。確保這一點的最簡單的辦法是限制該輸入點,使得X < XX ,X ε[X ,X ]。換句話說,P 必須在它們之間的兩個手柄的左邊。

爲了計算給定x的y,我們必須首先從x確定t。從t獲得y是將t應用於等式B的簡單問題。

我看到了確定給定y的t的兩種方法。

首先,你可以嘗試一個二進制搜索t。從0的下限開始,1的上限開始,並通過方程A計算這些t的值。保持等分間隔,直到獲得相當近似的近似值。雖然這應該可以正常工作,但它既不會特別快也不會非常精確(至少不會同時出現)。

第二種方法是實際解出t的方程式A.由於方程是立方體,因此實施起來有點困難。另一方面,計算變得非常快並且產生精確的結果。

公式A可以改寫爲
(-x 0 + 3× -3x + X )噸 +(3× -6x + 3×2 )噸 +(-3x + 3×)T +(X 0 -x)= 0
插入您的實際值對於x ..x ,我們得到以下形式的三次方程一個噸 + B噸 + C * T +爲d = 0我們知道[0,1]內只有一個解決方案。現在我們可以使用類似於this Stack Overflow answer中發佈的算法來求解這個公式。

以下是展示這種方法的一些C#類。它應該很簡單,可以將其轉換爲您選擇的語言。

using System; 

public class Point { 
    public Point(double x, double y) { 
     X = x; 
     Y = y; 
    } 
    public double X { get; private set; } 
    public double Y { get; private set; } 
} 

public class BezierCurve { 
    public BezierCurve(Point p0, Point p1, Point p2, Point p3) { 
     P0 = p0; 
     P1 = p1; 
     P2 = p2; 
     P3 = p3; 
    } 

    public Point P0 { get; private set; } 
    public Point P1 { get; private set; } 
    public Point P2 { get; private set; } 
    public Point P3 { get; private set; } 

    public double? GetY(double x) { 
     // Determine t 
     double t; 
     if (x == P0.X) { 
      // Handle corner cases explicitly to prevent rounding errors 
      t = 0; 
     } else if (x == P3.X) { 
      t = 1; 
     } else { 
      // Calculate t 
      double a = -P0.X + 3 * P1.X - 3 * P2.X + P3.X; 
      double b = 3 * P0.X - 6 * P1.X + 3 * P2.X; 
      double c = -3 * P0.X + 3 * P1.X; 
      double d = P0.X - x; 
      double? tTemp = SolveCubic(a, b, c, d); 
      if (tTemp == null) return null; 
      t = tTemp.Value; 
     } 

     // Calculate y from t 
     return Cubed(1 - t) * P0.Y 
      + 3 * t * Squared(1 - t) * P1.Y 
      + 3 * Squared(t) * (1 - t) * P2.Y 
      + Cubed(t) * P3.Y; 
    } 

    // Solves the equation ax³+bx²+cx+d = 0 for x ϵ ℝ 
    // and returns the first result in [0, 1] or null. 
    private static double? SolveCubic(double a, double b, double c, double d) { 
     if (a == 0) return SolveQuadratic(b, c, d); 
     if (d == 0) return 0; 

     b /= a; 
     c /= a; 
     d /= a; 
     double q = (3.0 * c - Squared(b))/9.0; 
     double r = (-27.0 * d + b * (9.0 * c - 2.0 * Squared(b)))/54.0; 
     double disc = Cubed(q) + Squared(r); 
     double term1 = b/3.0; 

     if (disc > 0) { 
      double s = r + Math.Sqrt(disc); 
      s = (s < 0) ? -CubicRoot(-s) : CubicRoot(s); 
      double t = r - Math.Sqrt(disc); 
      t = (t < 0) ? -CubicRoot(-t) : CubicRoot(t); 

      double result = -term1 + s + t; 
      if (result >= 0 && result <= 1) return result; 
     } else if (disc == 0) { 
      double r13 = (r < 0) ? -CubicRoot(-r) : CubicRoot(r); 

      double result = -term1 + 2.0 * r13; 
      if (result >= 0 && result <= 1) return result; 

      result = -(r13 + term1); 
      if (result >= 0 && result <= 1) return result; 
     } else { 
      q = -q; 
      double dum1 = q * q * q; 
      dum1 = Math.Acos(r/Math.Sqrt(dum1)); 
      double r13 = 2.0 * Math.Sqrt(q); 

      double result = -term1 + r13 * Math.Cos(dum1/3.0); 
      if (result >= 0 && result <= 1) return result; 

      result = -term1 + r13 * Math.Cos((dum1 + 2.0 * Math.PI)/3.0); 
      if (result >= 0 && result <= 1) return result; 

      result = -term1 + r13 * Math.Cos((dum1 + 4.0 * Math.PI)/3.0); 
      if (result >= 0 && result <= 1) return result; 
     } 

     return null; 
    } 

    // Solves the equation ax² + bx + c = 0 for x ϵ ℝ 
    // and returns the first result in [0, 1] or null. 
    private static double? SolveQuadratic(double a, double b, double c) { 
     double result = (-b + Math.Sqrt(Squared(b) - 4 * a * c))/(2 * a); 
     if (result >= 0 && result <= 1) return result; 

     result = (-b - Math.Sqrt(Squared(b) - 4 * a * c))/(2 * a); 
     if (result >= 0 && result <= 1) return result; 

     return null; 
    } 

    private static double Squared(double f) { return f * f; } 

    private static double Cubed(double f) { return f * f * f; } 

    private static double CubicRoot(double f) { return Math.Pow(f, 1.0/3.0); } 
} 
0

我有answered a similar question here。基本上,如果您事先知道控制點,則可以將f(t)函數轉換爲y(x)函數。無需手工完成,您可以使用像Wolfram Alpha這樣的服務來幫助您完成數學任務。