// ss5a.c
// gcc -o ss5a ss5a.c -lgd -lpng -lm
//
//  The perspective sucks
//  The light pressure of tilted thrusters is treated here as constant. 
//  The actual light pressure is proportional to cos^2( sun angle )
//  This assumes a 4:3 ratio, with gauges down the right side

#define  RMMKDIR    "rm -rf ss5adir ; mkdir ss5adir"
#define  PNG2SWF    "png2swf -o ss5a.swf -r 10 ss5adir/*.png" 

// tried to antialias with imagemagick - broke swftools, sigh
//#define PNGFMT  "ss5atmp.png"
//#define CONVERT "convert ss5atmp.png -resize 800x600 ss5adir/a%04d.png"
//#define FSZ     40             // this scales the font and the whole drawing

#define PNGFMT  "ss5adir/a%04d.png"
#define CONVERT ""
#define FSZ     20             // this scales the font and the whole drawing

#define FNT    "DejaVuMonoSans"
#define SCALE  0.09            // size of serversat


#define Z0    24.0             // depth scaling for cheesy perspective
#define ACC     14             // acceleration in degrees/minute^2
#define TSTART   0
#define TIME1  120             // acceleration time in seconds
#define TEND   240
#define TDELTA 2.0             // display timestep
#define NSTARS 500             // background stars (this shows transparency)
#define MOVE   0.3             // how much the background stars move

#define LINETHICK 1
#define XSIZE   40*FSZ
#define YSIZE   30*FSZ
#define MINUTE  60
#define HOUR  3600

#include "gd.h"
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>


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

// #define WIRING 1              // show solar cell wires

gdImagePtr im;
int        black, white, gray, dgray, trans ;
int        xgauge    = ( XSIZE + YSIZE ) / 2 ;     // down the right edge
double     scale     = SCALE * YSIZE ;             // scale serversat in pixels

// starting values
double     anglerad  = 0.0 ;
double     time      = 0.0 ;
double     angle_vel = 0.0 ;
double     angle_acc = 0.0 ;
double     angle     = 0.0 ;

double     min_vel   = 0.0 ;
double     max_vel   = ACC * TIME1 / MINUTE ;
double     angle_min = 0.0 ;
double     angle_max = ACC * TIME1 * TIME1 / ( MINUTE * MINUTE ) ;
double     min_acc   = -ACC ;
double     max_acc   = ACC ;

//--------------------------------------------------------------------
// fixed rectangle subroutines, swap order if needed 
//  gdImageRectangle silently fails unless it gets left, bottom, right, top

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 --------
//
// This is very weak.  I would love to be able to make multiple
// rotations of a serversat along multiple axes.

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

   // rotate, IN ONE PLANE ONLY
   // z is into the plane, away from observer
   double  rx  = inx ;
   double  ry  = iny * cos( anglerad ) - inz * sin( anglerad ) ;
   double  rz  = iny * sin( anglerad ) + inz * cos( anglerad ) ;

   // depth transform - THIS IS PROBABLY INCORRECT
   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 random star field and move it with time  ----------------------------
// 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   (4*FSZ)
#define  YACC     (10*FSZ)
#define  YVEL     (16*FSZ)
#define  YANG     (22*FSZ)
#define  F2       (4*FSZ/5)
#define  F4       (FSZ/4)

#define  RCLOCK    (3*FSZ+8)
#define  SECHAND   (3*FSZ)
#define  MINHAND1  (2*FSZ)
#define  MINHAND2  (2*FSZ+5)
#define  MINWID    40

int      gleft  = YSIZE + 2*FSZ ;
int      gright = XSIZE - 2*FSZ ;


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

