// ss4.c
// gcc -o ss4 ss4.c -lm -lgd

#define GIFOUT "ss4.gif"
#define FNT    "DejaVuMonoSans"
#define SCALE  0.09

#define XSIZE  800
#define YSIZE  600
#define ACC      8
#define MINUTE  60
#define HOUR  3600
#define TIME1  150
#define TSTART   0
#define TEND   300
#define TDELTA   5
#define ASTEP0 400
#define ASTEP1  25
#define LINETHICK 1
#define Z0    25.0
#define NSTARS 2000
#define MOVE   0.3

#include "gd.h"
#include <math.h>
#include <string.h>
#include <stdlib.h>

#define PI 3.1415926535897932
#define DEG2RAD(x) ((x)*PI/180.)

gdImagePtr im0, im1, im;
int        black, white, gray, dgray, trans ;
int        xgauge    = ( XSIZE + YSIZE ) / 2 ;     // down the right edge
double     scale     = SCALE * YSIZE ;
double     anglerad  = 0.0 ;
double     time      = 0.0 ;
double     angle_min = 0.0 ;
double     angle_max = ACC * TIME1 * TIME1 / ( MINUTE * MINUTE ) ;
double     min_vel   = 0.0 ;
double     max_vel   = ACC * TIME1 / MINUTE ;
double     min_acc   = -ACC ;
double     max_acc   = ACC ;
double     angle_vel = 0.0 ;
double     angle_acc = 0.0 ;
double     angle     = 0.0 ;

//--------------------------------------------------------------------
// fixed rectangle subroutines, swap order if needed
void RectE( gdImagePtr imm,
            double rx1, double ry1, double rx2, double ry2, int rcolor ) {
   if( rx1 < rx2 ) {
      if( ry1 < ry2 ) {
         gdImageRectangle( imm, rx1, ry1, rx2, ry2, rcolor );
      } else {
         gdImageRectangle( imm, rx1, ry2, rx2, ry1, rcolor );
      }
   } else {
      if( ry1 < ry2 ) {
         gdImageRectangle( imm, rx2, ry1, rx1, ry2, rcolor );
      } else {
         gdImageRectangle( imm, rx2, ry2, rx1, ry1, rcolor );
      }
   }
}

   
void RectF( gdImagePtr imm,
            double rx1, double ry1, double rx2, double ry2, int rcolor ) {
   if( rx1 < rx2 ) {
      if( ry1 < ry2 ) {
         gdImageFilledRectangle( imm, rx1, ry1, rx2, ry2, rcolor );
      } else {
         gdImageFilledRectangle( imm, rx1, ry2, rx2, ry1, rcolor );
      }
   } else {
      if( ry1 < ry2 ) {
         gdImageFilledRectangle( imm, rx2, ry1, rx1, ry2, rcolor );
      } else {
         gdImageFilledRectangle( imm, rx2, ry2, rx1, ry1, rcolor );
      }
   }
}

   
//--------------------------------------------------------------------
// rotation,  transformation, scaling --------

gdPoint sr( double inx, double iny ) {
   gdPoint retvar ;

   // rotate
   double  rx  = inx ;
   double  ry  = iny * cos( anglerad );
   double  rz  = iny * sin( anglerad );   // into the plane

   // depth transform
   double  trx = rx*(1.0+rz/Z0 )  ;
   double  try = ry*(1.0+rz/Z0) ;
   
   retvar.x = (int) ( 0.5*YSIZE + scale * trx ) ;
   retvar.y = (int) ( 0.5*YSIZE - scale * try ) ;

   return retvar;
}

// draw stars  ----------------------------------
// uses all globals

// star array, overlaps right edge, unscaled
double  starx[NSTARS], stary[NSTARS]; 

void
makestars() {
   int nstar ;
   for( nstar = 0 ; nstar < NSTARS ; nstar++ ) {
      starx[nstar] = drand48() * ( XSIZE + MOVE*TEND ) ;
      stary[nstar] = drand48() * YSIZE  ;
   }
}


