当前位置: 首页 > article >正文

多段圆弧拟合离散点实现切线连续

使用多段圆弧来拟合一个由离散点组成的曲线,并且保证切线连续。也就是说,生成的每一段圆弧之间在连接点处必须有一阶导数连续,也就是切线方向相同。

点集分割

确保每个段的终点是下一段的起点,相邻段共享连接点,避免连接点位于数据点之间。

int totalPoints = envelopePoints.size();
int m = (totalPoints - 1) / numArcs; // 每段间隔数,点数为m+1for (int i = 0; i < numArcs; ++i) {int startIdx = i * m;int endIdx = (i == numArcs - 1) ? (totalPoints - 1) : (startIdx + m);std::vector<gp_Pnt> segmentPoints(envelopePoints.begin() + startIdx, envelopePoints.begin() + endIdx + 1);// 处理该段
}

圆弧拟合约束

问题描述

给定起点 $ S(S_x, S_y) $、终点 $E(E_x, E_y) $ 和起点处的切线方向 $ \mathbf{T}(t_x, t_y) $,求解经过 $ S $ 和 $ E $ 的圆弧,并保证在 $ S $ 处的切线方向为 $ \mathbf{T} $。

推导过程

1. 圆心位置约束

圆心 $ C(O_x, O_y) $ 必须位于 $ S $ 点处与切线方向 $ \mathbf{T} $ 垂直的直线上。设 $ \mathbf{N}(-t_y, t_x) $ 为 $ \mathbf{T} $ 的法线方向,则圆心可表示为:
C = S + s ⋅ N C = S + s \cdot \mathbf{N} C=S+sN
即:
O x = S x − s ⋅ t y O_x = S_x - s \cdot t_y Ox=Sxsty
O y = S y + s ⋅ t x O_y = S_y + s \cdot t_x Oy=Sy+stx
其中,$ s $ 是圆心到$ S $点沿法线方向的距离,正负号表示方向。

2. 圆心到终点的距离约束

圆心到终点 $ E $ 的距离必须等于到起点$ S $ 的距离:
( E x − O x ) 2 + ( E y − O y ) 2 = ( S x − O x ) 2 + ( S y − O y ) 2 \sqrt{(E_x - O_x)^2 + (E_y - O_y)^2} = \sqrt{(S_x - O_x)^2 + (S_y - O_y)^2} (ExOx)2+(EyOy)2 =(SxOx)2+(SyOy)2
平方后得到:
( E x − O x ) 2 + ( E y − O y ) 2 = ( S x − O x ) 2 + ( S y − O y ) 2 (E_x - O_x)^2 + (E_y - O_y)^2 = (S_x - O_x)^2 + (S_y - O_y)^2 (ExOx)2+(EyOy)2=(SxOx)2+(SyOy)2

3. 代入圆心表达式

