在Qt中实现任意N阶贝塞尔曲线的绘制与动态调节

在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;
    }
};

四、性能优化

  1. 计算优化

    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;
    };
  2. 内存优化 :对于高阶曲线,使用 QVector 存储控制点,避免频繁内存分配。

  3. 渲染优化 :只在曲线或控制点发生变化时重绘,使用 update() 而不是 repaint()

参考代码 Qt 中实现任意N阶贝塞尔曲线绘制 & 动态调节 www.3dddown.com/csb/115590.html

五、应用场景扩展

  1. 路径规划:机器人运动轨迹平滑
  2. UI动画:自定义缓动函数
  3. 字体渲染:TrueType字体使用贝塞尔曲线
  4. 游戏开发:平滑的角色移动路径
  5. 工业设计:CAD曲线建模
相关推荐
真正的醒悟2 小时前
什么是标准等保架构
开发语言·php
郑州光合科技余经理2 小时前
同城020系统架构实战:中台化设计与部署
java·大数据·开发语言·后端·系统架构·uni-app·php
LcVong2 小时前
Android 25(API 25)+ JDK 17 环境搭建
android·java·开发语言
苏宸啊2 小时前
C++string(一)
开发语言·c++
老鱼说AI2 小时前
深入理解计算机系统1.5:抽象的重要性:操作系统与虚拟机
c语言·开发语言·汇编
a程序小傲3 小时前
高并发下如何防止重复下单?
java·开发语言·算法·面试·职场和发展·状态模式
uoKent3 小时前
c++中的封装、继承与多态
开发语言·c++·算法
Mr -老鬼3 小时前
UpdateEC - EasyClick 项目热更新系统(Rust构建)
开发语言·后端·rust
码农幻想梦3 小时前
KY221 打印日期
开发语言·模拟