贝塞尔曲线演示工具
该代码实现了一个基于Qt的贝塞尔曲线演示工具,主要功能包括:
1.可视化组件:
- 绘制网格背景和贝塞尔曲线控制线
- 显示可拖动的控制点(起点绿色,终点红色,中间点蓝色)
- 实时渲染贝塞尔曲线路径
2.交互功能:
- 鼠标拖动调整控制点位置
- 点选控制点高亮显示
- 动画演示贝塞尔曲线生成过程
3.技术特点:
- 使用QWidget实现自定义绘制
- 支持反锯齿渲染
- 通过QTimer实现平滑动画效果
- 采用面向对象设计分离控制点和曲线逻辑
该工具适用于教学演示和图形算法学习,直观展示贝塞尔曲线的数学原理和可视化效果。
cpp
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QApplication>
#include <QMainWindow>
#include <QWidget>
#include <QPainter>
#include <QPointF>
#include <QVector>
#include <QTimer>
#include <QPushButton>
#include <QLabel>
#include <QMouseEvent>
#include <QGridLayout>
#include <cmath>
class BezierPoint : public QWidget {
public:
BezierPoint(QWidget *parent = nullptr) : QWidget(parent), selected(false) {
setFixedSize(24, 24);
}
void setSelected(bool sel) {
selected = sel;
update();
}
protected:
void paintEvent(QPaintEvent *) override {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
if (selected) {
painter.setPen(QPen(Qt::red, 2));
painter.setBrush(Qt::yellow);
} else {
painter.setPen(QPen(QColor(232, 196, 142), 2));
painter.setBrush(Qt::white);
}
painter.drawEllipse(rect().center(), 8, 8);
}
private:
bool selected;
};
class BezierWidget : public QWidget {
Q_OBJECT
public:
BezierWidget(QWidget *parent = nullptr) : QWidget(parent), t(0.0), animating(false) {
setMinimumSize(800, 600);
setMouseTracking(true);
// Initialize control points
controlPoints << QPointF(100, 500) << QPointF(250, 200) << QPointF(500, 300) << QPointF(700, 450);
// Create point widgets
for (int i = 0; i < controlPoints.size(); ++i) {
BezierPoint *point = new BezierPoint(this);
point->move(controlPoints[i].x() - 12, controlPoints[i].y() - 12);
bezierPoints << point;
}
// Set up timer for animation
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, [this]() {
t += 0.005;
if (t > 1.0) {
t = 1.0;
timer->stop();
animating = false;
emit animationFinished();
}
update();
});
}
void startAnimation() {
t = 0.0;
animating = true;
timer->start(30);
}
void stopAnimation() {
animating = false;
timer->stop();
}
bool isAnimating() const { return animating; }
signals:
void animationFinished();
protected:
void paintEvent(QPaintEvent *) override {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// Draw background grid
drawGrid(painter);
// Draw control lines
drawControlLines(painter);
// Draw Bezier curve
drawBezierCurve(painter);
// Draw animation lines and points if animating
if (animating) {
drawAnimation(painter);
}
}
void mousePressEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
for (int i = 0; i < bezierPoints.size(); ++i) {
QRect pointRect(bezierPoints[i]->geometry());
if (pointRect.contains(event->pos())) {
selectedPoint = i;
bezierPoints[i]->setSelected(true);
return;
}
}
selectedPoint = -1;
}
}
void mouseMoveEvent(QMouseEvent *event) override {
if (selectedPoint >= 0 && selectedPoint < controlPoints.size()) {
// Move control point
controlPoints[selectedPoint] = event->pos();
bezierPoints[selectedPoint]->move(event->x() - 12, event->y() - 12);
update();
}
}
void mouseReleaseEvent(QMouseEvent *event) override {
Q_UNUSED(event);
if (selectedPoint >= 0 && selectedPoint < bezierPoints.size()) {
bezierPoints[selectedPoint]->setSelected(false);
}
selectedPoint = -1;
}
private:
void drawGrid(QPainter &painter) {
painter.save();
painter.setPen(QPen(QColor(200, 200, 200), 0.5));
// Draw grid lines
for (int x = 50; x < width(); x += 50) {
painter.drawLine(x, 50, x, height() - 50);
}
for (int y = 50; y < height(); y += 50) {
painter.drawLine(50, y, width() - 50, y);
}
// Draw border
painter.setPen(QPen(Qt::black, 1, Qt::DashLine));
painter.drawRect(50, 50, width() - 100, height() - 100);
painter.restore();
}
void drawControlLines(QPainter &painter) {
painter.save();
painter.setPen(QPen(QColor(232, 196, 142), 2));
// Draw control lines
for (int i = 0; i < controlPoints.size() - 1; ++i) {
painter.drawLine(controlPoints[i], controlPoints[i+1]);
}
// Draw control points
for (int i = 0; i < controlPoints.size(); ++i) {
painter.setPen(QPen(Qt::black, 1));
painter.setBrush(i == 0 ? Qt::green : (i == controlPoints.size()-1 ? Qt::red : Qt::blue));
painter.drawEllipse(controlPoints[i], 4, 4);
// Draw point label
painter.setPen(Qt::black);
painter.drawText(controlPoints[i] + QPointF(10, -10), QString::number(i+1));
}
painter.restore();
}
void drawBezierCurve(QPainter &painter) {
painter.save();
painter.setPen(QPen(Qt::red, 2));
// Draw Bezier curve
QPainterPath path;
path.moveTo(controlPoints[0]);
// Cubic Bezier curve with 4 points
if (controlPoints.size() == 4) {
path.cubicTo(controlPoints[1], controlPoints[2], controlPoints[3]);
}
// Quadratic Bezier curve with 3 points
else if (controlPoints.size() == 3) {
path.quadTo(controlPoints[1], controlPoints[2]);
}
// Linear Bezier curve with 2 points
else if (controlPoints.size() == 2) {
path.lineTo(controlPoints[1]);
}
painter.drawPath(path);
painter.restore();
}
void drawAnimation(QPainter &painter) {
// Draw intermediate points and lines
QVector<QPointF> currentLevel = controlPoints;
QVector<QVector<QPointF>> levels;
levels.append(currentLevel);
while (currentLevel.size() > 1) {
QVector<QPointF> nextLevel;
QColor lineColor;
switch (currentLevel.size() % 3) {
case 0: lineColor = QColor(0, 170, 0); break; // Green
case 1: lineColor = Qt::blue; break;
case 2: lineColor = Qt::red; break;
default: lineColor = Qt::darkGray;
}
painter.setPen(QPen(lineColor, 1.5));
// Draw lines between points in the current level
for (int i = 0; i < currentLevel.size() - 1; ++i) {
painter.drawLine(currentLevel[i], currentLevel[i+1]);
// Calculate next level points
QPointF p = interpolate(currentLevel[i], currentLevel[i+1], t);
nextLevel.append(p);
// Draw intermediate points
painter.setPen(QPen(Qt::black, 1));
painter.setBrush(lineColor);
painter.drawEllipse(p, 3, 3);
painter.setPen(QPen(lineColor, 1.5));
}
currentLevel = nextLevel;
levels.append(currentLevel);
}
// Draw the final point on the curve
if (!currentLevel.isEmpty()) {
painter.setPen(QPen(Qt::red, 2));
painter.setBrush(Qt::red);
painter.drawEllipse(currentLevel[0], 5, 5);
}
// Draw t value
painter.setPen(Qt::black);
painter.drawText(20, 30, QString("t: %1").arg(t, 0, 'f', 3));
}
QPointF interpolate(const QPointF &p1, const QPointF &p2, qreal t) {
return p1 + t * (p2 - p1);
}
private:
QVector<QPointF> controlPoints;
QVector<BezierPoint*> bezierPoints;
int selectedPoint = -1;
qreal t;
bool animating;
QTimer *timer;
};
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {
setWindowTitle("贝塞尔曲线可视化工具");
QWidget *centralWidget = new QWidget(this);
QGridLayout *layout = new QGridLayout(centralWidget);
bezierWidget = new BezierWidget(this);
layout->addWidget(bezierWidget, 0, 0, 1, 2);
// Create control buttons
QPushButton *startButton = new QPushButton("开始动画", this);
connect(startButton, &QPushButton::clicked, this, [=,this]() {
if (bezierWidget->isAnimating()) {
bezierWidget->stopAnimation();
startButton->setText("开始动画");
} else {
bezierWidget->startAnimation();
startButton->setText("停止动画");
}
});
QPushButton *resetButton = new QPushButton("重置曲线", this);
connect(resetButton, &QPushButton::clicked, this, [=,this]() {
bezierWidget->stopAnimation();
startButton->setText("开始动画");
bezierWidget->update();
});
layout->addWidget(startButton, 1, 0);
layout->addWidget(resetButton, 1, 1);
// Add information label
QLabel *infoLabel = new QLabel(
"使用说明:\n"
"1. 拖动控制点改变曲线形状\n"
"2. 点击'开始动画'查看曲线生成过程\n"
"3. 不同颜色线段展示递归插值过程\n"
"4. 红色圆点是曲线上的点(t值)"
);
layout->addWidget(infoLabel, 2, 0, 1, 2);
setCentralWidget(centralWidget);
resize(850, 700);
}
private:
BezierWidget *bezierWidget;
};
#endif // WIDGET_H