将 $ O_x $ 和 $ O_y $ 代入上式:
( E x − ( S x − s ⋅ t y ) ) 2 + ( E y − ( S y + s ⋅ t x ) ) 2 = ( S x − ( S x − s ⋅ t y ) ) 2 + ( S y − ( S y + s ⋅ t x ) ) 2 (E_x - (S_x - s \cdot t_y))^2 + (E_y - (S_y + s \cdot t_x))^2 = (S_x - (S_x - s \cdot t_y))^2 + (S_y - (S_y + s \cdot t_x))^2 (Ex(Sxsty))2+(Ey(Sy+stx))2=(Sx(Sxsty))2+(Sy(Sy+stx))2
简化右边:
( s ⋅ t y ) 2 + ( s ⋅ t x ) 2 = s 2 ( t x 2 + t y 2 ) = s 2 (s \cdot t_y)^2 + (s \cdot t_x)^2 = s^2 (t_x^2 + t_y^2) = s^2 (sty)2+(stx)2=s2(tx2+ty2)=s2
左边展开:
( E x − S x + s ⋅ t y ) 2 + ( E y − S y − s ⋅ t x ) 2 (E_x - S_x + s \cdot t_y)^2 + (E_y - S_y - s \cdot t_x)^2 (ExSx+sty)2+(EySystx)2
= ( E x − S x ) 2 + 2 s ⋅ t y ( E x − S x ) + s 2 t y 2 + ( E y − S y ) 2 − 2 s ⋅ t x ( E y − S y ) + s 2 t x 2 = (E_x - S_x)^2 + 2s \cdot t_y (E_x - S_x) + s^2 t_y^2 + (E_y - S_y)^2 - 2s \cdot t_x (E_y - S_y) + s^2 t_x^2 =(ExSx)2+2sty(ExSx)+s2ty2+(EySy)22stx(EySy)+s2tx2
= ( E x − S x ) 2 + ( E y − S y ) 2 + s 2 ( t x 2 + t y 2 ) + 2 s [ t y ( E x − S x ) − t x ( E y − S y ) ] = (E_x - S_x)^2 + (E_y - S_y)^2 + s^2 (t_x^2 + t_y^2) + 2s [t_y (E_x - S_x) - t_x (E_y - S_y)] =(ExSx)2+(EySy)2+s2(tx2+ty2)+2s[ty(ExSx)tx(EySy)]
由于 $ t_x^2 + t_y^2 = 1 $,上式进一步简化为:
= ( E x − S x ) 2 + ( E y − S y ) 2 + s 2 + 2 s [ t y ( E x − S x ) − t x ( E y − S y ) ] = (E_x - S_x)^2 + (E_y - S_y)^2 + s^2 + 2s [t_y (E_x - S_x) - t_x (E_y - S_y)] =(ExSx)2+(EySy)2+s2+2s[ty(ExSx)tx(EySy)]

4. 建立方程求解 $ s $

将左边和右边代入距离约束方程:
( E x − S x ) 2 + ( E y − S y ) 2 + s 2 + 2 s [ t y ( E x − S x ) − t x ( E y − S y ) ] = s 2 (E_x - S_x)^2 + (E_y - S_y)^2 + s^2 + 2s [t_y (E_x - S_x) - t_x (E_y - S_y)] = s^2 (ExSx)2+(EySy)2+s2+2s[ty(ExSx)tx(EySy)]=s2
化简得到:
( E x − S x ) 2 + ( E y − S y ) 2 + 2 s [ t y ( E x − S x ) − t x ( E y − S y ) ] = 0 (E_x - S_x)^2 + (E_y - S_y)^2 + 2s [t_y (E_x - S_x) - t_x (E_y - S_y)] = 0 (ExSx)2+(EySy)2+2s[ty(ExSx)tx(EySy)]=0
解得:
s = ( E x − S x ) 2 + ( E y − S y ) 2 − 2 [ t y ( E x − S x ) − t x ( E y − S y ) ] s = \frac{(E_x - S_x)^2 + (E_y - S_y)^2}{-2 [t_y (E_x - S_x) - t_x (E_y - S_y)]} s=2[ty(ExSx)tx(EySy)](ExSx)2+(EySy)2

5. 计算圆心和半径

代入 $ s$ 的值,得到圆心坐标:
O x = S x − s ⋅ t y O_x = S_x - s \cdot t_y Ox=Sxsty
O y = S y + s ⋅ t x O_y = S_y + s \cdot t_x Oy=Sy+stx
半径为:
R = ∣ s ∣ R = |s| R=s

6. 终点切线方向

圆弧在终点 $E $ 处的切线方向由圆心到$ E$ 的径向向量的垂直方向确定。径向向量为:
R = ( E x − O x , E y − O y ) \mathbf{R} = (E_x - O_x, E_y - O_y) R=(ExOx,EyOy)
切线方向为:
T E = ( R y , − R x ) \mathbf{T}_E = (R_y, -R_x) TE=(Ry,Rx)
归一化后得到终点处的切线方向。

7. 代码实现

// 计算必须经过起点和终点的圆弧参数
double Sx = arc.start.X();
double Sy = arc.start.Y();
double Ex = arc.end.X();
double Ey = arc.end.Y();
double tx = arc.startTangent.X();
double ty = arc.startTangent.Y();double numerator = (Ex - Sx) * (Ex - Sx) + (Ey - Sy) * (Ey - Sy); // SE_length squared
double denominator = 2 * (ty * (Sx - Ex) - tx * (Sy - Ey));if (std::abs(denominator) < 1e-6) {// 处理直线段情况arc.center = gp_Pnt(0, 0, 0);arc.radius = 0;arc.endTangent = arc.startTangent;arcs.push_back(arc);currentTangent = arc.endTangent;continue;
}double s = numerator / denominator;
double Ox = Sx - s * ty;
double Oy = Sy + s * tx;
arc.center = gp_Pnt(Ox, Oy);
arc.radius = std::abs(s);

