// DRAW.CPP - the system-independent graphics portion of RARS 0.39
// (was GRAPHICS.CPP) - by Mitchell E. Timin, State College, PA
// See GI.H, CAR.H & TRACK.H for class and structure declarations.
// This version is for Borland C++, version 3.1, and is for DOS.
// This is part of version 0.60 of RARS (Robot Auto Racing Simulation).
// GI.CPP is the system-dependent graphics portion of RARS.
// ver. 0.1 release January 12, 1995
// ver. 0.2 1/23/95
// ver. 0.3 2/7/95
// ver. 0.39 3/6/95 
// ver. 0.45 3/21/95
// ver. 0.50 4/5/95
// ver. 0.6b 5/8/95 b for beta

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "car.h"
#include "track.h"
#include "os.h"

static double finish_x, finish_y;         // These four variables are used to
static double finish_y_in, finish_y_out;  // locate the finish line on the screen.
static double spacing;                    // see leaders()
static segment *trackout; // global variables that describe the track:
static segment *trackin;
static     int  NSEG;
static  double  width;

extern Car* pcar[];      // array of pointers to the various cars
extern double length; // total lenth of track (smaller of inner and outer rails)
extern car_ID drivers[];   // array of pointers to their name strings
extern double CHR_HGT;   // height in feet of row of text
extern int no_display;   // when set, do no graphics

/* add prototypes which should have been there all along */
void draw_arc(double, double, double, double, double);

// These six functions are member functions of the Car class.
// All they do is fetch private data from the car objects.
inline double Car::get_speed(void)
{  return sqrt(xdot * xdot + ydot * ydot);  }

inline double Car::get_vc(void)
{  return vc;  }

inline double Car::get_alpha(void)
{  return alpha;  }

inline double Car::get_power(void)
{  return power;  }

inline double Car::get_lin_acc(void)
{  return tan_a/g;  }

inline double Car::get_lat_acc(void)
{  return cen_a/g;  }

int round(double given)  // convert double to int by rounding
{
   if(given > 0.0)
      return int(given + .5);
   else
      return int(given - .5);
}

// Convert input into an ASCII string with two decimal digits.
void make_dec_string(char* out,         // pointer to destination string
                     double input)      // value to be converted
{
   long int value;
   char intpart[5], decpart[4];
   int neg = 0;                          // to flag negative numbers      

   if(input < 0)  {
      neg = 1;
      input = -input;
   }

   value = (long int)(100.0 * input + .5);
   itoa(int(value / 100), intpart, 10);
   itoa(int(value % 100), decpart, 10);
   if(decpart[1] == '\0') {   // we might have to stick in a leading zero:
      decpart[2] = '\0';
      decpart[1] = decpart[0];
      decpart[0] = '0';
   }
   if(neg) {                 // we might have to put a minus sign in front:
      strcpy(out, "-");
      strcat(out, intpart);
   }
   else
      strcpy(out, intpart);
   strcat(out, ".");
   strcat(out, decpart);
}

// Assemble a string for the average speed of car i.  (goes to char* out)
void get_avg_spd(int i, char* out)
{
    make_dec_string(out, pcar[i]->speed_avg * MPH_FPS);
}

// Assemble a string for the maximum speed of car i.  (goes to char* out)
void get_max_spd(int i, char* out)
{
    make_dec_string(out, pcar[i]->speed_max * MPH_FPS);
}