void
drawstars() {
   int nstar, xs, ys ;
   for( nstar = 0 ; nstar < NSTARS ; nstar++ ) {
      xs = (int) ( starx[nstar] - MOVE*time )  ;
      ys = (int)   stary[nstar] ;
      if ( xs < YSIZE ) {
         gdImageSetPixel( im, xs, ys, white);
      }
   }
}

// draw gauges ---------------------------------
// uses globals

#define  YCLOCK    70
#define  YACC     180
#define  YVEL     310
#define  YANG     440

#define  RCLOCK    68
#define  SECHAND   60
#define  MINHAND1  40
#define  MINHAND2  45
#define  MINWID    40

int      gleft  = YSIZE + 40 ;
int      gright = XSIZE - 40 ;


int  gscale( double gvar, double gmin, double gmax ) {
    return ( (int) ( gleft + (gvar-gmin)
                           * ( (double)(gright-gleft))/(gmax-gmin) ) ) ;
}

void drawgauges()  {

   double wangle ;   // working angle
   double cx1, cy1, cx2, cy2 ;
   char   s[80];
   int    ic ;

   // clock at xgauge, 100 -----------------------------

   gdImageArc(  im, xgauge, YCLOCK, 2*RCLOCK, 2*RCLOCK, 0, 360, white );

   // clock / 60
   for( ic = 0;  ic < 60 ; ic += 1 ) {
      wangle  = PI * ic / 30 ;
      if ( ic%15 == 0 ) {
         cx1     = xgauge + (RCLOCK-6)*sin(wangle) ;
         cy1     = YCLOCK - (RCLOCK-6)*cos(wangle) ;
      }
      else if( ic%5 == 0 ) {
         cx1     = xgauge + (RCLOCK-4)*sin(wangle) ;
         cy1     = YCLOCK - (RCLOCK-4)*cos(wangle) ;
      }
      else {
         cx1     = xgauge + (RCLOCK-2)*sin(wangle) ;
         cy1     = YCLOCK - (RCLOCK-2)*cos(wangle) ;
      }
      cx2     = xgauge +  RCLOCK   *sin(wangle) ;
      cy2     = YCLOCK -  RCLOCK   *cos(wangle) ;
      gdImageLine( im, cx1, cy1, cx2, cy2, white ) ;
   }

   // second hand
   wangle  = 2.0 * PI * time / MINUTE ;
   cx1     = xgauge+SECHAND*sin(wangle) ;
   cy1     = YCLOCK-SECHAND*cos(wangle) ;
   gdImageLine( im, xgauge, YCLOCK, cx1, cy1, white ) ;


   // minute hand (two strokes )
   wangle  = 2.0 * PI * (time + MINWID) / HOUR ;
   cx1     = xgauge+MINHAND1*sin(wangle) ;
   cy1     = YCLOCK-MINHAND1*cos(wangle) ;

   wangle  = 2.0 * PI * (time         ) / HOUR ;
   cx2     = xgauge+MINHAND2*sin(wangle) ;
   cy2     = YCLOCK-MINHAND2*cos(wangle) ;
   gdImageLine( im, xgauge, YCLOCK, cx1, cy1, white ) ;
   gdImageLine( im,    cx2,    cy2, cx1, cy1, white );
   
   wangle  = 2.0 * PI * (time - MINWID) / HOUR ;
   cx1     = xgauge+MINHAND1*sin(wangle) ;
   cy1     = YCLOCK-MINHAND1*cos(wangle) ;
   gdImageLine( im,    cx2,    cy2, cx1, cy1, white );
   gdImageLine( im, xgauge, YCLOCK, cx1, cy1, white ) ;

   // text clock

   sprintf( s, "%3.0f secs", time );
   gdImageStringFT( im, NULL, white, FNT, 20, 0.0, xgauge-40, 180, s );

   // angular acceleration gauge ----------------

   gdImageStringFT( im, NULL, white, FNT, 20, 0.0,
                              YSIZE, YACC+40, "acceleration" );
   gdImageStringFT( im, NULL, white, FNT, 16, 0.0,
                              YSIZE, YACC+110, "degrees/min^2" );


   sprintf( s, "%2.0f", min_acc );
   gdImageStringFT( im, NULL, white, FNT, 20, 0.0, YSIZE,  YACC+80, s );

   sprintf( s, "%2.0f", max_acc );
   gdImageStringFT( im, NULL, white, FNT, 20, 0.0, gright+5, YACC+80, s );
   
   cx1 =  gscale( angle_acc, min_acc, max_acc ) ;
   cx2 =  gscale(         0, min_acc, max_acc ) ;
   RectE( im, gleft, YACC+50, gright, YACC+80, white );
   RectF( im,   cx1, YACC+50,    cx2, YACC+80, white );

   // angular velocity gauge ---------------------

   gdImageStringFT( im, NULL, white, FNT, 20, 0.0,
                              YSIZE, YVEL+40, "velocity" );
   gdImageStringFT( im, NULL, white, FNT, 16, 0.0,
                              YSIZE, YVEL+110, "degrees/min" );

   sprintf( s, "%2.0f", min_vel );
   gdImageStringFT( im, NULL, white, FNT, 20, 0.0, YSIZE,  YVEL+80, s );

   sprintf( s, "%2.0f", max_vel );
   gdImageStringFT( im, NULL, white, FNT, 20, 0.0, gright+5, YVEL+80, s );
   
   cx1 =  gscale( angle_vel, min_vel, max_vel ) ;
   cx2 =  gscale(         0, min_vel, max_vel ) ;
   RectE( im, gleft, YVEL+50, gright, YVEL+80, white );
   RectF( im,   cx1, YVEL+50,    cx2, YVEL+80, white );

   //  angle gauge -------------------------------

   gdImageStringFT( im, NULL, white, FNT, 20, 0.0,
                              YSIZE, YANG+40, "angle" );
   gdImageStringFT( im, NULL, white, FNT, 16, 0.0,
                              YSIZE, YANG+110, "degrees" );

   sprintf( s, "%2.0f", angle_min );
   gdImageStringFT( im, NULL, white, FNT, 20, 0.0, YSIZE,  YANG+80, s );

   sprintf( s, "%2.0f", angle_max );
   gdImageStringFT( im, NULL, white, FNT, 20, 0.0, gright+5, YANG+80, s );
   
   cx1 =  gscale(     angle, angle_min, angle_max ) ;
   cx2 =  gscale(         0, angle_min, angle_max ) ;
   RectE( im, gleft, YANG+50, gright, YANG+80, white );
   RectF( im,   cx1, YANG+50,    cx2, YANG+80, white );
}


