CAD中的spline详解

        从dxf文件中提取点、直线、圆、弧等元素比较简单,但是Spline的处理比较麻烦。经过一段时间探索总结一下成果。

一、基本公式

1.有理样条曲线

        查阅一些资料,认为CAD中使用的Spline 是非均匀有理样条曲线。实测CAD中每个控制点权重都是-1,所以下面的有理样条公式蜕变成标准B样条公式。

 2.B样条曲线

        B样条曲线的表达式如下

 

                d-多项式阶数,CAD中默认阶数4,方程最高次幂degree=3

                k-对应控制点数,比如n+1个控制点,则k取值[0,n]

                u-参数方程中的“参数”,多个u值构成节点向量,

        在CAD中节点向量个数=控制点数+阶数,所以节点向量最后索引是n+d,CAD中的节点向量值是根据拟合点评估的,后面举例说明。

 3.混合函数

  • B样条曲线的混合函数由Cox-deBoor递归公式定义

  • 推导一次导数(没有使用)

  • 推导二次导数(没有使用)

二、CAD样条实例

 1.绘制spline

         在CAD中使用Spline命令随便绘制一段简单的样条曲线,如下图:

        如上图所示,CAD中样条曲线拟合点数量3,阶数4,那么控制点数就是3+4-2。而且默认参数新生成的样条曲线前三个控制点共线,后三个控制点共线。而且线段比例是可以计算出来的,这一点对实现自己的样条非常关键。

2.参数方程 

         上图是一个3次多项式,阶数d=4,控制点数5,则k取值[0,4],控制点是C0, C1, C2, C3, C4,将这些参数带入公式,可得该曲线的表达式:

3.计算混合函数

 

         根据上面的公式可以总结出这样一个表:

 4.混合函数计算代码


