简单而粗暴的方法画任意阶数Bezier曲线

简单而粗暴的方法画任意阶数Bezier曲线

虽然说是任意阶数,但是嘞,算法原理是可以到任意阶数,计算机大概到100多阶就会溢出了

Bezier曲线介绍

本文代码

背景

在windows的OpenGL环境中,使用鼠标在屏幕上选点,并以点为基础画出Bezier曲线

  • 初始化
  • 鼠标操作
  • 3阶以内Bezier曲线
  • n阶Bezier曲线

初始化

创建窗口,初始化大小、显示模式、添加显示和鼠标等回调函数,设置背景颜色等。

完成之后,定义两个全局的int类型的vector 用于存储鼠标在窗口中选择的点。同时定义窗口的高度和宽度。

vector<int> x_loc = {};
vector<int> y_loc = {};
int height = 600;
int width  = 600;

鼠标操作

OpenGL中存在鼠标点击、拖动等操作的回调函数,使用十分方便,调用即可。

我们定义在鼠标左键按下抬起后为一次屏幕选点,并将所选的点的坐标压入存储存储点的坐标的容器中。

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;
    }
}
  • 回调函数使用为

    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阶曲线画的?

简单而粗暴。。。

原文地址:https://www.cnblogs.com/leexin/p/10955241.html

时间: 2024-08-26 16:07:25

简单而粗暴的方法画任意阶数Bezier曲线的相关文章

【转】设计模式:简单工厂、工厂方法、抽象工厂之小结与区别

简单工厂,工厂方法,抽象工厂都属于设计模式中的创建型模式.其主要功能都是帮助我们把对象的实例化部分抽取了出来,优化了系统的架构,并且增强了系统的扩展性. 本文是本人对这三种模式学习后的一个小结以及对他们之间的区别的理解. 简单工厂 简单工厂模式的工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的对象实例. 不修改代码的话,是无法扩展的. 工厂方法 工厂方法是针对每一种产品提供一个工厂类.通过不同的工厂实例来创建不同的产品实例. 在同一等级结构中,支持增加任意产品. 抽象工厂 抽象工厂是应

数独游戏求解:解法适用于任意阶数的数独

0.数独简介 数独(すうどく,Sūdoku)是一种运用纸.笔进行演算的逻辑游戏.以九阶数独为例,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行.每一列.每一个粗线宫内的数字均含1-9,不重复. 1)4阶(可填数字范围1~4,宫格2阶) 2)9阶(可填数字范围1~9,宫格3阶) 3)16阶(可填数字范围1~16,宫格4阶) *见附录 1.数独的表示 对于N阶数独可以用一个N*N的二维数组表示 1)数独阶数GridRank=N 2)宫格阶数SubGridRank=Sqrt

设计模式:简单工厂、工厂方法、抽象工厂之小结与区别 (转)

简单工厂,工厂方法,抽象工厂都属于设计模式中的创建型模式.其主要功能都是帮助我们把对象的实例化部分抽取了出来,优化了系统的架构,并且增强了系统的扩展性. 本文是本人对这三种模式学习后的一个小结以及对他们之间的区别的理解. 简单工厂 简单工厂模式的工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的对象实例. 不修改代码的话,是无法扩展的. 工厂方法 工厂方法是针对每一种产品提供一个工厂类.通过不同的工厂实例来创建不同的产品实例. 在同一等级结构中,支持增加任意产品. 抽象工厂 抽象工厂是应

推荐一种简单易学的读书方法

推荐一种简单易学的读书方法 本文由有道云笔记推荐 前段时间我简单统计了一下,从大学毕业后到现在的6年多时间里,总共读了200多本书,平均1年读20-40本,范围涉及企业管理.营销.励志.传记.小说.历史.哲学等等.书读的多了,慢慢的也形成了一套自认为适合自己的读书方法,可以简单地用12个字进行概括:"定目标.列书单.读两遍.致运用".下面我会仔细地跟大家聊聊. 一.定目标(确定读书目标)人的时间和精力是有限的,因此读书.学习都存在成本.为了能够以最小的投入获得最大的产出,我们一定要先确

重头开始学23种设计模式:三大工厂(简单工厂,工厂方法,抽象工厂)

在开发当中我们经常会使用三个设计模式,来帮我们解决项目代码的可扩展性. 在简单工厂,工厂方法,抽象工厂这三个设计模式当中,代码其实都很简单,主要是要理解运用. 简单工厂: 简单工厂说白了,就是利用Switch根据传递的参数,进行实例化. 工厂方法: 工厂方法,为解决每次都去增加Swicth的简单工厂的升级.为每一个产品提供一个工厂类. 抽象工厂: 抽象工厂,我觉得也是对工厂方法的再次升级,工厂方法每次只能创作一个产品,而抽象工厂就是产品线的产品族. 总结下,从网上找到一个大牛的回复: 我认为不能

设计模式--简单工厂、工厂方法和抽象工厂

简单工厂.工厂方法和抽象工厂三种设计模式都用来帮助我们将对象的实例化部分抽取出来,优化系统结构,在开发中经常使用.三者既有区别,也有联系,今天来对这三种设计模式做一个简单的记录. 概述 简单工厂:用来生产同一等级结构中的任意产品(对于增加新的产品,需要修改工厂) 工厂方法:用来生产同一等级结构中的固定产品(支持增加新的产品) 抽象工厂:用来生产不同产品族的全部产品(支持增加新的产品族,不支持增加新的产品) 简单工厂 一般来说,利用一个静态方法,即将createProduct方法设置为static

简单工厂、工厂方法和抽象工厂的区别

工厂模式一般分为简单工厂.工厂方法和抽象工厂三种,看了很多资料,好多讲的都是云里雾里的.要么是概念太多,让人看得一脸懵逼,要么是举得例子不太恰当,看了更让人迷惑了.经过自己一番研究,通过一个简单的例子,终于搞明白了它们之间的区别. 下面以生产宝马.奔驰汽车的工厂为例,讲解它们之间的区别. 一.简单工厂模式 创建一个工厂类,根据传入的参数来决定创建哪个汽车类 //汽车接口 public interface Car { void getCar(); } //宝马汽车类 public class BM

R语言:用简单的文本处理方法优化我们的读书体验

前言 延续之前的用R语言读琅琊榜小说,继续讲一下利用R语言做一些简单的文本处理.分词的事情.其实就是继续讲一下用R语言读书的事情啦,讲讲怎么用它里面简单的文本处理方法,来优化我们的读书体验,如果读邮件和读代码也算阅读的话..用的代码超级简单,不涉及其他包 这里讲两个示例,结尾再来吐槽和总结. 1)R-Blogger订阅邮件拆分 2) R代码库快速阅读方法 不在博客园上阅读时才会看到的,这篇博文归 http://www.cnblogs.com/weibaar所有 仅保证在博客园博客上的排版干净利索

R语言中最简单的向量赋值方法

R语言中最简单的向量赋值方法简介: 1. 生成等差数列的向量x x <- 1:10 #将x向量赋值为1 2 3 4 5 6 7 8 9 10 结果为 > x [1] 1 2 3 4 5 6 7 8 9 10 2. 将x的值全部修改成0 x[] <- 0 #非常简洁的赋值方法,建议使用 x[1:length(x)] <- 0 #不建议使用的赋值方法 结果为: > x[] <- 0 > x [1] 0 0 0 0 0 0 0 0 0 0 3.使用seq函数 x <