Qt实现三次样条Cardinal曲线

news/2024/10/18 14:17:20/

目录

1. 前言

2. 预备知识

3. 代码实现

4. 附录


1. 前言

       在设计矢量图案的时候,我们常常需要用到曲线来表达物体造型,单纯用鼠标轨迹绘制显然是不足的。于是我们希望能够实现这样的方法:通过设计师手工选择控制点,再通过插值得到过控制点(或在附近)的一条平滑曲线。在这样的需求下,样条曲线诞生了。简而言之,样条曲线是由多个多项式按比例系数组成的多项式函数,而比例系数是由控制点决定的。Hermite曲线、Cardinal曲线在平时的开发中,经常用于模拟运动物体的轨迹,如下:

2. 预备知识

       关于Hermite曲线、Cardinal曲线的数学理论,参见如下博文:

  • [计算机动画] 路径曲线与运动物体控制(Cardinal样条曲线)。
  • 三次参数样条曲线与Cardinal曲线。

3. 代码实现

如下为用Qt实现的Cardinal曲线

Cardinal.h

#pragma once#include <QtWidgets/QWidget>
#include "ui_Cardinal.h"QT_BEGIN_NAMESPACE
namespace Ui { class CardinalClass; };
QT_END_NAMESPACEclass Cardinal : public QWidget
{Q_OBJECTpublic:Cardinal(QWidget *parent = nullptr);~Cardinal();private:Ui::CardinalClass *ui;
};

Cardinal.cpp 

#include "Cardinal.h"Cardinal::Cardinal(QWidget *parent): QWidget(parent), ui(new Ui::CardinalClass())
{ui->setupUi(this);setWindowState(Qt::WindowMaximized);ui->doubleSpinBox->setMinimum(0);ui->doubleSpinBox->setMaximum(1);ui->doubleSpinBox->setValue(0.5);ui->doubleSpinBox->setSingleStep(0.1);connect(ui->doubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), ui->myCardinalPanel, &CardinalPanel::valueChanged);connect(ui->startDrawBtn, &QAbstractButton::clicked, ui->myCardinalPanel, &CardinalPanel::startDraw);connect(ui->clearBtn, &QAbstractButton::clicked, ui->myCardinalPanel, &CardinalPanel::clear);
}Cardinal::~Cardinal()
{delete ui;
}

CardinalPanel.h 

#pragma once#include <QWidget>
#include "ui_CardinalPanel.h"
#include<vector>
using std::list;class CardinalPanel : public QWidget
{Q_OBJECTpublic:CardinalPanel(QWidget *parent = nullptr);~CardinalPanel();
public:void valueChanged(double value);void startDraw();void clear();
private:virtual void mousePressEvent(QMouseEvent* event) override;virtual void paintEvent(QPaintEvent* event)  override;private:// 画鼠标左键按下选中的点void drawPoint();// 画Cardinal曲线void drawCardinal();// 计算MC矩阵void calMcMatrix(double s);// 压入头部和尾部两个点,用于计算void pushHeadAndTailPoint();
private:Ui::CardinalPanelClass ui;bool m_bStartDraw{false};double m_dfMcMatrix[4][4];list<QPoint> m_lstPoint;QPainterPath path;
};

CardinalPanel.cpp

#include "CardinalPanel.h"
#include<QMouseEvent>
#include<QPainter>
#include <QPainterPath>
#include<vector>
using std::vector;CardinalPanel::CardinalPanel(QWidget* parent): QWidget(parent)
{ui.setupUi(this);valueChanged(0.5);
}CardinalPanel::~CardinalPanel()
{}void CardinalPanel::valueChanged(double value)
{auto s = (1 - value) / 2.0;// 计算MC矩阵calMcMatrix(s);update();
}// 计算MC矩阵
void CardinalPanel::calMcMatrix(double s)
{m_dfMcMatrix[0][0] = -s, m_dfMcMatrix[0][1] = 2 - s, m_dfMcMatrix[0][2] = s - 2, m_dfMcMatrix[0][3] = s;//Mc矩阵m_dfMcMatrix[1][0] = 2 * s, m_dfMcMatrix[1][1] = s - 3, m_dfMcMatrix[1][2] = 3 - 2 * s, m_dfMcMatrix[1][3] = -s;m_dfMcMatrix[2][0] = -s, m_dfMcMatrix[2][1] = 0, m_dfMcMatrix[2][2] = s, m_dfMcMatrix[2][3] = 0;m_dfMcMatrix[3][0] = 0, m_dfMcMatrix[3][1] = 1, m_dfMcMatrix[3][2] = 0, m_dfMcMatrix[3][3] = 0;
}void CardinalPanel::clear()
{m_bStartDraw = false;m_lstPoint.clear();update();
}// 压入头部和尾部两个点,用于计算
void CardinalPanel::pushHeadAndTailPoint()
{// 随便构造两个点auto ptBegin = m_lstPoint.begin();auto x = ptBegin->x() + 20;auto y = ptBegin->y() + 20;m_lstPoint.insert(m_lstPoint.begin(), QPoint(x, y));auto ptEnd = m_lstPoint.back();x = ptEnd.x() + 20;y = ptEnd.y() + 20;m_lstPoint.insert(m_lstPoint.end(), QPoint(x, y));
}
void CardinalPanel::startDraw()
{m_bStartDraw = true;pushHeadAndTailPoint();update();
}void  CardinalPanel::mousePressEvent(QMouseEvent* event) 
{if ((Qt::LeftButton != event->button())){return QWidget::mousePressEvent(event);}m_lstPoint.insert(m_lstPoint.end(), event->pos());update();QWidget::mousePressEvent(event);
}// 画鼠标左键按下选中的点
void CardinalPanel::drawPoint()
{QPainter painter(this);painter.setBrush(QColor(Qt::red));const auto iPointSize = 8;// 先画鼠标左键按下选中的点auto nPointIndex = 0;for (auto iter = m_lstPoint.begin(); iter != m_lstPoint.end(); ++iter){// 头部、尾部的两个控制点不绘制if (m_bStartDraw && ( (iter == m_lstPoint.begin()) || (*iter == m_lstPoint.back()) )){continue;}painter.drawEllipse(*iter, iPointSize, iPointSize);}
}// 画Cardinal曲线
void CardinalPanel::drawCardinal()
{if (m_lstPoint.size() < 4){return;}QPainter painter(this);QPen pen(QColor(Qt::green), 6);painter.setPen(pen);path.clear();auto iter = m_lstPoint.begin();++iter; // 第1个点(基于0的索引)path.moveTo(*iter);--iter;auto endIter =  m_lstPoint.end();int nIndex = 0;while (true){--endIter;++nIndex;if (3 == nIndex){break;}}for (; iter != endIter; ++iter){auto& p0 = *iter;auto& p1 = *(++iter);auto& p2 = *(++iter);auto& p3 = *(++iter);--iter;--iter;--iter;vector<QPoint>vtTempPoint;vtTempPoint.push_back(p0);vtTempPoint.push_back(p1);vtTempPoint.push_back(p2);vtTempPoint.push_back(p3);//double value[4][1];for (auto i = 0; i < 4; ++i){vtTempPoint[i] = m_dfMcMatrix[i][0] * p0 + m_dfMcMatrix[i][1] * p1 + m_dfMcMatrix[i][2] * p2 + m_dfMcMatrix[i][3] * p3;}double t3, t2, t1, t0;for (double t = 0.0; t < 1; t += 0.01){t3 = t * t * t; t2 = t * t; t1 = t; t0 = 1;auto newPoint = t3 * vtTempPoint[0] + t2 * vtTempPoint[1] + t1 * vtTempPoint[2] + t0 * vtTempPoint[3];path.lineTo(newPoint);}}painter.drawPath(path);
}void CardinalPanel::paintEvent(QPaintEvent* event)
{drawPoint();// 再画Cardinal曲线if (m_bStartDraw){drawCardinal();}
}

