/*
** Astrolog (Version 3.05) File: xcharts.c
** Initially programmed 10/23-29/1991
**
** IMPORTANT: The planetary calculation routines used in this program
** have been Copyrighted and the core of this program is basically a
** conversion to C of the routines created by James Neely as listed in
** Michael Erlewine's 'Manual of Computer Programming for Astrologers',
** available from Matrix Software. The copyright gives us permission to
** use the routines for our own purposes but not to sell them or profit
** from them in any way.
**
** IMPORTANT: the graphics database and chart display routines used in
** this program are Copyright (C) 1991-1993 by Walter D. Pullen. Permission
** is granted to freely use and distribute these routines provided one
** doesn't sell, restrict, or profit from them in any way. Modification
** is allowed provided these notices remain with any altered or edited
** versions of the program.
*/

#include "astrolog.h"

#ifdef GRAPH

/* Are particular coordinates on the chart? */
#define ISLEGAL(X, Y) \
  ((X) >= 0 && (X) < chartx && (Y) >= 0 && (Y) < chartx)

/* draw a rectangle with thin sides. */
#define DrawEdge(X1, Y1, X2, Y2, O) \
  DrawBox(X1, Y1, X2, Y2, 1, 1, O)

/* draw a border around the whole window. */
#define DrawBoxAll(XSIZ, YSIZ, O) \
  DrawBox(0, 0, chartx-1, charty-1, XSIZ, YSIZ, O)

real symbol[TOTAL+1];
circlestruct PTR circ = NULL;


/*
*******************************************************************************
** Main subprograms
*******************************************************************************
*/

/* Return whether the specified object should be displayed in the current  */
/* graphics chart type. For example, don't include the Moon in the solar   */
/* system charts, don't include house cusps in astro-graph, and so on.     */

int Proper(i)
int i;
{
  int j;

  if (modex == 'l')                            /* astro-graph charts */
    j = (i <= THINGS || i > C_HI);
  else if (modex == 'z' || modex == 'g')       /* horizon or zenith charts */
    j = (i < THINGS || i > C_HI);
  else if (modex == 's')                       /* solar system charts */
    j = (i != 2 && (i < THINGS || i > C_HI));
  else
    j = TRUE;
  return j && !ignore[i];                      /* check restriction status */
}


/* Adjust an array of zodiac positions so that no two positions are within   */
/* a certain orb of each other. This is used by the wheel drawing chart      */
/* routines in order to make sure that we don't draw any planet glyphs on    */
/* top of each other. We'll later draw the glyphs at the adjusted positions. */

void FillSymbolRing(symbol)
real *symbol;
{
  int i, j, k = 1, l, k1, k2;
  real orb = DEFORB*256.0/(real)charty*(real)SCALE, temp;

  /* Keep adjusting as long as we can still make changes, or until we do 'n' */
  /* rounds. (With many objects, there just may not be enough room for all.) */

  for (l = 0; k && l < divisions*4; l++) {
    k = 0;
    for (i = 1; i <= total; i++) if (Proper(i)) {

      /* For each object, determine who is closest on either side. */

      k1 = 1000; k2 = -1000;
      for (j = 1; j <= total; j++)
        if (Proper(j) && i != j) {
          temp = symbol[j]-symbol[i];
          if (dabs(temp) > 180.0)
            temp -= DEGREES*Sgn(temp);
          if (temp<k1 && temp>=0.0)
            k1 = temp;
          else if (temp>k2 && temp<=0.0)
            k2 = temp;
        }

      /* If an object's too close on one side, then we move to the other. */

      if (k2>-orb && k1>orb) {
        k = 1; symbol[i] = Mod(symbol[i]+orb*0.51+k2*0.49);
      } else if (k1<orb && k2<-orb) {
        k = 1; symbol[i] = Mod(symbol[i]-orb*0.51+k1*0.49);

      /* If we are bracketed by close objects on both sides, then let's move */
      /* to the midpoint, so we are as far away as possible from either one. */

      } else if (k2>-orb && k1<orb) {
        k = 1; symbol[i] = Mod(symbol[i]+(k1+k2)*0.5);
      }
    }
  }
}


/* Set up arrays with the sine and cosine values of each degree. This is   */
/* used by the wheel chart routines which draw lots of circles. Memory is  */
/* allocated for this array if not already done. The allocation and        */
/* initialization is only done once, the first time the routine is called. */

void InitCircle()
{
  int i;

  if (circ != NULL)
    return;
  Allocate(circ, sizeof(circlestruct), circlestruct PTR);
  if (circ == NULL
#ifndef NOPC
    /* For PC's the array better not cross a segment boundary. */
    || HIWORD(LOWORD(circ) + sizeof(circlestruct)) > 0
#endif
    ) {
    fprintf(stderr, "%s: Out of memory for sine table.\n", appname);
    Terminate(1);
  }
  for (i = 0; i < 360; i++) {
    circ->x[i] = COSD((real) i);
    circ->y[i] = SIND((real) i);
  }
  circ->x[360] = circ->x[0]; circ->y[360] = circ->y[0];
}


/* Get a coordinate based on chart radius, a fraction, and (co)sin value. */
#define POINT(U, R, S) ((int) ((R)*(real)(U)*(S)+0.5))

/* Determine (co)sin factor based on zodiac angle and chart orientation. */
#define PX(A) COSD(180.0-(A)+Asc)
#define PY(A) SIND(180.0-(A)+Asc)

/* Draw a wheel chart, in which the 12 signs and houses are delineated, and  */
/* the planets are inserted in their proper places. This is the default      */
/* graphics chart to generate, as is done when the -v or -w (or no) switches */
/* are included with -X. Draw the aspects in the middle of chart, too.       */

