实现Bezier样条曲线

1.给出n+1 个控制点pk=(xk,yk,zk),这里k可取值0-n,多项式函数公式如下

获取的单个点的代码 

void zmBezier::getPoint(float u, double p[3])
{
    int n = m_count - 1;
    double x = 0, y = 0, z = 0;
    for(int k = 0; k <= n; k++)
    {
        x += m_ctrlPoints[k][0] * BEZ_k_n(n, k, u);
        y += m_ctrlPoints[k][1] * BEZ_k_n(n, k, u);
        z += m_ctrlPoints[k][2] * BEZ_k_n(n, k, u);
    }

    p[0] = x;
    p[1] = y;
    p[2] = z;
}

 

 2.混合函数是如下的多项式

double zmBezier::BEZ_k_n(int n, int k, double u)
{
    return  C_n_k(n, k) * pow(u, k) * pow(1 - u, n - k);
}

3.二项式系数

 

double zmBezier::C_n_k(int n, int k)
{
    n = m_count - 1;
    return factorial(n) / (factorial(k) * factorial(n - k));
}

 4.Bezier样条完整代码,全部用指针表示点


/**
Bezier曲线
给定n+1个控制点 Pk=(Xk,Yk,Zk),k取值0-n
多项式函数
-----------------------------------
              n
        P(u)= Σ  Pk × BEZ(u)            0≤u≤1
             k=0           k,n
-----------------------------------
混合函数
-----------------------------------
                         k       n-k
        BEZ(u)=C(n,k) × u × (1-u)       0≤u≤1
             k,n

-----------------------------------
二项式系数
-----------------------------------
                     n!
        C(n,k)=——————————————————
                k! × (n-k)!

-----------------------------------

不想使用 点 结构,全部用指针数组表示点集
*/
#ifndef ZMBEZIER_H
#define ZMBEZIER_H


class zmBezier
{
public:
    zmBezier();
    ~zmBezier();
    zmBezier(int n, double (*points)[3]);


    void getPoint(float u, double p[3]);                //获取参数u时的某一点
    void getCurve(int n, double (*curve)[3]);           //获取n个插值点,代表曲线
    void setCtrlPoints(int n, double (*points)[3]);     //设置控制点
    void getCtrlPoints(int &n, double (*points)[3]);    //获取控制点

private:
    inline double factorial(double n);                  //阶乘
    inline double C_n_k(int n, int k);                  //二项式系数,参数n为了形式上更接近二项式
    inline double BEZ_k_n(int n, int k, double u);      //混合函数

private:
    int m_count;                                        //控制点数量
    double (*m_ctrlPoints)[3];                          //控制点坐标
};

#endif // ZMBEZIER_H
#include "zmBezier.h"

#include<cmath>
#include<string>

zmBezier::zmBezier()

{
    m_count = 0;
    m_ctrlPoints = nullptr;
}

zmBezier::zmBezier(int n, double(*points)[3])
{
    m_count = n;

    m_ctrlPoints = new double[n][3];
    memcpy_s(m_ctrlPoints, sizeof (double)*n * 3, points, sizeof (double)*n * 3);
}

zmBezier::~zmBezier()
{
    m_count = 0;
    delete [] m_ctrlPoints;
}

double zmBezier::C_n_k(int n, int k)
{
    n = m_count - 1;
    return factorial(n) / (factorial(k) * factorial(n - k));
}

double zmBezier::factorial(double n)
{
    return tgamma(n + 1);
}

void zmBezier::getPoint(float u, double p[3])
{
    int n = m_count - 1;
    double x = 0, y = 0, z = 0;
    for(int k = 0; k <= n; k++)
    {
        x += m_ctrlPoints[k][0] * BEZ_k_n(n, k, u);
        y += m_ctrlPoints[k][1] * BEZ_k_n(n, k, u);
        z += m_ctrlPoints[k][2] * BEZ_k_n(n, k, u);
    }

    p[0] = x;
    p[1] = y;
    p[2] = z;
}

double zmBezier::BEZ_k_n(int n, int k, double u)
{
    return  C_n_k(n, k) * pow(u, k) * pow(1 - u, n - k);
}

void zmBezier::getCurve(int count, double (*curve)[3])
{

    double point[3] = {0};
    for(int k = 0; k < count; k++) {
        getPoint(1.0 * k / (count - 1), point);
        curve[k][0] = point[0];
        curve[k][1] = point[1];
        curve[k][2] = point[2];
    }
}

