Qt实现三次样条Cardinal曲线

news2024/11/26 10:50:09

目录

1. 前言

2. 预备知识

3. 代码实现


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_NAMESPACE

class Cardinal : public QWidget
{
    Q_OBJECT

public:
    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_OBJECT


public:
	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;
	float m_fUValue{0.5};
	bool m_bStartDraw{false};
	double m_dfMcMatrix[4][4];
	list<QPoint> m_lstPoint;
	QPainterPath path;
	bool m_bHasAddVirtPoint{false}; // 是否已经加入虚拟点
};

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时,就成了直线;越小越光滑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1097325.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全天在线的健康小助手,dido E55S Pro智能手表体验

如今只需要借助一块具有健康监测功能的智能手表&#xff0c;我们就可以轻松记录自己的日常健康数据&#xff0c;像是心率、血压和血氧等&#xff0c;通过每天规律性评估&#xff0c;我们可以及时发现身体的一些变化&#xff0c;排除一些潜在的健康隐患。最近我尝试了一款国产的…

three.js学习-智慧城市

前言 在前面基础知识&#xff08;摄像机&#xff0c;渲染器&#xff0c;轨道控制器&#xff0c;坐标轴&#xff0c;场景适配&#xff0c;渲染循环、几何体、材质、光等&#xff09;有了基础了解后&#xff0c;还需要对着色器&#xff08;坐标&#xff09;有一定的学习了解然后就…

关于Python爬虫就业与兼职方向

Python是一种强大的编程语言&#xff0c;可用于各种应用&#xff0c;如数据分析、机器学习、Web开发等。因此&#xff0c;越来越多的人开始学习Python&#xff0c;同时也有越来越多的Python引流兼职和就业机会出现。本文将探讨Python引流兼职和就业的情况。 Python引流兼职 P…

MacOS无法打开pkg,因为它来自身份不明的开发者。

解决方案&#xff1a; 低版本MacOS&#xff1a; 高版本MacOS&#xff1a;

源码分析RocketMQ之TransactionMQProducer-事物消息

Apache RocketMq 在4.3.0版本中已经支持分布式事物消息&#xff0c;采用了2PC的的思想实现提交事物消息&#xff0c;同时增加一个补偿逻辑来处理二阶段超时或者失败的消息。 一、事物消息生产者:TransactionMQProducer 发送事物消息 TransactionMQProducer#sendMessage…

手机通过WiFi连接调试UR机器人

1.测试物料 1.1ur机器人 https://item.taobao.com/item.htm?spma1z10.1-c.w4004-25069442759.18.2ff56d6bmuxX0Z&id740002623764 1.2 路由器&#xff08;TPLINK&#xff09; https://detail.tmall.com/item.htm?abbucket7&id548610924784&ns1&spma21n57.1.…

性能超越 Clickhouse | 物联网场景中的毫秒级查询案例

1 物联网应用场景简介 物联网&#xff08;Internet of Things&#xff0c;简称 IoT&#xff09;是指通过各种信息传感、通信和 IT 技术来实时连接、采集、监管海量的传感设备&#xff0c;从而实现对现实世界的精确感知和快速响应&#xff0c;继而实现自动化、智能化管理。在查…

DITA-OT 4.0新特性 - PDF themes,定制PDF样式的新方法

随着DITA-OT 4.0的发布&#xff0c;它提供了一种新的定制PDF样式方法&#xff0c;这种方法就是PDF theme。这篇文章来聊一聊这种定制PDF输出的新方法和实验结果。 在进入PDF theme细节之前&#xff0c;为各位读者梳理一下DITA-OT将DITA和Markdown发布成PDF的几种方法。 - 1 …

『Linux升级路』基本指令

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;Linux &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、认识操作系统 &#x1f4d2;1.1什么是操作系统 &#x1f4d2;1.2操作系统…

Day15|104.二叉树的最大深度

一、104.二叉树的最大深度 题目链接&#xff1a;https://leetcode.cn/problems/maximum-depth-of-binary-tree/ 文章链接&#xff1a;https://programmercarl.com/0104.%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.html#%E7%9B%B8%E5%85%B3%E9%…

