Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Halfplane Intersection #90

Open
wants to merge 48 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
93c9b26
Updated onSegment.h and both segment intersections
Chillee Apr 26, 2019
3b6ac0e
Updated headers
Chillee Apr 26, 2019
0a0261b
Updated some line width issues
Chillee Apr 26, 2019
bccd1cb
Fixed broken dependency
Chillee Apr 26, 2019
d2e700a
removed automatic epsilon checks
Chillee Apr 27, 2019
59f8511
Put it on one line now
Chillee Apr 27, 2019
1bdf870
Moved template up a line to fit parameters on one line
Chillee Apr 29, 2019
2f32d63
Changed tabs to spaces so it'll align
Chillee Apr 29, 2019
28456a1
Fixed some misc issues
Chillee Apr 29, 2019
b7a8157
Readded relevant description
Chillee Apr 29, 2019
ba9395d
Fixed formatting issue
Chillee Apr 30, 2019
fab7b2e
made change
Chillee May 2, 2019
e99191f
Added first commit of halfPlane
Chillee May 2, 2019
95b770b
merged
Chillee May 4, 2019
63425c3
Simplified/shortened the code
Chillee May 5, 2019
888dd17
merged
Chillee May 5, 2019
ed75bf9
Fixed some issues
Chillee May 5, 2019
8571390
Added some unit tests for halfplane
Chillee May 5, 2019
e5b85b7
Add failure example for MIT's halfplane code
Chillee May 5, 2019
0a4e8d3
Added fuzz tests with SJTU code
Chillee May 5, 2019
4df6e0e
Fit stuff within 63 columns
Chillee May 5, 2019
19c70ac
Added option for long double in Halfplane tests
Chillee May 5, 2019
cbd17e8
Abstracted away the angle comparison
Chillee May 5, 2019
2eee5c1
Added newline
Chillee May 5, 2019
a1d85bb
Updated header
Chillee May 5, 2019
b0b03a9
Responded to comments and tried a macro
Chillee May 5, 2019
81f9cd3
removed extraneous deque variables
Chillee May 5, 2019
d283267
Updated description and such
Chillee May 5, 2019
8c6bb7c
Shortened half plane through macros
Chillee May 5, 2019
bf838c6
Shortened half plane a bit
Chillee May 5, 2019
1c47a37
Started fixing the bugs
Chillee May 6, 2019
1aa4b39
Fixed bugs/found bugs
Chillee May 6, 2019
e61acd5
Doing some precision analysis
Chillee May 6, 2019
4cb642a
Updated with current version
Chillee May 6, 2019
50a3915
Shortened a bit
Chillee May 6, 2019
3d7e6f0
removed binary
Chillee May 9, 2019
a8b3ec7
Removed some debug code
Chillee May 14, 2019
b6f7c82
merged
Chillee Oct 23, 2019
8971c03
some debugging stuff
Chillee Oct 26, 2019
5f4ff5a
Added some experimentation code
Chillee Oct 31, 2019
3a94b90
merged
Chillee Jun 26, 2020
74faa81
remove unintended
Chillee Jun 26, 2020
f3916db
removed test cases
Chillee Jun 26, 2020
4330561
removed test cases
Chillee Jun 26, 2020
908a313
Added my current guess about the condition for failure
Chillee Jun 26, 2020
34c550e
Warning fixes
simonlindholm Oct 20, 2020
5fb4588
Use .angle()
simonlindholm Oct 20, 2020
26b50ee
Fix cmp to not say a < a
simonlindholm Oct 20, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions content/geometry/HalfPlane.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

/**
* Author: chilli
* Date: 2019-05-05
* License: CC0
* Source: https://github.com/zimpha/algorithmic-library/blob/master/computational-geometry/polygon.cc
* Description: Computes the intersection of a set of half-planes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this better? What format is the input and output in?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure how to describe the input... I wrote "Input is given as a set of planes, facing left.", but that seems like a pretty poor description.

* Time: O(n \log n)
* Status: fuzz-tested, submitted on https://maps19.kattis.com/problems/marshlandrescues
* Usage:
*/
#pragma once

#include "Point.h"
#include "sideOf.h"
#include "lineIntersection.h"