// Draws the path specified by the segment array and starting
// conditions which are given as parameters.  Also, fills in the
// un-initialized portions of the segment array.  Returns the length.
double drawpath(double xstart,      // coordinates of starting point
                double ystart,
                double alfstart,    // starting tangent angle
                segment *track)     // pointer to structure that defines path
{
   double length = 0;          // to accumulate total length of path
   double cenx, ceny;          // center of circle arc
   double radius;              // radius of circle arc (negative == rt. turn)
   double x, y, alf;           // position and direction of start of segment
   double newx, newy, newalf;  // and the one after that  (alf in radians)
   int i;

   x = xstart;  y = ystart;   // store starting point & direction
   alf = alfstart;

   for(i=0; i < NSEG; i++) {                 // for each segment:
      radius = track[i].radius;
      if(radius == 0.0) {                   // is this a straightaway?
         length += track[i].length;
         newx = x + track[i].length * cos(alf);      // find end coordinates
         newy = y + track[i].length * sin(alf);
         track[i].end_x = newx;   track[i].end_y = newy;    // fill in these
         track[i].beg_x = x;      track[i].beg_y = y;       // empty slots in
         track[i].beg_ang = track[i].end_ang = alf;         // the track array
         newalf = alf;                                // direction won't change
         if(!no_display)
            draw_line(x, y, newx, newy);              // draw the straight line
         if(i == 0)      {      // find pixel locations of start/finish line:
              finish_y = newy;   // assume straightaway parallel to x-axis
              finish_x = x + FINISH * length;
         }
      }
      else if(radius > 0.0) {
         length += radius * track[i].length;
         cenx = x - radius * sin(alf);  // compute center location:
         ceny = y + radius * cos(alf);
         track[i].cen_x = cenx;   track[i].cen_y = ceny;  // fill empty slots
         track[i].beg_ang = alf;
         newalf = alf + track[i].length;           // compute new direction
         if(newalf > 2.0 * PI)
            newalf -= 2.0 * PI;
         track[i].end_ang = newalf;                // fill this empty slot
         newx = cenx + radius * sin(newalf);   // location of end
         newy = ceny - radius * cos(newalf);
         track[i].end_x = newx;   track[i].end_y = newy;  // fill in these
         track[i].beg_x = x;      track[i].beg_y = y;     // empty slots
         if(!no_display)
            draw_arc(radius, cenx, ceny, alf, track[i].length); // draw the arc
      }
      else {
         length -= radius * track[i].length;
         cenx = x - radius * sin(alf);  // compute center location:
         ceny = y + radius * cos(alf);
         track[i].cen_x = cenx;   track[i].cen_y = ceny;  // fill empty slots
         track[i].beg_ang = alf;
         newalf = alf - track[i].length;           // compute new direction
         if(newalf < -2.0 * PI)
            newalf += 2.0 * PI;
         track[i].end_ang = newalf;                // fill this empty slot
         newx = cenx + radius * sin(newalf);   // location of end
         newy = ceny - radius * cos(newalf);
         track[i].end_x = newx;   track[i].end_y = newy;  // fill in these
         track[i].beg_x = x;      track[i].beg_y = y;     // empty slots
         if(!no_display)
            draw_arc(radius, cenx, ceny, alf, track[i].length); // draw the arc
      }
      x = newx;                     // repeat with new position and direction:
      y = newy;
      alf = newalf;
   }
   // To close the circuit, draw a line from the last point back to the first.
   // This usually is not necessary, but it prevents flood fill leaking.
   if (!no_display)
      draw_line(x, y, xstart, ystart);

   return length;      // return the length of the path
}

// Draw a little car on the screen, at given position and orientation,
// and with the given colors.  (to erase the car, call it with track_color)
void drawcar(double x,         // coordinates of center of car
            double y,
            double ang,       // orientation angle of car, wrt x-axis, radians
            int nose,  // color of front portion
            int tail)  // color of rear portion
{
   double xx, yy, endx, endy;
   double sine, cosine, dx, dy;
   int i;

   sine = sin(ang);    cosine = cos(ang);
   xx = x + cosine * CARLEN/2 - sine * CARWID/2;    // left front corner coords
   yy = y + cosine * CARWID/2 + sine * CARLEN/2;
   x = xx;  y = yy;                                 // save the above values
   dx = 0.3333 * CARWID * sine;
   dy = -.3333 * CARWID * cosine;
   // below we draw four parallel lines to form the body of the car:
   set_color(tail);
   for(i=0; i<=3; i++) {
      endx = xx - CARLEN * cosine;
      endy = yy - CARLEN * sine;
      draw_line(xx, yy, endx, endy);
      if(i == 3) break;
      xx += dx;
      yy += dy;
   }
   // now four short lines of the nose color to decorate the front:
   set_color(nose);
   xx = x;  yy = y;             // restore x and y to left front of car
   for(i=0; i<=3; i++) {
      endx = xx + CARWID * sine;
      endy = yy - CARWID * cosine;
      draw_line(xx, yy, endx, endy);
      if(i == 3) break;
      xx += dy;
      yy -= dx;
   }
}

