Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Geometry / Advanced Planar Geometry

7.4.2 Half-Plane Intersection

7-Geometry/7.4.2_Half-Plane_Intersection.cpp

Given a list of directed lines, compute the convex polygon contained in every left half-plane. Half-plane intersection sorts the lines by direction, keeps only the tightest representative among parallel lines, and maintains a deque of lines whose pairwise intersections form the current boundary. Directions are ordered by half-plane and cross-product signs, without epsilon comparisons, to satisfy std::sort's strict ordering requirement. EPS is then used outside the comparator to group parallel directions and retain only the tightest half-plane in each group.

  • half_plane_intersection(planes) returns the polygon cut out by the half-planes in counter-clockwise order. Each half-plane is represented by HalfPlane(p, q), meaning the closed region to the left of the directed line p $\to$ q. This implementation is intended for intersections that form a bounded polygon with positive area; empty, unbounded, or degenerate inputs may return an empty vector. Add explicit bounding-box half-planes when a bounded polygon is required. The two points defining each half-plane must differ.

Implementation

#include <algorithm>
#include <cmath>
#include <deque>
#include <type_traits>
#include <vector>

const double EPS = 1e-9;

template<typename T, typename U, typename C = std::common_type_t<T, U>>
bool EQ(T a, U b) {
  if constexpr (std::is_floating_point_v<C>) return C(a) == C(b) || std::fabs(C(a) - C(b)) <= EPS;
  return C(a) == C(b);
}

template<typename T, typename U, typename C = std::common_type_t<T, U>>
bool LT(T a, U b) {
  if constexpr (std::is_floating_point_v<C>) return C(a) < C(b) - EPS;
  return C(a) < C(b);
}

struct Point {
  double x, y;
  Point(double x = 0, double y = 0) : x(x), y(y) {}
  bool operator==(const Point &p) const { return x == p.x && y == p.y; }
  friend bool EQ(const Point &a, const Point &b) { return EQ(a.x, b.x) && EQ(a.y, b.y); }
  Point operator+(const Point &p) const { return {x + p.x, y + p.y}; }
  Point operator-(const Point &p) const { return {x - p.x, y - p.y}; }
  Point operator*(double k) const { return {x * k, y * k}; }
  double cross(const Point &p) const { return x * p.y - y * p.x; }
};

struct HalfPlane {
  Point p, dir;

  HalfPlane() = default;
  HalfPlane(Point p, Point q) : p(p), dir(q - p) {}

  bool out(const Point &q) const { return LT(dir.cross(q - p), 0); }

  Point intersect(const HalfPlane &h) const {
    double t = h.dir.cross(p - h.p) / dir.cross(h.dir);
    return p + dir * t;
  }

  bool operator<(const HalfPlane &h) const {
    auto half = [](const Point &d) { return d.y < 0 || (d.y == 0 && d.x < 0); };
    if (half(dir) != half(h.dir)) {
      return half(dir) < half(h.dir);
    }
    return dir.cross(h.dir) > 0;
  }
};

std::vector<Point> half_plane_intersection(std::vector<HalfPlane> planes) {
  std::sort(planes.begin(), planes.end());
  std::vector<HalfPlane> unique;
  for (const HalfPlane &h : planes) {
    if (!unique.empty() && EQ(unique.back().dir.cross(h.dir), 0)) {
      if (unique.back().dir.cross(h.p - unique.back().p) > 0) {
        unique.back() = h;
      }
      continue;
    }
    unique.push_back(h);
  }
  std::deque<HalfPlane> dq;
  auto bad_back = [&](const HalfPlane &h) {
    return dq.size() >= 2 && h.out(dq[dq.size() - 2].intersect(dq.back()));
  };
  auto bad_front = [&](const HalfPlane &h) {
    return dq.size() >= 2 && h.out(dq[0].intersect(dq[1]));
  };
  for (const HalfPlane &h : unique) {
    while (bad_back(h)) {
      dq.pop_back();
    }
    while (bad_front(h)) {
      dq.pop_front();
    }
    dq.push_back(h);
  }
  while (dq.size() >= 3 && dq.front().out(dq[dq.size() - 2].intersect(dq.back()))) {
    dq.pop_back();
  }
  while (dq.size() >= 3 && dq.back().out(dq[0].intersect(dq[1]))) {
    dq.pop_front();
  }
  if (dq.size() < 3) {
    return {};
  }
  std::vector<Point> res;
  for (int i = 0; i < static_cast<int>(dq.size()); i++) {
    if (EQ(dq[i].dir.cross(dq[(i + 1) % dq.size()].dir), 0)) {
      return {};
    }
    res.push_back(dq[i].intersect(dq[(i + 1) % dq.size()]));
  }
  return res;
}

Example Usage

#include <cassert>
using namespace std;

double polygon_area(const vector<Point> &p) {
  double area = 0;
  for (int i = 0, j = static_cast<int>(p.size()) - 1; i < static_cast<int>(p.size()); j = i++) {
    area += p[j].cross(p[i]);
  }
  return fabs(area) / 2.0;
}

int main() {
  vector<HalfPlane> box{
      HalfPlane(Point(0, 0), Point(4, 0)),
      HalfPlane(Point(4, 0), Point(4, 4)),
      HalfPlane(Point(4, 4), Point(0, 4)),
      HalfPlane(Point(0, 4), Point(0, 0)),
  };
  auto square = half_plane_intersection(box);
  assert(square.size() == 4);
  assert(EQ(polygon_area(square), 16));

  auto tighter = box;
  tighter.push_back(HalfPlane(Point(0, 1), Point(4, 1)));  // y >= 1
  assert(EQ(polygon_area(half_plane_intersection(tighter)), 12));

  box.push_back(HalfPlane(Point(2, 0), Point(2, 4)));  // x <= 2
  auto rect = half_plane_intersection(box);
  assert(rect.size() == 4);
  assert(EQ(polygon_area(rect), 8));

  vector<HalfPlane> empty{
      HalfPlane(Point(0, 0), Point(1, 0)),  // y >= 0
      HalfPlane(Point(1, 1), Point(0, 1)),  // y <= 1
      HalfPlane(Point(0, 2), Point(1, 2)),  // y >= 2
  };
  assert(half_plane_intersection(empty).empty());
  return 0;
}