二次Bézier曲线:计算点

时间:2011-04-12 11:31:34

标签: math point curve quadratic

我想计算二次曲线上的一个点。将它与HTML5的canvas元素一起使用。

当我在JavaScript中使用quadraticCurveTo()函数时,我有一个源点,一个目标点和一个控制点。

我怎样才能计算出创建的二次曲线上的一个点,让我们说t=0.5只用"只有"知道这三点吗?

5 个答案:

答案 0 :(得分:108)

使用二次Bézier公式,例如,在Bézier Curves的维基百科页面上找到:

quadratic Bezier formula

在伪代码中,那是

t = 0.5; // given example value
x = (1 - t) * (1 - t) * p[0].x + 2 * (1 - t) * t * p[1].x + t * t * p[2].x;
y = (1 - t) * (1 - t) * p[0].y + 2 * (1 - t) * t * p[1].y + t * t * p[2].y;

p[0]是起点,p[1]是控制点,p[2]是终点。 t是参数,从0到1。

答案 1 :(得分:30)

如果有人需要立方体形式:

        //B(t) = (1-t)**3 p0 + 3(1 - t)**2 t P1 + 3(1-t)t**2 P2 + t**3 P3

        x = (1-t)*(1-t)*(1-t)*p0x + 3*(1-t)*(1-t)*t*p1x + 3*(1-t)*t*t*p2x + t*t*t*p3x;
        y = (1-t)*(1-t)*(1-t)*p0y + 3*(1-t)*(1-t)*t*p1y + 3*(1-t)*t*t*p2y + t*t*t*p3y;

如果某人需要第n个表格,请参阅此算法。你喂N点,它会返回一个N + (N-1) + (N-2) ...点数组,这将解为(N * (N*1)) / 2。最后一点是给定T值的曲线上的位置。

   9
  7 8
 4 5 6
0 1 2 3

您将算法0 1 2 3作为控制点,这些位置将是阵列的其余部分。最后一点(9)是你想要的值。

这也是你如何细分bezier曲线,你给它你想要的值t然后你将细分曲线声明为金字塔的边。然后,您可以索引金字塔侧面的各个点以及从基座构建的金字塔的另一侧。例如,在quintic:

    E
   C D
  9 A B 
 5 6 7 8
0 1 2 3 4

(原谅十六进制,我想要它很漂亮)

您可以将两条完美细分的曲线索引为0,5,9,C,E和E,D,B,8,4。请特别注意看第一条曲线以控制点(0)开头并结束在曲线(E)上的一个点上,第二条曲线在曲线(E)上开始,在控制点(4)上结束。鉴于此,您可以完美地细分贝塞尔曲线,这就是您所期望的。连接两条曲线的新控制点位于曲线上。

/**
 * Performs deCasteljau's algorithm for a bezier curve defined by the given control points.
 *
 * A cubic for example requires four points. So it should get at least an array of 8 values
 *
 * @param controlpoints (x,y) coord list of the Bezier curve.
 * @param returnArray Array to store the solved points. (can be null)
 * @param t Amount through the curve we are looking at.
 * @return returnArray
 */
public static float[] deCasteljau(float[] controlpoints, float[] returnArray, float t) {
    int m = controlpoints.length;
    int sizeRequired = (m/2) * ((m/2) + 1);
    if (returnArray == null) returnArray = new float[sizeRequired];
    if (sizeRequired > returnArray.length) returnArray = Arrays.copyOf(controlpoints, sizeRequired); //insure capacity
    else System.arraycopy(controlpoints,0,returnArray,0,controlpoints.length);
    int index = m; //start after the control points.
    int skip = m-2; //skip if first compare is the last control point.
    for (int i = 0, s = returnArray.length - 2; i < s; i+=2) {
        if (i == skip) {
            m = m - 2;
            skip += m;
            continue;
        }
        returnArray[index++] = (t * (returnArray[i + 2] - returnArray[i])) + returnArray[i];
        returnArray[index++] = (t * (returnArray[i + 3] - returnArray[i + 1])) + returnArray[i + 1];
    }
    return returnArray;
}

您会注意到它只是每组积分金额的公式。对于N个解,你得到(N-1)个中值为(t),然后你得到那些中点,得到(N-2)个点,然后是(N-3)个等,直到你只有一个点。这一点在曲线上。因此,对于t,在0到1之间的值解决问题,将为您提供整个曲线。知道了这一点,我的实现只是在一个数组中向前传播值,节省了不止一次重新计算的内容。我已经将它用于100分并且它仍然闪电般快速。