void lapper(int which, int lap)  // shows lap count on scoreboard,
{                                // also returns lap+1 to advance the lap count
   char string[] = "     ";   

   if(lap < 0)
      lap = 0;

   set_fill_color(FIELD_COLOR);   // the green part is off the track
   // This bar erases the previous lap count:
   rectangle(SCORE_BOARD_X + 10*CHR_WID,
             SCORE_BOARD_Y - which*CHR_HGT,
             SCORE_BOARD_X + 13*CHR_WID,
             SCORE_BOARD_Y - (which+.9)*CHR_HGT);

   set_color(TEXT_COLOR);          // now print text in black:
   itoa(lap, string, 10);
   text_output(SCORE_BOARD_X + 10 * CHR_WID,
               SCORE_BOARD_Y - which * CHR_HGT, string);
}

void designate(int i)      // marks car i on scoreboard, with a ">"
{
   static int iwas = -1;    // the previous i, for erasing

   set_fill_color(FIELD_COLOR);
   // This small rectangle erases the previous designator:
   if(iwas >= 0)
     rectangle(SCORE_BOARD_X - .7 * CARLEN - CHR_WID,
               SCORE_BOARD_Y - iwas * CHR_HGT,
               SCORE_BOARD_X - .7 * CARLEN -.2 * CHR_WID,
               SCORE_BOARD_Y - (iwas + .9) * CHR_HGT);

   // now print the new designator
   if(i >= 0)  {
      set_color(TEXT_COLOR);
      text_output(SCORE_BOARD_X - .7 * CARLEN - CHR_WID,
               SCORE_BOARD_Y - i * CHR_HGT, ">");
   }
   else if(iwas > -1) {     // if no robot is designated, then erase the IP
      rectangle(IP_X, IP_Y, IP_X + 21 * CHR_WID, IP_Y - CHR_HGT); // erase name
      rectangle(IP_X,                IP_Y - CHR_HGT,         // erase data
                IP_X + 17.5 * CHR_WID, IP_Y - 7 * CHR_HGT);
   }

   iwas = i;
}

void border(void)   // draws a thin border around the entire screen
{
   set_color(RAIL_COLOR);
   draw_line(0.0, 0.0, X_MAX, 0.0);
   draw_line(X_MAX, 0.0, X_MAX, Y_MAX);
   draw_line(X_MAX, Y_MAX, 0.0, Y_MAX);
   draw_line(0.0, Y_MAX, 0.0, 0.0);
}

// Initializes graphics system, draws track, fills in colored regions:
void graph_setup(void)
{
   double alt_len;         // used in deciding the length of the track

   build_track();  // read track data and fill in trackout[], trackin[]

   // get track information into our file scope global variables:
   NSEG = get_track_description().NSEG;
   width = get_track_description().width;
   trackin = get_track_description().trackin;
   trackout = get_track_description().trackout;

   if(!no_display)  {
      initialize_graphics();
      // paint the whole screen green:
      set_fill_color(FIELD_COLOR);
      rectangle(0.0, Y_MAX, X_MAX, 0.0);
      border();               // draw border at screen boundary
   }

   // draw outer track boundary:
   length = drawpath(TRK_STRT_X, TRK_STRT_Y, 0, trackout);
   finish_y_out = finish_y;                   // locate one end of finish line
   // draw inner track boundary:
   alt_len = drawpath(TRK_STRT_X, TRK_STRT_Y+width, 0, trackin);
   if(alt_len < length)     // take length of shorter rail as track length
      length = alt_len;
   finish_y_in = finish_y;        // locate other end of finish line
   if(no_display)
      return;
   // pave the track:
   set_fill_color(TRACK_COLOR);
   flood_fill(TRK_STRT_X, TRK_STRT_Y + width/2);     // color the track
}

void refresh_finish_line()   // re-draw the finish line:
{
   set_color(TEXT_COLOR);
   draw_line(finish_x, finish_y_out, finish_x, finish_y_in);
}

