首页 > 其他分享 >WPF 基础 2D 图形学知识 判断点是否在线段上

WPF 基础 2D 图形学知识 判断点是否在线段上

时间:2024-09-29 11:01:47浏览次数:5  
标签:point 线段 图形学 2D Math WPF line BPoint APoint

在知道一个使用两个点表示的线段,和另一个点,求另一个点是否在线段上

本文算法属于通用的算法,可以在 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;
            }

以上代码放在 github 和 gitee 欢迎小伙伴访问

以上方法的计算有些重复,其实加上了 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; }
    }

以上代码放在 github 和 gitee 欢迎小伙伴访问

博客园博客只做备份,博客发布就不再更新,如果想看最新博客,请访问 https://blog.lindexi.com/

如图片看不见,请在浏览器开启不安全http内容兼容

知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名[林德熙](https://www.cnblogs.com/lindexi)(包含链接:https://www.cnblogs.com/lindexi ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我[联系](mailto:[email protected])。

标签:point,线段,图形学,2D,Math,WPF,line,BPoint,APoint
From: https://www.cnblogs.com/sexintercourse/p/18439197

相关文章

  • Wpf使用NLog将日志输出到LogViewer
    Wpf使用NLog将日志输出到LogViewer 1LogViewerLogViewer是通过UDP传输的高性能实时log查看器。具有一下特性:通过UDP读取日志通过文件导入日志导出日志到一个文件中排序、过滤(日志树,日志等级)和查找突出显示搜索文本从UPD接收日志时忽略IP地址列表多接收器支持多种......
  • WPF ProgressBar show value
    //xaml<Windowx:Class="WpfApp424.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mi......
  • [USACO22DEC] Making Friends P 题解
    T2[USACO22DEC]MakingFriendsP考虑删除一个点,会有如下的点相连接:题目要求如果两两个点建立联系,只会建立一次。所以,神奇地,我们取出当前待删的点所连接的最小的点,将它和剩下的点连接。手摸一下会发现这样就巧妙地给每个改建的边都建了一次。所以用一个set启发式合并就做完......
  • WPF FlowDocument List ListItem Paragraph BlockUIContainer Table TableRowGr
    <Windowx:Class="WpfApp419.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft......
  • WPF FlowDocument Paragraph
    <Windowx:Class="WpfApp418.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft......
  • [USACO22DEC] Breakdown P 题解
    T1[USACO22DEC]BreakdownP比较显然的一点是,一次加一条边/一次删一条边,显然转化,这是显然的一条套路。这题的\(K\le8\),很有意思的数据范围,然后调用我们聪明的人类大脑得知需要用到折半搜索。所以我们只考虑\(K\le4\)的情况,令\(\mathit{st}\)表示折半搜索中考虑的起点。维......
  • Springboot旅游攻略平台2de9n(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表用户,旅游攻略,旅游景点,攻略分类,景点分类,问题分类,旅游问答开题报告内容一、项目背景随着旅游业的蓬勃发展,游客对于旅游信息的获取需求日益多样化、个性化......
  • Android CCodec Codec2 (十五)C2DmaBufAllocator
    Codec2框架采用了全新的Buffer分配机制C2Allocator,这一篇文章我们一起来瞧瞧C2DmaBufAllocator是如何工作的。1、C2AllocatorC2Allocator声明在C2Buffer.h中,它定义了如下接口:getName:返回Allocator独一无二的名称;getId:返回Allocator独一无二的id;getTraits:返回Allocator......
  • 【Bevy实战】2D场景下Camera实践
    Bevy,一个用Rust构建的令人耳目一新的简单数据驱动游戏引擎。如果你是一名Rust开发者,同时又对游戏开发比较感兴趣,那么Bevy一定是你会接触甚至是使用的游戏引擎。当然,本文关注的重点并不是来介绍Bevy,以及它的一些基本概念,关于这块的内容读者完全可以到Bevy的官网、Github主页进行学......
  • P8908 [USACO22DEC] Palindromes P 题解
    P8908[USACO22DEC]PalindromesP题解算是好题,虽然没什么人做(简单地,我们考虑如何将一个字符串改变为回文串。显然如果我们判定所有\(\texttt{G}\)组成的是回文串,那么整个串一定是回文的。于是我们只考虑改变\(\texttt{G}\)的位置。那么由这类题的套路不难知道最优的变换......