// ap01.c
// cc -o ap01 ap01.c -lgd -lpng -lm
// 
// Apogee skew   Keith Lofstrom  May 11, 2009
//
// Uses the libgd library.  For documentation see the file:
//     /usr/share/doc/gd-*/index.html  
// or the website:
//     http://www.libgd.org/Reference

#define  RMMKDIR    "rm -rf ap01dir ; mkdir ap01dir"
#define  PNGFMT     "ap01dir/a%04d.png"
#define  PNG2SWF    "png2swf -o ap01.swf -r 20 ap01dir/*.png" 
#define  XSIZE      1000
#define  YSIZE       600
#define  YCENTER     372
#define  SPACE        60
#define  SIZE         20
#define  NUM          25
#define  NUMX          5
#define  NPICS       200
#define  ESIZE        90
#define  ORBSIZE      90
#define  SSIZE        20
#define  ZFACTR      0.1
#define  LAYER       0.8
#define  PSIZE         6
#define  TOP         100
#define  FNT        "DejaVuMonoSans"
#define  FSZ          40

#include <gd.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

int  sun1, white, red, green, blue     ; //  color indexes 
int  dred, gray, trans, lgray, cyan    ;
int  dgray, yellow, magenta, black     ; 
int  dcyan                             ;
int  col00, col01, col02, col03, col04 ; 
int  col05, col06, col07, col08, col09 ;
int  col10, col11, col12, col13, col14 ;
int  col15, col16, col17, col18, col19 ; 
int  color[NUM];
double  xarr[NUM];
double  yarr[NUM];
double  zarr[NUM];
double  xdis[NUM];  // scaled and translated x
double  wdis[NUM];  // unscaled y
double  ydis[NUM];  // scaled and translated y
double  zdis[NUM];  // unscaled z
double  xcenter = XSIZE / 2 ;
double  ycenter = YCENTER ;
int     ex      = (int) (XSIZE-1.4*ORBSIZE) ;
int     ey      = (int) (      1.4*ORBSIZE) ;

double     pi2    = 6.283185307 ;
gdImagePtr im;                          // Declare the image
FILE       *pngout;                     // Declare output file

void  colorstart() {
   white   = gdImageColorAllocate (im, 255, 255, 255);
   black   = gdImageColorAllocate (im,   0,   0,   0);
   gray    = gdImageColorAllocate (im, 128, 128, 128);
   dgray   = gdImageColorAllocate (im,  96,  96,  96);
   lgray   = gdImageColorAllocate (im, 192, 192, 192);
   cyan    = gdImageColorAllocate (im,   0, 192, 192);
   dcyan   = gdImageColorAllocate (im,   0,  48,  48);
   red     = gdImageColorAllocate (im, 255,   0,   0);
   dred    = gdImageColorAllocate (im, 128,   0,   0);
   yellow  = gdImageColorAllocate (im, 255, 255,   0);
   green   = gdImageColorAllocate (im,   0, 255,   0);
   blue    = gdImageColorAllocate (im,   0,   0, 255);
   magenta = gdImageColorAllocate (im, 192,   0, 192);
   trans   = gdImageColorAllocate (im,   1,   1,   1);
   col01   = gdImageColorAllocate (im, 255, 192,   0);
   col02   = gdImageColorAllocate (im, 192, 255,   0);
   col03   = gdImageColorAllocate (im, 128, 255, 128);
   col04   = gdImageColorAllocate (im,   0, 255, 192);
   col05   = gdImageColorAllocate (im,   0, 192, 255);
   col06   = gdImageColorAllocate (im, 128, 128, 255);
   col07   = gdImageColorAllocate (im, 192,   0, 255);
   col08   = gdImageColorAllocate (im, 255,   0, 192);
   col09   = gdImageColorAllocate (im, 255, 128, 128);
}