// Put up the scoreboard:
void scoreboard(STAGE stage)
{
   int i;

   double XS = SCORE_BOARD_X;
   double YS = SCORE_BOARD_Y;
   char string[] = "0123456789ABCD";
   int kount;

   spacing = 1.15 * CHR_HGT;   // for the leader board only
   // these rectangles are for the leader board car pictures:
   kount = car_count < 5 ? car_count : 5;
   // first erase the old board, if any:
   set_fill_color(FIELD_COLOR);
   rectangle(LDR_BRD_X - 1.9 * CARLEN, LDR_BRD_Y - CHR_HGT,
             LDR_BRD_X + 19*CHR_WID, LDR_BRD_Y - (kount + .7) * spacing);
   set_fill_color(TRACK_COLOR);   // rectangular background for car pictures:
   rectangle(LDR_BRD_X - 1.9 * CARLEN, LDR_BRD_Y - CHR_HGT,
             LDR_BRD_X -.5 * CARLEN, LDR_BRD_Y - (kount + .7) * spacing);
   // these rectangles are for the scoreboard car pictures:
   // first erase the old board, if any:
   set_fill_color(FIELD_COLOR);
   rectangle(XS-.7*CARLEN, YS+CHR_HGT/2, XS+13*CHR_WID,
                               YS - car_count * CHR_HGT);
   set_fill_color(TRACK_COLOR);
   rectangle(XS-.7*CARLEN, YS+CHR_HGT/2, XS+.5*CARLEN,
                               YS - car_count * CHR_HGT);
   // draw the cars on the scoreboard:
   for(i=0; i<car_count; i++) {
      drawcar(XS-.1*CARLEN, YS - i * CHR_HGT - CARWID/2, 0,
                     drivers[i].paint_job.nose, drivers[i].paint_job.tail);
      set_color(TEXT_COLOR);
      itoa(lap_count, string, 10);
      text_output(XS+2*CHR_WID, YS - i * CHR_HGT, drivers[i].rob_name);
      (void)lapper(i,-1);
   }
   // erase either "Practice" or "Race Length", and number of laps:
   rectangle(XS - 13 * CHR_WID, SCORE_BOARD_Y + 1.5 * CHR_HGT,
              XS - 3 * CHR_WID, SCORE_BOARD_Y - 1.5 * CHR_HGT);
   text_output(XS - 13 * CHR_WID, SCORE_BOARD_Y + 1.5 * CHR_HGT,
      stage == PRACTICE ? "  Practice   Car  Driver  Laps"
                        : "Race Length  Car  Driver  Laps");
   text_output(LOTIX, LOTIY, "track length      mi.");
   make_dec_string(string, length/5280.0);
   text_output(LOTIX + 11.5 * CHR_WID, LOTIY, string);
   // Show the name of the track:
   text_output(LOTIX, LOTIY + CHR_HGT, "track is");
   for(i=0; trackfile[i]; i++)
      if(trackfile[i] == '.' || trackfile[i] == 0)
         break;
      else
         string[i] = trackfile[i];
   string[i] = 0;
   text_output(LOTIX + 8 * CHR_WID, LOTIY + CHR_HGT, string);

   itoa(stage == PRACTICE ? practice : lap_count, string, 10);
   text_output(SCORE_BOARD_X-10*CHR_WID, SCORE_BOARD_Y, string);
   text_output(SCORE_BOARD_X-7*CHR_WID, SCORE_BOARD_Y, "laps");
   text_output(LDR_BRD_X - CHR_WID, LDR_BRD_Y, "LEADERS:   max   avg");
   text_output(LDR_BRD_X+11.5*CHR_WID, LDR_BRD_Y+CHR_HGT, "mph");
}

// update the leader board when necessary:
void leaders(int i, int* order)
{
   char string[] = "0123456789";
   double Y;

   Y = LDR_BRD_Y - spacing * (i + 1);

   // Erase old text:
   set_fill_color(FIELD_COLOR);             // The infield color
   rectangle(LDR_BRD_X, Y, LDR_BRD_X+19*CHR_WID, Y - CHR_HGT);

   set_color(TEXT_COLOR);
   text_output(LDR_BRD_X, Y, drivers[order[i]].rob_name);
   get_max_spd(order[i], string);                    // the maximum speed:
   text_output(LDR_BRD_X+7.5*CHR_WID, Y, string);
   get_avg_spd(order[i], string);                    // the average speed:
   text_output(LDR_BRD_X+13.5*CHR_WID, Y, string);
   drawcar(LDR_BRD_X-1.2*CARLEN, Y - CARWID/2, 0.0,
          drivers[order[i]].paint_job.nose, drivers[order[i]].paint_job.tail);
}

