2016-12-18 9 views
0

Я работаю над проектом с использованием кривых Безье, и мне трудно найти значение 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; 
} 
+0

Есть несколько вопросов о ближайшей точке кривой Безье на SO – MBo

+0

Спасибо @MBo за то, что так быстро прокомментировали! Есть ли тот, который, по вашему мнению, был бы наиболее проницательным? – InsigMath

+0

Я лично использовал метод решения квинтичного уравнения (нулевую производную функции расстояния). Он находит все возможные решения (если многие существуют), но может страдать от числовых ошибок. Но методы подразделения для Bezier тоже хорошо работают: http://stackoverflow.com/questions/2742610/ – MBo

ответ

1

Ваша проблема в том, что точка ввода Pt(tx,ty) не лежит на Безье(P0,P1,P2,P3) кривой на всех. Таким образом, расстояние до кривой всегда намного больше тогда ваш маленький эпсилон независимо от того, насколько близко матч 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; 
    } 
//--------------------------------------------------------------------------- 

Как я слишком ленив, чтобы отладить код I используюсь для Безье кубического решатель моего approx класса от этого связанного QA:

в самом поиске я установить параметры поиска для t=<0.0,1.0> с шагом 0.1 и 3 рекурсии, ведущей к 0.1/10^(3-1) окончательной точности t которая 0.001 в качестве int_t шага, но решение найдено в 30 шагов вместо твоего 1000

чтобы исправить код только помните x,y,t WIH наименьшее расстояние до tx,ty вместо только один, где distance<=epsilon

+0

Эй, @Spektre, большое спасибо за подробный ответ! Это не тот ответ, на который я надеялся, потому что контекст этого заключается в разделении кривой безье в анимационной программе, и я получил tx и ty из программы, но это означает, что программа потенциально правильно рисует кривую. В любом случае большое спасибо! – InsigMath

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; 

Сделайте то же самое для у.

Теперь разрешите кубику AXt^3 + BXt^2 + CXt + DX - px = 0 и найдите три корня для t. Сделайте то же самое для y и найдите три корня для t.

Теперь у нас есть 6 корней. Некоторые могут быть сложными, поэтому игнорируйте. Таким образом, может быть вне интервала 0 - epsilon, 1 + epsilon (разрешить небольшую остановку), поэтому игнорировать. Если точка px, py точно на кривой, два из значений t будут на 0-1 и идентичны, и это ваш ответ. На самом деле точка будет немного выключена. Итак, замените все значения (до 6) t обратно в bezier и получите x, y. По крайней мере одна пара x, y должна быть очень близка к px, py, за исключением, может быть, точки, близкой к точке возврата.

Вы также можете уточнить свой ответ, если у вас есть две близкие точки и px py находится на интервале между ними.

+0

Это не выглядит так просто, как вы могли бы подумать о первом взгляде. Поскольку BEZIER - это параметрическая кривая, а не функция, поэтому нет никаких нескольких или даже бесконечных решений, которые возможны для индивидуальной размерности и сопоставления пересечений, может получить уродливое дерево 'if' или даже' O (n^2) 'для 2D ... – Spektre

+0

Я немного расширил это. Точка должна быть достаточно близка к кривой, но метод должен работать, за исключением, может быть, точек на бугорках. –