void XChartWheel()
{
  int cx = chartx / 2, cy = charty / 2, unitx, unity, i, j, count = 0;
  real Asc, orb = DEFORB*256.0/(real)charty*(real)SCALE, temp;

  DrawBoxAll(1, 1, hilite);
  unitx = cx; unity = cy;
  Asc = xeast ? planet[abs(xeast)]+90*(xeast < 0) : house[1];
  InitCircle();

  /* Draw Ascendant/Descendant and Midheaven/Nadir lines across whole chart. */

  DrawLine(cx+POINT(unitx, 0.99, PX(house[1])),
           cy+POINT(unity, 0.99, PY(house[1])),
           cx+POINT(unitx, 0.99, PX(house[7])),
           cy+POINT(unity, 0.99, PY(house[7])), hilite, !xcolor);
  DrawLine(cx+POINT(unitx, 0.99, PX(house[10])),
           cy+POINT(unity, 0.99, PY(house[10])),
           cx+POINT(unitx, 0.99, PX(house[4])),
           cy+POINT(unity, 0.99, PY(house[4])), hilite, !xcolor);

  /* Draw small five or one degree increments around the zodiac sign ring. */

  for (i = 0; i < 360; i += 5-xcolor*4) {
    temp = (real) i;
    DrawLine(cx+POINT(unitx, 0.80, PX(temp)), cy+POINT(unity, 0.80, PY(temp)),
      cx+POINT(unitx, 0.75, PX(temp)), cy+POINT(unity, 0.75, PY(temp)),
      i%5 ? gray : on, 0);
  }

  /* Draw circles for the zodiac sign and house rings. */

  for (i = 0; i < 360; i++) {
    DrawLine(cx+POINT(unitx, 0.95, circ->x[i]),
      cy+POINT(unity, 0.95, circ->y[i]), cx+POINT(unitx, 0.95, circ->x[i+1]),
      cy+POINT(unity, 0.95, circ->y[i+1]), on, 0);
    DrawLine(cx+POINT(unitx, 0.80, circ->x[i]),
      cy+POINT(unity, 0.80, circ->y[i]), cx+POINT(unitx, 0.80, circ->x[i+1]),
      cy+POINT(unity, 0.80, circ->y[i+1]), on, 0);
    DrawLine(cx+POINT(unitx, 0.75, circ->x[i]),
      cy+POINT(unity, 0.75, circ->y[i]), cx+POINT(unitx, 0.75, circ->x[i+1]),
      cy+POINT(unity, 0.75, circ->y[i+1]), on, 0);
    DrawLine(cx+POINT(unitx, 0.65, circ->x[i]),
      cy+POINT(unity, 0.65, circ->y[i]), cx+POINT(unitx, 0.65, circ->x[i+1]),
      cy+POINT(unity, 0.65, circ->y[i+1]), on, 0);
  }

  /* Draw the glyphs for the signs and houses themselves. */

  for (i = 1; i <= SIGNS; i++) {
    temp = (real) (i-1)*30.0;
    DrawLine(cx+POINT(unitx, 0.95, PX(temp)),       /* Draw lines separating */
      cy+POINT(unity, 0.95, PY(temp)),              /* each sign and house   */
      cx+POINT(unitx, 0.80, PX(temp)),              /* from each other.      */
      cy+POINT(unity, 0.80, PY(temp)), on, 0);
    DrawLine(cx+POINT(unitx, 0.75, PX(house[i])),
      cy+POINT(unity, 0.75, PY(house[i])),
      cx+POINT(unitx, 0.65, PX(house[i])),
      cy+POINT(unity, 0.65, PY(house[i])), on, 0);
    if (xcolor && i%3 != 1)                                    /* Lines from */
      DrawLine(cx, cy, cx+POINT(unitx, 0.65, PX(house[i])),    /* each house */
        cy+POINT(unity, 0.65, PY(house[i])), gray, 1);         /* to center. */
    temp += 15.0;
    DrawTurtle(signdraw[i], cx+POINT(unitx, 0.875, PX(temp)),
      cy+POINT(unity, 0.875, PY(temp)), elemcolor[(i-1)%4]);
    temp = Midpoint(house[i], house[Mod12(i+1)]);
    DrawTurtle(housedraw[i], cx+POINT(unitx, 0.70, PX(temp)),
      cy+POINT(unity, 0.70, PY(temp)), elemcolor[(i-1)%4]);
  }
  for (i = 1; i <= total; i++) {   /* Figure out where to put planet glyphs. */
    symbol[i] = planet[i];
  }
  FillSymbolRing(symbol);

  /* For each planet, draw a small dot indicating where it is, and then */
  /* a line from that point to the planet's glyph.                      */

  for (i = 1; i <= total; i++) if (Proper(i)) {
    temp = symbol[i];
    DrawLine(cx+POINT(unitx, 0.52, PX(planet[i])),
      cy+POINT(unity, 0.52, PY(planet[i])),
      cx+POINT(unitx, 0.56, PX(temp)),
      cy+POINT(unity, 0.56, PY(temp)),
      ret[i] < 0.0 ? gray : on, (ret[i] < 0.0 ? 1 : 0) - xcolor);
    DrawObject(i, cx+POINT(unitx, 0.60, PX(temp)),
      cy+POINT(unity, 0.60, PY(temp)));
    DrawPoint(cx+POINT(unitx, 0.50, PX(planet[i])),
      cy+POINT(unity, 0.50, PY(planet[i])), objectcolor[i]);
  }
  if (bonus)    /* Don't draw aspects in bonus mode. */
    return;

  /* Draw lines connecting planets which have aspects between them. */

  CreateGrid(FALSE);
  for (j = total; j >= 2; j--)
    for (i = j-1; i >= 1; i--)
      if (grid->n[i][j] && Proper(i) && Proper(j))
        DrawLine(cx+POINT(unitx, 0.48, PX(planet[i])),
          cy+POINT(unity, 0.48, PY(planet[i])),
          cx+POINT(unitx, 0.48, PX(planet[j])),
          cy+POINT(unity, 0.48, PY(planet[j])),
          aspectcolor[grid->n[i][j]], abs(grid->v[i][j]/60/2));
}


/* Draw another wheel chart; however, this time we have two rings of planets */
/* because we are doing a relationship chart between two sets of data. This  */
/* chart is obtained when the -r0 is combined with the -X switch.            */

void XChartWheelRelation()
{
  int cx = chartx / 2, cy = charty / 2, unitx, unity, i, j;
  real Asc, temp;

  DrawBoxAll(1, 1, hilite);
  unitx = cx; unity = cy;
  Asc = xeast ? planet1[abs(xeast)]+90*(xeast < 0) : house1[1];
  InitCircle();

  /* Draw the horizon and meridian lines across whole chart, and draw the */
  /* zodiac and house rings, exactly like before. We are drawing only the */
  /* houses of one of the two charts in the relationship, however.        */

  DrawLine(cx+POINT(unitx, 0.99, PX(house1[1])),
           cy+POINT(unity, 0.99, PY(house1[1])),
           cx+POINT(unitx, 0.99, PX(house1[7])),
           cy+POINT(unity, 0.99, PY(house1[7])), hilite, !xcolor);
  DrawLine(cx+POINT(unitx, 0.99, PX(house1[10])),
           cy+POINT(unity, 0.99, PY(house1[10])),
           cx+POINT(unitx, 0.99, PX(house1[4])),
           cy+POINT(unity, 0.99, PY(house1[4])), hilite, !xcolor);
  for (i = 0; i < 360; i += 5-xcolor*4) {
    temp = (real) i;
    DrawLine(cx+POINT(unitx, 0.82, PX(temp)), cy+POINT(unity, 0.82, PY(temp)),
      cx+POINT(unitx, 0.78, PX(temp)), cy+POINT(unity, 0.78, PY(temp)),
      i%5 ? gray : on, 0);
  }
  for (i = 0; i < 360; i++) {
    DrawLine(cx+POINT(unitx, 0.95, circ->x[i]),
      cy+POINT(unity, 0.95, circ->y[i]), cx+POINT(unitx, 0.95, circ->x[i+1]),
      cy+POINT(unity, 0.95, circ->y[i+1]), on, 0);
    DrawLine(cx+POINT(unitx, 0.82, circ->x[i]),
      cy+POINT(unity, 0.82, circ->y[i]), cx+POINT(unitx, 0.82, circ->x[i+1]),
      cy+POINT(unity, 0.82, circ->y[i+1]), on, 0);
    DrawLine(cx+POINT(unitx, 0.78, circ->x[i]),
      cy+POINT(unity, 0.78, circ->y[i]), cx+POINT(unitx, 0.78, circ->x[i+1]),
      cy+POINT(unity, 0.78, circ->y[i+1]), on, 0);
    DrawLine(cx+POINT(unitx, 0.70, circ->x[i]),
      cy+POINT(unity, 0.70, circ->y[i]), cx+POINT(unitx, 0.70, circ->x[i+1]),
      cy+POINT(unity, 0.70, circ->y[i+1]), on, 0);
  }
  for (i = 1; i <= SIGNS; i++) {
    temp = (real) (i-1)*30.0;
    DrawLine(cx+POINT(unitx, 0.95, PX(temp)),
      cy+POINT(unity, 0.95, PY(temp)),
      cx+POINT(unitx, 0.82, PX(temp)),
      cy+POINT(unity, 0.82, PY(temp)), on, 0);
    DrawLine(cx+POINT(unitx, 0.78, PX(house1[i])),
      cy+POINT(unity, 0.78, PY(house1[i])),
      cx+POINT(unitx, 0.70, PX(house1[i])),
      cy+POINT(unity, 0.70, PY(house1[i])), on, 0);
    if (xcolor && i%3 != 1)
      DrawLine(cx, cy, cx+POINT(unitx, 0.70, PX(house1[i])),
        cy+POINT(unity, 0.70, PY(house1[i])), gray, 1);
    temp += 15.0;
    DrawTurtle(signdraw[i], cx+POINT(unitx, 0.885, PX(temp)),
      cy+POINT(unity, 0.885, PY(temp)), elemcolor[(i-1)%4]);
    temp = Midpoint(house1[i], house1[Mod12(i+1)]);
    DrawTurtle(housedraw[i], cx+POINT(unitx, 0.74, PX(temp)),
      cy+POINT(unity, 0.74, PY(temp)), elemcolor[(i-1)%4]);
  }

  /* Draw the outer ring of planets (based on the planets in the chart     */
  /* which the houses do not reflect - the houses belong to the inner ring */
  /* below). Draw each glyph, a line from it to its actual position point  */
  /* in the outer ring, and then draw another line from this point to a    */
  /* another dot at the same position in the inner ring as well.           */

  for (i = 1; i <= total; i++) {
    symbol[i] = planet2[i];
  }
  FillSymbolRing(symbol);
  for (i = 1; i <= total; i++) if (Proper(i)) {
    temp = symbol[i];
    DrawLine(cx+POINT(unitx, 0.58, PX(planet2[i])),
      cy+POINT(unity, 0.58, PY(planet2[i])),
      cx+POINT(unitx, 0.61, PX(temp)),
      cy+POINT(unity, 0.61, PY(temp)),
      ret2[i] < 0.0 ? gray : on, (ret2[i] < 0.0 ? 1 : 0) - xcolor);
    DrawObject(i, cx+POINT(unitx, 0.65, PX(temp)),
      cy+POINT(unity, 0.65, PY(temp)));
    DrawPoint(cx+POINT(unitx, 0.56, PX(planet2[i])),
      cy+POINT(unity, 0.56, PY(planet2[i])), objectcolor[i]);
    DrawPoint(cx+POINT(unitx, 0.43, PX(planet2[i])),
      cy+POINT(unity, 0.43, PY(planet2[i])), objectcolor[i]);
    DrawLine(cx+POINT(unitx, 0.45, PX(planet2[i])),
      cy+POINT(unity, 0.45, PY(planet2[i])),
      cx+POINT(unitx, 0.54, PX(planet2[i])),
      cy+POINT(unity, 0.54, PY(planet2[i])),
      ret2[i] < 0.0 ? gray : on, 2-xcolor);
  }

  /* Now draw the inner ring of planets. If it weren't for the outer ring,  */
  /* this would be just like the standard non-relationship wheel chart with */
  /* only one set of planets. Again, draw glyph, and a line to true point.  */

  for (i = 1; i <= total; i++) {
    symbol[i] = planet1[i];
  }
  FillSymbolRing(symbol);
  for (i = 1; i <= total; i++) if (Proper(i)) {
    temp = symbol[i];
    DrawLine(cx+POINT(unitx, 0.45, PX(planet1[i])),
      cy+POINT(unity, 0.45, PY(planet1[i])),
      cx+POINT(unitx, 0.48, PX(temp)),
      cy+POINT(unity, 0.48, PY(temp)),
      ret1[i] < 0.0 ? gray : on, (ret1[i] < 0.0 ? 1 : 0) - xcolor);
    DrawObject(i, cx+POINT(unitx, 0.52, PX(temp)),
      cy+POINT(unity, 0.52, PY(temp)));
    DrawPoint(cx+POINT(unitx, 0.43, PX(planet1[i])),
      cy+POINT(unity, 0.43, PY(planet1[i])), objectcolor[i]);
  }
  if (bonus)    /* Don't draw aspects in bonus mode. */
    return;

  /* Draw lines connecting planets between the two charts that have aspects. */

  CreateGridRelation(FALSE);
  for (j = total; j >= 1; j--)
    for (i = total; i >= 1; i--)
      if (grid->n[i][j] && Proper(i) && Proper(j))
        DrawLine(cx+POINT(unitx, 0.41, PX(planet1[j])),
          cy+POINT(unity, 0.41, PY(planet1[j])),
          cx+POINT(unitx, 0.41, PX(planet2[i])),
          cy+POINT(unity, 0.41, PY(planet2[i])),
          aspectcolor[grid->n[i][j]], abs(grid->v[i][j]/60/2));
}


