diff --git a/ch7/polygon.py b/ch7/polygon.py index 7108b2e..a47f5a3 100644 --- a/ch7/polygon.py +++ b/ch7/polygon.py @@ -3,6 +3,8 @@ import math + +EPS = 1e-9 # const double EPS = 1e-9; # double DEG_to_RAD(double d) { return d*M_PI / 180.0; } @@ -10,16 +12,26 @@ # 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) {} }; @@ -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; @@ -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 @@ -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) { @@ -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) { @@ -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; @@ -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) { @@ -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(); @@ -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); @@ -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 @@ -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