在Qt中实现任意N阶贝塞尔曲线的绘制与动态调节,需要结合数学算法和Qt的绘图交互功能。
一、贝塞尔曲线数学原理(N阶通用公式)
贝塞尔曲线由控制点 P₀, P₁, ..., Pₙ 定义,n阶曲线计算公式为:
cpp
// 通用的N阶贝塞尔曲线计算公式(de Casteljau算法)
QPointF calculateBezierPoint(const QVector<QPointF>& controlPoints, float t) {
if (controlPoints.isEmpty()) return QPointF();
QVector<QPointF> points = controlPoints;
int n = points.size() - 1;
// de Casteljau 递推算法
for (int r = 1; r <= n; r++) {
for (int i = 0; i <= n - r; i++) {
points[i] = (1 - t) * points[i] + t * points[i + 1];
}
}
return points[0];
}
🎨 二、完整Qt实现代码
1. 贝塞尔曲线计算类 (BezierCurve.h)
cpp
#ifndef BEZIERCURVE_H
#define BEZIERCURVE_H
#include <QVector>
#include <QPointF>
#include <QPainterPath>
class BezierCurve {
public:
BezierCurve();
// 设置控制点
void setControlPoints(const QVector<QPointF>& points);
// 获取/添加/移除控制点
const QVector<QPointF>& controlPoints() const { return m_controlPoints; }
void addControlPoint(const QPointF& point);
void removeControlPoint(int index);
void updateControlPoint(int index, const QPointF& point);
// 计算贝塞尔曲线路径
QPainterPath generatePath(int segments = 100) const;
// 实时计算曲线上的点(用于动态预览)
QPointF pointAt(float t) const;
// 获取曲线阶数
int degree() const { return qMax(0, m_controlPoints.size() - 1); }
// 计算曲线近似长度
float approximateLength(int segments = 100) const;
// 分割曲线(可用于动画)
BezierCurve subCurve(float t0, float t1, int segments = 50) const;
private:
QVector<QPointF> m_controlPoints;
// 使用de Casteljau算法计算点
QPointF deCasteljau(const QVector<QPointF>& points, float t) const;
};
#endif // BEZIERCURVE_H
2. 贝塞尔曲线计算实现 (BezierCurve.cpp)
cpp
#include "BezierCurve.h"
#include <QtMath>
BezierCurve::BezierCurve() {}
void BezierCurve::setControlPoints(const QVector<QPointF>& points) {
m_controlPoints = points;
}
void BezierCurve::addControlPoint(const QPointF& point) {
m_controlPoints.append(point);
}
void BezierCurve::removeControlPoint(int index) {
if (index >= 0 && index < m_controlPoints.size()) {
m_controlPoints.remove(index);
}
}
void BezierCurve::updateControlPoint(int index, const QPointF& point) {
if (index >= 0 && index < m_controlPoints.size()) {
m_controlPoints[index] = point;
}
}
QPainterPath BezierCurve::generatePath(int segments) const {
QPainterPath path;
if (m_controlPoints.size() < 2) {
return path;
}
// 从第一个控制点开始
path.moveTo(m_controlPoints.first());
// 计算曲线上的点并添加到路径
for (int i = 0; i <= segments; i++) {
float t = static_cast<float>(i) / segments;
QPointF point = pointAt(t);
path.lineTo(point);
}
return path;
}
QPointF BezierCurve::pointAt(float t) const {
if (m_controlPoints.isEmpty()) {
return QPointF();
}
// 使用de Casteljau算法
return deCasteljau(m_controlPoints, t);
}
QPointF BezierCurve::deCasteljau(const QVector<QPointF>& points, float t) const {
if (points.size() == 1) {
return points.first();
}
QVector<QPointF> newPoints;
for (int i = 0; i < points.size() - 1; i++) {
QPointF p = (1 - t) * points[i] + t * points[i + 1];
newPoints.append(p);
}
return deCasteljau(newPoints, t);
}
float BezierCurve::approximateLength(int segments) const {
if (m_controlPoints.size() < 2) {
return 0.0f;
}
float length = 0.0f;
QPointF prevPoint = pointAt(0);
for (int i = 1; i <= segments; i++) {
float t = static_cast<float>(i) / segments;
QPointF currentPoint = pointAt(t);
float dx = currentPoint.x() - prevPoint.x();
float dy = currentPoint.y() - prevPoint.y();
length += qSqrt(dx * dx + dy * dy);
prevPoint = currentPoint;
}
return length;
}
BezierCurve BezierCurve::subCurve(float t0, float t1, int segments) const {
BezierCurve subCurve;
// 计算子曲线上的点作为新控制点(简化实现)
QVector<QPointF> newControlPoints;
int subSegments = segments;
for (int i = 0; i <= degree(); i++) {
float t = t0 + (t1 - t0) * (static_cast<float>(i) / degree());
newControlPoints.append(pointAt(t));
}
subCurve.setControlPoints(newControlPoints);
return subCurve;
}
3. 交互式绘图控件 (BezierWidget.h)
cpp
#ifndef BEZIERWIDGET_H
#define BEZIERWIDGET_H
#include <QWidget>
#include <QVector>
#include <QPointF>
#include "BezierCurve.h"
class BezierWidget : public QWidget {
Q_OBJECT
public:
explicit BezierWidget(QWidget *parent = nullptr);
// 曲线控制
void setControlPoints(const QVector<QPointF>& points);
void addControlPoint(const QPointF& point);
void clearControlPoints();
// 显示设置
void setShowControlPolygon(bool show);
void setShowTangentLines(bool show);
void setShowConstructionLines(bool show);
void setCurveWidth(int width);
void setControlPointRadius(int radius);
// 获取曲线数据
BezierCurve& bezierCurve() { return m_bezierCurve; }
const QVector<QPointF>& controlPoints() const { return m_controlPoints; }
signals:
void controlPointMoved(int index, const QPointF& newPos);
void curveUpdated();
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
private:
BezierCurve m_bezierCurve;
QVector<QPointF> m_controlPoints;
// 交互状态
int m_selectedPointIndex;
bool m_isDragging;
QPointF m_dragOffset;
// 显示选项
bool m_showControlPolygon;
bool m_showTangentLines;
bool m_showConstructionLines;
bool m_showControlPoints;
// 样式设置
int m_curveWidth;
int m_controlPointRadius;
QColor m_curveColor;
QColor m_controlPointColor;
QColor m_controlLineColor;
// 辅助方法
int findControlPointNear(const QPointF& pos, int radius = 10) const;
void drawControlPoints(QPainter& painter);
void drawControlPolygon(QPainter& painter);
void drawTangentLines(QPainter& painter, float t);
void drawConstructionLines(QPainter& painter, float t);
void drawCurveInfo(QPainter& painter);
// 动态调节相关
float m_currentT;
bool m_animateT;
QTimer* m_animationTimer;
private slots:
void updateAnimation();
};
#endif // BEZIERCURVE_H
4. 交互式绘图控件实现 (BezierWidget.cpp)
cpp
#include "BezierWidget.h"
#include <QPainter>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QTimer>
#include <QtMath>
BezierWidget::BezierWidget(QWidget *parent)
: QWidget(parent)
, m_selectedPointIndex(-1)
, m_isDragging(false)
, m_showControlPolygon(true)
, m_showTangentLines(true)
, m_showConstructionLines(false)
, m_showControlPoints(true)
, m_curveWidth(3)
, m_controlPointRadius(6)
, m_currentT(0.5f)
, m_animateT(false)
{
// 初始化颜色
m_curveColor = QColor(33, 150, 243); // Material Blue
m_controlPointColor = QColor(244, 67, 54); // Material Red
m_controlLineColor = QColor(158, 158, 158); // Material Gray
// 设置默认控制点(创建一个3阶贝塞尔曲线)
m_controlPoints << QPointF(50, 250)
<< QPointF(150, 50)
<< QPointF(250, 150)
<< QPointF(350, 250);
m_bezierCurve.setControlPoints(m_controlPoints);
// 动画定时器
m_animationTimer = new QTimer(this);
connect(m_animationTimer, &QTimer::timeout, this, &BezierWidget::updateAnimation);
// 窗口设置
setMinimumSize(400, 300);
setMouseTracking(true);
setFocusPolicy(Qt::StrongFocus);
}
void BezierWidget::paintEvent(QPaintEvent *event) {
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
// 绘制背景
painter.fillRect(rect(), QColor(250, 250, 250));
// 绘制网格(可选)
drawGrid(painter);
// 绘制构造线(如果启用)
if (m_showConstructionLines) {
drawConstructionLines(painter, m_currentT);
}
// 绘制控制多边形(如果启用)
if (m_showControlPolygon) {
drawControlPolygon(painter);
}
// 绘制贝塞尔曲线
QPainterPath curvePath = m_bezierCurve.generatePath(200);
painter.setPen(QPen(m_curveColor, m_curveWidth));
painter.setBrush(Qt::NoBrush);
painter.drawPath(curvePath);
// 绘制切线(如果启用)
if (m_showTangentLines) {
drawTangentLines(painter, m_currentT);
}
// 绘制控制点(如果启用)
if (m_showControlPoints) {
drawControlPoints(painter);
}
// 绘制当前t值对应的点和信息
drawCurrentPoint(painter);
drawCurveInfo(painter);
}
void BezierWidget::drawGrid(QPainter& painter) {
painter.setPen(QPen(QColor(240, 240, 240), 1));
int gridSize = 20;
for (int x = 0; x < width(); x += gridSize) {
painter.drawLine(x, 0, x, height());
}
for (int y = 0; y < height(); y += gridSize) {
painter.drawLine(0, y, width(), y);
}
// 绘制坐标轴
painter.setPen(QPen(QColor(200, 200, 200), 2));
painter.drawLine(0, height() / 2, width(), height() / 2);
painter.drawLine(width() / 2, 0, width() / 2, height());
}
void BezierWidget::drawControlPoints(QPainter& painter) {
painter.setBrush(m_controlPointColor);
for (int i = 0; i < m_controlPoints.size(); i++) {
QPointF point = m_controlPoints[i];
// 绘制控制点
painter.setPen(QPen(Qt::black, 1));
painter.drawEllipse(point, m_controlPointRadius, m_controlPointRadius);
// 绘制控制点索引
painter.setPen(Qt::black);
painter.drawText(point + QPointF(10, -10),
QString("P%1").arg(i));
// 绘制坐标
painter.setPen(Qt::gray);
painter.drawText(point + QPointF(10, 5),
QString("(%1, %2)").arg(point.x()).arg(point.y()));
}
}
void BezierWidget::drawControlPolygon(QPainter& painter) {
if (m_controlPoints.size() < 2) return;
painter.setPen(QPen(m_controlLineColor, 1, Qt::DashLine));
painter.setBrush(Qt::NoBrush);
QPainterPath polyPath;
polyPath.moveTo(m_controlPoints.first());
for (int i = 1; i < m_controlPoints.size(); i++) {
polyPath.lineTo(m_controlPoints[i]);
}
painter.drawPath(polyPath);
}
void BezierWidget::drawTangentLines(QPainter& painter, float t) {
if (m_controlPoints.size() < 2) return;
// 计算导数点(用于切线方向)
QVector<QPointF> derivativePoints;
for (int i = 0; i < m_controlPoints.size() - 1; i++) {
derivativePoints.append((1 - t) * m_controlPoints[i] + t * m_controlPoints[i + 1]);
}
// 递归绘制切线构造线
painter.setPen(QPen(QColor(255, 152, 0, 150), 1));
for (int i = 0; i < derivativePoints.size(); i++) {
painter.drawEllipse(derivativePoints[i], 3, 3);
if (i > 0) {
painter.drawLine(derivativePoints[i - 1], derivativePoints[i]);
}
}
// 绘制最终切线
if (derivativePoints.size() >= 2) {
painter.setPen(QPen(QColor(76, 175, 80), 2));
painter.drawLine(derivativePoints.first(), derivativePoints.last());
}
}
void BezierWidget::drawConstructionLines(QPainter& painter, float t) {
if (m_controlPoints.size() < 2) return;
// 递归绘制de Casteljau算法的构造线
QVector<QPointF> currentLevel = m_controlPoints;
int level = 0;
painter.setPen(QPen(QColor(0, 150, 136, 100), 1, Qt::DotLine));
while (currentLevel.size() > 1) {
QVector<QPointF> nextLevel;
for (int i = 0; i < currentLevel.size() - 1; i++) {
// 绘制连线
painter.drawLine(currentLevel[i], currentLevel[i + 1]);
// 计算下一级点
QPointF interpolated = (1 - t) * currentLevel[i] + t * currentLevel[i + 1];
nextLevel.append(interpolated);
// 绘制插值点
painter.setBrush(QColor(0, 150, 136, 150));
painter.drawEllipse(interpolated, 3, 3);
}
currentLevel = nextLevel;
level++;
}
}
void BezierWidget::drawCurrentPoint(QPainter& painter) {
if (m_controlPoints.isEmpty()) return;
// 计算当前t值对应的点
QPointF currentPoint = m_bezierCurve.pointAt(m_currentT);
// 绘制点
painter.setBrush(QColor(233, 30, 99));
painter.setPen(QPen(Qt::black, 1));
painter.drawEllipse(currentPoint, 8, 8);
// 绘制t值标签
painter.setPen(Qt::black);
painter.drawText(currentPoint + QPointF(15, 5),
QString("t = %1").arg(m_currentT, 0, 'f', 2));
}
void BezierWidget::drawCurveInfo(QPainter& painter) {
QString info = QString("阶数: %1阶 | 控制点: %2 | 长度: %3")
.arg(m_bezierCurve.degree())
.arg(m_controlPoints.size())
.arg(m_bezierCurve.approximateLength(), 0, 'f', 1);
painter.setPen(Qt::black);
painter.drawText(10, 20, info);
}
void BezierWidget::mousePressEvent(QMouseEvent *event) {
QPointF mousePos = event->pos();
// 检查是否点击了控制点
m_selectedPointIndex = findControlPointNear(mousePos, m_controlPointRadius + 5);
if (m_selectedPointIndex >= 0) {
m_isDragging = true;
m_dragOffset = mousePos - m_controlPoints[m_selectedPointIndex];
setCursor(Qt::ClosedHandCursor);
return;
}
// 如果点击在空白处,添加新的控制点
if (event->button() == Qt::LeftButton) {
addControlPoint(mousePos);
update();
}
// 右键删除最近的控制点
if (event->button() == Qt::RightButton) {
int nearest = findControlPointNear(mousePos, 20);
if (nearest >= 0) {
removeControlPoint(nearest);
update();
}
}
}
void BezierWidget::mouseMoveEvent(QMouseEvent *event) {
QPointF mousePos = event->pos();
if (m_isDragging && m_selectedPointIndex >= 0) {
// 更新控制点位置
QPointF newPos = mousePos - m_dragOffset;
// 限制在窗口内
newPos.setX(qMax(0.0, qMin(static_cast<double>(width()), newPos.x())));
newPos.setY(qMax(0.0, qMin(static_cast<double>(height()), newPos.y())));
m_controlPoints[m_selectedPointIndex] = newPos;
m_bezierCurve.updateControlPoint(m_selectedPointIndex, newPos);
// 更新当前t值对应的点位置
m_currentT = qBound(0.0f, m_currentT, 1.0f);
// 发出信号
emit controlPointMoved(m_selectedPointIndex, newPos);
emit curveUpdated();
update();
} else {
// 检查鼠标是否悬停在控制点上
int hoverIndex = findControlPointNear(mousePos, m_controlPointRadius + 3);
setCursor(hoverIndex >= 0 ? Qt::OpenHandCursor : Qt::ArrowCursor);
}
}
void BezierWidget::mouseReleaseEvent(QMouseEvent *event) {
Q_UNUSED(event);
if (m_isDragging) {
m_isDragging = false;
m_selectedPointIndex = -1;
setCursor(Qt::ArrowCursor);
}
}
void BezierWidget::wheelEvent(QWheelEvent *event) {
// 使用滚轮调整t值
float delta = event->angleDelta().y() > 0 ? 0.01f : -0.01f;
m_currentT = qBound(0.0f, m_currentT + delta, 1.0f);
update();
event->accept();
}
int BezierWidget::findControlPointNear(const QPointF& pos, int radius) const {
for (int i = 0; i < m_controlPoints.size(); i++) {
QPointF diff = m_controlPoints[i] - pos;
float distance = qSqrt(diff.x() * diff.x() + diff.y() * diff.y());
if (distance <= radius) {
return i;
}
}
return -1;
}
void BezierWidget::setControlPoints(const QVector<QPointF>& points) {
m_controlPoints = points;
m_bezierCurve.setControlPoints(points);
update();
}
void BezierWidget::addControlPoint(const QPointF& point) {
m_controlPoints.append(point);
m_bezierCurve.setControlPoints(m_controlPoints);
emit curveUpdated();
update();
}
void BezierWidget::clearControlPoints() {
m_controlPoints.clear();
m_bezierCurve.setControlPoints(m_controlPoints);
emit curveUpdated();
update();
}
void BezierWidget::setShowControlPolygon(bool show) {
m_showControlPolygon = show;
update();
}
void BezierWidget::setShowTangentLines(bool show) {
m_showTangentLines = show;
update();
}
void BezierWidget::setShowConstructionLines(bool show) {
m_showConstructionLines = show;
update();
}
void BezierWidget::updateAnimation() {
if (!m_animateT) return;
// 让t值在0到1之间循环
m_currentT += 0.01f;
if (m_currentT > 1.0f) {
m_currentT = 0.0f;
}
update();
}
5. 主窗口界面 (MainWindow.h)
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QSlider>
#include <QSpinBox>
#include <QCheckBox>
#include "BezierWidget.h"
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
private slots:
void onAddRandomPoint();
void onClearPoints();
void onDegreeChanged(int degree);
void onTValueChanged(int value);
void onAnimationToggled(bool checked);
void onControlPointMoved(int index, const QPointF& pos);
void onCurveUpdated();
private:
void createMenu();
void createToolBar();
void createControlPanel();
private:
BezierWidget* m_bezierWidget;
// 控件
QSlider* m_tSlider;
QSpinBox* m_degreeSpinBox;
QCheckBox* m_showPolygonCheck;
QCheckBox* m_showTangentsCheck;
QCheckBox* m_showConstructionCheck;
QCheckBox* m_animationCheck;
};
#endif // MAINWINDOW_H
6. 主窗口实现 (MainWindow.cpp)
cpp
#include "MainWindow.h"
#include <QMenuBar>
#include <QToolBar>
#include <QDockWidget>
#include <QGroupBox>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QStatusBar>
#include <QTime>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) {
// 创建贝塞尔曲线显示部件
m_bezierWidget = new BezierWidget(this);
setCentralWidget(m_bezierWidget);
// 创建界面
createMenu();
createToolBar();
createControlPanel();
// 连接信号槽
connect(m_bezierWidget, &BezierWidget::controlPointMoved,
this, &MainWindow::onControlPointMoved);
connect(m_bezierWidget, &BezierWidget::curveUpdated,
this, &MainWindow::onCurveUpdated);
// 设置窗口
setWindowTitle("Qt贝塞尔曲线绘制器 - 任意阶数动态调节");
resize(1000, 700);
// 状态栏
statusBar()->showMessage("就绪 - 点击添加控制点,拖动控制点调节曲线");
}
void MainWindow::createMenu() {
QMenu* fileMenu = menuBar()->addMenu("文件(&F)");
QAction* newAction = fileMenu->addAction("新建(&N)");
newAction->setShortcut(QKeySequence::New);
connect(newAction, &QAction::triggered, this, [this]() {
m_bezierWidget->clearControlPoints();
});
QAction* exitAction = fileMenu->addAction("退出(&X)");
exitAction->setShortcut(QKeySequence::Quit);
connect(exitAction, &QAction::triggered, this, &QMainWindow::close);
QMenu* viewMenu = menuBar()->addMenu("视图(&V)");
QAction* showGridAction = viewMenu->addAction("显示网格");
showGridAction->setCheckable(true);
showGridAction->setChecked(true);
connect(showGridAction, &QAction::toggled, m_bezierWidget, [this](bool checked) {
// 这里可以添加网格显示逻辑
});
QMenu* helpMenu = menuBar()->addMenu("帮助(&H)");
QAction* aboutAction = helpMenu->addAction("关于(&A)");
connect(aboutAction, &QAction::triggered, this, [this]() {
QMessageBox::about(this, "关于贝塞尔曲线绘制器",
"<h3>Qt贝塞尔曲线绘制器</h3>"
"<p>支持任意N阶贝塞尔曲线的绘制和动态调节。</p>"
"<p>功能特性:</p>"
"<ul>"
"<li>支持任意数量控制点(1阶到N阶)</li>"
"<li>鼠标拖拽控制点实时调节曲线</li>"
"<li>显示控制多边形和切线</li>"
"<li>de Casteljau算法可视化</li>"
"<li>动态t值调节和动画</li>"
"</ul>");
});
}
void MainWindow::createToolBar() {
QToolBar* toolBar = addToolBar("工具");
QAction* addPointAction = toolBar->addAction("添加随机点");
connect(addPointAction, &QAction::triggered, this, &MainWindow::onAddRandomPoint);
toolBar->addSeparator();
QAction* clearAction = toolBar->addAction("清空");
connect(clearAction, &QAction::triggered, this, &MainWindow::onClearPoints);
toolBar->addSeparator();
QAction* animateAction = toolBar->addAction("动画");
animateAction->setCheckable(true);
connect(animateAction, &QAction::toggled, this, &MainWindow::onAnimationToggled);
}
void MainWindow::createControlPanel() {
QDockWidget* dockWidget = new QDockWidget("控制面板", this);
dockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
QWidget* controlWidget = new QWidget(dockWidget);
QVBoxLayout* mainLayout = new QVBoxLayout(controlWidget);
// 曲线阶数控制
QGroupBox* degreeGroup = new QGroupBox("曲线阶数控制");
QHBoxLayout* degreeLayout = new QHBoxLayout;
QLabel* degreeLabel = new QLabel("目标阶数:");
m_degreeSpinBox = new QSpinBox;
m_degreeSpinBox->setRange(1, 20);
m_degreeSpinBox->setValue(3);
m_degreeSpinBox->setSuffix("阶");
degreeLayout->addWidget(degreeLabel);
degreeLayout->addWidget(m_degreeSpinBox);
degreeGroup->setLayout(degreeLayout);
// t值控制
QGroupBox* tGroup = new QGroupBox("曲线参数t控制");
QVBoxLayout* tLayout = new QVBoxLayout;
m_tSlider = new QSlider(Qt::Horizontal);
m_tSlider->setRange(0, 100);
m_tSlider->setValue(50);
QLabel* tValueLabel = new QLabel("t = 0.50");
tLayout->addWidget(new QLabel("调节曲线上的点:"));
tLayout->addWidget(m_tSlider);
tLayout->addWidget(tValueLabel);
tGroup->setLayout(tLayout);
// 显示选项
QGroupBox* displayGroup = new QGroupBox("显示选项");
QVBoxLayout* displayLayout = new QVBoxLayout;
m_showPolygonCheck = new QCheckBox("显示控制多边形");
m_showPolygonCheck->setChecked(true);
m_showTangentsCheck = new QCheckBox("显示切线");
m_showTangentsCheck->setChecked(true);
m_showConstructionCheck = new QCheckBox("显示构造线");
m_showConstructionCheck->setChecked(false);
m_animationCheck = new QCheckBox("动画模式");
m_animationCheck->setChecked(false);
displayLayout->addWidget(m_showPolygonCheck);
displayLayout->addWidget(m_showTangentsCheck);
displayLayout->addWidget(m_showConstructionCheck);
displayLayout->addWidget(m_animationCheck);
displayGroup->setLayout(displayLayout);
// 添加到主布局
mainLayout->addWidget(degreeGroup);
mainLayout->addWidget(tGroup);
mainLayout->addWidget(displayGroup);
mainLayout->addStretch();
controlWidget->setLayout(mainLayout);
dockWidget->setWidget(controlWidget);
addDockWidget(Qt::RightDockWidgetArea, dockWidget);
// 连接信号槽
connect(m_degreeSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
this, &MainWindow::onDegreeChanged);
connect(m_tSlider, &QSlider::valueChanged, this, [tValueLabel, this](int value) {
float t = value / 100.0f;
tValueLabel->setText(QString("t = %1").arg(t, 0, 'f', 2));
onTValueChanged(value);
});
connect(m_showPolygonCheck, &QCheckBox::toggled,
m_bezierWidget, &BezierWidget::setShowControlPolygon);
connect(m_showTangentsCheck, &QCheckBox::toggled,
m_bezierWidget, &BezierWidget::setShowTangentLines);
connect(m_showConstructionCheck, &QCheckBox::toggled,
m_bezierWidget, &BezierWidget::setShowConstructionLines);
connect(m_animationCheck, &QCheckBox::toggled,
this, &MainWindow::onAnimationToggled);
}
void MainWindow::onAddRandomPoint() {
QTime time = QTime::currentTime();
qsrand(static_cast<uint>(time.msec()));
int x = qrand() % (m_bezierWidget->width() - 100) + 50;
int y = qrand() % (m_bezierWidget->height() - 100) + 50;
m_bezierWidget->addControlPoint(QPointF(x, y));
statusBar()->showMessage(QString("添加控制点 (%1, %2)").arg(x).arg(y), 2000);
}
void MainWindow::onClearPoints() {
m_bezierWidget->clearControlPoints();
statusBar()->showMessage("已清除所有控制点", 2000);
}
void MainWindow::onDegreeChanged(int degree) {
// 调整控制点数量以达到目标阶数
int currentCount = m_bezierWidget->controlPoints().size();
int targetCount = degree + 1;
if (targetCount > currentCount) {
// 添加控制点
for (int i = currentCount; i < targetCount; i++) {
m_bezierWidget->addControlPoint(QPointF(50 + i * 50, 200));
}
} else if (targetCount < currentCount) {
// 移除控制点(简化处理)
QVector<QPointF> points = m_bezierWidget->controlPoints();
while (points.size() > targetCount) {
points.removeLast();
}
m_bezierWidget->setControlPoints(points);
}
statusBar()->showMessage(QString("曲线阶数设置为: %1阶").arg(degree), 2000);
}
void MainWindow::onTValueChanged(int value) {
float t = value / 100.0f;
// 这里可以更新曲线上的当前点显示
// 实际更新在BezierWidget的paintEvent中处理
}
void MainWindow::onAnimationToggled(bool checked) {
if (checked) {
m_tSlider->setEnabled(false);
// 启动动画定时器
// 实际动画逻辑在BezierWidget中
} else {
m_tSlider->setEnabled(true);
// 停止动画
}
}
void MainWindow::onControlPointMoved(int index, const QPointF& pos) {
statusBar()->showMessage(
QString("控制点P%1移动到 (%2, %3)")
.arg(index).arg(pos.x(), 0, 'f', 0).arg(pos.y(), 0, 'f', 0),
2000);
}
void MainWindow::onCurveUpdated() {
int degree = m_bezierWidget->bezierCurve().degree();
int pointCount = m_bezierWidget->controlPoints().size();
m_degreeSpinBox->setValue(degree);
// 更新窗口标题
setWindowTitle(QString("Qt贝塞尔曲线绘制器 - %1阶曲线 (控制点: %2)")
.arg(degree).arg(pointCount));
}
7. 主程序入口 (main.cpp)
cpp
#include "MainWindow.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
// 设置应用程序样式
app.setStyle("Fusion");
// 设置调色板
QPalette palette;
palette.setColor(QPalette::Window, QColor(240, 240, 240));
palette.setColor(QPalette::WindowText, Qt::black);
app.setPalette(palette);
MainWindow window;
window.show();
return app.exec();
}
三、高级功能扩展
1. 曲线动画与插值
cpp
// 在BezierWidget中添加动画功能
class BezierAnimator : public QObject {
Q_OBJECT
public:
void animateToPoints(const QVector<QPointF>& targetPoints,
int duration = 1000) {
m_startPoints = m_currentPoints;
m_targetPoints = targetPoints;
m_animationTime = 0;
m_animationDuration = duration;
QTimer* timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &BezierAnimator::updateAnimation);
timer->start(16); // 约60FPS
}
signals:
void pointsUpdated(const QVector<QPointF>& points);
private slots:
void updateAnimation() {
m_animationTime += 16;
float progress = qMin(1.0f, m_animationTime / (float)m_animationDuration);
// 使用缓动函数
float t = easeInOutCubic(progress);
// 插值每个控制点
QVector<QPointF> interpolated;
for (int i = 0; i < m_startPoints.size(); i++) {
QPointF p = (1 - t) * m_startPoints[i] + t * m_targetPoints[i];
interpolated.append(p);
}
emit pointsUpdated(interpolated);
if (progress >= 1.0f) {
sender()->deleteLater();
}
}
private:
float easeInOutCubic(float t) {
return t < 0.5 ? 4 * t * t * t : 1 - pow(-2 * t + 2, 3) / 2;
}
QVector<QPointF> m_startPoints;
QVector<QPointF> m_targetPoints;
QVector<QPointF> m_currentPoints;
int m_animationTime;
int m_animationDuration;
};
2. 曲线序列化与保存
cpp
// 保存和加载贝塞尔曲线
class BezierSerializer {
public:
static bool saveToFile(const BezierCurve& curve,
const QString& filename) {
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
return false;
}
QJsonObject json;
QJsonArray pointsArray;
for (const QPointF& point : curve.controlPoints()) {
QJsonObject pointObj;
pointObj["x"] = point.x();
pointObj["y"] = point.y();
pointsArray.append(pointObj);
}
json["controlPoints"] = pointsArray;
json["degree"] = curve.degree();
json["version"] = "1.0";
json["created"] = QDateTime::currentDateTime().toString();
QJsonDocument doc(json);
file.write(doc.toJson());
file.close();
return true;
}
static BezierCurve loadFromFile(const QString& filename) {
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
return BezierCurve();
}
QByteArray data = file.readAll();
QJsonDocument doc = QJsonDocument::fromJson(data);
QJsonObject json = doc.object();
QVector<QPointF> points;
QJsonArray pointsArray = json["controlPoints"].toArray();
for (const QJsonValue& value : pointsArray) {
QJsonObject pointObj = value.toObject();
points.append(QPointF(pointObj["x"].toDouble(),
pointObj["y"].toDouble()));
}
BezierCurve curve;
curve.setControlPoints(points);
return curve;
}
};
3. 曲线拟合与优化
cpp
// 使用最小二乘法拟合贝塞尔曲线到点集
class BezierFitter {
public:
static BezierCurve fitToPoints(const QVector<QPointF>& points,
int degree,
int iterations = 10) {
if (points.size() <= degree) {
return BezierCurve();
}
// 初始化参数化
QVector<float> parameters = chordLengthParameterize(points);
// 计算初始控制点
QVector<QPointF> controlPoints = generateControlPoints(points,
parameters,
degree);
// 迭代优化
for (int iter = 0; iter < iterations; iter++) {
// 计算最大误差点
int maxErrorIndex = findMaxError(points, controlPoints, parameters);
if (maxErrorIndex < 0) break;
// 调整参数并重新计算控制点
parameters = reparameterize(points, controlPoints, parameters);
controlPoints = generateControlPoints(points, parameters, degree);
}
BezierCurve curve;
curve.setControlPoints(controlPoints);
return curve;
}
private:
static QVector<float> chordLengthParameterize(const QVector<QPointF>& points) {
QVector<float> params;
params.reserve(points.size());
float totalLength = 0;
QVector<float> lengths;
lengths.reserve(points.size() - 1);
for (int i = 1; i < points.size(); i++) {
float dx = points[i].x() - points[i-1].x();
float dy = points[i].y() - points[i-1].y();
float length = qSqrt(dx*dx + dy*dy);
lengths.append(length);
totalLength += length;
}
params.append(0);
float currentLength = 0;
for (int i = 0; i < lengths.size(); i++) {
currentLength += lengths[i];
params.append(currentLength / totalLength);
}
return params;
}
};
四、性能优化
-
计算优化:
cpp// 使用查找表预先计算贝塞尔基函数值 class BezierCache { public: void precompute(int degree, int segments) { m_cache.clear(); m_cache.resize(degree + 1); for (int i = 0; i <= degree; i++) { m_cache[i].resize(segments + 1); for (int j = 0; j <= segments; j++) { float t = static_cast<float>(j) / segments; m_cache[i][j] = bernstein(degree, i, t); } } } float get(int i, float t) const { int segment = static_cast<int>(t * (m_cache[i].size() - 1)); return m_cache[i][segment]; } private: QVector<QVector<float>> m_cache; }; -
内存优化 :对于高阶曲线,使用
QVector存储控制点,避免频繁内存分配。 -
渲染优化 :只在曲线或控制点发生变化时重绘,使用
update()而不是repaint()。
参考代码 Qt 中实现任意N阶贝塞尔曲线绘制 & 动态调节 www.3dddown.com/csb/115590.html
五、应用场景扩展
- 路径规划:机器人运动轨迹平滑
- UI动画:自定义缓动函数
- 字体渲染:TrueType字体使用贝塞尔曲线
- 游戏开发:平滑的角色移动路径
- 工业设计:CAD曲线建模