/* Next available MSG number is    14 */

/*****************************************************************************
      UTIL.C
      (C) Copyright 1988-1994 by Autodesk, Inc.

      This program is copyrighted by Autodesk, Inc. and is  licensed
      to you under the following conditions.  You may not distribute
      or  publish the source code of this program in any form.   You
      may  incorporate this code in object form in derivative  works
      provided  such  derivative  works  are  (i.) are  designed and
      intended  to  work  solely  with  Autodesk, Inc. products, and
      (ii.)  contain  Autodesk's  copyright  notice  "(C)  Copyright
      1988-1993 by Autodesk, Inc."

      AUTODESK  PROVIDES THIS PROGRAM "AS IS" AND WITH  ALL  FAULTS.
      AUTODESK  SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF  MER-
      CHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK,  INC.
      DOES  NOT  WARRANT THAT THE OPERATION OF THE PROGRAM  WILL  BE
      UNINTERRUPTED OR ERROR FREE.

  Description: Library of various utility functions. This is not a well
               organized library, just a rather random collection of 
               functions. But you might find some of the functions useful.
  
*****************************************************************************/


/****************************************************************************/
/*  INCLUDES                                                                */
/****************************************************************************/

#define MODULE_ID UTIL_C_

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

#include "xmf.h"
#include "ads_ix.h"


/****************************************************************************/
/*  DEFINES                                                                 */
/****************************************************************************/

#define ARBBOUND    0.015625          /* Bound for arbitrary matrix alg. */
#define ZERO_BULGE  1e-7              /* Bulge which is considered zero  */


/****************************************************************************/
/*  STATIC VARIABLES                                                        */
/****************************************************************************/

/* Save area for various AutoCAD system variables */

static struct resbuf cmdecho, blipmode, highlight, ucsicon, aunits, lunits,
                     orthomode, osmode, snapmode, thickness, elevation,
                     angdir;

static struct resbuf cmde, sortents;        


/****************************************************************************/
/*  STATIC FUNCTIONS                                                        */
/****************************************************************************/

static void sa_make_pickbox_equal_to_aperture _((int do_it));
static int  sa_e2u _((ads_point p1, ads_name ent_name, int tested,
                      ads_point m[4], ads_point normal, ads_point p2));


/****************************************************************************/
/*.doc sa_save_acad_vars(external) */
/*+
  Saves AutoCAD system variables which may negatively influence the 
  ads_command() function.
-*/
/****************************************************************************/


void
/*FCN*/sa_save_acad_vars()
{
    struct resbuf rb;

    ads_getvar(/*MSG0*/"CMDECHO",   &cmdecho  );
    ads_getvar(/*MSG0*/"BLIPMODE",  &blipmode );
    ads_getvar(/*MSG0*/"HIGHLIGHT", &highlight);
    ads_getvar(/*MSG0*/"UCSICON",   &ucsicon  );
    ads_getvar(/*MSG0*/"AUNITS",    &aunits   );
    ads_getvar(/*MSG0*/"LUNITS",    &lunits   );
    ads_getvar(/*MSG0*/"ORTHOMODE", &orthomode);
    ads_getvar(/*MSG0*/"OSMODE",    &osmode   );
    ads_getvar(/*MSG0*/"SNAPMODE",  &snapmode );
    ads_getvar(/*MSG0*/"THICKNESS", &thickness);
    ads_getvar(/*MSG0*/"ELEVATION", &elevation);
    ads_getvar(/*MSG0*/"ANGDIR",    &angdir   );

    rb.rbnext      = NULL;
    rb.restype     = RTSHORT;
    rb.resval.rint = 0;

    ads_setvar(/*MSG0*/"CMDECHO",  &rb);
    ads_setvar(/*MSG0*/"BLIPMODE", &rb);
    ads_setvar(/*MSG0*/"HIGHLIGHT",&rb);
    ads_setvar(/*MSG0*/"UCSICON",  &rb);
    ads_setvar(/*MSG0*/"AUNITS",   &rb);
    ads_setvar(/*MSG0*/"ORTHOMODE",&rb);
    ads_setvar(/*MSG0*/"OSMODE",   &rb);
    ads_setvar(/*MSG0*/"SNAPMODE", &rb);
    ads_setvar(/*MSG0*/"ANGDIR",   &rb);

    rb.resval.rint = 1;
    ads_setvar(/*MSG0*/"LUNITS",   &rb);

    rb.restype     = RTREAL;
    rb.resval.rreal = 0.0;
    ads_setvar(/*MSG0*/"THICKNESS", &rb);
    ads_setvar(/*MSG0*/"ELEVATION", &rb);
} /*sa_save_acad_vars*/


/****************************************************************************/
/*.doc sa_restore_acad_vars(external) */
/*+
  Restores the system variables which were saved by save_acad_vars().
-*/
/****************************************************************************/


void
/*FCN*/sa_restore_acad_vars()
{
    ads_setvar(/*MSG0*/"BLIPMODE",  &blipmode );
    ads_setvar(/*MSG0*/"HIGHLIGHT", &highlight);
    ads_setvar(/*MSG0*/"UCSICON",   &ucsicon  );
    ads_setvar(/*MSG0*/"AUNITS",    &aunits   );
    ads_setvar(/*MSG0*/"LUNITS",    &lunits   );
    ads_setvar(/*MSG0*/"ORTHOMODE", &orthomode);
    ads_setvar(/*MSG0*/"OSMODE",    &osmode   );
    ads_setvar(/*MSG0*/"SNAPMODE",  &snapmode );
    ads_setvar(/*MSG0*/"CMDECHO",   &cmdecho  );
    ads_setvar(/*MSG0*/"THICKNESS", &thickness);
    ads_setvar(/*MSG0*/"ELEVATION", &elevation);
    ads_setvar(/*MSG0*/"ANGDIR",    &angdir   );
} /*sa_restore_acad_vars*/


/****************************************************************************/
/*.doc sa_cmdecho_off(external) */
/*+
  Saves the value of the CMDECHO variable and sets it to 0.
-*/
/****************************************************************************/


