%%% Date: Mon, 12 Oct 92 22:32:32 EST
%%% Message-Id: <9210121232.AA18641@ee.latrobe.edu.au>
%%% To: ajs@msdrl.com
%%% Subject: graphbase.mf 0.2 gt mod 1 Mon 12 Oct 1992.
%%% Cc: ecsgrt@ee.latrobe.edu.au
%%% Status: OR
%%% 
%%% 12:10 GMT Mon 12 Oct 1992 - Geoffrey Tobin.
%%% 
%%% Dear Anthony,
%%% 
%%% Latest mod of "graphbase.mf".
%%% 
%%% This is still a long way from what it ought to be for Fig 2.1, but
%%% maybe you can use some of the new code.  Much of it is comments!
%%% 
%%% I've modified onedot based on Knuth's "drawdot", written a hatchpath
%%% macro to complement shadepath, attempted a gray level formula to match
%%% human light sensitivity, some macros to draw as well as shade or
%%% hatch, and an arrowpath macro to draw an arrow for arbitrary path and
%%% to draw that path.
%%% 
%%% THe 3-point arc code is included, with arcthreearrow to draw an arrow
%%% as well.
%%% 
%%% Some code for drawing Interpolated Splines is included.  The macros to
%%% test are ispline, isplinearrow, and isplineshade.
%%% 
%%% Backward arrows are on my agenda.
%%% 
%%% All the Best!
%%% Geoffrey Tobin
%%% 
%%%
%%%  File: graphbase.mf
%%%

mode_setup;
message "mfpic version 0.2 graphbase - gt mod 1 - 10:16 GMT Mon 12 Oct 1992";

% set up local environment

def mfpicenv =
begingroup

% miscellaneous utilities

% gt - op_pair operates with "op"
% on both parts of a pair p.

save op_pair;

vardef op_pair (text op) (expr p) =
  (op (xpart p), op (ypart p))
enddef;

save floorpair, ceilingpair;

def floorpair = op_pair (floor) enddef;
def ceilingpair = op_pair (ceiling) enddef;

% gt - Should there be more error-checking,
% eg of types, in these utility routines?
% That would slow them down.

% gt - textpairs converts the text t into the
% array of n pairs, pts, that it contains.

save textpairs;

def textpairs (text t) (suffix pairs_, n_) =
 n_ := 0;
 for q=t:
  pairs_[incr n_] := q;
 endfor;
enddef;

% gt - Watch out!  Need to ensure that "p_", etc.,
% don't clash with any name in the passed text "t".
% That's a nasty error to trace!
%
% A name conflict between local variables and variables
% in a text parameter is especially likely in low-level
% utility macros, such as minpair, maxpair and corner.
%
% Unfortunately, we can't *ensure* it won't happen.
% So I appended the underscore to reduce the
% probability of that happening.
%
% Evidently that's why Knuth uses "u_" in "max" and
% "min" in "plain.mf".

% gt - corner may be used for finding
% a corner of the bounding box of the
% set of points listed in u and t.
% Other uses may be imaginable. (?)

save corner;

vardef corner (text xop) (text yop)
              (expr u)(text t) =
  save p_;
  pair p_;
  p_ := u;
  for q=t:
    p_ := (xop (xpart p_, xpart q),
	   yop (ypart p_, ypart q));
  endfor;
  p_
enddef;

% gt - bottom right, bottom right,
% top left, top right corners.

save blpair, brpair, tlpair, trpair;

def blpair = corner (min) (min) enddef;
def brpair = corner (max) (min) enddef;
def tlpair = corner (min) (max) enddef;
def trpair = corner (max) (max) enddef;

def minpair = blpair enddef;
def maxpair = trpair enddef;

% setup
% gt - sets the graphics coordinates.

save bounds,
  xneg,xpos,yneg,ypos;

def bounds(expr a,b,c,d) = 
 xneg:=a; 
 xpos:=b; 
 yneg:=c; 
 ypos:=d; 
