n 階貝塞爾曲線計算公式實作
關于貝塞爾曲線是什麼,可以用來做什麼,這裡就不再介紹,如果你還不了解,可以先去看看下面這篇文章:貝塞爾曲線掃盲
1. 效果參考
![]() | |
2. 思路解析
百度百科上給出的一般參數公式是這樣的:
給定點 P0,P1,P2, … ,Pn,其貝塞爾曲線公式如下(即貝塞爾曲線上的點 B(t) 可由如下公式計算得到):
可以看出其公式是由一個格式固定的表達式之和來表示,這個表達式就是關鍵:
該表達式可分為四個部分看:
- 從 i 遞增到 n 的常數部分
- Pi 坐标部分
- (1 - t)^(n - i)
-
t^i
可以看出這四部分都與 i 的值相關,此外 t 值的計算方式為:i/(n+1)
如果直接從上面的公式上找規律比較抽象,那就從具體的例子中找規律吧:
設 Bt 為要計算的貝塞爾曲線上的坐标,N 為控制點個數,P0,P1,P2..Pn 為貝塞爾曲線控制點的坐标,當 N 值不同時有如下計算公式:
如 N 為 3 表示貝塞爾曲線的控制點有 3 個點,這時 n 為 2 ,這三個點分别用 P0,P1,P2 表示。
- N = 3: P = (1-t)^2*P0 + 2*(1-t)*t*P1 + t^2*P2
- N = 4: P = (1-t)^3*P0 + 3*(1-t)^2*t*P1 + 3(1-t)*t^2*P2 + t^3*P3
- N = 5: P = (1-t)^4*P0 + 4*(1-t)^3*t*P1 + 6(1-t)^2*t^2*P2 + 4*(1-t)*t^3*P3 + t^4*P4
将貝塞爾曲線一般參數公式中的表達式用如下方式表示:
設有常數 a,b 和 c,則該表達式可統一表示為如下形式:
a * (1 - t)^b * t^c * Pn;
分析當 N 分别為3,4,5 時對應 a,b,c 的值:
如 N = 3 時,公式有三個表達式,第一個表達式為 (1-t)^2*P0,其對應 a,b,c 值分别為:1,2,0
-
N = 3: 1,2,0 2,1,1 1,0,2
a: 1 2 1
b: 2 1 0
c: 0 1 2
-
N = 4: 1,3,0 3,2,1 3,1,2 1,0,3
a: 1 3 3 1
b: 3 2 1 0
c: 0 1 2 3
-
N = 5: 1,4,0 4,3,1 6,2,2 4,1,3 1,0,4
a: 1 4 6 4 1
b: 4 3 2 1 0
c: 0 1 2 3 4
根據上面的分析就可以總結出 a,b,c 對應的取值規則:
- b: (N - 1) 遞減到 0 (b 為 1-t 的幂)
- c: 0 遞增到 (N - 1) (c 為 t 的幂)
-
a: 在 N 分别為 1,2,3,4,5 時将其值用如下形式表示:
N=1:———1
N=2:——–1 1
N=3:——1 2 1
N=4:—–1 3 3 1
N=5:—1 4 6 4 1
a 值的改變規則為: 楊輝三角
3. 使用 java 來實作
接下來就實作它:先再來一個例子
比如計算控制點坐标分别為:P0(3,8),P1(2,3),P2(2,7),想要傳回 10 個在貝塞爾曲線上的點,用 java 可以這樣寫:
float[] p0 = {, };
float[] p1 = {, };
float[] p2 = {, };
float[][] result = new float[][];
for (int i = ; i < ; i++) {
float t = i / ;
result[i][] = (float) ( * Math.pow( - t, ) * Math.pow(t, ) * p0[] + * Math.pow( - t, ) * Math.pow(t, ) * p1[] + * Math.pow( - t, ) * Math.pow(t, ) * p2[]);
result[i][] = (float) ( * Math.pow( - t, ) * Math.pow(t, ) * p0[] + * Math.pow( - t, ) * Math.pow(t, ) * p1[] + * Math.pow( - t, ) * Math.pow(t, ) * p2[]);
}
好了,最後的計算方法是下面這個:
/**
* @param poss 貝塞爾曲線控制點坐标
* @param precision 精度,需要計算的該條貝塞爾曲線上的點的數目
* @return 該條貝塞爾曲線上的點(二維坐标)
*/
public float[][] calculate(float[][] poss, int precision) {
//次元,坐标軸數(二維坐标,三維坐标...)
int dimersion = poss[].length;
//貝塞爾曲線控制點數(階數)
int number = poss.length;
//控制點數不小于 2 ,至少為二維坐标系
if (number < || dimersion < )
return null;
float[][] result = new float[precision][dimersion];
//計算楊輝三角
int[] mi = new int[number];
mi[] = mi[] = ;
for (int i = ; i <= number; i++) {
int[] t = new int[i - ];
for (int j = ; j < t.length; j++) {
t[j] = mi[j];
}
mi[] = mi[i - ] = ;
for (int j = ; j < i - ; j++) {
mi[j + ] = t[j] + t[j + ];
}
}
//計算坐标點
for (int i = ; i < precision; i++) {
float t = (float) i / precision;
for (int j = ; j < dimersion; j++) {
float temp = f;
for (int k = ; k < number; k++) {
temp += Math.pow( - t, number - k - ) * poss[k][j] * Math.pow(t, k) * mi[k];
}
result[i][j] = temp;
}
}
return result;
}
在 android 中繼承 View 然後重寫 onDraw 方法,在 Activity 綁定的布局檔案中加入該自定義 View ,調用 calculate 方法就可以畫出來任意階的貝塞爾曲線啦。
........
// calculate 方法在 BezierImpl 中實作
private BezierImpl bezier = new BezierImpl();
private Paint paint = new Paint();
float[][] poss = {
{f, f},
{f, f},
{f, f},
{f, f},
{f, f},
{f, f},
{f, f},
{f, f},
{f, f}
};
@Override
protected void onDraw(Canvas canvas) {
float x0, y0, x, y;
paint.setColor(Color.DKGRAY);
paint.setStrokeWidth(f);
x0 = poss[][];
y0 = poss[][];
for (int i = ; i < poss.length; i++) {
x = poss[i][];
y = poss[i][];
canvas.drawLine(x0, y0, x, y, paint);
x0 = x;
y0 = y;
}
paint.setColor(Color.RED);
paint.setStrokeWidth(f);
float[][] po = bezier.calculate(poss, );
x0 = po[][];
y0 = po[][];
for (int i = ; i < ; i++) {
x = po[i][];
y = po[i][];
canvas.drawLine(x0, y0, x, y, paint);
x0 = x;
y0 = y;
}
}
...........
最後貼一個最近在做的一個自定義 View(GummyView),使用了到了貝塞爾曲線,用了上面的方法,有興趣的話可以 Fork 或給我留言 >.<。
位址:DuanJiaNing/GummyView
目前實作的效果是這樣的:
| | |