void
/*FCN*/sa_cmdecho_off()
{
    struct resbuf rb;

    ads_getvar(/*MSG0*/"CMDECHO", &cmde);

    rb.restype     = RTSHORT;
    rb.resval.rint = 0;

    ads_setvar(/*MSG0*/"CMDECHO", &rb);
} /*sa_cmdecho_off*/


/****************************************************************************/
/*.doc sa_cmdecho_back(external) */
/*+
  Restores the original value of the CMDECHO variable.
-*/
/****************************************************************************/


void
/*FCN*/sa_cmdecho_back()
{
    ads_setvar(/*MSG0*/"CMDECHO", &cmde);
} /*sa_cmdecho_back*/


/****************************************************************************/
/*.doc sa_undo_group(external) */
/*+
  Begins an undo group.
-*/
/****************************************************************************/


void
/*FCN*/sa_undo_group()
{
    /* Don't do this at all if UNDO is off. */
    struct resbuf cmdv;
    ads_getvar(/*MSG0*/"UNDOCTL", &cmdv);
    if (cmdv.resval.rint & 1) {		/* undo is on */
	sa_cmdecho_off();
	ads_command(RTSTR, /*MSG0*/"_.UNDO", RTSTR, /*MSG0*/"_GROUP", 0);
	sa_cmdecho_back();
    }
} /*sa_undo_group*/


/****************************************************************************/
/*.doc sa_undo_end(external) */
/*+
  Ends the undo group.
-*/
/****************************************************************************/


void
/*FCN*/sa_undo_end()
{
    /* Don't do this at all if UNDO is off. */
    struct resbuf cmdv;
    ads_getvar(/*MSG0*/"UNDOCTL", &cmdv);
    if (cmdv.resval.rint & 1) {		/* undo is on */
	sa_cmdecho_off();
	ads_command(RTSTR, /*MSG0*/"_.UNDO", RTSTR, /*MSG0*/"_END", 0);
	sa_cmdecho_back();
    }
} /*sa_undo_end*/


/****************************************************************************/
/*.doc sa_set_sortents(external) */
/*+
  Sets the mode of picking. When entities overlap, the method of
  disambiguating is controlled by the AutoCAD SORTENTS variable.
  See the description for SORTENTS for full meaning of the pick modes.
-*/
/****************************************************************************/


void
/*FCN*/sa_set_sortents(pick_mode)
  int pick_mode;
{
    struct resbuf rb_mode;

    rb_mode.rbnext = NULL;
    rb_mode.restype = RTSHORT;
    rb_mode.resval.rint = pick_mode;
    ads_setvar(/*MSG0*/"SORTENTS",&rb_mode);
} /*sa_set_sortents*/


/****************************************************************************/
/*.doc sa_save_sortents(external) */
/*+
  Saves the value of the SORTENTS system variable. This function is
  typically called if the value of SORTENTS is to be restored by calling
  sa_restore_sortents() later.
  NOTE: There is no stack of values maintain. Calling the function
        overwrites the previous value in sortents.
-*/
/****************************************************************************/


void
/*FCN*/sa_save_sortents()
{
    ads_getvar(/*MSG0*/"SORTENTS", &sortents);
} /*sa_save_sortents*/


/****************************************************************************/
/*.doc sa_restore_sortents(external) */
/*+
  Restores the value of the AutoCAD variable SORTENTS to the value that
  was obtained by a previous call to sa_save_sortents(). 
  NOTE: This function must be called only if there was a previous call
        to sa_save_sortents().
-*/
/****************************************************************************/


void
/*FCN*/sa_restore_sortents()
{
    ads_setvar(/*MSG0*/"SORTENTS", &sortents);
} /*sa_restore_sortents*/


/****************************************************************************/
/*.doc sa_make_pickbox_equal_to_aperture(external) */
/*+
  TRUE => Makes the value of PICKBOX variable equal to APERTURE variable.
  FALSE=> Returns value of PICKBOX variable back.
-*/
/****************************************************************************/


static void
/*FCN*/sa_make_pickbox_equal_to_aperture(do_it)

  int do_it;
{
    static struct resbuf pickbox;
    struct resbuf        aperture;
    
    if (do_it) {
        ads_getvar(/*MSG0*/"PICKBOX",  &pickbox );
        ads_getvar(/*MSG0*/"APERTURE", &aperture);
        ads_setvar(/*MSG0*/"PICKBOX",  &aperture);
    } else { /*Return it back*/
        ads_setvar(/*MSG0*/"PICKBOX",  &pickbox );
    }
} /*sa_make_pickbox_equal_to_aperture*/


/****************************************************************************/
/*.doc sa_points_are_collinear(external) */
/*+
  Returns TRUE if the three given points are collinear.
-*/
/****************************************************************************/


int
/*FCN*/sa_points_are_collinear(p1, p2, p3)

  ads_point p1, p2, p3;
{
    ads_point v1, v2;
    int       ok1, ok2;

    SUB_PNT(v1, p2, p1);
    SUB_PNT(v2, p3, p1);

    ok1 = sa_normalize(v1);
    ok2 = sa_normalize(v2);

    if (!ok1 || !ok2)
        return(TRUE);

    return(fabs(DOTPROD(v1, v2)) > EPS_COS);
} /*sa_points_are_collinear*/


/****************************************************************************/
/*.doc sa_cross(external) */
/*+
  Cross product of two vectors.
-*/
/****************************************************************************/


void
/*FCN*/sa_cross(v, v1, v2)

  ads_point v, v1, v2;
{
    v[X] = v1[Y] * v2[Z] - v1[Z] * v2[Y];
    v[Y] = v1[Z] * v2[X] - v1[X] * v2[Z];
    v[Z] = v1[X] * v2[Y] - v1[Y] * v2[X];
} /*sa_cross*/


/****************************************************************************/
/*.doc sa_det(external) */
/*+
  Evaluates the determinant of 3x3 matrix. The given vectors a, b and c 
  are the rows of the matrix.
-*/
/****************************************************************************/


double
/*FCN*/sa_det(a, b, c)

  ads_point a, b, c;
{
    return(a[X] * (b[Y] * c[Z] - b[Z] * c[Y]) +
           a[Y] * (b[Z] * c[X] - b[X] * c[Z]) +
           a[Z] * (b[X] * c[Y] - b[Y] * c[X]));
} /*sa_det*/