切线连续性处理

计算每段圆弧终点处的切线方向,并传递给下一段作为起点切线方向。

// 计算终点切线方向
gp_Vec radialVec(arc.center, arc.end);
if (radialVec.Magnitude() < 1e-6) {arc.endTangent = currentTangent; // 避免除零
} else {radialVec.Normalize();arc.endTangent = gp_Dir(radialVec.Y(), -radialVec.X(), 0);
}
currentTangent = arc.endTangent;

完整代码实现

#include <vector>
#include <gp_Pnt.hxx>
#include <gp_Dir.hxx>
#include <gp_Vec.hxx>
#include <cmath>struct ArcSegment {gp_Pnt start;gp_Dir startTangent;gp_Pnt end;gp_Dir endTangent;gp_Pnt center;double radius;
};std::vector<ArcSegment> fitArcsWithTangentContinuity(const std::vector<gp_Pnt>& envelopePoints, int numArcs) {std::vector<ArcSegment> arcs;if (envelopePoints.size() < 2 || numArcs < 1) return arcs;int totalPoints = envelopePoints.size();int m = (totalPoints - 1) / numArcs; // 每段间隔数,点数为m+1gp_Dir currentTangent;for (int i = 0; i < numArcs; ++i) {int startIdx = i * m;int endIdx = (i == numArcs - 1) ? (totalPoints - 1) : (startIdx + m);if (startIdx >= envelopePoints.size() || endIdx >= envelopePoints.size() || startIdx > endIdx) {break; // Invalid segment, skip}std::vector<gp_Pnt> segmentPoints(envelopePoints.begin() + startIdx, envelopePoints.begin() + endIdx + 1);if (segmentPoints.size() < 2) {continue; // Not enough points to define a segment}ArcSegment arc;arc.start = segmentPoints.front();arc.end = segmentPoints.back();// Determine start tangent directionif (i == 0) {// First segment: use direction from first two pointsgp_Vec initialVec(segmentPoints[0], segmentPoints[1]);if (initialVec.Magnitude() < 1e-6) {currentTangent = gp_Dir(1, 0, 0); // Default direction} else {initialVec.Normalize();currentTangent = gp_Dir(initialVec);}arc.startTangent = currentTangent;} else {arc.startTangent = currentTangent;}double tx = arc.startTangent.X();double ty = arc.startTangent.Y();// Calculate parameters for constrained circle passing through start and end pointsdouble Sx = arc.start.X();double Sy = arc.start.Y();double Ex = arc.end.X();double Ey = arc.end.Y();double numerator = (Ex - Sx) * (Ex - Sx) + (Ey - Sy) * (Ey - Ey);double denominator = 2 * (ty * (Sx - Ex) - tx * (Sy - Ey));if (std::abs(denominator) < 1e-6) {// Handle straight line case (infinite radius)arc.center = gp_Pnt(0, 0, 0); // Not meaningful for straight linearc.radius = 0;arc.endTangent = arc.startTangent;arcs.push_back(arc);currentTangent = arc.endTangent;continue;}double s = numerator / denominator;// Calculate circle parametersdouble Ox = Sx - s * ty;double Oy = Sy + s * tx;arc.center = gp_Pnt(Ox, Oy);arc.radius = std::abs(s);// Calculate end tangent directiongp_Vec radialVec(arc.center, arc.end);if (radialVec.Magnitude() < 1e-6) {arc.endTangent = currentTangent; // Avoid division by zero} else {radialVec.Normalize();arc.endTangent = gp_Dir(radialVec.Y(), -radialVec.X(), 0);}currentTangent = arc.endTangent;arcs.push_back(arc);}return arcs;
}