void drawgauges()  {

   double wangle ;   // working angle
   int    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 + (int)(0.5 + (RCLOCK-6)*sin(wangle)) ;
         cy1     = YCLOCK - (int)(0.5 + (RCLOCK-6)*cos(wangle)) ;
      }
      else if( ic%5 == 0 ) {
         cx1     = xgauge + (int)(0.5 + (RCLOCK-4)*sin(wangle)) ;
         cy1     = YCLOCK - (int)(0.5 + (RCLOCK-4)*cos(wangle)) ;
      }
      else {
         cx1     = xgauge + (int)(0.5 + (RCLOCK-2)*sin(wangle)) ;
         cy1     = YCLOCK - (int)(0.5 + (RCLOCK-2)*cos(wangle)) ;
      }
      cx2     = xgauge +  (int)(0.5 + RCLOCK   *sin(wangle) ) ;
      cy2     = YCLOCK -  (int)(0.5 + RCLOCK   *cos(wangle) ) ;
      gdImageLine( im, cx1, cy1, cx2, cy2, white ) ;
   }

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


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

   wangle  = 2.0 * PI * (time         ) / HOUR ;
   cx2     = xgauge+(int)(0.5 + MINHAND2*sin(wangle) ) ;
   cy2     = YCLOCK-(int)(0.5 + 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+(int)(0.5 + MINHAND1*sin(wangle) ) ;
   cy1     = YCLOCK-(int)(0.5 + 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, FSZ, 0.0, xgauge-2*FSZ, 9*FSZ, s );

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

   gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0,
                              YSIZE, YACC+2*FSZ-F4, "acceleration" );
   gdImageStringFT( im, NULL, white, FNT, F2, 0.0,
                              YSIZE, YACC+5*FSZ, "degrees/min^2" );


   sprintf( s, "%3.0f", min_acc );
   gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0, YSIZE-FSZ,  YACC+4*FSZ, s );

   sprintf( s, "%2.0f", max_acc );
   gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0, gright+F4, YACC+4*FSZ, s );
   
   cx1 =  gscale( angle_acc, min_acc, max_acc ) ;
   cx2 =  gscale(         0, min_acc, max_acc ) ;
   RectE( im, gleft, YACC+2*FSZ, gright, YACC+4*FSZ, white );
   RectF( im,   cx1, YACC+2*FSZ,    cx2, YACC+4*FSZ, white );

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

   gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0,
                              YSIZE, YVEL+2*FSZ-F4, "velocity" );
   gdImageStringFT( im, NULL, white, FNT, F2, 0.0,
                              YSIZE, YVEL+5*FSZ, "degrees/min" );

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

   sprintf( s, "%2.0f", max_vel );
   gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0, gright+F4, YVEL+4*FSZ, s );
   
   cx1 =  gscale( angle_vel, min_vel, max_vel ) ;
   cx2 =  gscale(         0, min_vel, max_vel ) ;
   RectE( im, gleft, YVEL+2*FSZ, gright, YVEL+4*FSZ, white );
   RectF( im,   cx1, YVEL+2*FSZ,    cx2, YVEL+4*FSZ, white );

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

   gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0,
                              YSIZE, YANG+2*FSZ-F4, "angle" );
   gdImageStringFT( im, NULL, white, FNT, F2, 0.0,
                              YSIZE, YANG+5*FSZ, "degrees" );

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

   sprintf( s, "%2.0f", angle_max );
   gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0, gright+F4, YANG+4*FSZ, s );
   
   cx1 =  gscale(     angle, angle_min, angle_max ) ;
   cx2 =  gscale(         0, angle_min, angle_max ) ;
   RectE( im, gleft, YANG+2*FSZ, gright, YANG+4*FSZ, white );
   RectF( im,   cx1, YANG+2*FSZ,    cx2, YANG+4*FSZ, white );
}


//  draw circle ---------------------------------
// uses some globals
// fill inner circle with area factor "fill"
// draw outer circle with full radius
// used for main solar cell, and thrusters

#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, 0.0 ) ;
      pointse[cntr] = sr( centerx +  radius*c , centery +  radius*s, 0.0 ) ;
   }

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

// ----------------------------------------------------------------------------
// perpendicular arrow
void arrow( int acolor, double acenterx, double acentery, double alength ) {
   gdPoint   pointsa[8];
   double    xwid  = 0.05*alength ;
   double    xhead = 0.15*alength ;
   double    zhead = 0.3*alength ;

   pointsa[0] = sr( acenterx,       acentery, alength       );
   pointsa[1] = sr( acenterx+xhead, acentery, alength-zhead );
   pointsa[2] = sr( acenterx+xwid,  acentery, alength-zhead );
   pointsa[3] = sr( acenterx+xwid,  acentery, 0.0           );
   pointsa[4] = sr( acenterx-xwid,  acentery, 0.0           );
   pointsa[5] = sr( acenterx-xwid,  acentery, alength-zhead );
   pointsa[6] = sr( acenterx-xhead, acentery, alength-zhead );
   pointsa[7] = sr( acenterx, acentery, alength );
   gdImageFilledPolygon(  im, pointsa, 8, acolor );
}

// ----------------------------------------------------------------------------
//  cell wiring stuff

//  draw scalable rectangle
void  RectS( double scolor, double sx1, double sy1, double sx2, double sy2) {
   gdPoint   pointss[5];               

   pointss[0] = sr( sx1, sy1, 0.0 );
   pointss[1] = sr( sx2, sy1, 0.0 );
   pointss[2] = sr( sx2, sy2, 0.0 );
   pointss[3] = sr( sx1, sy2, 0.0 );
   pointss[4] = sr( sx1, sy1, 0.0 );
   gdImageFilledPolygon(  im, pointss, 5, scolor );
}