// set up array positions 
void  astart() {
   color[ 0]=  yellow; zarr[ 0]= 0.0; yarr[ 0]= 0.0; xarr[ 0]= 0.0;
   color[ 1]=   col08; zarr[ 1]= 1.0; yarr[ 1]= 0.0; xarr[ 1]= 0.0;
   color[ 2]=    blue; zarr[ 2]= 1.0; yarr[ 2]= 1.0; xarr[ 2]= 0.0;
   color[ 3]=     red; zarr[ 3]= 0.0; yarr[ 3]= 1.0; xarr[ 3]= 0.0;
   color[ 4]=   green; zarr[ 4]=-1.0; yarr[ 4]= 1.0; xarr[ 4]= 0.0;
   color[ 5]=   col01; zarr[ 5]=-1.0; yarr[ 5]= 0.0; xarr[ 5]= 0.0;
   color[ 6]=   col02; zarr[ 6]=-1.0; yarr[ 6]=-1.0; xarr[ 6]= 0.0;
   color[ 7]=   col03; zarr[ 7]= 0.0; yarr[ 7]=-1.0; xarr[ 7]= 0.0;
   color[ 8]=   col04; zarr[ 8]= 1.0; yarr[ 8]=-1.0; xarr[ 8]= 0.0;
   color[ 9]=   col05; zarr[ 9]= 2.0; yarr[ 9]= 2.0; xarr[ 9]= 0.0;
   color[10]=   col06; zarr[10]= 2.0; yarr[10]= 1.0; xarr[10]= 0.0;
   color[11]=   col07; zarr[11]= 2.0; yarr[11]= 0.0; xarr[11]= 0.0;
   color[12]=   col08; zarr[12]= 2.0; yarr[12]=-1.0; xarr[12]= 0.0;
   color[13]=   col09; zarr[13]= 2.0; yarr[13]=-2.0; xarr[13]= 0.0;
   color[14]= magenta; zarr[14]= 1.0; yarr[14]=-2.0; xarr[14]= 0.0;
   color[15]=    cyan; zarr[15]= 0.0; yarr[15]=-2.0; xarr[15]= 0.0;
   color[16]=    gray; zarr[16]=-1.0; yarr[16]=-2.0; xarr[16]= 0.0;
   color[17]=   dgray; zarr[17]=-2.0; yarr[17]=-2.0; xarr[17]= 0.0;
   color[18]=   col01; zarr[18]=-2.0; yarr[18]=-1.0; xarr[18]= 0.0;
   color[19]=   col02; zarr[19]=-2.0; yarr[19]= 0.0; xarr[19]= 0.0;
   color[20]=   col03; zarr[20]=-2.0; yarr[20]= 1.0; xarr[20]= 0.0;
   color[21]=   white; zarr[21]=-2.0; yarr[21]= 2.0; xarr[21]= 0.0;
   color[22]=   col05; zarr[22]=-1.0; yarr[22]= 2.0; xarr[22]= 0.0;
   color[23]=   col06; zarr[23]= 0.0; yarr[23]= 2.0; xarr[23]= 0.0;
   color[24]=   col07; zarr[24]= 1.0; yarr[24]= 2.0; xarr[24]= 0.0;
}  

// draw arrowhead (either degrees or radians )
void arrowhead( double xp, double yp, double asize,
                double deg, double rad, int color ) {
   double angl = rad+pi2*deg/360.0 ;
   gdPoint arrow[3] ;

   arrow[0].x = (int)(xp);
   arrow[0].y = (int)(yp);
   arrow[1].x = (int)(xp+asize*(    -cos(angl)-0.5*sin(angl)));
   arrow[1].y = (int)(yp+asize*( 0.5*cos(angl)-    sin(angl)));
   arrow[2].x = (int)(xp+asize*(    -cos(angl)+0.5*sin(angl)));
   arrow[2].y = (int)(yp+asize*(-0.5*cos(angl)-    sin(angl)));
   gdImageFilledPolygon( im, arrow, 3, color );
}