double zmSpline::blend(int k, int d, double u)
{double res = 0;if( d == 1){//1阶时取值范围只有0和1//教科书给的公式   U_k<=u<=U_k+1 ,在节点处会带来问题,//=不能两边同时存在,终点还要特殊处理if(u < m_knots[m_knots.size() - 1]) {res = (m_knots[k] <= u && u < m_knots[k + 1]) ? 1 : 0;}else {res = (m_knots[k] < u && u <= m_knots[k + 1]) ? 1 : 0;}}else{double div1 = m_knots[k + d - 1] - m_knots[k];double div2 = m_knots[k + d] - m_knots[k + 1];//如果分母为0,认为0/0=0double c1 = (std::abs(div1 ) < 10e-14) ? 0 : (u - m_knots[k]) / div1;double c2 = (std::abs(div2) < 10e-14) ? 0 : (m_knots[k + d] - u) / div2;res = c1 * blend(k, d - 1, u) + c2 * blend(k + 1, d - 1, u);}return res;}

5.计算节点向量

        如果是单纯的读取dxf文件,节点向量可以从文件读取,是已知量,不用算,下面描述节点向量如何根据拟合点来计算。

        控制点数5,阶数4,节点向量个数5+4=9,也就是:

         绘制样条时已知量只有这3个拟合点,控制点是不知道的。节点向量也不知道。默认是根据拟合点弦长(还有其他方法,弦长平方根之类的)计算的节点向量。根据上面的弦长可以计算:

         上面只得到了3个节点值,但是实际需要9个。前后端点比较特殊,阶数4,那么前后端点对应的节点值需要各自重复4次,所以最终得到的节点向量如下:

         刚好是9个,这不是巧合。假设拟合点数量是nFit, 阶数为d,那么控制点数量nCtrl=nFit+(d-2),那么节点数量

nKnot=nCtrl+d= nFit+(d-2)+d= nFit+2*d-2

        根据nFit个拟合点可以计算出nFit个节点(第一个是0),前后节点各自重复d次,总的节点数量就是nKnot=nFit+2*d-2。

 6.节点向量计算代码


void zmSpline::computeKnotFromFitPoints()
{int size = m_fitPts.size();if(size < 2) {return;}//控制点数要比拟合点数多2//2点拟合-4点控制//5点拟合-7点控制......int n = size + 2;//阶数dint d = m_info.m_degree + 1;//节点数=控制点数+阶数m_knots.resize(n + d, 0);//节点前d个是0for(int i = 0; i < size - 1; i++){double dx = m_fitPts[i + 1].X() - m_fitPts[i].X();double dy = m_fitPts[i + 1].Y() - m_fitPts[i].Y();double length = std::hypot(dx, dy);m_knots[d + i] = m_knots[d + i - 1] + length;}//节点后d个相同,倒数d-1个复制倒数第d个for(int i = m_knots.size() - d + 1; i < m_knots.size(); i++) {m_knots[i] = m_knots[i - 1];}
}

7.读取dxf文件中的Spline

        使用libDxf读取dxf文件,能够得到样条曲线的如下信息:

         Spline信息结构体代码:

//从DXF文件中获取的信息
typedef struct {unsigned int m_degree;unsigned int m_nKnots;unsigned int m_nControl;unsigned int m_nFit;int m_flags;double m_tangentStartX;double m_tangentStartY;double m_tangentStartZ;double m_tangentEndX;double m_tangentEndY;double m_tangentEndZ;
} SplineInfo;

        再看一下这个多项式,所有都是已知量,将u细分带入计算,便能够计算去线上的任意点坐标。u取值范围[0, 135.3347],如果要计算101个曲线点,将u分成100份:0,1.353347,……,分别带入公式计算可以得到101个曲线点,在自己的程序中连接各点便能得到与dxf中一样的样条。当然计算越多,精度越高。

        从上面的内容可以实现读取dxf中的spline,然后在自己的代码中显示曲线。如果是要在自己的代码绘制spline,需要继续往下探索。

        将u的取值范围划分50份,逐个计算得到50个点,然后将点拷贝到CAD中可以看到计算结果与原始曲线的拟合程度。

三、绘制自己的spline

1.已知量

        刚开始绘制的时候一切都是未知的,用鼠标随机生成的点就是spline的拟合 点。然后我们默认阶数是4,与CAD中一致。假如我们用鼠标在自己的程序中获取了如下的3个拟合点:

        此时我们的已知量如下:

                a.阶数d=4

                b.拟合点数nFit=3

                c.控制点数nCtrl=nFit+d-2=5

                d.节点数量nKnots=nCtrl+d=9,还可以用弦长评估出这个9个值

                e. 可以计算出来 

        要求解曲线的参数方程,需要根据上面的已知量,计算出5个控制点。

         5个未知数,只有3个方程,还需要2个方程,其实是已知的,可以将5个未知数削减为3个,上面的3个方程只保留中间1个,为了后面描述的完整性,还是按照5个未知数计算。

2.边界条件

        查阅一些资料,大多数是通过评估或者设定端点的导数来增加两个方程,如果在绘制spline时,我们手动输入了端点的切向,那么就要通过计算导数来增加两个方程。如果采用默认值,切向是未知的,所以计算导数也不能用。经过一段时间思考和观察,发现CAD中前3个控制点、后3个控制点总是共线(前提是绘制的时候没有输入端点切向)。

         观察发现下面的比例关系,于是有了某种猜测:

        再随便画一个复杂点的spline观察

        再次验证了:前后三个控制点不但共线,而且线段比例和拟合点弦长有关系,而拟合点的弦长又推算出节点向量。所以前后三个控制点的长度比例和可以用节点向量计算。(这是我个人观察和猜测,毕竟没法获取CAD中真正的计算方式,不保证真的正确

        把上面的猜测应用到那条简单的spline里,往下计算:

        已知节点向量:

        我们得到了下面的方程:

规范一下样式:

再加上前面的3个方程,刚好5个方程,5个未知数,汇总如下:

整理成矩阵样式:

        再次说明一下,是已知量,可以削减一下上面的矩阵,但是为了完整描述,仍然把当做未知量计算。

        上述过程简化描述如下:

                a.默认曲线阶数d

                b.获取nFit个拟合点

                c.计算nFit+2*d-2个节点值

                d.计算nFit+d-2个控制点系数

                e.应用前后3个控制点共线、线段比例这个2个边界条件

                f.整理成矩阵,计算出nFit+d-2控制点

                g.整理出样条参数方程

3.从拟合点计算控制点的代码

void zmSpline::computeCtrlFromFitPoints()
{//dxf中控制点数=拟合点数+2//控制点头尾就是拟合点头尾const int size = m_fitPts.size() + 2;m_ctrlPts.resize(size, SplinePoint());//构建系数矩阵,size个控制点要构建size×size的系数矩阵using namespace Eigen;MatrixXd A(size, size);A.setZero();//所以u的索引是从d-1开始的//i行代表拟合点i对应的计算系数//k列代表控制点k对应的混合函数int d = m_info.m_degree + 1;for(int i = 0; i < size - 2; i++)for(int k = 0; k < size; k++) {A(i, k) = blend(k, d, m_knots[i + d - 1]);}//假如有6个拟合点,就有8个控制点,阶数4,则有8+4=12个节点,//比如0,0,0,0,5,9,15,26,30,30,30,30//控制点C0,C1,C2,C3,C4,C5,C6,C7//初次生成的曲线,前三个控制点共线//(C0-C1)=A0*(C1-C2)   ->   C0+(-1-A0)*C1+A0*C2=0//A0=(5-0)/9//初次生成的曲线,后三个控制点共线//(C7-C6)=A1*(C6-C5)   ->   A1*C5+(-1-A1)*C6+C7=0//A1=(30-26)/(30-15)double A0 = (m_knots[d] - m_knots[d - 1]) / m_knots[d + 1];double A1 = (m_knots[size] - m_knots[size - 1]) / (m_knots[size] - m_knots[size - 2]);A(size - 2, 0) = 1;A(size - 2, 1) = -1 - A0;A(size - 2, 2) = A0;A(size - 1, size - 3) = A1;A(size - 1, size - 2) = -1 - A1;A(size - 1, size - 1) = 1;
//    std::cout << "A:\n" << A << std::endl;//构建拟合点矩阵,最后2行是0MatrixXd B(size, 3);B.setZero();for(int i = 0; i < m_fitPts.size(); i++){B(i, 0) = m_fitPts[i].X();B(i, 1) = m_fitPts[i].Y();B(i, 2) = m_fitPts[i].Z();}/*zmVector v0, v1, v2;BesselTanget(m_fitPts[0], m_fitPts[1], m_fitPts[2], v0, v1, v2);//起点导数B(size - 2, 0) = v0.X() ;B(size - 2, 1) = v0.Y();B(size - 2, 2) = v0.Z();zmVector v_2, v_1, v_0;int sizeOfFit = m_fitPts.size();BesselTanget(m_fitPts[sizeOfFit - 3], m_fitPts[sizeOfFit - 2], m_fitPts[sizeOfFit - 1], v_2, v_1, v_0);//终点导数B(size - 1, 0) = v_0.X();B(size - 1, 1) =  v_0.Y();B(size - 1, 2) = v_0.Z();*///    std::cout << "B:\n" << B << std::endl << std::endl;//求解控制点矩阵MatrixXd X(size, 3);X = A.fullPivLu().solve(B);
//    std::cout << "X:\n" << X << std::endl << std::endl;for(int i = 0; i < size; i++){double x = X(i, 0);double y = X(i, 1);double z = X(i, 2);SplinePoint point(x, y, z, -1);m_ctrlPts[i] = point;}
}

 计算出控制点之后带入B样条曲线,便能计算曲线上各个点。

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

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

相关文章

物联网行业中小型嵌入式文件系统详解以及使用

一 概述 在嵌入式系统使用过程中&#xff0c;为了方便数据的存储&#xff0c;我们加入了串行的外部Flash(SPI通信)。在使用存储的时候&#xff0c;如需要记录一个字符串“奇迹物联Bloom OS”&#xff0c;我们可以把这些文字转化成 ASCII 码&#xff0c;存储在数组中&#xff0c…

WPF DataGrid 列表中,DataGrid.Columns 列根据不同的值显示不同内容

需求&#xff1a;在WPF DataGrid 控件中&#xff0c;有以下列&#xff0c;绑定了一个LogType&#xff0c;值分别是0,1,2&#xff0c;根据不同的值&#xff0c;显示不同的内容以及背景 <DataGrid ItemsSource"{Binding EventLog}"><DataGrid.Columns><…

Vue路由二(嵌套多级路由、路由query传参、路由命名、路由params传参、props配置、<router-link>的replace属性)

目录 1. 嵌套(多级)路由2. 路由query传参3. 路由命名4. 路由params传参5. props配置6. <router-link>的replace属性 1. 嵌套(多级)路由 pages/Car.vue <template><ul><li>car1</li><li>car2</li><li>car3</li></ul…

postgresql-patroni高可用安装部署

简介 patronietcd,算是目前比较主流的PG高可用搭配了。 patroni都出4.0版本了,一直没时间&#xff0c;断断续续写了好久&#xff0c;最近有人问到&#xff0c;那就当作一个笔记发表吧&#xff0c;自行搭建一个测试库做测试吧。来来回回改了好几遍。文中可能不妨地方没有同步修…

Linux使用Clash,clash-for-linux

文件下载 clash-for-linuxhttps://link.zhihu.com/?targethttps%3A//zywang.lanzn.com/ijE2a1m7h6mb&#xff08;百度和阿里云盘都不支持这个文件分享&#xff09;。 使用须知 - 此项目不提供任何订阅信息&#xff0c;请自行准备Clash订阅地址。 - 运行前请手动更改.env文件…

掌握ChatGPT:高效利用AI助手

2023 年 3 月 15 日&#xff0c;ChatGPT-4 的诞生标志着人类进入了一个全新的 人机协作时代。这个时代就像一个混沌初开的新世界&#xff0c;而 ChatGPT 则是这个新世界里诞生的一个新物种。 这个新物种的心智如同一个四五岁的小孩&#xff0c;在与它频繁互动中&#xff0c;人…

BFS 解决边权为1的最短路问题

文章目录 边权为1的最短路问题1926. 迷宫中离入口最近的出口题目解析算法原理代码实现 433. 最小基因变化题目解析算法原理代码实现 127. 单词接龙题目解析算法原理代码实现 675. 为高尔夫比赛砍树题目解析算法原理代码实现 边权为1的最短路问题 最短路问题&#xff1a; 比如…

Effective C++笔记之二十三:非void函数不写return

一.main函数 Qt Creator查看汇编的步骤如下 上图是g编译器下的汇编 eax就是main()函数的返回值 如果删掉return 0&#xff1b; 可以发现编译器还是把eax的值设为了0&#xff0c;由此可见&#xff0c;即使在main函数中不写return 0&#xff0c;编译器还是会默认添加个return 0。…

R语言统计分析——散点图2(散点图矩阵、高密度散点图)

参考资料&#xff1a;R语言实战【第2版】 1、散点图矩阵 pairs()函数可以创建基础的散点图矩阵。下面代码用于绘制一个散点图矩阵&#xff0c;包含mtcars数据集中的mpg、disp、drat和wt四个变量&#xff1a; pairs(~mpgdispdratwt,datamtcars,main"Basic Scatter Plot M…

太阳能光伏板航拍红外图像缺陷分类数据集

太阳能光伏板航拍红外图像缺陷分类数据集。 数据集共包含11种不同的缺陷分类&#xff0c; 总共20000张图片&#xff0c; 可用来做基于深度学习的缺陷分类 近红外&#xff0c;黑白图像&#xff0c;图示经过可视化处理。 数据集名称 太阳能光伏板缺陷分类数据集&#xff08;Sola…

三相可控整流电路 (三相半波,三相桥式)

目录 1. 三相半波整流电路 2. 三相桥式全控整流电路 三相可控整流电路利用三相交流电源&#xff0c;通过可控硅&#xff08;晶闸管&#xff09;将交流电整流为直流电。主要有两种常见类型&#xff1a;三相半波整流电路和三相桥式全控整流电路。 1. 三相半波整流电路 三相半波…

《沈阳体育学院学报》

《沈阳体育学院学报》创刊于1982年&#xff0c;是由沈阳体育学院主办&#xff0c;面向国内外公开发行的体育类学术期刊&#xff1b;国际标准刊号为ISSN 1004-0560&#xff0c;国内刊号为CN 21-1081/G8&#xff1b;双月刊&#xff0c;单月中旬出版。 《沈阳体育学院学报》是中文…

宝塔部署python项目

宝塔部署-python项目文章浏览阅读559次&#xff0c;点赞11次&#xff0c;收藏9次。在添加项目后&#xff0c;选择项目所在的路径&#xff0c;然后命令行启动主py文件。具体先看项目日志&#xff0c;根据日志在环境管理处下载包。首先下载项目需要的python版本。_宝塔部署python…

LabVIEW提高开发效率技巧----VI服务器和动态调用

VI服务器&#xff08;VI Server&#xff09;和动态调用是LabVIEW中的两个重要功能&#xff0c;可以有效提升程序的灵活性、模块化和可扩展性。通过这两者的结合&#xff0c;开发者可以在运行时动态加载和调用VI&#xff08;虚拟仪器&#xff09;&#xff0c;实现更为复杂的应用…

C++和OpenGL实现3D游戏编程【目录】

欢迎来到zhooyu的专栏。 个人主页&#xff1a;【zhooyu】 文章专栏&#xff1a;【OpenGL实现3D游戏编程】 贝塞尔曲面演示&#xff1a; 贝塞尔曲面演示zhooyu 本专栏内容&#xff1a; 我们从游戏的角度出发&#xff0c;用C去了解一下游戏中的功能都是怎么实现的。这一切还是要…

基于yolov8的无人机检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的无人机检测系统是一项前沿技术&#xff0c;结合了YOLOv8深度学习模型的强大目标检测能力与无人机的灵活性。YOLOv8作为YOLO系列的最新版本&#xff0c;在检测精度和速度上均有显著提升&#xff0c;特别适用于复杂和高动态的场景。 该系统通过捕获实…

论文笔记:基于LLM和多轮学习的漫画零样本角色识别与说话人预测

整理了ACM MM2024 Zero-Shot Character Identification and Speaker Prediction in Comics via Iterative Multimodal Fusion&#xff09;论文的阅读笔记 背景模型框架实现细节 实验数据集实验可视化消融实验 背景 最近读到一篇新文章&#xff0c;主要是做漫画中的零样本角色识…

pikachu下

CSRF(跨站请求伪造) CSRF(get) url变成了这样了&#xff0c;我们就可以新开个页面直接拿url去修改密码 http://pikachu-master/vul/csrf/csrfget/csrf_get_login.php?username1&password2&submitLogin CSRF(post&#xff09; 这里只是请求的方式不同&#xff0c;…

HC-SR04超声波传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 3.工作原理介绍 三、程序设计 main.c文件 ultrasonic.h文件 ultrasonic.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 HC-SR04超声波传感器是通过发送和接收超声波&#xff0c;利用时间差和声音传播速度…

带你深入了解C语言指针(四)

目录 前言一、回调函数是什么&#xff1f;二、qsort使用1.什么是qsort2.qsort函数的语法解析3.回顾冒泡排序4.使用qsort函数排序整型数据4.1 思路分析4.2 完整代码&#xff1a;4.3 总体逻辑展现 5.使用qsort函数排序结构数据5.1 strcmp( )函数5.2 思路分析5.2.1 按名字比较5.2.…