void instruments(int i)   // Instrument Panel for car i
{
   char out[16];
   static int alpha_up = 0;        // set when the descriptions are printed

   set_fill_color(FIELD_COLOR);             // The infield color
   rectangle(IP_X, IP_Y, IP_X + 21 * CHR_WID, IP_Y - CHR_HGT); // erase name

   if(i < 0 || i >= car_count)  {   // Do nothing if i is invalid.
      alpha_up = 0;
      return;
   }

   if(!alpha_up) {
      set_color(TEXT_COLOR);
      text_output(IP_X, IP_Y - CHR_HGT, "speedometer");
      text_output(IP_X, IP_Y - 2 * CHR_HGT, "true speed");
      text_output(IP_X, IP_Y - 3 * CHR_HGT, "lateral g's");
      text_output(IP_X, IP_Y - 4 * CHR_HGT, "in-line g's");
      text_output(IP_X, IP_Y - 5 * CHR_HGT, "skid angle");
      text_output(IP_X, IP_Y - 6 * CHR_HGT, "power, pct.");
      alpha_up = 1;
   }

   set_color(IP_NAME_COLOR);
   text_output(IP_X, IP_Y, drivers[i].rob_name);
   text_output(IP_X + (strlen(drivers[i].rob_name)-.5) * CHR_WID, IP_Y, "'s instruments:");

   set_color(IP_NUM_COLOR);
   rectangle(IP_X + 11 * CHR_WID, IP_Y - CHR_HGT,
             IP_X + 17 * CHR_WID, IP_Y - 2 * CHR_HGT);
   make_dec_string(out, pcar[i]->get_vc() * MPH_FPS);
   text_output(IP_X + 11 * CHR_WID, IP_Y - CHR_HGT, out);

   rectangle(IP_X + 11 * CHR_WID, IP_Y - 2 * CHR_HGT,
             IP_X + 17 * CHR_WID, IP_Y - 3 * CHR_HGT);
   make_dec_string(out, pcar[i]->get_speed() * MPH_FPS);
   text_output(IP_X + 11 * CHR_WID, IP_Y - 2 * CHR_HGT, out);

   rectangle(IP_X + 11 * CHR_WID, IP_Y - 3 * CHR_HGT,
             IP_X + 17 * CHR_WID, IP_Y - 4 * CHR_HGT);
   make_dec_string(out, pcar[i]->get_lat_acc());
   text_output(IP_X + 11 * CHR_WID, IP_Y - 3 * CHR_HGT, out);

   rectangle(IP_X + 11 * CHR_WID, IP_Y - 4 * CHR_HGT,
             IP_X + 17 * CHR_WID, IP_Y - 5 * CHR_HGT);
   make_dec_string(out, pcar[i]->get_lin_acc());
   text_output(IP_X + 11 * CHR_WID, IP_Y - 4 * CHR_HGT, out);

   rectangle(IP_X + 11 * CHR_WID, IP_Y - 5 * CHR_HGT,
             IP_X + 17 * CHR_WID, IP_Y - 6 * CHR_HGT);
   make_dec_string(out, pcar[i]->get_alpha() * DEGPRAD);
   text_output(IP_X + 11 * CHR_WID, IP_Y - 5 * CHR_HGT, out);

   rectangle(IP_X + 11 * CHR_WID, IP_Y - 6 * CHR_HGT,
             IP_X + 17 * CHR_WID, IP_Y - 7 * CHR_HGT);
   make_dec_string(out, pcar[i]->get_power() * 100.0);
   text_output(IP_X + 11 * CHR_WID, IP_Y - 6 * CHR_HGT, out);
}

/* new routine: draw_arc() routine that uses draw_line() to draw the arc */ 
#define LINESEG_LENGTH 07 /* this constant can be used to tweak the precision */
void draw_arc(double radius, double center_x, double center_y, double start_angle, double length)
{
	double a;
	double stepsize;
	double x1, y1, x2, y2;

	/* convert a right turn so it is consistent with the left turn */
	if (radius < 0.0)
	{
		radius = -radius;

		start_angle = start_angle - length - PI;
		while (start_angle < 0.0)
		{
			start_angle += (2 * PI);
		}
	}

	/* calculate the starting point */
	x1 = center_x + radius * sin(start_angle);
	y1 = center_y - radius * cos(start_angle);

	/* determine the step size */
	stepsize = LINESEG_LENGTH * (1.0 / (radius * SCALE));

	/* draw lines over the length from there, adapting the number of steps to the length */
	for (a = stepsize; a < length; a += stepsize)
	{
		/* calculate the end point of this line */
		x2 = center_x + radius * sin(start_angle + a);
		y2 = center_y - radius * cos(start_angle + a);

		/* draw the line */
		draw_line(x1, y1, x2, y2);

		/* make the end point the new starting point for the next line */
		x1 = x2;
		y1 = y2;
	}

	/* calculate the end point of the arc */
	x2 = center_x + radius * sin(start_angle + length);
	y2 = center_y - radius * cos(start_angle + length);

	/* draw the last line */
	draw_line(x1, y1, x2, y2);
}

