//************************************************************* //**** 2D GEOMETRY CLASSES AND UTILITIES, Jarek Rossignac ***** //**** SIMPLIFIED August 31 2008 ***** //**** DISCLAIMER: These utilities may contain errors ***** //************************************************************* pt P(pt P) {return new pt(P.x,P.y); }; // make copy of point pt P(float x, float y) {return new pt(x,y); }; // make point (x,y) pt A(pt A, pt B) {return new pt(A.x+B.x,A.y+B.y); }; // A+B pt A(pt A, float s, pt B) {return new pt(A.x+s*B.x,A.y+s*B.y); }; // A+sB pt S(float s, pt A) {return new pt(s*A.x,s*A.y); }; // sA pt S(pt A, float s, pt B) {return new pt(A.x+s*(B.x-A.x),A.y+s*(B.y-A.y)); }; // A+sAB pt S(pt P, float s, vec V) {return new pt(P.x + s*V.x, P.y + s*V.y); } // P+sV pt S(pt P, vec V) {return new pt(P.x + V.x, P.y + V.y); } // P+V pt M(pt A, pt B) {return(new pt((A.x+B.x)/2.0,(A.y+B.y)/2.0)); }; // (A+B)/2 pt M(pt A, pt B, pt C) {return new pt((A.x+B.x+C.x)/3.0,(A.y+B.y+C.y)/3.0); }; // (A+B+C)/3 pt M(float a, pt A, float b, pt B) {return A(S(a,A),b,B);} // aA+bB pt M(float a, pt A, float b, pt B, float c, pt C) {return A(S(a,A),M(b,B,c,C));} // aA+bB+cC pt M(float a, pt A, float b, pt B, float c, pt C, float d, pt D){return A(M(a,A,b,B),M(c,C,d,D));} // aA+bB+cC+dD pt T(pt P, float s, vec V) {float n = n(V); if (n>0) return new pt(P.x + s/n*V.x, P.y + s/n*V.y); else return P(P);} // P+sV/||V|| pt T(pt P, float s, pt Q) {vec V = V(P,Q); return T(P,s,V) ; }; // P+sPQ/||PQ|| pt R(pt Q, float a, pt P) {float dx=Q.x-P.x, dy=Q.y-P.y, c=cos(a), s=sin(a); return P(P.x+c*dx+s*dy, P.y-s*dx+c*dy); }; // Rotated Q by a around P pt R(pt Q, float a) {float dx=Q.x, dy=Q.y, c=cos(a), s=sin(a); return new pt(c*dx+s*dy, -s*dx+c*dy); }; // Rotated Q by a around origin vec V(vec V) {return new vec(V.x,V.y); }; // make copy of vector V vec V(float x, float y) {return new vec(x,y); }; // make vector (x,y) vec V(pt P, pt Q) {return new vec(Q.x-P.x,Q.y-P.y);}; // PQ vec R(vec V) {return new vec(-V.y,V.x);}; // V turned right 90 degrees (as seen on screen) vec U(vec V) {float n = n(V); if (n==0) return new vec(0,0); else return new vec(V.x/n,V.y/n);}; // V/||V|| vec S(float s,vec V) {return new vec(s*V.x,s*V.y);}; // sV vec S(vec U,float s,vec V) {return new vec(U.x+s*(V.x-U.x),U.y+s*(V.y-U.y));}; // (1-s)U+sV vec M(vec U, vec V) {return new vec((U.x+V.x)/2.0,(U.y+V.y)/2.0); }; // (U+V)/2 vec M(float a, vec U, float b, vec V) {return new vec(a*U.x+b*V.x,a*U.y+b*V.y);} // aU+bV vec R(vec U, float a) {vec W = U.makeRotatedBy(a); return W ; }; // U rotated by a vec R(vec U, float s, vec V) {float a = angle(U,V); vec W = U.makeRotatedBy(s*a); // interpolation (angle and length) between U and V float u = n(U); float v=n(V); S((u+s*(v-u))/u,W); return W ; }; float d(pt P, pt Q) {return sqrt(sq(Q.x-P.x)+sq(Q.y-P.y)); }; // ||AB|| float n(vec V) {return sqrt(sq(V.x)+sq(V.y));}; // ||V|| float n2(vec V) {return sq(V.x)+sq(V.y);}; // V*V float dot(vec U, vec V) {return U.x*V.x+U.y*V.y; }; //U*V float a (vec U, vec V) {float a = atan2(dot(R(U),V),dot(U,V)); // angle(UPI) return mPItoPIangle(a-2*PI); if(a<-PI) return mPItoPIangle(a+2*PI); return a; }; pt Mouse() {return(new pt(mouseX,mouseY));}; // current mouse location vec MouseDrag() {return new vec(mouseX-pmouseX,mouseY-pmouseY);}; // vector representing recent mouse displacement void v(pt P) {vertex(P.x,P.y);}; // next point when drawing polygons between beginShape(); and endShape(); void cross(pt P, float r) {line(P.x-r,P.y,P.x+r,P.y); line(P.x,P.y-r,P.x,P.y+r);}; // shows P as cross of length r void cross(pt P) {cross(P,2);}; // shows P as small cross void show(pt P, float r) {ellipse(P.x, P.y, 2*r, 2*r);}; // draws circle of center r around point void show(pt P) {ellipse(P.x, P.y, 4,4);}; // draws small circle around point void show(pt P, pt Q) {line(P.x,P.y,Q.x,Q.y); }; // draws edge (P,Q) void show(pt P, vec V) {line(P.x,P.y,P.x+V.x,P.y+V.y); } // show line from P along V void show(pt P, float s, vec V) {show(P,S(s,V));} // show line from P along sV void arrow(pt P, pt Q) {arrow(P,V(P,Q)); } // draws arrow from P to Q void arrow(pt P, float s, vec V) {arrow(P,S(s,V));} // show arrow from P along sV void arrow(pt P, vec V) {show(P,V); float n=n(V); float s=max(min(0.2,20./n),6./n); // show arrow from P along V pt Q=S(P,V); vec U = S(-s,V); vec W = R(S(.3,U)); beginShape(); v(S(S(Q,U),W)); v(Q); v(S(S(Q,U),-1,W)); endShape(CLOSE);}; boolean right(pt A, pt B, pt C) {return dot( A.makeVecTo(B).makeTurnedLeft() , B.makeVecTo(C) ) >0 ; }; // right turn (as seen on screen) boolean isRightTurn(pt A, pt B, pt C) {return dot( R(V(A,B)),V(B,C)) > 0 ; }; // right turn (as seen on screen) boolean isRightOf(pt A, pt Q, vec T) {return dot(R(T),V(Q,A)) > 0 ; }; // A is on right of ray(Q,T) (as seen on screen) float a(pt A, pt B, pt C) {return a(V(B,A),V(B,C)); } // angle (A,B,C) float area(pt A, pt B, pt C) {return 0.5*dot(V(A,C),R(V(A,B))); } // signed area of triangle float rBubble (pt A, pt B, pt C) { // grows as bubble pushes through, r>0 when ABC ic clockwise float a=d(B,C), b=d(C,A), c=d(A,B); float s=(a+b+c)/2; float d=sqrt(s*(s-a)*(s-b)*(s-c)); float r=a*b*c/4/d; if (abs(a(A,B,C))>PI/2) r=sq(d(A,C)/2)/r; if (abs(a(C,A,B))>PI/2) r=sq(d(C,B)/2)/r; if (abs(a(B,C,A))>PI/2) r=sq(d(B,A)/2)/r; if (!right(A,B,C)) r=-r; return r; }; pt center (pt A, pt B, pt C) { vec AB = V(A,B); vec AC = R(V(A,C)); return S(A,1./2/dot(AB,AC),M(-n2(AC),R(AB),n2(AB),AC)); }; // circumcenter //************************************************************************ //**** POINTS //************************************************************************ class pt { float x=0,y=0; // CREATE pt () {} pt (float px, float py) {x = px; y = py;}; pt (pt P) {x = P.x; y = P.y;}; pt (pt P, vec V) {x = P.x+V.x; y = P.y+V.y;}; pt (pt P, float s, vec V) {x = P.x+s*V.x; y = P.y+s*V.y;}; pt (pt A, float s, pt B) {x = A.x+s*(B.x-A.x); y = A.y+s*(B.y-A.y);}; // MODIFY void setTo(float px, float py) {x = px; y = py;}; void setTo(pt P) {x = P.x; y = P.y;}; void setToMouse() { x = mouseX; y = mouseY; }; void moveWithMouse() { x += mouseX-pmouseX; y += mouseY-pmouseY; }; void translateToTrack(float s, pt P) {setTo(T(P,s,V(P,this)));}; void track(float s, pt P) {setTo(T(P,s,V(P,this)));}; void scaleBy(float f) {x*=f; y*=f;}; void scaleBy(float u, float v) {x*=u; y*=v;}; void translateBy(vec V) {x += V.x; y += V.y;}; void translateBy(float s, vec V) {x += s*V.x; y += s*V.y;}; void translateBy(float u, float v) {x += u; y += v;}; void translateTowards(float s, pt P) {x+=s*(P.x-x); y+=s*(P.y-y); }; void translateTowardsBy(float s, pt P) {vec V = this.makeVecTo(P); V.normalize(); this.translateBy(s,V); }; void addPt(pt P) {x += P.x; y += P.y;}; // incorrect notation, but useful for computing weighted averages void addScaledPt(float s, pt P) {x += s*P.x; y += s*P.y;}; // incorrect notation, but useful for computing weighted averages void rotateBy(float a) {float dx=x, dy=y, c=cos(a), s=sin(a); x=c*dx+s*dy; y=-s*dx+c*dy; }; // around origin void rotateBy(float a, pt P) {float dx=x-P.x, dy=y-P.y, c=cos(a), s=sin(a); x=P.x+c*dx+s*dy; y=P.y-s*dx+c*dy; }; // around point P void rotateBy(float s, float t, pt P) {float dx=x-P.x, dy=y-P.y; dx-=dy*t; dy+=dx*s; dx-=dy*t; x=P.x+dx; y=P.y+dy; }; // s=sin(a); t=tan(a/2); void clipToWindow() {x=max(x,0); y=max(y,0); x=min(x,height); y=min(y,height); } // OUTPUT POINT pt clone() {return new pt(x,y); }; pt makeClone() {return new pt(x,y); }; pt makeTranslatedBy(vec V) {return(new pt(x + V.x, y + V.y));}; pt makeTranslatedBy(float s, vec V) {return(new pt(x + s*V.x, y + s*V.y));}; pt makeTransaltedTowards(float s, pt P) {return(new pt(x + s*(P.x-x), y + s*(P.y-y)));}; pt makeTranslatedBy(float u, float v) {return(new pt(x + u, y + v));}; pt makeRotatedBy(float a, pt P) {float dx=x-P.x, dy=y-P.y, c=cos(a), s=sin(a); return(new pt(P.x+c*dx+s*dy, P.y-s*dx+c*dy)); }; pt makeRotatedBy(float a) {float dx=x, dy=y, c=cos(a), s=sin(a); return(new pt(c*dx+s*dy, -s*dx+c*dy)); }; pt makeProjectionOnLine(pt P, pt Q) {float a=dot(P.makeVecTo(this),P.makeVecTo(Q)), b=dot(P.makeVecTo(Q),P.makeVecTo(Q)); return(P.makeTransaltedTowards(a/b,Q)); }; pt makeOffset(pt P, pt Q, float r) { float a = angle(vecTo(P),vecTo(Q))/2; float h = r/tan(a); vec T = vecTo(P); T.normalize(); vec N = T.left(); pt R = new pt(x,y); R.translateBy(h,T); R.translateBy(r,N); return R; }; // OUTPUT VEC vec vecTo(pt P) {return(new vec(P.x-x,P.y-y)); }; vec makeVecTo(pt P) {return(new vec(P.x-x,P.y-y)); }; vec makeVecToCenter () {return(new vec(x-height/2.,y-height/2.)); }; vec makeVecToAverage (pt P, pt Q) {return(new vec((P.x+Q.x)/2.0-x,(P.y+Q.y)/2.0-y)); }; vec makeVecToAverage (pt P, pt Q, pt R) {return(new vec((P.x+Q.x+R.x)/3.0-x,(P.y+Q.y+R.x)/3.0-y)); }; vec makeVecToMouse () {return(new vec(mouseX-x,mouseY-y)); }; vec makeVecToBisectProjection (pt P, pt Q) {float a=this.disTo(P), b=this.disTo(Q); return(this.makeVecTo(interpolate(P,a/(a+b),Q))); }; vec makeVecToNormalProjection (pt P, pt Q) {float a=dot(P.makeVecTo(this),P.makeVecTo(Q)), b=dot(P.makeVecTo(Q),P.makeVecTo(Q)); return(this.makeVecTo(interpolate(P,a/b,Q))); }; vec makeVecTowards(pt P, float d) {vec V = makeVecTo(P); float n = V.norm(); V.normalize(); V.scaleBy(d-n); return V; }; // OUTPUT TEST OR MEASURE float disTo(pt P) {return(sqrt(sq(P.x-x)+sq(P.y-y))); }; float disToMouse() {return(sqrt(sq(x-mouseX)+sq(y-mouseY))); }; boolean isInWindow() {return(((x>0)&&(x0)&&(y0; return(l); }; boolean isInTriangle(pt A, pt B, pt C) { boolean a = this.isLeftOf(B,C); boolean b = this.isLeftOf(C,A); boolean c = this.isLeftOf(A,B); return((a&&b&&c)||(!a&&!b&&!c));}; boolean isInCircle(pt C, float r) {return d(this,C)0); }; // true if B is a left turn (appears right on the screen, Y goes down) boolean mouseIsInWindow() {return(((mouseX>0)&&(mouseX0)&&(mouseY0.000001) {x/=n; y/=n;};}; void add(vec V) {x += V.x; y += V.y;}; void add(float s, vec V) {x += s*V.x; y += s*V.y;}; void add(float u, float v) {x += u; y += v;}; void turnLeft() {float w=x; x=-y; y=w;}; void rotateBy (float a) {float xx=x, yy=y; x=xx*cos(a)-yy*sin(a); y=xx*sin(a)+yy*cos(a); }; // OUTPUT VEC vec makeClone() {return(new vec(x,y));}; vec makeUnit() {float n=sqrt(sq(x)+sq(y)); if (n<0.000001) n=1; return(new vec(x/n,y/n));}; vec unit() {float n=sqrt(sq(x)+sq(y)); if (n<0.000001) n=1; return(new vec(x/n,y/n));}; vec makeScaledBy(float s) {return(new vec(x*s, y*s));}; vec makeTurnedLeft() {return(new vec(-y,x));}; vec left() {return(new vec(-y,x));}; vec makeOffsetVec(vec V) {return(new vec(x + V.x, y + V.y));}; vec makeOffsetVec(float s, vec V) {return(new vec(x + s*V.x, y + s*V.y));}; vec makeOffsetVec(float u, float v) {return(new vec(x + u, y + v));}; vec makeRotatedBy(float a) {return(new vec(x*cos(a)-y*sin(a),x*sin(a)+y*cos(a))); }; vec makeReflectedVec(vec N) { return makeOffsetVec(-2.*dot(this,N),N);}; // OUTPUT TEST MEASURE float norm() {return(sqrt(sq(x)+sq(y)));} boolean isNull() {return((abs(x)+abs(y)<0.000001));} float angle() {return(atan2(y,x)); } // DRAW, PRINT void write() {println("("+x+","+y+")");}; void show (pt P) {line(P.x,P.y,P.x+x,P.y+y); }; void showAt (pt P) {line(P.x,P.y,P.x+x,P.y+y); }; void showArrowAt (pt P) {line(P.x,P.y,P.x+x,P.y+y); float n=min(this.norm()/10.,height/50.); pt Q=P.makeTranslatedBy(this); vec U = this.makeUnit().makeScaledBy(-n); vec W = U.makeTurnedLeft().makeScaledBy(0.3); beginShape(); Q.makeTranslatedBy(U).makeTranslatedBy(W).v(); Q.v(); W.scaleBy(-1); Q.makeTranslatedBy(U).makeTranslatedBy(W).v(); endShape(CLOSE); }; void showLabel(String s, pt P) {pt Q = P.makeTranslatedBy(0.5,this); vec N = makeUnit().makeTurnedLeft(); Q.makeTranslatedBy(3,N).showLabel(s); }; } // end vec class vec average(vec U, vec V) {return(new vec((U.x+V.x)/2.0,(U.y+V.y)/2.0)); }; vec mouseDrag() {return new vec(mouseX-pmouseX,mouseY-pmouseY);}; //************************************************************************ //**** ANGLES //************************************************************************ float angle(vec V) {return(atan2(V.y,V.x)); }; float angle(vec U, vec V) {return(atan2(dot(U.makeTurnedLeft(),V),dot(U,V))); }; float mPItoPIangle(float a) { if(a>PI) return(mPItoPIangle(a-2*PI)); if(a<-PI) return(mPItoPIangle(a+2*PI)); return(a);}; float toDeg(float a) {return(a*180/PI);} float toRad(float a) {return(a*PI/180);} //************************************************************************ //**** TRIANGLES //************************************************************************ boolean ccw(pt A, pt B, pt C) {return C.isLeftOf(A,B) ;} float triArea(pt A, pt B, pt C) {return 0.5*dot(A.makeVecTo(B),A.makeVecTo(C).makeTurnedLeft()); } float triThickness(pt A, pt B, pt C) {float a = abs(dot(V(B,A),U(R(V(B,C))))), b = abs(dot(V(C,B),U(R(V(C,A))))), c = abs(dot(V(A,C),U(R(V(A,B))))); return min (a,b,c); } void show(pt A, pt B, pt C) {beginShape(); A.v(); B.v(); C.v(); endShape(CLOSE);} void show(pt A, pt B, pt C, float r) {if (triThickness(A,B,C)<2*r) return; float s=r; if (!ccw(A,B,C)) s=-s; pt AA = A.makeOffset(B,C,s); pt BB = B.makeOffset(C,A,s); pt CC = C.makeOffset(A,B,s); beginShape(); AA.v(); BB.v(); CC.v(); endShape(CLOSE); } float triInRadius (pt A, pt B, pt C) { float Z=triArea(A,B,C), a=B.disTo(C), b=C.disTo(A), c=A.disTo(B), s=a+b+c; return 2*Z/s; } pt triCenter (pt A, pt B, pt C) {return average(A,B,C);} pt triInCenter (pt A, pt B, pt C) { float Z=triArea(A,B,C), a=B.disTo(C), b=C.disTo(A), c=A.disTo(B), s=a+b+c, r=2*Z/s, R=a*b*c/(2*r*s); return weightedSum(a/s,A,b/s,B,c/s,C); } pt triCenterD (pt A, pt B, pt C) { float Z=triArea(A,B,C), a=B.disTo(C), b=C.disTo(A), c=A.disTo(B), ss=a*a+b*b+c*c; return weightedSum(a*a/ss,A,b*b/ss,B,c*c/ss,C); } pt triCenterN (pt A, pt B, pt C) { float Z=triArea(A,B,C), a=B.disTo(C), b=C.disTo(A), c=A.disTo(B), s=a+b+c; return weightedSum((s-a)/2/s,A,(s-b)/2/s,B,(s-c)/2/s,C); }