enddef;

% conversion

save xconv;

def xconv(expr xvalue) = 
 ((xvalue-xneg)/(xpos-xneg))*w 
enddef;

save unxconv;

def unxconv(expr pvalue) = 
 ((pvalue/w)*(xpos-xneg)+xneg) 
enddef;

save yconv;

def yconv(expr yvalue) = 
 ((yvalue-yneg)/(ypos-yneg))*h 
enddef;

save ztr;

transform ztr;

save setztr;

def setztr =
 ztr:=identity
 shifted -(xneg,yneg) 
 xscaled (w/(xpos-xneg))
 yscaled (h/(ypos-yneg));
enddef;

% pen width
% in pixel coordinates

save penwd;

newinternal penwd;

% gt - initial value of penwd.

interim penwd := 0.5pt;

% arrowheads
% in pixel coordinates

% hdwdr = arrowhead's ratio of width to length,
% hdten = tension used in drawing its barbs.

save hdwdr, hdten;

newinternal hdwdr, hdten;

% gt - initial values of hdwdr, hdten.

interim hdwdr := 1;
interim hdten := 1;

% draw an arrowhead.

save head, p,side;

def head(expr front, back, width, t) =
 pair p[], side;
 side := (width/2) * 
   ((front-back) rotated 90);
 p1 := back + side;
 p2 := back - side;
 draw front{back-front}..tension t..p1;
 draw front{back-front}..tension t..p2;
enddef;

% draw an arrowhead of length hlen
% for a path f.

save headpath, p;

def headpath(expr f,hlen) =
 pair p[];
 p2:=point infinity of f; 
 p1:=direction infinity of f;
 if p1<>(0,0):
  head(p2,p2-(hlen*unitvector(p1)),
    hdwdr,hdten);
 fi;
enddef;

% shading and hatching routines
% in pixel coordinates

% gt - modified onedot based on
% plain metafont's "drawdot".
% Used in stipple shading.
%
% Note:
% currentpen_path, def_pen_path_,
% t_, and penspeck are defined
% in plain metafont ("plain.mf"
% or "plain.base").

save onedot;

def onedot(expr p)(suffix v) =
  if unknown currentpen_path:
    def_pen_path_
  fi;
  addto v
    contour currentpen_path
    shifted p.t_
    withpen penspeck
enddef;

% gt - draw path f in picture v.
% ("onepath" is the old "onedot",
% but f is intended to be a general path.)
% Used, eg, in hatching and in drawpaths.

save onepath;

def onepath (expr f) (suffix v) =
  addto v doublepath f
    withpen currentpen; 
enddef;

% gt - Paths must be continuous - I think
% - but using suffix, we can pass arrays
% of paths.
%
% My eventual goal is to do as much as
% feasible, and memory-affordable, in
% graphics coordinates, so we can rotate
% and otherwise transform sets of paths
% before drawing.

% gt - draw the n paths f[] in picture v.

save drawpaths;

def drawpaths (expr n) (suffix f, v) =
  for i=1 upto n:
    onepath (f[i], v);
  endfor;
enddef;

% clip picture v to interior of path f.

save clip;

vardef clip(expr f)(suffix v) =
 save vt;
 picture vt;
 vt:=v;
 cull vt keeping (1,infinity);
 addto vt contour f;
 cull vt keeping (2,infinity);
 vt
enddef;

% gt - find bounding box of path f.

save boundingbox, p;

def boundingbox (expr f) (suffix ll, ur) =
  ur := ll := point 0 of f; 
  pair p[];
  for i=0 upto length f:
    p0 := point i of f; 
    p1 := precontrol i of f; 
    p2 := postcontrol i of f;
    ll := minpair (ll, p0, p1, p2);
    ur := maxpair (ur, p0, p1, p2);
  endfor; 
enddef;

% gt - shading.