/* Given longitude and latitude values on a globe, return the window        */
/* coordinates corresponding to them. In other words, project the globe     */
/* onto the view plane, and return where our coordinates got projected to,  */
/* as well as whether our location is hidden on the back side of the globe. */

int GlobeCalc(x1, y1, u, v, cx, cy, rx, ry, deg)
real x1, y1;
int *u, *v, cx, cy, rx, ry, deg;
{
  real j;

  /* Compute coordinates for a general globe invoked with -XG switch. */

  if (modex == 'g') {
    x1 = Mod(x1+(real)deg);    /* Shift by current globe rotation value. */
    if (tilt != 0.0) {
      x1 = DTOR(x1); y1 = DTOR(90.0-y1);         /* Do another coordinate  */
      CoorXform(&x1, &y1, tilt / DEGTORAD);      /* shift if the globe's   */
      x1 = Mod(RTOD(x1)); y1 = 90.0-RTOD(y1);    /* equator is tilted any. */
    }
    *v = cy + (int) ((real)(ry+1)*-COSD(y1));
    *u = cx + (int) ((real)(rx+1)*-COSD(x1)*SIND(y1));
    return x1 > 180.0;
  }

  /* Compute coordinates for a polar globe invoked with -XP switch. */

  j = bonus ? 90.0+x1+deg : 270.0-x1-deg;
  *v = cy + (int) (SIND(y1)*(real)(ry+1)*SIND(j));
  *u = cx + (int) (SIND(y1)*(real)(rx+1)*COSD(j));
  return bonus ? y1 < 90.0 : y1 > 90.0;
}


/* Draw a globe in the window, based on the specified rotational and tilt  */
/* values. In addition, we may draw in each planet at its zenith position. */

void DrawGlobe(deg)
int deg;
{
  char *nam, *loc, *lin, d;
  int X[TOTAL+1], Y[TOTAL+1], M[TOTAL+1], N[TOTAL+1],
    cx = chartx / 2, cy = charty / 2, rx, ry, lon, lat, unit = 12*SCALE,
    x, y, m, n, u, v, i, J, k, l;
  real planet1[TOTAL+1], planet2[TOTAL+1], x1, y1, j;
  color c;

  rx = cx-1; ry = cy-1;

  /* Loop through each coastline string, drawing visible parts on the globe. */

  while (ReadWorldData(&nam, &loc, &lin)) {
    i = nam[0]-'0';
    c = (modex == 'g' && bonus) ? gray : (i ? rainbowcolor[i] : maincolor[6]);

    /* Get starting longitude and latitude of current coastline piece. */

    lon = (loc[0] == '+' ? 1 : -1)*
      ((loc[1]-'0')*100 + (loc[2]-'0')*10 + (loc[3]-'0'));
    lat = (loc[4] == '+' ? 1 : -1)*((loc[5]-'0')*10 + (loc[6]-'0'));
    x = 180-lon;
    y = 90-lat;
    GlobeCalc((real) x, (real) y, &m, &n, cx, cy, rx, ry, deg);

    /* Go down the coastline piece, drawing each visible segment on globe. */

    for (i = 0; d = lin[i]; i++) {
      if (d == 'L' || d == 'H' || d == 'G')
        x--;
      else if (d == 'R' || d == 'E' || d == 'F')
        x++;
      if (d == 'U' || d == 'H' || d == 'E')
        y--;
      else if (d == 'D' || d == 'G' || d == 'F')
        y++;
      if (x > 359)
        x = 0;
      else if (x < 0)
        x = 359;
      if (!GlobeCalc((real) x, (real) y, &u, &v, cx, cy, rx, ry, deg))
        DrawLine(m, n, u, v, c, 0);
      m = u; n = v;
    }
  }

  /* Finally, draw the circular outline of the globe itself. */

  m = cx+rx+1; n = cy;
  for (i = 0; i <= 360; i++) {
    u = cx+(rx+1)*COSD((real)i); v = cy+(ry+1)*SIND((real)i);
    u = MIN(u, cx+rx); v = MIN(v, cy+ry);
    DrawLine(m, n, u, v, on, 0); m = u; n = v;
  }

  /* Now, only if we are in bonus chart mode, draw each planet at its */
  /* zenith location on the globe, assuming that location is visible. */

  if (modex != 'g' || !bonus)
    return;
  j = Lon;
  if (j < 0.0)
    j += DEGREES;
  for (i = 1; i <= total; i++) {
    planet1[i] = DTOR(planet[i]);
    planet2[i] = DTOR(planetalt[i]);
    ecltoequ(&planet1[i], &planet2[i]);    /* Calculate zenith long. & lat. */
  }
  for (i = 1; i <= total; i++) if (Proper(i)) {
    x1 = planet1[18]-planet1[i];
    if (x1 < 0.0)
      x1 += 2.0*PI;
    if (x1 > PI)
      x1 -= 2.0*PI;
    x1 = Mod(180.0-j-RTOD(x1));
    y1 = 90.0-RTOD(planet2[i]);
    X[i] = GlobeCalc(x1, y1, &u, &v, cx, cy, rx, ry, deg) ? -1000 : u;
    Y[i] = v; M[i] = X[i]; N[i] = Y[i]+unit/2;
  }

  /* Now that we have the coordinates of each object, figure out where to    */
  /* draw the glyphs. Again, we try not to draw glyphs on top of each other. */

  for (i = 1; i <= total; i++) if (Proper(i)) {
    k = l = chartx+charty;

    /* For each planet, we draw the glyph either right over or right under */
    /* the actual zenith location point. So, find out the closest distance */
    /* of any other planet assuming we place ours at both possibilities.   */

    for (J = 1; J < i; J++) if (Proper(J)) {
      k = MIN(k, abs(M[i]-M[J])+abs(N[i]-N[J]));
      l = MIN(l, abs(M[i]-M[J])+abs(N[i]-unit-N[J]));
    }

    /* Normally, we put the glyph right below the actual point. If however   */
    /* another planet is close enough to have their glyphs overlap, and the  */
    /* above location is better of, then we'll draw the glyph above instead. */

    if (k < unit || l < unit)
      if (k < l)
        N[i] -= unit;
  }
  for (i = total; i >= 1; i--) if (X[i] >= 0 && Proper(i))      /* Draw the */
    DrawObject(i, M[i], N[i]);                                  /* glyphs.  */
  for (i = total; i >= 1; i--) if (X[i] >= 0 && Proper(i)) {
#ifdef WIN
    if (!xbitmap)
      Xcolor(objectcolor[i]);
#endif
    DrawSpot(X[i], Y[i], objectcolor[i]);
  }
}


