Interpolation method 중 베지어곡선은 스플라인과 조금 다르게 '핸들러' 라는 점으로 곡선을 표현한다. 일러스트레이터나 포토샵의 '펜툴', '패스툴' 이 베지어곡선을 활용한 것이다. 일반적으로 2차원상의 곡선으로 활용하고, 보다 높은차원에서 '베지어곡면'으로 곡면 또한 표현할 수 있다.
베지어곡선이라 베지어씨가 만든줄 알았는데 Paul de casteljau가 만들었다고 한다. 베지어로 인해서 유명해졌을 뿐이라고 한다.
[math!]\begin{equation}\begin{split}B(t) &= \sum_{k=0}^{n} \dbinom{n}{k}P_k (1-t)^{n-k}t^k \\ &= \dbinom{n}{0}P_0(1-t)^n + \dbinom{n}{1}P_1(1-t)^{n-1}t + \cdots + \dbinom{n}{n}P_nt^n, \\ & t \in [0,1] \end{split}\end{equation}[/math!]
와 같이 수식으로 표현된다.
하지만 도형을 표현하기위해 베지어를 사용할때는 3차 베지어 이상 사용할 필요가 없다. 1,2,3차 베지어곡선의 연속으로 원하는 도형을 충분히 표현할 수 있기때문이다. 오히려 높은차수의 베지어곡선을 사용하면 원하는 도형의 표현이 더 힘들다.
2D상의 베지어곡선을 한번 구현해 봤다. 일러의 펜툴을 모방했는데 핸들러를 개별로다루는 기능은 넣지않았다.
접기
#include <list> #include <GL/gl.h> #include "SarkLibrary.h" using std::list; using namespace sarklib;
class Bezier{ private: class Anchor{ public: Point2 p; Point2 *h1, *h2;
Anchor(const Point2& point) : p(point), h1(NULL), h2(NULL) {}
void PivotHandler1(const Point2& point){ if( h1!=NULL ) *h1 = point; else h1 = new Point2(point); } void PivotHandler2(const Point2& point){ if( h2!=NULL ) *h2 = point; else h2 = new Point2(point); }
void EraseHandler1(){ if( h1!=NULL ){ delete h1; h1 = NULL; } } void EraseHandler2(){ if( h2!=NULL ){ delete h2; h2 = NULL; } } };
typedef list<Anchor>::pointer anchor_ptr; list<Anchor> mAnchors; anchor_ptr mCurrentAnchor;
static float step;
public: Bezier(){ mCurrentAnchor = NULL; }
void AddAnchor(const Point2& p, bool onBezier=false){ if( onBezier ){ } else{ mAnchors.push_back(Anchor(p)); mCurrentAnchor = &mAnchors.back(); } } void Clear(){ for(list<Anchor>::iterator i=mAnchors.begin(); i!=mAnchors.end(); i++){ i->EraseHandler1(); i->EraseHandler2(); } mAnchors.clear(); }
bool SelectPoint(const Point2& p){ static float allow = 5;
mCurrentAnchor=NULL; for(list<Anchor>::iterator i=mAnchors.begin(); i!=mAnchors.end(); i++){ if( i->p.x-allow <= p.x && p.x <= i->p.x+allow && i->p.y-allow <= p.y && p.y <= i->p.y+allow ){ mCurrentAnchor = &(*i); return true; } } return false; }
void ReleaseAnchor(){ mCurrentAnchor = NULL; }
void DragAnchorHandler(const Point2& p){ if( mCurrentAnchor == NULL ) return; mCurrentAnchor->PivotHandler1(p); mCurrentAnchor->PivotHandler2(2.0f * mCurrentAnchor->p - *mCurrentAnchor->h1); }
void DrawAnchor(bool withHandlers=true){ if( mAnchors.empty() ) return;
glBegin(GL_POINTS); for(list<Anchor>::iterator i=mAnchors.begin(); i!=mAnchors.end(); i++){ glVertex2f(i->p.x, i->p.y); if( withHandlers ){ if( i->h1!=NULL) glVertex2f(i->h1->x, i->h1->y); if( i->h2!=NULL) glVertex2f(i->h2->x, i->h2->y); } } glEnd();
if( withHandlers ){ glBegin(GL_LINES); for(list<Anchor>::iterator i=mAnchors.begin(); i!=mAnchors.end(); i++){ if( i->h1!=NULL ){ glVertex2f(i->p.x, i->p.y); glVertex2f(i->h1->x, i->h1->y); } if( i->h2!=NULL ){ glVertex2f(i->p.x, i->p.y); glVertex2f(i->h2->x, i->h2->y); } } glEnd(); } }
void Draw(){ if( mAnchors.size() < 2 ) return; list<Anchor>::iterator b = mAnchors.begin(); b++; list<Anchor>::iterator a = mAnchors.begin();
Point2 prvB, curB; glBegin(GL_LINES); do{ if( a->h1==NULL ){ if( b->h2==NULL ){ //linear prvB = a->p; for(float t=step; t<=1.0f; t+=step){ curB = (1.0f-t)*a->p + t*b->p; glVertex2f(prvB.x, prvB.y); glVertex2f(curB.x, curB.y); prvB = curB; } } else{ //a->p, b->h2, b->p //quadratic prvB = a->p; for(float t=step; t<=1.0f; t+=step){ curB = sarklib::math::sqre(1.0f-t)*a->p + 2.0f*t*(1.0f-t)*(*b->h2) + math::sqre(t)*b->p;
glVertex2f(prvB.x, prvB.y); glVertex2f(curB.x, curB.y); prvB = curB; } } } else{ if( b->h2==NULL ){ //a->p, a->h1, b->p //quadratic prvB = a->p; for(float t=step; t<=1.0f; t+=step){ curB = math::sqre(1.0f-t)*a->p + 2.0f*t*(1.0f-t)*(*a->h1) + math::sqre(t)*b->p;
glVertex2f(prvB.x, prvB.y); glVertex2f(curB.x, curB.y); prvB = curB; } } else{ //cubic prvB = a->p; for(float t=step; t<=1.0f; t+=step){ curB = math::pow(1.0f-t, 3.0f)*a->p + 3.0f*t*math::sqre(1.0f-t)*(*a->h1) + 3.0f*math::sqre(t)*(1.0f-t)*(*b->h2) + math::pow(t, 3.0f)*b->p;
glVertex2f(prvB.x, prvB.y); glVertex2f(curB.x, curB.y); prvB = curB; } } }
a++; b++; }while( b!= mAnchors.end() ); glEnd(); } };
float Bezier::step = 0.01f;
접기
접기
#include "Bezier.h"
#pragma comment(lib, "opengl32") #pragma comment(lib, "glu32") #pragma comment(lib, "glut32")
// properties float zoom=1.0f; float wndDx, wndDy; float wndWidth, wndHeight;
Bezier gBezier2D;
void OnInit(){ glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glPointSize(5); glLineWidth(2); }
void OnDisplay(){ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(0,0,0); gBezier2D.Draw();
glColor3f(0,1,0); gBezier2D.DrawAnchor();
glutSwapBuffers(); }
void OnReshape(int w, int h){ wndWidth = (float)w; wndHeight = (float)h; wndDx = wndWidth / (2.0f*zoom); wndDy = wndHeight / (2.0f*zoom); glOrtho(-wndDx, wndDx, -wndDy, wndDy, -100, 100); glutPostRedisplay(); }
void OnMouseButton(int bt, int state, int x, int y){ if( bt==GLUT_LEFT_BUTTON ){ if( state==GLUT_DOWN ){ Point2 point(((float)x - wndWidth/2.0f) / zoom, (wndHeight/2.0f-(float)y) / zoom ); if( !gBezier2D.SelectPoint(point) ){ gBezier2D.AddAnchor(point); } } else if( state==GLUT_UP ){ gBezier2D.ReleaseAnchor(); } } else if( bt==GLUT_RIGHT_BUTTON ){ if( state==GLUT_DOWN ) gBezier2D.Clear(); } glutPostRedisplay(); }
void OnMouseMotion(int x, int y){ gBezier2D.DragAnchorHandler( Point2(((float)x - wndWidth/2.0f) / zoom, (wndHeight/2.0f-(float)y) / zoom ) ); glutPostRedisplay(); }
int main(){ glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(800, 800); glutInitWindowPosition(400, 100); glutCreateWindow("bezier test");
glutDisplayFunc(OnDisplay); glutReshapeFunc(OnReshape); glutMouseFunc(OnMouseButton); glutMotionFunc(OnMouseMotion); OnInit();
glutMainLoop(); }
접기