// draw arrow 
void arrow( double x0, double y0, double x1, double y1, int color ) {
   double  asize = 0.02*(x1-x0) ;

   gdImageLine( im, (int)x0, (int)y0, (int)x1, (int)y1, color ) ;
   arrowhead( x1, y1, 0.02*(x1-x0), 0.0, 0.0, color ) ;
}


// draw earth and orbiting body and side arrow
int orbit = 2*ORBSIZE ;
void earth( double ang ) {
   double  x,  y  ;
   double  xs, ys ;
   double  deg= 360.0 * ang / pi2 ;
   gdPoint trap[4] ;
   gdImageFilledArc(im, ex, ey, ESIZE, ESIZE,   0, 360, dcyan, gdArc);
   gdImageFilledArc(im, ex, ey, ESIZE, ESIZE,  90, 270,  cyan, gdArc);
   gdImageArc(      im, ex, ey, orbit, orbit,  30, 330, white );
   gdImageArc(      im, ex, ey, orbit, orbit, 330,  30, dgray );
   
   x = -ORBSIZE - PSIZE  ;
   y = 3.0*PSIZE ;
   trap[0].x = ex+ (int) (x*cos(ang)+y*sin(ang));
   trap[0].y = ey+ (int) (y*cos(ang)-x*sin(ang));

   x = -ORBSIZE - PSIZE  ;
   y = 1.0*PSIZE ;
   trap[1].x = ex+(int) (x*cos(ang)+y*sin(ang));
   trap[1].y = ey+(int) (y*cos(ang)-x*sin(ang));

   x = -ORBSIZE + PSIZE  ;
   y = -3.0*PSIZE ;
   trap[2].x = ex+(int) (x*cos(ang)+y*sin(ang));
   trap[2].y = ey+(int) (y*cos(ang)-x*sin(ang));

   x = -ORBSIZE + PSIZE  ;
   y = -1.0*PSIZE ;
   trap[3].x = ex+(int) (x*cos(ang)+y*sin(ang));
   trap[3].y = ey+(int) (y*cos(ang)-x*sin(ang));

   if( (deg<210.0) && (deg>150.0) ) {
      gdImageFilledPolygon( im, trap, 4, dgray );
   } else {
      gdImageFilledPolygon( im, trap, 4, white );
   }

   // draw arrowhead to show side point of view

   x= -ORBSIZE - 2.0*PSIZE ;
   y=  0.0 ;
   xs = x*cos(ang)+y*sin(ang) + (double) ex ;
   ys = y*cos(ang)-x*sin(ang) + (double) ey ;
 
   arrowhead( xs, ys, 2*PSIZE,  0.0, -ang, white );
}

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

