diff options
author | Karl Berry <karl@freefriends.org> | 2009-06-08 01:04:41 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2009-06-08 01:04:41 +0000 |
commit | efda44c2e2e6bd08bf58ebdfb2f1879cff9e6f37 (patch) | |
tree | 105b5092688d84851b40816150f4a5c77df3ed08 /Master/texmf/asymptote/plain_picture.asy | |
parent | 9e32dd6aee7faf4e59888cfbd7a927d497b563ad (diff) |
first commit of asymptote binaries + support
git-svn-id: svn://tug.org/texlive/trunk@13665 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf/asymptote/plain_picture.asy')
-rw-r--r-- | Master/texmf/asymptote/plain_picture.asy | 1892 |
1 files changed, 1892 insertions, 0 deletions
diff --git a/Master/texmf/asymptote/plain_picture.asy b/Master/texmf/asymptote/plain_picture.asy new file mode 100644 index 00000000000..4d894dc6dea --- /dev/null +++ b/Master/texmf/asymptote/plain_picture.asy @@ -0,0 +1,1892 @@ +real camerafactor=2; // Factor used for camera adjustment. +pair viewportsize=0; // Horizontal and vertical viewport limits. + +restricted bool Aspect=true; +restricted bool IgnoreAspect=false; + +pair size(frame f) +{ + return max(f)-min(f); +} + +typedef real[][] transform3; +restricted transform3 identity4=identity(4); + +// A uniform 3D scaling. +transform3 scale3(real s) +{ + transform3 t=identity(4); + t[0][0]=t[1][1]=t[2][2]=s; + return t; +} + +// Simultaneous 3D scalings in the x, y, and z directions. +transform3 scale(real x, real y, real z) +{ + transform3 t=identity(4); + t[0][0]=x; + t[1][1]=y; + t[2][2]=z; + return t; +} + +transform3 shiftless(transform3 t) +{ + transform3 T=copy(t); + T[0][3]=T[1][3]=T[2][3]=0; + return T; +} + +// A coordinate in "flex space." A linear combination of user and true-size +// coordinates. +struct coord { + real user,truesize; + bool finite=true; + + // Build a coord. + static coord build(real user, real truesize) { + coord c=new coord; + c.user=user; + c.truesize=truesize; + return c; + } + + // Deep copy of coordinate. Users may add coords to the picture, but then + // modify the struct. To prevent this from yielding unexpected results, deep + // copying is used. + coord copy() { + return build(user, truesize); + } + + void clip(real min, real max) { + user=min(max(user,min),max); + truesize=0; + } +} + +struct coords2 { + coord[] x,y; + void erase() { + x.delete(); + y.delete(); + } + // Only a shallow copy of the individual elements of x and y + // is needed since, once entered, they are never modified. + coords2 copy() { + coords2 c=new coords2; + c.x=copy(x); + c.y=copy(y); + return c; + } + void append(coords2 c) { + x.append(c.x); + y.append(c.y); + } + void push(pair user, pair truesize) { + x.push(coord.build(user.x,truesize.x)); + y.push(coord.build(user.y,truesize.y)); + } + void push(coord cx, coord cy) { + x.push(cx); + y.push(cy); + } + void push(transform t, coords2 c1, coords2 c2) { + for(int i=0; i < c1.x.length; ++i) { + coord cx=c1.x[i], cy=c2.y[i]; + pair tinf=shiftless(t)*((finite(cx.user) ? 0 : 1), + (finite(cy.user) ? 0 : 1)); + pair z=t*(cx.user,cy.user); + pair w=(cx.truesize,cy.truesize); + w=length(w)*unit(shiftless(t)*w); + coord Cx,Cy; + Cx.user=(tinf.x == 0 ? z.x : infinity); + Cy.user=(tinf.y == 0 ? z.y : infinity); + Cx.truesize=w.x; + Cy.truesize=w.y; + push(Cx,Cy); + } + } + void xclip(real min, real max) { + for(int i=0; i < x.length; ++i) + x[i].clip(min,max); + } + void yclip(real min, real max) { + for(int i=0; i < y.length; ++i) + y[i].clip(min,max); + } +} + +struct coords3 { + coord[] x,y,z; + void erase() { + x.delete(); + y.delete(); + z.delete(); + } + // Only a shallow copy of the individual elements of x and y + // is needed since, once entered, they are never modified. + coords3 copy() { + coords3 c=new coords3; + c.x=copy(x); + c.y=copy(y); + c.z=copy(z); + return c; + } + void append(coords3 c) { + x.append(c.x); + y.append(c.y); + z.append(c.z); + } + void push(triple user, triple truesize) { + x.push(coord.build(user.x,truesize.x)); + y.push(coord.build(user.y,truesize.y)); + z.push(coord.build(user.z,truesize.z)); + } + void push(coord cx, coord cy, coord cz) { + x.push(cx); + y.push(cy); + z.push(cz); + } + void push(transform3 t, coords3 c1, coords3 c2, coords3 c3) { + for(int i=0; i < c1.x.length; ++i) { + coord cx=c1.x[i], cy=c2.y[i], cz=c3.z[i]; + triple tinf=shiftless(t)*((finite(cx.user) ? 0 : 1), + (finite(cy.user) ? 0 : 1), + (finite(cz.user) ? 0 : 1)); + triple z=t*(cx.user,cy.user,cz.user); + triple w=(cx.truesize,cy.truesize,cz.truesize); + w=length(w)*unit(shiftless(t)*w); + coord Cx,Cy,Cz; + Cx.user=(tinf.x == 0 ? z.x : infinity); + Cy.user=(tinf.y == 0 ? z.y : infinity); + Cz.user=(tinf.z == 0 ? z.z : infinity); + Cx.truesize=w.x; + Cy.truesize=w.y; + Cz.truesize=w.z; + push(Cx,Cy,Cz); + } + } +} + +bool operator <= (coord a, coord b) +{ + return a.user <= b.user && a.truesize <= b.truesize; +} + +bool operator >= (coord a, coord b) +{ + return a.user >= b.user && a.truesize >= b.truesize; +} + +// Find the maximal elements of the input array, using the partial ordering +// given. +coord[] maxcoords(coord[] in, bool operator <= (coord,coord)) +{ + // As operator <= is defined in the parameter list, it has a special + // meaning in the body of the function. + + coord best; + coord[] c; + + int n=in.length; + + // Find the first finite restriction. + int first=0; + for(first=0; first < n; ++first) + if(finite(in[first].user)) break; + + if(first == n) + return c; + else { + // Add the first coord without checking restrictions (as there are none). + best=in[first]; + c.push(best); + } + + static int NONE=-1; + + int dominator(coord x) + { + // This assumes it has already been checked against the best. + for(int i=1; i < c.length; ++i) + if(x <= c[i]) + return i; + return NONE; + } + + void promote(int i) + { + // Swap with the top + coord x=c[i]; + c[i]=best; + best=c[0]=x; + } + + void addmaximal(coord x) + { + coord[] newc; + + // Check if it beats any others. + for(int i=0; i < c.length; ++i) { + coord y=c[i]; + if(!(y <= x)) + newc.push(y); + } + newc.push(x); + c=newc; + best=c[0]; + } + + void add(coord x) + { + if(x <= best || !finite(x.user)) + return; + else { + int i=dominator(x); + if(i == NONE) + addmaximal(x); + else + promote(i); + } + } + + for(int i=1; i < n; ++i) + add(in[i]); + + return c; +} + +typedef real scalefcn(real x); + +struct scaleT { + scalefcn T,Tinv; + bool logarithmic; + bool automin,automax; + void operator init(scalefcn T, scalefcn Tinv, bool logarithmic=false, + bool automin=false, bool automax=false) { + this.T=T; + this.Tinv=Tinv; + this.logarithmic=logarithmic; + this.automin=automin; + this.automax=automax; + } + scaleT copy() { + scaleT dest=scaleT(T,Tinv,logarithmic,automin,automax); + return dest; + } +}; + +scaleT operator init() +{ + scaleT S=scaleT(identity,identity); + return S; +} + +typedef void boundRoutine(); + +struct autoscaleT { + scaleT scale; + scaleT postscale; + real tickMin=-infinity, tickMax=infinity; + boundRoutine[] bound; // Optional routines to recompute the bounding box. + bool automin=false, automax=false; + bool automin() {return automin && scale.automin;} + bool automax() {return automax && scale.automax;} + + real T(real x) {return postscale.T(scale.T(x));} + scalefcn T() {return scale.logarithmic ? postscale.T : T;} + real Tinv(real x) {return scale.Tinv(postscale.Tinv(x));} + + autoscaleT copy() { + autoscaleT dest=new autoscaleT; + dest.scale=scale.copy(); + dest.postscale=postscale.copy(); + dest.tickMin=tickMin; + dest.tickMax=tickMax; + dest.bound=copy(bound); + dest.automin=(bool) automin; + dest.automax=(bool) automax; + return dest; + } +} + +struct ScaleT { + bool set; + autoscaleT x; + autoscaleT y; + autoscaleT z; + + ScaleT copy() { + ScaleT dest=new ScaleT; + dest.set=set; + dest.x=x.copy(); + dest.y=y.copy(); + dest.z=z.copy(); + return dest; + } +}; + +struct Legend { + string label; + pen plabel; + pen p; + frame mark; + bool above; + void operator init(string label, pen plabel=currentpen, pen p=nullpen, + frame mark=newframe, bool above=true) { + this.label=label; + this.plabel=plabel; + this.p=(p == nullpen) ? plabel : p; + this.mark=mark; + this.above=above; + } +} + +pair rectify(pair dir) +{ + real scale=max(abs(dir.x),abs(dir.y)); + if(scale != 0) dir *= 0.5/scale; + dir += (0.5,0.5); + return dir; +} + +pair point(frame f, pair dir) +{ + pair m=min(f); + pair M=max(f); + return m+realmult(rectify(dir),M-m); +} + +path[] align(path[] g, transform t=identity(), pair position, + pair align, pen p=currentpen) +{ + pair m=min(g); + pair M=max(g); + pair dir=rectify(inverse(t)*-align); + if(basealign(p) == 1) + dir -= (0,m.y/(M.y-m.y)); + pair a=m+realmult(dir,M-m); + return shift(position+align*labelmargin(p))*t*shift(-a)*g; +} + +// Returns a transform for aligning frame f in the direction align +transform shift(frame f, pair align) +{ + return shift(align-point(f,-align)); +} + +// Returns a copy of frame f aligned in the direction align +frame align(frame f, pair align) +{ + return shift(f,align)*f; +} + +struct transformation { + transform3 modelview; // For orientation and positioning + transform3 projection; // For 3D to 2D projection + bool infinity; + bool oblique; + void operator init(transform3 modelview, bool oblique=false) { + this.modelview=modelview; + this.projection=identity4; + infinity=true; + this.oblique=oblique; + } + void operator init(transform3 modelview, transform3 projection) { + this.modelview=modelview; + this.projection=projection; + infinity=false; + oblique=false; + } + transform3 compute() { + return projection*modelview; + } +} + +struct projection { + transform3 t; // projection*modelview (cached) + bool infinity; + bool oblique; + bool absolute=false; + triple camera; + triple up; + triple target; + bool showtarget=true; // Expand bounding volume to include target? + typedef transformation projector(triple camera, triple up, triple target); + projector projector; + bool autoadjust=true; // Adjust camera to lie outside bounding volume? + bool center=false; // Center target within bounding volume? + real angle; // Lens angle (currently only used by PRC viewpoint). + int ninterpolate; // Used for projecting nurbs to 2D Bezier curves. + + void calculate() { + transformation T=projector(camera,up,target); + t=T.compute(); + infinity=T.infinity; + oblique=T.oblique; + ninterpolate=infinity ? 1 : 16; + } + + transformation transformation() { + return projector(camera,up,target); + } + + transform3 modelview() {return transformation().modelview;} + + triple vector() { + return camera-target; + } + + void operator init(triple camera, triple up=(0,0,1), triple target=(0,0,0), + bool showtarget=true, bool autoadjust=true, + bool center=false, projector projector) { + this.camera=camera; + this.up=up; + this.target=target; + this.showtarget=showtarget; + this.autoadjust=autoadjust; + this.center=center; + this.projector=projector; + calculate(); + } + + projection copy() { + projection P=new projection; + P.t=t; + P.infinity=infinity; + P.absolute=absolute; + P.oblique=oblique; + P.camera=camera; + P.up=up; + P.target=target; + P.showtarget=showtarget; + P.autoadjust=autoadjust; + P.center=center; + P.projector=projector; + P.angle=angle; + P.ninterpolate=ninterpolate; + return P; + } + + // Return the maximum distance of box(m,M) from target. + real distance(triple m, triple M) { + triple[] c={m,(m.x,m.y,M.z),(m.x,M.y,m.z),(m.x,M.y,M.z), + (M.x,m.y,m.z),(M.x,m.y,M.z),(M.x,M.y,m.z),M}; + return max(abs(c-target)); + } + + // Move the camera so that the box(m,M) rotated about target will always + // lie in front of the clipping plane. + bool adjust(triple m, triple M) { + triple v=camera-target; + real d=distance(m,M); + static real lambda=camerafactor*(1-sqrtEpsilon); + if(lambda*d >= abs(v)) { + camera=target+camerafactor*d*unit(v); + calculate(); + return true; + } + return false; + } +} + +projection currentprojection; + +triple min3(pen p) +{ + return linewidth(p)*(-0.5,-0.5,-0.5); +} + +triple max3(pen p) +{ + return linewidth(p)*(0.5,0.5,0.5); +} + +// A function that draws an object to frame pic, given that the transform +// from user coordinates to true-size coordinates is t. +typedef void drawer(frame f, transform t); + +// A generalization of drawer that includes the final frame's bounds. +typedef void drawerBound(frame f, transform t, transform T, pair lb, pair rt); + +struct picture { + // Three-dimensional version of drawer and drawerBound: + typedef void drawer3(frame f, transform3 t, picture pic, projection P); + typedef void drawerBound3(frame f, transform3 t, transform3 T, + picture pic, projection P, triple lb, triple rt); + + // The functions to do the deferred drawing. + drawerBound[] nodes; + drawerBound3[] nodes3; + + bool uptodate=true; + + // The coordinates in flex space to be used in sizing the picture. + struct bounds { + coords2 point,min,max; + bool exact=true; // An accurate picture bounds is provided by the user. + void erase() { + point.erase(); + min.erase(); + max.erase(); + } + bounds copy() { + bounds b=new bounds; + b.point=point.copy(); + b.min=min.copy(); + b.max=max.copy(); + b.exact=exact; + return b; + } + void xclip(real Min, real Max) { + point.xclip(Min,Max); + min.xclip(Min,Max); + max.xclip(Min,Max); + } + void yclip(real Min, real Max) { + point.yclip(Min,Max); + min.yclip(Min,Max); + max.yclip(Min,Max); + } + void clip(triple Min, triple Max) { + xclip(Min.x,Max.x); + yclip(Min.y,Max.y); + } + } + + struct bounds3 { + coords3 point,min,max; + bool exact=true; // An accurate picture bounds is provided by the user. + void erase() { + point.erase(); + min.erase(); + max.erase(); + } + bounds3 copy() { + bounds3 b=new bounds3; + b.point=point.copy(); + b.min=min.copy(); + b.max=max.copy(); + b.exact=exact; + return b; + } + } + + bounds bounds; + bounds3 bounds3; + + // Transform to be applied to this picture. + transform T; + transform3 T3; + + // Cached user-space bounding box + triple userMin,userMax; + bool userSetx,userSety,userSetz; + + ScaleT scale; // Needed by graph + Legend[] legend; + + pair[] clipmax; // Used by beginclip/endclip + pair[] clipmin; + + // The maximum sizes in the x, y, and z directions; zero means no restriction. + real xsize=0, ysize=0; + + real xsize3=0, ysize3=0, zsize3=0; + + // Fixed unitsizes in the x y, and z directions; zero means use + // xsize, ysize, and zsize. + real xunitsize=0, yunitsize=0, zunitsize=0; + + // If true, the x and y directions must be scaled by the same amount. + bool keepAspect=true; + + // A fixed scaling transform. + bool fixed; + transform fixedscaling; + + void init() { + userMin=userMax=(0,0,0); + userSetx=userSety=userSetz=false; + T3=identity(4); + } + init(); + + // Erase the current picture, retaining any size specification. + void erase() { + nodes.delete(); + nodes3.delete(); + bounds.erase(); + bounds3.erase(); + T=identity(); + scale=new ScaleT; + legend.delete(); + init(); + } + + bool empty2() { + return nodes.length == 0; + } + + bool empty3() { + return nodes3.length == 0; + } + + bool empty() { + return empty2() && empty3(); + } + + pair userMin() {return (userMin.x,userMin.y);} + pair userMax() {return (userMax.x,userMax.y);} + + void userMinx(real x) { + userMin=(x,userMin.y,userMin.z); + userSetx=true; + } + + void userMiny(real y) { + userMin=(userMin.x,y,userMin.z); + userSety=true; + } + + void userMinz(real z) { + userMin=(userMin.x,userMin.y,z); + userSetz=true; + } + + void userMaxx(real x) { + userMax=(x,userMax.y,userMax.z); + userSetx=true; + } + + void userMaxy(real y) { + userMax=(userMax.x,y,userMax.z); + userSety=true; + } + + void userMaxz(real z) { + userMax=(userMax.x,userMax.y,z); + userSetz=true; + } + + void userCorners(pair c00, pair c01, pair c10, pair c11) { + userMin=(min(c00.x,c01.x,c10.x,c11.x),min(c00.y,c01.y,c10.y,c11.y), + userMin.z); + userMax=(max(c00.x,c01.x,c10.x,c11.x),max(c00.y,c01.y,c10.y,c11.y), + userMax.z); + } + + void userCorners(triple c000, triple c001, triple c010, triple c011, + triple c100, triple c101, triple c110, triple c111) { + userMin=(min(c000.x,c001.x,c010.x,c011.x,c100.x,c101.x,c110.x,c111.x), + min(c000.y,c001.y,c010.y,c011.y,c100.y,c101.y,c110.y,c111.y), + min(c000.z,c001.z,c010.z,c011.z,c100.z,c101.z,c110.z,c111.z)); + userMax=(max(c000.x,c001.x,c010.x,c011.x,c100.x,c101.x,c110.x,c111.x), + max(c000.y,c001.y,c010.y,c011.y,c100.y,c101.y,c110.y,c111.y), + max(c000.z,c001.z,c010.z,c011.z,c100.z,c101.z,c110.z,c111.z)); + } + + void userCopy(picture pic) { + userMin=(triple) pic.userMin; + userMax=(triple) pic.userMax; + userSetx=pic.userSetx; + userSety=pic.userSety; + userSetz=pic.userSetz; + } + + typedef real binop(real, real); + + // Cache the current user-space bounding box x coodinates + void userBoxX(real min, real max, binop m=min, binop M=max) { + if(userSetx) { + userMin=(m(userMin.x,min),userMin.y,userMin.z); + userMax=(M(userMax.x,max),userMax.y,userMax.z); + } else { + userMin=(min,userMin.y,userMin.z); + userMax=(max,userMax.y,userMax.z); + userSetx=true; + } + } + + // Cache the current user-space bounding box y coodinates + void userBoxY(real min, real max, binop m=min, binop M=max) { + if(userSety) { + userMin=(userMin.x,m(userMin.y,min),userMin.z); + userMax=(userMax.x,M(userMax.y,max),userMax.z); + } else { + userMin=(userMin.x,min,userMin.z); + userMax=(userMax.x,max,userMax.z); + userSety=true; + } + } + + // Cache the current user-space bounding box z coodinates + void userBoxZ(real min, real max, binop m=min, binop M=max) { + if(userSetz) { + userMin=(userMin.x,userMin.y,m(userMin.z,min)); + userMax=(userMax.x,userMax.y,M(userMax.z,max)); + } else { + userMin=(userMin.x,userMin.y,min); + userMax=(userMax.x,userMax.y,max); + userSetz=true; + } + } + + // Cache the current user-space bounding box + void userBox(pair min, pair max) { + userBoxX(min.x,max.x); + userBoxY(min.y,max.y); + } + + // Cache the current user-space bounding box + void userBox(triple min, triple max) { + userBoxX(min.x,max.x); + userBoxY(min.y,max.y); + userBoxZ(min.z,max.z); + } + + // Clip the current user-space bounding box + void userClip(pair min, pair max) { + userBoxX(min.x,max.x,max,min); + userBoxY(min.y,max.y,max,min); + } + + void add(drawerBound d, bool exact=false) { + uptodate=false; + if(!exact) bounds.exact=false; + nodes.push(d); + } + + void add(drawer d, bool exact=false, bool above=true) { + uptodate=false; + if(!exact) bounds.exact=false; + nodes.push(new void(frame f, transform t, transform T, pair, pair) { + d(f,t*T); + }); + } + + void add(drawerBound3 d, bool exact=false, bool above=true) { + uptodate=false; + if(!exact) bounds.exact=false; + if(above) + nodes3.push(d); + else + nodes3.insert(0,d); + } + + void add(drawer3 d, bool exact=false, bool above=true) { + add(new void(frame f, transform3 t, transform3 T, picture pic, + projection P, triple, triple) { + d(f,t*T,pic,P); + },exact,above); + } + + void clip(drawer d, bool exact=false) { + bounds.clip(userMin,userMax); + this.add(d,exact); + } + + void clip(drawerBound d, bool exact=false) { + bounds.clip(userMin,userMax); + this.add(d,exact); + } + + // Add a point to the sizing. + void addPoint(pair user, pair truesize=0) { + bounds.point.push(user,truesize); + userBox(user,user); + } + + // Add a point to the sizing, accounting also for the size of the pen. + void addPoint(pair user, pair truesize=0, pen p) { + addPoint(user,truesize+min(p)); + addPoint(user,truesize+max(p)); + } + + void addPoint(triple user, triple truesize=(0,0,0)) { + bounds3.point.push(user,truesize); + userBox(user,user); + } + + void addPoint(triple user, triple truesize=(0,0,0), pen p) { + addPoint(user,truesize+min3(p)); + addPoint(user,truesize+max3(p)); + } + + // Add a box to the sizing. + void addBox(pair userMin, pair userMax, pair trueMin=0, pair trueMax=0) { + bounds.min.push(userMin,trueMin); + bounds.max.push(userMax,trueMax); + userBox(userMin,userMax); + } + + void addBox(triple userMin, triple userMax, triple trueMin=(0,0,0), + triple trueMax=(0,0,0)) { + bounds3.min.push(userMin,trueMin); + bounds3.max.push(userMax,trueMax); + userBox(userMin,userMax); + } + + // Add a (user space) path to the sizing. + void addPath(path g) { + if(size(g) > 0) + addBox(min(g),max(g)); + } + void addPath(path[] g) { + for(int i=0; i < g.length; ++i) + addPath(g[i]); + } + + // Add a path to the sizing with the additional padding of a pen. + void addPath(path g, pen p) { + if(size(g) > 0) + addBox(min(g),max(g),min(p),max(p)); + } + + void size(real x, real y=x, bool keepAspect=this.keepAspect) { + if(!empty()) uptodate=false; + xsize=x; + ysize=y; + this.keepAspect=keepAspect; + } + + void size3(real x, real y=x, real z=y, bool keepAspect=this.keepAspect) { + if(!empty3()) uptodate=false; + xsize3=x; + ysize3=y; + zsize3=z; + this.keepAspect=keepAspect; + } + + void unitsize(real x, real y=x, real z=y) { + uptodate=false; + xunitsize=x; + yunitsize=y; + zunitsize=z; + } + + // The scaling in one dimension: x --> a*x + b + struct scaling { + real a,b; + static scaling build(real a, real b) { + scaling s=new scaling; + s.a=a; s.b=b; + return s; + } + real scale(real x) { + return a*x+b; + } + real scale(coord c) { + return scale(c.user) + c.truesize; + } + } + + // Calculate the minimum point in scaling the coords. + real min(real m, scaling s, coord[] c) { + for(int i=0; i < c.length; ++i) + if(finite(c[i].user) && s.scale(c[i]) < m) + m=s.scale(c[i]); + return m; + } + + // Calculate the maximum point in scaling the coords. + real max(real M, scaling s, coord[] c) { + for(int i=0; i < c.length; ++i) + if(finite(c[i].user) && s.scale(c[i]) > M) + M=s.scale(c[i]); + return M; + } + + // Calculate the min for the final frame, given the coordinate transform. + pair min(transform t) { + if(bounds.min.x.length == 0 && bounds.point.x.length == 0 && + bounds.max.x.length == 0) return 0; + pair a=t*(1,1)-t*(0,0), b=t*(0,0); + scaling xs=scaling.build(a.x,b.x); + scaling ys=scaling.build(a.y,b.y); + return (min(min(min(infinity,xs,bounds.point.x),xs,bounds.min.x), + xs,bounds.max.x), + min(min(min(infinity,ys,bounds.point.y),ys,bounds.min.y), + ys,bounds.max.y)); + } + + // Calculate the max for the final frame, given the coordinate transform. + pair max(transform t) { + if(bounds.min.x.length == 0 && bounds.point.x.length == 0 && + bounds.max.x.length == 0) return 0; + pair a=t*(1,1)-t*(0,0), b=t*(0,0); + scaling xs=scaling.build(a.x,b.x); + scaling ys=scaling.build(a.y,b.y); + return (max(max(max(-infinity,xs,bounds.point.x),xs,bounds.min.x), + xs,bounds.max.x), + max(max(max(-infinity,ys,bounds.point.y),ys,bounds.min.y), + ys,bounds.max.y)); + } + + // Calculate the min for the final frame, given the coordinate transform. + triple min(transform3 t) { + if(bounds3.min.x.length == 0 && bounds3.point.x.length == 0 && + bounds3.max.x.length == 0) return (0,0,0); + triple a=t*(1,1,1)-t*(0,0,0), b=t*(0,0,0); + scaling xs=scaling.build(a.x,b.x); + scaling ys=scaling.build(a.y,b.y); + scaling zs=scaling.build(a.z,b.z); + return (min(min(min(infinity,xs,bounds3.point.x),xs,bounds3.min.x), + xs,bounds3.max.x), + min(min(min(infinity,ys,bounds3.point.y),ys,bounds3.min.y), + ys,bounds3.max.y), + min(min(min(infinity,zs,bounds3.point.z),zs,bounds3.min.z), + zs,bounds3.max.z)); + } + + // Calculate the max for the final frame, given the coordinate transform. + triple max(transform3 t) { + if(bounds3.min.x.length == 0 && bounds3.point.x.length == 0 && + bounds3.max.x.length == 0) return (0,0,0); + triple a=t*(1,1,1)-t*(0,0,0), b=t*(0,0,0); + scaling xs=scaling.build(a.x,b.x); + scaling ys=scaling.build(a.y,b.y); + scaling zs=scaling.build(a.z,b.z); + return (max(max(max(-infinity,xs,bounds3.point.x),xs,bounds3.min.x), + xs,bounds3.max.x), + max(max(max(-infinity,ys,bounds3.point.y),ys,bounds3.min.y), + ys,bounds3.max.y), + max(max(max(-infinity,zs,bounds3.point.z),zs,bounds3.min.z), + zs,bounds3.max.z)); + } + + // Calculate the sizing constants for the given array and maximum size. + real calculateScaling(string dir, coord[] coords, real size, + bool warn=true) { + access simplex; + simplex.problem p=new simplex.problem; + + void addMinCoord(coord c) { + // (a*user + b) + truesize >= 0: + p.addRestriction(c.user,1,c.truesize); + } + void addMaxCoord(coord c) { + // (a*user + b) + truesize <= size: + p.addRestriction(-c.user,-1,size-c.truesize); + } + + coord[] m=maxcoords(coords,operator >=); + coord[] M=maxcoords(coords,operator <=); + + for(int i=0; i < m.length; ++i) + addMinCoord(m[i]); + for(int i=0; i < M.length; ++i) + addMaxCoord(M[i]); + + int status=p.optimize(); + if(status == simplex.problem.OPTIMAL) { + return scaling.build(p.a(),p.b()).a; + } else if(status == simplex.problem.UNBOUNDED) { + if(warn) write("warning: "+dir+" scaling in picture unbounded"); + return 0; + } else { + if(!warn) return 1; + bool userzero=true; + for(int i=0; i < coords.length; ++i) { + if(coords[i].user != 0) userzero=false; + if(!finite(coords[i].user) || !finite(coords[i].truesize)) + abort("unbounded picture"); + } + if(userzero) return 1; + write("warning: cannot fit picture to "+dir+"size "+(string) size + +"...enlarging..."); + return calculateScaling(dir,coords,sqrt(2)*size,warn); + } + } + + void append(coords2 point, coords2 min, coords2 max, transform t, + bounds bounds) + { + // Add the coord info to this picture. + if(t == identity()) { + point.append(bounds.point); + min.append(bounds.min); + max.append(bounds.max); + } else { + point.push(t,bounds.point,bounds.point); + // Add in all 4 corner points, to properly size rectangular pictures. + point.push(t,bounds.min,bounds.min); + point.push(t,bounds.min,bounds.max); + point.push(t,bounds.max,bounds.min); + point.push(t,bounds.max,bounds.max); + } + } + + void append(coords3 point, coords3 min, coords3 max, transform3 t, + bounds3 bounds) + { + // Add the coord info to this picture. + if(t == identity4) { + point.append(bounds.point); + min.append(bounds.min); + max.append(bounds.max); + } else { + point.push(t,bounds.point,bounds.point,bounds.point); + // Add in all 8 corner points, to properly size cuboid pictures. + point.push(t,bounds.min,bounds.min,bounds.min); + point.push(t,bounds.min,bounds.min,bounds.max); + point.push(t,bounds.min,bounds.max,bounds.min); + point.push(t,bounds.min,bounds.max,bounds.max); + point.push(t,bounds.max,bounds.min,bounds.min); + point.push(t,bounds.max,bounds.min,bounds.max); + point.push(t,bounds.max,bounds.max,bounds.min); + point.push(t,bounds.max,bounds.max,bounds.max); + } + } + + // Returns the transform for turning user-space pairs into true-space pairs. + transform scaling(real xsize, real ysize, bool keepAspect=true, + bool warn=true) { + if(xsize == 0 && xunitsize == 0 && ysize == 0 && yunitsize == 0) + return identity(); + + coords2 Coords; + + append(Coords,Coords,Coords,T,bounds); + + real sx; + if(xunitsize == 0) { + if(xsize != 0) sx=calculateScaling("x",Coords.x,xsize,warn); + } else sx=xunitsize; + + real sy; + if(yunitsize == 0) { + if(ysize != 0) sy=calculateScaling("y",Coords.y,ysize,warn); + } else sy=yunitsize; + + if(sx == 0) { + sx=sy; + if(sx == 0) + return identity(); + } else if(sy == 0) sy=sx; + + + if(keepAspect && (xunitsize == 0 || yunitsize == 0)) + return scale(min(sx,sy)); + else + return scale(sx,sy); + } + + transform scaling(bool warn=true) { + return scaling(xsize,ysize,keepAspect,warn); + } + + // Returns the transform for turning user-space pairs into true-space triples. + transform3 scaling(real xsize, real ysize, real zsize, bool keepAspect=true, + bool warn=true) { + if(xsize == 0 && xunitsize == 0 && ysize == 0 && yunitsize == 0 + && zsize == 0 && zunitsize == 0) + return identity(4); + + coords3 Coords; + + append(Coords,Coords,Coords,T3,bounds3); + + real sx; + if(xunitsize == 0) { + if(xsize != 0) sx=calculateScaling("x",Coords.x,xsize,warn); + } else sx=xunitsize; + + real sy; + if(yunitsize == 0) { + if(ysize != 0) sy=calculateScaling("y",Coords.y,ysize,warn); + } else sy=yunitsize; + + real sz; + if(zunitsize == 0) { + if(zsize != 0) sz=calculateScaling("z",Coords.z,zsize,warn); + } else sz=zunitsize; + + if(sx == 0) { + sx=max(sy,sz); + if(sx == 0) + return identity(4); + } + if(sy == 0) sy=max(sz,sx); + if(sz == 0) sz=max(sx,sy); + + if(keepAspect && (xunitsize == 0 || yunitsize == 0 || zunitsize == 0)) + return scale3(min(sx,sy,sz)); + else + return scale(sx,sy,sz); + } + + transform3 scaling3(bool warn=true) { + return scaling(xsize3,ysize3,zsize3,keepAspect,warn); + } + + frame fit(transform t, transform T0=T, pair m, pair M) { + frame f; + for(int i=0; i < nodes.length; ++i) + nodes[i](f,t,T0,m,M); + return f; + } + + frame fit3(transform3 t, transform3 T0=T3, picture pic, projection P, + triple m, triple M) { + frame f; + for(int i=0; i < nodes3.length; ++i) + nodes3[i](f,t,T0,pic,P,m,M); + return f; + } + + // Returns a rigid version of the picture using t to transform user coords + // into truesize coords. + frame fit(transform t) { + return fit(t,min(t),max(t)); + } + + frame fit3(transform3 t, picture pic, projection P) { + return fit3(t,pic,P,min(t),max(t)); + } + + void add(void d(picture, transform), bool exact=false) { + add(new void(frame f, transform t) { + picture opic=new picture; + d(opic,t); + add(f,opic.fit(identity())); + },exact); + } + + void add(void d(picture, transform3), bool exact=false, bool above=true) { + add(new void(frame f, transform3 t, picture pic2, projection P) { + picture opic=new picture; + d(opic,t); + add(f,opic.fit3(identity4,pic2,P)); + },exact,above); + } + + void add(void d(picture, transform3, transform3, triple, triple), + bool exact=false, bool above=true) { + add(new void(frame f, transform3 t, transform3 T, picture pic2, + projection P, triple lb, triple rt) { + picture opic=new picture; + d(opic,t,T,lb,rt); + add(f,opic.fit3(identity4,pic2,P)); + },exact,above); + } + + frame scaled() { + frame f=fit(fixedscaling); + pair d=size(f); + static real epsilon=100*realEpsilon; + if(d.x > xsize*(1+epsilon)) + write("warning: frame exceeds xlimit: "+(string) d.x+" > "+ + (string) xsize); + if(d.y > ysize*(1+epsilon)) + write("warning: frame exceeds ylimit: "+(string) d.y+" > "+ + (string) ysize); + return f; + } + + // Calculate additional scaling required if only an approximate picture + // size estimate is available. + transform scale(frame f, real xsize=this.xsize, real ysize=this.ysize, + bool keepaspect=this.keepAspect) { + if(bounds.exact) return identity(); + pair m=min(f); + pair M=max(f); + real width=M.x-m.x; + real height=M.y-m.y; + real xgrow=xsize == 0 || width == 0 ? 1 : xsize/width; + real ygrow=ysize == 0 || height == 0 ? 1 : ysize/height; + if(keepAspect) { + real[] grow; + if(xsize > 0) grow.push(xgrow); + if(ysize > 0) grow.push(ygrow); + return scale(grow.length == 0 ? 1 : min(grow)); + } else return scale(xgrow,ygrow); + + } + + // Calculate additional scaling required if only an approximate thisture + // size estimate is available. + transform3 scale3(frame f, real xsize3=this.xsize3, + real ysize3=this.ysize3, real zsize3=this.zsize3, + bool keepaspect=this.keepAspect) { + if(bounds3.exact) return identity(4); + triple m=min3(f); + triple M=max3(f); + real width=M.x-m.x; + real height=M.y-m.y; + real depth=M.z-m.z; + real xgrow=xsize3 == 0 || width == 0 ? 1 : xsize3/width; + real ygrow=ysize3 == 0 || height == 0 ? 1 : ysize3/height; + real zgrow=zsize3 == 0 || depth == 0 ? 1 : zsize3/depth; + if(keepAspect) { + real[] grow; + if(xsize3 > 0) grow.push(xgrow); + if(ysize3 > 0) grow.push(ygrow); + if(zsize3 > 0) grow.push(zgrow); + return scale3(grow.length == 0 ? 1 : min(grow)); + } else return scale(xgrow,ygrow,zgrow); + } + + // Return the transform that would be used to fit the picture to a frame + transform calculateTransform(real xsize, real ysize, bool keepAspect=true, + bool warn=true) { + transform t=scaling(xsize,ysize,keepAspect,warn); + return scale(fit(t),xsize,ysize,keepAspect)*t; + } + + transform calculateTransform(bool warn=true) { + if(fixed) return fixedscaling; + return calculateTransform(xsize,ysize,keepAspect,warn); + } + + transform3 calculateTransform3(real xsize=xsize3, real ysize=ysize3, + real zsize=zsize3, + bool keepAspect=true, bool warn=true, + projection P=currentprojection) { + transform3 t=scaling(xsize,ysize,zsize,keepAspect,warn); + return scale3(fit3(t,null,P),keepAspect)*t; + } + + pair min(real xsize=this.xsize, real ysize=this.ysize, + bool keepAspect=this.keepAspect, bool warn=true) { + return min(calculateTransform(xsize,ysize,keepAspect,warn)); + } + + pair max(real xsize=this.xsize, real ysize=this.ysize, + bool keepAspect=this.keepAspect, bool warn=true) { + return max(calculateTransform(xsize,ysize,keepAspect,warn)); + } + + triple min3(real xsize=this.xsize3, real ysize=this.ysize3, + real zsize=this.zsize3, bool keepAspect=this.keepAspect, + bool warn=true, projection P) { + return min(calculateTransform3(xsize,ysize,zsize,keepAspect,warn,P)); + } + + triple max3(real xsize=this.xsize3, real ysize=this.ysize3, + real zsize=this.zsize3, bool keepAspect=this.keepAspect, + bool warn=true, projection P) { + return max(calculateTransform3(xsize,ysize,zsize,keepAspect,warn,P)); + } + + // Returns the 2D picture fit to the requested size. + frame fit2(real xsize=this.xsize, real ysize=this.ysize, + bool keepAspect=this.keepAspect) { + if(fixed) return scaled(); + if(empty2()) return newframe; + transform t=scaling(xsize,ysize,keepAspect); + frame f=fit(t); + transform s=scale(f,xsize,ysize,keepAspect); + if(s == identity()) return f; + return fit(s*t); + } + + static frame fitter(string,picture,string,real,real,bool,bool,string,string, + projection); + frame fit(string prefix="", string format="", + real xsize=this.xsize, real ysize=this.ysize, + bool keepAspect=this.keepAspect, bool view=false, + string options="", string script="", + projection P=currentprojection) { + return fitter == null ? fit2(xsize,ysize,keepAspect) : + fitter(prefix,this,format,xsize,ysize,keepAspect,view,options,script,P); + } + + // In case only an approximate picture size estimate is available, return the + // fitted frame slightly scaled (including labels and true size distances) + // so that it precisely meets the given size specification. + frame scale(real xsize=this.xsize, real ysize=this.ysize, + bool keepAspect=this.keepAspect) { + frame f=fit(xsize,ysize,keepAspect); + transform s=scale(f,xsize,ysize,keepAspect); + if(s == identity()) return f; + return s*f; + } + + // Copies the drawing information, but not the sizing information into a new + // picture. Fitting this picture will not scale as the original picture would. + picture drawcopy() { + picture dest=new picture; + dest.nodes=copy(nodes); + dest.nodes3=copy(nodes3); + dest.T=T; + dest.T3=T3; + dest.userCopy(this); + dest.scale=scale.copy(); + dest.legend=copy(legend); + + return dest; + } + + // A deep copy of this picture. Modifying the copied picture will not affect + // the original. + picture copy() { + picture dest=drawcopy(); + + dest.uptodate=uptodate; + dest.bounds=bounds.copy(); + dest.bounds3=bounds3.copy(); + + dest.xsize=xsize; dest.ysize=ysize; + dest.xsize3=xsize; dest.ysize3=ysize3; dest.zsize3=zsize3; + dest.keepAspect=keepAspect; + dest.xunitsize=xunitsize; dest.yunitsize=yunitsize; + dest.zunitsize=zunitsize; + dest.fixed=fixed; dest.fixedscaling=fixedscaling; + + return dest; + } + + // Add a picture to this picture, such that the user coordinates will be + // scaled identically when fitted + void add(picture src, bool group=true, filltype filltype=NoFill, + bool above=true) { + // Copy the picture. Only the drawing function closures are needed, so we + // only copy them. This needs to be a deep copy, as src could later have + // objects added to it that should not be included in this picture. + + if(src == this) abort("cannot add picture to itself"); + + uptodate=false; + + picture srcCopy=src.drawcopy(); + // Draw by drawing the copied picture. + if(srcCopy.nodes.length > 0) + nodes.push(new void(frame f, transform t, transform T, pair m, pair M) { + add(f,srcCopy.fit(t,T*srcCopy.T,m,M),group,filltype,above); + }); + + if(srcCopy.nodes3.length > 0) { + nodes3.push(new void(frame f, transform3 t, transform3 T3, picture pic, + projection P, triple m, triple M) { + add(f,srcCopy.fit3(t,T3*srcCopy.T3,pic,P,m,M),group,above); + }); + } + + legend.append(src.legend); + + if(src.userSetx) userBoxX(src.userMin.x,src.userMax.x); + if(src.userSety) userBoxY(src.userMin.y,src.userMax.y); + if(src.userSetz) userBoxZ(src.userMin.z,src.userMax.z); + + append(bounds.point,bounds.min,bounds.max,srcCopy.T,src.bounds); + append(bounds3.point,bounds3.min,bounds3.max,srcCopy.T3,src.bounds3); + + if(!src.bounds.exact) bounds.exact=false; + if(!src.bounds3.exact) bounds3.exact=false; + } +} + +picture operator * (transform t, picture orig) +{ + picture pic=orig.copy(); + pic.T=t*pic.T; + pic.userCorners(t*(pic.userMin.x,pic.userMin.y), + t*(pic.userMin.x,pic.userMax.y), + t*(pic.userMax.x,pic.userMin.y), + t*(pic.userMax.x,pic.userMax.y)); + pic.bounds.exact=false; + return pic; +} + +picture operator * (transform3 t, picture orig) +{ + picture pic=orig.copy(); + pic.T3=t*pic.T3; + pic.userCorners(t*pic.userMin, + t*(pic.userMin.x,pic.userMin.y,pic.userMax.z), + t*(pic.userMin.x,pic.userMax.y,pic.userMin.z), + t*(pic.userMin.x,pic.userMax.y,pic.userMax.z), + t*(pic.userMax.x,pic.userMin.y,pic.userMin.z), + t*(pic.userMax.x,pic.userMin.y,pic.userMax.z), + t*(pic.userMax.x,pic.userMax.y,pic.userMin.z), + t*pic.userMax); + pic.bounds3.exact=false; + return pic; +} + +picture currentpicture; + +void size(picture pic=currentpicture, real x, real y=x, + bool keepAspect=pic.keepAspect) +{ + pic.size(x,y,keepAspect); +} + +void size3(picture pic=currentpicture, real x, real y=x, real z=y, + bool keepAspect=pic.keepAspect) +{ + pic.size3(x,y,z,keepAspect); +} + +void unitsize(picture pic=currentpicture, real x, real y=x, real z=y) +{ + pic.unitsize(x,y,z); +} + +void size(picture pic=currentpicture, real xsize, real ysize, + pair min, pair max) +{ + pair size=max-min; + pic.unitsize(size.x != 0 ? xsize/size.x : 0, + size.y != 0 ? ysize/size.y : 0); +} + +void size(picture dest, picture src) +{ + dest.size(src.xsize,src.ysize,src.keepAspect); + dest.size3(src.xsize3,src.ysize3,src.zsize3,src.keepAspect); + dest.unitsize(src.xunitsize,src.yunitsize,src.zunitsize); +} + +pair min(picture pic, bool user=false) +{ + transform t=pic.calculateTransform(); + pair z=pic.min(t); + return user ? inverse(t)*z : z; +} + +pair max(picture pic, bool user=false) +{ + transform t=pic.calculateTransform(); + pair z=pic.max(t); + return user ? inverse(t)*z : z; +} + +pair size(picture pic, bool user=false) +{ + transform t=pic.calculateTransform(); + pair M=pic.max(t); + pair m=pic.min(t); + if(!user) return M-m; + t=inverse(t); + return t*M-t*m; +} + +pair point(picture pic=currentpicture, pair dir, bool user=true) +{ + pair z=pic.userMin()+realmult(rectify(dir),pic.userMax()-pic.userMin()); + return user ? z : pic.calculateTransform()*z; +} + +pair truepoint(picture pic=currentpicture, pair dir, bool user=true) +{ + transform t=pic.calculateTransform(); + pair m=pic.min(t); + pair M=pic.max(t); + pair z=m+realmult(rectify(dir),M-m); + return user ? inverse(t)*z : z; +} + +// Transform coordinate in [0,1]x[0,1] to current user coordinates. +pair relative(picture pic=currentpicture, pair z) +{ + return pic.userMin()+realmult(z,pic.userMax()-pic.userMin()); +} + +void add(picture pic=currentpicture, drawer d, bool exact=false) +{ + pic.add(d,exact); +} + +void add(picture pic=currentpicture, void d(picture,transform), + bool exact=false) +{ + pic.add(d,exact); +} + +void begingroup(picture pic=currentpicture) +{ + pic.add(new void(frame f, transform) { + begingroup(f); + },true); +} + +void endgroup(picture pic=currentpicture) +{ + pic.add(new void(frame f, transform) { + endgroup(f); + },true); +} + +void Draw(picture pic=currentpicture, path g, pen p=currentpen) +{ + pic.add(new void(frame f, transform t) { + draw(f,t*g,p); + },true); + pic.addPath(g,p); +} + +void _draw(picture pic=currentpicture, path g, pen p=currentpen, + margin margin=NoMargin) +{ + pic.add(new void(frame f, transform t) { + draw(f,margin(t*g,p).g,p); + },true); + pic.addPath(g,p); +} + +void Draw(picture pic=currentpicture, explicit path[] g, pen p=currentpen) +{ + for(int i=0; i < g.length; ++i) Draw(pic,g[i],p); +} + +void fill(picture pic=currentpicture, path[] g, pen p=currentpen, + bool copy=true) +{ + if(copy) + g=copy(g); + pic.add(new void(frame f, transform t) { + fill(f,t*g,p,false); + },true); + pic.addPath(g); +} + +void latticeshade(picture pic=currentpicture, path[] g, bool stroke=false, + pen fillrule=currentpen, pen[][] p, bool copy=true) +{ + if(copy) { + g=copy(g); + p=copy(p); + } + pic.add(new void(frame f, transform t) { + latticeshade(f,t*g,stroke,fillrule,p,false); + },true); + pic.addPath(g); +} + +void axialshade(picture pic=currentpicture, path[] g, bool stroke=false, + pen pena, pair a, pen penb, pair b, bool copy=true) +{ + if(copy) + g=copy(g); + pic.add(new void(frame f, transform t) { + axialshade(f,t*g,stroke,pena,t*a,penb,t*b,false); + },true); + pic.addPath(g); +} + +void radialshade(picture pic=currentpicture, path[] g, bool stroke=false, + pen pena, pair a, real ra, pen penb, pair b, real rb, + bool copy=true) +{ + if(copy) + g=copy(g); + pic.add(new void(frame f, transform t) { + pair A=t*a, B=t*b; + real RA=abs(t*(a+ra)-A); + real RB=abs(t*(b+rb)-B); + radialshade(f,t*g,stroke,pena,A,RA,penb,B,RB,false); + },true); + pic.addPath(g); +} + +void gouraudshade(picture pic=currentpicture, path[] g, bool stroke=false, + pen fillrule=currentpen, pen[] p, pair[] z, int[] edges, + bool copy=true) +{ + if(copy) { + g=copy(g); + p=copy(p); + z=copy(z); + edges=copy(edges); + } + pic.add(new void(frame f, transform t) { + gouraudshade(f,t*g,stroke,fillrule,p,t*z,edges,false); + },true); + pic.addPath(g); +} + +void gouraudshade(picture pic=currentpicture, path[] g, bool stroke=false, + pen fillrule=currentpen, pen[] p, int[] edges, bool copy=true) +{ + if(copy) { + g=copy(g); + p=copy(p); + edges=copy(edges); + } + pic.add(new void(frame f, transform t) { + gouraudshade(f,t*g,stroke,fillrule,p,edges,false); + },true); + pic.addPath(g); +} + +void tensorshade(picture pic=currentpicture, path[] g, bool stroke=false, + pen fillrule=currentpen, pen[][] p, path[] b=g, + pair[][] z=new pair[][], bool copy=true) +{ + if(copy) { + g=copy(g); + p=copy(p); + b=copy(b); + z=copy(z); + } + pic.add(new void(frame f, transform t) { + pair[][] Z=new pair[z.length][]; + for(int i=0; i < z.length; ++i) + Z[i]=t*z[i]; + tensorshade(f,t*g,stroke,fillrule,p,t*b,Z,false); + },true); + pic.addPath(g); +} + +void tensorshade(frame f, path[] g, bool stroke=false, + pen fillrule=currentpen, pen[] p, + path b=g.length > 0 ? g[0] : nullpath) +{ + tensorshade(f,g,stroke,fillrule,new pen[][] {p},b); +} + +void tensorshade(frame f, path[] g, bool stroke=false, + pen fillrule=currentpen, pen[] p, + path b=g.length > 0 ? g[0] : nullpath, pair[] z) +{ + tensorshade(f,g,stroke,fillrule,new pen[][] {p},b,new pair[][] {z}); +} + +void tensorshade(picture pic=currentpicture, path[] g, bool stroke=false, + pen fillrule=currentpen, pen[] p, + path b=g.length > 0 ? g[0] : nullpath) +{ + tensorshade(pic,g,stroke,fillrule,new pen[][] {p},b); +} + +void tensorshade(picture pic=currentpicture, path[] g, bool stroke=false, + pen fillrule=currentpen, pen[] p, + path b=g.length > 0 ? g[0] : nullpath, pair[] z) +{ + tensorshade(pic,g,stroke,fillrule,new pen[][] {p},b,new pair[][] {z}); +} + +// Smoothly shade the regions between consecutive paths of a sequence using a +// given array of pens: +void draw(picture pic=currentpicture, path[] g, pen[] p) +{ + path[] G; + pen[][] P; + string differentlengths="arrays have different lengths"; + if(g.length != p.length) abort(differentlengths); + for(int i=0; i < g.length-1; ++i) { + path g0=g[i]; + path g1=g[i+1]; + if(length(g0) != length(g1)) abort(differentlengths); + for(int j=0; j < length(g0); ++j) { + G.push(subpath(g0,j,j+1)--reverse(subpath(g1,j,j+1))--cycle); + P.push(new pen[] {p[i],p[i],p[i+1],p[i+1]}); + } + } + tensorshade(pic,G,P); +} + +void functionshade(picture pic=currentpicture, path[] g, bool stroke=false, + pen fillrule=currentpen, string shader, bool copy=true) +{ + if(copy) + g=copy(g); + pic.add(new void(frame f, transform t) { + functionshade(f,t*g,stroke,fillrule,shader); + },true); + pic.addPath(g); +} + +void filldraw(picture pic=currentpicture, path[] g, pen fillpen=currentpen, + pen drawpen=currentpen) +{ + begingroup(pic); + fill(pic,g,fillpen); + Draw(pic,g,drawpen); + endgroup(pic); +} + +void clip(picture pic=currentpicture, path[] g, bool stroke=false, + pen fillrule=currentpen, bool copy=true) +{ + if(copy) + g=copy(g); + pic.userClip(min(g),max(g)); + pic.clip(new void(frame f, transform t) { + clip(f,t*g,stroke,fillrule,false); + },true); +} + +void beginclip(picture pic=currentpicture, path[] g, bool stroke=false, + pen fillrule=currentpen, bool copy=true) +{ + if(copy) + g=copy(g); + + pic.clipmin.push(min(g)); + pic.clipmax.push(max(g)); + + pic.add(new void(frame f, transform t) { + beginclip(f,t*g,stroke,fillrule,false); + },true); +} + +void endclip(picture pic=currentpicture) +{ + if(pic.clipmin.length > 0 && pic.clipmax.length > 0) + pic.userClip(pic.clipmin.pop(),pic.clipmax.pop()); + pic.clip(new void(frame f, transform) { + endclip(f); + },true); +} + +void unfill(picture pic=currentpicture, path[] g, bool copy=true) +{ + if(copy) + g=copy(g); + pic.add(new void(frame f, transform t) { + unfill(f,t*g,false); + },true); +} + +void filloutside(picture pic=currentpicture, path[] g, pen p=currentpen, + bool copy=true) +{ + if(copy) + g=copy(g); + pic.add(new void(frame f, transform t) { + filloutside(f,t*g,p,false); + },true); + pic.addPath(g); +} + +// Use a fixed scaling to map user coordinates in box(min,max) to the +// desired picture size. +transform fixedscaling(picture pic=currentpicture, pair min, pair max, + pen p=nullpen, bool warn=false) +{ + Draw(pic,min,p+invisible); + Draw(pic,max,p+invisible); + pic.fixed=true; + return pic.fixedscaling=pic.calculateTransform(pic.xsize,pic.ysize, + pic.keepAspect); +} + +// Add frame src about position to frame dest with optional grouping. +void add(frame dest, frame src, pair position, bool group=false, + filltype filltype=NoFill, bool above=true) +{ + add(dest,shift(position)*src,group,filltype,above); +} + +// Add frame src about position to picture dest with optional grouping. +void add(picture dest=currentpicture, frame src, pair position=0, + bool group=true, filltype filltype=NoFill, bool above=true) +{ + if(is3D(src)) { + dest.add(new void(frame f, transform3, picture, projection) { + add(f,src); // always add about 3D origin (ignore position) + },true); + dest.addBox((0,0,0),(0,0,0),min3(src),max3(src)); + } else { + dest.add(new void(frame f, transform t) { + add(f,shift(t*position)*src,group,filltype,above); + },true); + dest.addBox(position,position,min(src),max(src)); + } +} + +// Like add(picture,frame,pair) but extend picture to accommodate frame. +void attach(picture dest=currentpicture, frame src, pair position=0, + bool group=true, filltype filltype=NoFill, bool above=true) +{ + transform t=dest.calculateTransform(); + add(dest,src,position,group,filltype,above); + pair s=size(dest.fit(t)); + size(dest,dest.xsize != 0 ? s.x : 0,dest.ysize != 0 ? s.y : 0); +} + +// Like add(picture,frame,pair) but align frame in direction align. +void add(picture dest=currentpicture, frame src, pair position, pair align, + bool group=true, filltype filltype=NoFill, bool above=true) +{ + add(dest,align(src,align),position,group,filltype,above); +} + +// Like attach(picture,frame,pair) but extend picture to accommodate frame; +void attach(picture dest=currentpicture, frame src, pair position, + pair align, bool group=true, filltype filltype=NoFill, + bool above=true) +{ + attach(dest,align(src,align),position,group,filltype,above); +} + +// Add a picture to another such that user coordinates in both will be scaled +// identically in the shipout. +void add(picture dest, picture src, bool group=true, filltype filltype=NoFill, + bool above=true) +{ + dest.add(src,group,filltype,above); +} + +void add(picture src, bool group=true, filltype filltype=NoFill, + bool above=true) +{ + currentpicture.add(src,group,filltype,above); +} + +// Fit the picture src using the identity transformation (so user +// coordinates and truesize coordinates agree) and add it about the point +// position to picture dest. +void add(picture dest, picture src, pair position, bool group=true, + filltype filltype=NoFill, bool above=true) +{ + add(dest,src.fit(identity()),position,group,filltype,above); +} + +void add(picture src, pair position, bool group=true, filltype filltype=NoFill, + bool above=true) +{ + add(currentpicture,src,position,group,filltype,above); +} + +// Fill a region about the user-coordinate 'origin'. +void fill(pair origin, picture pic=currentpicture, path[] g, pen p=currentpen) +{ + picture opic; + fill(opic,g,p); + add(pic,opic,origin); +} + +void postscript(picture pic=currentpicture, string s) +{ + pic.add(new void(frame f, transform) { + postscript(f,s); + },true); +} + +void tex(picture pic=currentpicture, string s) +{ + pic.add(new void(frame f, transform) { + tex(f,s); + },true); +} + +void postscript(picture pic=currentpicture, string s, pair min, pair max) +{ + pic.add(new void(frame f, transform t) { + postscript(f,s,t*min,t*max); + },true); +} + +void tex(picture pic=currentpicture, string s, pair min, pair max) +{ + pic.add(new void(frame f, transform t) { + tex(f,s,t*min,t*max); + },true); +} + +void layer(picture pic=currentpicture) +{ + pic.add(new void(frame f, transform) { + layer(f); + },true); +} + +void erase(picture pic=currentpicture) +{ + pic.uptodate=false; + pic.erase(); +} |