% gt - I'm not so happy with dot densities
% over a uniform range.
% Here's code to approximate what may
% be the human eye's light sensitivity.
%
% Mind you, this sort of stuff is done much
% faster in C.

save exp;

vardef exp (expr x) =
  mexp (256 * x)
enddef;

% graya scales the spacing sp;
% grayb scales the graylevel g.

save graya, grayb;

newinternal graya, grayb;

% initial values of gray parameters.

interim graya := 0.5 pt;
interim grayb := 3/20;

% setgraypars sets gray parameters.
% experimentation is recommended.

save setgraypars;

def setgraypars (expr a, b) =
  graya := a;
  grayb := b;
enddef;

% gt - grayspace gives the dot spacing
% for graylevel g.
%
% Not sure how this model performs.

save grayspace;

vardef grayspace (expr g) =
  if g <= 1:  % white
    infinity
  elseif g >= 21:  % black
    0
  else:  % gray
    graya / (1 - exp (-g * grayb))
  fi
enddef;

% gt - stipple upright box with lower left
% at ll, upper right at ur, in picture v;
% 2sp is dot spacing (rows offset by sp).
%
% NB: "stipple" means "shade with dots",
% if I understand my English dictionary.
%
% Thomas Leathrum devised the trick whereby
% the dots are arranged on a regular grid
% of mesh size sp by sp with the pixel
% origin as one crosspoint.  This ensures
% that objects shaded with the same stipple
% density may be cleanly overlaid.

save shadebox, sll, mn, m, n, twosp, p;

def shadebox (expr sp, ll, ur) (suffix v) =
  pair sll;
  sll:=sp*(ceilingpair(ll/sp));
  pair mn;
  mn:=floorpair((ur-sll)/sp);
  m:=xpart mn;
  n:=ypart mn;
  twosp:=2sp;
  v:=nullpicture;
  pair p[];
  p2:=sll;
  for i=0 upto m: 
    p3:=p2 if odd i: +(0,sp) fi;
    for j=0 upto n:
      if (not odd (i+j)):
        onedot (p3, v);
        p3:=p3+(0,twosp);
      fi;
    endfor;
    p2:=p2+(sp,0);
  endfor; 
enddef;

% stipple interior of closed path f;
% if spacing not positive, fill.

save shadepath, ll, ur, v;

def shadepath (expr sp, f) =
 if not cycle f: ;
 elseif sp<=0:
   fill f; 
 elseif sp < infinity: 
   pair ll, ur;
   boundingbox (f, ll, ur);
   picture v;
   shadebox (sp, ll, ur, v);
   addto currentpicture 
     also clip(f,v);
 fi;
enddef;

% gt - hatch an upright box in picture v,
% with line separation sep x sep.
%
% Notice the similarity to shadebox.

save hatchbox, llx, lly, urx, ury, sll,
     mn, m, n, f;

def hatchbox (expr sep, ll, ur) (suffix v) =
  llx := xpart ll;
  lly := ypart ll;
  urx := xpart ur;
  ury := ypart ur;
  pair sll;
  sll := sep * ceilingpair (ll/sep);
  pair mn;
  mn := floorpair ((ur-sll)/sep);
  m := xpart mn;
  n := ypart mn;
  v := nullpicture;
  path f;
  f := (xpart sll, lly)--(xpart sll, ury);
  for i=0 upto m:
    onepath (f, v);
    f := f translated (sep, 0);
  endfor;
  f := (llx, ypart sll)--(urx, ypart sll);
  for j=0 upto n:
    onepath (f, v);
    f := f translated (0, sep);
  endfor;
enddef;

save hatchpath, ll, ur, v;

def hatchpath (expr sep, f) =
 if not cycle f: ;
 elseif sep<=0:
   fill f; 
 elseif sep < infinity: 
   pair ll, ur;
   boundingbox (f, ll, ur);
   picture v;
   hatchbox (sep, ll, ur, v);
   addto currentpicture 
     also clip (f, v);
 fi;
