half_plane_intersection.hpp¶
Directed boundary whose feasible half-plane is on its left side.
求若干有向直线左侧半平面的交集多边形,并处理空集或无界情况。
Implementation¶
#ifndef NOYA_HALF_PLANE_INTERSECTION_HPP
#define NOYA_HALF_PLANE_INTERSECTION_HPP 1
/// @complexity Time: O(n log n).
/// Space: O(n).
#include "noya/geometry_base.hpp"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <deque>
#include <optional>
#include <vector>
namespace noya {
/// @brief Directed boundary whose feasible half-plane is on its left side.
struct half_plane {
point<long double> origin;
point<long double> direction;
half_plane() = default;
half_plane(point<long double> from, point<long double> to)
: origin(from), direction(to - from) {
assert(norm2(direction) > 0);
}
bool contains(const point<long double> &value,
long double epsilon = 1e-12L) const {
return cross(direction, value - origin) >= -epsilon;
}
};
namespace half_plane_internal {
inline int direction_half(const point<long double> &direction) {
return direction.y > 0 || (direction.y == 0 && direction.x >= 0) ? 0 : 1;
}
inline bool direction_less(const half_plane &left, const half_plane &right) {
int left_half = direction_half(left.direction);
int right_half = direction_half(right.direction);
if (left_half != right_half) {
return left_half < right_half;
}
long double product = cross(left.direction, right.direction);
if (product != 0) {
return product > 0;
}
if (left.origin.x != right.origin.x) {
return left.origin.x < right.origin.x;
}
return left.origin.y < right.origin.y;
}
inline bool same_direction(const half_plane &left, const half_plane &right,
long double epsilon) {
return std::abs(cross(left.direction, right.direction)) <= epsilon &&
dot(left.direction, right.direction) > 0;
}
inline std::optional<point<long double>>
boundary_intersection(const half_plane &left, const half_plane &right,
long double epsilon) {
long double denominator = cross(left.direction, right.direction);
if (std::abs(denominator) <= epsilon) {
return std::nullopt;
}
long double ratio =
cross(right.origin - left.origin, right.direction) / denominator;
return left.origin + left.direction * ratio;
}
} // namespace half_plane_internal
/// @brief Return the counter-clockwise polygon of a nonempty bounded
/// half-plane intersection. Empty and unbounded intersections return {}.
inline std::vector<point<long double>>
half_plane_intersection(std::vector<half_plane> half_planes,
long double epsilon = 1e-12L) {
using half_plane_internal::boundary_intersection;
using half_plane_internal::same_direction;
std::sort(half_planes.begin(), half_planes.end(),
half_plane_internal::direction_less);
std::vector<half_plane> unique;
for (const half_plane ¤t : half_planes) {
if (!unique.empty() && same_direction(unique.back(), current, epsilon)) {
if (unique.back().contains(current.origin, epsilon)) {
unique.back() = current;
}
} else {
unique.push_back(current);
}
}
std::deque<half_plane> deque;
for (const half_plane ¤t : unique) {
while (deque.size() >= 2) {
auto intersection =
boundary_intersection(deque[deque.size() - 2], deque.back(), epsilon);
if (intersection && current.contains(*intersection, epsilon)) {
break;
}
deque.pop_back();
}
while (deque.size() >= 2) {
auto intersection = boundary_intersection(deque[0], deque[1], epsilon);
if (intersection && current.contains(*intersection, epsilon)) {
break;
}
deque.pop_front();
}
deque.push_back(current);
}
while (deque.size() >= 3) {
auto intersection =
boundary_intersection(deque[deque.size() - 2], deque.back(), epsilon);
if (intersection && deque.front().contains(*intersection, epsilon)) {
break;
}
deque.pop_back();
}
while (deque.size() >= 3) {
auto intersection = boundary_intersection(deque[0], deque[1], epsilon);
if (intersection && deque.back().contains(*intersection, epsilon)) {
break;
}
deque.pop_front();
}
if (deque.size() < 3) {
return {};
}
std::vector<point<long double>> polygon;
for (int index = 0; index < int(deque.size()); index++) {
auto intersection = boundary_intersection(
deque[index], deque[(index + 1) % deque.size()], epsilon);
if (!intersection) {
return {};
}
polygon.push_back(*intersection);
}
for (const point<long double> &value : polygon) {
for (const half_plane &constraint : unique) {
if (!constraint.contains(value, epsilon * 16)) {
return {};
}
}
}
if (std::abs(polygon_area2(polygon)) <= epsilon) {
return {};
}
return polygon;
}
} // namespace noya
#endif // NOYA_HALF_PLANE_INTERSECTION_HPP
#include <algorithm>
#include <cassert>
#include <cmath>
#include <deque>
#include <optional>
#include <vector>
/// @complexity Time: O(n log n).
/// Space: O(n).
/// @complexity Time: O(1) per primitive; O(n log n) for convex hull.
/// Space: O(1) per primitive and O(n) for hull construction.
namespace noya {
/// @brief Two-dimensional point with vector arithmetic and lexicographic order.
template <class T> struct point {
T x{};
T y{};
point() = default;
point(T x_, T y_) : x(x_), y(y_) {}
point &operator+=(const point &other) {
x += other.x;
y += other.y;
return *this;
}
point &operator-=(const point &other) {
x -= other.x;
y -= other.y;
return *this;
}
point &operator*=(const T &scale) {
x *= scale;
y *= scale;
return *this;
}
point &operator/=(const T &scale) {
x /= scale;
y /= scale;
return *this;
}
friend point operator+(point left, const point &right) {
return left += right;
}
friend point operator-(point left, const point &right) {
return left -= right;
}
friend point operator*(point value, const T &scale) { return value *= scale; }
friend point operator*(const T &scale, point value) { return value *= scale; }
friend point operator/(point value, const T &scale) { return value /= scale; }
friend bool operator==(const point &, const point &) = default;
friend bool operator<(const point &left, const point &right) {
return left.x < right.x || (left.x == right.x && left.y < right.y);
}
};
/// @brief Circle represented by a center and a nonnegative radius.
template <class Real> struct circle {
point<Real> center;
Real radius{};
};
/// @brief Return the dot product of two vectors.
template <class T> T dot(const point<T> &a, const point<T> &b) {
return a.x * b.x + a.y * b.y;
}
/// @brief Return the signed cross product of two vectors.
template <class T> T cross(const point<T> &a, const point<T> &b) {
return a.x * b.y - a.y * b.x;
}
/// @brief Return cross(a - origin, b - origin).
template <class T>
T cross(const point<T> &origin, const point<T> &a, const point<T> &b) {
return cross(a - origin, b - origin);
}
/// @brief Return the squared Euclidean norm.
template <class T> T norm2(const point<T> &value) { return dot(value, value); }
/// @brief Compare a value with zero using an optional absolute tolerance.
template <class T> int sign(const T &value, const T &epsilon = T{}) {
return (value > epsilon) - (value < -epsilon);
}
/// @brief Return -1, 0, or 1 for a clockwise, collinear, or counter-clockwise
/// turn.
template <class T>
int orientation(const point<T> &a, const point<T> &b, const point<T> &c,
const T &epsilon = T{}) {
return sign(cross(a, b, c), epsilon);
}
/// @brief Test whether p lies on the closed segment [a, b].
template <class T>
bool on_segment(const point<T> &p, const point<T> &a, const point<T> &b,
const T &epsilon = T{}) {
if (orientation(a, b, p, epsilon) != 0) {
return false;
}
return std::min(a.x, b.x) - epsilon <= p.x &&
p.x <= std::max(a.x, b.x) + epsilon &&
std::min(a.y, b.y) - epsilon <= p.y &&
p.y <= std::max(a.y, b.y) + epsilon;
}
/// @brief Test whether the closed segments [a, b] and [c, d] intersect.
template <class T>
bool segments_intersect(const point<T> &a, const point<T> &b, const point<T> &c,
const point<T> &d, const T &epsilon = T{}) {
int ab_c = orientation(a, b, c, epsilon);
int ab_d = orientation(a, b, d, epsilon);
int cd_a = orientation(c, d, a, epsilon);
int cd_b = orientation(c, d, b, epsilon);
if (ab_c == 0 && on_segment(c, a, b, epsilon)) {
return true;
}
if (ab_d == 0 && on_segment(d, a, b, epsilon)) {
return true;
}
if (cd_a == 0 && on_segment(a, c, d, epsilon)) {
return true;
}
if (cd_b == 0 && on_segment(b, c, d, epsilon)) {
return true;
}
return ab_c * ab_d < 0 && cd_a * cd_b < 0;
}
/// @brief Intersect the infinite lines through (a, b) and (c, d), returning
/// nullopt when they are parallel or coincident.
template <class T>
std::optional<point<long double>>
line_intersection(const point<T> &a, const point<T> &b, const point<T> &c,
const point<T> &d, long double epsilon = 0) {
point<long double> first{static_cast<long double>(a.x),
static_cast<long double>(a.y)};
point<long double> second{static_cast<long double>(b.x),
static_cast<long double>(b.y)};
point<long double> third{static_cast<long double>(c.x),
static_cast<long double>(c.y)};
point<long double> fourth{static_cast<long double>(d.x),
static_cast<long double>(d.y)};
point<long double> direction_a = second - first;
point<long double> direction_b = fourth - third;
long double denominator = cross(direction_a, direction_b);
if (std::abs(denominator) <= epsilon) {
return std::nullopt;
}
long double ratio = cross(third - first, direction_b) / denominator;
return first + direction_a * ratio;
}
/// @brief Return the convex hull in counter-clockwise order without repetition.
template <class T>
std::vector<point<T>> convex_hull(std::vector<point<T>> points,
bool keep_collinear = false) {
std::sort(points.begin(), points.end());
points.erase(std::unique(points.begin(), points.end()), points.end());
if (points.size() <= 1) {
return points;
}
bool all_collinear = true;
for (int i = 2; i < int(points.size()); i++) {
all_collinear &= orientation(points[0], points[1], points[i]) == 0;
}
if (keep_collinear && all_collinear) {
return points;
}
std::vector<point<T>> lower, upper;
for (const point<T> &p : points) {
while (lower.size() >= 2) {
int turn = orientation(lower[lower.size() - 2], lower.back(), p);
if (turn > 0 || (keep_collinear && turn == 0)) {
break;
}
lower.pop_back();
}
lower.push_back(p);
}
for (auto it = points.rbegin(); it != points.rend(); ++it) {
while (upper.size() >= 2) {
int turn = orientation(upper[upper.size() - 2], upper.back(), *it);
if (turn > 0 || (keep_collinear && turn == 0)) {
break;
}
upper.pop_back();
}
upper.push_back(*it);
}
lower.pop_back();
upper.pop_back();
lower.insert(lower.end(), upper.begin(), upper.end());
return lower;
}
/// @brief Return twice the signed area of a polygon.
template <class T> T polygon_area2(const std::vector<point<T>> &polygon) {
T result{};
for (int i = 0; i < int(polygon.size()); i++) {
result += cross(polygon[i], polygon[(i + 1) % polygon.size()]);
}
return result;
}
/// @brief Classify a point relative to a polygon: -1 outside, 0 boundary, 1
/// inside.
template <class T>
int point_in_polygon(const point<T> &p, const std::vector<point<T>> &polygon) {
bool inside = false;
for (int i = 0; i < int(polygon.size()); i++) {
point<T> a = polygon[i];
point<T> b = polygon[(i + 1) % polygon.size()];
if (on_segment(p, a, b)) {
return 0;
}
if (a.y <= p.y && p.y < b.y && orientation(a, b, p) > 0) {
inside = !inside;
}
if (b.y <= p.y && p.y < a.y && orientation(a, b, p) < 0) {
inside = !inside;
}
}
return inside ? 1 : -1;
}
/// @brief Return the Euclidean distance between two points.
template <class T> long double distance(const point<T> &a, const point<T> &b) {
return std::hypot(
static_cast<long double>(a.x) - static_cast<long double>(b.x),
static_cast<long double>(a.y) - static_cast<long double>(b.y));
}
} // namespace noya
namespace noya {
/// @brief Directed boundary whose feasible half-plane is on its left side.
struct half_plane {
point<long double> origin;
point<long double> direction;
half_plane() = default;
half_plane(point<long double> from, point<long double> to)
: origin(from), direction(to - from) {
assert(norm2(direction) > 0);
}
bool contains(const point<long double> &value,
long double epsilon = 1e-12L) const {
return cross(direction, value - origin) >= -epsilon;
}
};
namespace half_plane_internal {
inline int direction_half(const point<long double> &direction) {
return direction.y > 0 || (direction.y == 0 && direction.x >= 0) ? 0 : 1;
}
inline bool direction_less(const half_plane &left, const half_plane &right) {
int left_half = direction_half(left.direction);
int right_half = direction_half(right.direction);
if (left_half != right_half) {
return left_half < right_half;
}
long double product = cross(left.direction, right.direction);
if (product != 0) {
return product > 0;
}
if (left.origin.x != right.origin.x) {
return left.origin.x < right.origin.x;
}
return left.origin.y < right.origin.y;
}
inline bool same_direction(const half_plane &left, const half_plane &right,
long double epsilon) {
return std::abs(cross(left.direction, right.direction)) <= epsilon &&
dot(left.direction, right.direction) > 0;
}
inline std::optional<point<long double>>
boundary_intersection(const half_plane &left, const half_plane &right,
long double epsilon) {
long double denominator = cross(left.direction, right.direction);
if (std::abs(denominator) <= epsilon) {
return std::nullopt;
}
long double ratio =
cross(right.origin - left.origin, right.direction) / denominator;
return left.origin + left.direction * ratio;
}
} // namespace half_plane_internal
/// @brief Return the counter-clockwise polygon of a nonempty bounded
/// half-plane intersection. Empty and unbounded intersections return {}.
inline std::vector<point<long double>>
half_plane_intersection(std::vector<half_plane> half_planes,
long double epsilon = 1e-12L) {
using half_plane_internal::boundary_intersection;
using half_plane_internal::same_direction;
std::sort(half_planes.begin(), half_planes.end(),
half_plane_internal::direction_less);
std::vector<half_plane> unique;
for (const half_plane ¤t : half_planes) {
if (!unique.empty() && same_direction(unique.back(), current, epsilon)) {
if (unique.back().contains(current.origin, epsilon)) {
unique.back() = current;
}
} else {
unique.push_back(current);
}
}
std::deque<half_plane> deque;
for (const half_plane ¤t : unique) {
while (deque.size() >= 2) {
auto intersection =
boundary_intersection(deque[deque.size() - 2], deque.back(), epsilon);
if (intersection && current.contains(*intersection, epsilon)) {
break;
}
deque.pop_back();
}
while (deque.size() >= 2) {
auto intersection = boundary_intersection(deque[0], deque[1], epsilon);
if (intersection && current.contains(*intersection, epsilon)) {
break;
}
deque.pop_front();
}
deque.push_back(current);
}
while (deque.size() >= 3) {
auto intersection =
boundary_intersection(deque[deque.size() - 2], deque.back(), epsilon);
if (intersection && deque.front().contains(*intersection, epsilon)) {
break;
}
deque.pop_back();
}
while (deque.size() >= 3) {
auto intersection = boundary_intersection(deque[0], deque[1], epsilon);
if (intersection && deque.back().contains(*intersection, epsilon)) {
break;
}
deque.pop_front();
}
if (deque.size() < 3) {
return {};
}
std::vector<point<long double>> polygon;
for (int index = 0; index < int(deque.size()); index++) {
auto intersection = boundary_intersection(
deque[index], deque[(index + 1) % deque.size()], epsilon);
if (!intersection) {
return {};
}
polygon.push_back(*intersection);
}
for (const point<long double> &value : polygon) {
for (const half_plane &constraint : unique) {
if (!constraint.contains(value, epsilon * 16)) {
return {};
}
}
}
if (std::abs(polygon_area2(polygon)) <= epsilon) {
return {};
}
return polygon;
}
} // namespace noya