在给定坐标的情况下为贝塞尔曲线找到合适的参数值

时间:2016-12-18 05:17:04

标签: algorithm math graphics bezier

我正在研究一个涉及贝塞尔曲线的项目,我很难找到一个值t,它在[0,1]范围内,对应于贝塞尔曲线上的特定位置。我设置了一个测试,其中我将增量值 t (特别是以0.001到1的增量)并将它们放在贝塞尔曲线的x和y参数方程中,然后我用它减去该值。真值,如果它在 x y 的小阈值范围内,我找到了一个合适的 t 。不幸的是, t 似乎没有一个与所需坐标相对应的值。这意味着循环实际上完成而没有成功完成for循环中的条件。

应该在曲线上的坐标是(75,-2.26384401)。我把我的代码放在下面,显示控制点的坐标和真正的 x y 坐标。任何帮助将不胜感激。谢谢!

int _tmain(int argc, _TCHAR* argv[])
{
float P0[2] = { 55, -11.105 };
float P1[2] = { 72.569, -11.105 };
float P2[2] = { 50.217, 1.396 };
float P3[2] = { 100, 1.396 };

float t = 1.0F;
float int_t = t / 1000; // intervals
float x;
float y;
float tx = 75.0000000F; // where it should be in x
float ty = -2.26384401F; // where it should be in y
float epsilon = 0.01;
float final_t;

for (float i = 0; i < t; i += int_t)
{
    final_t = i;
    x = powf(1.0F - i, 3.0F)*P0[0] + 3.0F*powf(1.0F - i, 2.0F)*i*P1[0] + 
        3.0F*(1.0F - i)*powf(i, 2.0F)*P2[0] + powf(i, 3.0F)*P3[0]; // x(t)

    y = powf(1.0F - i, 3.0F)*P0[1] + 3.0F*powf(1.0F - i, 2.0F)*i*P1[1] + 
        3.0F*(1.0F - i)*powf(i, 2.0F)*P2[1] + powf(i, 3.0F)*P3[1]; // y(t)

    // for my testing
    cout << "---------------------------------" << endl;
    cout << "i = " << i << endl;
    cout << "x = " << x << endl;
    cout << "y = " << y << endl;
    cout << "---------------------------------" << endl;

    if ((fabsf(x - tx) <= epsilon) && (fabsf(y - ty) <= epsilon))
        break;
}
cout << endl;
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cout << "t = " << final_t << endl;

return 0;
}

2 个答案:

答案 0 :(得分:1)

您的问题是输入点Pt(tx,ty)根本不在 BEZIER (P0,P1,P2,P3)曲线上。所以曲线的距离总是大得多然后是你的小epsilon,无论你找到t的匹配程度如何......

这是它的样子:

overview

BEZIER 曲线为灰色。绿色X是输入(tx,ty)点,红色+位于曲线t=0.753上的最近点

这里的 C ++ / VCL 代码我这样做:

//---------------------------------------------------------------------------
#include <math.h>
#include "approx.h"
double P0[2] = { 55, -11.105 };
double P1[2] = { 72.569, -11.105 };
double P2[2] = { 50.217, 1.396 };
double P3[2] = { 100, 1.396 };

double Pt[2] = { 75.0000000,-2.26384401 };
//---------------------------------------------------------------------------
void BEZIER_getxy(double *p0,double *p1,double *p2,double *p3,double &x,double &y,double  t) // return x,y of point on BEZIER curve for t
    {
    double a0,a1,a2,a3,tt,ttt;
    tt=t*t; ttt=tt*t;
    a0=                                    (    p0[0]);
    a1=                        (3.0*p1[0])-(3.0*p0[0]);
    a2=            (3.0*p2[0])-(6.0*p1[0])+(3.0*p0[0]);
    a3=(    p3[0])-(3.0*p2[0])+(3.0*p1[0])-(    p0[0]);
    x=a0+(a1*t)+(a2*tt)+(a3*ttt);
    a0=                                    (    p0[1]);
    a1=                        (3.0*p1[1])-(3.0*p0[1]);
    a2=            (3.0*p2[1])-(6.0*p1[1])+(3.0*p0[1]);
    a3=(    p3[1])-(3.0*p2[1])+(3.0*p1[1])-(    p0[1]);
    y=a0+(a1*t)+(a2*tt)+(a3*ttt);
    }
