2017-01-15 190 views
0

我試圖在我的程序中實現N階貝塞爾曲線的公式。 它在我看來,我做了一切正確,但視覺效果不正確。N順序的貝塞爾曲線

這就是:Curve problem

紅色立方體是P0和藍色是P8。 白色立方體是製作曲線的實際一組點。 橙色立方體是控制點。

我所看到的是曲線末端附近有一個循環,曲線連接到最後一個(藍色立方體)點。看起來有一個看不見的地方。 另一件事是,P0和P1之間也是奇怪的事情...

任何人都可以幫我解決它嗎?

這裏是我使用的代碼:

private void Update() 
    { 
     controlPointsCoords = ControlPoints.Select(p => p.transform.position).ToArray(); 

     for (int p = 0; p < PointsSet.Count; p++) 
     { 
      PointsSet[p].transform.position = CurveDegreeN 
      (
       controlPointsCoords, 
       Rt(p, PointsSet.Count) 
      ); 
     } 
    } 

    private Vector3 CurveDegreeN(Vector3[] pointsCoords, float u) 
    { 
     float X = 0, Y = 0, Z = 0; 
     float n = pointsCoords.Length - 1; 

     for (int i = 0; i < pointsCoords.Length; i++) 
     { 
      var coef = (Factorial(n)/(Factorial((float)i) * Factorial(n - i))) * Mathf.Pow(u, i) * Mathf.Pow(1 - u, n - i); 
      X += coef * pointsCoords[i].x; 
      Y += coef * pointsCoords[i].y; 
      Z += coef * pointsCoords[i].z; 
     } 

     return new Vector3(X, Y, Z); 
    } 

    private float Factorial(float n) 
    { 
     if (n == 0) return 1; 

     float res = 0.0f; 
     for (int i = 1; i < n; i++) res += (float)Math.Log(i); 
     return (float)Math.Exp(res); 
    } 


    private float Rt(int current, int count) 
    { 
     return ((float)current - 0)/((float)count - 0) * (1 - 0) + 0; 
    } 

我希望這將是明確的人! 提前謝謝!

更新: 我將積分減少到3.以下是結果:3 Points curve。 在這裏清晰可見,計算中出現了一些問題......還有什麼建議?

+0

您能否顯示視覺效果以及如何繪製?另外:你看到[這](http://stackoverflow.com/questions/15599766/n-th-order-bezier-curves)? – TaW

+0

你可以看到,如果你去的視覺效果:https://i.stack.imgur.com/Iuitt.png –

+0

@GeorgeVasilchenko你的'coef'幾乎肯定會計算錯誤(所以基本的調試包括打印它們,看看無論它們是否有意義),也是Bezier實現的原因之一,它絕對值得使用de Casteljau的算法來重新實現您的解決方案,因爲它不依賴於這些更高級的數學結構,所以不會出錯。見答案。 –

回答

1

從簡化代碼開始,因爲這對於調試來說是不可靠的。第一步:除非有實際的好處,否則不要使用微積分。使用完整的二項式計算和冪的t通常與插值一樣快(或慢)(貝塞爾曲線平均表示爲列表減少),但插值是死的 - 很容易用簡單的加法和乘法實現,而二項式計算和權力是更多的工作。所以讓我們來評估幾何而不是微積分:

function drawCurve(coords[]): 
    points = [] 
    // the higher you make "steps", the more curve points you generate: 
    for (s=0, steps=10; s<=steps; s++): 
    t = s/steps 
    nt = 1 - t 
    list[] = coords.shallowCopy() 

    // We now run our list reduction to get our on-curve 
    // point at t, using de Casteljau's algorithm: 
    while(list.length > 1) 
     for(i = 0, e = list.length; i < e; i++): 
     list[i] = nt * list[i] + t * list[i+1] 
     list.pop() 

    // And what's left is our on-curve point at t. 
    // Beauty; push and move on to the next point. 
    points.push(list[0]) 
    return points 

完成。通過排除二項式和冪,並且純粹基於迭代插值(即使用de Casteljau's algorithm)實現曲線評估,實際上沒有什麼東西可以在此代碼中「做錯了」:代碼具有很高的質量!

通過使用數組[3]而不是3d矢量類來顯式說明座標,您可以使此代碼更加高效,以便在插值過程中不必依賴運算符重載或函數調用slowdowns步驟,讓你喜歡的東西:

function drawCurve(coords[]): 
    coords = flatten(coords) // one-time convert Vector3 to flat [x,y,z] arrays 
    ... 
    while(list.length > 1) 
     for(i = 0, e = list.length; i < e; i++): 
     v1 = list[i] 
     v2 = list[i+1] 
     list[i][0] = nt * v1[0] + t * v2[0] // x 
     list[i][1] = nt * v1[1] + t * v2[1] // y 
     list[i][2] = nt * v1[2] + t * v2[2] // z 
     list.pop() 
    points.push(new Vector3(list[0])) 
    return points 

(和最後的優化,但通常不值得的,是解開的while爲好,以實現基於初始L=list.length和反ifor循環,其中L減1,i重設爲0時i==L,並終止時L==1

如果你絕對起碼需要微積分(這是說實話不是這裏的情況)「有效地」產生你的二項式係數:他們是超級簡單基於Pascal's triangle所以

lut = [  [1],   // n=0 
      [1,1],   // n=1 
      [1,2,1],   // n=2 
      [1,3,3,1],  // n=3 
     [1,4,6,4,1],  // n=4 
     [1,5,10,10,5,1], // n=5 
     [1,6,15,20,15,6,1]] // n=6 

binomial(n,k): 
    while(n >= lut.length): 
    s = lut.length 
    nextRow = [] 
    nextRow[0] = 1 
    for(i=1, prev=s-1; i<prev; i++): 
     nextRow[i] = lut[prev][i-1] + lut[prev][i] 
    nextRow[s] = 1 
    lut.push(nextRow) 
    return lut[n][k] 

(如果你這樣做,要麼確保你還記得你:你的數學協處理器的愛使用階乘來評價他們,他們可以從字面上只需添加了一些整數產生編程和數組偏移量從0開始,或者在行/列位置[0]添加虛擬值,以便「直觀地」請致電binomial(4,2)以獲得4個選擇2個而不是5個選擇3)

+1

我會,如果我可以給+2解釋帕斯卡三角和德卡斯特勞。 – LutzL

0

除了這是一個對二項式計算效率最低的事實,您應該預先計算二項式序列並且不要爲您繪製的每個點重新計算它們,並且您可以避免大部分電源調用通過採用Horner類似的方法來實現功能....

你的代碼似乎是正確的,並且視覺與控制點一致,強制中間部分變成直線。高階多項式插值在樣本點或控制點具有(相對)較大的值變化的區段可能會有一些複雜的擺動。


更新:我起初並不顯得過於密切的輔助功能,因爲我不會計算使用階乘的獨立計算二項式,但你的階乘函數不包含的因素n與參數調用n,即它計算(n-1)!

+0

你說的代碼看起來是正確的,但是你認爲爲什麼在最後和最後1分之間存在循環?如果我移動最後一點,那麼曲線的一部分會保留,因爲還有一個點。 –

+0

是否存在循環?由於與幾乎線性曲線的長片段快速偏離,所以存在曲率高的部分。此外,除了最後一點外,其他所有點都在一個平面上,而最後一點與該平面相距一定距離,這也導致最後一段的快速變化。 – LutzL

+0

我會嘗試從不同的角度製作更多的截圖... –