/* Draw one "Ley line" on the world map, based coordinates given in terms of */
/* longitude and vertical fractional distance from the center of the earth.  */

void DrawLeyLine(l1, f1, l2, f2, o)
real l1, f1, l2, f2;
bit o;
{
  l1 = Mod(l1); l2 = Mod(l2);

  /* Convert vertical fractional distance to a corresponding coordinate. */

  f1 = 90.0-ASIN(f1)/(PI/2.0)*90.0; f2 = 90.0-ASIN(f2)/(PI/2.0)*90.0;
  DrawWrap((int) (l1*(real)SCALE+0.5)+1,
           (int) (f1*(real)SCALE+0.5)+1,
           (int) (l2*(real)SCALE+0.5)+1,
           (int) (f2*(real)SCALE+0.5)+1, o);
}


/* Draw the main set of planetary Ley lines on the map of the world. This */
/* consists of drawing an icosahedron and then a dodecahedron lattice.    */

void DrawLeyLines(deg)
int deg;
{
  color icosa, dodeca;
  real off = (real) deg, phi, h, h1, h2, r, i;

  icosa = aspectcolor[10]; dodeca = aspectcolor[13];
  phi = (sqrt(5.0)+1.0)/2.0;                     /* Icosahedron constants. */
  h = 1.0/(phi*2.0-1.0);
  for (i = off; i < DEGREES+off; i += 72.0) {    /* Draw icosahedron edges. */
    DrawLeyLine(i, h, i+72.0, h, icosa);
    DrawLeyLine(i-36.0, -h, i+36.0, -h, icosa);
    DrawLeyLine(i, h, i, 1.0, icosa);
    DrawLeyLine(i+36.0, -h, i+36.0, -1.0, icosa);
    DrawLeyLine(i, h, i+36.0, -h, icosa);
    DrawLeyLine(i, h, i-36.0, -h, icosa);
  }
  r = 1.0/sqrt(3.0)/phi/cos(DTOR(54.0));         /* Dodecahedron constants. */
  h2 = sqrt(1.0-r*r); h1 = h2/(phi*2.0+1.0);
  for (i = off; i < DEGREES+off; i += 72.0) {    /* Draw docecahedron edges. */
    DrawLeyLine(i-36.0, h2, i+36.0, h2, dodeca);
    DrawLeyLine(i, -h2, i+72.0, -h2, dodeca);
    DrawLeyLine(i+36.0, h2, i+36.0, h1, dodeca);
    DrawLeyLine(i, -h2, i, -h1, dodeca);
    DrawLeyLine(i+36.0, h1, i+72.0, -h1, dodeca);
    DrawLeyLine(i+36.0, h1, i, -h1, dodeca);
  }
}


/* Draw a map of the world on the screen. This is similar to drawing the  */
/* globe, but is simplified because this is just a rectangular image, and */
/* the window coordinates are proportional to the longitude and latitude. */

void DrawWorld(deg)
int deg;
{
  char *nam, *loc, *lin, d;
  int lon, lat, x, y, xold, yold, i;
  color c;

  /* Loop through each coastline string, drawing it on the world map. */

  while (ReadWorldData(&nam, &loc, &lin)) {
    i = nam[0]-'0';
    c = modex == 'l' ? on : (i ? rainbowcolor[i] : maincolor[6]);

    /* Get starting longitude and latitude of current coastline piece. */

    lon = (loc[0] == '+' ? 1 : -1)*
      ((loc[1]-'0')*100 + (loc[2]-'0')*10 + (loc[3]-'0'));
    lat = (loc[4] == '+' ? 1 : -1)*((loc[5]-'0')*10 + (loc[6]-'0'));
    xold = x = (int) Mod((real)(180-lon+deg));
    yold = y = 91-lat;

    /* Go down the coastline piece, drawing each segment on world map. */

    for (i = 0; d = lin[i]; i++) {
      if (d == 'L' || d == 'H' || d == 'G')
        x--;
      else if (d == 'R' || d == 'E' || d == 'F')
        x++;
      if (d == 'U' || d == 'H' || d == 'E')
        y--;
      else if (d == 'D' || d == 'G' || d == 'F')
        y++;
      if (x > 360) {
        x = 1;
        xold = 0;
      }

      /* If we are doing a Mollewide map projection, then transform the */
      /* coordinates appropriately before drawing the segment.          */

      if ((exdisplay & DASHXW0) > 0 && modex != 'l')
        DrawLine((180+(xold-180)*(int)sqrt((real)(32400-4*(yold-91)*(yold-91)
                 ))/180)*SCALE, yold*SCALE,
                 (180+(x-180)*(int)sqrt((real)(32400-4*(y-91)*(y-91)))/180)*
                 SCALE, y*SCALE, c, 0);
      else
        DrawLine(xold*SCALE, yold*SCALE, x*SCALE, y*SCALE, c, 0);
      if (x < 1)
        x = 360;
      xold = x; yold = y;
    }
  }

  /* Again, if we are doing the non-rectangular Mollewide map projection, */
  /* draw the outline of the globe/map itself.                            */

  if ((exdisplay & DASHXW0) > 0 && modex != 'l') {
    if (!bonus)
      for (xold = 0, y = -89; y <= 90; y++, xold = x)
        for (x = (int)(sqrt((real)(32400-4*y*y))+0.5), i = -1; i < 2; i += 2)
          DrawLine((180+i*xold)*SCALE, (90+y)*SCALE,
            (180+i*x)*SCALE, (91+y)*SCALE, on, 0);
  } else
    DrawBoxAll(1, 1, hilite);
}


/* Adjust an array of longitude positions so that no two are within a    */
/* certain orb of each other. This is used by the astro-graph routine to */
/* make sure we don't draw any planet glyphs marking the lines on top of */
/* each other. This is almost identical to the FillSymbolRing() routine  */
/* used by the wheel charts; however, there the glyphs are placed in a   */
/* continuous ring, while here we have the left and right screen edges.  */
/* Also, here we are placing two sets of planets at the same time.       */

void FillSymbolLine(symbol)
real *symbol;
{
  real orb = DEFORB*1.35*(real)SCALE, max = DEGREES*(real)SCALE, temp;
  int i, j, k = 1, l, k1, k2;

  /* Keep adjusting as long as we can still make changes. */

  for (l = 0; k && l < divisions*4; l++) {
    k = 0;
    for (i = 1; i <= TOTAL*2; i++)
      if (Proper((i+1)/2) && symbol[i] >= 0.0) {

        /* For each object, determine who is closest to the left and right. */

        k1 = max-symbol[i]; k2 = -symbol[i];
        for (j = 1; j <= THINGS*2; j++) {
          if (Proper((j+1)/2) && i != j) {
            temp = symbol[j]-symbol[i];
            if (temp<k1 && temp>=0.0)
              k1 = temp;
            else if (temp>k2 && temp<=0.0)
              k2 = temp;
          }
        }

        /* If an object's too close on one side, then we move to the other. */

        if (k2>-orb && k1>orb) {
          k = 1; symbol[i] = symbol[i]+orb*0.51+k2*0.49;
        } else if (k1<orb && k2<-orb) {
          k = 1; symbol[i] = symbol[i]-orb*0.51+k1*0.49;
        } else if (k2>-orb && k1<orb) {
          k = 1; symbol[i] = symbol[i]+(k1+k2)*0.5;
        }
      }
  }
}