enddef;

% gt - shading & hatching macros
% with a syntax like draw, fill,
% unfill and erase.
% sp, sep are in pixel coords,
% f in graphics coordinates;
% f is transformed transparently.

save shade;

def shade (expr sp) expr f =
  shadepath (sp, f transformed ztr);
enddef;

save hatch;

def hatch (expr sep) expr f =
  hatchpath (sep, f transformed ztr);
enddef;

% gt - common combinations.

save drawshade;

def drawshade (expr sp) expr f =
  draw f transformed ztr;
  shade (sp) f;
enddef;

save drawhatch;

def drawhatch (expr sep) expr f =
  draw f transformed ztr;
  hatch (sep) f;
enddef;

% * rest of macros start in graphing 
% coordinates but convert to pixel 
% to draw
% * variables ending in "_px" 
% converted to pixel
% * exceptions are the TeX dimensions
% here called:
% ptwd, hlen, dlen, slen, len, sp, sep
% all of which are in pixel coordinates
% * macros beginning with "mk" operate
% entirely in graphing coordinates

% general path construction

save mkpath;

vardef mkpath(expr smooth, cyclic, n)
  (suffix pts) =
 if smooth:
  if cyclic:
   pts[1]{pts[2]-pts[n]}
  else:
   pts[1]
  fi
  for i=2 upto n-1:
   ..pts[i]{pts[i+1]-pts[i-1]}
  endfor
  if cyclic:
   ..pts[n]{pts[1]-pts[n-1]}..cycle
  else:
   ..pts[n]
  fi
 else:
  for i=1 upto n-1:
    pts[i] --
  endfor
  pts[n]
  if cyclic:
   -- cycle
  fi
 fi
enddef;

% points, lines, and arrows

save pointd, p;

def pointd(expr a,ptwd) = 
 pair p_px;
 p_px:=a transformed ztr;
 fill fullcircle scaled ptwd shifted p_px; 
enddef;

save line;

def line(expr a,b) = 
 draw (a..b) transformed ztr; 
enddef;

% gt - arrowpath draws path f
% with an arrowhead;
% hlen is in pixel coordinates;
% f is in graphics coords;
% f is transformed transparently.
% Compare shade, hatch, etc.,
% and contrast shadepath.

save arrowpath, f_px;

def arrowpath (expr hlen) expr f =
  path f_px;
  f_px := f transformed ztr;
  draw f_px;
  headpath (f_px, hlen);
enddef;

% gt - arrow now uses arrowpath.

save arrow;

def arrow(expr tl,hd,hlen) =
 arrowpath (hlen) tl..hd ;
enddef;

% gt - "px" was too frequent
% in dottedline, and made the code
% hard to read, so I've deleted it.
% Only a and b are in graphics coords.

save dottedline,
  p, v, l, delta, n;

def dottedline (expr a, b, dlen, slen) =
  pair p[];
  p1 := a transformed ztr;
  p3 := b transformed ztr;
  l := length (p3-p1); 
  if (l > 2dlen) and 
    (dlen >= 0) and (slen >= 0): 
  else: 
    pair v;
    v := unitvector (p3-p1);
    n := floor ((l+slen-dlen) / (dlen+slen));
    delta := (l-dlen) / n - (dlen+slen);
    for i=1 upto n:
      p2 := p1 + dlen * v; 
      draw p1..p2; 
      p1 := p2 + (slen+delta) * v;
    endfor; 
  fi;
  draw p1..p3;
enddef;

save dottedarrow;

def dottedarrow(expr tl,hd,dlen,
  slen,hlen) =
 dottedline(tl,hd,dlen,slen); 
 headpath((tl..hd) transformed ztr,hlen);
enddef;

% axes and axis marks

save axes;

