// ap02.c
// cc -o ap02 ap02.c -lgd -lpng -lm
// 
// Apogee skew   Keith Lofstrom  May 12, 2009
//
// FIXED ... apogee skew 
// at the semiminor points TOWARDS apogee
//
// 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 ap02dir ; mkdir ap02dir"
#define  PNGFMT     "ap02dir/a%04d.png"
#define  PNG2SWF    "png2swf -o ap02.swf -r 20 ap02dir/*.png" 
#define  XSIZE      1000
#define  YSIZE       600
#define  YCENTER     360
#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
#define  ASCALE      2.0

#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     carr[2*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) (1.5*ORBSIZE) ;
int     ey      = (int) (1.5*ORBSIZE) ;
int     re      = ESIZE / 2 ;

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

// define colors -----------------------------------------------------------
#define  CMAX      240
#define  CSTEP     30
#define  CCNT      5
void  colorstart() {
   int    cp, cq,  ix, ired, igreen, iblue ;
   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);

   for( cp=0 ; cp < CCNT ; cp++ ) {
      for( cq=0 ; cq < CCNT ; cq++ ) {
         ix     = CCNT*cp+cq ;
         ired   = CMAX - CSTEP*( cp + cq ) ;
         igreen = 2*CSTEP * cp ;
         iblue  = 2*CSTEP * cq ;
         carr[    ix] = gdImageColorAllocate(im, ired  , igreen  , iblue  );
         carr[NUM+ix] = gdImageColorAllocate(im, ired/2, igreen/2, iblue/2);
      }
   }
}

// set up array positions ---------------------------------------------------
void  astart() {
   int     ap, aq, ai ;
   double  n = (double) NUM ;
   double n2 = sqrt( n ) ;
   int    ni = (int) n2 ;
   double nm = 0.5*(n2-1.0) ;

   for( ap=0 ; ap < ni ; ap++ ) {
      for( aq=0 ; aq < ni ; aq++ ) {
         ai = ni * ap + aq ;
         zarr[ai] = ((double) ap ) - nm ;
         yarr[ai] = ((double) aq ) - nm ;
         xarr[ai] = 0.0                 ;
      }    
   }  
   carr[NUM-1]=white ;
}  

// 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 rotating earth and orbiting body and side arrow  -------------

void earth( double ang ) {
   double  x,  y  ;
   double  xs, ys ;
   int  x0, y0 ;
   int  x1, y1 ;
   double  an0 ;
   int i    ;
   int xcnt ;
   int orbit = 2*ORBSIZE ;

   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 );

   // draw longitude lines  ( 6 = 180/30 degrees )
   for( i=0 ; i < 6 ; i++ ) {
      an0 = (ang/6.0) + pi2*( (double) i ) / 12.0 ;
      x0  = ex - (int)( ((double)re) * cos(an0 ));
      x1  = ex + (int)( ((double)re) * cos(an0 ));
      y0  = ey + (int)( ((double)re) * sin(an0 ));
      y1  = ey - (int)( ((double)re) * sin(an0 ));
      gdImageLine( im, x0, y0, x1, y1, black );
   }

   // draw latitude lines
   for( i=1 ; i < 3 ; i++ ) {
      an0 = pi2*( (double) i ) / 12.0 ;
      x0  = (int)( 2.0 * ((double)re) * cos(an0) );
      gdImageArc( im, ex, ey, x0, x0, 0, 360, black );
   }
   
   // 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     ;

   double  xu, yu ;
   int     xi, yi ;
   int     icol   ;
   double  deg    ; 

   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.25*XSIZE, 0.27*YSIZE,    // angle, x, y
		       "    Top View" );               // the text

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

      gdImageStringFT( im, NULL,                       // imagespace, bounding
                       white, FNT, FSZ/2,              // color, font, fontsize
                       0.0, 0.71*XSIZE, 0.27*YSIZE,    // angle, x, y
		       "   Side 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.26*XSIZE, 0.25*ycenter, 0.50*XSIZE, 0.25*ycenter, lgray );
      arrow( 0.71*XSIZE, 0.25*ycenter, 0.95*XSIZE, 0.25*ycenter, lgray );

      //  draw main view from top ----------------------------------------
      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 ) ) ) {

                  // small top view
                  s0 = (int) ( 0.35*SIZE * (1.00 + ZFACTR * zdis[i] ) ) ;
                  ox = (int) ( 0.26*XSIZE + 0.25*(SPACE*xcnt+xdis[i]) );
                  oy = (int) ( 0.25*ydis[i] );
                  gdImageFilledArc(im, ox, oy, s0, s0, 0, 360, carr[i], gdArc);
   
                  // draw points in top orbit representation

                  xu = -ORBSIZE - ASCALE * wdis[i] ;
                  yu =  ASCALE * ( ((double)xcnt) + 2.0*zdis[i]-0.5*NUMX );

                  xi = ex + (int)( xu*cos(angle)+yu*sin(angle)) ;
                  yi = ey + (int)( yu*cos(angle)-xu*sin(angle)) ;

		  if(  ( xi <   ex      ) 
                     ||( yi < ( ey-re ) )
                     ||( yi > ( ey+re ) ) ) icol = carr[    i];
                  else                      icol = carr[NUM+i];

                  gdImageFilledArc( im, xi, yi, 3, 3, 0, 360, icol, gdArc );
               }

               if( ( wdis[i] >= test ) && ( wdis[i] < ( test+LAYER ) ) ) {
                  // main side view
                  s0 = (int) (      SIZE * (1.00 + ZFACTR * wdis[i] ) ) ;
                  ox = (int) ( SPACE*xcnt+xdis[i]);
                  oy = (int) ( ycenter-SPACE*zdis[i])  ;
                  gdImageFilledArc(im, ox, oy, s0, s0, 0, 360, carr[i], gdArc);

                  // small side view  
                  s0 = (int) ( 0.35*SIZE * (1.00 + ZFACTR * wdis[i] ) ) ;
                  ox = (int) ( 0.71*XSIZE + 0.25*(SPACE*xcnt+xdis[i]));
                  oy = (int) ( 0.25*(ycenter-SPACE*zdis[i]) ) ;
                  gdImageFilledArc(im, ox, oy, s0, s0, 0, 360, carr[i], gdArc);
               }
            }

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

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

   system( PNG2SWF );
}