Bootstrap的徽章样式设计,徽章常用作作为显示未读内容或动态计数内容

Bootstrap的徽章样式&#xff0c;通过添加类badge来实现。 目录 01-往标题中添加徽章02-给按钮、链接添加徽章03-设置徽章的颜色04-设置胶囊形徽章 01-往标题中添加徽章 通常在<span>标签添加类badge实现。 示例代码如下&#xff1a; <!DOCTYPE html> <html&g…

二维码智慧门牌管理系统:解决公安标准地址与实际楼栋名称的差异

文章目录 前言一、二维码智慧门牌管理系统的核心功能二、广泛应用领域 前言 在当今信息化社会&#xff0c;精准的地标信息是日常生活中不可或缺的部分。特别是在小区管理中&#xff0c;精准的楼栋名称和地址信息显得尤为重要。但实际上&#xff0c;公安标准地址与实际楼栋名称…

JVM 垃圾回收算法详解

目录 1 垃圾回收算法1.1 标记清除算法1.2 复制算法1.3 标记整理算法1.4 分代回收算法1.4.1 对象进入老年代的条件 1 垃圾回收算法 有四种垃圾回收算法&#xff1a; 标记清除算法复制算法标记整理算法分代回收算法 1.1 标记清除算法 标记&#xff1a;遍历内存区域&#xff0…

AlexNet论文阅读

开始之前的简介:这篇论文是王林蓉师姐推荐给我看的第一篇入门级别的cv领域的论文,也算是我入手研究生阶段的第一篇论文.我是打算先看看这一领域的论文,然后写的自己一点随笔.若有错误欢迎指正. 一. 专有词汇 非饱和神经元 dropout 饱和非线性,非饱和非线性 二. 论文结构 三. 核…

10G SDH传输分析仪该如何选择

TFN D450S 传输分析仪 功能全面 使用方便 是 通信人的不二选择

深入了解企业税收违法信息API:实现智能风险评估

引言 企业税收违法是一项严重的经济犯罪&#xff0c;可能导致严重的法律后果和金融损失。为了帮助企业和金融机构识别并预防潜在的税收违法行为&#xff0c;智能风险评估变得至关重要。在这一领域&#xff0c;企业税收违法信息API发挥着重要的作用&#xff0c;提供了关键的数据…

一图读懂「五度情报站」全盘视野,情报智取,先知先行,决策有道!

「五度情报站」是一款集企业情报监测、管理、分析等多功能于一体微信小程序&#xff0c;其依托全体量产业大数据及强大的数据治理能力&#xff0c;收录了商业、市场、竞争、企业、技术、金融等全类别情报信息&#xff0c;构建了面向用户的业务型标签体系&#xff0c;设计了实用…

PyTorch深度学习实战(21)——从零开始实现Faster R-CNN目标检测

PyTorch深度学习实战&#xff08;21&#xff09;——从零开始实现Faster R-CNN目标检测 0. 前言1. Fast R-CNN 目标检测模型组成1.1 锚框1.2 区域提议网络1.3 分类和回归 2. 实现 R-CNN 目标检测2.1 数据处理2.2 模型构建2.3 模型训练与测试 小结系列链接 0. 前言 Faster R-CN…

手机抬手亮屏解锁,用到了哪些硬件?

随着时代发展&#xff0c;智能手机以丰富的功能及便利性&#xff0c;成为了人们必不可少的物品&#xff0c;其中人脸解锁功能是非常有用的功能&#xff0c;广受年轻人的喜爱&#xff0c;那么你知道她是如何实现吗&#xff1f;今天凡小亿带你们探索&#xff01; 手机抬手亮屏解锁…

谨以此篇,纪念我2023年曲折的计算机保研之路

目录 阶段一&#xff1a;迷茫阶段二&#xff1a;准备个人意愿保研材料准备套磁老师5.1日 浙大线上编程测试5.8日 浙大线上面试 —— 一面5.17日 浙大线上面试——二面5.29日 实验室面试结果5.27日 南开线上面试6.20日 华师电话面试 阶段三&#xff1a;旅途北航CS&#xff08;6.…