def axes(expr hlen) =
 arrow((0,yneg),(0,ypos),hlen); 
 arrow((xneg,0),(xpos,0),hlen);
enddef;

save xmarks;

def xmarks(expr len)(text t) =
 for a=t: 
  draw (xconv(a),yconv(0)-(len/2))..
    (xconv(a),yconv(0)+(len/2)); 
 endfor; 
enddef;

save ymarks;

def ymarks(expr len)(text t) =
 for a=t: 
  draw (xconv(0)-(len/2),yconv(a))..
    (xconv(0)+(len/2),yconv(a)); 
 endfor; 
enddef;

% upright rectangles

save mkrect;

vardef mkrect(expr ll,ur) =
 ll--(xpart ll,ypart ur)--
   ur--(xpart ur,ypart ll)--cycle
enddef;

save rect;

def rect(expr ll,ur) =
 draw mkrect(ll,ur) transformed ztr;
enddef;

save dottedrect;

def dottedrect(expr ll,ur,dlen,slen) =
 dottedline(ll,(xpart ll,ypart ur),
   dlen,slen);
 dottedline((xpart ll,ypart ur),ur,
   dlen,slen);
 dottedline(ur,(xpart ur,ypart ll),
   dlen,slen);
 dottedline((xpart ur,ypart ll),ll,
   dlen,slen);
enddef;

save block;

def block(expr ll,ur) =
 fill mkrect(ll,ur) transformed ztr;
enddef;

% gt - rectshade now uses shade.

save rectshade;

def rectshade(expr sp,ll,ur) =
  shade (sp) mkrect (ll, ur);
enddef;

% circles and ellipses

save mkellipse;

vardef mkellipse(expr center,radx,rady,
  angle) =
 save t;
 transform t; 
 t := identity
   xscaled (2 * radx)
   yscaled (2 * rady)
   rotated angle 
   shifted center;
 fullcircle transformed t
enddef;

save ellipse;

def ellipse(expr center,radx,rady,
  angle) =
 draw 
   mkellipse(center,radx,rady,angle)
   transformed ztr;
enddef;

save circle;

def circle(expr center,rad) =
 ellipse(center,rad,rad,0);
enddef;

% gt - ellshade now uses shade.

save ellshade;

def ellshade (expr sp, center, 
  radx, rady, angle) =
 shade (sp)
   mkellipse (center, radx, rady, angle);
enddef;

save circshade;

def circshade(expr sp, center,rad) =
 ellshade(sp,center,rad,rad,0);
enddef;

% circular arcs

% gt - mkarc now calculates
% n using ceiling, not floor;
% and saves theta, not i.

save mkarc;

vardef mkarc(expr center,from,sweep) =
  if sweep=0:
    from
  else:
   begingroup
    save n, theta, p;
    n := 1 + ceiling (abs (sweep) / 45);
    if n<3: n:=3; fi;
    theta:=sweep/(n-1);
    pair p[];
    p1:=from; 
    for i=2 upto n:
     p[i]:=p[i-1] 
       rotatedabout (center,theta);
    endfor;
    mkpath(true,false,n,p)
   endgroup
  fi
enddef;

% gt - note that when sweep is a multiple
% of 360 degrees, disp is logically
% infinite, not zero; then the center is
% at infinity.  In practice, arccenter
% ought not to be called in that case.

save arccenter;

vardef arccenter(expr from,to,sweep) =
 save midpt, disp;
 pair midpt;
 midpt:=(0.5)[from,to];
 disp:=
   if ((sweep mod 360)=0):
    0
   else:
    cosd(sweep/2)/sind(sweep/2)
   fi;
 midpt+(disp*((to-from) rotated 90)/2)
enddef;

% gt - mkarcto makes an arc given two points
% on the arc and the sweep angle.
% If sweep is a multiple of 360 degrees,
% then the arc is a straight line;
% if sweep is also nonzero, then that
% line should be infinite, but I use
% from--to instead.

save mkarcto;