void zmBezier::setCtrlPoints(int n, double(*points)[3])
{
    delete [] m_ctrlPoints;

    m_count = n;

    m_ctrlPoints = new double[n][3];
    int size = sizeof (double) * n * 3;
    memcpy_s(m_ctrlPoints, size, points, size);
}

void zmBezier::getCtrlPoints(int &n, double (*points)[3])
{
    n = m_count;

    if(m_count)
    {
        int size = sizeof (double) * n * 3;
        memcpy_s(points, size, m_ctrlPoints, size);
    }
}

5. 继承QWidget,定义可显示的控制点

#ifndef MYCTRLPOINT_H
#define MYCTRLPOINT_H

#include <QWidget>

class myCtrlPoint : public QWidget
{
    Q_OBJECT
public:
    myCtrlPoint(QWidget *parent);

    QPoint getPosition();
    void setPostion(const QPoint &point);
protected:
    void paintEvent(QPaintEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;

private:
    QPoint m_clicked;

};

#endif // MYCTRLPOINT_H

主要是实现鼠标事件:

5.1 鼠标左键单击,点变成绿色

5.2 鼠标左键拖动,点在父窗口中移动 

5.3 鼠标右键,从父类中删除自己


#include"myCanvas.h"
#include"myCtrlPoint.h"

#include<QKeyEvent>
#include<QPainter>
#include<QMouseEvent>

myCtrlPoint::myCtrlPoint(QWidget *parent)
    : QWidget(parent)
{
    setFixedSize(20, 20);
}

void myCtrlPoint::paintEvent(QPaintEvent *event)
{
    QWidget::paintEvent(event);
    QPainter painter(this);
    if(m_clicked != QPoint())  {
        painter.setBrush(Qt::green);
    }
    else {
        painter.setBrush(Qt::lightGray);
    }
    painter.drawRect(rect());
}


void myCtrlPoint::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        m_clicked = event->globalPos();
        update();
    }
    else if(event->button() == Qt::RightButton)
    {
        myCanvas *canvase = (myCanvas *)parent();
        canvase->m_ctrlWidgets.removeOne(this);
        this->deleteLater();
        canvase->update();
    }


}

void myCtrlPoint::mouseMoveEvent(QMouseEvent *event)
{
    if(m_clicked == QPoint())
    {
        QWidget::mouseMoveEvent(event);

    }
    else
    {
        QPoint cur = event->globalPos();
        QPoint dis = cur - m_clicked;
        m_clicked = cur;
        move(mapToParent(QPoint(0, 0)) + dis);
        ((QWidget *)parent())->update();
    }
}

void myCtrlPoint::mouseReleaseEvent(QMouseEvent *event)
{
    m_clicked = QPoint();
    update();
}

QPoint myCtrlPoint::getPosition()
{
    return mapToParent(rect().center());
}

void myCtrlPoint::setPostion(const QPoint &point)
{
    QPoint target = point - rect().topLeft();
    move(target);

}

6. 继承QWidget,实现一块画布

#ifndef MYCANVAS_H
#define MYCANVAS_H

#include <QWidget>

#include"zmBezier.h"


class myCtrlPoint;
class myCanvas : public QWidget
{
    friend class myCtrlPoint;
    Q_OBJECT
public:
    explicit myCanvas(QWidget *parent = nullptr);
    ~myCanvas();

protected:
    void paintEvent(QPaintEvent *event) override;
    void mouseDoubleClickEvent(QMouseEvent *event) override;

private:
    zmBezier m_curve;
    double m_points[1024][3];                   //不想paintEvent中动态分配内存
    QVector<myCtrlPoint *>m_ctrlWidgets;
};

#endif // MYCANVAS_H

6.1 构造时随机生成4个控制点

6.2 绘制事件中绘制控制点之间的连线、绘制Bezier曲线

6.3 鼠标左键双击空白处会添加一个控制点

6.4 因为不想再绘制事件中动态分配内存,所以用了一个比较大的数组

6.5 控制点是画布的友元类,方便控制点删除自己

#include"myCanvas.h"
#include"myCtrlPoint.h"

#include<QTime>
#include<QDebug>
#include<QPainter>
#include<QMouseEvent>
#include<QRandomGenerator>

myCanvas::myCanvas(QWidget *parent)
    : QWidget(parent)
{
    QRandomGenerator random(QTime::currentTime().second());

    for(int i = 0; i < 4; i++)
    {
        myCtrlPoint *ctrl = new myCtrlPoint(this);
        m_ctrlWidgets.append(ctrl);

        ctrl->setPostion(QPoint(random.generateDouble() * 400, random.generateDouble() * 400));
    }

    resize(500, 500);
}

