首页 > 其他分享 >.NET 模拟&编辑平滑曲线

.NET 模拟&编辑平滑曲线

时间:2024-12-12 20:26:16浏览次数:5  
标签:double 平滑 曲线 贝塞尔 控制点 NET 模拟 leftPointX

本文介绍不依赖贝塞尔曲线,如何绘制一条平滑曲线,用于解决无贝塞尔控制点的情况下绘制曲线、但数据点不在贝塞尔曲线的场景。

在上一家公司我做过一个平滑曲线编辑工具,用于轮椅调整加减速曲线。基于几个用户可控制的点,生成一条平滑的曲线,控制点需要保持在曲线上。

今天和小伙伴沟通,白板以自定义形状绘制笔迹,也可以使用到这个数据点模拟的技术,我回顾总结下

贝塞尔平滑曲线

我们先讲贝塞尔曲线GDI+ 中的贝塞尔自由绘制曲线 - Windows Forms .NET Framework | Microsoft Learn。一般情况我们绘制平滑曲线,直接以贝塞尔曲线API将多个点作为参数,直接进行绘制。这种情况下API会自动将第一个点作为控制点,得到贝塞尔曲线,比如下面生成一条平滑Geometry:

 1     var geometryTest = new StreamGeometry();
 2     using(var ctx = geometryTest.Open())
 3     {
 4         ctx.BeginFigure(_points[0], true, false);
 5         if(keyPoints.Count % 2 == 0)
 6         {
 7             //绘制二阶贝塞尔函数,需要保证为偶数点
 8             ctx.PolyQuadraticBezierTo(keyPoints, true, true);
 9         }
10         else
11         {
12             //绘制二阶贝塞尔函数,需要保证为偶数点
13             keyPoints.Insert(0, keyPoints[0]);
14             ctx.PolyQuadraticBezierTo(keyPoints, true, true);
15         }
16     }

这里的PolyQuadraticBezierTo函数,塞点集列表进去并设置平滑参数isSmoothJoin=true

1     public abstract void PolyQuadraticBezierTo(
2       IList<Point> points,
3       bool isStroked,
4       bool isSmoothJoin);
5 
6     public abstract void PolyBezierTo(IList<Point> points, bool isStroked, bool isSmoothJoin);

官网有介绍,列表中第一个点作为控制点:StreamGeometryContext.PolyQuadraticBezierTo 方法 (System.Windows.Media) |Microsoft 学习

上面是自动设置控制点,这类实现方案会有一个问题:数据点最终可能不在曲线上

基于贝塞尔曲线,我们也可以计算控制点。但计算控制点,也是同样无法保证原始数据点会在拟合后的曲线上。

模拟平滑曲线

以现有数据点,如果直接相连肯定只会生成多个折线。如果我们添加多个点,可以模拟一条类似曲线路径的多边形近似点集,与Geometry下的FlattenedPathGeometry有点类似。

方案一,可以使用MathNet.Numerics生成一条X方向的N阶曲线,然后输入X坐标输出Y坐标,得到曲线上的点。 MathNet.Numerics可以参考 .NET 白板书写加速-曲线拟合预测 - 唐宋元明清2188 - 博客园。但这方案会生成无数点,曲线绘制性能无法得到保证。所以添加这些曲线路径的点,如何以最小的点集实现?可以对相邻点,对向量角度变化以及相邻间距设置一个最小阈值,最终得到符合的点集

方案二,用我之前实现方案,根据最简多项式代码算出近似样条曲线点集。原理同MathNet.Numerics里的Polynomial函数,下面是部分代码:

 1     private const double Tolerance = 0.5;
 2 
 3     /// <summary>
 4     /// 获取拟合后的点集
 5     /// </summary>
 6     /// <param name="points"></param>
 7     /// <returns></returns>
 8     public static List<Point> GetFittingLinePoints(List<Point> points)
 9     {
10         var orderedPoints = (from pt in points orderby pt.X select pt).ToList();
11         var secondDerivatives = SecondDerivativeHelper.GetSecondDerivatives(orderedPoints);
12         List<Point> polyLinePoints = PointFakeApproximationHelper.GetSplinePolyLineApproximation(orderedPoints, secondDerivatives, Tolerance);
13         return polyLinePoints;
14     }

