Skip to content
106 changes: 101 additions & 5 deletions ch7/polygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,35 @@


import math

EPS = 1e-9
# const double EPS = 1e-9;

# double DEG_to_RAD(double d) { return d*M_PI / 180.0; }

# double RAD_to_DEG(double r) { return r*180.0 / M_PI; }

class point:
x = 0 # default values
y = 0
def __init__(self, x, y): # constructor
self.x = x
self.y = y

def __lt__(self, b): return (self.x, self.y) < (b.x, b.y)

def __str__(self): return "{} {}".format(self.x, self.y)
def __hash__(self):return hash((self.x,self.y))
# bool operator == (point other) const {
# return (fabs(x-other.x) < EPS && (fabs(y-other.y) < EPS)); }
# bool operator <(const point &p) const {
# return x < p.x || (abs(x-p.x) < EPS && y < p.y); } };

class vec:
def __init__(self, x=0, y=0):
self.x = x
self.y = y

def toVec(a, b):
return vec(b.x-a.x, b.y-a.y)
# struct vec { double x, y; // name: `vec' is different from STL vector
# vec(double _x, double _y) : x(_x), y(_y) {} };

Expand All @@ -31,12 +43,15 @@ def dist(p1, p2): # Euclidean distance

# returns the perimeter of polygon P, which is the sum of
# Euclidian distances of consecutive line segments (polygon edges)
# def perimeter(P) return math.fsum(dist(P[i], P[i+1]) for i in range(len(P)-1))
def perimeter(P):
ans = 0.0
for i in range(len(P)-1): # note: P[n-1] = P[0]
ans += dist(P[i], P[i+1]) # as we duplicate P[0]
return ans

def area(P):
return math.fsum(cross(P[i], P[i+1]) for i in range(len(P)-1))/2
# // returns the area of polygon P
# double area(const vector<point> &P) {
# double ans = 0.0;
Expand All @@ -45,14 +60,23 @@ def perimeter(P):
# return fabs(ans)/2.0; // only do / 2.0 here
# }



def dot(a, b): return a.x * b.x + a.y * b.y
# double dot(vec a, vec b) { return (a.x*b.x + a.y*b.y); }

def norm_sq(v): return v.x * v.x + v.y * v.y
# double norm_sq(vec v) { return v.x*v.x + v.y*v.y; }

def angle(a, o, b):
oa = toVec(o, a)
ob = toVec(o, b)
return math.acos(dot(oa, ob) / math.sqrt(norm_sq(oa) * norm_sq(ob)))
# double angle(point a, point o, point b) { // returns angle aob in rad
# vec oa = toVec(o, a), ob = toVec(o, b);
# return acos(dot(oa, ob) / sqrt(norm_sq(oa) * norm_sq(ob))); }

def cross(a, b): return a.x*b.y-a.y*b.x
# double cross(vec a, vec b) { return a.x*b.y - a.y*b.x; }

# // returns the area of polygon P, which is half the cross products
Expand All @@ -64,17 +88,28 @@ def perimeter(P):
# return fabs(ans)/2.0;
# }

def ccw(p, q, r): return (cross(toVec(p,q),toVec(p,r)) > 0)
# note python i used class opperators for tovec (Agis Daniels)
# // note: to accept collinear points, we have to change the `> 0'
# // returns true if point r is on the left side of line pq
# bool ccw(point p, point q, point r) {
# return cross(toVec(p, q), toVec(p, r)) > 0;
# }

def collinear(p, q, r): return abs(cross(toVec(p, q), toVec(p, r))) < EPS
# // returns true if point r is on the same line as the line pq
# bool collinear(point p, point q, point r) {
# return fabs(cross(toVec(p, q), toVec(p, r))) < EPS;
# }

def isConvex(P):
e,s=len(P),1
if e<4: return False
t1=ccw(P[0],P[1],P[2]) # first turn
for i in range(s, e-1):
if ccw(P[i],P[i+1],P[1 if i+2==n else i+2]) != t1:
return False
return True
# // returns true if we always make the same turn
# // while examining all the edges of the polygon one by one
# bool isConvex(const vector<point> &P) {
Expand All @@ -88,6 +123,18 @@ def perimeter(P):
# return true; // otherwise -> convex
# }