myCanvas::~myCanvas()
{

}

void myCanvas::paintEvent(QPaintEvent *event)
{
    QWidget::paintEvent(event);
    QPainter painter(this);
    painter.drawText(20, 20, "1.左键拖动控制点");
    painter.drawText(20, 40, "2.右键删除控制点");
    painter.drawText(20, 60, "3.左键双击空白处添加控制点");


    int n = m_ctrlWidgets.count();
    if(n)
    {
        painter.setPen(QPen(Qt::blue, 1, Qt::DotLine));

        for(int i = 0; i < n - 1; i++)
        {
            painter.drawLine(m_ctrlWidgets[i]->getPosition(), m_ctrlWidgets[i + 1]->getPosition());
        }

//        double (*ctrls)[3] = new double[n][3];       尽量别动态分配了,下面限制下点数
        if(n > 1024) {
            n = 1024;
        }

        for(int i = 0; i < n; i++)
        {
//            ctrls[i][0] = m_ctrlWidgets[i]->getPosition().x();
//            ctrls[i][1] = m_ctrlWidgets[i]->getPosition().y();
//            ctrls[i][2] = 0;

            m_points[i][0] = m_ctrlWidgets[i]->getPosition().x();
            m_points[i][1] = m_ctrlWidgets[i]->getPosition().y();
            m_points[i][2] = 0;

        }
        m_curve.setCtrlPoints(n, m_points);
//        m_curve.setCtrlPoints(n, ctrls);
//        delete [] ctrls;

        int request = 100;
//        double (*points)[3] = new double[request][3];

//        m_curve.getCurve(request, points);
        m_curve.getCurve(request, m_points);

        painter.setPen(QPen(Qt::green, 1));
        for(int i = 0; i < request - 1; i++) {
            painter.drawLine(m_points[i][0], m_points[i][1],
                             m_points[i + 1][0], m_points[i + 1][1]);
        }

//        delete [] points;
    }
}

void myCanvas::mouseDoubleClickEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        QPoint point = event->pos();
        myCtrlPoint *ctrl = new myCtrlPoint(this);
        m_ctrlWidgets.append(ctrl);

        ctrl->setPostion(point);
        ctrl->show();
        update();
    }

}

 7.直接显示画布


#include<QApplication>

#include"myCanvas.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    myCanvas camvas;
    camvas.show();

    return a.exec();
}

  • 15
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenGL提供了一个库函数`glMap1f()`来实现样条曲线的绘制。在使用该函数之前,需要先调用`glEnable()`函数开启样条曲线功能。 具体实现步骤如下: 1. 定义一个控制点数组,该数组存储了用于定义样条曲线形状的控制点的坐标。 2. 调用`glMap1f()`函数创建一个一维的样条曲线,其中第一个参数是`GL_MAP1_VERTEX_3`,表示创建一个三维的顶点样条曲线。第二个参数是0到1之间的浮点数,表示控制点在曲线上的密度,数值越大表示点越多,曲线越平滑。第三个参数是控制点数组的起始地址,第四个参数是每个控制点在数组中占用的连续内存块的大小。 3. 调用`glEvalCoord1f()`函数来计算曲线上某个点的坐标。 4. 使用`glBegin()`和`glEnd()`函数来绘制曲线。 例如,以下代码片段展示了如何创建并绘制一个三次Bezier样条曲线: ```c GLfloat ctrlpoints[] = { -4.0, 0.0, 0.0, -2.0, 4.0, 0.0, 2.0,-4.0, 0.0, 4.0, 0.0, 0.0 }; glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, ctrlpoints); glEnable(GL_MAP1_VERTEX_3); glBegin(GL_LINE_STRIP); for (int i = 0; i <= 30; i++) { glEvalCoord1f((GLfloat) i / 30.0); } glEnd(); ``` 这段代码创建了一个三次Bezier样条曲线,其中控制点数组`ctrlpoints`包含了四个控制点的坐标。`glMap1f()`函数创建一个三维的顶点样条曲线,密度为1.0,控制点数组的起始地址和每个控制点在数组中占用的内存块大小分别为`ctrlpoints`和`4`。`glEnable()`函数开启了样条曲线功能。 接着,使用`glBegin()`和`glEnd()`函数来绘制曲线,其中使用`glEvalCoord1f()`函数来计算曲线上的点的坐标。在`glBegin()`和`glEnd()`函数中使用`GL_LINE_STRIP`参数表示绘制一条连接所有顶点的线段。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值