运行效果如下:


 

可以看到当u值越大时,曲线越尖锐,当变为1时,就成了直线;越小越光滑。

4. 附录

 如果想实现3D版的Cardinal曲线,请参考:osg实现三次样条Cardinal曲线


http://www.ppmy.cn/news/1165554.html

相关文章

TCP通信-使用线程池优化

下面的通信架构存在问题&#xff1a; 客户端与服务端的线程模型是&#xff1a; N-N的关系&#xff0c;客户端并发越多&#xff0c;系统瘫痪的越快。 引入线程池处理多个客户端消息 代码实现 public class ClientDemo1 {public static void main(String[] args) {try {Syste…

《基于 Vue 组件库 的 Webpack5 配置》9.module.exports 可为数组类型且注意编译顺序

module.exports常见是对象类型&#xff0c;其实也可用数组类型&#xff1b;注意编译顺序&#xff0c;从后往前 编&#xff1a; 也就是说先编 another.js&#xff0c;再编 index.js&#xff1b;所以代码第 9 行不能设置为 true&#xff0c;仅在第一次&#xff0c;也就是代码第19…

Jmeter测试关联接口

Jmeter用于接口测试时&#xff0c;后一个接口经常需要用到前一次接口返回的结果&#xff0c;本文主要介绍jmeter通过正则表达式提取器来实现接口关联的方式&#xff0c;可供参考。 一、实例场景&#xff1a; 有如下两个接口&#xff0c;通过正则表达式提取器&#xff0c;将第一…

Chrome使用本地修改过的js替换原js内容

步骤 1.进入开发人员工具&#xff1a;按F12 或 按ctrlshitfi 或 菜单“更多工具”->“开发人员工具” 2.在“源代码/来源”页面找到需要更改的js文件&#xff0c;“右键”->“替换内容” 3.在弹出的标签点击“选择文件夹”来选择一个存放内容的本地文件夹 4.弹出的询问标…

Unity 中3D数学基础-向量

本文主要全面讲解向量的数学运算已经对应的实际应用意义! 1.向量的认识 向量(矢量) 有1、2、3维! 向量既可以表示大小也可以表示方向,也可以表示一个空间坐标点!因此他虽然是一个数学数字表示,但是实际空间意义有这三种! 坐标点A(x,y,z) 表示与原点连线构成的方向…

黑马程序员Java Web--14.综合案例--修改功能实现

一、BrandMapper包 首先&#xff0c;在BrandMapper包中定义用来修改的方法&#xff0c;和使用注解的sql语句。 BrandMapper包所在路径&#xff1a; package com.itheima.mapper; /**** 修改* **/Update("update tb_brand set brand_name #{brandName},company_name #{c…

工程中的SOVD——从ECU到车辆

SOVD标准正在改变诊断方式&#xff0c;特别是在互联网上当多个合作方进行交互时&#xff0c;其提供了很大的优势。在开发的早期阶段&#xff0c;需要使用附加的方法来利用这个标准&#xff0c;因为该标准并不是专为ECU诊断而开发的&#xff0c;而且还需格外注意数据的处理&…

简单谈谈我参加数据分析省赛的感受与体会

数据分析省赛的感受与体会 概要考试前的感受与体会考试注意事项小结 概要 大数据分析省赛指的是在省级范围内举办的大数据分析竞赛活动。该竞赛旨在鼓励和推动大数据分析领域的技术创新和人才培养&#xff0c;促进大数据技术与应用的深度融合&#xff0c;切实解决实际问题。参…