在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曲线建模
相关推荐
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner1 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz6 天前
QML Hello World 入门示例
qt
xcyxiner9 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner10 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner10 天前
DicomViewer (添加模型类)3
qt
xcyxiner11 天前
DicomViewer (目录调整) 2
qt
xcyxiner11 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00613 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术13 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript