2019-6-27-WPF-如何给定两个点画出一条波浪线
| title | author | date | CreateTime | categories |
|---|---|---|---|---|
|
WPF 如何给定两个点画出一条波浪线
|
lindexi
|
2019-6-27 10:17:6 +0800
|
2019-6-26 14:36:5 +0800
|
WPF
|
在知道两个点可以连一条线段,那么将线段修改为波浪线可以如何做?
在知道两个点 p1 和 p2 的时候,按照这两个点画出一条波浪线要求波浪线的中间就在两个点连接的线段
我做了一个程序用于显示效果,这个程序的界面用到很好用的 HandyControl 库
可以尝试设置一些参数画出好看的线,这个程序全部开源,可以在github下载全部源代码,核心源代码将会放在本文后面
下面告诉大家如何在知道两个点的时候画出一条波浪线
在开始之前需要小伙伴熟悉贝塞尔的概念如果使用控制点画出贝塞尔,使用本文的方法画出的波浪线会比直接连出线段的投影长度短一点,但是看起来几乎相同
贝塞尔曲线可以通过两个点和一个锚点控制一段曲线,我将两个点连接一段线段在这段线段上均匀放置一些锚点,在线段的上下方分别放控制点,通过一个锚点和两个控制点可以绘制出一段贝塞尔线。请看图绿色的点是控制点,黑色的点是锚点刚好所有的锚点都在一条线段上,通过锚点和控制点就可以画出曲线
将控制点和锚点链接作为贝塞尔的效果如下图
这里用到了两个控制点和一个锚点画贝塞尔,这里需要用到 Path 绘制的特性。在 Path 绘制的时候将会不断记住上一个点的值,也就是我输入的点其实会影响到上一条绘制命令的点如在我已经知道了两个控制点和一个锚点组成的数组。我每次调用 streamGeometryContext.BezierTo 的时候都需要传入三个点,第一个点控制点用来控制前一个命令的锚点而第二个点也是控制点用于控制本次调用方法里面的锚点也就是第三个点
从上面可以知道绘制曲线会受到很多常量的影响,这些常量建议作为用户定义。为了让大家能和代码关联起来,先定义一些变量,如增幅和频率等,可以下载我的代码运行一下,自己修改这些值
知道了曲线是如何画的,现在的问题就是如何求出这两个控制点和一个锚点的值
本文将会用到 bcp 算法,在说到用两个点绘制波浪线的时候,小伙伴最先想到的是使用水平线,但实际上任意两个点很少是水平的,也就是需要将波浪线绘制在一个协办上,于是如下图定义了摆角和斜角
这里的 bcpInclination 是摆角表示波形的高度,这个值配合频率可以决定波形的高度,反过来知道频率和高度就可以计算出这个值
另外还可以了斜角 wiggle angle 表示给出的两点连出的线段与水平的夹角
现在尝试计算一下摆角的值,在计算摆角的值之前需要先拿到频率和高度
从图片可以看到三角形 abc 的两个值,如 a 的值就是四分之一的频率,三角形的 b 的值就是波形的高度的一半
a = wave length / 4
b = wave height / 2
这里的频率其实和波形长度是相同的,计算方法是在输入两个点的时候,计算出线段的长度
var distance = (p1 - p2).Length;
通过波形的长度计算出这段长度有多少个波形
var waveLength = WaveLength;
var howManyWaves = distance / waveLength;
于是反过来计算波形的频率
var waveInterval = distance / howManyWaves;
此时简化数学计算,可以知道 waveInterval 就是波形的长度
于是按照向量的计算方法可以知道摆角的值可以使用下面代码计算
var bcpInclination = CalculateAngle(new Point(0, 0), new Point(a, b));
上图的三角形的 c 边就是定义的 max bcp length 的值,表示在当波形是三角形时的长度
用三角形的计算方法,在知道 a 和 b 的时候求 c 边的长度可以使用下面方法
var maxBcpLength =
Math.Sqrt(waveInterval / 4.0 * (waveInterval / 4.0) + waveHeight / 2.0 * (waveHeight / 2.0));
其实上面的代码只是模拟计算而已,代码可以继续优化
在知道了 maxBcpLength 可以通过一个常量知道控制点距离锚点的距离
如上图可以知道 bcpLength 的值就是控制点距离锚点的距离
刚好 bcpLength 就是最大长度的某个比例,可以通过下面代码计算
var bcpLength = maxBcpLength * curveSquaring;
上面代码的 curveSquaring 是一个常量可以给用户定义,用于控制波形实际高度与给定高度的关系
在知道了控制点距离锚点的长度,需要求当前控制点的坐标需要再知道两个值,一个是某个端点的坐标另一个是线段的切斜角度
刚好控制点的一个端点就是锚点,也就是链接锚点和控制点成为的线段刚好就是控制点距离锚点的长度,那么先求锚点对应的值
求锚点的值可以转换为求某线段上,均匀分多个点,求这些点的坐标的计算方法
如果这条线段是水平线段,那么很好求,于是先假设线段是水平的求出均匀的点,通过知道三角形的两个角度和一条边可以计算出另一条边的长度,请看图和代码这部分比较简单
var flexPt = new Point(prevFlexPt.X + Math.Cos(angle) * waveInterval / 2.0,
prevFlexPt.Y + Math.Sin(angle) * waveInterval / 2.0);
上面代码的 prevFlexPt 就是上一个锚点的坐标,而 flexPt 就是当前锚点的坐标,第一个锚点就是线段的一个端点
现在第二个计算是倾斜角,请看下图可以知道控制点的倾斜角
可以知道倾斜角和摆角相关,在知道锚点的坐标和距离和倾斜角就可以计算出控制点的坐标,在知道控制点和锚点就可以画出曲线
这是核心代码
var p1 = startPoint;
var p2 = endPoint; var distance = (p1 - p2).Length; var angle = CalculateAngle(p1, p2);
var waveLength = WaveLength;
var waveHeight = WaveHeight;
var howManyWaves = distance / waveLength;
var waveInterval = distance / howManyWaves;
var maxBcpLength =
Math.Sqrt(waveInterval / 4.0 * (waveInterval / 4.0) + waveHeight / 2.0 * (waveHeight / 2.0)); var curveSquaring = CurveSquaring;
var bcpLength = maxBcpLength * curveSquaring;
var bcpInclination = CalculateAngle(new Point(0, 0), new Point(waveInterval / 4.0, waveHeight / 2.0)); var wigglePoints = new List<(Point bcpOut, Point bcpIn, Point anchor)>();
var prevFlexPt = p1;
var polarity = 1; for (var waveIndex = 0; waveIndex < howManyWaves * 2; waveIndex++)
{
var bcpOutAngle = angle + bcpInclination * polarity;
var bcpOut = new Point(prevFlexPt.X + Math.Cos(bcpOutAngle) * bcpLength,
prevFlexPt.Y + Math.Sin(bcpOutAngle) * bcpLength);
var flexPt = new Point(prevFlexPt.X + Math.Cos(angle) * waveInterval / 2.0,
prevFlexPt.Y + Math.Sin(angle) * waveInterval / 2.0);
var bcpInAngle = angle + (Math.PI - bcpInclination) * polarity;
var bcpIn = new Point(flexPt.X + Math.Cos(bcpInAngle) * bcpLength,
flexPt.Y + Math.Sin(bcpInAngle) * bcpLength); wigglePoints.Add((bcpOut, bcpIn, flexPt)); polarity *= -1;
prevFlexPt = flexPt;
} var streamGeometry = new StreamGeometry();
using (var streamGeometryContext = streamGeometry.Open())
{
streamGeometryContext.BeginFigure(wigglePoints[0].anchor, true, false); for (var i = 1; i < wigglePoints.Count; i += 1)
{
var (bcpOut, bcpIn, anchor) = wigglePoints[i]; streamGeometryContext.BezierTo(bcpOut, bcpIn, anchor, true, false);
}
} private static double CalculateAngle(Point p1, Point p2)
{
return Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
}
上面代码用到一些属性
public double WaveLength { get; set; } = 4;
public double WaveHeight { get; set; } = 5;
public double CurveSquaring { get; set; } = 0.57;
这个类的代码在 WaveLine.cs
本文参考了 How to draw a wiggle between two points with Python and Drawbot - Medium
2019-6-27-WPF-如何给定两个点画出一条波浪线的更多相关文章
- 2019/8/27 Test(luogu 五月天模拟赛)
\(2019/8/27\)大考 \(\color{#ff0808}{\text{初二诀别赛(SAD)}}\) 题目名称 链接 寿司 \(BSOJ5111\) 秀秀的森林 \(BSOJ5125\) 分组 ...
- 给定两个字符串 s 和 t,它们只包含小写字母。 字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。 请找出在 t 中被添加的字母。
给定两个字符串 s 和 t,它们只包含小写字母.字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母.请找出在 t 中被添加的字母. 示例: 输入: s = "abcd" ...
- 给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组
题目描述: 给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组. 说明:初始化 nums1 和 nums2 的元素数量分别为 m ...
- Alpha冲刺(4/10)——2019.4.27
所属课程 软件工程1916|W(福州大学) 作业要求 Alpha冲刺(4/10)--2019.4.27 团队名称 待就业六人组 1.团队信息 团队名称:待就业六人组 团队描述:同舟共济扬帆起,乘风破浪 ...
- 给定两个数组,这两个数组是排序好的,让你求这两个数组合到一起之后第K大的数。
题目:给定两个数组,这两个数组是排序好的,让你求这两个数组合到一起之后第K大的数. 解题思路: 首先取得数组a的中位数a[aMid],然后在b中二分查找a[aMid],得到b[bMid],b[bSt] ...
- 9.11排序与查找(一)——给定两个排序后的数组A和B,当中A的末端有足够的缓冲空间容纳B。将B合并入A并排序
/** * 功能:给定两个排序后的数组A和B,当中A的末端有足够的缓冲空间容纳B.将B合并入A并排序. */ /** * 问题:假设将元素插入数组A的前端,就必须将原有的元素向后移动,以腾出空间. ...
- WPF入门(三)->两个几何图形合并(CombinedGeometry)
原文:WPF入门(三)->两个几何图形合并(CombinedGeometry) 在WPF中,提供了一个CombinedGeometry对象可以使两个几何图形合并产生效果 CombinedGeom ...
- Beta冲刺(6/7)——2019.5.27
所属课程 软件工程1916|W(福州大学) 作业要求 Beta冲刺(6/7)--2019.5.27 团队名称 待就业六人组 1.团队信息 团队名称:待就业六人组 团队描述:同舟共济扬帆起,乘风破浪万里 ...
- [New!!!]欢迎大佬光临本蒟蒻的博客(2019.11.27更新)
更新于2019.12.22 本蒟蒻在博客园安家啦!!! 本蒟蒻的博客园主页 为更好管理博客,本蒟蒻从今天开始,正式转入博客园. 因为一些原因,我的CSDN博客将彻底不会使用!!!(带来不便,敬请谅解) ...
随机推荐
- CSIC_716_20191114【生成器、匿名函数、内置函数、三元表达式、列表生成式、生成器表达式】
生成器: 函数与yield连用,凡是函数中有yield的,调用该函数的时候均不会立即执行,而是会返回一个生成器. 生成器本质上是一个迭代器,需要通过 [生成器.__next__()]或者[nex ...
- leetcode-157周赛-5216-统计元音字母序列的数目
题目描述: 方法:倒推 class Solution(object): def countVowelPermutation(self, n): MOD = 10 ** 9 + 7 a=e=i=o=u= ...
- XAMPP的安装及使用教程
https://blog.csdn.net/qq_36595013/article/details/80373597#3%E9%85%8D%E7%BD%AEapache
- duilib教程之duilib入门简明教程10.界面设计器 DuiDesigner
上一个教程讲解了怎么布局最大化.最小化.关闭按钮,但是如果手动去计算这三个按钮的位置和大小的话,非常的不直观,也很不方便. 所以这一章准备介绍duilib的UI设计器,由于这个设计器很不完善,也 ...
- 图片压缩(js压缩,底部有vue压缩图片依赖使用的教程链接)
directTurnIntoBase64(fileObj, callback) { var r = new FileReader(); // 转成base64 r.onload = function( ...
- JSON关联属性转换异常
问题:FastJSON在转换对象过程中,该对象还有关联属性,该属性还是一个对象,就出现栈溢出异常,会报一下错误,解决办法:在该属性类的一边加上@JSONField(serialize=false);有 ...
- [kuangbin带你飞]专题一 简单搜索 - F - Prime Path
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #i ...
- Celery - 异步任务 , 定时任务 , 周期任务
1.什么是Celery?Celery 是芹菜Celery 是基于Python实现的模块, 用于执行异步定时周期任务的其结构的组成是由 1.用户任务 app 2.管道 broker 用于存储 ...
- day3:python测试题
1.Python的怎么单行注释和多行注释: 单行注释:# 多行注释: ''' ''' 或者 “”“ ”“” . 2.布尔值分别是什么 ? True /Fa ...
- 用Navicat for mysql连接mysql报错1251-解决办法
今天下了个 MySQL8.0,发现Navicat连接不上,总是报错1251: 原因是MySQL8.0版本的加密方式和MySQL5.0的不一样,连接会报错. 试了很多种方法,终于找到一种可以实现的: 更 ...