/* Draw an astro-graph chart on a map of the world, i.e. the draw the     */
/* Ascendant, Descendant, Midheaven, and Nadir lines corresponding to the */
/* time in the chart. This chart is done when the -L switch is combined   */
/* with the -X switch.                                                    */

void XChartAstroGraph()
{
  real planet1[TOTAL+1], planet2[TOTAL+1],
    end1[TOTAL*2+1], end2[TOTAL*2+1],
    symbol1[TOTAL*2+1], symbol2[TOTAL*2+1],
    lon = Lon, longm, x, y, z, ad, oa, am, od, dm, lat;
  int unit = SCALE, lat1 = -60, lat2 = 75, y1, y2, xold1, xold2, i, j, k;

  /* Erase top and bottom parts of map. We don't draw the astro-graph lines */
  /* above certain latitudes, and this gives us room for glyph labels, too. */

  y1 = (91-lat1)*SCALE;
  y2 = (91-lat2)*SCALE;
  DrawBlock(1, 1, chartx-2, y2-1, off);
  DrawBlock(1, charty-2, chartx-2, y1+1, off);
  DrawLine(0, charty/2, chartx-2, charty/2, hilite, 4);    /* Draw equator. */
  DrawLine(1, y2, chartx-2, y2, on, 0);
  DrawLine(1, y1, chartx-2, y1, on, 0);
  for (i = 1; i <= total*2; i++)
    end1[i] = end2[i] = -1000.0;

  /* Draw small hatches every 5 degrees along edges of world map. */

  for (i = lat1; i <= lat2; i += 5) {
    j = (91-i)*SCALE;
    k = 2+(i%10 == 0)+2*(i%30 == 0);
    DrawLine(1, j, k, j, hilite, 0);
    DrawLine(chartx-2, j, chartx-1-k, j, hilite, 0);
  }
  for (i = -180; i < 180; i += 5) {
    j = (180-i)*SCALE;
    k = 2+(i%10 == 0)+2*(i%30 == 0)+(i%90 == 0);
    DrawLine(j, y2+1, j, y2+k, hilite, 0);
    DrawLine(j, y1-1, j, y1-k, hilite, 0);
  }

  /* Calculate zenith locations of each planet. */

  for (i = 1; i <= total; i++) {
    planet1[i] = DTOR(planet[i]);
    planet2[i] = DTOR(planetalt[i]);
    ecltoequ(&planet1[i], &planet2[i]);
  }

  /* Draw the Midheaven lines and zenith location markings. */

  if (lon < 0.0)
    lon += DEGREES;
  for (i = 1; i <= total; i++) if (Proper(i)) {
    x = DTOR(MC)-planet1[i];
    if (x < 0.0)
      x += 2.0*PI;
    if (x > PI)
      x -= 2.0*PI;
    z = lon+RTOD(x);
    if (z > 180.0)
      z -= DEGREES;
    j = (int) (Mod(180.0-z+degree)*(real)SCALE);
    DrawLine(j, y1+unit*4, j, y2-unit*1, elemcolor[1], 0);
    end2[i*2-1] = (real) j;
    y = RTOD(planet2[i]);
    k = (int) ((91.0-y)*(real)SCALE);
    DrawBlock(j-1, k-1, j+1, k+1, hilite);
    DrawBlock(j, k, j, k, off);

    /* Draw Nadir lines assuming we aren't in bonus chart mode. */

    if (!bonus) {
      j += 180*SCALE;
      if (j > chartx-2)
        j -= (chartx-2);
      end1[i*2-1] = (real) j;
      DrawLine(j, y1+unit*2, j, y2-unit*2, elemcolor[3], 0);
    }
  }

  /* Now, normally, unless we are in bonus chart mode, we will go on to draw */
  /* the Ascendant and Descendant lines here.                                */

  longm = DTOR(Mod(MC+lon));
  if (!bonus) for (i = 1; i <= total; i++) if (Proper(i)) {
    xold1 = xold2 = -1000;
    for (lat = (real) lat1; lat <= (real) lat2;
      lat += 1.0/(real)SCALE) {

      /* First compute and draw the current segment of Ascendant line. */

      j = (int) ((91.0-lat)*(real)SCALE);
      ad = tan(planet2[i])*tan(DTOR(lat));
      if (ad*ad > 1.0)
        ad = 1000.0;
      else {
        ad = ASIN(ad);
        oa = planet1[i]-ad;
        if (oa < 0.0)
          oa += 2.0*PI;
        am = oa-PI/2.0;
        if (am < 0.0)
          am += 2.0*PI;
        z = longm-am;
        if (z < 0.0)
          z += 2.0*PI;
        if (z > PI)
          z -= 2.0*PI;
        z = RTOD(z);
        k = (int) (Mod(180.0-z+degree)*(real)SCALE);
        DrawWrap(xold1, j+1, k, j, elemcolor[0]);
        if (lat == (real) lat1) {                            /* Line segment */
          DrawLine(k, y1, k, y1+unit*4, elemcolor[0], 0);    /* pointing to  */
          end2[i*2] = (real) k;                              /* Ascendant.   */
        }
        xold1 = k;
      }

      /* The curving Ascendant and Descendant lines actually touch each at  */
      /* low latitudes. Sometimes when we start out, a particular planet's  */
      /* lines haven't appeared yet, i.e. we are scanning at a latitude     */
      /* where our planet's lines don't exist. If this is the case, then    */
      /* when they finally do start, draw a thin horizontal line connecting */
      /* the Ascendant and Descendant lines so they don't just start in     */
      /* space. Note that these connected lines aren't labeled with glyphs. */

      if (ad == 1000.0) {
        if (xold1 >= 0) {
          DrawWrap(xold1, j+1, xold2, j+1, gray);
          lat = 90.0;
        }
      } else {

      /* Then compute and draw corresponding segment of Descendant line. */

        od = planet1[i]+ad;
        dm = od+PI/2.0;
        z = longm-dm;
        if (z < 0.0)
          z += 2.0*PI;
        if (z > PI)
          z -= 2.0*PI;
        z = RTOD(z);
        k = (int) (Mod(180.0-z+degree)*(real)SCALE);
        DrawWrap(xold2, j+1, k, j, elemcolor[2]);
        if (xold2 < 0)
          DrawWrap(xold1, j, k, j, gray);
        if (lat == (real) lat1)                              /* Line segment */
          DrawLine(k, y1, k, y1+unit*2, elemcolor[2], 0);    /* pointing to  */
        xold2 = k;                                           /* Descendant.  */
      }
    }

    /* Draw segments pointing to top of Ascendant and Descendant lines. */

    if (ad != 1000.0) {
      DrawLine(xold1, y2, xold1, y2-unit*1, elemcolor[0], 0);
      DrawLine(k, y2, k, y2-unit*2, elemcolor[2], 0);
      end1[i*2] = (real) k;
    }
  }
#ifdef WIN
  if (!xbitmap)
    Xcolor(maincolor[5]);
#endif
  DrawPoint((int)((180.0-Lon)*(real)SCALE), (int)((91-Lat)*(real)SCALE), on);
#ifdef WIN
  if (!xbitmap)
    Xcolor(on);
#endif

  /* Determine where to draw the planet glyphs. We have four sets of each    */
  /* planet - each planet's glyph appearing in the chart up to four times -  */
  /* one for each type of line. The Midheaven and Ascendant lines are always */
  /* labeled at the bottom of the chart, while the Nadir and Midheaven lines */
  /* at the top. Therefore we need to place two sets of glyphs, twice.       */

  for (i = 1; i <= total*2; i++) {
    symbol1[i] = end1[i];
    symbol2[i] = end2[i];
  }
  FillSymbolLine(symbol1);
  FillSymbolLine(symbol2);

  /* Now actually draw the planet glyphs. */

  for (i = 1; i <= total*2; i++) {
    j = (i+1)/2;
    if (Proper(j)) {
      if ((turtlex = (int) symbol1[i]) > 0) {
        DrawLine((int) end1[i], y2-unit*2, (int) symbol1[i], y2-unit*4,
          ret[j] < 0.0 ? gray : on, (ret[i] < 0.0 ? 1 : 0) - xcolor);
        DrawObject(j, turtlex, y2-unit*10);
      }
      if ((turtlex = (int) symbol2[i]) > 0) {
        DrawLine((int) end2[i], y1+unit*4, (int) symbol2[i], y1+unit*8,
          ret[j] < 0.0 ? gray : on, (ret[i] < 0.0 ? 1 : 0) - xcolor);
        DrawObject(j, turtlex, y1+unit*14);
        DrawTurtle(objectdraw[i & 1 ? 18 : 19], (int) symbol2[i],
          y1+unit*24, objectcolor[j]);
      }
    }
  }
}


