/*****
* path.cc
* Andy Hammerlindl 2002/06/06
*
* Stores and returns information on a predefined path.
*
* When changing the path algorithms, also update the corresponding
* three-dimensional algorithms in path3.cc.
*****/

#include "path.h"
#include "util.h"
#include "angle.h"
#include "camperror.h"
#include "mathop.h"
#include "predicates.h"
#include "rounding.h"

namespace camp {

const double Fuzz2=1000.0*DBL_EPSILON;
const double Fuzz=sqrt(Fuzz2);
const double Fuzz4=Fuzz2*Fuzz2;
const double BigFuzz=10.0*Fuzz2;
const double fuzzFactor=100.0;

const double third=1.0/3.0;

path nullpath;

const char *nopoints="nullpath has no points";

void checkEmpty(Int n) {
 if(n == 0)
   reportError(nopoints);
}

// Accurate computation of sqrt(1+x)-1.
inline double sqrt1pxm1(double x)
{
 return x/(sqrt(1.0+x)+1.0);
}
inline pair sqrt1pxm1(pair x)
{
 return x/(Sqrt(1.0+x)+1.0);
}

// Solve for the real roots of the quadratic equation ax^2+bx+c=0.
quadraticroots::quadraticroots(double a, double b, double c)
{
 // Remove roots at numerical infinity.
 if(fabs(a) <= Fuzz2*fabs(b)+Fuzz4*fabs(c)) {
   if(fabs(b) > Fuzz2*fabs(c)) {
     distinct=quadraticroots::ONE;
     roots=1;
     t1=-c/b;
   } else if(c == 0.0) {
     distinct=quadraticroots::MANY;
     roots=1;
     t1=0.0;
   } else {
     distinct=quadraticroots::NONE;
     roots=0;
   }
 } else {
   double factor=0.5*b/a;
   double denom=b*factor;
   if(fabs(denom) <= Fuzz2*fabs(c)) {
     double x=-c/a;
     if(x >= 0.0) {
       distinct=quadraticroots::TWO;
       roots=2;
       t2=sqrt(x);
       t1=-t2;
     } else {
       distinct=quadraticroots::NONE;
       roots=0;
     }
   } else {
     double x=-2.0*c/denom;
     if(x > -1.0) {
       distinct=quadraticroots::TWO;
       roots=2;
       double r2=factor*sqrt1pxm1(x);
       double r1=-r2-2.0*factor;
       if(r1 <= r2) {
         t1=r1;
         t2=r2;
       } else {
         t1=r2;
         t2=r1;
       }
     } else if(x == -1.0) {
       distinct=quadraticroots::ONE;
       roots=2;
       t1=t2=-factor;
     } else {
       distinct=quadraticroots::NONE;
       roots=0;
     }
   }
 }
}

// Solve for the complex roots of the quadratic equation ax^2+bx+c=0.
Quadraticroots::Quadraticroots(pair a, pair b, pair c)
{
 if(a == 0.0) {
   if(b != 0.0) {
     roots=1;
     z1=-c/b;
   } else if(c == 0.0) {
     roots=1;
     z1=0.0;
   } else
     roots=0;
 } else {
   roots=2;
   pair factor=0.5*b/a;
   pair denom=b*factor;
   if(denom == 0.0) {
     z1=Sqrt(-c/a);
     z2=-z1;
   } else {
     z1=factor*sqrt1pxm1(-2.0*c/denom);
     z2=-z1-2.0*factor;
   }
 }
}

inline bool goodroot(double a, double b, double c, double t)
{
 return goodroot(t) && quadratic(a,b,c,t) >= 0.0;
}

// Accurate computation of cbrt(sqrt(1+x)+1)-cbrt(sqrt(1+x)-1).
inline double cbrtsqrt1pxm(double x)
{
 double s=sqrt1pxm1(x);
 return 2.0/(cbrt(x+2.0*(sqrt(1.0+x)+1.0))+cbrt(x)+cbrt(s*s));
}

// Taylor series of cos((atan(1.0/w)+pi)/3.0).
static inline double costhetapi3(double w)
{
 static const double c1=1.0/3.0;
 static const double c3=-19.0/162.0;
 static const double c5=425.0/5832.0;
 static const double c7=-16829.0/314928.0;
 double w2=w*w;
 double w3=w2*w;
 double w5=w3*w2;
 return c1*w+c3*w3+c5*w5+c7*w5*w2;
}

// Solve for the real roots of the cubic equation ax^3+bx^2+cx+d=0.
cubicroots::cubicroots(double a, double b, double c, double d)
{
 static const double ninth=1.0/9.0;
 static const double fiftyfourth=1.0/54.0;

 // Remove roots at numerical infinity.
 if(fabs(a) <= Fuzz2*(fabs(b)+fabs(c)*Fuzz2+fabs(d)*Fuzz4)) {
   quadraticroots q(b,c,d);
   roots=q.roots;
   if(q.roots >= 1) t1=q.t1;
   if(q.roots == 2) t2=q.t2;
   return;
 }

 // Detect roots at numerical zero.
 if(fabs(d) <= Fuzz2*(fabs(c)+fabs(b)*Fuzz2+fabs(a)*Fuzz4)) {
   quadraticroots q(a,b,c);
   roots=q.roots+1;
   t1=0;
   if(q.roots >= 1) t2=q.t1;
   if(q.roots == 2) t3=q.t2;
   return;
 }

 b /= a;
 c /= a;
 d /= a;

 double b2=b*b;
 double Q=3.0*c-b2;
 if(fabs(Q) < Fuzz2*(3.0*fabs(c)+fabs(b2)))
   Q=0.0;

 double R=(3.0*Q+b2)*b-27.0*d;
 if(fabs(R) < Fuzz2*((3.0*fabs(Q)+fabs(b2))*fabs(b)+27.0*fabs(d)))
   R=0.0;

 Q *= ninth;
 R *= fiftyfourth;

 double Q3=Q*Q*Q;
 double R2=R*R;
 double D=Q3+R2;
 double mthirdb=-b*third;

 if(D > 0.0) {
   roots=1;
   t1=mthirdb;
   if(R2 != 0.0) t1 += cbrt(R)*cbrtsqrt1pxm(Q3/R2);
 } else {
   roots=3;
   double v=0.0,theta;
   if(R2 > 0.0) {
     v=sqrt(-D/R2);
     theta=atan(v);
   } else theta=0.5*PI;
   double factor=2.0*sqrt(-Q)*(R >= 0 ? 1 : -1);

   t1=mthirdb+factor*cos(third*theta);
   t2=mthirdb-factor*cos(third*(theta-PI));
   t3=mthirdb;
   if(R2 > 0.0)
     t3 -= factor*((v < 100.0) ? cos(third*(theta+PI)) : costhetapi3(1.0/v));
 }
}

pair path::point(double t) const
{
 checkEmpty(n);

 Int i = Floor(t);
 Int iplus;
 t = fmod(t,1);
 if (t < 0) t += 1;

 if (cycles) {
   i = imod(i,n);
   iplus = imod(i+1,n);
 }
 else if (i < 0)
   return nodes[0].point;
 else if (i >= n-1)
   return nodes[n-1].point;
 else
   iplus = i+1;

 double one_t = 1.0-t;

 pair a = nodes[i].point,
   b = nodes[i].post,
   c = nodes[iplus].pre,
   d = nodes[iplus].point,
   ab   = one_t*a   + t*b,
   bc   = one_t*b   + t*c,
   cd   = one_t*c   + t*d,
   abc  = one_t*ab  + t*bc,
   bcd  = one_t*bc  + t*cd,
   abcd = one_t*abc + t*bcd;

 return abcd;
}

pair path::precontrol(double t) const
{
 checkEmpty(n);

 Int i = Floor(t);
 Int iplus;
 t = fmod(t,1);
 if (t < 0) t += 1;

 if (cycles) {
   i = imod(i,n);
   iplus = imod(i+1,n);
 }
 else if (i < 0)
   return nodes[0].pre;
 else if (i >= n-1)
   return nodes[n-1].pre;
 else
   iplus = i+1;

 double one_t = 1.0-t;

 pair a = nodes[i].point,
   b = nodes[i].post,
   c = nodes[iplus].pre,
   ab   = one_t*a   + t*b,
   bc   = one_t*b   + t*c,
   abc  = one_t*ab  + t*bc;

 return (abc == a) ? nodes[i].pre : abc;
}


pair path::postcontrol(double t) const
{
 checkEmpty(n);

 Int i = Floor(t);
 Int iplus;
 t = fmod(t,1);
 if (t < 0) t += 1;

 if (cycles) {
   i = imod(i,n);
   iplus = imod(i+1,n);
 }
 else if (i < 0)
   return nodes[0].post;
 else if (i >= n-1)
   return nodes[n-1].post;
 else
   iplus = i+1;

 double one_t = 1.0-t;

 pair b = nodes[i].post,
   c = nodes[iplus].pre,
   d = nodes[iplus].point,
   bc   = one_t*b   + t*c,
   cd   = one_t*c   + t*d,
   bcd  = one_t*bc  + t*cd;

 return (bcd == d) ? nodes[iplus].post : bcd;
}

path path::reverse() const
{
 mem::vector<solvedKnot> nodes(n);
 Int len=length();
 for (Int i = 0, j = len; i < n; i++, j--) {
   nodes[i].pre = postcontrol(j);
   nodes[i].point = point(j);
   nodes[i].post = precontrol(j);
   nodes[i].straight = straight(j-1);
 }
 return path(nodes, n, cycles);
}

path path::subpath(Int a, Int b) const
{
 if(empty()) return path();

 if (a > b) {
   const path &rp = reverse();
   Int len=length();
   path result = rp.subpath(len-a, len-b);
   return result;
 }

 if (!cycles) {
   if (a < 0) {
     a = 0;
     if(b < 0)
       b = 0;
   }
   if (b > n-1) {
     b = n-1;
     if(a > b)
       a = b;
   }
 }

 Int sn = b-a+1;
 mem::vector<solvedKnot> nodes(sn);

 for (Int i = 0, j = a; j <= b; i++, j++) {
   nodes[i].pre = precontrol(j);
   nodes[i].point = point(j);
   nodes[i].post = postcontrol(j);
   nodes[i].straight = straight(j);
 }
 nodes[0].pre = nodes[0].point;
 nodes[sn-1].post = nodes[sn-1].point;

 return path(nodes, sn);
}

inline pair split(double t, const pair& x, const pair& y) { return x+(y-x)*t; }

inline void splitCubic(solvedKnot sn[], double t, const solvedKnot& left_,
                      const solvedKnot& right_)
{
 solvedKnot &left=(sn[0]=left_), &mid=sn[1], &right=(sn[2]=right_);
 if(left.straight) {
   mid.point=split(t,left.point,right.point);
   pair deltaL=third*(mid.point-left.point);
   left.post=left.point+deltaL;
   mid.pre=mid.point-deltaL;
   pair deltaR=third*(right.point-mid.point);
   mid.post=mid.point+deltaR;
   right.pre=right.point-deltaR;
   mid.straight=true;
 } else {
   pair x=split(t,left.post,right.pre); // m1
   left.post=split(t,left.point,left.post); // m0
   right.pre=split(t,right.pre,right.point); // m2
   mid.pre=split(t,left.post,x); // m3
   mid.post=split(t,x,right.pre); // m4
   mid.point=split(t,mid.pre,mid.post); // m5
 }
}

path path::subpath(double a, double b) const
{
 if(empty()) return path();

 if (a > b) {
   const path &rp = reverse();
   Int len=length();
   return rp.subpath(len-a, len-b);
 }

 solvedKnot aL, aR, bL, bR;
 if (!cycles) {
   if (a < 0) {
     a = 0;
     if (b < 0)
       b = 0;
   }
   if (b > n-1) {
     b = n-1;
     if (a > b)
       a = b;
   }
   aL = nodes[(Int)floor(a)];
   aR = nodes[(Int)ceil(a)];
   bL = nodes[(Int)floor(b)];
   bR = nodes[(Int)ceil(b)];
 } else {
   if(run::validInt(a) && run::validInt(b)) {
     aL = nodes[imod((Int) floor(a),n)];
     aR = nodes[imod((Int) ceil(a),n)];
     bL = nodes[imod((Int) floor(b),n)];
     bR = nodes[imod((Int) ceil(b),n)];
   } else reportError("invalid path index");
 }

 if (a == b) return path(point(a));

 solvedKnot sn[3];
 path p = subpath(Ceil(a), Floor(b));
 if (a > floor(a)) {
   if (b < ceil(a)) {
     splitCubic(sn,a-floor(a),aL,aR);
     splitCubic(sn,(b-a)/(ceil(b)-a),sn[1],sn[2]);
     return path(sn[0],sn[1]);
   }
   splitCubic(sn,a-floor(a),aL,aR);
   p=concat(path(sn[1],sn[2]),p);
 }
 if (ceil(b) > b) {
   splitCubic(sn,b-floor(b),bL,bR);
   p=concat(p,path(sn[0],sn[1]));
 }
 return p;
}

// Special case of subpath for paths of length 1 used by intersect.
void path::halve(path &first, path &second) const
{
 solvedKnot sn[3];
 splitCubic(sn,0.5,nodes[0],nodes[1]);
 first=path(sn[0],sn[1]);
 second=path(sn[1],sn[2]);
}

// Calculate the coefficients of a Bezier derivative divided by 3.
static inline void derivative(pair& a, pair& b, pair& c,
                             const pair& z0, const pair& c0,
                             const pair& c1, const pair& z1)
{
 a=z1-z0+3.0*(c0-c1);
 b=2.0*(z0+c1)-4.0*c0;
 c=c0-z0;
}

bbox path::bounds() const
{
 if(!box.empty) return box;

 if (empty()) {
   // No bounds
   return bbox();
 }

 Int len=length();
 box.add(point(len));
 times=bbox(len,len,len,len);

 for (Int i = 0; i < len; i++) {
   addpoint(box,i);
   if(straight(i)) continue;

   pair a,b,c;
   derivative(a,b,c,point(i),postcontrol(i),precontrol(i+1),point(i+1));

   // Check x coordinate
   quadraticroots x(a.getx(),b.getx(),c.getx());
   if(x.distinct != quadraticroots::NONE && goodroot(x.t1))
     addpoint(box,i+x.t1);
   if(x.distinct == quadraticroots::TWO && goodroot(x.t2))
     addpoint(box,i+x.t2);

   // Check y coordinate
   quadraticroots y(a.gety(),b.gety(),c.gety());
   if(y.distinct != quadraticroots::NONE && goodroot(y.t1))
     addpoint(box,i+y.t1);
   if(y.distinct == quadraticroots::TWO && goodroot(y.t2))
     addpoint(box,i+y.t2);
 }
 return box;
}

bbox path::bounds(double min, double max) const
{
 bbox box;

 Int len=length();
 for (Int i = 0; i < len; i++) {
   addpoint(box,i,min,max);
   if(straight(i)) continue;

   pair a,b,c;
   derivative(a,b,c,point(i),postcontrol(i),precontrol(i+1),point(i+1));

   // Check x coordinate
   quadraticroots x(a.getx(),b.getx(),c.getx());
   if(x.distinct != quadraticroots::NONE && goodroot(x.t1))
     addpoint(box,i+x.t1,min,max);

   if(x.distinct == quadraticroots::TWO && goodroot(x.t2))
     addpoint(box,i+x.t2,min,max);

   // Check y coordinate
   quadraticroots y(a.gety(),b.gety(),c.gety());
   if(y.distinct != quadraticroots::NONE && goodroot(y.t1))
     addpoint(box,i+y.t1,min,max);
   if(y.distinct == quadraticroots::TWO && goodroot(y.t2))
     addpoint(box,i+y.t2,min,max);
 }
 addpoint(box,len,min,max);
 return box;
}

inline void add(bbox& box, const pair& z, const pair& min, const pair& max)
{
 box += z+min;
 box += z+max;
}

bbox path::internalbounds(const bbox& padding) const
{
 bbox box;

 // Check interior nodes.
 Int len=length();

 // Check interior segments.
 for (Int i = 0; i < len; i++) {
   if(straight(i)) continue;

   pair a,b,c;
   derivative(a,b,c,point(i),postcontrol(i),precontrol(i+1),point(i+1));

   // Check x coordinate
   quadraticroots x(a.getx(),b.getx(),c.getx());
   if(x.distinct != quadraticroots::NONE && goodroot(x.t1))
     add(box,point(i+x.t1),padding.left,padding.right);
   if(x.distinct == quadraticroots::TWO && goodroot(x.t2))
     add(box,point(i+x.t2),padding.left,padding.right);

   // Check y coordinate
   quadraticroots y(a.gety(),b.gety(),c.gety());
   if(y.distinct != quadraticroots::NONE && goodroot(y.t1))
     add(box,point(i+y.t1),pair(0,padding.bottom),pair(0,padding.top));
   if(y.distinct == quadraticroots::TWO && goodroot(y.t2))
     add(box,point(i+y.t2),pair(0,padding.bottom),pair(0,padding.top));
 }
 return box;
}

// {{{ Arclength Calculations

static pair a,b,c;

static double ds(double t)
{
 double dx=quadratic(a.getx(),b.getx(),c.getx(),t);
 double dy=quadratic(a.gety(),b.gety(),c.gety(),t);
 return sqrt(dx*dx+dy*dy);
}

// Calculates arclength of a cubic Bezier curve using adaptive Simpson
// integration.
double arcLength(const pair& z0, const pair& c0, const pair& c1,
                const pair& z1)
{
 double integral;
 derivative(a,b,c,z0,c0,c1,z1);

 if(!simpson(integral,ds,0.0,1.0,DBL_EPSILON,1.0))
   reportError("nesting capacity exceeded in computing arclength");
 return integral;
}

double path::cubiclength(Int i, double goal) const
{
 const pair& z0=point(i);
 const pair& z1=point(i+1);
 double L;
 if(straight(i)) {
   L=(z1-z0).length();
   return (goal < 0 || goal >= L) ? L : -goal/L;
 }

 double integral=arcLength(z0,postcontrol(i),precontrol(i+1),z1);

 L=3.0*integral;
 if(goal < 0 || goal >= L) return L;

 double t=goal/L;
 goal *= third;
 static double dxmin=sqrt(DBL_EPSILON);
 if(!unsimpson(goal,ds,0.0,t,100.0*DBL_EPSILON,integral,1.0,dxmin))
   reportError("nesting capacity exceeded in computing arctime");
 return -t;
}

double path::arclength() const
{
 if (cached_length != -1) return cached_length;

 double L=0.0;
 for (Int i = 0; i < n-1; i++) {
   L += cubiclength(i);
 }
 if(cycles) L += cubiclength(n-1);
 cached_length = L;
 return cached_length;
}

double path::arctime(double goal) const
{
 if (cycles) {
   if (goal == 0 || cached_length == 0) return 0;
   if (goal < 0)  {
     const path &rp = this->reverse();
     double result = -rp.arctime(-goal);
     return result;
   }
   if (cached_length > 0 && goal >= cached_length) {
     Int loops = (Int)(goal / cached_length);
     goal -= loops*cached_length;
     return loops*n+arctime(goal);
   }
 } else {
   if (goal <= 0)
     return 0;
   if (cached_length > 0 && goal >= cached_length)
     return n-1;
 }

 double l,L=0;
 for (Int i = 0; i < n-1; i++) {
   l = cubiclength(i,goal);
   if (l < 0)
     return (-l+i);
   else {
     L += l;
     goal -= l;
     if (goal <= 0)
       return i+1;
   }
 }
 if (cycles) {
   l = cubiclength(n-1,goal);
   if (l < 0)
     return -l+n-1;
   if (cached_length > 0 && cached_length != L+l) {
     reportError("arclength != length.\n"
                 "path::arclength(double) must have broken semantics.\n"
                 "Please report this error.");
   }
   cached_length = L += l;
   goal -= l;
   return arctime(goal)+n;
 }
 else {
   cached_length = L;
   return length();
 }
}

// }}}

// {{{ Direction Time Calulation
// Algorithm Stolen from Knuth's MetaFont
inline double cubicDir(const solvedKnot& left, const solvedKnot& right,
                      const pair& rot)
{
 pair a,b,c;
 derivative(a,b,c,left.point,left.post,right.pre,right.point);
 a *= rot; b *= rot; c *= rot;

 quadraticroots ret(a.gety(),b.gety(),c.gety());
 switch(ret.distinct) {
   case quadraticroots::MANY:
   case quadraticroots::ONE:
   {
     if(goodroot(a.getx(),b.getx(),c.getx(),ret.t1)) return ret.t1;
   } break;

   case quadraticroots::TWO:
   {
     if(goodroot(a.getx(),b.getx(),c.getx(),ret.t1)) return ret.t1;
     if(goodroot(a.getx(),b.getx(),c.getx(),ret.t2)) return ret.t2;
   } break;

   case quadraticroots::NONE:
     break;
 }

 return -1;
}

// TODO: Check that we handle corner cases.
// Velocity(t) == (0,0)
double path::directiontime(const pair& dir) const {
 if (dir == pair(0,0)) return 0;
 pair rot = pair(1,0)/unit(dir);

 double t; double pre,post;
 for (Int i = 0; i < n-1+cycles; ) {
   t = cubicDir(this->nodes[i],(cycles && i==n-1) ? nodes[0]:nodes[i+1],rot);
   if (t >= 0) return i+t;
   i++;
   if (cycles || i != n-1) {
     pair Pre = (point(i)-precontrol(i))*rot;
     pair Post = (postcontrol(i)-point(i))*rot;
     static pair zero(0.0,0.0);
     if(Pre != zero && Post != zero) {
       pre = angle(Pre);
       post = angle(Post);
       if ((pre <= 0 && post >= 0 && pre >= post - PI) ||
           (pre >= 0 && post <= 0 && pre <= post + PI))
         return i;
     }
   }
 }

 return -1;
}
// }}}

// {{{ Path Intersection Calculations

const unsigned maxdepth=DBL_MANT_DIG;
const unsigned mindepth=maxdepth-16;

void roots(std::vector<double> &roots, double a, double b, double c, double d)
{
 cubicroots r(a,b,c,d);
 if(r.roots >= 1) roots.push_back(r.t1);
 if(r.roots >= 2) roots.push_back(r.t2);
 if(r.roots == 3) roots.push_back(r.t3);
}

void roots(std::vector<double> &r, double x0, double c0, double c1, double x1,
          double x)
{
 double a=x1-x0+3.0*(c0-c1);
 double b=3.0*(x0+c1)-6.0*c0;
 double c=3.0*(c0-x0);
 double d=x0-x;
 roots(r,a,b,c,d);
}

// Return all intersection times of path g with the pair z.
void intersections(std::vector<double>& T, const path& g, const pair& z,
                  double fuzz)
{
 double fuzz2=fuzz*fuzz;
 Int n=g.length();
 bool cycles=g.cyclic();
 for(Int i=0; i < n; ++i) {
   // Check both directions to circumvent degeneracy.
   std::vector<double> r;
   roots(r,g.point(i).getx(),g.postcontrol(i).getx(),
         g.precontrol(i+1).getx(),g.point(i+1).getx(),z.getx());
   roots(r,g.point(i).gety(),g.postcontrol(i).gety(),
         g.precontrol(i+1).gety(),g.point(i+1).gety(),z.gety());

   size_t m=r.size();
   for(size_t j=0 ; j < m; ++j) {
     double t=r[j];
     if(t >= -Fuzz2 && t <= 1.0+Fuzz2) {
       double s=i+t;
       if((g.point(s)-z).abs2() <= fuzz2) {
         if(cycles && s >= n-Fuzz2) s=0;
         T.push_back(s);
       }
     }
   }
 }
}

inline bool online(const pair&p, const pair& q, const pair& z, double fuzz)
{
 if(p == q) return (z-p).abs2() <= fuzz*fuzz;
 return (z.getx()-p.getx())*(q.gety()-p.gety()) ==
   (q.getx()-p.getx())*(z.gety()-p.gety());
}

// Return all intersection times of path g with the (infinite)
// line through p and q; if there are an infinite number of intersection points,
// the returned list is guaranteed to include the endpoint times of
// the intersection if endpoints=true.
void lineintersections(std::vector<double>& T, const path& g,
                      const pair& p, const pair& q, double fuzz,
                      bool endpoints=false)
{
 Int n=g.length();
 if(n == 0) {
   if(online(p,q,g.point((Int) 0),fuzz)) T.push_back(0.0);
   return;
 }
 bool cycles=g.cyclic();
 double dx=q.getx()-p.getx();
 double dy=q.gety()-p.gety();
 double det=p.gety()*q.getx()-p.getx()*q.gety();
 for(Int i=0; i < n; ++i) {
   pair z0=g.point(i);
   pair c0=g.postcontrol(i);
   pair c1=g.precontrol(i+1);
   pair z1=g.point(i+1);
   pair t3=z1-z0+3.0*(c0-c1);
   pair t2=3.0*(z0+c1)-6.0*c0;
   pair t1=3.0*(c0-z0);
   double a=dy*t3.getx()-dx*t3.gety();
   double b=dy*t2.getx()-dx*t2.gety();
   double c=dy*t1.getx()-dx*t1.gety();
   double d=dy*z0.getx()-dx*z0.gety()+det;
   std::vector<double> r;
   if(max(max(max(a*a,b*b),c*c),d*d) >
      Fuzz4*max(max(max(z0.abs2(),z1.abs2()),c0.abs2()),c1.abs2()))
     roots(r,a,b,c,d);
   else r.push_back(0.0);
   if(endpoints) {
     path h=g.subpath(i,i+1);
     intersections(r,h,p,fuzz);
     intersections(r,h,q,fuzz);
     if(online(p,q,z0,fuzz)) r.push_back(0.0);
     if(online(p,q,z1,fuzz)) r.push_back(1.0);
   }
   size_t m=r.size();
   for(size_t j=0 ; j < m; ++j) {
     double t=r[j];
     if(t >= -Fuzz2 && t <= 1.0+Fuzz2) {
       double s=i+t;
       if(cycles && s >= n-Fuzz2) s=0;
       T.push_back(s);
     }
   }
 }
}

// An optimized implementation of intersections(g,p--q);
// if there are an infinite number of intersection points, the returned list is
// only guaranteed to include the endpoint times of the intersection.
void intersections(std::vector<double>& S, std::vector<double>& T,
                  const path& g, const pair& p, const pair& q, double fuzz)
{
 double length2=(q-p).abs2();
 if(length2 == 0.0) {
   std::vector<double> S1;
   intersections(S1,g,p,fuzz);
   size_t n=S1.size();
   for(size_t i=0; i < n; ++i) {
     S.push_back(S1[i]);
     T.push_back(0.0);
   }
 } else {
   pair factor=(q-p)/length2;
   std::vector<double> S1;
   lineintersections(S1,g,p,q,fuzz,true);
   size_t n=S1.size();
   for(size_t i=0; i < n; ++i) {
     double s=S1[i];
     double t=dot(g.point(s)-p,factor);
     if(t >= -Fuzz2 && t <= 1.0+Fuzz2) {
       S.push_back(s);
       T.push_back(t);
     }
   }
 }
}

void add(std::vector<double>& S, double s, const path& p, double fuzz2)
{
 pair z=p.point(s);
 size_t n=S.size();
 for(size_t i=0; i < n; ++i)
   if((p.point(S[i])-z).abs2() <= fuzz2) return;
 S.push_back(s);
}

void add(std::vector<double>& S, std::vector<double>& T, double s, double t,
        const path& p, double fuzz2)
{
 pair z=p.point(s);
 size_t n=S.size();
 for(size_t i=0; i < n; ++i)
   if((p.point(S[i])-z).abs2() <= fuzz2) return;
 S.push_back(s);
 T.push_back(t);
}

void add(double& s, double& t, std::vector<double>& S, std::vector<double>& T,
        std::vector<double>& S1, std::vector<double>& T1,
        double pscale, double qscale, double poffset, double qoffset,
        const path& p, double fuzz2, bool single)
{
 if(single) {
   s=s*pscale+poffset;
   t=t*qscale+qoffset;
 } else {
   size_t n=S1.size();
   for(size_t i=0; i < n; ++i)
     add(S,T,pscale*S1[i]+poffset,qscale*T1[i]+qoffset,p,fuzz2);
 }
}

void add(double& s, double& t, std::vector<double>& S, std::vector<double>& T,
        std::vector<double>& S1, std::vector<double>& T1,
        const path& p, double fuzz2, bool single)
{
 size_t n=S1.size();
 if(single) {
   if(n > 0) {
     s=S1[0];
     t=T1[0];
   }
 } else {
   for(size_t i=0; i < n; ++i)
     add(S,T,S1[i],T1[i],p,fuzz2);
 }
}

void intersections(std::vector<double>& S, path& g,
                  const pair& p, const pair& q, double fuzz)
{
 double fuzz2=max(fuzzFactor*fuzz*fuzz,Fuzz2);
 std::vector<double> S1;
 lineintersections(S1,g,p,q,fuzz);
 size_t n=S1.size();
 for(size_t i=0; i < n; ++i)
   add(S,S1[i],g,fuzz2);
}

bool intersections(double &s, double &t, std::vector<double>& S,
                  std::vector<double>& T, path& p, path& q,
                  double fuzz, bool single, bool exact, unsigned depth)
{
 if(errorstream::interrupt) throw interrupted();

 double fuzz2=max(fuzzFactor*fuzz*fuzz,Fuzz2);

 Int lp=p.length();
 if(((lp == 1 && p.straight(0)) || lp == 0) && exact) {
   std::vector<double> T1,S1;
   intersections(T1,S1,q,p.point((Int) 0),p.point(lp),fuzz);
   add(s,t,S,T,S1,T1,p,fuzz2,single);
   return S1.size() > 0;
 }

 Int lq=q.length();
 if(((lq == 1 && q.straight(0)) || lq == 0) && exact) {
   std::vector<double> S1,T1;
   intersections(S1,T1,p,q.point((Int) 0),q.point(lq),fuzz);
   add(s,t,S,T,S1,T1,p,fuzz2,single);
   return S1.size() > 0;
 }

 pair maxp=p.max();
 pair minp=p.min();
 pair maxq=q.max();
 pair minq=q.min();

 if(maxp.getx()+fuzz >= minq.getx() &&
    maxp.gety()+fuzz >= minq.gety() &&
    maxq.getx()+fuzz >= minp.getx() &&
    maxq.gety()+fuzz >= minp.gety()) {
   // Overlapping bounding boxes

   --depth;
//    fuzz *= 2;
//    fuzz2=max(fuzzFactor*fuzz*fuzz,Fuzz2);

   if((maxp-minp).length()+(maxq-minq).length() <= fuzz || depth == 0) {
     if(single) {
       s=0.5;
       t=0.5;
     } else {
       S.push_back(0.5);
       T.push_back(0.5);
     }
     return true;
   }

   path p1,p2;
   double pscale,poffset;
   std::vector<double> S1,T1;

   if(lp <= 1) {
     if(lp == 1) p.halve(p1,p2);
     if(lp == 0 || p1 == p || p2 == p) {
       intersections(T1,S1,q,p.point((Int) 0),p.point((Int) 0),fuzz);
       add(s,t,S,T,S1,T1,p,fuzz2,single);
       return S1.size() > 0;
     }
     pscale=poffset=0.5;
   } else {
     Int tp=lp/2;
     p1=p.subpath(0,tp);
     p2=p.subpath(tp,lp);
     poffset=tp;
     pscale=1.0;
   }

   path q1,q2;
   double qscale,qoffset;

   if(lq <= 1) {
     if(lq == 1) q.halve(q1,q2);
     if(lq == 0 || q1 == q || q2 == q) {
       intersections(S1,T1,p,q.point((Int) 0),q.point((Int) 0),fuzz);
       add(s,t,S,T,S1,T1,p,fuzz2,single);
       return S1.size() > 0;
     }
     qscale=qoffset=0.5;
   } else {
     Int tq=lq/2;
     q1=q.subpath(0,tq);
     q2=q.subpath(tq,lq);
     qoffset=tq;
     qscale=1.0;
   }

   bool Short=lp == 1 && lq == 1;

   static size_t maxcount=9;
   size_t count=0;

   if(intersections(s,t,S1,T1,p1,q1,fuzz,single,exact,depth)) {
     add(s,t,S,T,S1,T1,pscale,qscale,0.0,0.0,p,fuzz2,single);
     if(single || depth <= mindepth)
       return true;
     count += S1.size();
     if(Short && count > maxcount) return true;
   }

   S1.clear();
   T1.clear();
   if(intersections(s,t,S1,T1,p1,q2,fuzz,single,exact,depth)) {
     add(s,t,S,T,S1,T1,pscale,qscale,0.0,qoffset,p,fuzz2,single);
     if(single || depth <= mindepth)
       return true;
     count += S1.size();
     if(Short && count > maxcount) return true;
   }

   S1.clear();
   T1.clear();
   if(intersections(s,t,S1,T1,p2,q1,fuzz,single,exact,depth)) {
     add(s,t,S,T,S1,T1,pscale,qscale,poffset,0.0,p,fuzz2,single);
     if(single || depth <= mindepth)
       return true;
     count += S1.size();
     if(Short && count > maxcount) return true;
   }

   S1.clear();
   T1.clear();
   if(intersections(s,t,S1,T1,p2,q2,fuzz,single,exact,depth)) {
     add(s,t,S,T,S1,T1,pscale,qscale,poffset,qoffset,p,fuzz2,single);
     if(single || depth <= mindepth)
       return true;
     count += S1.size();
     if(Short && count > maxcount) return true;
   }

   return S.size() > 0;
 }
 return false;
}

// }}}

ostream& operator<< (ostream& out, const path& p)
{
 Int n = p.length();
 if(n < 0)
   out << "<nullpath>";
 else {
   for(Int i = 0; i < n; i++) {
     out << p.point(i);
     if(p.straight(i)) out << "--";
     else
       out << ".. controls " << p.postcontrol(i) << " and "
           << p.precontrol(i+1) << newl << " ..";
   }
   if(p.cycles)
     out << "cycle";
   else
     out << p.point(n);
 }
 return out;
}

path concat(const path& p1, const path& p2)
{
 Int n1 = p1.length(), n2 = p2.length();

 if (n1 == -1) return p2;
 if (n2 == -1) return p1;

 mem::vector<solvedKnot> nodes(n1+n2+1);

 Int i = 0;
 nodes[0].pre = p1.point((Int) 0);
 for (Int j = 0; j < n1; j++) {
   nodes[i].point = p1.point(j);
   nodes[i].straight = p1.straight(j);
   nodes[i].post = p1.postcontrol(j);
   nodes[i+1].pre = p1.precontrol(j+1);
   i++;
 }
 for (Int j = 0; j < n2; j++) {
   nodes[i].point = p2.point(j);
   nodes[i].straight = p2.straight(j);
   nodes[i].post = p2.postcontrol(j);
   nodes[i+1].pre = p2.precontrol(j+1);
   i++;
 }
 nodes[i].point = nodes[i].post = p2.point(n2);

 return path(nodes, i+1);
}

// Interface to orient2d predicate optimized for pairs.
double orient2d(const pair& a, const pair& b, const pair& c)
{
 double detleft, detright, det;
 double detsum, errbound;
 double orient;

 FPU_ROUND_DOUBLE;

 detleft = (a.getx() - c.getx()) * (b.gety() - c.gety());
 detright = (a.gety() - c.gety()) * (b.getx() - c.getx());
 det = detleft - detright;

 if (detleft > 0.0) {
   if (detright <= 0.0) {
     FPU_RESTORE;
     return det;
   } else {
     detsum = detleft + detright;
   }
 } else if (detleft < 0.0) {
   if (detright >= 0.0) {
     FPU_RESTORE;
     return det;
   } else {
     detsum = -detleft - detright;
   }
 } else {
   FPU_RESTORE;
   return det;
 }

 errbound = ccwerrboundA * detsum;
 if ((det >= errbound) || (-det >= errbound)) {
   FPU_RESTORE;
   return det;
 }

 double pa[]={a.getx(),a.gety()};
 double pb[]={b.getx(),b.gety()};
 double pc[]={c.getx(),c.gety()};

 orient = orient2dadapt(pa, pb, pc, detsum);
 FPU_RESTORE;
 return orient;
}

// Returns true iff the point z lies in or on the bounding box
// of a,b,c, and d.
bool insidebbox(const pair& a, const pair& b, const pair& c, const pair& d,
               const pair& z)
{
 bbox B(a);
 B.addnonempty(b);
 B.addnonempty(c);
 B.addnonempty(d);
 return B.left <= z.getx() && z.getx() <= B.right && B.bottom <= z.gety()
   && z.gety() <= B.top;
}

inline bool inrange(double x0, double x1, double x)
{
 return (x0 <= x && x <= x1) || (x1 <= x && x <= x0);
}

// Return true if point z is on z0--z1; otherwise compute contribution to
// winding number.
bool checkstraight(const pair& z0, const pair& z1, const pair& z, Int& count)
{
 if(z0.gety() <= z.gety() && z.gety() <= z1.gety()) {
   double side=orient2d(z0,z1,z);
   if(side == 0.0 && inrange(z0.getx(),z1.getx(),z.getx()))
     return true;
   if(z.gety() < z1.gety() && side > 0) ++count;
 } else if(z1.gety() <= z.gety() && z.gety() <= z0.gety()) {
   double side=orient2d(z0,z1,z);
   if(side == 0.0 && inrange(z0.getx(),z1.getx(),z.getx()))
     return true;
   if(z.gety() < z0.gety() && side < 0) --count;
 }
 return false;
}

// returns true if point is on curve; otherwise compute contribution to
// winding number.
bool checkcurve(const pair& z0, const pair& c0, const pair& c1,
               const pair& z1, const pair& z, Int& count, unsigned depth)
{
 if(depth == 0) return true;
 --depth;

 if(insidebbox(z0,c0,c1,z1,z)) {
   const pair m0=0.5*(z0+c0);
   const pair m1=0.5*(c0+c1);
   const pair m2=0.5*(c1+z1);
   const pair m3=0.5*(m0+m1);
   const pair m4=0.5*(m1+m2);
   const pair m5=0.5*(m3+m4);
   if(checkcurve(z0,m0,m3,m5,z,count,depth) ||
      checkcurve(m5,m4,m2,z1,z,count,depth)) return true;
 } else
   if(checkstraight(z0,z1,z,count)) return true;
 return false;
}

// Return the winding number of the region bounded by the (cyclic) path
// relative to the point z, or the largest odd integer if the point lies on
// the path.
Int path::windingnumber(const pair& z) const
{
 static const Int undefined=Int_MAX % 2 ? Int_MAX : Int_MAX-1;

 if(!cycles)
   reportError("path is not cyclic");

 bbox b=bounds();

 if(z.getx() < b.left || z.getx() > b.right ||
    z.gety() < b.bottom || z.gety() > b.top) return 0;

 Int count=0;
 for(Int i=0; i < n; ++i)
   if(straight(i)) {
     if(checkstraight(point(i),point(i+1),z,count))
       return undefined;
   } else
     if(checkcurve(point(i),postcontrol(i),precontrol(i+1),point(i+1),z,count,
                   maxdepth)) return undefined;
 return count;
}

path path::transformed(const transform& t) const
{
 mem::vector<solvedKnot> nodes(n);

 for (Int i = 0; i < n; ++i) {
   nodes[i].pre = t * this->nodes[i].pre;
   nodes[i].point = t * this->nodes[i].point;
   nodes[i].post = t * this->nodes[i].post;
   nodes[i].straight = this->nodes[i].straight;
 }

 path p(nodes, n, cyclic());
 return p;
}

path transformed(const transform& t, const path& p)
{
 Int n = p.size();
 mem::vector<solvedKnot> nodes(n);

 for (Int i = 0; i < n; ++i) {
   nodes[i].pre = t * p.precontrol(i);
   nodes[i].point = t * p.point(i);
   nodes[i].post = t * p.postcontrol(i);
   nodes[i].straight = p.straight(i);
 }

 return path(nodes, n, p.cyclic());
}

path nurb(pair z0, pair z1, pair z2, pair z3,
         double w0, double w1, double w2, double w3, Int m)
{
 mem::vector<solvedKnot> nodes(m+1);

 if(m < 1) reportError("invalid sampling interval");

 double step=1.0/m;
 for(Int i=0; i <= m; ++i) {
   double t=i*step;
   double t2=t*t;
   double onemt=1.0-t;
   double onemt2=onemt*onemt;
   double W0=w0*onemt2*onemt;
   double W1=w1*3.0*t*onemt2;
   double W2=w2*3.0*t2*onemt;
   double W3=w3*t2*t;
   nodes[i].point=(W0*z0+W1*z1+W2*z2+W3*z3)/(W0+W1+W2+W3);
 }

 static const double twothirds=2.0/3.0;
 pair z=nodes[0].point;
 nodes[0].pre=z;
 nodes[0].post=twothirds*z+third*nodes[1].point;
 for(int i=1; i < m; ++i) {
   pair z0=nodes[i].point;
   pair zm=nodes[i-1].point;
   pair zp=nodes[i+1].point;
   pair pre=twothirds*z0+third*zm;
   pair pos=twothirds*z0+third*zp;
   pair dir=unit(pos-pre);
   nodes[i].pre=z0-length(z0-pre)*dir;
   nodes[i].post=z0+length(pos-z0)*dir;
 }
 z=nodes[m].point;
 nodes[m].pre=twothirds*z+third*nodes[m-1].point;
 nodes[m].post=z;
 return path(nodes,m+1);
}

} //namespace camp