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); } }