簡單而粗暴的方法畫任意階數Bezier曲線

簡單而粗暴的方法畫任意階數Bezier曲線

雖說是任意階數,可是嘞,算法原理是能夠到任意階數,計算機大概到100多階就會溢出了c++

Bezier曲線介紹git

本文代碼github

背景

在windows的OpenGL環境中,使用鼠標在屏幕上選點,並以點爲基礎畫出Bezier曲線算法

  • 初始化
  • 鼠標操做
  • 3階之內Bezier曲線
  • n階Bezier曲線

初始化

建立窗口,初始化大小、顯示模式、添加顯示和鼠標等回調函數,設置背景顏色等。windows

完成以後,定義兩個全局的int類型的vector 用於存儲鼠標在窗口中選擇的點。同時定義窗口的高度和寬度。函數

vector<int> x_loc = {};
vector<int> y_loc = {};
int height = 600;
int width  = 600;
  • 定義畫點函數
void drawPixel(double x, double y, int point_size)
{
    glViewport(0, 0, (GLsizei)width, (GLsizei)height);
    glEnable(GL_POINT_SMOOTH);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glPointSize(point_size);
    glBegin(GL_POINTS);
    glVertex2d(x, y);
    glEnd();
}

其中point_size爲點的大小。指針

鼠標操做

OpenGL中存在鼠標點擊、拖動等操做的回調函數,使用十分方便,調用便可。code

咱們定義在鼠標左鍵按下擡起後爲一次屏幕選點,並將所選的點的座標壓入存儲存儲點的座標的容器中。blog

void Mouse_hit(int button, int state, int x, int y)
{
    /// state == 1 mean button up
    /// state == 0 mean button down
    /// button == 0 mean left button
    /// button == 1 mean middle button
    /// button == 2 mean right button
    /// [x, y] is the location of mouse pointer
    if (button == 0 && state == 1)
    {
        x_loc.push_back(x);
        y_loc.push_back(y);
        cout << "point location: " << x_loc[x_loc.size() - 1] << " " << y_loc[y_loc.size() - 1] << endl;
    }
if (button == 2 && state == 1){
       x_loc.clear();
      y_loc.clear();
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glutSwapBuffers(); 
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glutSwapBuffers(); 
      glutPostRedisplay();
      cout<<" Clear Screen!"<<endl;
    }
    glutSwapBuffers(); 
      glutPostRedisplay();
}
  • 回調函數使用爲get

    glutMouseFunc(Mouse_hit);
  • Mouse_hit函數中state表明當前鼠標的狀態是按下仍是擡起

  • button爲按下的是左、中、右三鍵中的哪個

  • [x, y]爲當前鼠標指針的座標。次座標不是世界座標系,使用時得進行轉換,看後面

座標轉換

拿一張圖簡單說明一下。因爲鼠標獲取的是世界座標系下的位置,而在屏幕上繪製點與線是使用的是當前繪圖座標系,因此要進行簡單的座標變換。

可在顯示回調函數中使用以下代碼重設OpenGL窗口。

glViewport(0, 0, (GLsizei)width, (GLsizei)height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, width, height, 0);

好了,設置一下前景色和點的大小形狀等,來看看畫點的效果。

3階之內的Bezier曲線

對於3階之內的Bezier曲線,直接將Bezier曲線的定義公式展開,求解係數便可。

void drawBezier_1(vector<int> x, vector<int> y, int num_of_points)
{
    float ax, bx;
    float ay, by;
    int temp_loc = x.size() - 2;

    glColor3f(0.0f, 0.0f, 1.0f);
    drawPixel(x[temp_loc + 0], y[temp_loc + 0], 7);
    drawPixel(x[temp_loc + 1], y[temp_loc + 1], 7);
    ax = x[temp_loc + 0];
    ay = y[temp_loc + 0];
    bx = x[temp_loc + 1];
    by = y[temp_loc + 1];

    float t;
    t = 0.0;
    float dt = 0.002;
    while (t <= 1)
    {
        float x_temp = (1 - t) * ax + t * bx;
        float y_temp = (1 - t) * ay + t * by;
        drawPixel(x_temp, y_temp, 1);
        t += dt;
    }
}

