图形学小白也能懂:用初中几何和Python代码,直观验证“两直线垂直斜率积为-1”
图形学小白也能懂:用初中几何和Python代码,直观验证“两直线垂直斜率积为-1”
记得第一次在游戏里实现角色移动时,我盯着那个45度斜向跳跃的bug发呆了半小时——明明代码里写的是垂直方向速度叠加,为什么角色会像喝醉酒一样歪着走?直到翻出初中数学课本,才意识到问题出在斜率关系的理解偏差上。今天我们就用Python和初中几何知识,亲手验证这个图形学中的基础定理:两条直线垂直时,它们的斜率乘积等于-1。
1. 从屏幕坐标系到数学坐标系
在开始画线之前,我们需要统一战场。电脑屏幕的坐标系和数学课本上的笛卡尔坐标系有个关键区别:
- 屏幕坐标系:原点(0,0)在左上角,y轴向下为正方向
- 数学坐标系:原点(0,0)在中心,y轴向上为正方向
import matplotlib.pyplot as plt import numpy as np # 创建数学坐标系风格的绘图 fig, ax = plt.subplots() ax.spines['left'].set_position('zero') ax.spines['bottom'].set_position('zero') ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') ax.xaxis.set_ticks_position('bottom') ax.yaxis.set_ticks_position('left')提示:在matplotlib中,
ax.spines控制坐标轴的显示位置,通过设置到'zero'位置并隐藏其他边框,可以模拟数学坐标系。
2. 动态生成垂直直线对
让我们设计一个交互式验证方案:固定第一条直线,动态生成与之垂直的第二条直线。关键参数包括:
| 参数 | 描述 | 示例值 |
|---|---|---|
| k1 | 第一条直线斜率 | 0.5 |
| b1 | 第一条直线截距 | 2 |
| x_range | 绘图范围 | [-10, 10] |
def plot_perpendicular_lines(k1=0.5, b1=2): # 生成第一条直线 x = np.linspace(-10, 10, 400) y1 = k1 * x + b1 # 计算垂直直线的斜率和截距 k2 = -1 / k1 if k1 != 0 else float('inf') b2 = 0 # 为简化计算,让第二条直线经过原点 # 绘制图形 plt.figure(figsize=(8,6)) plt.plot(x, y1, label=f'y = {k1}x + {b1}') if not np.isinf(k2): y2 = k2 * x + b2 plt.plot(x, y2, label=f'y = {k2}x + {b2}') else: plt.axvline(x=0, label='x = 0 (垂直于水平线)') plt.legend() plt.grid(True) plt.axis('equal') plt.show() return k1 * k2 if k1 !=0 and not np.isinf(k2) else -1运行这个函数时,你会看到无论怎么调整k1的值(除了0和无穷大),两条直线的斜率乘积总是-1。试试以下参数组合:
- k1=1, b1=0 → k2=-1
- k1=2, b1=3 → k2=-0.5
- k1=-0.25, b1=1 → k2=4
3. 几何验证:勾股定理的代码实现
让我们用代码重现课本上的几何证明。选择三个关键点:
- 直线1与y轴交点A(0,b1)
- 直线2与y轴交点B(0,b2)
- 两直线交点C
def verify_with_pythagoras(k1=0.5, b1=2, b2=0): # 计算交点C的坐标 x_c = (b2 - b1) / (k1 - (-1/k1)) y_c = k1 * x_c + b1 # 三点坐标 A = np.array([0, b1]) B = np.array([0, b2]) C = np.array([x_c, y_c]) # 计算各边长度平方 AB_sq = np.sum((B - A)**2) AC_sq = np.sum((C - A)**2) BC_sq = np.sum((C - B)**2) # 验证勾股定理 return { 'AB²': AB_sq, 'AC² + BC²': AC_sq + BC_sq, 'Difference': abs(AB_sq - (AC_sq + BC_sq)), 'Slope Product': k1 * (-1/k1) }输出结果示例:
{ 'AB²': 4.0, 'AC² + BC²': 4.000000000000001, 'Difference': 8.881784197001252e-16, 'Slope Product': -1.0 }注意:微小的浮点数误差是计算机计算的正常现象,通常10^-15量级的差异可以视为零。
4. 特殊情况的处理艺术
在图形编程中,边界情况往往是bug的温床。让我们完善代码处理所有特殊情况:
水平线(k1=0):
- 垂直线应为x=常数,斜率无穷大
- 斜率乘积:0 × ∞ → 数学上视为-1
垂直线(k1=∞):
- 水平线应为y=常数,斜率为0
def is_perpendicular(k1, k2, tolerance=1e-6): if k1 == 0: return np.isinf(k2) or abs(1/k2) < tolerance elif np.isinf(k1): return k2 == 0 else: return abs(k1 * k2 + 1) < tolerance测试案例:
test_cases = [ (0, float('inf')), # 水平与垂直 (1, -1), # 标准斜线 (float('inf'), 0), # 垂直与水平 (0.5, -2), # 一般情况 (1.5, 2/3) # 错误案例 ] for k1, k2 in test_cases: print(f"{k1:5} × {str(k2):8} → {'有效' if is_perpendicular(k1,k2) else '无效'}")5. 在游戏开发中的实际应用
理解这个定理后,我们可以在Unity或Godot等引擎中解决实际问题。比如计算2D游戏中的反弹向量:
def calculate_reflection(normal, incident): """ 计算入射向量在法线方向的反射向量 :param normal: 单位法向量 (垂直于反射面) :param incident: 入射向量 :return: 反射向量 """ return incident - 2 * np.dot(incident, normal) * normal这个反射公式的核心就是利用了法线(垂直于表面)与入射角的关系。在斜坡滑块的物理引擎实现中:
# 假设斜坡斜率为k,则法线斜率为-1/k def slope_physics(k, object_velocity): normal_slope = -1/k normal_vector = np.array([1, normal_slope]) normal_vector /= np.linalg.norm(normal_vector) # 单位化 # 计算沿斜坡的分量 tangent_vector = np.array([1, k]) tangent_vector /= np.linalg.norm(tangent_vector) velocity_along = np.dot(object_velocity, tangent_vector) return velocity_along * tangent_vector在实现2D光照效果时,这个定理同样大显身手。计算点光源到墙面的法线方向,就能确定光线的反射路径,创造出逼真的镜面反射效果。
