/* Next available MSG number is  33 */

/*    

   ADC.C

   Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994 by Autodesk, Inc.
 
   Permission to use, copy, modify, and distribute this software in 
   object code form for any purpose and without fee is hereby granted, 
   provided that the above copyright notice appears in all copies and 
   that both that copyright notice and the limited warranty and 
   restricted rights notice below appear in all supporting 
   documentation.
 
   AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.  
   AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 
   MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC.
   DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 
   UNINTERRUPTED OR ERROR FREE.
 
   Use, duplication, or disclosure by the U.S. Government is subject to 
   restrictions set forth in FAR 52.227-19 (Commercial Computer 
   Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) 
   (Rights in Technical Data and Computer Software), as applicable.
    
   .

    
    Some example ADS code demonstrating the use of ads_tablet() and
    tablet mode in general.
     
    DIGPASS shows how to establish a "pass-through" tablet
    transformation in any UCS (doing it in world coordinates is
    trivial).  It is used by BILINCAL.

    SAMPLE, BILINCAL, and BILINMPT together demonstrate a possible
    implementation of a different tablet transformation than any
    supported directly by AutoCAD.

    SAVCAL and RSTCAL constitute a trivial matched pair of functions
    to save the current tablet calibration and subsequently to restore
    it.

        Greg Lutz
        May, 1991

    The matrix arithmetic in this file holds strictly to the ADS/LISP
    conventions:

        1.  m[i][j] is the element in row i, column j of matrix m.
        2.  Transforming vector v by matrix m is done by treating v
            as a column vector and computing m x v.
*/

#include  <stdio.h>
#include  <math.h>
#include  "adslib.h"

/* Special assertion handler for ADS applications. */

#ifndef NDEBUG
#define assert(ex) {if (!(ex)){ads_printf( \
                    /*MSG1*/"TADC: Assertion (%s) failed: file \"%s\", \
                    line %d\n", /*MSG0*/" "#ex" ",__FILE__,__LINE__); \
                    ads_abort(/*MSG2*/"Assertion failed.");}}
#else
#define assert(ex)
#endif

#ifndef GOOD
#define GOOD    0
#define BAD     (-1)
#endif


/*  Some macros which we need more generally available somewhere, sometime: */

#define TYPSTRUC(size)  typedef struct {char dummy[size];}
#define movecnt(c,t,f)  if (1) {TYPSTRUC(c)*_p_; *(_p_)(t) = *(_p_)(f);} else
#define cpy(s,t,f) movecnt(sizeof(s), t, f)

#define cpyname(to, from)   cpy(ads_name, to, from)  /* Copy an ads_name */
#define cpypoint(to, from)  cpy(ads_point, to, from) /* Copy an ads_point */


/*  The arbaxis crossover point: */
#define ARBBOUND  0.015625            /* aka "1/64" */

/*  Saved tablet calibration from savcal() */
static struct resbuf *tcal = NULL;


/*  Local function declarations */

int     digpass     _((void));
int     idigpass    _((void));
int     bilincal    _((void));
int     ibilinmpt   _((ads_real *in, ads_real *out));
int     bilinmpt    _((void));
void    cross       _((ads_real *ap, ads_real *bp, ads_real *cp));
void    matxmat     _((ads_matrix cp, ads_matrix ap, ads_matrix bp));
int     normalize   _((ads_real *ap, ads_real *bp));
void    pswap       _((ads_real *p1, ads_real *p2));
int     sample      _((void));
void    dline       _((char *p1str, char *p2str));
void    dvert       _((char *p1str));
int     savcal      _((void));
int     rstcal      _((void));
void    aperror     _((char *routine));


/*  Table of externally defined functions */
static struct {
    char *fcn_name;
    int (*fcn) _((void));
} fcn_table[] = {
    {/*MSG0*/"digpass", digpass},
    {/*MSG0*/"c:bilincal", bilincal},
    {/*MSG0*/"bilinmpt", bilinmpt},
    {/*MSG0*/"c:sample", sample},
    {/*MSG0*/"savcal", savcal},
    {/*MSG0*/"rstcal", rstcal},
};
#define NFCN    (sizeof fcn_table/sizeof fcn_table[0])