typedef Point<double> P;
typedef array<P, 2> Line;
#define sp(a) a[0], a[1]
#define ang(a) atan2((a[1] - a[0]).y, (a[1] - a[0]).x)

int angDiff(Line a, Line b) { return sgn(ang(a) - ang(b)); }
bool cmp(Line a, Line b) {
auto s = angDiff(a, b);
return s == 0 ? sideOf(sp(b), a[0]) >= 0 : s < 0;
}
vector<P> halfPlaneIntersection(vector<Line> vs) {
sort(all(vs), cmp);
vector<Line> deq(sz(vs) + 5);
vector<P> ans(sz(vs) + 5);
deq[0] = vs[0];
int dh = 0, dt = 1, ah = 0, at = 0;
for (int i = 1; i < sz(vs); ++i) {
if (angDiff(vs[i], vs[i - 1]) == 0) continue;
while (ah<at && sideOf(sp(vs[i]),ans[at-1]) < 0) at--,dt--;
while (ah<at && sideOf(sp(vs[i]),ans[ah]) < 0) ah++,dh++;
ans[at++] = lineInter(sp(deq[dt - 1]), sp(vs[i])).second;
deq[dt++] = vs[i];
}
while (ah<at && sideOf(sp(deq[dh]),ans[at-1]) < 0) at--,dt--;
while (ah<at && sideOf(sp(deq[dt]),ans[ah]) < 0) ah++, dh++;
if (dt - dh <= 2) return {};
ans[at++] = lineInter(sp(deq[dh]), sp(deq[dt - 1])).second;
Chillee marked this conversation as resolved.
Show resolved Hide resolved
return {ans.begin() + ah, ans.begin() + at};
}
3 changes: 2 additions & 1 deletion content/geometry/PolygonArea.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
#include "Point.h"