获取俩点之间点集:

 1         /// <summary>
 2         /// 用折线逼近三次多项式并给出公差。
 3         /// </summary>
 4         /// <param name="leftPoint">三次多项式左点</param>
 5         /// <param name="rightPoint">三次多项式右点</param>
 6         /// <param name="secondDerivativeLeft">左点三次多项式二阶导数.</param>
 7         /// <param name="secondDerivativeRight">右端三次多项式二阶导数.</param>
 8         /// <param name="tolerance">公差,即样条到近似折线的最大距离。</param>
 9         /// <returns>在给定公差的情况下逼近三次多项式的多边形点的列表。</returns>
10         private static Collection<Point> GetApproximation(Point leftPoint, Point rightPoint, double secondDerivativeLeft, double secondDerivativeRight, double tolerance)
11         {
12             // 左右俩点的X、Y轴值
13             double leftPointX = leftPoint.X, rightPointX = rightPoint.X;
14             double leftPointY = leftPoint.Y, rightPointY = rightPoint.Y;
15             // 次区间多项式系数
16             double a = (secondDerivativeRight - secondDerivativeLeft) / (6 * (rightPointX - leftPointX));
17             double b = (secondDerivativeLeft - 6 * a * leftPointX) / 2;
18             double c = (rightPointY - rightPointX * rightPointX * (a * rightPointX + b) - leftPointY + leftPointX * leftPointX * (a * leftPointX + b)) / (rightPointX - leftPointX);
19             double d = leftPointY - leftPointX * (leftPointX * (a * leftPointX + b) + c);
20 
21             //如果a的值为0,则给a赋值double类型的最小正数
22             if(a == 0)
23                 a = double.Epsilon;
24 
25             //通过多项式与拆线的逼近,获取多边形点的列表
26             Collection<Point> points = CubicPolynomialPolylineApproximation.Approximate(new Polynomial(new double[] { d, c, b, a }), leftPointX, rightPointX, tolerance);
27             return points;
28         }

效果如下图,左侧是原数据点集(绿色),右则截图是模拟后的点集显示(红色):

 

把这些新增点与原数据点,用直接连接起来,就是一条比较平滑的曲线了。同时原数据点也在拟合的曲线上。另外,如果还需要优化下这些线段的平滑,可以使用贝塞尔曲线替换直线连接,已经加了很多密集点,绿点不会脱离曲线的。

Github仓库代码 GitHub - kybs00/CurveLineEditDemo: 平滑曲线模拟及编辑Demo

编辑平滑曲线

上面我们已经完成了平滑曲线的点集模拟,连接起来就是一条曲线。在一些业务场景中,需要以曲线上的点为控制操作点,移动点以达到编辑曲线。

在曲线上设置多个操作点。如何在曲线上设置期望位置的点,可以看 .NET 曲线上的点- 获取距离最近的点 - 唐宋元明清2188 - 博客园。点击时,获取曲线离点击位置最近的点即可

选择点后,操作控制点移动。曲线根据操作点的位置变更,重新生成新的曲线。曲线编辑效果如下图:

重新生成曲线这部分代码没有啥难点,核心代码就是上面的平滑曲线模拟。看上面仓库代码即可

 

参考文章:

GDI+ 中的贝塞尔自由绘制曲线 - Windows Forms .NET Framework | Microsoft Learn

Bezier曲线反求控制点_如何反求贝塞尔曲线的控制点-CSDN博客

标签:double,平滑,曲线,贝塞尔,控制点,NET,模拟,leftPointX
From: https://www.cnblogs.com/kybs0/p/18603295