/*  Calibration points for bilincal() and bilinmpt(): */
static ads_point t[4], p[4];
static int cbrated = FALSE;           /* Not calibrated yet */
static int bilinsgn;                  /* Sign to use in quadratic */


/* MAIN - the main routine */


void
/*FCN*/main(argc, argv)
  int argc;
  char *argv[];
{
    int rqcode, rscode, i;
    char errmsg[80];

    ads_init(argc, argv);             /* Initialize the interface */
    rscode = RSRSLT;                  /* Initial request code */
    for ( ;; ) {
        /*  Send link partner a result code, get back a request: */
        if ((rqcode = ads_link(rscode)) < 0) {

            sprintf(errmsg, /*MSG31*/"TADC: bad status from ads_link() = %d\n",
                    rqcode);
#ifdef Macintosh
            macalert(errmsg);
#else
            puts(errmsg);
            fflush(stdout);
#endif /* Macintosh */
            exit(1);
        }

        /*  Handle the request code  */
        switch (rqcode) {
        case RQXLOAD:
            /*  (Re-)define functions  */
            rscode = RSRSLT;
            for (i = 0; i < NFCN && rscode == RSRSLT; i++)
                if (ads_defun(fcn_table[i].fcn_name, i) != RTNORM
                    || ads_regfunc(fcn_table[i].fcn, i) != RTNORM   
                   )
                rscode = RSERR;       /* Result code = error */
            if (rscode == RSRSLT)
                /*  NOTE!!!  The following line will have unknown
                    consequences if this application is loaded via
                    acad.ads!!!!*/
                ads_printf(/*MSG32*/"\n\nType SAMPLE to run test application\n");
            break;
        case RQSUBR:
            i = ads_getfuncode();
            rscode = (*fcn_table[i].fcn)();
            break;
        }
    }
}


/*  DIGPASS  --  External function to establish a tablet transformation
                 such that a tablet point with coordinates (x,y) maps
                 into the point (x,y) in the current UCS.  Returns
                 RSRSLT or RSERR.  */

static int
/*FCN*/digpass()
{
    register int stat = idigpass();
    ads_retvoid();
    return stat;
}



/*  IDIGPASS  --  Internal function which does the actual work of
                  DIGPASS.  Returns RSRSLT or RSERR.  */
static int
/*FCN*/idigpass()
{
    ads_matrix uwxform, wlxform, ulxform;
    static ads_point ey = {0.0, 1.0, 0.0},
                     ez = {0.0, 0.0, 1.0};
    ads_point ucszdir;
    struct resbuf uo, ux, uy, *ttl;
    int i;  

    /*  Fetch the origin, x-axis direction, and y-axis direction of the
        current UCS, and compute the z-axis direction  */

    ads_getvar(/*MSG0*/"UCSORG", &uo);
    ads_getvar(/*MSG0*/"UCSXDIR", &ux);
    ads_getvar(/*MSG0*/"UCSYDIR", &uy);
    cross(ucszdir, ux.resval.rpoint, uy.resval.rpoint);

    /*  Use these to build the transformation from UCS to world coords
        (the above axis vectors, and the origin, become the COLUMNS of
        this matrix).  */

    for (i = X; i <= Z; i++) {
        uwxform[i][X] = ux.resval.rpoint[i];
        uwxform[i][Y] = uy.resval.rpoint[i];
        uwxform[i][Z] = ucszdir[i];
        uwxform[i][T] = uo.resval.rpoint[i];
    }

    /*  Use the AutoCAD "arbaxis" algorithm to construct the transforma-
        tion from world coordinates to the current LCS (determined
        solely by the UCS Z-axis direction) */

    if (fabs(ucszdir[X]) < ARBBOUND && fabs(ucszdir[Y]) < ARBBOUND)
        cross(wlxform[X], ey, ucszdir);
    else
        cross(wlxform[X], ez, ucszdir);
    normalize(wlxform[X], wlxform[X]);
    cross(wlxform[Y], ucszdir, wlxform[X]);
    normalize(wlxform[Y], wlxform[Y]);
    cpypoint(wlxform[Z], ucszdir);
    for (i = X; i <= Z; i++)
        wlxform[i][T] = 0.0;          /* No translation */

    /*  Compose those two matrices to get the UCS-to-LCS transformation  */
    matxmat(ulxform, wlxform, uwxform);

    /*  Transformation's Z-column should be (0,0,1): */

#if 0  /* These asserts cause compilation errors on the Sparc... */
    assert(fabs(ulxform[X][Z]) < 1.0E-10);
    assert(fabs(ulxform[Y][Z]) < 1.0E-10);
    assert(fabs(ulxform[Z][Z] - 1.0) < 1.0E-10);
#endif

    /*  That's it: if the tablet sends in point (x,y), this matrix will
        act as if x and y were in UCS coords and transform them into the
        corresponding point (u,v) in LCS coords.  AutoCAD needs to turn
        that back into UCS coordinates, and (x,y) is the pair it comes
        up with.  We just need to throw away the Z-column (which we just
        assured is zero anyway) and pass the rows to ads_tablet().  */

    ulxform[X][Z] = ulxform[X][T];
    ulxform[Y][Z] = ulxform[Y][T];
    ttl = ads_buildlist(RTSHORT, 1,
                        RT3DPOINT, ulxform[X],
                        RT3DPOINT, ulxform[Y],
                        RT3DPOINT, ez,
                        RT3DPOINT, ucszdir,
                        RTNONE);
    i = ads_tablet(ttl, NULL);        /* Send it to AutoCAD */
    ads_relrb(ttl);
    if (i != RTNORM) {
        aperror(/*MSG0*/"ads_tablet");
        return RSERR;
    } else
        return RSRSLT;
}