vardef mkarcto (expr from, to, sweep) =
  if from = to:
    from
  elseif (sweep mod 360) = 0:
    from--to
  else
   begingroup
    save center;
    pair center;
    center:=arccenter (from, to, sweep);
    mkarc (center, from, sweep)
   endgroup
  fi
enddef;

% gt - arc now uses mkarcto.

save arc;

def arc(expr from,to,sweep) =
 draw mkarcto (from, to, sweep)
    transformed ztr;
enddef;

% gt - arcarrow now uses mkarcto
%      and arrowpath.

save arcarrow;

def arcarrow(expr hlen,from,to,sweep) =
  arrowpath (hlen) mkarcto (from, to, sweep);
enddef;

% gt - mkchordto makes a cyclic path from
% the arc from "from" to "to" with a sweep
% angle of "sweep", and its chord from
% "to" to "from".

save mkchordto;

vardef mkchordto (expr from, to, sweep) =
  mkarcto (from, to, sweep) -- cycle
enddef;

% gt - arcshade now uses mkchordto
%      and shade.

save arcshade;

def arcshade(expr sp,from,to,sweep) =
  shade (sp) mkchordto (from, to, sweep);
enddef;

% gt - three-point arcs.

save mkarcthree;

vardef mkarcthree (expr first, mid, last) =
  save p, sweep, n, theta, center;
  pair p[];
  p1 := first;
  sweep := 2 (angle (last-mid) - angle (mid-first));
  if abs (sweep) <= 90:
    n := 3;
    p2 := mid;
    p3 := last;
  else:
    n := 1 + ceiling (abs (sweep) / 45);
    theta := sweep / (n-1);
    pair center;
    center := arcthreecenter (first, mid, last);
    for i=2 upto n:
      p[i] := p[i-1] rotatedabout (center, theta);
    endfor;
  fi;
  mkpath (true, false, n, p)
enddef;

save arcthreecenter;

vardef arcthreecenter (expr first, mid, last) =
  save c, m, d;
  pair c, m[], d[];
  d1 := (mid - first) rotated 90;
  d2 := (last - mid) rotated 90;
  m1 := 0.5 [first, mid];
  m2 := 0.5 [mid, last];
  c = m1 + whatever * d1 = m2 + whatever * d2;
  c
enddef;

save arcthree;

def arcthree (expr first, mid, last) =
  draw mkarcthree (first, mid, last) transformed ztr;
enddef;

save arcthreearrow;

def arcthreearrow (expr hlen, first, mid, last) =
  arrowpath (hlen) mkarcthree (first, mid, last);
enddef;

% modified polar coordinates

% gt - mklinedir makes a path from point "a"
% to a point displaced "len" in direction "theta"
% from "a".

save mklinedir;

vardef mklinedir (expr a, theta, len) =
  a -- (a + len * (dir theta))
enddef;

% gt - linedir now uses mklinedir.

save linedir;

def linedir(expr a,theta,len) =
  draw mklinedir (a, theta, len)
	 transformed ztr;
enddef;

% gt - arrowdir now uses mklinedir
%      and arrowpath.

save arrowdir;

def arrowdir(expr hlen,a,theta,len) =
 arrowpath (hlen)
     mklinedir (a, theta, len);
enddef;

% gt - mkarcth makes an arc path with
% given center, radius "rad", initial
% angle "frtheta", and final angle
% "totheta".

save mkarcth;

vardef mkarcth (expr center,
    frtheta, totheta, rad) =
  save from;
  pair from;
  from := center + rad * (dir frtheta);
  mkarc (center, from, totheta-frtheta)
enddef;

% gt - arcth now uses mkarcth.

save arcth;

def arcth(expr center,
  frtheta,totheta,rad) =
  draw mkarcth (center, frtheta,
		totheta, rad)
    transformed ztr;
enddef;

% gt - arcth now uses mkarcth
%      and arrowpath.