/* Draw an aspect and midpoint grid in the window, with planets labeled down */
/* the diagonal. This chart is done when the -g switch is combined with the  */
/* -X switch. The chart is always 20 by 20 cells in size; therefore based on */
/* how the restrictions are set up, there may be blank columns and rows, or  */
/* else only the first 20 unrestricted objects will be included.             */

void XChartGrid()
{
  int unit, siz, x, y, i, j, k;
  color c;

  unit = CELLSIZE*SCALE; siz = OBJECTS*unit;
  CreateGrid(bonus);
  
  /* Loop through each cell in each row and column of grid. */

  for (y = 1, j = 0; y <= OBJECTS; y++) {
    do {
      j++;
    } while (ignore[j] && j <= total);
    DrawLine(0, y*unit, siz, y*unit, gray, !xcolor);
    DrawLine(y*unit, 0, y*unit, siz, gray, !xcolor);
    if (j <= total) for (x = 1, i = 0; x <= OBJECTS; x++) {
      do {
        i++;
      } while (ignore[i] && i <= total);
      if (i <= total) {
        turtlex = x*unit-unit/2;
        turtley = y*unit-unit/2 - (scale > 200 ? 5 : 0);

        /* If this is an aspect cell, draw glyph of aspect in effect. */

        if (bonus ? x > y : x < y)
          DrawTurtle(aspectdraw[grid->n[i][j]], turtlex,
            turtley, c = aspectcolor[grid->n[i][j]]);

        /* If this is a midpoint cell, draw glyph of sign of midpoint. */

        else if (bonus ? x < y : x > y)
          DrawTurtle(signdraw[grid->n[i][j]], turtlex,
            turtley, c = elemcolor[(grid->n[i][j]-1)%4]);

        /* For cells on main diagonal, draw glyph of planet. */

        else {
          DrawEdge((y-1)*unit, (y-1)*unit, y*unit, y*unit, hilite);
          DrawObject(i, turtlex, turtley);
        }

        /* When the scale size is 300, we can print text in each cell: */

        if (scale > 200 && label) {
          k = abs(grid->v[i][j]);

          /* For the aspect portion, print the orb in degrees and minutes. */

          if (bonus ? x > y : x < y) {
            if (grid->n[i][j])
              sprintf(string, "%c%d %d%d'", k != grid->v[i][j] ? '-' : '+',
                k/60, (k%60)/10, (k%60)%10);
            else
              sprintf(string, "");

          /* For the midpoint portion, print the degrees and minutes. */

          } else if (bonus ? x < y : x > y)
            sprintf(string, "%d%d %d%d'",
              (k/60)/10, (k/60)%10, (k%60)/10, (k%60)%10);

          /* For the main diagonal, print degree and sign of each planet. */

          else {
            c = elemcolor[(grid->n[i][j]-1)%4];
            sprintf(string, "%c%c%c %d%d", SIGNAM(grid->n[i][j]), k/10, k%10);
          }
          DrawText(string, x*unit-unit/2-FONTX*3, y*unit-3, c);
        }
      }
    }
  }
  DrawBoxAll(1, 1, hilite);
}


/* Draw an aspect (or midpoint) grid in the window, between the planets in  */
/* two different charts, with the planets labeled at the top and side. This */
/* chart is done when the -g switch is combined with the -r0 and -X switch. */
/* Like above, the chart always has a fixed number of cells.                */

void XChartGridRelation()
{
  int unit, siz, x, y, i, j, k, l;
  color c;

  unit = CELLSIZE*SCALE; siz = (OBJECTS+1)*unit;
  CreateGridRelation(bonus != (exdisplay & DASHg0) > 0);
  for (y = 0, j = -1; y <= OBJECTS; y++) {
    do {
      j++;
    } while (ignore[j] && j <= total);
    DrawLine(0, (y+1)*unit, siz, (y+1)*unit, gray, !xcolor);
    DrawLine((y+1)*unit, 0, (y+1)*unit, siz, gray, !xcolor);
    DrawEdge(0, y*unit, unit, (y+1)*unit, hilite);
    DrawEdge(y*unit, 0, (y+1)*unit, unit, hilite);
    if (j <= total) for (x = 0, i = -1; x <= OBJECTS; x++) {
      do {
        i++;
      } while (ignore[i] && i <= total);

      /* Again, we are looping through each cell in each row and column. */

      if (i <= total) {
        turtlex = x*unit+unit/2;
        turtley = y*unit+unit/2 - (!xbitmap && scale > 200 ? 5 : 0);

        /* If current cell is on top row or left hand column, draw glyph */
        /* of planet owning the particular row or column in question.    */

        if (y == 0 || x == 0) {
          if (x+y > 0)
            DrawObject(j == 0 ? i : j, turtlex, turtley);
        } else {

        /* Otherwise, draw glyph of aspect in effect, or glyph of */
        /* sign of midpoint, between the two planets in question. */

          if (bonus == (exdisplay & DASHg0) > 0)
            DrawTurtle(aspectdraw[grid->n[i][j]], turtlex, turtley,
              c = aspectcolor[grid->n[i][j]]);
          else
            DrawTurtle(signdraw[grid->n[i][j]], turtlex, turtley,
              c = elemcolor[(grid->n[i][j]-1)%4]);
        }

        /* Again, when scale size is 300, print some text in current cell: */

        if (scale > 200 && label) {

          /* For top and left edges, print sign and degree of the planet. */

          if (y == 0 || x == 0) {
            if (x+y > 0) {
              k = (int)((y == 0 ? planet2[i] : planet1[j])/30.0)+1;
              l = (int)((y == 0 ? planet2[i] : planet1[j])-(real)(k-1)*30.0);
              c = elemcolor[(k-1)%4];
              sprintf(string, "%c%c%c %d%d", SIGNAM(k), l/10, l%10);

              /* For extreme upper left corner, print some little arrows */
              /* pointing out chart1's planets and chart2's planets.     */

            } else {
              c = hilite;
              sprintf(string, "1v 2->");
            }
          } else {
            k = abs(grid->v[i][j]);

            /* For aspect cells, print the orb in degrees and minutes. */

            if (bonus == (exdisplay & DASHg0) > 0)
              if (grid->n[i][j])
                sprintf(string, "%c%d %d%d'", k != grid->v[i][j] ? (exdisplay &
                  DASHga ? 'a' : '-') : (exdisplay & DASHga ? 's' : '+'),
                  k/60, (k%60)/10, (k%60)%10);
              else
                sprintf(string, "");

            /* For midpoint cells, print degree and minute. */

            else
              sprintf(string, "%d%d %d%d'",
                (k/60)/10, (k/60)%10, (k%60)/10, (k%60)%10);
          }
          DrawText(string, x*unit+unit/2-FONTX*3, (y+1)*unit-3, c);
        }
      }
    }
  }
  DrawBoxAll(1, 1, hilite);
}


/* Draw the local horizon, and draw in the planets where they are at the */
/* time in question, as done when the -Z is combined with the -X switch. */