/*  Some geometric utility functions  */


/* CROSS - cross product of two vectors, a = b X c */

void
/*FCN*/cross(ap, bp, cp)
  ads_real *ap;
  ads_real *bp, *cp;
{
    ap[X] = bp[Y] * cp[Z] - bp[Z] * cp[Y];
    ap[Y] = bp[Z] * cp[X] - bp[X] * cp[Z];
    ap[Z] = bp[X] * cp[Y] - bp[Y] * cp[X];
}


/*  MATXMAT -- Multiply two matrices.  The matrix type, ads_matrix,
               may be any array type with at least three rows and four
               columns.  */

void
/*FCN*/matxmat(matrixc, matrixa, matrixb)
  ads_matrix matrixc, matrixa, matrixb;
{
    int i, k;

    for (i = X; i <= Z; i++)
        for (k = X; k <= Z; k++)
            matrixc[i][k] = matrixa[i][X] * matrixb[X][k] +
                            matrixa[i][Y] * matrixb[Y][k] +
                            matrixa[i][Z] * matrixb[Z][k];
    for (i = X; i <= Z; i++)
        matrixc[i][T] = matrixa[i][X] * matrixb[X][T] +
                        matrixa[i][Y] * matrixb[Y][T] +
                        matrixa[i][Z] * matrixb[Z][T] +
                        matrixa[i][T];
}


/*  NORMALIZE  --  Normalize a vector: ap = unit vector in direction of bp.
                   Returns BAD if bp is null.  */

int
/*FCN*/normalize(ap, bp)
  register ads_real *ap;
  ads_real *bp;
{
    ads_real d;

    if ((d = (bp[X] * bp[X] + bp[Y] * bp[Y] + bp[Z] * bp[Z]))
        <= 1.0E-12)
        return BAD;
    d = 1.0 / sqrt(d);
    ap[X] = bp[X] * d;
    ap[Y] = bp[Y] * d;
    ap[Z] = bp[Z] * d;
    return GOOD;
}



/*  BILINCAL  --  Calibrate the digitizer for a bilinear mapping, i.e.,
                  a transformation which maps a quadrilateral on the
                  tablet into a quadrilateral in model space, with edges
                  mapping linearly into edges and interior points
                  mapping as follows: given point P inside the
                  quadrilateral on the tablet, draw a line which passes
                  through P and divides the two quadrilateral edges it
                  intersects in equal ratios.  Draw a line between the
                  two corresponding edges of the target quadrilateral
                  (in model space) which divides those edges in the same
                  ratio as that determined above.  Repeat this
                  operation, constructing a line between the other pair
                  of edges of the tablet quadrilateral, and similarly
                  draw a line between the corresponding edges of the
                  target quadrilateral dividing them in the same ratio.
                  P maps into the point at the intersection of the two
                  lines thus drawn in the target quadrilateral.  This is
                  essentially the 2-space analogue of a Coon's patch.

                  BILINCAL just determines the mapping by soliciting the
                  calibration points.  bilinmpt(), below, does the hard
                  work.  Note that, though there is one case in which we
                  complain about degenerate calibration points, there
                  are many other cases we miss entirely.  This code does
                  not purport to be very robust with respect to bad
                  input points.  */