//  draw cell wiring ---------------------------------
//  This draws a cheesy wiring grid on the front of the cell

void drawcellwire( int  wcolor, double  widthspace,
                    double centerx, double centery, double wsize ) {

   int    icnt ;
   int    nlines = (int) ( 0.6666666*wsize/widthspace-1.0 ) ;
   double vsize  = widthspace*( 1.5*nlines );

   // center bar
   RectS( wcolor,  centerx-widthspace, centery-vsize,  
                   centerx+widthspace, centery+vsize );

   // horizontal lines
   for( icnt=0 ; icnt<=nlines ; icnt++ ) {
     RectS(  wcolor,  -wsize+centerx, (3.0*icnt    )*widthspace+centery-vsize,
                       wsize+centerx, (3.0*icnt+1.0)*widthspace+centery-vsize );
   }
}

// this draws dots on the upper left edge, useful for scaling sizes in
// openoffice
void lineofdots() {
    int ctr;
    for( ctr=0; ctr < XSIZE; ctr +=4 ) {
       gdImageSetPixel( im, ctr, 0, gray );
       gdImageSetPixel( im, ctr, 1, gray );
       gdImageSetPixel( im, 0, ctr, gray );
       gdImageSetPixel( im, 1, ctr, gray );
    }
}

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

int main () {
   FILE   *pngout;
   char   comment[80]     ;
   char   dirname[80]     ;
   char   framename[80]   ;
   double timem           ;            // time in minutes
   double t1, t2, t3      ;            // thrust value for each cell
   int    frame           ;            // frame count
   int    npics           ;            // last frame count

   npics  = (int) ( (double)(TEND/TDELTA) +0.5 ) ;
   
   makestars();

   // make directory
   system( RMMKDIR );
 
   for(  frame = 0 ; frame <= npics ; frame++ ) {

      im    = gdImageCreateTrueColor(XSIZE, YSIZE);
      white = gdImageColorAllocate (im, 255, 255, 255);
      black = gdImageColorAllocate (im,   0,   0,   0);
      gray  = gdImageColorAllocate (im, 127, 127, 127);
      dgray = gdImageColorAllocate (im,  64,  64,  64);
      trans = gdImageColorAllocate (im,   1,   1,   1);
      
      time = TDELTA*( (double) frame ) ;

      //  start out stopped, display for a while
      if(   time <=   0.0 ) {
        angle_acc =  0.0 ;
        angle_vel =  0.0 ;
        angle     =  0.0 ;
        t1        =  0.5 ;
        t2        =  0.5 ;
        t3        =  0.5 ;
        strcpy( comment, "stopped" );
      }
      //  accelerate between   0 seconds and TIME1 seconds 
      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 ;
        strcpy( comment, "accelerating" );
      }
      //  decelerate between TIME1 seconds and 2*TIME1 seconds 
      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 ;
        strcpy( comment, "decelerating" );
      }
      //  end up stopped, display for a while
      else {
        angle_acc = 0.0 ;
        angle_vel = 0.0 ;
        angle     =  angle_max;
        t1        =  0.5 ;
        t2        =  0.5 ;
        t3        =  0.5 ;
        strcpy( comment, "stopped" );
      }

      anglerad    = DEG2RAD( angle );

      lineofdots();                  // calibration strips
      drawstars();                   // draw stars     
      drawgauges();                  // draw gauges

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

      gdImageSetThickness(  im, LINETHICK );

      drawcircle( gray,  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 );

#ifdef WIRING
      drawcellwire( gray, 0.10, 0.0000, 0.0000, 2.0000 );
#endif // WIRING

      // arrow and status text
      
      RectS( white, -4.5000, -0.0001, 4.5000, 0.0001 ) ;
      arrow( white, -4.0000, 0.0000, 2.0000 ) ;
      arrow( white,  4.0000, 0.0000, 2.0000 ) ;

      gdImageStringFT( im, NULL, white, FNT, 2*FSZ, 0.0, FSZ, 4*FSZ, comment );
      
      // output frame to directory in PNG format

      sprintf( framename, PNGFMT , frame );
      // sprintf( framename, PNGFMT );
      pngout = fopen ( framename , "wb");
      gdImagePngEx( im, pngout, 1 );
      gdImageDestroy (im);
      fclose (pngout);
      // sprintf( framename, CONVERT, frame );
      // system( framename );   // rescale image to antialias it
  }

  system( PNG2SWF );
}
