Next: Pens, Previous: Data types, Up: Programming
guide
This is like a path except the computation of the cubic spline is
deferred until drawing time (when it is resolved into a path); this allows
two guides with free endpoint conditions to be joined together smoothly.
path
A path is specified as a list of pairs or paths interconnected with
--
, which denotes a straight line segment, or ..
, which
denotes a cubic spline.
Specifying a final node cycle
creates a cyclic path that
connects smoothly back to the initial node, as in this approximation
(accurate to within 0.06%) of a unit circle:
guide unitcircle=E..N..W..S..cycle;
This example uses the standard compass directions E=(1,0)
,
N=(0,1)
, NE=unit(N+E)
, and ENE=unit(E+NE)
, etc.,
which along with the directions up
, down
, right
,
and left
are defined as pairs in the default Asymptote
base file
plain.asy
.
The routine circle(pair c, real r)
in plain.asy
constructs a circle of radius r
centered on c
by
transforming unitcircle:
guide circle(pair c, real r) { return shift(c)*scale(r)*unitcircle; }If high accuracy is needed, a true circle may be produced with this routine, defined in the module
graph.asy
:
guide Circle(pair c, real r, int ngraph=400);
Each interior node of a cubic spline may be given a
direction prefix or suffix {dir}
: the direction of the pair
dir
specifies the direction of the incoming or outgoing tangent,
respectively, to the curve at that node. Exterior nodes may be
given direction specifiers only on their interior side. Cubic splines between
a node z
, with postcontrol point Z
, and a node w
,
with precontrol point W
, are computed as the Bezier curve
A good reference on Bezier curves and the algorithms that Asymptote
uses to determine the control points is Donald Knuth's monograph,
The MetaFontbook
, chapters 3 and 14.
This example draws an approximate quarter circle:
size(100,0); draw((1,0){up}..{left}(0,1));
A circular arc consistent with the above approximation centered on
c
with radius r
from angle1
to angle2
degrees, drawing counterclockwise if angle2 >= angle1
, can be
constructed with
guide arc(pair c, real r, real angle1, real angle2);If
r
< 0, the complementary arc of radius |r|
is constructed.
For convenience, an arc centered at c
from pair z1
to
z2
(assuming |z2-c|=|z1-c|
) in the direction CCW
(counter-clockwise) or CW (clockwise) may also be constructed with
guide arc(pair c, explicit pair z1, explicit pair z2, bool direction=CCW)
If high accuracy is needed, a true arc may be produced with this
routine, defined in the module graph.asy
:
guide Arc(pair c, real r, real angle1, real angle2, int ngraph=400);
Instead of specifying the tangent directions before and after a node, one can also specify the control points directly:
draw((0,0)..controls (0,100) and (100,100)..(100,0));
One can also change the spline tension from its default value of 1 to any real value greater than or equal to 0.75:
draw((100,0)..tension 2 ..(100,100)..(0,100)); draw((100,0)..tension 2 and 1 ..(100,100)..(0,100)); draw((100,0)..tension atleast 1 ..(100,100)..(0,100));
The MetaPost ...
path connector, which requests, when possible, an
inflection-free curve confined to a triangle defined by the
endpoints and directions, is implemented in Asymptote
as the
convenient abbreviation ::
for ..tension atleast 1 ..
(the ellipsis ...
is used in Asymptote
to indicate a
variable number of arguments; see Rest arguments). For example,
compare
draw((0,0){up}..(100,25){right}..(200,0){down});
draw((0,0){up}::(100,25){right}::(200,0){down});
The ---
connector is an abbreviation for ..tension atleast
infinity..
and the &
connector concatenates two paths which meet
at a common point. Here is an example of all five path connectors:
size(300,0); pair[] z=new pair[10]; z[0]=(0,100); z[1]=(50,0); z[2]=(180,0); for(int n=3; n <= 9; ++n) z[n]=z[n-3]+(200,0); path p=z[0]..z[1]---z[2]::{up}z[3] &z[3]..z[4]--z[5]::{up}z[6] &z[6]::z[7]---z[8]..{up}z[9]; draw(p,grey+linewidth(4mm)); dot(z);
The curl parameter specifies the curvature at the endpoints of a path (0 means straight; the default value of 1 means approximately circular):
draw((100,0){curl 0}..(100,100)..{curl 0}(0,100));
The implicit initializer for paths and guides is nullpath
,
which is useful for building up a path within a loop.
A direction specifier given to nullpath
modifies the node on
the other side: the paths
a..{up}nullpath..b; c..nullpath{up}..d; e..{up}nullpath{down}..f;are respectively equivalent to
a..nullpath..{up}b; c{up}..nullpath..d; e{down}..nullpath..{up}f;
An Asymptote
path, being connected, is equivalent to a
Postscript subpath
. The ^^
binary operator, which
requests that the pen be moved (without drawing or affecting
endpoint curvatures) from the final point of the left-hand path to the
initial point of the right-hand path, may be used to group several
Asymptote
paths into a path[]
array (equivalent to a
PostScript
path):
size(0,100); path unitcircle=E..N..W..S..cycle; path g=scale(2)*unitcircle; filldraw(unitcircle^^g,evenodd+yellow,black);
The PostScript
even-odd fill rule here specifies that only the
region bounded between the two unit circles is filled (see fillrule).
In this example, the same effect can be achieved by using the default
zero winding number fill rule, if one is careful to alternate the
orientation of the paths:
filldraw(unitcircle^^reverse(g),yellow,black);
The ^^
operator is used by the box3d
function in
three.asy
to construct a two-dimensional projection of the edges of a 3D
cube, without retracing steps:
import three; size(0,100); currentprojection=oblique; draw(unitcube); dot(unitcube,red); label("$O$",(0,0,0),NW); label("(1,0,0)",(1,0,0),E); label("(0,1,0)",(0,1,0),N); label("(0,0,1)",(0,0,1),S);
Here are some useful functions for paths:
int length(path);
int size(path);
pair point(path p, int n);
p
is cyclic, return the coordinates of node n
mod
length(p)
. Otherwise, return the coordinates of node n
,
unless n
< 0 (in which case point(0)
is returned) or
n
> length(p)
(in which case point(length(p))
is returned).
pair point(path p, real t);
floor(t)
and floor(t)+1
corresponding to the cubic spline parameter
t=t-floor(t)
(see Bezier). If t
lies outside the range
[0,length(p)
], it is first reduced modulo length(p)
in the case where p
is cyclic or else converted to the corresponding
endpoint of p
.
pair dir(path, int n);
n
. If the path contains only one point, (0,0) is returned.
pair dir(path, real t);
floor(t)
and floor(t)+1
corresponding to the
cubic spline parameter t=t-floor(t)
(see Bezier).
If the path contains only one point, (0,0) is returned.
pair precontrol(path, int n);
n
.
pair precontrol(path, real t);
t
.
pair postcontrol(path, int n);
n
.
pair postcontrol(path, real t);
t
.
real arclength(path);
real arctime(path, real L);
point(path, real)
, at which the
cumulative arclength (measured from the beginning of the path) equals L
.
real dirtime(path, pair z);
point(path, real)
, at which the tangent
to the path has direction z
, or -1 if the path never achieves
direction z
.
path reverse(path p);
path subpath(path p, int n, int m);
n
to node m
.
If n
< m
, the direction of the subpath is reversed.
path subpath(path p, real a, real b);
a
to path time b
,
in the sense of point(path, real)
. If a
< b
, the
direction of the subpath is reversed.
pair intersect(path p, path q, real fuzz=0);
p
and q
have at least one intersection point, return a
pair of times representing the respective path times along
p
and q
, in the sense of point(path, real)
, for
one such intersection point (as chosen by the algorithm described on
page 137 of The MetaFontbook
).
Perform the computations to the absolute error specified by fuzz
,
or, if fuzz
is 0, to machine precision. If the paths do not
intersect, return the pair (-1,-1)
.
pair intersectionpoint(path p, path q, real fuzz=0);
point(p,intersect(p,q,fuzz).x)
, the actual point
of intersection.
slice firstcut(path p, path q);
p
before and after the first intersection
of p
with path q
as a structure slice
(if no such
intersection exists, the entire path is considered to be `before' the
intersection):
struct slice { public path before,after; }
Note that firstcut.after
plays the role of the MetaPost
cutbefore
command.
slice lastcut(path p, path q);
p
before and after the last intersection
of p
with path q
as a slice
(if no such
intersection exists, the entire path is considered to be `after' the
intersection).
Note that lastcut.before
plays the role of the MetaPost
cutafter
command.
pair min(path);
pair max(path);
bool cyclic(path);
true
iff path is cyclic
bool straight(path, int i);
true
iff the segment between node i
and node i+1
is straight.
bool inside(path g, pair z, pen p=currentpen);
true
iff the point z
is inside the region bounded
by the cyclic path g
according to the fillrule of pen p
(see fillrule).
Finally, we point out an efficiency distinction in the use of guides and paths:
guide g; for(int i=0; i < 10; ++i) g=g--(i,i); path p=g;
runs in linear time, whereas
path p; for(int i=0; i < 10; ++i) p=p--(i,i);
runs in quadratic time, as the entire path up to that point is copied at each step of the iteration.