static int
/*FCN*/bilincal()
{
    struct resbuf tm;
    int i;
    char sb[100];
    ads_point p11, p12, p0;

    /*  Make AutoCAD pass us raw digitizer coordinates  */

    if (digpass() != RSRSLT) {
        ads_fail(/*MSG3*/"Unable to establish digitizer pass-through mapping");
        return RSERR;
    }

    /*  Turn Tablet mode on  */

    tm.restype = RTSHORT;
    tm.resval.rint = 1;
    ads_setvar(/*MSG0*/"TABMODE", &tm);
    ads_getvar(/*MSG0*/"TABMODE", &tm);
    if (tm.resval.rint == 0) {
        ads_fail(/*MSG4*/"Unable to turn on Tablet mode");
        return RSERR;
    }

    /*  Get the calibration points */

    ads_printf(/*MSG5*/"\nFour-point calibration for bilinear mapping...");
    for (i = 0; i < 4; i++) {
        tm.resval.rint = 1;
        ads_setvar(/*MSG0*/"TABMODE", &tm);
        sprintf(sb, /*MSG6*/"\nDigitize point #%d: ", i + 1);
        if (ads_getpoint(NULL, sb, t[i]) != RTNORM)
            return RSERR;
        tm.resval.rint = 0;
        ads_setvar(/*MSG0*/"TABMODE", &tm);   /* Turn Tablet mode off */
        sprintf(sb, /*MSG7*/"\nEnter coordinates for point #%d: ", i + 1);
        if (ads_getpoint(NULL, sb, p[i]) != RTNORM)
            return RSERR;
    }

    /*  Reorder the points to assure that the last two tablet points
        have different Y-coordinates (to avoid some divisions by zero
        in bilinmpt)  */

    if (t[2][Y] == t[3][Y]) {
        if (t[1][Y] != t[3][Y]) {
            pswap(t[1],t[2]);
            pswap(p[1],p[2]);
        } else if (t[0][Y] != t[3][Y]) {
            pswap(t[0],t[2]);
            pswap(p[0],p[2]);
        } else {
            ads_fail(/*MSG8*/"Degenerate calibration points");
            return RSERR;
        }
    }

    /*  Figure out which sign to use in the solution of the quadratic
        equation inside bilinmpt, by determining which choice most
        nearly maps one of the calibration points correctly: */

    bilinsgn = 1;
    ibilinmpt(t[0], p11);
    bilinsgn = -1;
    ibilinmpt(t[0], p12);
    cpypoint(p0, p[0]);
    p0[Z] = p11[Z] = p12[Z] = 0.0;
    if (ads_distance(p11, p0) < ads_distance(p12, p0))
        bilinsgn = 1;

    tm.resval.rint = 1;               /* Exit with Tablet mode ON */
    ads_setvar(/*MSG0*/"TABMODE", &tm);

    cbrated = TRUE;
    ads_retvoid();
    return RSRSLT;
}


/*  BILINMPT  --  External function to perform the bilinear mapping on
                  a point.  Expects a single ads_point as argument,
                  returns an ads_point as value (if successful).  */

static int
/*FCN*/bilinmpt()
{
    struct resbuf *args;
    ads_point pprime;

    if (!cbrated) {
        ads_fail(/*MSG9*/"Bilinear mapping isn't calibrated");
        return RSERR;
    }
    args = ads_getargs();
    if (args->rbnext != NULL ||
        (args->restype != RTPOINT && args->restype != RT3DPOINT)) {
        ads_fail(/*MSG10*/"Improper arguments to bilinmpt");
        return RSERR;
    }

    /*  Call function to do the actual mathematics */

    if (ibilinmpt(args->resval.rpoint, pprime) != RSRSLT) {
        ads_fail(/*MSG11*/"bilinmpt can't transform point");
        return RSERR;
    } else {
        pprime[Z] = 0.0;
        ads_retpoint(pprime);
        return RSRSLT;
    }
}


