Skip to main content

atcoder/lib/
geometry.rs

1//! 整数座標の2次元幾何を扱います。
2//!
3//! x 軸は右向き、y 軸は上向きの座標系を想定します。画面やグリッドのように
4//! y が下向きに増える座標系では、時計回り・反時計回りの見え方が逆になります。
5//! 座標だけでなく、差・積・和を含む途中計算も `isize` に収まる必要があります。
6
7use std::cmp::Ordering;
8use std::ops::{Add, Mul, Sub};
9
10/// 整数座標上の点またはベクトルです。
11///
12/// 演算結果が [`isize`] の範囲を超えないことは呼び出し側で保証してください。
13///
14/// # Examples
15///
16/// ```
17/// use atcoder::geometry::Point;
18///
19/// let a = Point::new(1, 2);
20/// let b = Point::new(4, 6);
21/// assert_eq!(a.distance_squared(b), 25);
22/// ```
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub struct Point {
25    /// x 座標です。
26    pub x: isize,
27    /// y 座標です。
28    pub y: isize,
29}
30
31impl Point {
32    /// 座標 `(x, y)` の点を作ります。
33    pub const fn new(x: isize, y: isize) -> Self {
34        Self { x, y }
35    }
36
37    /// 2つのベクトルの内積を返します。
38    ///
39    /// `self.x * other.x + self.y * other.y` です。両方が零ベクトルでない場合、
40    /// 正ならなす角は鋭角、`0` なら直角、負なら鈍角または反対向きです。
41    /// 点を引き算して作った方向ベクトルの角度判定に使えます。計算量は `O(1)` です。
42    pub const fn dot(self, other: Self) -> isize {
43        self.x * other.x + self.y * other.y
44    }
45
46    /// 2つのベクトルの外積を返します。
47    ///
48    /// `self.x * other.y - self.y * other.x` です。正なら `other` は `self` の
49    /// 反時計回り側、負なら時計回り側です。`0` は平行で、逆向きや零ベクトルも含みます。
50    /// 絶対値は2つのベクトルが作る平行四辺形の面積です。計算量は `O(1)` です。
51    pub const fn cross(self, other: Self) -> isize {
52        self.x * other.y - self.y * other.x
53    }
54
55    /// 2つのベクトルが平行なら `true` を返します。
56    ///
57    /// 外積が `0` かを判定します。逆向きも平行とし、零ベクトルを含む場合も `true` です。
58    pub const fn is_parallel(self, other: Self) -> bool {
59        self.cross(other) == 0
60    }
61
62    /// 2つのベクトルが直交するなら `true` を返します。
63    ///
64    /// 内積が `0` かを判定します。零ベクトルを含む場合も `true` です。
65    pub const fn is_orthogonal(self, other: Self) -> bool {
66        self.dot(other) == 0
67    }
68
69    /// 原点からの距離の二乗を返します。
70    ///
71    /// `x * x + y * y` です。平方根を取らずに距離の大小を比較できます。
72    pub const fn norm_squared(self) -> isize {
73        self.dot(self)
74    }
75
76    /// 原点からの距離の二乗を返します。
77    ///
78    /// [`norm_squared`](Self::norm_squared) と同じです。
79    pub const fn norm2(self) -> isize {
80        self.norm_squared()
81    }
82
83    /// `other` までの距離の二乗を返します。
84    ///
85    /// `(self.x - other.x)^2 + (self.y - other.y)^2` です。実際の距離ではなく
86    /// その二乗を返すため、距離の比較にも閾値の二乗を使います。計算量は `O(1)` です。
87    pub fn distance_squared(self, other: Self) -> isize {
88        (self - other).norm_squared()
89    }
90
91    /// `other` までの距離の二乗を返します。
92    ///
93    /// [`distance_squared`](Self::distance_squared) と同じです。
94    pub fn dist2(self, other: Self) -> isize {
95        self.distance_squared(other)
96    }
97}
98
99impl Add for Point {
100    type Output = Self;
101
102    fn add(self, rhs: Self) -> Self::Output {
103        Self::new(self.x + rhs.x, self.y + rhs.y)
104    }
105}
106
107impl Sub for Point {
108    type Output = Self;
109
110    fn sub(self, rhs: Self) -> Self::Output {
111        Self::new(self.x - rhs.x, self.y - rhs.y)
112    }
113}
114
115impl Mul<isize> for Point {
116    type Output = Self;
117
118    fn mul(self, rhs: isize) -> Self::Output {
119        Self::new(self.x * rhs, self.y * rhs)
120    }
121}
122
123/// 原点から見た2点の偏角を比較します。
124///
125/// 正のx軸を始点に反時計回りで比較し、同じ向きなら [`Ordering::Equal`] を返します。
126/// 原点は含めず、座標同士の積が `isize` に収まる範囲で使用してください。
127/// 計算量は `O(1)` です。
128/// 同じ向きの点同士は距離では並べ替えません。別の点を中心にする場合は、
129/// その中心を両方の点から引いたベクトルを比較してください。
130///
131/// # Examples
132///
133/// ```
134/// use atcoder::geometry::{argcmp, Point};
135///
136/// let mut points = [Point::new(0, -1), Point::new(0, 1), Point::new(1, 0)];
137/// points.sort_by(|&a, &b| argcmp(a, b));
138/// assert_eq!(points, [Point::new(1, 0), Point::new(0, 1), Point::new(0, -1)]);
139/// ```
140///
141/// # 参考
142///
143/// ngtkanaさんの[整数演算による偏角ソートの記事](https://blog.ngtkana.com/entry/2021/11/13/202103)を参考にしています。
144pub fn argcmp(a: Point, b: Point) -> Ordering {
145    // 辞書順で (y, x) < (0, 0) は「y < 0、または y == 0 かつ x < 0」。
146    // 下半平面と負のx軸を後半に分け、角度が一周する境界を正のx軸に置く。
147    ((a.y, a.x) < (0, 0))
148        .cmp(&((b.y, b.x) < (0, 0)))
149        // 同じ半分の中では外積の符号で順序を決める。a×b > 0 なら a が先。
150        .then((b.x * a.y).cmp(&(a.x * b.y)))
151}
152
153/// 3点 `a`, `b`, `c` の向きを外積で返します。
154///
155/// 正なら反時計回り、負なら時計回り、`0` なら一直線上です。計算量は `O(1)` です。
156///
157/// 有向直線 `a → b` から見て `c` が左側なら正、右側なら負です。
158/// 戻り値は `(b - a) × (c - a)` そのもので、`-1 / 0 / 1` に正規化しません。
159/// 絶対値は三角形 `abc` の面積の2倍です。`0` だけでは `c` が線分上かは
160/// 分からないため、線分上の判定には [`on_segment`] を使ってください。
161///
162/// ```
163/// use atcoder::geometry::{ccw, Point};
164/// let a = Point::new(0, 0);
165/// let b = Point::new(2, 0);
166/// assert_eq!(ccw(a, b, Point::new(1, 3)), 6);  // 左側(上側)
167/// assert_eq!(ccw(a, b, Point::new(1, -3)), -6); // 右側(下側)
168/// assert_eq!(ccw(a, b, Point::new(3, 0)), 0);  // 直線上だが線分外
169/// ```
170pub fn ccw(a: Point, b: Point, c: Point) -> isize {
171    (b - a).cross(c - a)
172}
173
174/// 点 `point` が閉線分 `ab` 上にあるか判定します。
175///
176/// 端点を含みます。計算量は `O(1)` です。
177/// `a == b` のときは、`point == a` の場合だけ `true` です。
178pub fn on_segment(a: Point, b: Point, point: Point) -> bool {
179    // 同一直線上で、point から両端へのベクトルが反対向き(端点なら一方が0)か調べる。
180    ccw(a, b, point) == 0 && (a - point).dot(b - point) <= 0
181}
182
183/// 2つの閉線分 `ab` と `cd` が交差するか判定します。
184///
185/// 端点での接触や同一直線上の重なりも交差に含めます。計算量は `O(1)` です。
186/// 長さ0の線分も扱えます。交点の座標や交差の種類は返しません。
187///
188/// # Examples
189///
190/// ```
191/// use atcoder::geometry::{segments_intersect, Point};
192///
193/// assert!(segments_intersect(
194///     Point::new(0, 0),
195///     Point::new(2, 2),
196///     Point::new(0, 2),
197///     Point::new(2, 0),
198/// ));
199/// ```
200pub fn segments_intersect(a: Point, b: Point, c: Point, d: Point) -> bool {
201    let ab_c = ccw(a, b, c);
202    let ab_d = ccw(a, b, d);
203    let cd_a = ccw(c, d, a);
204    let cd_b = ccw(c, d, b);
205
206    if ab_c == 0 && on_segment(a, b, c) {
207        return true;
208    }
209    if ab_d == 0 && on_segment(a, b, d) {
210        return true;
211    }
212    if cd_a == 0 && on_segment(c, d, a) {
213        return true;
214    }
215    if cd_b == 0 && on_segment(c, d, b) {
216        return true;
217    }
218
219    (ab_c > 0) != (ab_d > 0) && (cd_a > 0) != (cd_b > 0)
220}
221
222/// 多角形の符号付き面積の2倍を返します。
223///
224/// `points` は周上の順に並べます。反時計回りなら正、時計回りなら負です。
225/// 空または頂点が2個以下なら `0` を返します。頂点数を `N` とすると `O(N)` です。
226/// 最後の頂点と最初の頂点は自動的につなぐため、先頭頂点を末尾に再追加する必要はありません。
227/// 自己交差しない多角形の通常の面積は、戻り値の絶対値を2で割った値です。
228/// 戻り値が奇数なら面積は半整数なので、整数除算による切り捨てに注意してください。
229pub fn polygon_area2(points: &[Point]) -> isize {
230    if points.is_empty() {
231        return 0;
232    }
233    (0..points.len())
234        .map(|index| points[index].cross(points[(index + 1) % points.len()]))
235        .sum()
236}
237
238#[cfg(test)]
239mod tests {
240    use std::cmp::Ordering;
241
242    use super::{argcmp, polygon_area2, segments_intersect, Point};
243
244    #[test]
245    fn compares_polar_angles_counterclockwise_from_positive_x_axis() {
246        let points = [
247            Point::new(1, 0),
248            Point::new(1, 1),
249            Point::new(0, 1),
250            Point::new(-1, 1),
251            Point::new(-1, 0),
252            Point::new(-1, -1),
253            Point::new(0, -1),
254            Point::new(1, -1),
255        ];
256        for (i, &a) in points.iter().enumerate() {
257            for (j, &b) in points.iter().enumerate() {
258                assert_eq!(argcmp(a, b), i.cmp(&j));
259            }
260        }
261    }
262
263    #[test]
264    fn compares_same_direction_vectors_as_equal() {
265        assert_eq!(argcmp(Point::new(1, 2), Point::new(2, 4)), Ordering::Equal);
266        assert_eq!(
267            argcmp(Point::new(-1, -2), Point::new(-2, -4)),
268            Ordering::Equal
269        );
270        assert_ne!(
271            argcmp(Point::new(1, 2), Point::new(-1, -2)),
272            Ordering::Equal
273        );
274    }
275
276    #[test]
277    fn detects_touching_and_disjoint_segments() {
278        assert!(segments_intersect(
279            Point::new(0, 0),
280            Point::new(2, 0),
281            Point::new(2, 0),
282            Point::new(3, 1)
283        ));
284        assert!(!segments_intersect(
285            Point::new(0, 0),
286            Point::new(1, 0),
287            Point::new(2, 0),
288            Point::new(3, 0)
289        ));
290    }
291
292    #[test]
293    fn computes_signed_double_area() {
294        let triangle = [Point::new(0, 0), Point::new(3, 0), Point::new(0, 2)];
295        assert_eq!(polygon_area2(&triangle), 6);
296    }
297}