关键点说明

  1. **点集分割:**通过均匀分割确保相邻段共享连接点,避免位置不连续。
  2. **圆弧约束拟合:**使用几何约束直接计算必须经过起点和终点的圆弧,保证位置连续。
  3. **切线连续性:**通过传递切线方向确保每段圆弧在连接点处切线方向一致。
  4. **特殊情况处理:**当分母接近零时,处理为直线段,避免计算错误。

此方案在保证切线连续的同时,简化了计算逻辑,适用于大多数离散点拟合场景。

相关文章:

多段圆弧拟合离散点实现切线连续

使用多段圆弧来拟合一个由离散点组成的曲线,并且保证切线连续。也就是说&#xff0c;生成的每一段圆弧之间在连接点处必须有一阶导数连续&#xff0c;也就是切线方向相同。 点集分割 确保每个段的终点是下一段的起点&#xff0c;相邻段共享连接点&#xff0c;避免连接点位于数…...

烧结银:解锁金刚石超强散热潜力​

烧结银&#xff1a;解锁金刚石超强散热潜力​ 在材料科学与热管理领域&#xff0c;金刚石凭借超高的热导率&#xff0c;被誉为 “散热之王”&#xff0c;然而&#xff0c;受限于其特殊的性质&#xff0c;金刚石在实际应用中难以充分发挥散热优势。而烧结银AS9335的出现&#x…...

【蓝桥杯】第十四届C++B组省赛

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;蓝桥杯 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 试题A&#xff1a;日期统计试题B&#xff1a;01串的熵试题C&#xff1a;冶炼金属试题D&#xff1a;飞机降落试题E&#xff1a;接…...

企业级海外网络专线行业应用案例及服务商推荐

在全球化业务快速发展的今天&#xff0c;传统网络技术已难以满足企业需求。越来越多企业开始选择新型海外专线解决方案&#xff0c;其中基于SD-WAN技术的企业级海外网络专线备受关注。这类服务不仅能保障跨国数据传输&#xff0c;还能根据业务需求灵活调整网络配置。接下来我们…...

阿里云服务器安装docker以及mysql数据库

(1) 官方下载路径 官方下载地址: Index of linux/static/stable/x86_64/阿里云镜像地址: https://mirrors.aliyun.com/docker-ce/下载最新的 Docker 二进制文件&#xff1a;wget https://download.docker.com/linux/static/stable/x86_64/docker-20.10.23.tgz登录到阿里云服务…...

力扣经典算法篇-5-多数元素(哈希统计,排序,摩尔投票法)

题干&#xff1a; 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 示例 1&#xff1a; 输入&#xff1a;nums [3,2,3] 输出&…...

axios介绍以及配置

Axios 是一个基于 Promise 的 HTTP 客户端&#xff0c;用于浏览器和 Node.js 环境中进行 HTTP 请求。 一、特点与基本用法 1.特点 浏览器兼容性好&#xff1a;能在多种现代浏览器中使用&#xff0c;包括 Chrome、Firefox、Safari 等。支持 Promise API&#xff1a;基于 Prom…...

深入解析:HarmonyOS Design设计语言的核心理念

深入解析&#xff1a;HarmonyOS Design设计语言的核心理念 在当今数字化迅速发展的时代&#xff0c;用户对操作系统的体验要求越来越高。华为的HarmonyOS&#xff08;鸿蒙操作系统&#xff09;应运而生&#xff0c;旨在为用户提供全场景、全设备的智慧体验。其背后的设计语言—…...

大数据技术之Scala:特性、应用与生态系统

摘要 Scala 作为一门融合面向对象编程与函数式编程范式的编程语言&#xff0c;在大数据领域展现出独特优势。本文深入探讨 Scala 的核心特性&#xff0c;如函数式编程特性、类型系统以及与 Java 的兼容性等。同时&#xff0c;阐述其在大数据处理框架&#xff08;如 Apache Spa…...

程序化广告行业(47/89):竞价指标剖析与流量对接要点

程序化广告行业&#xff08;47/89&#xff09;&#xff1a;竞价指标剖析与流量对接要点 大家好&#xff01;一直以来&#xff0c;我都希望能和大家一同深入探索程序化广告行业的奥秘&#xff0c;这也是我持续撰写这一系列博客的动力。今天&#xff0c;咱们接着来剖析程序化广告…...