/*  IBILINMPT  --  Perform the bilinear transformation on point "inpt",
                   leaving the result in "pprime"  */

static int
/*FCN*/ibilinmpt(inpt, pprime)
  ads_real *inpt, *pprime;
{
    ads_real x, y, xb, yb, xt, yt, a, b, c, r1, r2;
    ads_real x1, x2, x3, x4, y1, y2, y3, y4;

    x = inpt[X];
    y = inpt[Y];

    /*  Ok, Mathematica time.  Pardon the dearth of details, and the
        utter lack of simplification.  */

    /*  Put the four corners of the tablet quadrilateral in individual
        variables (this was helpful during debugging)  */

    x1 = t[0][X];
    x2 = t[1][X];
    x3 = t[2][X];
    x4 = t[3][X];
    y1 = t[0][Y];
    y2 = t[1][Y];
    y3 = t[2][Y];
    y4 = t[3][Y];

    /*  Compute the three coefficients in the quadratic equation whose
        solution is yt */

    c = x2*y*y3*y3 - x4*y*y3*y3 - x*y2*y3*y3 + x4*y2*y3*y3 - x1*y*y3*y4 -
        x2*y*y3*y4 + x3*y*y3*y4 + x4*y*y3*y4 + x*y1*y3*y4 - x4*y1*y3*y4 +
        x*y2*y3*y4 - x3*y2*y3*y4 + x1*y*y4*y4 - x3*y*y4*y4 - x*y1*y4*y4 +
        x3*y1*y4*y4;

    b = x1*y*y3 - x2*y*y3 - x3*y*y3 + x4*y*y3 - x*y1*y3 + x4*y1*y3 +
        x*y2*y3 + x3*y2*y3 - 2*x4*y2*y3 + x*y3*y3 - x2*y3*y3 - x1*y*y4 +
        x2*y*y4 + x3*y*y4 - x4*y*y4 + x*y1*y4 - 2*x3*y1*y4 + x4*y1*y4 -
        x*y2*y4 + x3*y2*y4 - 2*x*y3*y4 + x1*y3*y4 + x2*y3*y4 + x*y4*y4 -
        x1*y4*y4;   

    a = x3*y1 - x4*y1 - x3*y2 + x4*y2 - x1*y3 + x2*y3 + x1*y4 -
        x2*y4;

    /*  The quadratic equation for yt has two solutions; the choice of
        which to use is made externally by an appropriate setting of
        bilinsgn.  */

    yt = (-b + bilinsgn * sqrt(b * b - 4.0 * a * c)) / (2.0 * a);

    /*  Fill in xb, yb, and xt.  When finished, (xb,yb) will be a point
        on the lower edge of the quadrilateral, and (xt,yt) will be a
        point on its upper edge.  They will divide their respective
        edges in the same ratio, and inpt(x,y) will lie on the line
        joining them.  */

    xb = (x2*y3 - x1*y4 + x1*yt - x2*yt) / (y3 - y4);
    yb = (y2*y3 - y1*y4 + y1*yt - y2*yt) / (y3 - y4);
    xt = (x4*y3 - x3*y4 + x3*yt - x4*yt) / (y3 - y4); 
    if (x2 == x1)
        r1 = (y2 - yb) / (y2 - y1);
    else
        r1 = (x2 - xb) / (x2 - x1);
    if (xt == xb)
        r2 = (yt - y) / (yt - yb);
    else
        r2 = (xt - x) / (xt - xb);
    
    /*  The r1 and r2 are the "Coon's patch" coordinates of the input
        point; transfer them to the output.  */

    xb = r1 * p[0][X] + (1 - r1) * p[1][X];
    yb = r1 * p[0][Y] + (1 - r1) * p[1][Y];
    xt = r1 * p[2][X] + (1 - r1) * p[3][X];
    yt = r1 * p[2][Y] + (1 - r1) * p[3][Y];
    pprime[X] = r2 * xb + (1 - r2) * xt;
    pprime[Y] = r2 * yb + (1 - r2) * yt;
    return RSRSLT;
}



/*  PSWAP  --  Swap two 2D points */

static void
/*FCN*/pswap(p1, p2)
  ads_real *p1, *p2;
{
    ads_real t;

    t = p1[X];
    p1[X] = p2[X];
    p2[X] = t;
    t = p1[Y];
    p1[Y] = p2[Y];
    p2[Y] = t;
}