/****************************************************************************/
/*.doc sa_plane_from_point_and_normal(external) */
/*+
  Takes point on plane 'origin' and point on plane normal 'zaxis' and 
  returns two additional points 'xaxis' and 'yaxis' lying on the plane. 
  The points 'xaxis' and 'yaxis' are calculated based on the arbitrary axis 
  algorithm.
  
  Function returns one of the standard ADS result codes.
-*/
/****************************************************************************/


int
/*FCN*/sa_plane_from_point_and_normal(origin, zaxis, xaxis, yaxis)

  ads_point origin,zaxis;             /* Two input points      */
  ads_point xaxis,yaxis;              /* Two calculated points */
{
    int       success;
    ads_point normal;
    ads_point m[4];

    SUB_PNT(normal, zaxis, origin);

    success = sa_make_arb_matrix(normal, m);
    if (success != RTNORM)
        return(RTERROR);

    ADD_PNT(xaxis, origin, m[X]);
    ADD_PNT(yaxis, origin, m[Y]);

    return(RTNORM);
} /*sa_plane_from_point_and_normal*/


/****************************************************************************/
/*.doc sa_3d_angle(external) */
/*+
  Returns the angle of triangle (apex,p1,p2). All  points are considered 
  threedimensional, the resulting angle is within interval <0;PI>.
-*/
/****************************************************************************/


double
/*FCN*/sa_3d_angle(apex, p1, p2)

  ads_point apex, p1, p2;
{
    ads_point v1, v2;
    double    l1, l2;
    double    cosa;
    double    angle;

    SUB_PNT(v1, p1, apex); l1 = LENGTH(v1);
    SUB_PNT(v2, p2, apex); l2 = LENGTH(v2);

    if ((l1 < EPS) || (l2 < EPS))
        return(0.0);

    cosa = DOTPROD(v1, v2) / (l1 * l2);
    if (cosa >  1.0)
        cosa =  1.0;
    if (cosa < -1.0)
        cosa = -1.0;

    angle = acos(cosa);

    return(angle);
} /*sa_3d_angle*/


/****************************************************************************/
/*.docsa_ rotate_point_around_axis(external) */
/*+
  Rotates point 'pp' around axis (p1,p2) through angle 'angle'. The axis 
  is oriented from 'p1' to 'p2'.

  Function returns one of the standard ADS result codes.
-*/
/****************************************************************************/


int
/*FCN*/sa_rotate_point_around_axis(pp, angle, p1, p2)

  ads_point pp;
  double    angle;
  ads_point p1, p2;
{
    ads_point p;
    ads_point n;
    double    cosa, sina, cosb, sinb, cosc, sinc;
    double    lenxy,len;
    double    px,pz;

    if (sa_points_are_collinear(pp, p1, p2))
        return(RTREJ);

    SUB_PNT(n, p2, p1);

    lenxy = sqrt(SQR(n[X]) + SQR(n[Y]));

    if (lenxy > EPS) {
        cosa = n[X] / lenxy;
        sina = n[Y] / lenxy;
    } else {
        cosa = 1.0;
        sina = 0.0;
    }

    len  = LENGTH(n);
    cosb = n[Z]  / len;
    sinb = lenxy / len;

    cosc = cos(angle);
    sinc = sin(angle);

    SUB_PNT(p, pp, p1);

    px   =  cosa * p[X] + sina * p[Y];
    p[Y] = -sina * p[X] + cosa * p[Y];
    p[X] =  px;

    pz   =  cosb * p[Z] + sinb * p[X];
    p[X] = -sinb * p[Z] + cosb * p[X];
    p[Z] =  pz;

    px   =  cosc * p[X] - sinc * p[Y];
    p[Y] =  sinc * p[X] + cosc * p[Y];
    p[X] =  px;

    pz   =  cosb * p[Z] - sinb * p[X];
    p[X] =  sinb * p[Z] + cosb * p[X];
    p[Z] =  pz;

    px   =  cosa * p[X] - sina * p[Y];
    p[Y] =  sina * p[X] + cosa * p[Y];
    p[X] =  px;

    ADD_PNT(pp, p, p1);

    return(RTNORM);
} /*sa_rotate_point_around_axis*/


/****************************************************************************/
/*.doc sa_angle_around_axis(external) */
/*+
  Returns angle between points 'from' and 'to' measured around axis (p1,p2). 
  The axis is oriented from p1 to p2.
-*/
/****************************************************************************/


double
/*FCN*/sa_angle_around_axis(p1, p2, from, to)

  ads_point p1, p2;
  ads_point from, to;
{
    ads_point nor1, nor2;
    ads_point v, v1, v2;
    double    l1, l2;
    double    angle;
    double    cosa;

    if (sa_points_are_collinear(p1, p2, from) || 
        sa_points_are_collinear(p1, p2, to)) {
        return(0.0);
    }

    SUB_PNT(v, p2, p1);

    SUB_PNT(v1, from, p1);
    SUB_PNT(v2, to  , p1);

    sa_cross(nor1, v1, v); l1 = LENGTH(nor1);
    sa_cross(nor2, v2, v); l2 = LENGTH(nor2);

    cosa  = DOTPROD(nor1, nor2) / (l1 * l2);
    angle = acos(cosa);

    if (sa_det(v, v1, v2) < 0.0)
        angle = -angle;

    if (angle <  0.0)
        angle += 2*PI;
    if (angle > 2*PI)
        angle -= 2*PI;

    return(angle);
} /*sa_angle_around_axis*/


/****************************************************************************/
/*.doc sa_orientate_cs_upwards(external) */
/*+
  Orientates the given coordinate system, defined by four points (origin,
  xaxis, yaxis and zaxis), so that its z-axis (from origin to point zaxis) 
  goes upwards, that is in the direction of the Z-axis of the current UCS.

  If the z-axis of the given coordinates system lies in the XY plane of the 
  current UCS, then the orientation is determined according to either the X 
  or Y axis of the current UCS, depending on to which axis is the given 
  z-axis more parallel.
-*/
/****************************************************************************/


