第 94 课 - 计算几何基础
1. 学习目标
通过本节课的学习,你将能够:
- 理解点、向量、线段在计算机中的标准表示方法。
- 掌握向量的点积和叉积这两种核心运算的定义与实现。
- 理解叉积的几何意义,并利用其符号判断向量的相对方向(顺时针、逆时针、共线)。
- 应用叉积解决两个基础几何问题:判断点在线段的哪一侧,以及判断两条线段是否相交。
2. 核心概念
在计算几何中,我们通常将几何对象转化为数字进行精确计算。一切始于点和向量。
2.1 点与向量的表示
- 点 (Point):我们用一个坐标
(x, y)来表示平面上的一个点。 - 向量 (Vector):向量表示一个具有大小和方向的量。在几何上,它常用来表示从点A到点B的位移。向量可以由两个点相减得到:
向量 AB = (B.x - A.x, B.y - A.y)。注意,向量只有方向和长度,没有固定的位置。
2.2 向量的基本运算
- 加减:对应分量相加减。例如
(a, b) + (c, d) = (a+c, b+d)。 - 标量乘法:
k * (x, y) = (k*x, k*y)。 - 点积 (Dot Product):
a · b = a.x * b.x + a.y * b.y,结果是一个标量。点积的几何意义是投影,可用于判断向量是否垂直(点积为0)。 - 叉积 (Cross Product):这是计算几何中最核心的运算。对于二维向量
a = (a.x, a.y)和b = (b.x, b.y),它们的叉积是一个标量(在三维中是向量):a × b = a.x * b.y - a.y * b.x
2.3 叉积的几何意义与应用
叉积的绝对值等于以这两个向量为邻边的平行四边形的面积。 更重要的是叉积的符号:
- a × b > 0:向量b位于向量a的逆时针方向(左侧)。
- a × b < 0:向量b位于向量a的顺时针方向(右侧)。
- a × b == 0:向量a与向量b共线(平行或反向)。
这个性质是我们判断方向和位置关系的基石。例如,给定三个点A、B、C,我们可以计算 向量AB 和 向量AC 的叉积,来判断C点相对于线段AB的位置。
3. 代码示例
import math
class Point:
"""表示一个二维平面上的点"""
def __init__(self, x=0, y=0):
self.x = x
self.y = y
# 定义点的减法,用于生成向量
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)
def __repr__(self):
return f"({self.x}, {self.y})"
class Vector:
"""表示一个二维向量"""
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def from_points(cls, p1, p2):
"""从两个点创建向量"""
return cls(p2.x - p1.x, p2.y - p1.y)
# 向量加法
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
# 向量减法
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
# 标量乘法
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
# 计算向量的模(长度)
def magnitude(self):
return math.sqrt(self.x**2 + self.y**2)
# 计算点积
def dot(self, other):
return self.x * other.x + self.y * other.y
# 计算叉积
def cross(self, other):
return self.x * other.y - self.y * other.x
def __repr__(self):
return f"<{self.x}, {self.y}>"
def cross_product_sign(p, q, r):
"""计算向量 pq 和 pr 的叉积符号,用于判断方向。
返回值: 1(逆时针), -1(顺时针), 0(共线)"""
vec_pq = Vector.from_points(p, q)
vec_pr = Vector.from_points(p, r)
cp = vec_pq.cross(vec_pr)
if cp > 0:
return 1 # 逆时针
elif cp < 0:
return -1 # 顺时针
else:
return 0 # 共线
def segments_intersect(p1, p2, p3, p4):
"""判断线段(p1,p2)和线段(p3,p4)是否相交。
使用叉积法:需要满足两个条件。"""
# 条件1: 线段p1p2的端点p3, p4应在其两侧。
d1 = cross_product_sign(p1, p2, p3)
d2 = cross_product_sign(p1, p2, p4)
# 条件2: 线段p3p4的端点p1, p2应在其两侧。
d3 = cross_product_sign(p3, p4, p1)
d4 = cross_product_sign(p3, p4, p2)
# 一般情况:每个线段的两个端点分别在另一线段的两侧。
if (d1 * d2 < 0) and (d3 * d4 < 0):
return True
# 特殊情况:处理三点共线且重叠的情况(简化版,实际竞赛需更严谨)
# 这里仅演示思路,不包含所有边界情况。
# 可检查点是否在对方线段上(使用点积判断投影是否在线段内)
# 完整的算法需要考虑更多边界情况。
return False
# --- 测试代码 ---
if __name__ == "__main__":
# 创建点
A = Point(0, 0)
B = Point(4, 0)
C = Point(2, 3)
D = Point(2, -1)
# 测试叉积符号 (方向判断)
print(f"点 C(2,3) 相对于线段 AB(0,0)->(4,0) 的位置: ", end="")
direction = cross_product_sign(A, B, C)
if direction == 1:
print("左侧 (逆时针)")
elif direction == -1:
print("右侧 (顺时针)")
else:
print("在线上 (共线)")
print(f"点 D(2,-1) 相对于线段 AB(0,0)->(4,0) 的位置: ", end="")
direction = cross_product_sign(A, B, D)
if direction == 1:
print("左侧 (逆时针)")
elif direction == -1:
print("右侧 (顺时针)")
else:
print("在线上 (共线)")
# 测试线段相交
P1, P2 = Point(0, 0), Point(2, 2)
P3, P4 = Point(0, 2), Point(2, 0)
print(f"\n线段 P1P2(0,0)-(2,2) 和 线段 P3P4(0,2)-(2,0) 是否相交? {segments_intersect(P1, P2, P3, P4)}")
P1, P2 = Point(0, 0), Point(1, 1)
P3, P4 = Point(1, 0), Point(2, 1)
print(f"线段 P1P2(0,0)-(1,1) 和 线段 P3P4(1,0)-(2,1) 是否相交? {segments_intersect(P1, P2, P3, P4)}")
4. 实践练习
练习 1:基础叉积计算
给定向量 u = (1, 2) 和 v = (3, 1),手动计算或编写代码求出它们的点积和叉积,并根据叉积的符号判断 v 相对于 u 的方向。
预期输出:
点积: 5
叉积: -5
v 相对于 u 的方向: 顺时针 (右侧)
练习 2:判断三点构成的转向
编写一个函数 turn_direction(A, B, C),输入三个点,返回从A->B->C是左转、右转还是直行。
要求:使用叉积实现。
示例输入与输出:
print(turn_direction(Point(0,0), Point(1,1), Point(2,0))) # 应输出 “右转”
print(turn_direction(Point(0,0), Point(1,1), Point(0,2))) # 应输出 “左转”
print(turn_direction(Point(0,0), Point(2,2), Point(1,1))) # 应输出 “共线”
练习 3:点是否在三角形内(选做)
给定一个三角形的三个顶点 A, B, C 和一个测试点 P,判断点 P 是否在三角形 ABC 的内部(包括边界)。
思路:可以利用叉积判断点P是否同时在三条边AB、BC、CA的同一侧(例如,都在左侧或都在右侧)。
示例:A(0,0), B(4,0), C(0,4), P(1,1) 应在三角形内;P(3,3) 应在三角形外。
5. 常见错误
- 混淆点积与叉积:点积用于判断垂直或计算投影,结果是标量;叉积用于判断方向或计算面积,结果是带符号的标量(二维)。
- 误解叉积为零的含义:叉积为零只能说明两个向量共线(方向相同或相反),但不能直接推出点共线。要判断三点共线,需计算两个向量的叉积是否为零。
- 忽略叉积的符号:只关注叉积的大小(绝对值)而忽略其符号,会丢失最重要的方向信息。
- 线段相交判断不完整:仅检查
d1 * d2 < 0和d3 * d4 < 0是一般情况。当有共线点时,这个判断会失效。完整的算法还需要处理端点相交、一条线段端点落在另一条线段上等退化情况,通常需要检查点是否在线段上(点积法)。 - 坐标系问题:在标准的数学坐标系(y轴向上)中,叉积符号的意义如前所述。但在计算机图形学常用的屏幕坐标系(y轴向下)中,左右关系可能会反过来,需要根据具体环境调整。
6. 小结
本节课我们学习了计算几何的入门知识:
- 基础表示:点用坐标
(x, y)表示,向量用分量差表示。 - 核心运算:叉积 (
a.x*b.y - a.y*b.x) 是计算几何的灵魂,其符号揭示了两个向量的相对方向(逆时针/顺时针/共线)。 - 方向判断:利用三个点构成的两个向量的叉积符号,可以判断转向。
- 线段相交:利用叉积判断两个线段的端点是否分别在另一条线段的两侧,是判断线段相交的基本方法(需注意退化情况)。 这些概念是解决更复杂几何问题(如下一节课的凸包)的基石。熟练掌握叉积的应用是成为计算几何高手的第一步。
练习编辑器
rust
Loading...