class Polyloop {             // class of polyloops (closed loop polygon)
  int vn = 6, cap=5000;      // number of control vertices and the cap on vn
  pt[] P = new pt [cap];     // control points
  vec[] L = new vec [cap];     // adjustment vectors for smoothing

  // CONSRTUCT, COPY FROM
  Polyloop () {vn=0; for (int i=0; i<cap; i++) P[i]=P();  for (int i=0; i<cap; i++) L[i]=V(); }    // makes points and vectors
  void reset (int pvn) {vn=pvn; for (int i=0; i<vn; i++) {P[i].setTo(-sin((i+0.5)*TWO_PI/vn)*(height*0.15)+height/2.0,cos((i+0.5)*TWO_PI/vn)*(height*0.15)+height/2.0);}; }
  void from(int pvn, pt [] Q)  {vn=pvn; for (int i=0; i<vn; i++) P[i].setTo(Q[i]);  };
  void empty()  { vn=0; };
  void appendPt(pt Q)  { P[vn++].setTo(Q);  };                                      // add point at end of list
  void setTo(Polyloop Q) {vn=Q.vn; for (int i=0; i<vn; i++) P[i].setTo(Q.P[i]);};
  void extendToMouse() {if(d(P[vn],Mouse())>5) P[vn++].setTo(Mouse()); }            // appends a point

  // ACCESS POINTS and LOOPING INDICES
  pt rP(int i) {return P[i];}      // returns  P[i] (so that the caller chan change it!!!
  pt cP(int i) {return P(P[i]);}  // returns a (safe) copy of P[i]
  int n(int j) {  if (j==vn-1) {return (0);}  else {return(j+1);}  };  // n vertex in loop
  int p(int j) {  if (j==0) {return (vn-1);}  else {return(j-1);}  };  // previous vertex in loop                                                     
  pt pickPoint() {pt Q = P[0];  float dist = P[0].disToMouse();
    for(int i = 0; i < P.length; i++) if(P[i].disToMouse() < dist) {Q = P[i]; dist = P[i].disToMouse(); };
    if (dist>width/10) Q=null; return Q; }
  // DISPLAY 
  void drawVertices() {beginShape(POINTS); for (int i=0; i<vn; i++) P[i].v();  endShape(); };  // draws vertices as dots
  void showVertices() {for (int i=0; i<vn; i++) P[i].show(2.5);   };                             // draws vertices as circles that may be filled
  void drawVertexIDs() {for (int i=0; i<vn; i++) {vec V=S(-10,U(proVec(P[p(i)],P[i],P[n(i)]))); V.add(-3,5); P[i].showLabel(str(i),V); };}; 
  void drawCurve() {strokeWeight(2); beginShape();  for (int i=0; i<vn; i++) P[i].v(); endShape(CLOSE); strokeWeight(1);} 
  void drawOpen() {beginShape();  for (int i=0; i<vn; i++) P[i].v(); endShape(); } 
  void drawBezierSpans() {for (int i=0; i<vn; i++) drawBezier(i);}
  void drawBezier(int i) {drawCubicBezier(P[i], T(P[i],1./6,V(P[p(i)],P[n(i)])), T(P[n(i)],-1./6,V(P[i],P[n(n(i))])), P[n(i)]); }
//  void drawSmooth(float s) {Polyloop Q = new Polyloop(); Q.refine(0); Q.drawCurve();} 
  void drawSmooth(float s, int r) { // renders a refined version of P, s is the J-spline parameter, r is the number of refinements
     Polyloop Q = new Polyloop(); Q.setTo(this); for(int i=0; i<r; i++) Q.refine(0); Q.drawCurve(); } 
  void showMouseProjection(float s, int r) {Polyloop Q = new Polyloop(); Q.setTo(this); for(int i=0; i<r; i++) Q.refine(0); 
    if(Q.contains(Mouse())) fill(green); else fill(magenta); Q.project(Mouse()).show(3); noFill();}
  
 // ARCHIVAL ON FILE


  // RESAMPLE
  float length () {float L=0; for (int i=0; i<vn; i++) L+=P[i].disTo(P[n(i)]); return(L); }
  void resample(int n) {Polyloop Q = new Polyloop(); Q.resampledFrom(this,n); this.setTo(Q);}
  void resampledFrom(Polyloop C, int nrv) {  
    float L = C.length();                            
    float d = L / nrv;                             
    float rd=d;  // remaining distance to n sample
    float cl=0;  // length of remaining portion of current edge
    int k=0,nk=0;
    pt Q = new pt(0,0);
     vn=0;
     Q.setTo(C.P[k]);            
     P[vn++].setTo(Q);      
     while (vn<nrv)  {
       nk=C.n(k); 
       cl=Q.disTo(C.P[nk]);                            
       if (rd<cl) {Q.translateTowardsBy(rd,C.P[nk]);
                   P[vn++].setTo(Q); cl-=rd; rd=d; }
              else {rd-=cl; Q.setTo(C.P[nk]); k++;     };
     };
   }
     
  void translateTowards(float s, Polyloop Q)  {if (Q.vn>=vn) for (int i=0; i<vn; i++) P[i].translateTowards(s,Q.P[i]);  }; // blend towards Q by ratio s