void
/*FCN*/sa_orientate_cs_upwards(origin, xaxis, yaxis, zaxis)

  ads_point origin;                   /* Point at origin          */
  ads_point xaxis, yaxis, zaxis;      /* Point on X, Y and Z axis */
{
    double    len;
    int       reverse;
    ads_point p;

    len = DISTANCE(origin, zaxis);

    if (fabs(origin[Z] - zaxis[Z]) / len >= ARBBOUND)
        reverse = (origin[Z] > zaxis[Z]);
    else
    if (fabs(origin[X] - zaxis[X]) >= fabs(origin[Y] - zaxis[Y]))
        reverse = (origin[X] > zaxis[X]);
    else
        reverse = (origin[Y] > zaxis[Y]);

    if (reverse) {
        CPY_PNT(p,      origin);
        CPY_PNT(origin, zaxis);
        CPY_PNT(zaxis,  p);

        CPY_PNT(p,     xaxis);
        CPY_PNT(xaxis, yaxis);
        CPY_PNT(yaxis, p);
    }
} /*sa_orientate_cs_upwards*/


/****************************************************************************/
/*.doc sa_is_planar_pline(external) */
/*+
  Retruns TRUE if the pline having the given vertex is a planar 2D polyline, 
  otherwise returns FALSE. The output argument 'normal' will contain the
  entity normal vector.
-*/
/****************************************************************************/


int
/*FCN*/sa_is_planar_pline(vertex_name, normal)

  ads_name  vertex_name;
  ads_point normal;
{
    ads_name      pline_name;
    int           flags = 8;    /* Just in case that group 70 is not found */
    int           success;
    struct resbuf *rb, *rb_rel;

    normal[X] = normal[Y] = normal[Z] = 0.0;

    success = sa_pline_name_from_vertex_name(vertex_name, pline_name);
    if (success != RTNORM)
        return(FALSE);

    rb = rb_rel = ads_entget(pline_name);

    while (rb != NULL) {
        if (rb->restype == 70) {
            flags = rb->resval.rint;
        } else if (rb ->restype == 210) {
            CPY_PNT(normal, rb->resval.rpoint);
        }
        rb = rb->rbnext;
    }
    
    ads_relrb(rb_rel);

    return((flags & (8 + 16 + 64)) == 0);
} /*sa_is_planar_pline*/


/****************************************************************************/
/*.doc sa_get_pline_segment_data(external) */
/*+
  Returns the endpoints p1 and p2, center, radius and normal vector of 
  a polyline segment, given by its name (name of VERTEX entity) and by vertex 
  entity data list of result buffers. 

  If the pline segment is straight, then center contains the midpoint of 
  the segment and radius is 0.0.

  All the returned coordinates are expressed in ECS.
  
  Function returns one of the standard ADS result codes.
-*/
/****************************************************************************/


int
/*FCN*/sa_get_pline_segment_data(plseg_name, plseg_rb, p1, p2, 
                                 center, radius, normal)

  ads_name      plseg_name;      /* Name of pline segment (vertex)          */
  struct resbuf *plseg_rb;       /* Vertex data as a list of result buffers */

  ads_point     p1, p2;          /* Returned endpoints of the segment       */
  ads_point     center;          /* Returned center of arc segment          */
  double        *radius;         /* Returned radius of arc segment          */
  ads_point     normal;          /* Entity normal vector                    */
{
    int           success;
    double        bulge;
    struct resbuf *rb;
    struct resbuf *next_rb, *first_rb;
    ads_name      next_name, first_name;
    ads_name      pline_name;
    double        f, dist;

    success = RTNORM;
    next_rb = first_rb = NULL;

    /* Check whether it is realy a planar non-splined 2D polyline */

    if (!sa_is_planar_pline(plseg_name, normal))
        return(RTREJ);

    /* Get the first point and bulge */

    bulge = 0.0;
    rb = plseg_rb;

    while (rb != NULL) {
        if (rb->restype == 42) {
            bulge = rb->resval.rreal;
        } else if (rb->restype == 10) {
            CPY_PNT(p1, rb->resval.rpoint);
        }
        rb = rb->rbnext;
    } /*while*/

    /* Go to the next vertex */

    success = ads_entnext(plseg_name, next_name);
    if (success != RTNORM)
        return(RTERROR);

    next_rb = ads_entget(next_name);
    if (next_rb == NULL)
        return(RTERROR);

    rb = next_rb->rbnext;
    if (rb->restype != 0) {
        success = RTERROR;
        goto Cleanup;
    }

    /* If it is the last segment of closed entity then go to first segment */

    if (strcmp(rb->resval.rstring, /*MSG0*/"SEQEND") == 0) {

        while ((rb != NULL) && (rb->restype != -2))
            rb = rb->rbnext;

        if (rb == NULL) {
            success = RTERROR;
            goto Cleanup;
        }

        pline_name[0] = rb->resval.rlname[0];
        pline_name[1] = rb->resval.rlname[1];

        success = ads_entnext(pline_name, first_name);
        if (success != RTNORM) {
            success = RTERROR;
            goto Cleanup;
        }

        first_rb = ads_entget(first_name);
        if (first_rb == NULL) {
            success = RTERROR;
            goto Cleanup;
        }

        rb = first_rb->rbnext;
        if (rb->restype != 0) {
            success = RTERROR;
            goto Cleanup;
        }
    } else if (strcmp(rb->resval.rstring, /*MSG0*/"VERTEX") != 0) {
        success = RTERROR;
        goto Cleanup;
    }

    /* Get second point of either the next or the first vertex */

    while ((rb != NULL) && (rb->restype != 10))
        rb = rb->rbnext;

    if (rb == NULL) {
        success = RTERROR;
        goto Cleanup;
    }

    CPY_PNT(p2, rb->resval.rpoint);


    /* Calculate center and radius of the pline arc segment */

    if (fabs(bulge) < ZERO_BULGE) {
        *radius = 0.0;
        ADD_PNT(center, p1, p2);
        center[X] /= 2.0;
        center[Y] /= 2.0;
        center[Z] /= 2.0;
    } else {
        dist = DISTANCE(p1, p2);
        *radius = fabs(0.25 * dist * (bulge + 1.0 / bulge));

        f = -0.25 * (bulge - 1/bulge);
        center[X] = (p1[X] + p2[X])/2 + f*(p1[Y] - p2[Y]);
        center[Y] = (p1[Y] + p2[Y])/2 + f*(p2[X] - p1[X]);
        center[Z] = (p1[Z] + p2[Z])/2;
    }
    success = RTNORM;

Cleanup:

    if (next_rb  != NULL)
        ads_relrb(next_rb);
    if (first_rb != NULL)
        ads_relrb(first_rb);

    return(success);
} /*sa_get_pline_segment_data*/