void XChartHorizon()
{
  real lon, lat,
    lonz[TOTAL+1], latz[TOTAL+1], azi[TOTAL+1], alt[TOTAL+1];
  int x[TOTAL+1], y[TOTAL+1], m[TOTAL+1], n[TOTAL+1],
    cx = chartx / 2, cy = charty / 2, unit = 12*SCALE,
    x1, y1, x2, y2, xs, ys, i, j, k, l;

  DrawBoxAll(1, 1, hilite);

  /* Make a slightly smaller rectangle within the window to draw the planets */
  /* in. Make segments on all four edges marking 5 degree increments.        */

  x1 = y1 = unit/2; x2 = chartx-x1; y2 = charty-y1;
  xs = x2-x1; ys = y2-y1;
  for (i = 0; i < 180; i += 5) {
    j = y1+(int)((real)i*(real)ys/180.0);
    k = 2+(i%10 == 0)+2*(i%30 == 0);
    DrawLine(x1+1, j, x1+1+k, j, hilite, 0);
    DrawLine(x2-1, j, x2-1-k, j, hilite, 0);
  }
  for (i = 0; i < 360; i += 5) {
    j = x1+(int)((real)i*(real)xs/DEGREES);
    k = 2+(i%10 == 0)+2*(i%30 == 0);
    DrawLine(j, y1+1, j, y1+1+k, hilite, 0);
    DrawLine(j, y2-1, j, y2-1-k, hilite, 0);
  }

  /* Draw vertical lines dividing our rectangle into four areas. In our     */
  /* local space chart, the middle line represents due south, the left line */
  /* due east, the right line due west, and the edges due north. A fourth   */
  /* horizontal line divides that which is above and below the horizon.     */

  DrawLine(cx, y1, cx, y2, gray, 1);
  DrawLine((cx+x1)/2, y1, (cx+x1)/2, y2, gray, 1);
  DrawLine((cx+x2)/2, y1, (cx+x2)/2, y2, gray, 1);
  DrawEdge(x1, y1, x2, y2, on);
  DrawLine(x1, cy, x2, cy, on, 1);

  /* Calculate the local horizon coordinates of each planet. First convert */
  /* zodiac position and declination to zenith longitude and latitude.     */

  lon = DTOR(Mod(Lon)); lat = DTOR(Lat);
  for (i = 1; i <= total; i++) {
    lonz[i] = DTOR(planet[i]); latz[i] = DTOR(planetalt[i]);
    ecltoequ(&lonz[i], &latz[i]);
  }
  for (i = 1; i <= total; i++) if (Proper(i)) {
    lonz[i] = DTOR(Mod(RTOD(lonz[18]-lonz[i]+PI/2.0)));
    equtolocal(&lonz[i], &latz[i], PI/2.0-lat);
    azi[i] = DEGREES-RTOD(lonz[i]); alt[i] = RTOD(latz[i]);
    x[i] = x1+(int)((real)xs*(Mod(90.0-azi[i]))/DEGREES+0.5);
    y[i] = y1+(int)((real)ys*(90.0-alt[i])/180.0+0.5);
    m[i] = x[i]; n[i] = y[i]+unit/2;
  }

  /* As in the DrawGlobe() routine, we now determine where to draw the   */
  /* glyphs in relation to the actual points, so that the glyphs aren't  */
  /* drawn on top of each other if possible. Again, we assume that we'll */
  /* put the glyph right under the point, unless there would be some     */
  /* overlap and the above position is better off.                       */

  for (i = 1; i <= total; i++) if (Proper(i)) {
    k = l = chartx+charty;
    for (j = 1; j < i; j++) if (Proper(j)) {
      k = MIN(k, abs(m[i]-m[j])+abs(n[i]-n[j]));
      l = MIN(l, abs(m[i]-m[j])+abs(n[i]-unit-n[j]));
    }
    if (k < unit || l < unit)
      if (k < l)
        n[i] -= unit;
  }
  for (i = total; i >= 1; i--) if (Proper(i))    /* Draw planet's glyph. */
    DrawObject(i, m[i], n[i]);
  for (i = total; i >= 1; i--) if (Proper(i)) {
#ifdef WIN
    if (!xbitmap)
      Xcolor(objectcolor[i]);
#endif
    if (!bonus || i > BASE)
      DrawPoint(x[i], y[i], objectcolor[i]);    /* Draw small or large dot */
    else                                        /* near glyph indicating   */
      DrawSpot(x[i], y[i], objectcolor[i]);     /* exact local location.   */
  }
}


/* Draw the local horizon, and draw in the planets where they are at the  */
/* time in question. This chart is done when the -Z0 is combined with the */
/* -X switch. This is an identical function to XChartHorizon(); however,  */
/* that routine's chart is entered on the horizon and meridian. Here we   */
/* center the chart around the center of the sky straight up from the     */
/* local horizon, with the horizon itself being an encompassing circle.   */

void XChartHorizonSky()
{
  real lon, lat, rx, ry, s, a, sqr2,
    lonz[TOTAL+1], latz[TOTAL+1], azi[TOTAL+1], alt[TOTAL+1];
  int x[TOTAL+1], y[TOTAL+1], m[TOTAL+1], n[TOTAL+1],
    cx = chartx / 2, cy = charty / 2, unit = 12*SCALE, i, j, k, l;

  /* Draw a circle in window to indicate horizon line, lines dividing   */
  /* the window into quadrants to indicate n/s and w/e meridians, and   */
  /* segments on these lines and the edges marking 5 degree increments. */

  InitCircle();
  sqr2 = sqrt(2.0);
  DrawLine(cx, 0, cx, charty-1, gray, 1);
  DrawLine(0, cy, chartx-1, cy, gray, 1);
  for (i = -125; i <= 125; i += 5) {
    k = 2+(i/10*10 == i ? 1 : 0)+(i/30*30 == i ? 2 : 0);
    s = 1.0/(90.0*sqr2);
    j = cy+(int)(s*cy*i);
    DrawLine(cx-k, j, cx+k, j, hilite, 0);
    j = cx+(int)(s*cx*i);
    DrawLine(j, cy-k, j, cy+k, hilite, 0);
  }
  for (i = 5; i < 55; i += 5) {
    k = 2+(i/10*10 == i ? 1 : 0)+(i/30*30 == i ? 2 : 0);
    s = 1.0/(180.0-90.0*sqr2);
    j = (int)(s*cy*i);
    DrawLine(0, j, k, j, hilite, 0);
    DrawLine(0, charty-1-j, k, charty-1-j, hilite, 0);
    DrawLine(chartx-1, j, chartx-1-k, j, hilite, 0);
    DrawLine(chartx-1, charty-1-j, chartx-1-k, charty-1-j, hilite, 0);
    j = (int)(s*cx*i);
    DrawLine(j, 0, j, k, hilite, 0);
    DrawLine(chartx-1-j, 0, chartx-1-j, k, hilite, 0);
    DrawLine(j, charty-1, j, charty-1-k, hilite, 0);
    DrawLine(chartx-1-j, charty-1, chartx-1-j, charty-1-k, hilite, 0);
  }
  rx = cx/sqr2; ry = cy/sqr2;
  for (i = 0; i < 360; i++)
    DrawLine(cx+(int)(rx*circ->x[i]), cy+(int)(ry*circ->y[i]),
      cx+(int)(rx*circ->x[i+1]), cy+(int)(ry*circ->y[i+1]), on, 0);
  for (i = 0; i < 360; i += 5) {
    k = 2+(i/10*10 == i ? 1 : 0)+(i/30*30 == i ? 2 : 0);
    DrawLine(cx+(int)((rx-k)*circ->x[i]), cy+(int)((ry-k)*circ->y[i]),
      cx+(int)((rx+k)*circ->x[i]), cy+(int)((ry+k)*circ->y[i]), on, 0);
  }

  /* Calculate the local horizon coordinates of each planet. First convert */
  /* zodiac position and declination to zenith longitude and latitude.     */

  lon = DTOR(Mod(Lon)); lat = DTOR(Lat);
  for (i = 1; i <= total; i++) {
    lonz[i] = DTOR(planet[i]); latz[i] = DTOR(planetalt[i]);
    ecltoequ(&lonz[i], &latz[i]);
  }
  for (i = 1; i <= total; i++) if (Proper(i)) {
    lonz[i] = DTOR(Mod(RTOD(lonz[18]-lonz[i]+PI/2.0)));
    equtolocal(&lonz[i], &latz[i], PI/2.0-lat);
    azi[i] = a = DEGREES-RTOD(lonz[i]); alt[i] = 90.0-RTOD(latz[i]);
    s = alt[i]/90.0;
    x[i] = cx+(int)(rx*s*COSD(180.0+azi[i])+0.5);
    y[i] = cy+(int)(ry*s*SIND(180.0+azi[i])+0.5);
    if (!ISLEGAL(x[i], y[i]))
      x[i] = -1000;
    m[i] = x[i]; n[i] = y[i]+unit/2;
  }

  /* As in the DrawGlobe() routine, we now determine where to draw the   */
  /* glyphs in relation to the actual points, so that the glyphs aren't  */
  /* drawn on top of each other if possible. Again, we assume that we'll */
  /* put the glyph right under the point, unless there would be some     */
  /* overlap and the above position is better off.                       */

  for (i = 1; i <= total; i++) if (Proper(i)) {
    k = l = chartx+charty;
    for (j = 1; j < i; j++) if (Proper(j)) {
      k = MIN(k, abs(m[i]-m[j])+abs(n[i]-n[j]));
      l = MIN(l, abs(m[i]-m[j])+abs(n[i]-unit-n[j]));
    }
    if (k < unit || l < unit)
      if (k < l)
        n[i] -= unit;
  }
  for (i = total; i >= 1; i--) if (m[i] >= 0 && Proper(i))    /* Draw glyph. */
    DrawObject(i, m[i], n[i]);
  for (i = total; i >= 1; i--) if (x[i] >= 0 && Proper(i)) {
#ifdef WIN
    if (!xbitmap)
      Xcolor(objectcolor[i]);
#endif
    if (!bonus || i > BASE)
      DrawPoint(x[i], y[i], objectcolor[i]);    /* Draw small or large dot */
    else                                        /* near glyph indicating   */
      DrawSpot(x[i], y[i], objectcolor[i]);     /* exact local location.   */
  }
  DrawBoxAll(1, 1, hilite);
}