相关文章

  • 12.12 CW 模拟赛 T3. 消除贫困
    思路朴素容易发现一个人资金变化是这样的:对于\(op=1\)的情况,会将其直接变成\(x\)对于\(op=2\)的情况,将其变成\(\max(x,当前值)\)直接用线段树暴力的维护即可巧妙容易发现\(op=2\)相当于一个大保底,我们先倒着处理出每个人到\(i\)位置至少有多少......
  • 12.12 CW 模拟赛 T1. 理想路径
    前言作为一个别的不行抗伤无敌的\(\rm{man}\),区区反向\(\rm{rk\1}\)不足为惧\(\rm{HD0X}\)巨佬场切\(2700\),\(\%\%\%\)思路朴素先把考场上一些基础的想法搬过来考虑一个环什么时候会导致产生字典序负环,这个好像还比较显然,就是如果出去的那个点的字典序小......
  • 通过模拟发送mq消息来测试实现-依据支付凭证不能重复入账
    通过模拟发送mq消息来测试实现-依据支付凭证不能重复入账1.依据MQ消息的json串转换为md5记录,作为收银台表的唯一约束。如果支付状态发生变化,则payMd5会跟随着变化。2.消息流程客户支付成功>微信支付微服务接收到微信支付的异步通知回调通知>发送给支付网关微服务(发送mq消息......
  • EtherNet/IP转profinet网模块应用在AB罗克韦尔PLC与西门子1500PLC通讯案例
        在工业自动化领域,不同品牌的PLC(可编程逻辑控制器)之间进行通讯往往是项目实施中面临的一个重要问题。本文将详细介绍如何利用EtherNet/IP转profinet网关模块(远创智控的YC-PN-EIP)实现罗克韦尔PLC与西门子1500PLC之间稳定、高效的通讯,帮助大家在类似的项目......
  • ubuntu网络配置工具netplan详解
     1.首先查看当前的 netplan配置文件:ls/etc/netplan/通常会有一个类似 01-netcfg.yaml 或 50-cloud-init.yaml 的文件。 2.编辑netplan配置文件firefly@firefly:~$vim/etc/netplan/01-netcfg.yamlnetwork:version:2renderer:networkdethernets:......
  • 一套以用户体验出发的.NET8 Web开源框架
    前言今天大姚给大家分享一套以用户体验出发的.NET8Web开源框架:YiFramework。项目介绍YiFramework是一个基于.NET8+Abp.vNext+SqlSugar的DDD领域驱动设计后端开源框架,前端使用Vue3,项目架构模式三层架构\DDD领域驱动设计,内置RBAC权限管理、BBS论坛社区系统以用户体验出发......
  • ResNet模型的训练和推理
    ResNet2024年5月7日更新在此教程中,我们将对ResNet模型及其原理进行一个简单的介绍,并实现ResNet模型的训练和推理,目前支持数据集有:MNIST、fashionMNIST、CIFAR10等,并给用户提供一个详细的帮助文档。目录基本介绍ResNet描述为什么要引入ResNet?网络结构分析ResNet实现总......
  • VGGNet模型的训练和推理
    VGGNet2024年5月10日更新在此教程中,我们将对VGGNet模型及其原理进行一个简单的介绍,并实VGGNet模型的训练和推理,目前支持数据集有:MNIST、fashionMNIST、CIFAR10等,并给用户提供一个详细的帮助文档。目录基本介绍VGGNett描述创新点网络结构VGGNet的特点VGGNet实现总体概......
  • LeNet5模型的训练和推理
    LeNet52024年5月1日更新在此教程中,我们将对LeNet5模型及其原理进行一个简单的介绍,并实现不依赖库(使用了torch库来加载数据集)的LeNet模型的训练和推理,目前支持MNIST、FashionMNIST和CIFAR-10等数据集,给用户提供一个详细的帮助文档。目录基本介绍LeNet5描述网络结构LeNet......
  • 星空模拟程序
    这个程序可以在控制台中模拟星空闪烁的效果,每次循环都会随机生成星星的位置并绘制,然后短暂停顿,给人以星空动态变化的感觉#include<stdio.h>#include<stdlib.h>#include<time.h>#include<windows.h>#include<conio.h> //函数声明,用于获取控制台窗口的宽度intgetC......