/****************************************************************************/
/*.doc sa_pline_name_from_vertex_name(external) */
/*+
  Gives polyline name from a given name of a polyline segment (VERTEX).

  Function returns one of the standard ADS result codes.
-*/
/****************************************************************************/


int
/*FCN*/sa_pline_name_from_vertex_name(vertex_name, pline_name)

  ads_name vertex_name;               /* Name of polyline segment (VERTEX) */
  ads_name pline_name;                /* Returned name of the polyline     */
{
    ads_name      name;
    struct resbuf *rb;
    int           success;

    ads_name_set(vertex_name, name);

    for (;;) {
        success = ads_entnext(name, name);
        if (success != RTNORM)
            return(RTERROR);

        rb = ads_entget(name);
        if ((rb == NULL) || (rb->rbnext == NULL) || 
            (rb->rbnext->restype != 0)) {
            ads_relrb(rb);
            return(RTERROR);
        }

        if (strcmp(rb->rbnext->resval.rstring, /*MSG0*/"SEQEND") == 0) {
            struct resbuf *rel_rb;

            rel_rb = rb;
            rb = rb->rbnext->rbnext;

            while (rb->restype != -2)
                rb = rb->rbnext;

            ads_name_set(rb->resval.rlname, pline_name);

            ads_relrb(rel_rb);
            return(RTNORM);
        } /*if*/

        ads_relrb(rb);
        if (success != RTNORM)
            return(RTERROR);
    } /*forever*/
} /*sa_pline_name_from_vertex_name*/


/****************************************************************************/
/*.doc sa_tranform_pt(external) */
/*+
  Transforms given point p1 by 3x4 matrix 'm'. Transformed point is returned 
  in point p2.
-*/
/****************************************************************************/


void
/*FCN*/sa_transform_pt(m, p1, p2)

  ads_point m[4];
  ads_point p1,p2;
{
    ads_point p;

    CPY_PNT(p, p1);

    p2[X] = m[X][X] * p[X] + m[Y][X] * p[Y] + m[Z][X] * p[Z] + m[3][X];
    p2[Y] = m[X][Y] * p[X] + m[Y][Y] * p[Y] + m[Z][Y] * p[Z] + m[3][Y];
    p2[Z] = m[X][Z] * p[X] + m[Y][Z] * p[Y] + m[Z][Z] * p[Z] + m[3][Z];

} /*sa_transform_pt*/


/****************************************************************************/
/*.doc sa_normalize(external) */
/*+
  Normalizes given vector v. Returns FALSE is the vector is nearly zero.
-*/
/****************************************************************************/


int
/*FCN*/sa_normalize(v)

  ads_point v;
{
    double len;

    len = LENGTH(v);

    if (len < EPS)
        return(FALSE);

    v[X] /= len;
    v[Y] /= len;
    v[Z] /= len;

    return(TRUE);
} /*sa_normalize*/


/****************************************************************************/
/*.doc sa_make_arb_matrix(external) */
/*+
  Makes arbitrary matrix 'm' for direction of z-axis 'zaxis'.

  Function returns one of the standard ADS result codes.
-*/
/****************************************************************************/


int
/*FCN*/sa_make_arb_matrix(zaxis, m)

  ads_point zaxis;                    /* Given direction of z-axis */
  ads_point m[4];                     /* Returned arbitrary matrix */
{
    static ads_point unitmat[3] = {{1.0, 0.0, 0.0},
                                   {0.0, 1.0, 0.0},
                                   {0.0, 0.0, 1.0}};
    int ok;

    CPY_PNT(m[Z], zaxis);
    ok = sa_normalize(m[Z]);

    if (!ok)
        return(RTERROR);

    if ((fabs(m[Z][X]) < ARBBOUND) && (fabs(m[Z][Y]) < ARBBOUND))
        sa_cross(m[X], unitmat[Y], m[Z]);
    else
        sa_cross(m[X], unitmat[Z], m[Z]);

    sa_normalize(m[X]);

    sa_cross(m[Y], m[Z], m[X]);
    sa_normalize(m[Y]);

    m[3][X] = m[3][Y] = m[3][Z] = 0.0;  /* No translation */

    return(RTNORM);
} /*sa_make_arb_matrix*/


/****************************************************************************/
/*.doc sa_get_cs_of_picked_entity(external) */
/*+
  Function prompts to pick an entity (line, arc, circle, 2D pline segment)
  and returns its axis or plane.

  axis_required: TRUE  => returns axis in two points origin and zaxis.
                 FALSE => returns plane in three points origin, xaxis and yaxis

  The plane may also be considered a coordinates system:

  origin ... Point at the origin.
  xaxis  ... Point on the positive portion of the X-axis.
  yaxis  ... Point in the halfplane of the positive Y-axis.

  The axis or plane is obtained from the entity picked as follows:

  Axis:
  ----
  LINE        ... The endpoints of the line.
  ARC, CIRCLE ... The axis of the arc or circle (perpendicular to the plane
                  of the arc).
  PLINE       ... Handled either as an arc or as a line depending on the 
                  type of the picked pline segment.
  
  Plane:
  -----
  ARC, CIRCLE ... The plane of the arc or circle, origin at the center.
  PLINE       ... arc segment      -> handled as an arc,
                  straight segment -> plane of the polyline, origin at the 
                                      endpoint closer to the picked point.

  The points xaxis and yaxix are calculated based on the arbitrary
  axis algorithm.
  
  All returned points are expressed in the current UCS. Picking nested 
  entities is properly handled and the returned axis/plane is the axis/plane
  of the nexted entity itself, not of the enclosing block.
    
  Function returns one of the standard ADS result codes.
-*/
/****************************************************************************/


