/*****
* 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