/* Given an x and y coordinate, return the angle formed by the line from the */
/* origin to this coordinate. This is just converting from rectangular to    */
/* polar coordinates; however, we don't determine the radius here.           */

real Angle(x, y)
real x, y;
{
  real a;

  a = atan(y/x);
  if (a < 0.0)
    a += PI;
  if (y < 0.0)
    a += PI;
  return Mod(RTOD(a));
}


/* Draw a chart depicting an aerial view of the solar system in space, with */
/* all the planets drawn around the Sun, and the specified central planet   */
/* in the middle, as done when the -S is combined with the -X switch.       */

void XChartSpace()
{
  int x[TOTAL+1], y[TOTAL+1], m[TOTAL+1], n[TOTAL+1],
    cx = chartx / 2, cy = charty / 2, unit = 12*SCALE, i, j, k, l;
  real sx, sy, sz = 30.0, x1, y1, a;

  /* Determine the scale of the window. For a scale size of 300, make    */
  /* the window 6 AU in radius (enough for inner planets out to asteroid */
  /* belt). For a scale of 200, make window 30 AU in radius (enough for  */
  /* planets out to Neptune). For scale of 100, make it 90 AU in radius  */
  /* (enough for all planets including the orbits of the uranians.)      */

  if (scale < 200)
    sz = 90.0;
  else if (scale > 200)
    sz = 6.0;
  sx = (real)cx/sz; sy = (real)cy/sz;
  for (i = 0; i <= BASE; i++) if (Proper(i)) {

    /* Determine what glyph corresponds to our current planet. Normally the */
    /* array indices are the same, however we have to do some swapping for  */
    /* non-geocentric based charts where a planet gets replaced with Earth. */

    if (centerplanet == 0)
      j = i < 2 ? 1-i : i;
    else if (centerplanet == 1)
      j = i;
    else
      j = i == 0 ? centerplanet : (i == centerplanet ? 0 : i);
    x1 = spacex[j]; y1 = spacey[j];
    x[i] = cx-(int)(x1*sx); y[i] = cy+(int)(y1*sy);
    m[i] = x[i]; n[i] = y[i]+unit/2;
  }

  /* As in the DrawGlobe() routine, we now determine where to draw the   */
  /* glyphs in relation to the actual points, so that the glyphs aren't  */
  /* drawn on top of each other if possible. Again, we assume that we'll */
  /* put the glyph right under the point, unless there would be some     */
  /* overlap and the above position is better off.                       */

  for (i = 0; i <= BASE; i++) if (Proper(i)) {
    k = l = chartx+charty;
    for (j = 0; j < i; j++) if (Proper(j)) {
      k = MIN(k, abs(m[i]-m[j])+abs(n[i]-n[j]));
      l = MIN(l, abs(m[i]-m[j])+abs(n[i]-unit-n[j]));
    }
    if (k < unit || l < unit)
      if (k < l)
        n[i] -= unit;
  }

  /* Draw the 12 sign boundaries from the center body to edges of screen. */

  a = Mod(Angle(spacex[3], spacey[3])-planet[3]);
  for (i = 0; i < SIGNS; i++) {
    k = cx+2*(int)((real)cx*COSD((real)i*30.0+a));
    l = cy+2*(int)((real)cy*SIND((real)i*30.0+a));
    DrawClip(cx, cy, k, l, gray, 1);
  }
  for (i = BASE; i >= 0; i--) if (Proper(i) && ISLEGAL(m[i], n[i]))
    DrawObject(i, m[i], n[i]);
  for (i = BASE; i >= 0; i--) if (Proper(i) && ISLEGAL(x[i], y[i])) {
#ifdef WIN
    if (!xbitmap)
      Xcolor(objectcolor[i]);
#endif
    if (!bonus || i > BASE)
      DrawPoint(x[i], y[i], objectcolor[i]);    /* Draw small or large dot */
    else                                        /* near glyph indicating   */
      DrawSpot(x[i], y[i], objectcolor[i]);     /* exact local location.   */
  }
  DrawBoxAll(1, 1, hilite);
}


/* Create a chart in the window based on the current graphics chart mode. */
/* This is the main dispatch routine for all of the program's graphics.   */

void XChart()
{
  int i, j, k;

  if (xbitmap) {
    if (chartx > BITMAPX)
      chartx = BITMAPX;
    if (charty > BITMAPY)
      charty = BITMAPY;
    DrawBlock(0, 0, chartx - 1, charty - 1, off);    /* Clear bitmap screen. */
  }
  switch (modex) {
  case 'c':
    if (relation != DASHr0)
      XChartWheel();
    else
      XChartWheelRelation();
    break;
  case 'l':
    DrawWorld(degree);     /* First draw map of world.           */
    XChartAstroGraph();    /* Then draw astro-graph lines on it. */
    break;
  case 'a':
    if (relation != DASHr0)
      XChartGrid();
    else
      XChartGridRelation();
    break;
  case 'z':
    if (exdisplay & DASHZ0)
      XChartHorizonSky();
    else
      XChartHorizon();
    break;
  case 's':
    XChartSpace();
    break;
  case 'w':
    DrawWorld(degree);                          /* First draw map of world. */
    if (bonus && (exdisplay & DASHXW0) == 0)    /* Then maybe Ley lines.    */
      DrawLeyLines(degree);
    break;
  case 'p':
  case 'g':
    DrawGlobe(degree);
    break;
  }

  /* Print text showing chart information at bottom of window. */

  if (xtext && modex != 'w' && modex != 'p' && modex != 'g') {
    if (Mon == -1)
      sprintf(string, "(no time or space)");
    else if (relation == DASHrc)
      sprintf(string, "(composite)");
    else {
      i = (int) Mon; j = (int) (FRACT(Tim)*100.0+0.1);
      k = ansi; ansi = FALSE;
      sprintf(string, "%2.0f %c%c%c %.0f %2.0f:%d%d (%.2f GMT) %s",
        Day, monthname[i][0], monthname[i][1], monthname[i][2], Yea,
        floor(Tim), j/10, j%10, -Zon, StringLocation(Lon, Lat, 100.0));
      ansi = k;
    }
    DrawText(string, (chartx-StringLen(string)*FONTX)/2, charty-3, hilite);
  }
}
#endif

/* xcharts.c */