int
/*FCN*/sa_get_cs_of_picked_entity(prompt, axis_required, 
                                  origin, xaxis, yaxis, zaxis)

  char      *prompt;                      /* Prompt (optional)        */
  int       axis_required;                /* TRUE==Axis, FALSE==Plane */
  ads_point origin, xaxis, yaxis, zaxis;  /* Returned points          */
{
    int               success;
    ads_name          ent_name;
    struct resbuf     *ent_rb;
    ads_point         m[4];
    struct resbuf     *blocks;
    struct resbuf     *rb;
    ads_point         pick_pt;
    int               ent_type;
    int               nested_entity;
    ads_point         normal;

    success = RTNORM;
    ent_rb = blocks = NULL;

    normal[X] = normal[Y] = normal[Z] = 0.0;

    /* Pick an entity */

Pick:

    if (prompt != NULL) {
        success = ads_nentsel(prompt, ent_name, pick_pt, m, &blocks);
    } else if (axis_required) {
        success = ads_nentsel(XMSG("\n\
Pick a line, circle, arc or 2D-polyline segment:\n", 1),
                      ent_name, pick_pt, m, &blocks);
    } else {
        success = ads_nentsel(XMSG("\n\
Pick a circle, arc or 2D-polyline segment:\n", 2),
                      ent_name, pick_pt, m, &blocks);
    }

    if ((success == RTCAN) || (success == RTREJ))
        return(success);

    if (success != RTNORM) {
        ads_printf(XMSG("\nNo entity picked.\n\n", 3));
        goto Pick;
    }

    /* Set nested_entity variable, erase list of enclosing blocks */

    nested_entity = (blocks != NULL);
    if (nested_entity)
        ads_relrb(blocks);

    /* Get the list of result buffers for the picked entity */

    ent_rb = ads_entget(ent_name);
    if (ent_rb == NULL) {
        success = RTERROR;
        goto Cleanup;
    }

    rb = ent_rb->rbnext;
    if (rb->restype != 0) {
        success = RTERROR;
        goto Cleanup;
    }

    /* Find out which type of entity has been picked */

    if (strcmp(rb->resval.rstring, /*MSG0*/"LINE")   == 0)
        ent_type = 1;
    else
    if (strcmp(rb->resval.rstring, /*MSG0*/"CIRCLE") == 0)
        ent_type = 2;
    else
    if (strcmp(rb->resval.rstring, /*MSG0*/"ARC")    == 0)
        ent_type = 3;
    else
    if (strcmp(rb->resval.rstring, /*MSG0*/"VERTEX") == 0)
        ent_type = 4;
    else {

Improper_entity:

        ads_printf(XMSG("\nImproper type of entity picked.\n\n", 4));
        ads_relrb(ent_rb);
        goto Pick;
    }

    if ((ent_type == 1) && (!axis_required))
        goto Improper_entity;

    /* Get points origin, xaxis, yaxis, zaxis from the picked entity */

    if (ent_type == 1 /*line*/) {

        while (rb != NULL) {
            if (rb->restype == 10) 
                CPY_PNT(origin,rb->resval.rpoint);
            else if (rb->restype == 11)
                CPY_PNT(zaxis,rb->resval.rpoint);
            rb = rb->rbnext;
        } /*while*/

        CPY_PNT(xaxis, origin);
        CPY_PNT(yaxis, origin);

    } else if ((ent_type == 2 /*circle*/) || (ent_type == 3 /*arc*/)) {
        
        while (rb != NULL) {
            if (rb->restype == 10)
                CPY_PNT(origin, rb->resval.rpoint);
            else if (rb->restype == 210)
                CPY_PNT(normal, rb->resval.rpoint);
            rb = rb->rbnext;
        } /*while*/

        CPY_PNT(xaxis, origin); xaxis[X] += 1.0;
        CPY_PNT(yaxis, origin); yaxis[Y] += 1.0;
        CPY_PNT(zaxis, origin); zaxis[Z] += 1.0;

    } else {

        /* Polyline */

        ads_point p1, p2;
        double    radius;

        success = sa_get_pline_segment_data(ent_name, ent_rb, p1, p2, 
                                            origin, &radius, normal);
        if (success == RTREJ) {
            ads_printf(XMSG("\n2D polyline required.\n\n", 5));
            ads_relrb(ent_rb);
            goto Pick;
        } else if (success != RTNORM) {
            success = RTERROR;
            goto Cleanup;
        }
        
        /* Exchange 'p1' and 'p2' to make 'p1' visually closer to 
           the picked point 'pick_pt' */

        {
            ads_point pd1,pd2;

            sa_e2u(p1, ent_name, nested_entity, m, normal, pd1);
            sa_e2u(p2, ent_name, nested_entity, m, normal, pd2);

            if (sa_visual_distance(pd1, pick_pt) > 
                sa_visual_distance(pd2, pick_pt)) {
                ads_point p;
                CPY_PNT(p,  p1);
                CPY_PNT(p1, p2);
                CPY_PNT(p2, p );
            }
        }

        if (radius == 0.0) /*straight segment*/ {

            if (axis_required) {
                CPY_PNT(origin,p1);
                CPY_PNT(zaxis, p2);
                CPY_PNT(xaxis, origin);
                CPY_PNT(yaxis, origin);
            } else /*plane required*/ {
                CPY_PNT(origin,p1    );
                CPY_PNT(xaxis, origin);  xaxis[X] += 1.0;
                CPY_PNT(yaxis, origin);  yaxis[Y] += 1.0;
                CPY_PNT(zaxis, origin);  zaxis[Z] += 1.0;
            }
        } else /*arc segment*/ {
            CPY_PNT(xaxis, origin);  xaxis[X] += 1.0;
            CPY_PNT(yaxis, origin);  yaxis[Y] += 1.0;
            CPY_PNT(zaxis, origin);  zaxis[Z] += 1.0;
        }
    }                                 /*else pline*/


    if (DISTANCE(origin, zaxis) < EPS) {
        ads_printf(XMSG("\nDegenerated entity picked.\n\n", 6));
        ads_relrb(ent_rb);
        goto Pick;
    }

    /* Transform 'origin', 'xaxis', 'yaxis', 'zaxis' from ECS to UCS */

    sa_e2u(origin, ent_name, nested_entity, m, normal, origin);
    sa_e2u(xaxis,  ent_name, nested_entity, m, normal, xaxis );
    sa_e2u(yaxis,  ent_name, nested_entity, m, normal, yaxis );
    sa_e2u(zaxis,  ent_name, nested_entity, m, normal, zaxis );

    success = RTNORM;

Cleanup:

   /* Erase the list of result buffers */

    if (ent_rb != NULL)
        ads_relrb(ent_rb);

    return(success);
} /*sa_get_cs_of_picked_entity*/