  // SMOOTHEN (FILTER)
  void smoothen(int n) {for (int i=0; i<n; i++) smoothen();}
  void smoothen() {computeL(); applyL(0.3); computeL(); applyL(-0.3);}
  void computeL() {for (int i=0; i<vn; i++) L[i]=vecToCubic(P[p(p(i))],P[p(i)],P[i],P[n(i)],P[n(n(i))]);};
  void applyL(float s) {for (int i=0; i<vn; i++) P[i].translateBy(s,L[i]);};
  void setL(int x, vec y) {L[x]=y;}
  void applyL() {for (int i=0; i<vn; i++){P[i].translateBy(L[i]);}};
  //void applyLhere(float roi) {for (int i=0; i<vn; i++) if (P[i].disToMouse()<roi) P[i].translateBy(0.5,L[i]);};
  //void showL() {for (int i=0; i<vn; i++) L[i].showAt(P[i]); }; 
 
  // REFINE (SUBDIVIDE)
  void refine(float s) {refine(s,s);}
  void refine(float a, float b) { 
      pt[] Q = new pt [2*vn];     
      for (int i=0; i<vn; i++) { Q[2*i]= B(P[p(i)],P[i],P[n(i)],a); Q[2*i+1]=F(P[p(i)],P[i],P[n(i)],P[n(n(i))],b); };
      vn*=2; for (int i=0; i<vn; i++) P[i].setTo(Q[i]);
     }
     
  // POINT / POLYLOOP DISTANCE, PROJECTION, INCLUSION   
   pt project(pt M) {
     int v=0; for (int i=1; i<vn; i++) if (M.disTo(P[i])<M.disTo(P[v])) v=i; // compute index v to closest vertex
     int e=-1;
     float d = M.disTo(P[v]);
     for (int i=0; i<vn; i++) if ( M.projectsBetween(P[i],P[n(i)])  && (M.disToLine(P[i],P[n(i)])<d)) {e=i; d=M.disToLine(P[e],P[n(e)]);};
     if (e!=-1) return(M.makeProjectionOnLine(P[e],P[n(e)])); else return(P[v].makeClone());
     }
  float distance(pt M) {return(M.disTo(project(M)));}
  boolean contains (pt M) {boolean isIn=false; for (int i=1; i<vn-1; i++) if (M.isInTriangle(P[0],P[i],P[n(i)])) isIn=!isIn; return(isIn); }

  // GLOBAL TRANSFORMATIONS 
  void frame() {
     float sx=height; float sy=height; float bx=0.0; float by=0.0; 
     for (int i=0; i<vn; i++) {if (P[i].x>bx) {bx=P[i].x;}; if (P[i].x<sx) {sx=P[i].x;}; if (P[i].y>by) {by=P[i].y;}; if (P[i].y<sy) {sy=P[i].y;}; };
     float m=max(bx-sx,by-sy);  float dx=(m-(bx-sx))/2; float dy=(m-(by-sy))/2; 
     for (int i=0; i<vn; i++) {P[i].x=(P[i].x-sx+dx)*4*height/5/m+height/10;  P[i].y=(P[i].y-sy+dy)*4*height/5/m+height/10;};    }   
  void scaleWithMouse() {float s = scaleTracedWithMouse(); for (int i=0; i<vn; i++) P[i].setTo(L(screenCenter(),s,P[i]));};   
  void translateWithMouse() {vec V=MouseDrag(); for (int i=0; i<vn; i++) P[i].translateBy(V); };   
  void rotateWithMouse() {float a=angleTracedWithMouse(); for (int i=0; i<vn; i++) P[i].rotateBy(a,screenCenter());}; 
   
  float area(){
    float sum=0;
    int vn=this.vn;
    float x1=this.P[vn-1].x; float y1=this.P[vn-1].y;
    float x2=this.P[0].x; float y2=this.P[0].y;
    float temp = (x2-x1)*((y1+y2)/2);
    sum+=temp;
    //float s = min(x1,x2); if(temp>0){fill(dgreen,50);}else{fill(dred,50);} rectMode(CORNER); if(x1<x2){rect(s,((y1+y2)/2),(x2-x1),height-((y1+y2)/2));}else{s=max(x1,x2); rect(s,((y1+y2)/2),(x2-x1),height-((y1+y2)/2));} noFill();
    for(int i=0;i<(vn-1);i++){
      x1=this.P[i].x; y1=this.P[i].y;
      x2=this.P[i+1].x; y2=this.P[i+1].y;
      temp = (x2-x1)*((y1+y2)/2);
      //s = min(x1,x2); if(temp>0){fill(dgreen,50);}else{fill(dred,50);} rectMode(CORNER); if(x1<x2){rect(s,((y1+y2)/2),(x2-x1),height-((y1+y2)/2));}else{s=max(x1,x2); rect(s,((y1+y2)/2),(x2-x1),height-((y1+y2)/2));} noFill();
      sum+=temp;
    }
    sum=abs(sum);
    return sum;
  }
  
  // ARCHIVAL ON FILE
 void savePts() {this.savePts(0);}
 void loadPts() {this.loadPts("C0.pts");}
 void savePts(int n) { String [] inppts = new String [vn+1];
    int s=0; inppts[s++]=str(vn); for (int i=0; i<vn; i++) {inppts[s++]=str(P[i].x)+","+str(P[i].y);};
    saveStrings("C"+str(n)+".pts",inppts);  };
 void loadPts(String fn) { String [] ss = loadStrings(fn);
    String subpts;
    int s=0; int comma; vn = int(ss[s]);
    for(int i=0;i<vn; i++) { comma=ss[++s].indexOf(',');
      P[i]=new pt (float(ss[s].substring(0, comma)), float(ss[s].substring(comma+1, ss[s].length()))); }; };

      
  } // end of Polyloop class