void drawBezier_2(vector<int> x, vector<int> y, int num_of_points)
{
    float ax, bx;
    float ay, by;
    float tSquared;
    int temp_loc = x.size() - 3;

    ax = x[temp_loc + 0] - 2 * x[temp_loc + 1] + x[temp_loc + 2];
    ay = y[temp_loc + 0] - 2 * y[temp_loc + 1] + y[temp_loc + 2];
    bx = x[temp_loc + 0] * (-2) + x[temp_loc + 1] * 2;
    by = y[temp_loc + 0] * (-2) + y[temp_loc + 1] * 2;

    glColor3f(0.0f, 0.0f, 1.0f);
    drawPixel(x[temp_loc + 0], y[temp_loc + 0], 7);
    drawPixel(x[temp_loc + 1], y[temp_loc + 1], 7);
    drawPixel(x[temp_loc + 2], y[temp_loc + 2], 7);

    float t;
    t = 0.0;
    float dt = 0.002;
    while (t <= 1)
    {
        tSquared = t * t;
        float x_temp = ax * tSquared + bx * t + x[temp_loc + 0];
        float y_temp = ay * tSquared + by * t + y[temp_loc + 0];
        drawPixel(x_temp, y_temp, 1);
        t += dt;
    }
}

void drawBezier_3(vector<int> x, vector<int> y, int num_of_points)
{
    float ax, bx, cx;
    float ay, by, cy;
    float tSquared, tCubed;
    int temp_loc = x.size() - 4;

    cx = 3.0 * (x[temp_loc + 1] - x[temp_loc + 0]);
    bx = 3.0 * (x[temp_loc + 2] - x[temp_loc + 1]) - cx;
    ax = x[temp_loc + 3] - x[temp_loc + 0] - cx - bx;

    cy = 3.0 * (y[temp_loc + 1] - y[temp_loc + 0]);
    by = 3.0 * (y[temp_loc + 2] - y[temp_loc + 1]) - cy;
    ay = y[temp_loc + 3] - y[temp_loc + 0] - cy - by;

    glColor3f(0.0f, 0.0f, 1.0f);
    drawPixel(x[temp_loc + 0], y[temp_loc + 0], 7);
    drawPixel(x[temp_loc + 1], y[temp_loc + 1], 7);
    drawPixel(x[temp_loc + 2], y[temp_loc + 2], 7);
    drawPixel(x[temp_loc + 3], y[temp_loc + 3], 7);

    float t;
    t = 0.0;
    float dt = 0.002;
    while (t <= 1)
    {
        tSquared = t * t;
        tCubed = tSquared * t;
        float x_temp = (ax * tCubed) + (bx * tSquared) + (cx * t) + x[temp_loc + 0];
        float y_temp = (ay * tCubed) + (by * tSquared) + (cy * t) + y[temp_loc + 0];
        drawPixel(x_temp, y_temp, 1);
        t += dt;
    }
}
  • 對應階數的函數均可實現,每過階數+1個點畫一次曲線。

n階Bezier曲線

由Bezier的定義公式咱們能夠發現,畫Bezier曲線須要求組合數 ,求組合數須要求階乘,而後還須要求。由於c++中有求冪的函數,因此實現階乘組合數便可。

  • 階乘

    double fac(int n)  
    {
      double result = 1;
      if (n == 0)
          return result;
      for (int i = 1; i <= n; i++){
          result *= i;
      }
      return result;
    }

    爲了擴大計算範圍,使用了double類型

  • 組合數

    double combinate(int n, int k) 
    {
      if (k == 0)
          return 1;
      double result = 0;
      result = fac(n) / (fac(k)*(fac(n - k)));
      return result;
    }

    爲了擴大計算範圍,也使用了double類型,其中k <= n

  • n階Bezier曲線

    void drawBezier(vector<int> x, vector<int> y, int num_of_points) {
    
      float px = 0.0, py = 0.0; //point current should draw
      int n;        //number of points -1
      float t = 0.0, dt = 0.0005; //t in [0, 1], dt is changes each time in t
      n = x.size() - 1;
      while (t <= 1) {
          for (int i = 0; i <= n; i++) {
              double temp = combinate(n, i)*powf(t, i)*powf(1 - t, n - i);
              px += temp * x[i];
              py += temp * y[i];
          }
          drawPixel(px, py, 1);
          t += dt;
          px = 0.0;
          py = 0.0;
      }
    }

效果

  • 1階

  • 2階

  • 3階

  • n階曲線畫的❤

簡單而粗暴。。。

相關文章
相關標籤/搜索