/****************************************************************************/
/*.doc sa_snap(external) */
/*+
  Prompts to pick an entity and gives its snap point.

  Function returns one of the standard ADS result codes.
-*/
/****************************************************************************/


int
/*FCN*/sa_snap(prompt, mode, p)

  char      *prompt;                  /* Prompt (optional)       */
  char      *mode;                    /* "END", "MID", "CEN" etc.*/
  ads_point p;                        /* Returned snap point     */
{
    int      success;
    ads_name ent_name;

Pick:

    sa_make_pickbox_equal_to_aperture(TRUE);
    success = ads_entsel(prompt, ent_name, p);
    sa_make_pickbox_equal_to_aperture(FALSE);

    if ((success == RTCAN) || (success == RTREJ)) {
        return(success);
    }
    if (success != RTNORM) {
        ads_printf(XMSG("\nNo entity picked.\n\n", 7));
        goto Pick;
    }

    success = ads_osnap(p, mode, p);
    if (success != RTNORM) {
        ads_printf(XMSG("\nImproper type of entity picked.\n\n", 8));
        goto Pick;
    }

    return(RTNORM);
} /*sa_snap*/


/****************************************************************************/
/*.doc sa_uniform_scaling_factor(external) */
/*+
  Function extracts the scaling factor from matrix 'm'. If
  this matrix represents uniform scaling (the same scaling in
  all directions) then it returns this (positive) scaling factor. 
  Otherwise it returns value -1.0.
-*/
/****************************************************************************/


double
/*FCN*/sa_uniform_scaling_factor(m)

  ads_point  m[4];
{
    double sx,sy,sz,s;

    sx = sqrt(SQR(m[X][X]) + SQR(m[X][Y]) + SQR(m[X][Z]));
    sy = sqrt(SQR(m[Y][X]) + SQR(m[Y][Y]) + SQR(m[Y][Z]));
    sz = sqrt(SQR(m[Z][X]) + SQR(m[Z][Y]) + SQR(m[Z][Z]));
    s = (sx + sy + sz) / 3;

    if ((fabs(s) < EPS) || 
        (fabs(s-sx)/s + fabs(s-sy)/s + fabs(s-sy)/s > 1e-5))
        return(-1.0);
    else
        return(s);

} /*sa_uniform_scaling_factor*/


/****************************************************************************/
/*.doc sa_plane_equation(external) */
/*+
  Function calcuates the plane eqation from:

  - 3 points p1, p2, p3                    (pts3 == TRUE)
  - plane point p1 and point on normal p2  (pts3 == FALSE)

  The unit normal vector to the  plane is returned in 'n' and the 
  constant coefficient is returned in 'd', according to the implicit 
  plane equation:

            n[X]*x + n[Y]*y + n[Z]*z + d == 0.0

  Function returns one of the standard ADS result codes.
-*/
/****************************************************************************/


int
/*FCN*/sa_plane_equation(pts3, p1, p2, p3, n, d)

  int       pts3;             /* TRUE==3 points, FALSE==pt & normal pt */
  ads_point p1, p2, p3;
  ads_point n;                /* Returned unit normal vector           */
  double    *d;               /* Returned constant coefficient         */
{
    ads_point v1, v2, nn;
    double    dd;
    int       ok;

    if (pts3) /*3 points*/ {
        SUB_PNT(v1, p2, p1);
        SUB_PNT(v2, p3, p1);
        sa_cross(nn, v1, v2);
        ok = sa_normalize(nn);
        if (!ok)
            return(RTERROR);
        dd = -(DOTPROD(nn, p1) + DOTPROD(nn, p2) + DOTPROD(nn, p3)) / 3;
    } else /*2 points*/ {
        SUB_PNT(nn, p2, p1);
        ok = sa_normalize(nn);
        if (!ok)
            return(RTERROR);
        dd = -DOTPROD(nn, p1);
    }

    CPY_PNT(n, nn);
    *d = dd;
    return(RTNORM);
} /*sa_plane_equation*/


/****************************************************************************/
/*.doc sa_u2w(external) */
/*+
  Transforms a given point from UCS to WCS.

  Function returns one of the standard ADS result codes.
-*/
/****************************************************************************/


int
/*FCN*/sa_u2w(p1, p2)

  ads_point p1,p2;
{
    int           success;
    struct resbuf wcs,ucs;

    wcs.restype     = RTSHORT;
    wcs.resval.rint = 0;
    ucs.restype     = RTSHORT;
    ucs.resval.rint = 1;

    success = ads_trans(p1, &ucs, &wcs, 0, p2);

    return(success);
} /*sa_u2w*/


/****************************************************************************/
/*.doc sa_w2u(external) */
/*+
  Transforms a given point from WCS to UCS.

  Function returns one of the standard ADS result codes.
-*/
/****************************************************************************/


int
/*FCN*/sa_w2u(p1, p2)

  ads_point p1,p2;
{
    int           success;
    struct resbuf wcs,ucs;

    wcs.restype     = RTSHORT;
    wcs.resval.rint = 0;
    ucs.restype     = RTSHORT;
    ucs.resval.rint = 1;

    success = ads_trans(p1, &wcs, &ucs, 0, p2);

    return(success);
} /*sa_w2u*/


/****************************************************************************/
/*.doc sa_get_radius_of_picked_entity(external) */
/*+
  Returns radius of a picked entity (arc, circle or 2D polyline segment).
  Nested entities are properly handled, that is you may pick an entity
  nested inside a block.

  Function returns one of the standard ADS result codes.
-*/
/****************************************************************************/


