struct rmf {
triple p,r,t,s;
void operator init(triple p, triple r, triple t) {
this.p=p;
this.r=r;
this.t=t;
s=cross(t,r);
}
transform3 transform() {
return transform3(r,s,t);
}
}
// Rotation minimizing frame
//
http://www.cs.hku.hk/research/techreps/document/TR-2007-07.pdf
rmf[] rmf(path3 g, real[] t, triple perp=O)
{
triple T=dir(g,0);
triple Tp=abs(perp) < sqrtEpsilon ? perp(T) : unit(perp);
rmf[] R=new rmf[t.length];
R[0]=rmf(point(g,0),Tp,T);
for(int i=1; i < t.length; ++i) {
rmf Ri=R[i-1];
real t=t[i];
triple p=point(g,t);
triple v1=p-Ri.p;
if(v1 != O) {
triple r=Ri.r;
triple u1=unit(v1);
triple ti=Ri.t;
triple tp=ti-2*dot(u1,ti)*u1;
ti=dir(g,t);
triple rp=r-2*dot(u1,r)*u1;
triple u2=unit(ti-tp);
rp=rp-2*dot(u2,rp)*u2;
R[i]=rmf(p,unit(rp),unit(ti));
} else
R[i]=R[i-1];
}
return R;
}
rmf[] rmf(triple z0, triple c0, triple c1, triple z1, real[] t, triple perp=O)
{
static triple s0;
real norm=sqrtEpsilon*max(abs(z0),abs(c0),abs(c1),abs(z1));
// Special case of dir for t in (0,1].
triple dir(real t) {
if(t == 1) {
triple dir=z1-c1;
if(abs(dir) > norm) return unit(dir);
dir=2.0*c1-c0-z1;
if(abs(dir) > norm) return unit(dir);
return unit(z1-z0+3.0*(c0-c1));
}
triple a=z1-z0+3.0*(c0-c1);
triple b=2.0*(z0+c1)-4.0*c0;
triple c=c0-z0;
triple dir=a*t*t+b*t+c;
if(abs(dir) > norm) return unit(dir);
dir=2.0*a*t+b;
if(abs(dir) > norm) return unit(dir);
return unit(a);
}
triple T=c0-z0;
if(abs(T) < norm) {
T=z0-2*c0+c1;
if(abs(T) < norm)
T=z1-z0+3.0*(c0-c1);
}
T=unit(T);
triple Tp=perp == O ? cross(s0,T) : perp;
Tp=abs(Tp) < sqrtEpsilon ? perp(T) : unit(Tp);
rmf[] R=new rmf[t.length];
R[0]=rmf(z0,Tp,T);
for(int i=1; i < t.length; ++i) {
rmf Ri=R[i-1];
real t=t[i];
triple p=bezier(z0,c0,c1,z1,t);
triple v1=p-Ri.p;
if(v1 != O) {
triple r=Ri.r;
triple u1=unit(v1);
triple ti=Ri.t;
triple tp=ti-2*dot(u1,ti)*u1;
ti=dir(t);
triple rp=r-2*dot(u1,r)*u1;
triple u2=unit(ti-tp);
rp=rp-2*dot(u2,rp)*u2;
R[i]=rmf(p,unit(rp),unit(ti));
} else
R[i]=R[i-1];
}
s0=R[t.length-1].s;
return R;
}
drawfcn drawTube(triple[] g, real w, triple min, triple max) {
return new void(frame f, transform3 t=identity4, material[] m,
light light=currentlight, render render=defaultrender)
{
material m=material(m[0],light);
drawTube(f,t*g,w,m.p,m.opacity,m.shininess,m.metallic,m.fresnel0,
t*min,t*max,m.opacity == 1);
};
}
surface tube(triple z0, triple c0, triple c1, triple z1, real w)
{
surface s;
static real[] T={0,1/3,2/3,1};
rmf[] rmf=rmf(z0,c0,c1,z1,T);
real aw=a*w;
triple[] arc={(w,0,0),(w,aw,0),(aw,w,0),(0,w,0)};
triple[] g={z0,c0,c1,z1};
void f(transform3 R) {
triple[][] P=new triple[4][];
for(int i=0; i < 4; ++i) {
transform3 T=shift(g[i])*rmf[i].transform()*R;
P[i]=new triple[] {T*arc[0],T*arc[1],T*arc[2],T*arc[3]};
}
s.push(patch(P,copy=false));
}
f(identity4);
f(t1);
f(t2);
f(t3);
s.PRCprimitive=false;
s.primitive=primitive(drawTube(g,w,min(s),max(s)),
new bool(transform3 t) {
return unscaled(t,X,Y);
});
return s;
}
real tubethreshold=20;
// Note: casting an array of surfaces to a single surface will disable
// primitive compression.
surface operator cast(surface[] s) {
surface S;
for(surface p : s)
S.append(p);
return S;
}
struct tube
{
surface[] s;
path3 center; // tube axis
void Null(transform3) {}
void Null(transform3, bool) {}
surface[] render(path3 g, real r) {
triple z0=point(g,0);
triple c0=postcontrol(g,0);
triple c1=precontrol(g,1);
triple z1=point(g,1);
real norm=sqrtEpsilon*max(abs(z0),abs(c0),abs(c1),abs(z1),r);
surface[] s;
void Split(triple z0, triple c0, triple c1, triple z1,
int depth=mantissaBits) {
if(depth > 0) {
pair threshold(triple z0, triple c0, triple c1) {
triple u=c1-z0;
triple v=c0-z0;
real x=abs(v);
return (x,abs(u*x^2-dot(u,v)*v));
}
pair a0=threshold(z0,c0,c1);
pair a1=threshold(z1,c1,c0);
real rL=r*arclength(z0,c0,c1,z1)*tubethreshold;
if((a0.x >= norm && rL*a0.y^2 > a0.x^8) ||
(a1.x >= norm && rL*a1.y^2 > a1.x^8)) {
triple m0=0.5*(z0+c0);
triple m1=0.5*(c0+c1);
triple m2=0.5*(c1+z1);
triple m3=0.5*(m0+m1);
triple m4=0.5*(m1+m2);
triple m5=0.5*(m3+m4);
--depth;
Split(z0,m0,m3,m5,depth);
Split(m5,m4,m2,z1,depth);
return;
}
}
s.push(tube(z0,c0,c1,z1,r));
}
Split(z0,c0,c1,z1);
return s;
}
void operator init(path3 p, real width) {
center=p;
real r=0.5*width;
void generate(path3 p) {
int n=length(p);
for(int i=0; i < n; ++i) {
if(straight(p,i)) {
triple v=point(p,i);
triple u=point(p,i+1)-v;
transform3 t=shift(v)*align(unit(u))*scale(r,r,abs(u));
// Draw opaque surfaces with core for better small-scale rendering.
surface unittube=t*unitcylinder;
unittube.primitive=primitive(unitcylinderDraw(core=true),
new bool(transform3 t) {
return unscaled(t,X,Y);
});
s.push(unittube);
} else
s.append(render(subpath(p,i,i+1),r));
}
}
transform3 t=scale3(r);
bool cyclic=cyclic(p);
int begin=0;
int n=length(p);
for(int i=cyclic ? 0 : 1; i < n; ++i)
if(abs(dir(p,i,1)-dir(p,i,-1)) > sqrtEpsilon) {
generate(subpath(p,begin,i));
triple dir=dir(p,i,-1);
transform3 T=t*align(dir);
s.push(shift(point(p,i))*T*(straight(p,i-1) && straight(p,i) ?
unithemisphere : unitsphere));
begin=i;
}
path3 g=subpath(p,begin,n);
generate(g);
}
}