template<class T>
T polygonArea2(vector<Point<T>>& v) {
T polygonArea2(vector<Point<T>> v) {
Chillee marked this conversation as resolved.
Show resolved Hide resolved
if (sz(v) < 3) return 0;
T a = v.back().cross(v[0]);
rep(i,0,sz(v)-1) a += v[i].cross(v[i+1]);
return a;
Expand Down
15 changes: 7 additions & 8 deletions content/geometry/lineIntersection.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ If a unique intersection point of the lines going through s1,e1 and s2,e2 exists
\includegraphics[width=\textwidth]{content/geometry/lineIntersection}
\end{minipage}
* Status: tested
* Usage:
* Usage:
* point<double> intersection;
* if (1 == LineIntersection(s1,e1,s2,e2,intersection))
* cout << "intersection point at " << intersection << endl;
Expand All @@ -21,11 +21,10 @@ If a unique intersection point of the lines going through s1,e1 and s2,e2 exists
#include "Point.h"

template<class P>
int lineIntersection(const P& s1, const P& e1, const P& s2,
const P& e2, P& r) {
if ((e1-s1).cross(e2-s2)) { // if not parallel
r = s2-(e2-s2)*s1.cross(e1, s2)/(e1-s1).cross(e2-s2);
return 1;
} else
return -(s1.cross(e1, s2)==0 || s2==e2);
pair<int, P> lineInter(P s1, P e1, P s2, P e2) {
Chillee marked this conversation as resolved.
Show resolved Hide resolved
auto d = (e1-s1).cross(e2-s2);
if (d == 0) //if parallel
return {-((e1-s1).cross(s2-s1)==0 || s2==e2), P(0,0)};
Chillee marked this conversation as resolved.
Show resolved Hide resolved
else
return {1, s2-(e2-s2)*(e1-s1).cross(s2-s1)/d};
Chillee marked this conversation as resolved.
Show resolved Hide resolved
}
254 changes: 254 additions & 0 deletions fuzz-tests/geometry/HalfPlane.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#include <bits/stdc++.h>
using namespace std;

#define rep(i, a, b) for (int i = a; i < int(b); ++i)
#define trav(a, v) for (auto &a : v)
#define all(x) x.begin(), x.end()
#define sz(x) (int)(x).size()

typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

#include "../../content/geometry/HalfPlane.h"
#include "../../content/geometry/PolygonArea.h"

ostream &operator<<(ostream &os, P p) { return cout << "(" << p.x << "," << p.y << ")"; }

namespace sjtu {
typedef double T;
const T EPS = 1e-8;
inline int sign(T a) { return a < -EPS ? -1 : a > EPS; }
struct Point {
T x, y;
Point() {}
Point(T _x, T _y) : x(_x), y(_y) {}
Point operator+(const Point &p) const { return Point(x + p.x, y + p.y); }
Point operator-(const Point &p) const { return Point(x - p.x, y - p.y); }
Point operator*(T d) const { return Point(x * d, y * d); }
Point operator/(T d) const { return Point(x / d, y / d); }
bool operator<(const Point &p) const {
int c = sign(x - p.x);
if (c)
return c == -1;
return sign(y - p.y) == -1;
}
T dot(const Point &p) const { return x * p.x + y * p.y; }
T det(const Point &p) const { return x * p.y - y * p.x; }
T alpha() const { return atan2(y, x); }
T distTo(const Point &p) const {
T dx = x - p.x, dy = y - p.y;
return hypot(dx, dy);
}
T alphaTo(const Point &p) const {
T dx = x - p.x, dy = y - p.y;
return atan2(dy, dx);
}
// clockwise
Point rotAlpha(const T &alpha, const Point &o = Point(0, 0)) const {
T nx = cos(alpha) * (x - o.x) + sin(alpha) * (y - o.y);
T ny = -sin(alpha) * (x - o.x) + cos(alpha) * (y - o.y);
return Point(nx, ny) + o;
}
Point rot90() const { return Point(-y, x); }
Point unit() { return *this / abs(); }
void read() { scanf("%lf%lf", &x, &y); }
T abs() { return hypot(x, y); }
T abs2() { return x * x + y * y; }
void write() { cout << "(" << x << "," << y << ")" << endl; }
};
#define cross(p1, p2, p3) ((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y))
#define crossOp(p1, p2, p3) sign(cross(p1, p2, p3))

Point isSS(Point p1, Point p2, Point q1, Point q2) {
T a1 = cross(q1, q2, p1), a2 = -cross(q1, q2, p2);
return (p1 * a2 + p2 * a1) / (a1 + a2);
}
struct Border {
Point p1, p2;
T alpha;
void setAlpha() { alpha = atan2(p2.y - p1.y, p2.x - p1.x); }
void read() {
p1.read();
p2.read();
setAlpha();
}
};

int n;
const int MAX_N_BORDER = 20000 + 10;
Border border[MAX_N_BORDER];

bool operator<(const Border &a, const Border &b) {
int c = sign(a.alpha - b.alpha);
if (c != 0)
return c == 1;
return crossOp(b.p1, b.p2, a.p1) >= 0;
}

bool operator==(const Border &a, const Border &b) { return sign(a.alpha - b.alpha) == 0; }

void add(T x, T y, T nx, T ny) {
border[n].p1 = Point(x, y);
border[n].p2 = Point(nx, ny);
border[n].setAlpha();
n++;
}

Point isBorder(const Border &a, const Border &b) { return isSS(a.p1, a.p2, b.p1, b.p2); }

Border que[MAX_N_BORDER];
int qh, qt;

bool check(const Border &a, const Border &b, const Border &me) {
Point is = isBorder(a, b);
return crossOp(me.p1, me.p2, is) > 0;
}

void convexIntersection() {
qh = qt = 0;
sort(border, border + n);
n = unique(border, border + n) - border;
for (int i = 0; i < n; ++i) {
Border cur = border[i];
while (qh + 1 < qt && !check(que[qt - 2], que[qt - 1], cur))
--qt;
while (qh + 1 < qt && !check(que[qh], que[qh + 1], cur))
++qh;
que[qt++] = cur;
}
while (qh + 1 < qt && !check(que[qt - 2], que[qt - 1], que[qh]))
--qt;
while (qh + 1 < qt && !check(que[qh], que[qh + 1], que[qt - 1]))
++qh;
}

T calcArea() {
static Point ps[MAX_N_BORDER];
int cnt = 0;

if (qt - qh <= 2) {
return 0;
}

for (int i = qh; i < qt; ++i) {
int next = i + 1 == qt ? qh : i + 1;
ps[cnt++] = isBorder(que[i], que[next]);
}

T area = 0;
for (int i = 0; i < cnt; ++i) {
area += ps[i].det(ps[(i + 1) % cnt]);
}
area /= 2;
area = fabsl(area);
return area;
}

T halfPlaneIntersection(vector<Line> cur) {
n = 0;
for (auto i: cur)
add(i[0].x, i[0].y, i[1].x, i[1].y);
convexIntersection();
return calcArea();
}
} // namespace sjtu

const double INF = 100;
const double EPS = 1e-8;
void addInf(vector<Line> &res, double INF = INF) {
vector<P> infPts({P(INF, INF), P(-INF, INF), P(-INF, -INF), P(INF, -INF)});
for (int i = 0; i < 4; i++)
res.push_back({infPts[i], infPts[(i + 1) % 4]});
}
P randPt(int lim = INF) { return P(rand() % lim, rand() % lim); }
void test1() {
vector<Line> t({{P(0, 0), P(5, 0)}, {P(5, -2), P(5, 2)}, {P(5, 2), P(2, 2)}, {P(0, 3), P(0, -3)}});
auto res = halfPlaneIntersection(t);
assert(polygonArea2(res) == 20);
addInf(t);
res = halfPlaneIntersection(t);
assert(polygonArea2(res) == 20);
}
void testInf() {
vector<Line> t({{P(0, 0), P(5, 0)}});
addInf(t);
auto res = halfPlaneIntersection(t);
assert(polygonArea2(res) / (4 * INF * INF) == 1);
t = vector<Line>({{P(0, 0), P(5, 0)}, {P(0, 0), P(0, 5)}});
addInf(t);
res = halfPlaneIntersection(t);
assert(polygonArea2(res) / (2 * INF * INF) == 1);
}
void testLine() {
vector<Line> t({{P(0, 0), P(5, 0)}, {P(5, 0), P(0, 0)}});
addInf(t);
auto res = halfPlaneIntersection(t);
assert(sz(res) >= 2);
assert(polygonArea2(res) == 0);
}
void testPoint() {
vector<Line> t({{P(0, 0), P(5, 0)}, {P(5, 0), P(0, 0)}, {P(0, 0), P(0, 5)}, {P(0, 5), P(0, 0)}});
addInf(t);
auto res = halfPlaneIntersection(t);
assert(sz(res) >= 1);
assert(polygonArea2(res) == 0);
}
void testEmpty() {
vector<Line> t(
{{P(0, 0), P(5, 0)}, {P(5, 0), P(0, 0)}, {P(0, 0), P(0, 5)}, {P(0, 5), P(0, 0)}, {P(0, 2), P(5, 2)}});
addInf(t);
auto res = halfPlaneIntersection(t);
assert(sz(res) == 0);
}
void testRandom() {
int lim = 1e1;
for (int i = 0; i < 10000; i++) {
vector<Line> t;
for (int i = 0; i < 6; i++) {
Line cand{P(0, 0), P(0, 0)};
while (cand[0] == cand[1])
cand = {randPt(lim), randPt(lim)};
t.push_back(cand);
}
addInf(t, lim);
auto res = halfPlaneIntersection(t);
double area = sjtu::halfPlaneIntersection(t);
double diff = abs(2 * area - polygonArea2(res));
if (diff > EPS) {
cout << diff << ' ' << area << ' ' << endl;
for (auto i : t)
cout << i[0] << "->" << i[1] << ' ';
cout << endl;
for (auto i : res)
cout << i << ',';
cout << endl;
}
assert(diff < EPS);
}
}
void testSelf() {
int lim = 1e2;
}

int main() {
test1();
testInf();
testLine();
testPoint();
testEmpty();
testRandom();
vector<Line> t({{P(0,0), P(5,0)}, {P(0,1), P(5,1)}});
reverse(all(t));
addInf(t);

auto res = halfPlaneIntersection(t);
for (auto i: res) cout<<i<<' ';
cout<<endl;
// Failure case for MIT's half plane code
// vector<Line> t({{P(9, 8), P(9, 1)}, {P(3, 3), P(3, 5)}, {P(7, 6), P(0, 8)}});
// addInf(t);
// cout << polygonArea2(halfPlaneIntersection(t)) << endl;
// cout << MIT::Intersection_Area(convert(t)) << endl;
cout << "Tests passed!" << endl;
}
Empty file added fuzz-tests/utils/genConvex.h
Empty file.