import graph3; pen defaultbackpen=linetype(new real[] {4,4},4,scale=false); // A solid geometry package. // Try to find a bounding tangent line between two paths. real[] tangent(path p, path q, bool side) { static real fuzz=1.0e-5; if((cyclic(p) && inside(p,point(q,0)) || cyclic(q) && inside(q,point(p,0))) && intersect(p,q,fuzz).length == 0) return new real[]; for(int i=0; i < 100; ++i) { real ta=side ? mintimes(p)[1] : maxtimes(p)[1]; real tb=side ? mintimes(q)[1] : maxtimes(q)[1]; pair a=point(p,ta); pair b=point(q,tb); real angle=angle(b-a,warn=false); if(abs(angle) <= sqrtEpsilon || abs(abs(0.5*angle)-pi) <= sqrtEpsilon) return new real[] {ta,tb}; transform t=rotate(-degrees(angle)); p=t*p; q=t*q; } return new real[]; } path line(path p, path q, real[] t) { return point(p,t[0])--point(q,t[1]); } // Return the projection of a generalized cylinder of height h constructed // from area base in the XY plane and aligned with axis. path[] cylinder(path3 base, real h, triple axis=Z, projection P) { base=rotate(-colatitude(axis),cross(axis,Z))*base; path3 top=shift(h*axis)*base; path Base=project(base,P); path Top=project(top,P); real[] t1=tangent(Base,Top,true); real[] t2=tangent(Base,Top,false); path p=subpath(Base,t1[0]/P.ninterpolate,t2[0]/P.ninterpolate); path q=subpath(Base,t2[0]/P.ninterpolate,t1[0]/P.ninterpolate); return Base^^Top^^line(Base,Top,t1)^^line(Base,Top,t2); } // The three-dimensional "wireframe" used to visualize a volume of revolution struct skeleton { struct curve { path3[] front; path3[] back; } // transverse skeleton (perpendicular to axis of revolution) curve transverse; // longitudinal skeleton (parallel to axis of revolution) curve longitudinal; } // A surface of revolution generated by rotating a planar path3 g // from angle1 to angle2 about c--c+axis. struct revolution { triple c; path3 g; triple axis; real angle1,angle2; triple M; triple m; static real epsilon=10*sqrtEpsilon; void operator init(triple c=O, path3 g, triple axis=Z, real angle1=0, real angle2=360) { this.c=c; this.g=g; this.axis=unit(axis); this.angle1=angle1; this.angle2=angle2; M=max(g); m=min(g); } revolution copy() { return revolution(c,g,axis,angle1,angle2); } triple vertex(int i, real j) { triple v=point(g,i); triple center=c+dot(v-c,axis)*axis; triple perp=v-center; triple normal=cross(axis,perp); return center+Cos(j)*perp+Sin(j)*normal; } // Construct the surface of rotation generated by rotating g // from angle1 to angle2 sampled n times about the line c--c+axis. // An optional surface pen color(int i, real j) may be specified // to override the color at vertex(i,j). surface surface(int n=nslice, pen color(int i, real j)=null) { return surface(c,g,axis,n,angle1,angle2,color); } path3 slice(real position, int n=nCircle) { triple v=point(g,position); triple center=c+dot(v-c,axis)*axis; triple perp=v-center; if(abs(perp) <= epsilon*max(abs(m),abs(M))) return center; triple v1=center+rotate(angle1,axis)*perp; triple v2=center+rotate(angle2,axis)*perp; path3 p=Arc(center,v1,v2,axis,n); return (angle2-angle1) % 360 == 0 ? p&cycle : p; } // add transverse slice to skeleton s; void transverse(skeleton s, real t, int n=nslice, projection P) { skeleton.curve s=s.transverse; path3 S=slice(t,n); int L=length(g); real midtime=0.5*L; real sign=sgn(dot(axis,P.camera-c))*sgn(dot(axis,dir(g,midtime))); if(dot(M-m,axis) == 0 || (t <= epsilon && sign < 0) || (t >= L-epsilon && sign > 0)) s.front.push(S); else { path3 Sp=slice(t+epsilon,n); path3 Sm=slice(t-epsilon,n); path sp=project(Sp,P); path sm=project(Sm,P); real[] t1=tangent(sp,sm,true); real[] t2=tangent(sp,sm,false); if(t1.length > 1 && t2.length > 1) { real t1=t1[0]/P.ninterpolate; real t2=t2[0]/P.ninterpolate; int len=length(S); if(t2 < t1) { real temp=t1; t1=t2; t2=temp; } path3 p1=subpath(S,t1,t2); path3 p2=subpath(S,t2,len); path3 P2=subpath(S,0,t1); if(abs(midpoint(p1)-P.camera) <= abs(midpoint(p2)-P.camera)) { s.front.push(p1); if(cyclic(S)) s.back.push(p2 & P2); else { s.back.push(p2); s.back.push(P2); } } else { if(cyclic(S)) s.front.push(p2 & P2); else { s.front.push(p2); s.front.push(P2); } s.back.push(p1); } } else { if((t <= midtime && sign < 0) || (t >= midtime && sign > 0)) s.front.push(S); else s.back.push(S); } } } // add m evenly spaced transverse slices to skeleton s void transverse(skeleton s, int m=0, int n=nslice, projection P) { if(m == 0) { int N=size(g); for(int i=0; i < N; ++i) transverse(s,(real) i,n,P); } else if(m == 1) transverse(s,reltime(g,0.5),n,P); else { real factor=1/(m-1); for(int i=0; i < m; ++i) transverse(s,reltime(g,i*factor),n,P); } } // return approximate silhouette based on m evenly spaced transverse slices; // must be recomputed if camera is adjusted path3[] silhouette(int m=64, projection P=currentprojection) { if(is3D()) warning("2Dsilhouette", "silhouette routine is intended only for 2d projections"); path3 G,H; int N=size(g); int M=(m == 0) ? N : m; real factor=m == 1 ? 0 : 1/(m-1); int n=nslice; real tfirst=-1; real tlast; for(int i=0; i < M; ++i) { real t=(m == 0) ? i : reltime(g,i*factor); path3 S=slice(t,n); path3 Sp=slice(t+epsilon,n); path3 Sm=slice(t-epsilon,n); path sp=project(Sp,P); path sm=project(Sm,P); real[] t1=tangent(sp,sm,true); real[] t2=tangent(sp,sm,false); if(t1.length > 1 && t2.length > 1) { real t1=t1[0]/P.ninterpolate; real t2=t2[0]/P.ninterpolate; if(t1 != t2) { G=G..point(S,t1); H=point(S,t2)..H; if(tfirst < 0) tfirst=t; tlast=t; } } } int L=length(g); real midtime=0.5*L; real sign=sgn(dot(axis,P.camera-c))*sgn(dot(axis,dir(g,midtime))); skeleton sfirst; transverse(sfirst,tfirst,n,P); triple delta=this.M-this.m; path3 cap; if(dot(delta,axis) == 0 || (tfirst <= epsilon && sign < 0)) { cap=sfirst.transverse.front[0]; } else { if(sign > 0) { if(sfirst.transverse.front.length > 0) G=reverse(sfirst.transverse.front[0])..G; } else { if(sfirst.transverse.back.length > 0) G=sfirst.transverse.back[0]..G; } } skeleton slast; transverse(slast,tlast,n,P); if(dot(delta,axis) == 0 || (tlast >= L-epsilon && sign > 0)) { cap=slast.transverse.front[0]; } else { if(sign > 0) { if(slast.transverse.back.length > 0) H=reverse(slast.transverse.back[0])..H; } else { if(slast.transverse.front.length > 0) H=slast.transverse.front[0]..H; } } return size(cap) == 0 ? G^^H : G^^H^^cap; } // add longitudinal curves to skeleton; void longitudinal(skeleton s, int n=nslice, projection P) { real t, d=0; // Find a point on g of maximal distance from the axis. int N=size(g); for(int i=0; i < N; ++i) { triple v=point(g,i); triple center=c+dot(v-c,axis)*axis; real r=abs(v-center); if(r > d) { t=i; d=r; } } path3 S=slice(t,n); path3 Sm=slice(t+epsilon,n); path3 Sp=slice(t-epsilon,n); path sp=project(Sp,P); path sm=project(Sm,P); real[] t1=tangent(sp,sm,true); real[] t2=tangent(sp,sm,false); transform3 T=transpose(align(axis)); real Longitude(triple v) {return longitude(T*(v-c),warn=false);} real ref=Longitude(point(g,t)); real angle(real t) {return Longitude(point(S,t/P.ninterpolate))-ref;} void push(real[] T) { if(T.length > 1) { path3 p=rotate(angle(T[0]),c,c+axis)*g; path3 p1=subpath(p,0,t); path3 p2=subpath(p,t,length(p)); if(length(p1) > 0 && (length(p2) == 0 || abs(midpoint(p1)-P.camera) <= abs(midpoint(p2)-P.camera))) { s.longitudinal.front.push(p1); s.longitudinal.back.push(p2); } else { s.longitudinal.back.push(p1); s.longitudinal.front.push(p2); } } } push(t1); push(t2); } skeleton skeleton(int m=0, int n=nslice, projection P) { skeleton s; transverse(s,m,n,P); longitudinal(s,n,P); return s; } } revolution operator * (transform3 t, revolution r) { triple trc=t*r.c; return revolution(trc,t*r.g,t*(r.c+r.axis)-trc,r.angle1,r.angle2); } surface surface(revolution r, int n=nslice, pen color(int i, real j)=null) { return r.surface(n,color); } // Draw on picture pic the skeleton of the surface of revolution r. // Draw the front portion of each of the m transverse slices with pen p and // the back portion with pen backpen. Rotational arcs are based on // n-point approximations to the unit circle. void draw(picture pic=currentpicture, revolution r, int m=0, int n=nslice, pen frontpen=currentpen, pen backpen=frontpen, pen longitudinalpen=frontpen, pen longitudinalbackpen=backpen, light light=currentlight, string name="", render render=defaultrender, projection P=currentprojection) { if(is3D()) { pen thin=thin(); void drawskeleton(frame f, transform3 t, projection P) { skeleton s=r.skeleton(m,n,inverse(t)*P); if(frontpen != nullpen) { draw(f,t*s.transverse.back,thin+defaultbackpen+backpen,light); draw(f,t*s.transverse.front,thin+frontpen,light); } if(longitudinalpen != nullpen) { draw(f,t*s.longitudinal.back,thin+defaultbackpen+longitudinalbackpen, light); draw(f,t*s.longitudinal.front,thin+longitudinalpen,light); } } bool group=name != "" || render.defaultnames; if(group) begingroup3(pic,name == "" ? "skeleton" : name,render); pic.add(new void(frame f, transform3 t, picture pic, projection P) { drawskeleton(f,t,P); if(pic != null) pic.addBox(min(f,P),max(f,P),min(frontpen),max(frontpen)); }); frame f; drawskeleton(f,identity4,P); pic.addBox(min3(f),max3(f)); if(group) endgroup3(pic); } else { skeleton s=r.skeleton(m,n,P); if(frontpen != nullpen) { draw(pic,s.transverse.back,defaultbackpen+backpen,light); draw(pic,s.transverse.front,frontpen,light); } if(longitudinalpen != nullpen) { draw(pic,s.longitudinal.back,defaultbackpen+longitudinalbackpen, light); draw(pic,s.longitudinal.front,longitudinalpen,light); } } } // Return a right circular cylinder of height h in the direction of axis // based on a circle centered at c with radius r. // Note: unitcylinder provides a smoother and more efficient representation. revolution cylinder(triple c=O, real r, real h, triple axis=Z) { triple C=c+r*perp(axis); axis=h*unit(axis); return revolution(c,C--C+axis,axis); } // Return a right circular cone of height h in the direction of axis // based on a circle centered at c with radius r. The parameter n // controls the accuracy near the degenerate point at the apex. revolution cone(triple c=O, real r, real h, triple axis=Z, int n=nslice) { axis=unit(axis); return revolution(c,approach(c+r*perp(axis)--c+h*axis,n),axis); } // Return an approximate sphere of radius r centered at c obtained by rotating // an (n+1)-point approximation to a half circle about the Z axis. // Note: unitsphere provides a smoother and more efficient representation. revolution sphere(triple c=O, real r, int n=nslice) { return revolution(c,Arc(c,r,180-sqrtEpsilon,0,sqrtEpsilon,0,Y,n),Z); }