//  draw circle ---------------------------------
// uses some globals
// fill inner circle with area factor "fill"
// draw outer circle with rull radius

#define     NPTS    100 
gdPoint     pointsf[NPTS];               // filled circle points
gdPoint     pointse[NPTS];               // edge   circle points

void drawcircle( int  color, double  fill,
                 double centerx, double centery, double radius ) {

    int     cntr ;
    double  dangle  = (2.0 * PI)/( NPTS-1 );
    double  sradius = radius*sqrt(fill) ;
    double  c, s;

    for( cntr=0 ; cntr < NPTS ; cntr++ ) {
       c = cos( dangle * cntr ) ;
       s = sin( dangle * cntr ) ;

       pointsf[cntr] = sr( centerx + sradius*c , centery + sradius*s ) ;
       pointse[cntr] = sr( centerx +  radius*c , centery +  radius*s ) ;
    }

    gdImageFilledPolygon(  im, pointsf, NPTS, color );
    gdImagePolygon(        im, pointse, NPTS, color );
}

//=====================================================================

int main () {
   FILE   *gifout;
   char   comment[80];
   double timem     = 0.0 ;
   double t1, t2, t3      ;
   int    astep     = ASTEP0 ;
   int    y               ;
   
   im1   = gdImageCreate (XSIZE, YSIZE);
   white = gdImageColorAllocate (im1, 255, 255, 255);
   black = gdImageColorAllocate (im1,   0,   0,   0);
   gray  = gdImageColorAllocate (im1, 127, 127, 127);
   dgray = gdImageColorAllocate (im1,  64,  64,  64);
   trans = gdImageColorAllocate (im1,   1,   1,   1);

   makestars();

   gifout = fopen ( GIFOUT , "wb");
   // gdImageGifAnimBegin( im1, gifout, 1, -1 ) ; // no repeat
   // gdImageGifAnimBegin( im1, gifout, 1,  4 ) ; // repeat 4 times
    gdImageGifAnimBegin( im1, gifout, 1,  0 ) ; // continuous repeat

   for( time = TSTART ; time <= TEND+0.1 ; time += TDELTA ) {

      //  accelerate between   0 seconds and 150 seconds 
      //  decelerate between 150 seconds and 300 seconds 
      if(   time <=   0.0 ) {
        angle_acc =  0.0 ;
        angle_vel =  0.0 ;
        angle     =  0.0 ;
        t1        =  0.5 ;
        t2        =  0.5 ;
        t3        =  0.5 ;
        astep     =  ASTEP0 ;
        strcpy( comment, "stopped" );
      }
      else if( time < 1.0*TIME1 ) {
        timem     = time/ MINUTE ;
        angle_acc =  ACC ;
        angle_vel =  ACC * timem ;
        angle     =  ACC * 0.5 * timem * timem ;
        t1        =  0.0 ;
        t2        =  0.0 ;
        t3        =  1.0 ;
        astep     =  ASTEP1 ;
        strcpy( comment, "accelerating" );
      }
      else if( time < 2.0*TIME1 ) {
        timem     = (2.0*TIME1-time)/ MINUTE ;
        angle_acc = -ACC ;
        angle_vel =  ACC * timem ;
        angle     =  angle_max - ( 0.5 * ACC * timem * timem );
        t1        =  1.0 ;
        t2        =  1.0 ;
        t3        =  0.0 ;
        astep     =  ASTEP1 ;
        strcpy( comment, "decelerating" );

      } else {
        angle_acc = 0.0 ;
        angle_vel = 0.0 ;
        angle     =  angle_max;
        t1        =  0.5 ;
        t2        =  0.5 ;
        t3        =  0.5 ;
        astep     =  ASTEP0 ;
        strcpy( comment, "stopped" );
      }

      anglerad    = DEG2RAD( angle );

      im   = gdImageCreate( XSIZE, YSIZE ) ;
      gdImagePaletteCopy (  im, im1 ) ;

      drawstars();                   // draw stars     
      drawgauges();                  // draw gauges

      // draw serversat -----------------------------------------------

      gdImageSetThickness(  im, LINETHICK );

      drawcircle( dgray, 1.0,  0.0000,  0.0000, 3.0000 );
      drawcircle( white,  t1, -3.4641,  2.0000, 1.0000 );
      drawcircle( white,  t2,  3.4641,  2.0000, 1.0000 );
      drawcircle( white,  t3,  0.0000, -4.0000, 1.0000 );

      gdImageStringFT( im, NULL, white, FNT, 50.0, 0.0, 10, 90, comment );
      
      // add frame to animation -------------------------------------

      gdImageGifAnimAdd  ( im, gifout, 0, 0, 2, astep,
                        gdDisposalRestoreBackground, im1 );

      im0 = im1 ;
      im1 = im  ;
      gdImageDestroy (im0);
  }

  gdImageGifAnimEnd(gifout);
  fclose (gifout);
  gdImageDestroy (im1);
  return 0;
}
