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博客将彻底不会使用!!!(带来不便,敬请谅解) ...
随机推荐
- Laravel 迁移检查表是否存在
Schema::hasTable('TableName'); //检查表释放存在 Schema::hasColumn('tableName', 'columeName'); //检查表是否存在某个字段 ...
- windows2012 日志查看过程
Windows2012界面修改好造成有些人不知道在哪里查找windows 日志 我这边截图描述一下 1. 2.输入 命令 eventvwr.msc 3.弹出 windows 事件查看器 4.若需要 ...
- linux dmesg命令使用
linux dmesg命令使用 2012-11-27 09:37 2783人阅读 评论(0) 收藏 举报 分类: linux内核与编程(199) 版权声明:本文为博主原创文章,未经博主允许不得转载 ...
- Altera: set pin locations using tcl
1, compile the project; 2, store current tcl settings: Project –> Generate Tcl File from Project- ...
- 【JZOJ6294】动态数点
description analysis 这题出的失败在只卡正解不卡暴力 比较好想的方法是枚举约数,向两边二分,但是这个不满足二分性 首先用\(ST\)表维护区间的\(\gcd\),不用线段树,这样查 ...
- c++ strlen() 函数
{ char *buf = new char[1024]; ZeroMemory(buf,1024) for(int i = 0; i < 1023; i++) { buf[i] = '5'; ...
- 专访阿里云MVP王俊杰:开发者的超能力是用技术让世界更美好
[王俊杰:阿里云MVP,陕西创博网络科技有限公司总经理.大数据与物联网的爱好者与实践者. 8年以上互联网从业经验,曾从事军工相关仿真分析软件研发与集成.4年以上大数据系统开发经验.目前正与天水市秦州区 ...
- spring Cache + Redis 开发数据字典以及自定义标签
一.数据库表结构 1. 分类表:dict_type 2. 子项表:dict_entry 二.页面维护功能示意图: 1. 分类管理 点击子项管理进入子项管理页面 2.子项管理 三.数据字典添加到缓 ...
- Maven远程仓库地址修改(精)
国内最快的maven镜像 阿里云maven镜像 <repositories> <repository> <id>spring-snapshots</id&g ...
- badboy录制添加检查点
前提条件 安装badboy 下载地址:http://www.badboy.com.au/download/index 录制脚本 1.例:录制www.baidu.com 2.打开badboy工具输入录制 ...