惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Jina AI
Jina AI
T
Threat Research - Cisco Blogs
量子位
Last Week in AI
Last Week in AI
aimingoo的专栏
aimingoo的专栏
Martin Fowler
Martin Fowler
F
Fortinet All Blogs
爱范儿
爱范儿
D
Docker
人人都是产品经理
人人都是产品经理
S
SegmentFault 最新的问题
Microsoft Azure Blog
Microsoft Azure Blog
B
Blog RSS Feed
A
About on SuperTechFans
P
Proofpoint News Feed
博客园 - 司徒正美
Recent Announcements
Recent Announcements
I
InfoQ
Hugging Face - Blog
Hugging Face - Blog
Microsoft Security Blog
Microsoft Security Blog
有赞技术团队
有赞技术团队
Webroot Blog
Webroot Blog
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
SecWiki News
SecWiki News
Attack and Defense Labs
Attack and Defense Labs
Hacker News: Ask HN
Hacker News: Ask HN
AI
AI
博客园_首页
腾讯CDC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Recorded Future
Recorded Future
T
Tailwind CSS Blog
MyScale Blog
MyScale Blog
T
Tenable Blog
宝玉的分享
宝玉的分享
Google Online Security Blog
Google Online Security Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
S
Secure Thoughts
C
Check Point Blog
S
Security Affairs
L
LINUX DO - 最新话题
大猫的无限游戏
大猫的无限游戏
Scott Helme
Scott Helme
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Hacker News - Newest:
Hacker News - Newest: "LLM"
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
月光博客
月光博客
Y
Y Combinator Blog

hsfzxjy 的博客

解决 VSCode + CMake + MSVC 编译器信息乱码的问题 使用 3090 部署 1.58bit 动态量化版 DeepSeek R1 671b 如何在 VS Code DevContainer 中配置 HTTP 代理 如何在跳板机背后的服务器上使用 VS Code Remote - Containers Cohesive Digests for Ints and Floats Rust 中的隐匿概念 —— Place(位置) 美术馆 一尺之槌,日取其半,1075日而竭 老生常谈:使用 Cloudflare 自选 IP 加速站点访问 辩义 State、Nation 与 Country 将 Base64 编码的数据快速转换为 Uint8Array 折腾 NPU·第1章 —— 搭建 Level Zero 开发环境 折腾 NPU·第0章 —— Intel NPU 概述与 Level-Zero 新增域名 monad.run CSS 中为特定字符设置不同字体 Arbitary Lifetime Transmutation via Rust Unsoundness Dijkstra 算法的延伸 Manacher 回文计数算法 硬卧 Go Fact: Zero-sized Field at the Rear of a Struct Has Non-zero Size Display *big.Rat Losslessly and Smartly in Golang 代码的仪式 Building Electron From Scratch 中式亲属称谓研究之一:构建半群 Some Notes on Kotlin Coroutines Git sparse-checkout and partial clones for Mega-Repos 辩义“封建” Diving from the CUDA Error 804 into a bug of libnvidia-container Modern Cryptography, GPG and Integration with Git(hub) Move the Root Partition of Ubuntu A New Programmer Kicks a Roadblock Git-based Dependencies in Dart and Go Reversy Naming 人类一败涂地 Invalid Golang Pointers Can Bite You Even If You Don't Dereference Side Project(副业) A Flaw of Promoting Complex Trait Bounds in Rust Initialize Process Pool Worker with Individual Value Rust - Python FFI From Scratch [Extending Hexo For My Site] Part 1 [Extending Hexo For My Site] Part 0 Debug a 'torch.tensor(1).cuda()' hanging 不自由的互联网 Retrieve Contents over HTTP without curl or wget [Unravelling mocona] Part 1 - Verbosity or Anti-Pattern [Unravelling mocona] Part 0 - Preface Understanding pickle in Python Rough Notes on Deploying Vaultwarden & NextCloud Bookmarks 语言狂热者与实用主义者 Demystify the randomness in CUDA kernels Performant Bulk Mutations in IndexedDB Auto Rebuild .pyx Files with pyximport Cython and Threads Obtain a Random Available TCP Port with Bash Information Theory: KL Divergence Information Theory: Entropy and Mutual Information 铁板烧 西郊线 Proof of the Gumbel Max Trick Option::as_ref Rc, RefCell and Interior Mutability Visualizing Correlation 三月十日杂感 三月一日杂感 二月十一日杂感 一月二十六日杂感 SS Configuration 一月七日杂感 四月·病 Haskell 笔记:State Monad Haskell 笔记:Monad 引论 Haskell 笔记:Applicative Haskell 笔记:Category Theory and Functor Haskell 笔记:data, type, newtype Haskell 笔记:folds 使用 Aria2 在 Ubuntu 中下载百度云资源 从伪并行的 Python 多线程说起 一个 Reentrant Error 引发的对 Python 信号机制的探索和思考 Linux 文件权限 HSFZMUN 4.0 部署小记 午后雨·科大 最后的雨夜·广州 揭秘·变态的平方根倒数算法 神坑·Python 装饰类无限递归 Python“黑魔法”之 Encoding & Decoding Ubuntu 重新映射键盘布局 为什么我要翻墙 Python“黑魔法”之 Generator Coroutines 除夕杂感 17 行代码实现的简易 Javascript 字符串模板 Python“黑魔法”之 Meta Classes 诗集 生活,需要被“发现” 家书·十八岁成人礼 炫技?还是需求? 【译】响应式图片的现状 【译】“为什么有这么多的编程语言?” Wisecity 商赛总结——也谈前端自动化测试 记一次 DoS 诈骗网站的经历 那一年,我们望向星空
数学美 之 判断线段相交的最简方法
2016-02-19 · via hsfzxjy 的博客