(如果你想知道,不,它不值得.SVG在立方体停下来是正确的。)

答案 2 :(得分:7)

我创建了这个演示:

// x = a * (1-t)³ + b * 3 * (1-t)²t + c * 3 * (1-t)t² + d * t³
//------------------------------------------------------------
// x = a - 3at + 3at² - at³ 
//       + 3bt - 6bt² + 3bt³
//             + 3ct² - 3ct³
//                    + dt³
//--------------------------------
// x = - at³  + 3bt³ - 3ct³ + dt³
//     + 3at² - 6bt² + 3ct²
//     - 3at + 3bt
//     + a
//--------------------------------
// 0 = t³ (-a+3b-3c+d) +  => A
//     t² (3a-6b+3c)   +  => B
//     t  (-3a+3b)     +  => c
//     a - x              => D
//--------------------------------

var A = d - 3*c + 3*b - a,
    B = 3*c - 6*b + 3*a,
    C = 3*b - 3*a,
    D = a-x;

// So we need to solve At³ + Bt² + Ct + D = 0 

<强> Full example here

可以帮助别人。

答案 3 :(得分:2)

我编辑了talkhabis答案(三次曲线),因此该曲线以正确的坐标显示。 (无法评论) 需要更改Y坐标(-p []。y + 150)。 (为此添加一个新的变量可能是更好,更有效的解决方案,但您知道了)

// Apply points to SVG and create the curve and controllers :

var path  =  document.getElementById('path'),
    ctrl1 =  document.getElementById('ctrl1'),
    ctrl2 =  document.getElementById('ctrl2'),
    D = 'M ' + p0.x + ' ' + (-p0.y+150) +
    'C ' + c0.x + ' ' + (-c0.y+150) +', ' + c1.x + ' ' + (-c1.y+150) + ', ' + p1.x + ' ' + (-p1.y+150);

path.setAttribute('d',D);
ctrl1.setAttribute('d','M'+p0.x+','+(-p0.y+150)+'L'+c0.x+','+(-c0.y+150));
ctrl2.setAttribute('d','M'+p1.x+','+(-p1.y+150)+'L'+c1.x+','+(-c1.y+150));

// Lets test the "Bezier Function" 

var t = 0, point = document.getElementById('point');

setInterval(function(){

  var p = Bezier(p0,c0,c1,p1,t);
  point.setAttribute('cx',p.x);
  point.setAttribute('cy',-p.y+150);

  t += 0.01;
  if(t>=1) t=0;

},50);


// OK ... Now tring to get "y" on cruve based on mouse "x" : 

var svg = document.getElementById('svg'),
    point2 = document.getElementById('point2');

svg.onmousemove = function(e){

    var x = (e.pageX - 50)/2,  
        y = (e.pageY - 50)/2;
   // "-50" because of "50px margin" on the left side 
   // and "/2" because the svg width is 300 units and 600 px => 300 = 600/2    

  // Get the x,y by mouse x
  var p = YBX(p0,c0,c1,p1,x); 

  point2.setAttribute('cx',p.x);
  point2.setAttribute('cy',-p.y+150);  
} 

http://jsfiddle.net/u214gco8/1/

我还创建了一些C代码来测试三次曲线的结果。只需在主要功能中输入X和Y坐标即可。

#include <stdio.h>
#include <stdlib.h> 
#include <math.h> 

void bezierCurve(int x[] , int y[]) 
{ 
    double xu = 0.0 , yu = 0.0 , u = 0.0 ; 
    int i = 0 ; 
    for(u = 0.0 ; u <= 1.0 ; u += 0.05) 
    { 
        xu = pow(1-u,3)*x[0]+3*u*pow(1-u,2)*x[1]+3*pow(u,2)*(1-u)*x[2] 
             +pow(u,3)*x[3]; 
        yu = pow(1-u,3)*y[0]+3*u*pow(1-u,2)*y[1]+3*pow(u,2)*(1-u)*y[2] 
            +pow(u,3)*y[3]; 
        printf("X: %i   Y: %i \n" , (int)xu , (int)yu) ; 
    } 
} 

int main(void) {
    int x[] = {0,75,50,300};
    int y[] = {0,2,140,100};
    bezierCurve(x,y);
    return 0;
}

https://ideone.com/glLXcB

答案 4 :(得分:1)

请注意:如果您使用此处提供的常用公式,那么不要期望t = 0.5将曲线的长度的一半返回到点。在大多数情况下,它不会。

“§23下的here更多内容 - 以固定距离间隔跟踪曲线”here