WPF 基础 2D 图形学知识 判断点是否在线段上
在知道一个使用两个点表示的线段,和另一个点,求另一个点是否在线段上
本文算法属于通用的算法,可以在 WPF 和 UWP 和 Xamarin 等上运行,基本上所有的 .NET 平台都能执行
如下图,如果点在线段上,那么修改线段颜色

假定有线段的定义如下
public record Line
{
public Point APoint { get; init; }
public Point BPoint { get; init; }
}
以上代码使用了 .NET 5 加 C# 9.0 的新语法
在传入一个点,求这个点是否在线段上,最简单理解的算法是根据两点之间直线距离最短,只需要求 P 点和线段的 AB 两点的距离是否等于 AB 的距离。如果相等,那么证明 P 点在线段 AB 上,代码如下
private static bool CheckIsPointOnLine(Point point, Line line, double epsilon = 0.1)
{
// 最简单理解的算法是根据两点之间直线距离最短,只需要求 P 点和线段的 AB 两点的距离是否等于 AB 的距离。如果相等,那么证明 P 点在线段 AB 上
var ap = point - line.APoint;
var bp = point - line.BPoint;
var ab = line.BPoint - line.APoint;
// 只不过求 Length 内部需要用到一次 Math.Sqrt 性能会比较差
if (Math.Abs(ap.Length + bp.Length - ab.Length) < epsilon)
{
return true;
}
else
{
return false;
}
}
不使用 Vector 类,可以替换为如下计算方法。上面代码的 ap 等变量是使用 WPF 的两个点的相减能拿到 Vectore 类,而在 Vectore 类里面有 Length 属性而优化代码的。其实核心计算和下面代码相同。下面代码是 Tone Škoda 提供的,详细请看 https://stackoverflow.com/a/56850069/6116637
public static double CalcDistanceBetween2Points(double x1, double y1, double x2, double y2)
{
return Math.Sqrt(Math.Pow (x1 - x2, 2) + Math.Pow (y1 - y2, 2));
}
public static bool PointLinesOnLine (double x, double y, double x1, double y1, double x2, double y2, double allowedDistanceDifference)
{
double dist1 = CalcDistanceBetween2Points(x, y, x1, y1);
double dist2 = CalcDistanceBetween2Points(x, y, x2, y2);
double dist3 = CalcDistanceBetween2Points(x1, y1, x2, y2);
return Math.Abs(dist3 - (dist1 + dist2)) <= allowedDistanceDifference;
}
以下是另一个方法,以下方法性能比上面一个好
根据点和任意线段端点连接的线段和当前线段斜率相同,同时点在两个端点中间,就可以认为点在线段内
(x - x1) / (x2 - x1) = (y - y1) / (y2 - y1)
因为乘法性能更高,因此计算方法可以如下
(x - x1) * (y2 - y1) = (y - y1) * (x2 - x1)
(x - x1) * (y2 - y1) - (y - y1) * (x2 - x1) = 0
但是乘法的误差很大,因此还是继续使用除法
另外,需要判断点在两个端点中间
x1 < x < x2, assuming x1 < x2
y1 < y < y2, assuming y1 < y2
以下是代码
if (EqualPoint(point, line.APoint, epsilon) || EqualPoint(point, line.BPoint, epsilon))
{
return true;
}
// 乘法性能更高,误差大。请试试在返回 true 的时候,看看 crossProduct 的值,可以发现这个值依然很大
var crossProduct = (point.X - line.APoint.X) * (line.BPoint.Y - line.APoint.Y) -
(point.Y - line.APoint.Y) * (line.BPoint.X - line.APoint.X);
if (Math.Abs((point.X - line.APoint.X) / (line.BPoint.X - line.APoint.X) - (point.Y - line.APoint.Y) / (line.BPoint.Y - line.APoint.Y)) < epsilon)
{
var minX = Math.Min(line.APoint.X, line.BPoint.X);
var maxX = Math.Max(line.APoint.X, line.BPoint.X);
var minY = Math.Min(line.APoint.Y, line.BPoint.Y);
var maxY = Math.Max(line.APoint.Y, line.BPoint.Y);
if (minX < point.X && point.X < maxX && minY < point.Y && point.Y < maxY)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
上面代码的 crossProduct 是不用使用的,只是为了告诉大家,尽管乘法性能比较好,但是误差比较大
当然以上算法有漏洞,在于如果 A 和 B 两个点的 Y 坐标相同或 X 坐标相同的时候,那么以上算法不适合。可以先判断 crossProduct 的值,如果是等于零,那么证明有 A 和 B 两个点的 Y 坐标相同或 X 坐标相同
var crossProduct = (point.X - line.APoint.X) * (line.BPoint.Y - line.APoint.Y) -
(point.Y - line.APoint.Y) * (line.BPoint.X - line.APoint.X);
// 先判断 crossProduct 是否等于 0 可以解决 A 和 B 两个点的 Y 坐标相同或 X 坐标相同的时候,使用除法的坑
if (crossProduct == 0 || Math.Abs((point.X - line.APoint.X) / (line.BPoint.X - line.APoint.X) - (point.Y - line.APoint.Y) / (line.BPoint.Y - line.APoint.Y)) < epsilon)
{
var minX = Math.Min(line.APoint.X, line.BPoint.X);
var maxX = Math.Max(line.APoint.X, line.BPoint.X);
var minY = Math.Min(line.APoint.Y, line.BPoint.Y);
var maxY = Math.Max(line.APoint.Y, line.BPoint.Y);
if (minX <= point.X && point.X <= maxX && minY <= point.Y && point.Y <= maxY)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
以上方法的计算有些重复,其实加上了 crossProduct 只是为了水平和垂直的线段,其实可以做特殊处理,如下面代码
public static class Math2DExtensions
{
public static bool CheckIsPointOnLineSegment(Point point, Line line, double epsilon = 0.1)
{
// 以下是另一个方法,以下方法性能比上面一个好
// 根据点和任意线段端点连接的线段和当前线段斜率相同,同时点在两个端点中间
// (x - x1) / (x2 - x1) = (y - y1) / (y2 - y1)
// x1 < x < x2, assuming x1 < x2
// y1 < y < y2, assuming y1 < y2
// 但是需要额外处理 X1 == X2 和 Y1 == Y2 的计算
var minX = Math.Min(line.APoint.X, line.BPoint.X);
var maxX = Math.Max(line.APoint.X, line.BPoint.X);
var minY = Math.Min(line.APoint.Y, line.BPoint.Y);
var maxY = Math.Max(line.APoint.Y, line.BPoint.Y);
if (!(minX <= point.X) || !(point.X <= maxX) || !(minY <= point.Y) || !(point.Y <= maxY))
{
return false;
}
// 以下处理水平和垂直线段
if (Math.Abs(line.APoint.X - line.BPoint.X) < epsilon)
{
// 如果 X 坐标是相同,那么只需要判断点的 X 坐标是否相同
// 因为在上面代码已经判断了 点的 Y 坐标是在线段两个点之内
return Math.Abs(line.APoint.X - point.X) < epsilon || Math.Abs(line.BPoint.X - point.X) < epsilon;
}
if (Math.Abs(line.APoint.Y - line.BPoint.Y) < epsilon)
{
return Math.Abs(line.APoint.Y - point.Y) < epsilon || Math.Abs(line.BPoint.Y - point.Y) < epsilon;
}
if (Math.Abs((point.X - line.APoint.X) / (line.BPoint.X - line.APoint.X) - (point.Y - line.APoint.Y) / (line.BPoint.Y - line.APoint.Y)) < epsilon)
{
return true;
}
else
{
return false;
}
}
}
public record Line
{
public Point APoint { get; init; }
public Point BPoint { get; init; }
}
WPF 基础 2D 图形学知识 判断点是否在线段上的更多相关文章
- HDU4305:Lightning(生成树计数+判断点是否在线段上)
Lightning Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total S ...
- 高德地图API开发二三事(一)如何判断点是否在折线上及引申思考
最近使用高德地图 JavaScript API 开发地图应用,提炼了不少心得,故写点博文,做个系列总结一下,希望能帮助到LBS开发同胞们. 项目客户端使用高德地图 JavaScript API,主要业 ...
- WPF基础到企业应用系列6——布局全接触
本文转自:http://knightswarrior.blog.51cto.com/1792698/365351 一. 摘要 首先很高兴这个系列能得到大家的关注和支持,这段时间一直在研究Windows ...
- WPF 基础到企业应用系列索引
转自:http://www.cnblogs.com/zenghongliang/archive/2010/07/09/1774141.html WPF 基础到企业应用系列索引 WPF 基础到企业应用系 ...
- WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)
一. 摘要 首先圣殿骑士非常高兴这个系列能得到大家的关注和支持.这个系列从七月份開始到如今才第七篇,上一篇公布是在8月2日,掐指一算有二十多天没有继续更新了,最主要原因一来是想把它写好,二来是由于近期 ...
- C# WPF基础巩固
时间如流水,只能流去不流回. 学历代表你的过去,能力代表你的现在,学习能力代表你的将来. 学无止境,精益求精. 一.写作目的 做C# WPF开发,无论是工作中即将使用,还是只应付跳槽面试,开发基础是非 ...
- WPF笔记(1.1 WPF基础)——Hello,WPF!
原文:WPF笔记(1.1 WPF基础)--Hello,WPF! Example 1-1. Minimal C# WPF application// MyApp.csusing System;using ...
- day 28 网络基础相关的知识
1.网络基础相关的知识 架构 C/S 架构: client 客户端 server服务器端 优势: 能充分发挥PC机的性能 B/S 架构: browser 浏览器 server服务器 隶 ...
- python基础--python基本知识、七大数据类型等
在此申明一下,博客参照了https://www.cnblogs.com/jin-xin/,自己做了部分的改动 (1)python应用领域 目前Python主要应用领域: 云计算: 云计算最火的语言, ...
- Java基础 之一 基本知识
Java基础 之一 基本知识 1.数据类型 Java有8种基本数据类型 int.short .long.byte.float.double.char.boolean 先说明以下单位之间的关系 1位 = ...
随机推荐
- Windows中控制台(cmd)模式下运行程序卡死/挂起现象解决方案(快速编辑模式)
最近在运行编译好的exe文件时,发现了一个现象,就是通过cmd运行exe文件或者双击执行运行exe文件,偶尔会出现程序没有执行的情况.最开始发现这个现象时,还以为是程序出现了什么Bug.后面经过网上查 ...
- vue,vuex,element实现无限tab页效果
直接撸代码 ?满足你 码云地址 效果图 tab页由来 甲方爸爸的更改需求,无力反抗 分析代码 懒的写,直接撸就行 参考文章 点我
- 感悟:FPGA的串行及并行设计思路
前言 FPGA设计过程中, 会遇到大量的串行转并行或者并行转串行的问题; 这些问题主要体现在FPGA对于速度和面积的均衡上; 一般而言, FPGA使用并行的设计可以提高处理的速度, 消耗更多的资源; ...
- NFNet:NF-ResNet的延伸,不用BN的4096超大batch size训练 | 21年论文
论文认为Batch Normalization并不是网络的必要构造,反而会带来不少问题,于是开始研究Normalizer-Free网络,希望既有相当的性能也能支持大规模训练.论文提出ACG梯度裁剪方法 ...
- 配置腾讯云轻量级linux服务器用到的资源和步骤
pasv_address=82.157.112.34 #请修改为您的 Linux 云服务器公网 IPsftp://82.157.112.34:21 ①下载系统可视化https://cloud.tenc ...
- GraphQl in ASP.NET Core
GraphQl in ASP.NET Core https://graphql.cn/ https://graphql-dotnet.github.io/docs/getting-started/ar ...
- #FFT#P4173 残缺的字符串
题目 有一个长度为 \(m\) 的字符串 \(A\) 和 一个长度为 \(n\) 的字符串 \(B\), 它们有若干位置为通配符,现在问 \(B\) 的每一个后缀是否存在前缀为 \(A\). 分析 考 ...
- #贪心,树#C 平衡的树
分析 处理出子树内剩余删减以及最大的剩余\(a\)和, 如果删了还是超过\(b\)输出无解 代码 #include <cstdio> #include <cctype> #de ...
- 开源机密计算平台:蓬莱-OpenHarmony
演讲嘉宾 | 杜 东 回顾整理 | 廖 涛 排版校对 | 李萍萍 嘉宾简介 杜东,上海交通大学助理研究员.中国计算机学会CCF会员,ACM会员.研究兴趣为操作系统与体系结构.服务器无感知(Se ...
- 【FAQ】推送服务常见问题及解答
目录 1.报错6003解决方案. 2.推送成功收不到消息. 3.Gettoken返回0,无法进入onToken函数,无法收到广播. 4.如何在华为开放平台查看消息属性,推送的时候带了importanc ...