/*  SAMPLE  --  Draw a sample figure for exercising the bilinear
                mapping.  This function is not religious about
                restoring system variables to previous settings.  */

static int
/*FCN*/sample()
{
    char junk[20];
    struct resbuf svb;
    int i;
    static char *msg[] = {
        "",
        /*MSG12*/"We will now draw a sample figure which you can use to test",
        /*MSG13*/"bilinear tablet transformations.  When complete, plot this",
        /*MSG14*/"figure and attach it to your tablet surface.  Replace one of",
        /*MSG15*/"the items in your BUTTONS menu with the following:",
        "",
        /*MSG0*/"^P(bilinmpt (getpoint)) \\^P",
        "",
        /*MSG16*/"Then enter BILINCAL, and when you are prompted for points,",
        /*MSG17*/"digitize (with the normal pick button) the corners of the",
        /*MSG18*/"quadrilateral in this order:",
        "",
        /*MSG19*/"   lower left, lower right, upper left, upper right",
        "",
        /*MSG20*/"Give them coordinates of (0,0), (16,0), (0,16), and (16,16),",
        /*MSG21*/"respectively.  Then, using the button to which you attached",
        /*MSG22*/"the above menu item, digitize lines connecting the four",
        /*MSG23*/"corners of the quadrilateral.  To see what this transformation",
        /*MSG24*/"does to straight lines, enter a polyline following the curved",
        /*MSG25*/"path in the figure and another polyline along the straight", 
        /*MSG26*/"diagonal path.",
        "",
        NULL
    };

    /*  Print the message */

    ads_textscr();
    for (i = 0; msg[i] != NULL; i++)
        ads_printf("%s\n", msg[i]);
    if (ads_getstring(0, /*MSG27*/"Press RETURN to continue: ", junk) != RTNORM)
        return RSERR;

    /*  Draw the figure */

    ads_graphscr();
    ads_getvar(/*MSG0*/"CMDECHO", &svb);
    svb.resval.rint = 0;
    ads_setvar(/*MSG0*/"CMDECHO", &svb);
         
    ads_command(RTSTR, /*MSG0*/"_.ZOOM", RTSTR, /*MSG0*/"_W", RTSTR, "-10,0",
                RTSTR, "1.5,12", RTNONE);

    dline("1.0,1.0", "-7.0,1.0");
    dline("-7.0,1.0", "-9.0,8.0");
    dline("-9.0,8.0", "-6.0,11.0");
    dline("-6.0,11.0", "1.0,1.0");
    dline("0.5,1.0", "-6.1875,10.8125");
    dline("-6.375,10.625", "0.0,1.0");
    dline("-0.5,1.0", "-6.5625,10.4375");
    dline("-6.75,10.25", "-1.0,1.0");
    dline("-1.5,1.0", "-6.9375,10.0625");
    dline("-7.125,9.875", "-2.0,1.0");
    dline("-2.5,1.0", "-7.3125,9.6875");
    dline("-7.5,9.5", "-3.0,1.0");
    dline("-3.5,1.0", "-7.6875,9.3125");
    dline("-7.875,9.125", "-4.0,1.0");
    dline("-4.5,1.0", "-8.0625,8.9375");
    dline("-8.25,8.75", "-5.0,1.0");
    dline("-5.5,1.0", "-8.4375,8.5625");
    dline("-8.625,8.375", "-6.0,1.0");
    dline("-6.5,1.0", "-8.8125,8.1875");
    dline("0.5625,1.625", "-7.125,1.4375");
    dline("-7.25,1.875", "0.125,2.25");
    dline("-0.3125,2.875", "-7.375,2.3125");
    dline("-7.5,2.75", "-0.75,3.5");
    dline("-1.1875,4.125", "-7.625,3.1875");
    dline("-7.75,3.625", "-1.625,4.75");
    dline("-2.0625,5.375", "-7.875,4.0625");
    dline("-8.0,4.5", "-2.5,6.0");
    dline("-2.9375,6.625", "-8.125,4.9375");
    dline("-8.25,5.375", "-3.375,7.25");
    dline("-3.8125,7.875", "-8.375,5.8125");
    dline("-8.5,6.25", "-4.25,8.5");
    dline("-4.6875,9.125", "-8.625,6.6875");
    dline("-8.75,7.125", "-5.125,9.75");
    dline("-5.5625,10.375", "-8.875,7.5625");

    ads_command(RTSTR,/*MSG0*/"_.PLINE", RTNONE);
    dvert("-7.0,1.0");
    dvert("-6.7636,1.2868");
    dvert("-6.6445,1.4492");
    dvert("-6.3673,1.8608");
    dvert("-6.3281,1.9219");
    dvert("-6.1843,2.1674");
    dvert("-6.0508,2.418");
    dvert("-5.926,2.6764");
    dvert("-5.8125,2.9375");
    dvert("-5.7069,3.2089");
    dvert("-5.6133,3.4805");
    dvert("-5.5271,3.7649");
    dvert("-5.4531,4.0469");
    dvert("-5.3866,4.3442");
    dvert("-5.332,4.6367");
    dvert("-5.2853,4.947");
    dvert("-5.25,5.25");
    dvert("-5.2231,5.573");
    dvert("-5.207,5.8867");
    dvert("-5.2001,6.2224");
    dvert("-5.2031,6.5469");
    dvert("-5.2163,6.8951");
    dvert("-5.2383,7.2305");
    dvert("-5.2716,7.5911");
    dvert("-5.3125,7.9375");
    dvert("-5.3659,8.3105");
    dvert("-5.4258,8.668");
    dvert("-5.4993,9.0532");
    dvert("-5.5781,9.4219");
    dvert("-5.5911,9.477");
    dvert("-5.7695,10.1992");
    dvert("-5.8442,10.4783");
    dvert("-6.0,11.0");
    ads_command(RTSTR, "", RTNONE);

    dline("-7.0,1.0", "-6.0,11.0");   /* Draw the "diagonal" */

    svb.resval.rint = 1;  
    ads_setvar(/*MSG0*/"CMDECHO", &svb);      /* Turn command echo back on. */

    ads_retvoid();
    return RSRSLT;
}