解析几何的巅峰 是 向量 那无关过程的狂妄与简洁 映射着大自然无与伦比的美

引子

如何判断两条直线是否相交?

这很容易。平面直线,无非就是两种关系:相交 或 平行。因此,只需判断它们是否平行即可。而直线平行,等价于它们的斜率相等,只需分别计算出它们的斜率,即可做出判断。

但倘若我把“直线”换成“线段”呢——如何判断两条线段是否相交?

这就有些难度了。和 直线 不同,线段 是有固定长度的,即使它们所属的两条直线相交,这两条线段也不一定相交。

也许你会说:分情况讨论不就行了嘛:

  • 先计算两条线段的斜率,判断是否平行。若平行,则一定不相交。
  • 若不平行,求出两条线段的直线方程,联立之,解出交点坐标。
  • 运用定比分点公式,判断交点是否在两条线段上。

的确,从理论上这是一个可行的办法,这也是人们手动计算时普遍采用的方法。

然而,这个方法并不怎么适用于计算机。原因如下:

  • 计算中出现了除法(斜率计算、定比分点),因此每次计算前都要判断除数是否为 0(或接近 0)。这很麻烦,严重干扰逻辑的表达。
  • 浮点精度丢失带来的误差。人类计算时可以采用分数,但计算机不行。计算机在储存浮点数时会有精度丢失的现象。一旦算法的计算量大起来,误差会被急剧放大,影响结果准确性。
  • 效率低下。浮点乘除会十分耗时,不适用于对实时性要求较高的生产环境(如 游戏)。

那么,有更好的方法?

当然有。

类型预定义

本文的算法将用 python 描述,主要用到两个数据类型:


class Point(object):

def __init__(self, x, y):
self.x, self.y = x, y


class Vector(object):

def __init__(self, start_point, end_point):
self.start, self.end = start_point, end_point
self.x = end_point.x - start_point.x
self.y = end_point.y - start_point.y

先在此处说明。

问题分析

对于“判断两条直线是否相交”这个问题,我们之所以能迅速而准确地进行判断,是因为“相交”与“不相交”这两个状态有着明显的不同点,即 斜率是否相等

那么现在,为了判断两条线段是否相交,我们也要找出“相交”与“不相交”这两个状态的不同点。

假设现在有两条线段 AB 和 CD,我们画出它们之间的三种关系:

  1. 不相交
  2. 交点位于某条线段上
  3. 相交

其中,情况 1 为不相交,情况 2、3 为相交。

作出向量 AC、AD、BC、BD。

