// Useful lossy PRC compression values.
restricted real Zero=0;
restricted real Single=0.000001;
restricted real Low=0.0001;
restricted real Medium=0.001;
restricted real High=0.01;
restricted int PRCsphere=0; // Renders slowly but produces smaller PRC files.
restricted int NURBSsphere=1; // Renders fast but produces larger PRC files.
struct interaction
{
int type;
triple center; // position to rotate billboard objects about
bool targetsize;
static interaction defaultinteraction;
struct render
{
real compression; // lossy PRC compression parameter (0=no compression)
real granularity; // PRC rendering granularity
bool closed; // use one-sided PRC rendering?
bool tessellate; // use tessellated mesh to store straight patches
bool3 merge; // merge PRC nodes before rendering, for faster but
// lower quality rendering (the value default means
// merge opaque patches only).
int sphere; // PRC sphere type (PRCsphere or NURBSsphere).
// General parameters:
real margin; // shrink amount for rendered OpenGL viewport, in bp.
bool partnames; // assign part name indices to compound objects
bool defaultnames; // assign default names to unnamed objects
interaction interaction; // billboard interaction mode
static render defaultrender;
void operator init(render render=defaultrender,
real compression=render.compression,
real granularity=render.granularity,
bool closed=render.closed,
bool tessellate=render.tessellate,
bool3 merge=render.merge,
int sphere=render.sphere,
real margin=render.margin,
bool partnames=render.partnames,
bool defaultnames=render.defaultnames,
interaction interaction=render.interaction)
{
this.compression=compression;
this.granularity=granularity;
this.closed=closed;
this.tessellate=tessellate;
this.merge=merge;
this.sphere=sphere;
this.margin=margin;
this.partnames=partnames;
this.defaultnames=defaultnames;
this.interaction=interaction;
}
}
// A 3D scaling in the x direction.
transform3 xscale3(real x)
{
transform3 t=identity(4);
t[0][0]=x;
return t;
}
// A 3D scaling in the y direction.
transform3 yscale3(real y)
{
transform3 t=identity(4);
t[1][1]=y;
return t;
}
// A 3D scaling in the z direction.
transform3 zscale3(real z)
{
transform3 t=identity(4);
t[2][2]=z;
return t;
}
// A 3D scaling by s in the v direction.
transform3 scale(triple v, real s)
{
v=unit(v);
s -= 1;
return new real[][] {
{1+s*v.x^2, s*v.x*v.y, s*v.x*v.z, 0},
{s*v.x*v.y, 1+s*v.y^2, s*v.y*v.z, 0},
{s*v.x*v.z, s*v.y*v.z, 1+s*v.z^2, 0},
{0, 0, 0, 1}};
}
// A transformation representing rotation by an angle in degrees about
// an axis v through the origin (in the right-handed direction).
transform3 rotate(real angle, triple v)
{
if(v == O) abort("cannot rotate about the zero vector");
v=unit(v);
real x=v.x, y=v.y, z=v.z;
real s=Sin(angle), c=Cos(angle), t=1-c;
// A transformation representing rotation by an angle in degrees about
// the line u--v (in the right-handed direction).
transform3 rotate(real angle, triple u, triple v)
{
return shift(u)*rotate(angle,v-u)*shift(-u);
}
// Reflects about the plane through u, v, and w.
transform3 reflect(triple u, triple v, triple w)
{
triple n=unit(cross(v-u,w-u));
if(n == O)
abort("points determining reflection plane cannot be colinear");
// Project u onto v.
triple project(triple u, triple v)
{
v=unit(v);
return dot(u,v)*v;
}
// Return a unit vector perpendicular to a given unit vector v.
triple perp(triple v)
{
triple u=cross(v,Y);
real norm=sqrtEpsilon*abs(v);
if(abs(u) > norm) return unit(u);
u=cross(v,Z);
return (abs(u) > norm) ? unit(u) : X;
}
// Return the transformation corresponding to moving the camera from the target
// (looking in the negative z direction) to the point 'eye' (looking at target,
// orienting the camera so that direction 'up' points upwards.
// Since, in actuality, we are transforming the points instead of the camera,
// we calculate the inverse matrix.
// Based on the gluLookAt implementation in the OpenGL manual.
transform3 look(triple eye, triple up=Z, triple target=O)
{
triple f=unit(target-eye);
if(f == O)
f=-Z; // The eye is already at the origin: look down.
triple s=cross(f,up);
// If the eye is pointing either directly up or down, there is no
// preferred "up" direction. Pick one arbitrarily.
s=s != O ? unit(s) : perp(f);
// Return a matrix to do perspective distortion based on a triple v.
transform3 distort(triple v)
{
transform3 t=identity(4);
real d=length(v);
if(d == 0) return t;
t[3][2]=-1/d;
t[3][3]=0;
return t;
}
// With this, save() and restore() in plain also save and restore the
// currentprojection.
addSaveFunction(new restoreThunk() {
projection P=currentprojection.copy();
return new void() {
currentprojection=P;
};
});
// Uses the homogenous coordinate to perform perspective distortion.
// When combined with a projection to the XY plane, this effectively maps
// points in three space to a plane through target and
// perpendicular to the vector camera-target.
projection perspective(triple camera, triple up=Z, triple target=O,
real zoom=1, real angle=0, pair viewportshift=0,
bool showtarget=true, bool autoadjust=true,
bool center=autoadjust)
{
if(camera == target)
abort("camera cannot be at target");
return projection(camera,up,target,zoom,angle,viewportshift,
showtarget,autoadjust,center,
new transformation(triple camera, triple up, triple target)
{return transformation(look(camera,up,target),
distort(camera-target));});
}
projection perspective(real x, real y, real z, triple up=Z, triple target=O,
real zoom=1, real angle=0, pair viewportshift=0,
bool showtarget=true, bool autoadjust=true,
bool center=autoadjust)
{
return perspective((x,y,z),up,target,zoom,angle,viewportshift,showtarget,
autoadjust,center);
}
projection orthographic(real x, real y, real z, triple up=Z,
triple target=O, real zoom=1, pair viewportshift=0,
bool showtarget=true, bool center=true)
{
return orthographic((x,y,z),up,target,zoom,viewportshift,showtarget,
center=center);
}
// Compute camera position with x axis below the horizontal at angle alpha,
// y axis below the horizontal at angle beta, and z axis up.
triple camera(real alpha, real beta)
{
real denom=Tan(alpha+beta);
real Tanalpha=Tan(alpha);
real Tanbeta=Tan(beta);
return (sqrt(Tanalpha/denom),sqrt(Tanbeta/denom),sqrt(Tanalpha*Tanbeta));
}
projection oblique(real angle=45)
{
transform3 t=identity(4);
real c2=Cos(angle)^2;
real s2=1-c2;
t[0][2]=-c2;
t[1][2]=-s2;
t[2][2]=1;
t[2][3]=-1;
return projection((c2,s2,1),up=Y,normal=(0,0,1),
new transformation(triple,triple,triple) {
return transformation(t);});
}
// Map pair z to a triple by inverting the projection P onto the
// plane perpendicular to normal and passing through point.
triple invert(pair z, triple normal, triple point,
projection P=currentprojection)
{
transform3 t=P.t;
real[][] A={{t[0][0]-z.x*t[3][0],t[0][1]-z.x*t[3][1],t[0][2]-z.x*t[3][2]},
{t[1][0]-z.y*t[3][0],t[1][1]-z.y*t[3][1],t[1][2]-z.y*t[3][2]},
{normal.x,normal.y,normal.z}};
real[] b={z.x*t[3][3]-t[0][3],z.y*t[3][3]-t[1][3],dot(normal,point)};
real[] x=solve(A,b,warn=false);
return x.length > 0 ? (x[0],x[1],x[2]) : P.camera;
}
// Map pair to a triple on the projection plane.
triple invert(pair z, projection P=currentprojection)
{
return invert(z,P.normal,P.target,P);
}
// Map pair dir to a triple direction at point v on the projection plane.
triple invert(pair dir, triple v, projection P=currentprojection)
{
return invert(project(v,P)+dir,P.normal,v,P)-v;
}
dir operator * (transform3 t, dir d)
{
dir D=d.copy();
D.init(unit(shiftless(t)*d.dir));
return D;
}
void checkEmpty(int n) {
if(n == 0)
abort("nullpath3 has no points");
}
int adjustedIndex(int i, int n, bool cycles)
{
checkEmpty(n);
if(cycles)
return i % n;
else if(i < 0)
return 0;
else if(i >= n)
return n-1;
else
return i;
}
struct flatguide3 {
triple[] nodes;
bool[] cyclic; // true if node is really a cycle
control[] control; // control points for segment starting at node
Tension[] Tension; // Tension parameters for segment starting at node
dir[] in,out; // in and out directions for segment starting at node
bool cyclic() {int n=cyclic.length; return n > 0 ? cyclic[n-1] : false;}
bool precyclic() {int i=find(cyclic); return i >= 0 && i < cyclic.length-1;}
int size() {
return cyclic() ? nodes.length-1 : nodes.length;
}
// A version of asin that tolerates numerical imprecision
real asin1(real x)
{
return asin(min(max(x,-1),1));
}
// A version of acos that tolerates numerical imprecision
real acos1(real x)
{
return acos(min(max(x,-1),1));
}
struct Controls {
triple c0,c1;
// 3D extension of John Hobby's control point formula
// (cf. The MetaFont Book, page 131),
// as described in John C. Bowman and A. Hammerlindl,
// TUGBOAT: The Communications of the TeX Users Group 29:2 (2008).
void operator init(triple v0, triple v1, triple d0, triple d1, real tout,
real tin, bool atLeast) {
triple v=v1-v0;
triple u=unit(v);
real L=length(v);
d0=unit(d0);
d1=unit(d1);
real theta=acos1(dot(d0,u));
real phi=acos1(dot(d1,u));
if(dot(cross(d0,v),cross(v,d1)) < 0) phi=-phi;
c0=v0+d0*L*relativedistance(theta,phi,tout,atLeast);
c1=v1-d1*L*relativedistance(phi,theta,tin,atLeast);
}
}
private triple cross(triple d0, triple d1, triple reference)
{
triple normal=cross(d0,d1);
return normal == O ? reference : normal;
}
private real angle(triple d0, triple d1, triple reference)
{
real theta=acos1(dot(unit(d0),unit(d1)));
return dot(cross(d0,d1,reference),reference) >= 0 ? theta : -theta;
}
// 3D extension of John Hobby's angle formula (The MetaFont Book, page 131).
// Notational differences: here psi[i] is the turning angle at z[i+1],
// beta[i] is the tension for segment i, and in[i] is the incoming
// direction for segment i (where segment i begins at node i).
real[] theta(triple[] v, real[] alpha, real[] beta,
triple dir0, triple dirn, real g0, real gn, triple reference)
{
real[] a,b,c,f,l,psi;
int n=alpha.length;
bool cyclic=v.cyclic;
for(int i=0; i < n; ++i)
l[i]=1/length(v[i+1]-v[i]);
int i0,in;
if(cyclic) {i0=0; in=n;}
else {i0=1; in=n-1;}
for(int i=0; i < in; ++i)
psi[i]=angle(v[i+1]-v[i],v[i+2]-v[i+1],reference);
if(cyclic) {
l.cyclic=true;
psi.cyclic=true;
} else {
psi[n-1]=0;
if(dir0 == O) {
real a0=alpha[0];
real b0=beta[0];
real chi=g0*(b0/a0)^2;
a[0]=0;
b[0]=3a0-a0/b0+chi;
real C=chi*(3a0-1)+a0/b0;
c[0]=C;
f[0]=-C*psi[0];
} else {
a[0]=c[0]=0;
b[0]=1;
f[0]=angle(v[1]-v[0],dir0,reference);
}
if(dirn == O) {
real an=alpha[n-1];
real bn=beta[n-1];
real chi=gn*(an/bn)^2;
a[n]=chi*(3bn-1)+bn/an;
b[n]=3bn-bn/an+chi;
c[n]=f[n]=0;
} else {
a[n]=c[n]=0;
b[n]=1;
f[n]=angle(v[n]-v[n-1],dirn,reference);
}
}
for(int i=i0; i < n; ++i) {
real in=beta[i-1]^2*l[i-1];
real A=in/alpha[i-1];
a[i]=A;
real B=3*in-A;
real out=alpha[i]^2*l[i];
real C=out/beta[i];
b[i]=B+3*out-C;
c[i]=C;
f[i]=-B*psi[i-1]-C*psi[i];
}
// Fill in missing directions for n cyclic nodes.
void aim(flatguide3 g, int N)
{
bool cyclic=true;
int start=0, end=0;
// If the cycle contains one or more direction specifiers, break the loop.
for(int k=0; k < N; ++k)
if(g.solved(k)) {cyclic=false; end=k; break;}
for(int k=N-1; k >= 0; --k)
if(g.solved(k)) {cyclic=false; start=k; break;}
while(start < N && g.control[start].active) ++start;
int n=N-(start-end);
if(n <= 1 || (cyclic && n <= 2)) return;
// Fill in missing directions for the sequence of nodes i...n.
void aim(flatguide3 g, int i, int n)
{
int j=n-i;
if(j > 1 || g.out[i].dir != O || g.in[i].dir != O) {
triple[] v=new triple[j+1];
real[] alpha=new real[j];
real[] beta=new real[j];
for(int k=0; k < j; ++k) {
v[k]=g.nodes[i+k];
alpha[k]=g.Tension[i+k].out;
beta[k]=g.Tension[i+k].in;
}
v[j]=g.nodes[n];
// Construct a path from a path3 by applying P to each control point.
path path(path3 p, pair P(triple)=xypart)
{
int n=length(p);
if(n < 0) return nullpath;
guide g=P(point(p,0));
if(n == 0) return g;
for(int i=1; i < n; ++i)
g=straight(p,i-1) ? g--P(point(p,i)) :
g..controls P(postcontrol(p,i-1)) and P(precontrol(p,i))..P(point(p,i));
pair post=P(postcontrol(p,n-1));
pair pre=P(precontrol(p,n));
return cyclic(p) ? g..controls post and pre..cycle :
g..controls post and pre..P(point(p,n));
}
// Fill in empty direction specifiers inherited from explicit control points.
for(int i=0; i < n; ++i) {
if(g.control[i].active) {
g.out[i].init(g.control[i].post-g.nodes[i]);
g.in[i].init(g.nodes[i+1]-g.control[i].pre);
}
}
// Propagate directions across nodes.
for(int i=0; i < n; ++i) {
int next=g.cyclic[i+1] ? 0 : i+1;
if(g.out[next].active())
g.in[i].default(g.out[next]);
if(g.in[i].active()) {
g.out[next].default(g.in[i]);
g.out[i+1].default(g.in[i]);
}
}
// Compute missing 3D directions.
// First, resolve cycles
int i=find(g.cyclic);
if(i > 0) {
aim(g,i);
// All other cycles can now be reduced to sequences.
triple v=g.out[0].dir;
for(int j=i; j <= n; ++j) {
if(g.cyclic[j]) {
g.in[j-1].default(v);
g.out[j].default(v);
if(g.nodes[j-1] == g.nodes[j] && !g.control[j-1].active)
g.control[j-1]=control(g.nodes[j-1],g.nodes[j-1]);
}
}
}
// Next, resolve sequences.
int i=0;
int start=0;
while(i < n) {
// Look for a missing outgoing direction.
while(i <= n && g.solved(i)) {start=i; ++i;}
if(i > n) break;
// Look for the end of the sequence.
while(i < n && !g.solved(i)) ++i;
while(start < i && g.control[start].active) ++start;
if(start < i)
aim(g,start,i);
}
// Compute missing 3D control points.
for(int i=0; i < n; ++i) {
int next=g.cyclic[i+1] ? 0 : i+1;
if(!g.control[i].active) {
control c;
if((g.out[i].Curl && g.in[i].Curl) ||
(g.out[i].dir == O && g.in[i].dir == O)) {
// fill in straight control points for path3 functions
triple delta=(g.nodes[i+1]-g.nodes[i])/3;
c=control(g.nodes[i]+delta,g.nodes[i+1]-delta,straight=true);
} else {
Controls C=Controls(g.nodes[i],g.nodes[next],g.out[i].dir,g.in[i].dir,
g.Tension[i].out,g.Tension[i].in,
g.Tension[i].atLeast);
c=control(C.c0,C.c1);
}
g.control[i]=c;
}
}
// Return a unit normal vector to a planar path p (or O if the path is
// nonplanar).
triple normal(path3 p)
{
triple normal;
real fuzz=sqrtEpsilon*abs(max(p)-min(p));
real absnormal;
real theta;
// Return a unit normal vector to a polygon with vertices in p.
triple normal(triple[] p)
{
triple normal;
real fuzz=sqrtEpsilon*abs(maxbound(p)-minbound(p));
real absnormal;
real theta;
// Transform3 that projects in direction dir onto plane with normal n
// through point O.
transform3 planeproject(triple n, triple O=O, triple dir=n)
{
real a=n.x, b=n.y, c=n.z;
real u=dir.x, v=dir.y, w=dir.z;
real delta=1.0/(a*u+b*v+c*w);
real d=-(a*O.x+b*O.y+c*O.z)*delta;
return new real[][] {
{(b*v+c*w)*delta,-b*u*delta,-c*u*delta,-d*u},
{-a*v*delta,(a*u+c*w)*delta,-c*v*delta,-d*v},
{-a*w*delta,-b*w*delta,(a*u+b*v)*delta,-d*w},
{0,0,0,1}
};
}
// Transform3 that projects in direction dir onto plane defined by p.
transform3 planeproject(path3 p, triple dir=O)
{
triple n=normal(p);
return planeproject(n,point(p,0),dir == O ? n : dir);
}
// Transform for projecting onto plane through point O with normal cross(u,v).
transform transform(triple u, triple v, triple O=O,
projection P=currentprojection)
{
transform3 t=P.t;
real[] tO=t*new real[] {O.x,O.y,O.z,1};
real tO3=tO[3];
real factor=1/tO3^2;
real[] x=(tO3*t[0]-tO[0]*t[3])*factor;
real[] y=(tO3*t[1]-tO[1]*t[3])*factor;
triple x=(x[0],x[1],x[2]);
triple y=(y[0],y[1],y[2]);
u=unit(u);
v=unit(v);
return (0,0,dot(u,x),dot(v,x),dot(u,y),dot(v,y));
}
// Project Label onto plane through point O with normal cross(u,v).
Label project(Label L, triple u, triple v, triple O=O,
projection P=currentprojection) {
Label L=L.copy();
L.position=project(O,P.t);
L.transform(transform(u,v,O,P));
return L;
}
int n=f.size();
checkEmpty(n);
guide3 G;
if(n >= 0) {
int start=cyclic ? n : n-1;
for(int i=start; i > 0; --i) {
G=G..f.nodes[i];
control c=f.control[i-1];
if(c.active)
G=G..operator controls(c.pre,c.post);
else {
dir in=f.in[i-1];
triple d=in.dir;
if(d != O) G=G..operator spec(-d,JOIN_OUT);
else if(in.Curl) G=G..operator curl(in.gamma,JOIN_OUT);
dir out=f.out[i-1];
triple d=out.dir;
if(d != O) G=G..operator spec(-d,JOIN_IN);
else if(out.Curl) G=G..operator curl(out.gamma,JOIN_IN);
}
}
if(cyclic) G=G..cycle;
else G=G..f.nodes[0];
}
return G;
}
triple intersectionpoint(path3 p, path3 q, real fuzz=-1)
{
real[] t=intersect(p,q,fuzz);
if(t.length == 0) abort("paths do not intersect");
return point(p,t[0]);
}
// return an array containing all intersection points of p and q
triple[] intersectionpoints(path3 p, path3 q, real fuzz=-1)
{
real[][] t=intersections(p,q,fuzz);
return sequence(new triple(int i) {return point(p,t[i][0]);},t.length);
}
triple[] intersectionpoints(explicit path3[] p, explicit path3[] q,
real fuzz=-1)
{
triple[] v;
for(int i=0; i < p.length; ++i)
for(int j=0; j < q.length; ++j)
v.append(intersectionpoints(p[i],q[j],fuzz));
return v;
}
// return the linear transformation that maps X,Y,Z to u,v,w.
transform3 transform3(triple u, triple v, triple w=cross(u,v))
{
return new real[][] {
{u.x,v.x,w.x,0},
{u.y,v.y,w.y,0},
{u.z,v.z,w.z,0},
{0,0,0,1}
};
}
// return the rotation that maps Z to a unit vector u about cross(u,Z),
transform3 align(triple u)
{
real a=u.x;
real b=u.y;
real c=u.z;
real d=a^2+b^2;
if(d != 0) {
d=sqrt(d);
real e=1/d;
return new real[][] {
{-b*e,-a*c*e,a,0},
{a*e,-b*c*e,b,0},
{0,d,c,0},
{0,0,0,1}};
}
return c >= 0 ? identity(4) : diagonal(1,-1,-1,1);
}
// Align Label with normal in direction dir.
Label align(Label L, triple dir)
{
Label L=L.copy();
L.transform3(align(unit(dir)));
return L;
}
// return a rotation that maps X,Y to the projection plane.
transform3 transform3(projection P=currentprojection)
{
triple w=unit(P.normal);
triple v=unit(perp(P.up,w));
if(v == O) v=cross(perp(w),w);
triple u=cross(v,w);
return u != O ? transform3(u,v,w) : identity(4);
}
triple[] triples(real[] x, real[] y, real[] z)
{
if(x.length != y.length || x.length != z.length)
abort("arrays have different lengths");
return sequence(new triple(int i) {return (x[i],y[i],z[i]);},x.length);
}
path3[] operator cast(path3 p)
{
return new path3[] {p};
}
path3 circle(triple c, real r, triple normal=Z)
{
path3 p=normal == Z ? unitcircle3 : align(unit(normal))*unitcircle3;
return shift(c)*scale3(r)*p;
}
// return an arc centered at c from triple v1 to v2 (assuming |v2-c|=|v1-c|),
// drawing in the given direction.
// The normal must be explicitly specified if c and the endpoints are colinear.
path3 arc(triple c, triple v1, triple v2, triple normal=O, bool direction=CCW)
{
v1 -= c;
real r=abs(v1);
v1=unit(v1);
v2=unit(v2-c);
if(normal == O) {
normal=cross(v1,v2);
if(normal == O) abort("explicit normal required for these endpoints");
}
// return an arc centered at c with radius r from c+r*dir(theta1,phi1) to
// c+r*dir(theta2,phi2) in degrees, drawing in the given direction
// relative to the normal vector cross(dir(theta1,phi1),dir(theta2,phi2)).
// The normal must be explicitly specified if c and the endpoints are colinear.
path3 arc(triple c, real r, real theta1, real phi1, real theta2, real phi2,
triple normal=O, bool direction)
{
return arc(c,c+r*dir(theta1,phi1),c+r*dir(theta2,phi2),normal,direction);
}
// return an arc centered at c with radius r from c+r*dir(theta1,phi1) to
// c+r*dir(theta2,phi2) in degrees, drawing drawing counterclockwise
// relative to the normal vector cross(dir(theta1,phi1),dir(theta2,phi2))
// iff theta2 > theta1 or (theta2 == theta1 and phi2 >= phi1).
// The normal must be explicitly specified if c and the endpoints are colinear.
path3 arc(triple c, real r, real theta1, real phi1, real theta2, real phi2,
triple normal=O)
{
return arc(c,r,theta1,phi1,theta2,phi2,normal,
theta2 > theta1 || (theta2 == theta1 && phi2 >= phi1) ? CCW : CW);
}
private real epsilon=1000*realEpsilon;
// Return a representation of the plane through point O with normal cross(u,v).
path3 plane(triple u, triple v, triple O=O)
{
return O--O+u--O+u+v--O+v--cycle;
}
// 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, triple position, bool group=true,
bool above=true)
{
dest.add(new void(picture f, transform3 t) {
f.add(shift(t*position)*src,group,above);
});
}
// Choose the angle to be just large enough to view the entire image.
real angle(projection P) {
T=identity4;
real h=-0.5*P.target.z;
pair r,R;
real diff=realMax;
pair s;
int i;
do {
r=minratio(f);
R=maxratio(f);
pair lasts=s;
if(P.autoadjust) {
s=r+R;
if(s != 0) {
transform3 t=shift(h*s.x,h*s.y,0);
f=t*f;
T=t*T;
adjusted=true;
}
}
diff=abs(s-lasts);
++i;
} while (diff > angleprecision && i < maxangleiterations);
real aspect=width > 0 ? height/width : 1;
real rx=-r.x*aspect;
real Rx=R.x*aspect;
real ry=-r.y;
real Ry=R.y;
if(!P.autoadjust) {
if(rx > Rx) Rx=rx;
else rx=Rx;
if(ry > Ry) Ry=ry;
else ry=Ry;
}
return (1+angleprecision)*max(aTan(rx)+aTan(Rx),aTan(ry)+aTan(Ry));
}
}
// Fit an array of 3D pictures simultaneously using the sizing of picture all.
frame[] fit3(string prefix="", picture[] pictures, picture all,
string format="", bool view=true, string options="",
string script="", light light=currentlight,
projection P=currentprojection)
{
frame[] out;
scene S=scene(all,P);
triple m=all.min(S.t);
triple M=all.max(S.t);
out=new frame[pictures.length];
int i=0;
bool reverse=settings.reverse;
settings.animating=true;
// Fit an array of pictures simultaneously using the size of the first picture.
fit=new frame[](string prefix="", picture[] pictures, string format="",
bool view=true, string options="", string script="",
projection P=currentprojection) {
if(pictures.length == 0)
return new frame[];