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();
}