当前位置:首页 > Rust > 正文

深入理解半平面交算法(Rust语言从零实现计算几何核心算法)

在计算几何领域,半平面交算法是一个非常重要的基础算法。它广泛应用于多边形裁剪、可见性区域计算、线性规划等问题中。本教程将使用 Rust 语言,手把手带你从零实现一个清晰、高效且易于理解的半平面交算法,即使你是编程新手也能轻松上手。

什么是半平面?

在二维平面上,一条直线将平面分成两个部分,每一部分称为一个半平面(Half-plane)。通常我们用不等式表示,例如:\[ ax + by + c \leq 0 \]就定义了一个半平面。

当我们有多个这样的半平面时,它们的交集(即同时满足所有不等式的点的集合)可能是一个凸多边形、一条线段、一个点,或者为空。我们的目标就是求出这个交集区域。

深入理解半平面交算法(Rust语言从零实现计算几何核心算法) Rust半平面交算法 计算几何Rust实现 半平面交教程 Rust几何算法 第1张

算法思路概述

半平面交的经典解法是使用双端队列(deque)维护当前交集的边界。基本步骤如下:

  1. 将所有半平面按极角排序(即方向向量的角度);
  2. 依次加入半平面,并用双端队列维护有效边界;
  3. 每次加入新半平面后,检查队首或队尾是否被新半平面“切掉”;
  4. 最后检查队首与队尾是否互相无效,进行清理;
  5. 输出最终凸多边形的顶点。

Rust 实现细节

我们将使用 Rust 的标准库,配合一些简单的数学结构来实现。首先定义点和直线(半平面)的数据结构。

1. 定义基本结构

#[derive(Debug, Clone, Copy)]struct Point {    x: f64,    y: f64,}impl Point {    fn new(x: f64, y: f64) -> Self {        Point { x, y }    }    // 向量减法    fn sub(&self, other: &Point) -> Point {        Point::new(self.x - other.x, self.y - other.y)    }    // 叉积:用于判断转向    fn cross(&self, other: &Point) -> f64 {        self.x * other.y - self.y * other.x    }}// 半平面由直线 ax + by + c <= 0 表示#[derive(Debug, Clone)]struct HalfPlane {    a: f64,    b: f64,    c: f64,}impl HalfPlane {    fn new(a: f64, b: f64, c: f64) -> Self {        HalfPlane { a, b, c }    }    // 方向向量 (dx, dy) = (-b, a)    fn direction(&self) -> Point {        Point::new(-self.b, self.a)    }    // 判断点 p 是否在半平面内    fn contains(&self, p: &Point) -> bool {        self.a * p.x + self.b * p.y + self.c <= 1e-9    }}

2. 求两条直线的交点

fn intersect(h2: &HalfPlane, h2: &HalfPlane) -> Option<Point> {    let det = h2.a * h2.b - h2.a * h2.b;    if det.abs() < 1e-9 {        return None; // 平行    }    let x = (h2.b * h2.c - h2.b * h2.c) / det;    let y = (h2.a * h2.c - h2.a * h2.c) / det;    Some(Point::new(-x, -y))}

3. 按极角排序半平面

我们需要根据方向向量的极角对半平面排序,注意处理方向相反的情况(保留更“紧”的约束)。

use std::cmp::Ordering;fn angle_cmp(h2: &HalfPlane, h2: &HalfPlane) -> Ordering {    let d1 = h2.direction();    let d2 = h2.direction();    // 计算 atan2 角度    let ang1 = d1.y.atan2(d1.x);    let ang2 = d2.y.atan2(d2.x);    // 处理角度环绕问题    if (ang1 - ang2).abs() < 1e-9 {        // 同方向:保留常数项更小的(约束更强)        if h2.c < h2.c {            Ordering::Less        } else {            Ordering::Greater        }    } else if ang1 < ang2 {        Ordering::Less    } else {        Ordering::Greater    }}

4. 主算法:双端队列实现

use std::collections::VecDeque;fn half_plane_intersection(mut planes: Vec<HalfPlane>) -> Option<Vec<Point>> {    // 1. 排序    planes.sort_by(|a, b| angle_cmp(a, b));    // 2. 去重(同方向只保留最强约束)    let mut unique_planes = vec![];    for p in planes {        if unique_planes.is_empty() ||           angle_cmp(&unique_planes.last().unwrap(), &p) != Ordering::Equal {            unique_planes.push(p);        }    }    let n = unique_planes.len();    if n == 0 { return Some(vec![]); }    // 3. 双端队列    let mut dq: VecDeque<HalfPlane> = VecDeque::new();    let mut points: VecDeque<Point> = VecDeque::new();    for i in 0..n {        let cur = &unique_planes[i];        // 检查队尾        while dq.len() >= 2 {            let last = dq.pop_back().unwrap();            let second_last = dq.back().unwrap().clone();            if let Some(inter) = intersect(&second_last, &last) {                if cur.contains(&inter) {                    dq.push_back(last);                    break;                }                points.pop_back();            } else {                dq.push_back(last);                break;            }        }        dq.push_back(cur.clone());        // 记录新交点        if dq.len() >= 2 {            if let Some(p) = intersect(dq.get(dq.len()-2).unwrap(), dq.back().unwrap()) {                points.push_back(p);            }        }        // 检查队首        while dq.len() >= 2 {            let front = dq.pop_front().unwrap();            let second_front = dq.front().unwrap().clone();            if let Some(inter) = intersect(&front, &second_front) {                let last_plane = dq.back().unwrap();                if last_plane.contains(&inter) {                    dq.push_front(front);                    break;                }                points.pop_front();            } else {                dq.push_front(front);                break;            }        }    }    // 4. 清理首尾冲突    while dq.len() >= 2 {        let last = dq.pop_back().unwrap();        if let Some(inter) = intersect(dq.front().unwrap(), &last) {            if !dq.back().unwrap().contains(&inter) {                points.pop_back();            } else {                dq.push_back(last);                break;            }        } else {            dq.push_back(last);            break;        }    }    if points.is_empty() {        None    } else {        Some(points.into())    }}

总结与应用场景

通过以上步骤,我们成功用 Rust 实现了 半平面交算法。该算法的时间复杂度为 \(O(n \log n)\),主要开销在排序阶段。

这项技术在以下场景非常有用:

  • 游戏开发中的视野裁剪
  • 机器人路径规划中的障碍物规避区域计算
  • 计算机图形学中的多边形布尔运算
  • 线性规划的二维可视化

希望这篇教程能帮助你掌握 Rust半平面交算法的核心思想与实现技巧。如果你正在学习计算几何Rust实现,不妨动手敲一遍代码,加深理解!

关键词回顾:Rust半平面交算法计算几何Rust实现半平面交教程Rust几何算法