save arctharrow;

def arctharrow(expr hlen,center, 
  frtheta,totheta,rad) =
 arrowpath (hlen)
     mkarcth (center, frtheta,
              totheta, rad);
enddef;

% gt - mkwedge makes a wedge-shaped path
% with apex at "center", radius "rad",
% initial angle "frtheta", and final angle
% "totheta".

save mkwedge;

vardef mkwedge (expr center, frtheta, totheta, rad) =
  center -- mkarcth (from, frtheta, totheta, rad)
         -- cycle
enddef;

% gt - wedge draws a sector of a circle.

save wedge;

def wedge (expr center, frtheta, totheta, rad) =
  draw mkwedge (center, frtheta, totheta, rad)
    transformed ztr;
enddef;

% gt - wedgeshade now uses mkwedge and shade.

save wedgeshade;

def wedgeshade (expr sp, center, 
  frtheta, totheta, rad) =
  shade (sp) mkwedge (center, frtheta, totheta, rad);
enddef;

% gt - drawshadewedge draws and shades a wedge.

save drawshadewedge;

def drawshadewedge (expr sp, center, 
  frtheta, totheta, rad) =
 draw mkwedge (center, frtheta, totheta, rad)
   transformed ztr;
 shade (sp) mkwedge (center, frtheta, totheta, rad);
enddef;

% curves

% gt - watch out for that "text containing a local
% variable's name" conflict!  I dearly wish that
% weren't a danger.
% Perhaps it's not so likely at the level of "mkcurve",
% as the "mk" macros are often fed numeric constants.

save mkcurve;

vardef mkcurve(expr smooth,cyclic)
  (text t) =
 save n_, p_;
 pair p_[];
 textpairs (t) (p_, n_);
 mkpath(smooth,cyclic,n_,p_)
enddef;

save curve;

def curve(expr smooth,cyclic)
  (text t) =
 draw mkcurve(smooth,cyclic,t)
   transformed ztr; 
enddef;

% gt - curvedarrow now uses arrowpath.

save curvedarrow;

def curvedarrow(expr smooth,hlen)
  (text t) =
  arrowpath (hlen)
      mkcurve (smooth, false, t);
enddef;

% shading of cyclic curves

% gt - cycleshade now uses shade.

save cycleshade;

def cycleshade(expr sp,smooth)(text t) =
  shade (sp) mkcurve (smooth,true,t);
enddef;

% gt - interpolated splines with controls.

% gt - mkipath uses the interpolation points,
% p[], and the left and right control points,
% l[] and r[].
% Observe that for cyclic I-splines, l[n] is
% used, not l1, though they are equal; this
% simplifies the algorithm.

save mkipath;

vardef mkipath (expr closed, n)
  (suffix p, l, r) =
  for i=1 upto n-1:
    p[i]..controls r[i] and l[i+1]..
  endfor
  if closed:
    cycle
  else:
    p[n]
  fi
enddef;

% gt - mkisplineA uses the I-spline data,
% in the order that Fig 2.1 gives them,
% points line pl and control line cl,
% stores them in p[], l[] and r[],
% then calls mkipath.
%
% pl should have the form:
%   (x1,y1) ... (xn,yn)
% and cl the form:
%   (lx1,ly1) (rx1,ry1) ... (lxn,lyn) (rxn,ryn)
% which reflect how Fig outputs its data.
%
% Don't feed it the "9999 9999", please!
%
% Perhaps the input should be massaged by a
% preprocessor program (e.g. in C), to separate
% the initially interleaved left and right control
% points, before being given to graphbase?
% That would simplify mkisplineA, and run faster.

save mkisplineA;

vardef mkisplineA (expr closed)
  (text pl) (text cl) =
  save p, l, r, n, i, isleft;
  pair p[], l[], r[];
  boolean isleft;
  textpairs (pl) (p, n);
  i := 1;
  isleft := true;
  for b=cl:
    if isleft:
      l[i] := b;
      isleft := false;
    else:
      r[i] := b;
      i := i+1;
      isleft := true;
    fi;
  endfor;
  mkipath (closed, n, p, l, r)