int main() {
   double  angle ;
   int     i     ;
   int     xcnt  ;
   int     frame ;
   char    pngfilename[64];

   double  test   ;   // depth testing
   int     ox, oy ;
   int     s0     ;

   system( RMMKDIR );

   for( frame = 0 ; frame < NPICS ; frame++ ) {
      im = gdImageCreateTrueColor(XSIZE, YSIZE );
      sprintf( pngfilename,  PNGFMT, frame );
      pngout = fopen( pngfilename, "wb");

      angle = frame * pi2 / NPICS ;
      colorstart() ;
      astart()     ;
      earth(angle) ;

      //  labels -----------------------------------------------------------

      gdImageStringFT( im, NULL,                       // imagespace, bounding 
                       white, FNT, FSZ,                // color, font, fontsize
                       0.0, 0.00*XSIZE, 0.98*YSIZE,    // angle, x, y
                       " Side View" );                 // the text

      gdImageStringFT( im, NULL,                       // imagespace, bounding
                       white, FNT, FSZ/2,              // color, font, fontsize
                       0.0, 0.00*XSIZE, 0.28*YSIZE,    // angle, x, y
		       "   Side View" );               // the text

      gdImageStringFT( im, NULL,                       // imagespace, bounding
                       white, FNT, FSZ/2,              // color, font, fontsize
                       0.0, 0.25*XSIZE, 0.28*YSIZE,    // angle, x, y
		       " Front View" );                // the text

      gdImageStringFT( im, NULL,                       // imagespace, bounding
                       white, FNT, FSZ/2,              // color, font, fontsize
                       0.0, 0.46*XSIZE, 0.28*YSIZE,    // angle, x, y
		       "   Top View" );                // the text

      //  time code -------------------------------------------------------
 
      double time0 = ( 4.0 * (double) frame ) / ( (double) NPICS ) ;
      char   timestring[64] ;

      sprintf( timestring, "%3.1f/4.0 hours, 1440x speedup", time0 );

      gdImageStringFT( im, NULL,                       // imagespace, bounding 
                       white, FNT, 0.6*FSZ,            // color, font, fontsize
                       0.0, XSIZE-14.0*FSZ,            // angle, x
                       0.98*YSIZE, timestring );       // y, the text 

      //  direction arrows ------------------------------------------------

      arrow( 0.00*XSIZE,      ycenter, 0.96*XSIZE,      ycenter, lgray );
      arrow( 0.01*XSIZE, 0.25*ycenter, 0.25*XSIZE, 0.25*ycenter, lgray );
      arrow( 0.46*XSIZE, 0.25*ycenter, 0.70*XSIZE, 0.25*ycenter, lgray );

      //  draw main view from side ----------------------------------------
      for( i=0 ; i < NUM ; i++ ) {
         zdis[i] = cos(angle)*zarr[i]+sin(angle)*yarr[i] ;
         wdis[i] = cos(angle)*yarr[i]-sin(angle)*zarr[i];
         ydis[i] = ycenter - SPACE*wdis[i];
         xdis[i] = xcenter + SPACE*(2.0*zdis[i]-0.5*NUMX) ;
      }


      for( test = -5.0*LAYER ; test < 5.0*LAYER ; test += LAYER ) {
         for( i=0 ; i < NUM ; i++ ) {
            for( xcnt = 0 ; xcnt < NUMX ; xcnt++ ) {
               if( ( zdis[i] >= test ) && ( zdis[i] < ( test+LAYER ) ) ) {

                  // main side view
                  s0 = (int) (   SIZE * (1.00 + ZFACTR * zdis[i] ) ) ;
	          ox = (int) (SPACE*xcnt + xdis[i] ) ;
                  oy = (int) ydis[i];
		  gdImageFilledArc(im, ox, oy, s0, s0, 0, 360, color[i], gdArc);

                  // small side view
                  s0 = (int) ( 0.25*SIZE * (1.00 + ZFACTR * zdis[i] ) ) ;
                  ox = (int) ( 0.01*XSIZE + 0.25*(SPACE*xcnt+xdis[i]) );
                  oy = (int) ( 0.25*ydis[i] );
                  gdImageFilledArc(im, ox, oy, s0, s0, 0, 360, color[i], gdArc);

               }

               if( ( wdis[i] >= test ) && ( wdis[i] < ( test+LAYER ) ) ) {
                  // small top view  
                  s0 = (int) ( 0.25*SIZE * (1.00 + ZFACTR * wdis[i] ) ) ;
                  ox = (int) ( 0.46*XSIZE + 0.25*(SPACE*xcnt+xdis[i]));
                  oy = (int) ( 0.25*(ycenter+SPACE*zdis[i]) ) ;
                  gdImageFilledArc(im, ox, oy, s0, s0, 0, 360, color[i], gdArc);
               }
            }

            // small front view - apogee to left, closer
            s0 = (int) ( 0.25*SIZE * (1.00 - ZFACTR * zdis[i] ) ) ;
            ox = (int) ( 0.35*XSIZE + 0.25*SPACE*zdis[i] ) ;
            oy = (int) ( 0.25*ydis[i] );
            gdImageFilledArc(im, ox, oy, s0, s0, 0, 360, color[i], gdArc);
         }
      }

      /* Output the frame in PNG format. */
      gdImagePngEx( im, pngout, 1 );
      gdImageDestroy(im);
      fclose(pngout);
   }

   system( PNG2SWF );
}