dfs记忆化搜索刷题 + 总结

文章目录 记忆化搜索 vs 动态规划斐波那契数题解代码 不同路径题解代码 最长递增子序列题解代码 猜数字大小II题解代码 矩阵中的最长递增路径题解代码 总结 记忆化搜索 vs 动态规划 1. 记忆化搜索&#xff1a;有完全相同的问题/数据保存起来&#xff0c;带有备忘录的递归 2.记忆…...

vue2 全局封装axios统一管理api

在vue项目中&#xff0c;经常会使用到axios来与后台进行数据交互&#xff0c;axios丰富的api满足我们基本的需求。但是对于项目而言&#xff0c;每次都需要对异常进行捕获或者处理的话&#xff0c;代码会很繁重冗余。我们需要将其公共部分封装起来&#xff0c;比如异常处理&…...

大模型有哪些算法

大模型&#xff08;Large-scale Models&#xff09;通常指参数量大、架构复杂、在特定任务或领域表现出色的深度学习模型。这些模型的算法核心往往基于Transformer 架构及其变体&#xff0c;同时结合了大规模数据、硬件加速和优化技巧。以下是当前主流大模型及其核心算法的分类…...

【Linux】进程的详讲(中上)

目录 &#x1f4d6;1.什么是进程? &#x1f4d6;2.自己写一个进程 &#x1f4d6;3.操作系统与内存的关系 &#x1f4d6;4.PCB(操作系统对进程的管理) &#x1f4d6;5.真正进程的组成 &#x1f4d6;6.形成进程的过程 &#x1f4d6;7、Linux环境下的进程知识 7.1 task_s…...

Python Cookbook-4.17 字典的并集与交集

任务 给定两个字典&#xff0c;需要找到两个字典都包含的键(交集)&#xff0c;或者同时属于两个字典的键(并集)。 解决方案 有时&#xff0c;尤其是在Python2.3中&#xff0c;你会发现对字典的使用完全是对集合的一种具体化的体现。在这个要求中&#xff0c;只需要考虑键&am…...

优选算法的巧思之径:模拟专题

专栏&#xff1a;算法的魔法世界 个人主页&#xff1a;手握风云 目录 一、模拟 二、例题讲解 2.1. 替换所有的问号 2.2. 提莫攻击 2.3. Z字形变换 2.4. 外观数列 2.5. 数青蛙 一、模拟 模拟算法说简单点就是照葫芦画瓢&#xff0c;现在草稿纸上模拟一遍算法过程&#xf…...

【云服务器】在Linux CentOS 7上快速搭建我的世界 Minecraft 服务器搭建,并实现远程联机,详细教程

【云服务器】在Linux CentOS 7上快速搭建我的世界 Minecraft 服务器搭建&#xff0c;详细详细教程 一、 服务器介绍二、下载 Minecraft 服务端三、安装 JDK 21四、搭建服务器五、本地测试连接六、添加服务&#xff0c;并设置开机自启动 前言&#xff1a; 推荐使用云服务器部署&…...

文本分析(非结构化数据挖掘)——特征词选择(基于TF-IDF权值)

TF-IDF是一种用于信息检索和文本挖掘的常用加权算法&#xff0c;用于评估一个词在文档或语料库中的重要程度。它结合了词频&#xff08;TF&#xff09;和逆文档频率&#xff08;IDF&#xff09;两个指标&#xff0c;能够有效过滤掉常见词&#xff08;如“的”、“是”等&#x…...

【JavaSE】小练习 —— 图书管理系统

【JavaSE】JavaSE小练习 —— 图书管理系统 一、系统功能二、涉及的知识点三、业务逻辑四、代码实现4.1 book 包4.2 user 包4.3 Main 类4.4 完善管理员菜单和普通用户菜单4.5 接着4.4的管理员菜单和普通用户菜单&#xff0c;进行操作选择&#xff08;1查找图书、2借阅图书.....…...

命令模式介绍及应用案例

