2013-02-16 62 views
2

我需要澄清一下我正在嘗試的技術。我試圖將實體從A點移動到B點,但我不希望實體以直線行進。XNA CatmullRom曲線

例如,如果實體位於x:0,y:0並且我想要到達點x:50,y:0,我希望實體以曲線行進到目標,我會想象它將離開的最大距離是x:25 y:25,因此它在X上向目標移動,但已經從y上的目標移開。

我已經調查了幾個選項,包括樣條曲線,但我認爲會做這項工作的是CatmullRom曲線。我有點困惑如何使用它?我想知道每幀移動實體的位置,而不是函數返回哪個插值。我會很感激一些關於如何使用它的問題。

如果有其他方法可能會更容易,我已經錯過了,我會很高興聽到他們。

編輯:

爲了證明我應得的曲線:

Vector2 blah = Vector2.CatmullRom(
    StartPosition, 
    new Vector2(StartPosition.X + 5, StartPosition.Y + 5), 
    new Vector2(StartPosition.X + 10, StartPosition.Y + 5), 
    /*This is the end position*/ 
    new Vector2(StartPosition.X + 15, StartPosition.Y), 0.25f); 

的想法最終是我即時產生這些問題,但我只是試圖去解決它在時刻。

+0

所以你有樣條曲線的產生,但需要以恆定的速度沿着它移動? – Corey 2013-02-16 22:42:44

+0

@Corey我認爲...我會編輯這個問題來粗略地展示我迄今爲止所做的事情 – Bushes 2013-02-16 22:46:04

+0

@Corey問題編輯 – Bushes 2013-02-16 22:48:33

回答

5

正如您所注意到的,樣條線會生成不同長度的線段。曲線越緊密,分段越短。這對於顯示而言很不錯,對於移動設備的路徑生成不太有用。

爲了獲得樣條路徑的恆速遍歷的合理近似值,您需要沿着曲線段進行一些插值。由於您已經有一組線段(在由Vector2.CatmullRom()返回的點對之間),您需要一種以恆定速度行走這些段的方法。

給定一組點和一條沿着定義爲那些點之間的線的路徑移動的總距離,下面的(或多或少的僞代碼)會找到一個沿着路徑的特定距離的點:

Point2D WalkPath(Point2D[] path, double distance) 
{ 
    Point curr = path[0]; 
    for (int i = 1; i < path.Length; ++i) 
    { 
     double dist = Distance(curr, path[i]); 
     if (dist < distance) 
      return Interpolate(curr, path[i], distance/dist; 

     distance -= dist; 
     curr = path[i]; 
    } 
    return curr; 
} 

有各種優化可以做,以加快這,如存儲與路徑中的每個點的路徑距離,使其更易於步行操作過程中查找。隨着你的路徑變得越來越複雜,這變得越來越重要,但是對於只有幾個分段的路徑來說可能是過度的。

編輯:Here's an example我在JavaScript中使用了這種方法。這是一個驗證的概念,所以在代碼不要太嚴格:P

編輯:花鍵上一代
給定一組的「結」的更多信息分 - 是點一個曲線必然順序傳遞 - 對於曲線算法最明顯的適合是Catmull-Rom。缺點是C-R需要兩個額外的控制點,這些控制點可能很難自動生成。

一段時間後,我在網上發現了一篇相當有用的文章(我無法找到正確的歸因),根據路徑中的點集計算了一組控制點。這裏是我的C#代碼爲計算控制點方法:

// Calculate control points for Point 'p1' using neighbour points 
public static Point2D[] GetControlsPoints(Point2D p0, Point2D p1, Point2D p2, double tension = 0.5) 
{ 
    // get length of lines [p0-p1] and [p1-p2] 
    double d01 = Distance(p0, p1); 
    double d12 = Distance(p1, p2); 
    // calculate scaling factors as fractions of total 
    double sa = tension * d01/(d01 + d12); 
    double sb = tension * d12/(d01 + d12); 
    // left control point 
    double c1x = p1.X - sa * (p2.X - p0.X); 
    double c1y = p1.Y - sa * (p2.Y - p0.Y); 
    // right control point 
    double c2x = p1.X + sb * (p2.X - p0.X); 
    double c2y = p1.Y + sb * (p2.Y - p0.Y); 
    // return control points 
    return new Point2D[] { new Point2D(c1x, c1y), new Point2D(c2x, c2y) }; 
} 

tension參數調整控制點生成改變曲線的密封性。值越高,曲線越寬,曲線越緊密,值越低。玩它,看看什麼價值最適合你。

給定一組「N」節(曲線上的點),我們可以生成一組將被用來生成節之間的曲線控制點:所以現在

// Generate all control points for a set of knots 
public static List<Point2D> GenerateControlPoints(List<Point2D> knots) 
{ 
    if (knots == null || knots.Count < 3) 
     return null; 
    List<Point2D> res = new List<Point2D>(); 
    // First control point is same as first knot 
    res.Add(knots.First()); 
    // generate control point pairs for each non-end knot 
    for (int i = 1; i < knots.Count - 1; ++i) 
    { 
     Point2D[] cps = GetControlsPoints(knots[i - 1], knots[i], knots[i+1]); 
     res.AddRange(cps); 
    } 
    // Last control points is same as last knot 
    res.Add(knots.Last()); 
    return res; 
} 

你有一組2*(n-1)控制點,然後您可以使用該控制點生成結點之間的實際曲線段。

public static Point2D LinearInterp(Point2D p0, Point2D p1, double fraction) 
{ 
    double ix = p0.X + (p1.X - p0.X) * fraction; 
    double iy = p0.Y + (p1.Y - p0.Y) * fraction; 
    return new Point2D(ix, iy); 
} 

public static Point2D BezierInterp(Point2D p0, Point2D p1, Point2D c0, Point2D c1, double fraction) 
{ 
    // calculate first-derivative, lines containing end-points for 2nd derivative 
    var t00 = LinearInterp(p0, c0, fraction); 
    var t01 = LinearInterp(c0, c1, fraction); 
    var t02 = LinearInterp(c1, p1, fraction); 
    // calculate second-derivate, line tangent to curve 
    var t10 = LinearInterp(t00, t01, fraction); 
    var t11 = LinearInterp(t01, t02, fraction); 
    // return third-derivate, point on curve 
    return LinearInterp(t10, t11, fraction); 
} 

// generate multiple points per curve segment for entire path 
public static List<Point2D> GenerateCurvePoints(List<Point2D> knots, List<Point2D> controls) 
{ 
    List<Point2D> res = new List<Point2D>(); 
    // start curve at first knot 
    res.Add(knots[0]); 
    // process each curve segment 
    for (int i = 0; i < knots.Count - 1; ++i) 
    { 
     // get knot points for this curve segment 
     Point2D p0 = knots[i]; 
     Point2D p1 = knots[i + 1]; 
     // get control points for this curve segment 
     Point2D c0 = controls[i * 2]; 
     Point2D c1 = controls[i * 2 + 1]; 
     // calculate 20 points along curve segment 
     int steps = 20; 
     for (int s = 1; s < steps; ++s) 
     { 
      double fraction = (double)s/steps; 
      res.Add(BezierInterp(p0, p1, c0, c1, fraction)); 
     } 
    } 
    return res; 
} 

一旦運行這個在你結你現在有一組相隔距離可變插值點,根據線路的曲率距離。由此,您可以迭代運行原始的WalkPath方法來生成一組相距不變的點,這些點以恆定的速度定義您的手機沿着曲線的進度。

您的手機在路徑任意點的標題(大致)是兩側的點之間的角度。對於路徑中的任意點np[n-1]p[n+1]之間的角度是航向角度。

// get angle (in Radians) from p0 to p1 
public static double AngleBetween(Point2D p0, Point2D p1) 
{ 
    return Math.Atan2(p1.X - p0.X, p1.Y - p0.Y); 
} 

我已經適應從我的代碼上面,因爲我用的是Point2D類我很久以前寫的,有很多的功能 - 浮點運算,插值等 - 建於我可能已經增加了一些在翻譯過程中出現錯誤,但希望在玩遊戲時很容易發現它們。

讓我知道如何去。如果遇到任何特殊困難,我會看看我能做些什麼來幫助。

+0

好的,我得到了一般要點。我稍微停留在生成曲線本身的點上。我列入的是一個例子。最讓我最感興趣的是如何生成曲線,而不管它必須運行哪個軸。我需要用什麼數學來獲得這個數學?我的一個想法是將實體的x軸旋轉到其行進方向,然後更改y值以生成曲線。如果這是正確的,我該怎麼做? – Bushes 2013-02-17 12:02:10

+0

答覆已更新。 – Corey 2013-02-18 02:17:08

+0

歡呼的非常詳細的答案。一有機會,我會放棄它! – Bushes 2013-02-18 11:23:18