/*  DLINE  --  Draw a line.  */

static void
/*FCN*/dline(p1str, p2str)
  char *p1str, *p2str;
{
    ads_command(RTSTR, /*MSG0*/"_.LINE", RTSTR, p1str, RTSTR, p2str, RTSTR, "",
                RTNONE);
}

/*  DVERT  --  Draw one vertex of a Polyline */

static void
/*FCN*/dvert(p1str)
  char *p1str;
{
    ads_command(RTSTR, p1str, RTNONE);
}


/*  SAVCAL  --  External function to save the current tablet calibration
                locally.  */

static int
/*FCN*/savcal()
{
    int stat;
    struct resbuf *rb;

    /*  Free any previously saved calibration  */
    if (tcal != NULL) {
        ads_relrb(tcal);
        tcal = NULL;
    }

    /*  Build argument list to request current calibration from 
        ads_tablet()  */
    rb = ads_buildlist(RTSHORT, 0, RTNONE);
    stat = ads_tablet(rb, &tcal);
    ads_relrb(rb);

    if (stat == RTNORM)
        stat = RSRSLT;
    else {
        aperror(/*MSG0*/"ads_tablet");
        stat = RSERR;
    }
    ads_retvoid();
    return stat;
}


/*  RSTCAL  --  External function to restore a tablet calibration
                previously saved by savcal().  */

static int
/*FCN*/rstcal()
{
    int stat = RSERR;

    if (tcal == NULL)
        ads_fail(/*MSG28*/"rstcal: no calibration saved by savcal\n");
    else if (ads_tablet(tcal, NULL) != RTNORM)
        aperror(/*MSG0*/"ads_tablet");
    else
        stat = RSRSLT;
    ads_retvoid();
    return stat;
}


/*  APERROR  --  Poor brother to ads_perr:  display the value of
                 errno associated with a given ADS routine.  */
static void
/*FCN*/aperror(routine)
  char *routine;
{
    struct resbuf gvb;
    char msgbuf[60];

    ads_getvar(/*MSG0*/"ERRNO", &gvb);
    if (routine == NULL)
        sprintf(msgbuf, /*MSG29*/"Error %d", gvb.resval.rint);
    else
        sprintf(msgbuf, /*MSG30*/"Error %d from %s", gvb.resval.rint, routine);
    ads_fail(msgbuf);
}