命令模式介绍 命令模式&#xff08;Command Pattern&#xff09; 是一种行为设计模式&#xff0c;它将请求封装为一个对象&#xff0c;从而使你可以用不同的请求对客户进行参数化&#xff0c;并且支持请求的排队、记录日志、撤销操作等功能。命令模式的核心思想是将“请求”封…...

多线程(多线程案例)(续~)

目录 一、单例模式 1. 饿汉模式 2. 懒汉模式 二、阻塞队列 1. 阻塞队列是什么 2. 生产者消费者模型 3. 标准库中的阻塞队列 4. 自实现阻塞队列 三、定时器 1. 定时器是什么 2. 标准库中的定时器 欢迎观看我滴上一篇关于 多线程的博客呀&#xff0c;直达地址&#xf…...

python笔记之函数

函数初探 python在要写出函数很简单&#xff0c;通过关键字def即可写出&#xff0c;简单示例如下 def add(a, b):return ab 以上即可以定义出一个简单的函数&#xff1a;接收两个变量a和b&#xff0c;返回a和b相加的结果&#xff0c;当然这么说也不全对&#xff0c;原因就是…...

合合信息大模型加速器2.0实测:当AI开始“读心术“与“考古“

凌晨三点的编辑部&#xff0c;我盯着屏幕上密密麻麻的财务报表和如天书般的专利图纸&#xff0c;感觉咖啡因正在大脑中上演"黑凤凰"式崩溃。这时&#xff0c;合合信息的AI助手突然开口&#xff1a;"您需要的是自动关联32个数据表&#xff0c;还是让模型直接生成…...

一个判断A股交易状态的python脚本

最近在做股票数据相关的项目&#xff0c;需要用到判断某一天某个时刻A股的状态&#xff0c;比如休市&#xff0c;收盘&#xff0c;交易中等&#xff0c;发动脑筋想了一下&#xff0c;这个其实还是比较简单的&#xff0c;这里我把实现方法分享给大家。 思路 当天是否休市 对于某…...

【go】数组与切片

数组Array 重点&#xff1a; 数组是值类型 数组的定义 var 数组名 [数组大小] 数据类型,例如var intArr [5] int&#xff0c;定义完数组后数组里的元素有默认值。、 数组的地址&intArr、&intArr[0]。 数组占据连续的内存。 int通常是4字节&#xff08;32位&…...

hadoop集群配置-scp命令

scp 命令用于在不同主机之间复制文件或目录&#xff0c;在Hadoop集群配置中常用于将配置文件或相关资源分发到各个节点。以下是 scp 命令的基本用法和在Hadoop集群配置中的示例&#xff1a; 基本语法 scp [-r] [源文件或目录] [目标用户目标主机:目标路径] - -r &#xff1a;…...

闪记(FlashNote):让灵感快速成文的轻量级笔记工具

闪记&#xff08;FlashNote&#xff09;&#xff1a;让灵感快速成文的轻量级笔记工具 你是否经常遇到这样的情况&#xff1a;桌面上放了一大堆的新建123.txt&#xff0c;想记录一个想法&#xff0c;应该是一键开个一个快捷键然后瞬间记录就自动保存了&#xff0c;现在的很多笔记…...

打车APP订单系统逻辑梳理与实现

一、逻辑分析 打车 APP 订单系统是整个打车业务的核心&#xff0c;负责处理从乘客下单到行程结束的一系列流程&#xff0c;涉及乘客、司机和平台三方的交互。 乘客端 下单&#xff1a;乘客打开 APP&#xff0c;输入上车地点、目的地&#xff0c;选择车型等信息后提交订单。此时…...

《大模型部署》——ollama下载及大模型本地部署(详细快速部署)

ollama Ollama 是一款开源跨平台的大语言模型&#xff08;LLM&#xff09;运行工具&#xff0c;旨在简化本地部署和管理 AI 模型的流程。 下载ollama 进入官网下载https://ollama.com/ 选择需要的系统下载 下载完成后直接进行安装 下载大模型 选择想要部署的模型&#…...

【蓝桥杯速成】| 17.完全背包(一维easy版)

题目一&#xff1a;爬楼梯 问题描述 57. 爬楼梯&#xff08;第八期模拟笔试&#xff09; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬至多m (1 < m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 注意&#xff1a;给定 n 是一个正整…...