//---------------------------------------------------------------------------
void BEZIER_gett (double *p0,double *p1,double *p2,double *p3,double  x,double  y,double &t) // return t which is closest to (x,y)
    {
    double e,xx,yy;
    approx at;
    for (at.init(0.0,1.0,0.1,3,&e);!at.done;at.step())  // search t
        {
        BEZIER_getxy(p0,p1,p2,p3,xx,yy,at.a);
        xx-=x; xx*=xx;
        yy-=y; yy*=yy;
        e=xx+yy; // error is distance between points ^ 2
        }
    t=at.aa;
    }
//---------------------------------------------------------------------------
void BEZIER_draw (double *p0,double *p1,double *p2,double *p3,TCanvas *can,double x0,double y0,double zoom) // just render curve with VCL/GDI
    {
    int e;
    double x,y,t;
    BEZIER_getxy(p0,p1,p2,p3,x,y,0.0);
    x=x0+(x*zoom);
    y=y0-(y*zoom);
    can->MoveTo(x,y);
    for (e=1,t=0.0;e;t+=0.02)
        {
        if (t>=1.0) { e=0; t=1.0; }
        BEZIER_getxy(p0,p1,p2,p3,x,y,t);
        x=x0+(x*zoom);
        y=y0-(y*zoom);
        can->LineTo(x,y);
        }
    }
//---------------------------------------------------------------------------
void TMain::draw() // this is just my window redraw routine
    {
    if (!_redraw) return;

    // clear buffer
    bmp->Canvas->Brush->Color=clBlack;
    bmp->Canvas->FillRect(TRect(0,0,xs,ys));
    double x0=-40.0,y0=170.0,zoom=3.0;  // view
    double x,y,w=10,t;

    // whole BEZIER curve (Gray curve)
    bmp->Canvas->Pen->Color=clDkGray;
    BEZIER_draw(P0,P1,P2,P3,bmp->Canvas,x0,y0,zoom);

    // input point Pt (Green X)
    bmp->Canvas->Pen->Color=0x0000FF00;
    x=x0+(Pt[0]*zoom);
    y=y0-(Pt[1]*zoom);
    bmp->Canvas->MoveTo(x-w,y-w);
    bmp->Canvas->LineTo(x+w,y+w);
    bmp->Canvas->MoveTo(x+w,y-w);
    bmp->Canvas->LineTo(x-w,y+w);

    // closest point (Red +)
    bmp->Canvas->Pen->Color=clRed;
    BEZIER_gett (P0,P1,P2,P3,Pt[0],Pt[1],t);
    BEZIER_getxy(P0,P1,P2,P3,x,y,t);
    x=x0+(x*zoom);
    y=y0-(y*zoom);
    bmp->Canvas->MoveTo(x-w,y);
    bmp->Canvas->LineTo(x+w,y);
    bmp->Canvas->MoveTo(x,y-w);
    bmp->Canvas->LineTo(x,y+w);

    Caption=t;

    // render backbuffer
    Main->Canvas->Draw(0,0,bmp);
    _redraw=false;
    }
//---------------------------------------------------------------------------

由于我懒得调试你的代码,我用于 BEZIER 三次解算器我的approx这个相关的 QA

在搜索本身中,我使用步骤t=<0.0,1.0>0.1递归设置3的搜索参数,导致0.1/10^(3-1)最终精度t,即0.001 1}}作为int_t步骤,但解决方案位于30步骤而非您1000

为了补救您的代码,请记住x,y,ttx,ty的最小距离,而不仅仅是distance<=epsilon

的距离

答案 1 :(得分:0)

简单的方法是使用三次解算器。

现在求解x并求解y。 如果该点真的在曲线上,您将获得相同的t值。可能它有点偏离,所以替换ts,然后选择最接近的。

系数

  DX = m_P0.x;
  CX = 3.0f * ( m_P1.x - m_P0.x );
  BX = ( 3.0f * m_P2.x ) - ( 6.0f * m_P1.x ) + ( 3.0f * m_P0.x );
  AX = m_P3.x - ( 3.0f * m_P2.x ) + ( 3.0f * m_P1.x ) - m_P0.x; 

为y做同样的事。

现在解决立方AXt ^ 3 + BXt ^ 2 + CXt + DX - px = 0并找到三个 根源为t。 对y执行相同操作,并找到t的三个根。

现在我们有6个根。有些可能很复杂,所以请忽略。因此可能在0 - epsilon,1 + epsilon(允许一点倾斜)之外,所以忽略。如果点px,py正好在曲线上,则t值中的两个将在0-1上相同,这就是你的答案。 实际上,这一点有点过时了。因此,将所有(最多6个)t值替换回贝塞尔曲线并获得x,y。至少一个x,y对应该非常接近px,py,除了可能非常接近尖点的点。

如果你有两个关闭点并且它们之间的间隔是px py,你可以进一步细化你的答案。