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-如何给定两个点画出一条波浪线的更多相关文章

  1. 2019/8/27 Test(luogu 五月天模拟赛)

    \(2019/8/27\)大考 \(\color{#ff0808}{\text{初二诀别赛(SAD)}}\) 题目名称 链接 寿司 \(BSOJ5111\) 秀秀的森林 \(BSOJ5125\) 分组 ...

  2. 给定两个字符串 s 和 t,它们只包含小写字母。 字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。 请找出在 t 中被添加的字母。

    给定两个字符串 s 和 t,它们只包含小写字母.字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母.请找出在 t 中被添加的字母. 示例: 输入: s = "abcd" ...

  3. 给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组

    题目描述: 给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组. 说明:初始化 nums1 和 nums2 的元素数量分别为 m ...

  4. Alpha冲刺(4/10)——2019.4.27

    所属课程 软件工程1916|W(福州大学) 作业要求 Alpha冲刺(4/10)--2019.4.27 团队名称 待就业六人组 1.团队信息 团队名称:待就业六人组 团队描述:同舟共济扬帆起,乘风破浪 ...

  5. 给定两个数组,这两个数组是排序好的,让你求这两个数组合到一起之后第K大的数。

    题目:给定两个数组,这两个数组是排序好的,让你求这两个数组合到一起之后第K大的数. 解题思路: 首先取得数组a的中位数a[aMid],然后在b中二分查找a[aMid],得到b[bMid],b[bSt] ...

  6. 9.11排序与查找(一)——给定两个排序后的数组A和B,当中A的末端有足够的缓冲空间容纳B。将B合并入A并排序

    /**  * 功能:给定两个排序后的数组A和B,当中A的末端有足够的缓冲空间容纳B.将B合并入A并排序. */ /** * 问题:假设将元素插入数组A的前端,就必须将原有的元素向后移动,以腾出空间. ...

  7. WPF入门(三)->两个几何图形合并(CombinedGeometry)

    原文:WPF入门(三)->两个几何图形合并(CombinedGeometry) 在WPF中,提供了一个CombinedGeometry对象可以使两个几何图形合并产生效果 CombinedGeom ...

  8. Beta冲刺(6/7)——2019.5.27

    所属课程 软件工程1916|W(福州大学) 作业要求 Beta冲刺(6/7)--2019.5.27 团队名称 待就业六人组 1.团队信息 团队名称:待就业六人组 团队描述:同舟共济扬帆起,乘风破浪万里 ...

  9. [New!!!]欢迎大佬光临本蒟蒻的博客(2019.11.27更新)

    更新于2019.12.22 本蒟蒻在博客园安家啦!!! 本蒟蒻的博客园主页 为更好管理博客,本蒟蒻从今天开始,正式转入博客园. 因为一些原因,我的CSDN博客将彻底不会使用!!!(带来不便,敬请谅解) ...

随机推荐

  1. opencv3.1.0 在控制台程序中报错:winnt.h(6464): error C2872: ACCESS_MASK: 不明确的

    在winnt.h里面有一个cv的命名空间,同样定义了一个ACCESS_MASK,跟opencv的cv::ACCESS_MASK发生了冲突!!! 该冲突在MFC中没有出现,在控制台程序中才会报错!对于o ...

  2. 前端常用的库和实用技术之JavaScript高级技巧

    javascript高级技巧 变量作用域和闭包 <!DOCTYPE html> <html lang="en"> <head> <meta ...

  3. 查看ubuntu系统的版本信息

    显示如下 Linux version 4.10.0-28-generic (buildd@lgw01-12) linux内核版本号 gcc version 5.4.0 20160609 gcc编译器版 ...

  4. Java集合框架(List,Set,Map)

    单列集合基本框架 List接口特点:1. 它是一个元素存取有序的集合.例如,存元素的顺序是11.22.33.那么集合中,元素的存储就是按照11.22.33的顺序完成的). 2. 它是一个带有索引的集合 ...

  5. Windows start

    启动一个单独的窗口以运行指定的程序或命令. START ["title"] [/D path] [/I] [/MIN] [/MAX] [/SEPARATE | /SHARED]   ...

  6. Windows route

    ROUTE [-f] [-p] [-4|-6] command [destination]                  [MASK netmask]  [gateway] [METRIC met ...

  7. oracle 删除掉重复数据只保留一条

    用SQL语句,删除掉重复项只保留一条 在几千条记录里,存在着些相同的记录,如何能用SQL语句,删除掉重复的呢 .查找表中多余的重复记录,重复记录是根据单个字段(peopleId)来判断 select ...

  8. Eclipse中servlet简易模版

    package ${enclosing_package}; import java.io.IOException; import javax.servlet.ServletException; imp ...

  9. ROS 自定义消息类型方法

    流程 1.在package中新建文件夹名为msg 2.在msg文件夹中创建消息(此处以my_msg.msg)为例,注意的是要以msg为后缀名 内容举例如下: int32 data1 float64 d ...

  10. 杂项-语言-Swift:Swift

    ylbtech-杂项-语言-Swift:Swift Swift,苹果于2014年WWDC(苹果开发者大会)发布的新开发语言,可与Objective-C*共同运行于Mac OS和iOS平台,用于搭建基于 ...