int
/*FCN*/sa_get_radius_of_picked_entity(prompt, radius)

  char   *prompt;                     /* Prompt (optional) */
  double *radius;                     /* Returned radius   */
{
    int           success;
    int           entity_type;
    ads_name      ent_name;
    struct resbuf *ent_rb, *rb, *blocks;
    double        matrix[4][3];
    double        scale,rad;
    ads_point     pick_pt;
    ads_point     normal;

    /* Pick an entity */

Pick:
    success = ads_nentsel(prompt, ent_name, pick_pt, matrix, &blocks);

    if ((success == RTCAN) || (success == RTREJ)) {
        return(success);
    }
    if (success != RTNORM) {
        ads_printf(XMSG("\nNo entity picked.\n\n", 9));
        goto Pick;
    }

    if (blocks != NULL) {
        ads_relrb(blocks);

        if ((scale = sa_uniform_scaling_factor(matrix)) < 0) {
        ads_printf(XMSG("\nBlock was non-uniformly scaled, no radius exists.\n\n", 10));
            goto Pick;
        }
    } else {
        scale = 1.0;
    }

    /* Find out which type of entity has been picked */

    ent_rb = ads_entget(ent_name);
    if (ent_rb == NULL)
        return(RTERROR);

    rb = ent_rb->rbnext;
    if (rb->restype != 0) {
        ads_relrb(ent_rb);
        return(RTERROR);
    }

    if (strcmp(rb->resval.rstring, /*MSG0*/"CIRCLE") == 0)
        entity_type = 1;
    else if (strcmp(rb->resval.rstring, /*MSG0*/"ARC") == 0)
        entity_type = 2;
    else if (strcmp(rb->resval.rstring, /*MSG0*/"VERTEX") == 0)
        entity_type = 3;
    else {
        ads_relrb(ent_rb);
        ads_printf(XMSG("\nImproper type of entity picked.\n\n", 11));
        goto Pick;
    }

    /* Get the entity radius from the entity data */

    switch (entity_type) {

    case 1: /*circle*/
    case 2: /*arc*/
        while ((rb != NULL) && (rb->restype != 40))
            rb = rb->rbnext;
        if (rb == NULL) {
            ads_relrb(ent_rb);
            return(RTERROR);
        }

        rad = rb->resval.rreal;
        break;

    case 3: /*polyline*/
        {
            ads_point p1, p2, cen;

            success = sa_get_pline_segment_data(ent_name, ent_rb, p1, p2, 
                                                cen, &rad, normal);
            if (success == RTREJ) { /*Mesh, etc.*/ 
                ads_relrb(ent_rb);
                ads_printf(XMSG("\n2D polyline required.\n\n", 12));
                goto Pick;
            } else if (success != RTNORM) {
                ads_relrb(ent_rb);
                return(RTERROR);
            }
            if (rad == 0.0) {
                ads_relrb(ent_rb);
                ads_printf(XMSG("\nPolyline arc segment must be picked.\n\n", 13));
                goto Pick;
            }
        }
        break;
    } /*switch*/

    *radius = scale * rad;
    return(RTNORM);

} /*sa_get_radius_of_picked_entity*/


/****************************************************************************/
/*.doc sa_visual_distance(external) */
/*+
  Returns visual distance (in display coordinate system) of two given 
  points p1 and p2, expressed in current UCS.
-*/
/****************************************************************************/


double
/*FCN*/sa_visual_distance(p1, p2)

  ads_point p1,p2;
{
    ads_point     pd1, pd2;
    struct resbuf ucs, dcs;
    double        dist;

    ucs.restype     = RTSHORT;
    ucs.resval.rint = 1;
    dcs.restype     = RTSHORT;
    dcs.resval.rint = 2;

    ads_trans(p1, &ucs, &dcs, FALSE, pd1);
    ads_trans(p2, &ucs, &dcs, FALSE, pd2);
    pd1[Z] = pd2[Z] = 0.0;
    dist = DISTANCE(pd1, pd2);

    return(dist);
} /*sa_visual_distance*/


/****************************************************************************/
/*.doc sa_e2u(external) */
/*+
  Transform point from ECS to UCS (supports nested entities).

  Function returns one of the standard ADS result codes.
-*/
/****************************************************************************/


static int
/*FCN*/sa_e2u(p1, ent_name, nested, m, normal, p2)

  ads_point p1;       /* Point in ECS to be transformed                     */
  ads_name  ent_name; /* Entity name                                        */
  int       nested;   /* Is entity nested ?                                 */
  ads_point m[4];     /* Matrix from ads_nentsel()                          */
  ads_point normal;   /* Entity normal (for arc, circle), (0,0,0) otherwise */
  ads_point p2;       /* Resulting point in UCS                             */
{
    ads_point     arbm[4];
    struct resbuf ecs, ucs;
    int           success;
    ads_point     p;

    ecs.restype          = RTENAME;
    ecs.resval.rlname[0] = ent_name[0];
    ecs.resval.rlname[1] = ent_name[1];

    ucs.restype     = RTSHORT;
    ucs.resval.rint = 1;

    arbm[X][X] = 1.0; arbm[X][Y] = 0.0; arbm[X][Z] = 0.0;
    arbm[Y][X] = 0.0; arbm[Y][Y] = 1.0; arbm[Y][Z] = 0.0;
    arbm[Z][X] = 0.0; arbm[Z][Y] = 0.0; arbm[Z][Z] = 1.0;
    arbm[3][X] = 0.0; arbm[3][Y] = 0.0; arbm[3][Z] = 0.0;

    /* If normal is provided (for arc & circle), make arbitrary matrix */

    if (nested && (normal != NULL) && (LENGTH(normal) > EPS)) {
        sa_make_arb_matrix(normal, arbm);
    }

    if (!nested) {
        success = ads_trans(p1, &ecs, &ucs, 0, p);
    } else {
        /*ECS->MCS*/

        sa_transform_pt(arbm, p1, p);

        /*MCS->WCS*/

        sa_transform_pt(m, p, p);

        /*WCS->UCS*/

        success = sa_w2u(p, p);
    }
    CPY_PNT(p2, p);

    return(success);
} /*sa_e2u*/


/****************************************************************************/
/*.doc sa_normalize_angle(external) */
/*+
  Normalizes angle (in radians) to interval <0;2*PI).
-*/
/****************************************************************************/


void
/*FCN*/sa_normalize_angle(angle)

  double *angle;
{
    *angle = fmod(*angle, 2*PI);
    if (*angle < 0.0)
        *angle += 2*PI;
} /*sa_normalize_angle*/