def insidePolygon(pt, P):
if len(P)<4: return -1
n, ans, s=len(P), False, 0.0
for i in range(n-1):
a, b=P[i], P[i+1]
if abs(dist(a, pt) + dist(pt, b) - dist(a, b)) < EPS:
ans=True
if ans: return 0
for i in range(n-1):
a=angle(P[i], pt, P[i+1])
s+= a if ccw(pt, P[i], P[i+1]) else -a
return 1 if abs(s) > math.pi else -1
# // returns 1/0/-1 if point p is inside/on (vertex/edge)/outside of
# // either convex/concave polygon P
# int insidePolygon(point pt, const vector<point> &P) {
Expand All @@ -108,6 +155,10 @@ def perimeter(P):
# return fabs(sum) > M_PI ? 1 : -1; // 360d->in, 0d->out
# }

def lineInterSectSeg(p,q,A,B):
a, b, c=B.y-A.y, A.x-B.x, cross(B,A)
u, v=abs(a*p.x+b*p.y+c), abs(a*q.x+b*q.y+x)
return point((p.x*v+q.x*u)/(u+v), (p.y*v+q.y*u)/(u+v))
# // compute the intersection point between line segment p-q and line A-B
# point lineIntersectSeg(point p, point q, point A, point B) {
# double a = B.y-A.y, b = A.x-B.x, c = B.x*A.y - A.x*B.y;
Expand All @@ -116,6 +167,15 @@ def perimeter(P):
# return point((p.x*v + q.x*u) / (u+v), (p.y*v + q.y*u) / (u+v));
# }

def cutPolygon(A, B, Q):
P=[]
for i in range(len(Q)):
l1, l2=cross(toVec(A, B),toVec(A, Q[i])), 0
if i!=len(Q)-1: l2=cross(toVec(A, B), toVec(A, Q[i+1]))
if l1>-EPS: P.append(Q[i])
if l1*l2<-EPS: P.append(lineInterSectSeg(Q[i], Q[i+1], A, B))
if P and P[-1]!=P[0]: P.append(P[0])
return P
# // cuts polygon Q along the line formed by point A->point B (order matters)
# // (note: the last point must be the same as the first point)
# vector<point> cutPolygon(point A, point B, const vector<point> &Q) {
Expand All @@ -132,6 +192,30 @@ def perimeter(P):
# return P;
# }

def CH_Graham(Pts):
P=[p for p in Pts]
n=len(P)
if n<4:
if P[0]!=P[-1]: P.append(P[0])
return P

def ccw_cmp(a, b): return ccw(P[0], a, b)
def cmp_class(f):
class K:
def __init__(F, o): F.pt=o
def __lt__(F, o): return f(F.pt, o.pt)
P0=P.index(min(P, key=lambda p: (p.y,-p.x)))
P[0],P[P0]=P[P0],P[0]

P=[P[0]]+sorted(P[1:], key=cmp_class(ccw_cmp))

S, i=[P[-1],P[0]], 2
while i<n:
j=len(S)-1
if not ccw(S[j-1], S[j], P[i]):
S.append(P[i]); i+=1
else: S.pop()
return S
# vector<point> CH_Graham(vector<point> &Pts) { // overall O(n log n)
# vector<point> P(Pts); // copy all points
# int n = (int)P.size();
Expand Down Expand Up @@ -162,6 +246,18 @@ def perimeter(P):
# return S; // return the result
# }

#compressed version of the code below and the link below
#https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
def CH_Andrew(ps):
P, H=sorted(set(ps)), []
if len(P)<=1: return P # if only one unique point just return point
def f(B): #f is a mapping of the two loops since its dup code
for p in P:
while len(H)>B and not ccw(H[-2], H[-1], p): H.pop()
H.append(p)
H.pop()
f(1); P=P[::-1]; f(len(H)+1); return H #4 line low, rev, up, ret
#c++ implementation below
# vector<point> CH_Andrew(vector<point> &Pts) { // overall O(n log n)
# int n = Pts.size(), k = 0;
# vector<point> H(2*n);
Expand Down Expand Up @@ -230,7 +326,7 @@ def perimeter(P):
# //1 P0 P2
# //0 1 2 3 4 5 6 7 8 9

# P = cutPolygon(P[2], P[4], P);
P = cutPolygon(P[2], P[4], P);
# printf("Perimeter = %.2lf\n", perimeter(P)); // smaller now, 29.15
# printf("Area = %.2lf\n", area(P)); // 40.00

Expand All @@ -243,8 +339,8 @@ def perimeter(P):
# //2 | P_in |
# //1 P0--------------P1
# //0 1 2 3 4 5 6 7 8 9

# P = CH_Graham(P); // now this is a rectangle
P = CH_Graham(P); #// now this is a rectangle
print(perimeter(P))
# printf("Perimeter = %.2lf\n", perimeter(P)); // precisely 28.00
# printf("Area = %.2lf\n", area(P)); // precisely 48.00
# printf("Is convex = %d\n", isConvex(P)); // true
Expand Down