enddef;

% gt - mkisplineB uses the points line,
% and the separated left and right controls.
%
% See how much simpler this is than
% mkisplineA.

save mkisplineB;

vardef mkisplineB (expr closed)
  (text pl) (text lc) (text rc) =
  save p, l, r, n, i;
  pair p[], l[], r[];
  textpairs (pl) (p, n);
  textpairs (lc) (l, i);
  textpairs (rc) (r, i);
  mkipath (closed, n, p, l, r)
enddef;

% gt - the usual variations.
%
% These use mkisplineA.  I'd prefer
% mkisplineB.

% draw an interpolated spline,
% with points line pl and interleaved
% control line cl.

save ispline;

def ispline (expr closed)
  (text pl) (text cl) =
  draw mkisplineA (closed) (pl) (cl)
    transformed ztr;
enddef;

save isplinearrow;

def isplinearrow (expr hlen, closed)
  (text pl) (text cl) =
  arrowpath (hlen)
      mkisplineA (closed) (pl) (cl);
enddef;

% gt - isplineshade assumes that the
% I-spline is closed.

save isplineshade;

def isplineshade (expr sp)
  (text pl) (text cl) =
  shade (sp)
      mkisplineA (true) (pl) (cl);
enddef;

% functions

% gt - better be on the safe side with
% the function text, so use "_" on local
% variables in "mkfcn".

save mkfcn;

vardef mkfcn(expr smooth,bmin,bmax,bst)
  (suffix bv)(text fcnpr) =
 save p_, i_;
 pair p_[];
 i_ := 0;
 for bv=bmin step bst 
   until bmax+(bst/2):
  p_[incr i_] := fcnpr; 
 endfor;
 mkpath (smooth, false, i_ , p_)
enddef;

save function;

def function(expr smooth,xmin,xmax,st)
  (text fx) =
 draw mkfcn (smooth, xmin, xmax, st,
   x, (x,fx))
   transformed ztr; 
enddef;

save parafcn;

def parafcn(expr smooth,tmin,tmax,st)
  (text ft) =
 draw mkfcn (smooth, tmin, tmax, st,
   t, ft)
   transformed ztr; 
enddef;

% gt - mksfn constructs a path from
% two functions and the verticals
% at either side.
%
% mksfn is used by shadefcn.

save mksfn;

vardef mksfn (expr smooth, xmin, xmax, st)
    (text fcni) (text fcnii) =
  mkfcn(smooth,xmin,xmax,st,x,(x,fcni))
  --
  reverse
  mkfcn(smooth,xmin,xmax,st,x,(x,fcnii))
  -- cycle
enddef;

% gt - description:
% shadefcn shades between two functions over
% the range xmin to xmax, stepping by st,
% with dot spacing sp.
% it does not draw the functions.

% gt - shadefcn now uses mksfn.
% I don't see the connection between the dot
% spacing sp and the function step size st.

save shadefcn, st;

def shadefcn(expr sp, xmin, xmax)
    (text fcni)(text fcnii) =
  st := unxconv (sp);
  shade (sp)
    mksfn (false, xmin, xmax, st) (fcni) (fcnii);
enddef;

% gt - drawshadefcn draws both functions fcni
% and fcnii, and shades between them.

save drawshadefcn;

def drawshadefcn (expr sp, smooth, xmin, xmax, st)
    (text fcni) (text fcnii) =
  function (smooth, xmin, xmax, st) (fcni);
  function (smooth, xmin, xmax, st) (fcnii);
  shadefcn (sp, xmin, xmax) (fcni) (fcnii);
enddef;

enddef;  % mfpicenv

def endmfpicenv =
 endgroup;
enddef;