首先介绍一个概念: 向量有序对的旋转方向。这个概念指:对于共起点有序向量二元组(a, b),其旋转方向为 使 a 能够旋转一个小于 180 度的角并与 b 重合的方向,简记为 direct(a, b)。若 ab 反向共线,则旋转方向取任意值。

举个例子:图一中,direct(AC, AD) 为顺时针方向。

接下来我们要分析四个值:direct(AC, AD)direct(BC, BD)direct(CA, CB)direct(DA, DB)

  1. 对于图一,direct(AC, AD)direct(BC, BD) 都为顺时针,direct(CA, CB) 为逆时针,direct(DA, DB) 为顺时针。
  2. 对于图二,direct(AC, AD) 为顺时针,direct(BC, BD) 为任意方向,direct(CA, CB) 为逆时针,direct(DA, DB) 为顺时针。
  3. 对于图三,direct(AC, AD)direct(DA, DB) 为顺时针,direct(BC, BD)direct(CA, CB) 为逆时针。

不难发现,两条线段相交的充要条件是:direct(AC, AD) != direct(BC, BD)direct(CA, CB) != direct(DA, DB)。这便是“相交”与“不相交”这两个状态的不同点。

然而你可能会觉得:旋转方向这么一个虚无飘渺的东西,怎么用程序去描述啊?

再来看一幅图:

再来定义有向角:

有向角 <a, b> 为 向量a 逆时针 旋转到与 向量b 重合所经过的角度。

不难看出,对于向量ab

  • direct(a, b) 为逆时针,则 0 <= <a, b> <= 180,从而 sin<a, b> >= 0
  • direct(a, b) 为顺时针,则 180 <= <a, b> <= 360,从而 sin<a, b> <= 0

这样一来,我们可以将旋转方向的问题转化为 求有向角正弦值 的问题。而这个问题,是很容易的。

如上图,记

$$ OA = (x_1, y_1), OB = (x_2, y_2) $$ $$ |OA| = r_1, |OB| = r_2 $$

$$ sin(\lt OA, OB\gt) $$ $$ = sin \theta $$ $$ = sin (\beta - \alpha) $$ $$ = sin \beta cos \alpha - sin \alpha cos \beta $$ $$ = \frac{(sin \beta cos \alpha - sin \alpha cos \beta) r_1 r_2}{r_1 r_2} $$ $$ = {(x_1 y_2 - x_2 y_1) \over (r_1 r_2)} $$

而这里,我们要的只是 sin(<OA, OB>) 的符号,而 r1r2 又都是恒正的,因此只需判断 x1 * y2 - x2 * y1 的符号即可。

这个方法的数学背景是 叉乘,可以前往 Wikipedia 了解更多。

思路小结

  • 由点 A,B,C,D 计算出向量 AC,AD,BC,BD
  • 计算 sin(<AC, AD>) * sin(<BC, BD>)sin(<CA, CB>) * sin(<DA, DB>),若皆为非正数,则相交;否则,不相交。

实现

终于到代码部分了,想必大家都已不耐烦了吧。

在向量的辅助下,代码显得异常简单。

ZERO = 1e-9

def negative(vector):
"""取反"""
return Vector(vector.end_point, vector.start_point)

def vector_product(vectorA, vectorB):
'''计算 x_1 * y_2 - x_2 * y_1'''
return vectorA.x * vectorB.y - vectorB.x * vectorA.y

def is_intersected(A, B, C, D):
'''A, B, C, D 为 Point 类型'''
AC = Vector(A, C)
AD = Vector(A, D)
BC = Vector(B, C)
BD = Vector(B, D)
CA = negative(AC)
CB = negative(BC)
DA = negative(AD)
DB = negative(BD)

return (vector_product(AC, AD) * vector_product(BC, BD) <= ZERO) \
and (vector_product(CA, CB) * vector_product(DA, DB) <= ZERO)

一气呵成,没有恼人的除法,没有情况讨论,只是纯粹的简单运算。


作者:hsfzxjy
链接:
许可:CC BY-NC-ND 4.0.
著作权归作者所有。本文不允许被用作商业用途,非商业转载请注明出处。