// echr11.c
// compile:  gcc -o echr11 echr11.c -lgd -lpng -lm
#define  NAME  "echr11"

// A NiOH2 WO3 electrochrome

#define RATE    6  
#define HVEL    12.0             // Ion velocity
#define ITILT   0.15

#define NPICS   100              // 
#define FNT    "DejaVuMonoSans"
#define FSZ     25               // this scales the font and the whole drawing
#define XSIZE   1024
#define YSIZE   768
#define WIDE    ( 2*FSZ)
#define TALL    (22*FSZ)
#define LTALL   (18*FSZ)
#define XCENT   (20*FSZ)
#define YCENT   (15*FSZ)
#define XCC     (XCENT)
#define XM4     (XCENT-4*WIDE)   // front surface front glass
#define XM3     (XCENT-3*WIDE)   // front of NIOH
#define XM2     (XCENT-2*WIDE)   // back of NIOH
#define XM1     (XCENT-1*WIDE)   // front of WO3
#define XP1     (XCENT+1*WIDE)   // back surface mirror
#define XL1     (XCENT+2*WIDE)   // label box left
#define XL4     (XSIZE-1*WIDE)   // label box right
#define XL2     ((XL1+XL4)/2 -9 )//
#define XL3     ((XL1+XL4)/2 +9 )//
#define YL1     (YCENT- 6*FSZ)   // label box 1 top
#define YL2     (YCENT- 3*FSZ)   // label box 1 bottom
#define YL3     (YCENT+ 3*FSZ)   // label box 2 top
#define YL4     (YCENT+ 6*FSZ)   // label box 2 bottom
#define XCCH    (XCC-HVEL)       // stopping zone for ions
#define XM3H    (XM3+HVEL)       // stopping zone for ions

#define  ASIZE  2 

#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.)

// ----------------------------------------------------
char     rmmkdir[200] ;
char     pngfmt[ 200] ;
char     png2swf[200] ;
int      rate = RATE          ; // swf frame rate
double   ffrac                ; // frame fraction
int      sign                 ; // sign of battery
int      active               ; // changing state

gdImagePtr im;

int      ytop= YCENT - TALL/2 ;
int      yltop=YCENT -LTALL/2 ;
int      ybot= YCENT + TALL/2 ;
int      ylbot=YCENT +LTALL/2 ;
int      deltay = (int)( ((double)ITILT)*((double)XCENT) ) ;
int      brown, dblue, lblue  ;
int      black, white, gray   ;
int      red,   lred          ;
int      green, lgreen, sun1  ;
int      backgr, dgray, trans ;
int      light[256]           ; // Yellow color range
int      light1               ;
double   pi2                  ; //  
double   nioh[1000][1000]     ; // NIOH2 cell state
double   wo3[1000][1000]      ; // WO3 cell state
int      hyd[1000]            ; // hydrogen atom position

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

//----------------------------------------
void atom_init() {
   int  x, y  ;
   for( y=ytop ; y<= ybot ; y += ASIZE ) {
      for( x = 0 ; x < WIDE ; x += ASIZE ) {
         nioh[x][y]=drand48() ;
         wo3[x][y] =drand48() ;
      }
      hyd[y] = XCC ;
}  }

//----------------------------------------
void frame_init() {
   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);
   red    = gdImageColorAllocate (im, 255,   0,   0);
   green  = gdImageColorAllocate (im,   0, 255,   0);
   lred   = gdImageColorAllocate (im, 255, 224, 224);
   lgreen = gdImageColorAllocate (im, 224, 255, 224);
   backgr = gdImageColorAllocate (im,   0,  51, 102);
   trans  = gdImageColorAllocate (im,   1,   1,   1);
   brown  = gdImageColorAllocate (im,  60,  40,   0);
   dblue  = gdImageColorAllocate (im,   0,   0, 100);
   lblue  = gdImageColorAllocate (im, 200, 210, 255);
}

//----------------------------------------
void light_init() {
   int i          ;
   int rx, gx, bx ;
   double di      ;

   for( i=0 ; i < 256 ; i++ ) {
      di        = (double) i                             ;
      int rx    = (double)(                         di ) ;
      int gx    = (double)(  51.0 + (204.0/256.0) * di ) ;
      int bx    = (double)( 102.0 - (102.0/256.0) * di ) ;

      // printf( "%3d%4d%4d%4d\n", i, rx, gx, bx );

      light[i] = gdImageColorAllocate( im, rx, gx, bx ) ;
   }
   sun1   = light[0] ;
   light1 = light[255] ;
}  

//----------------------------------------
void draw_background() {
   gdImageFilledRectangle( im, 0, 0, XSIZE-1, YSIZE-1, backgr );
}

//----------------------------------------
void draw_light() {

   int    light01  = light[ 127 ] ;

   // gamma is funny, so this corrects additive light
   int    light02  = light[ ((int)(127.0 * (1.0+0.3*sqrt(ffrac)))) ] ;
   int    light03  = light[ ((int)(127.0 *      ffrac ))     ] ;
   int    light04  = light[ ((int)(127.0 * (1.0-ffrac)))     ] ;

   gdPoint  alight[8] ;

   // incoming triangle
   alight[0].x = 0              ;
   alight[0].y = yltop - deltay ;
   alight[1].x = 0              ;
   alight[1].y = yltop + deltay ;
   alight[2].x = XCENT          ;
   alight[2].y = yltop          ;
   gdImageFilledPolygon( im , alight, 3 , light01 ) ;

   // reflected addition 
   alight[0].x = 0              ;
   alight[0].y = yltop + deltay ;
   alight[1].x = 0              ;
   alight[1].y = ylbot - deltay ;
   alight[2].x = XCENT          ;
   alight[2].y = ylbot          ;
   alight[3].x = XCENT          ;
   alight[3].y = yltop          ;
   gdImageFilledPolygon( im , alight, 4 , light02 ) ;

   // reflected triangle
   alight[0].x = 0              ;
   alight[0].y = ylbot + deltay ;
   alight[1].x = 0              ;
   alight[1].y = ylbot - deltay ;
   alight[2].x = XCENT          ;
   alight[2].y = ylbot          ;
   gdImageFilledPolygon( im , alight, 3 , light03 ) ;
}

//----------------------------------------
void draw_sun() {
   gdImageFilledRectangle( im, 0, yltop-deltay, 10, ylbot-deltay , light1 );
}

//----------------------------------------
void draw_atoms() {
   int  x, y ;
   double  hvd = -HVEL * (double) sign;
   for( y=ytop ; y< ybot ; y += ASIZE ) {
      
      if( (y/ASIZE)%3 != 0 ) {
	 x = hyd[y] + (int)( hvd * drand48() ) ;
	 if( ( x > XM3 ) && ( x < XCC  ) ) { hyd[y] = x ; }
         else                              { x = hyd[y] ; }
         if( active == 1 ) {
            gdImageFilledRectangle( im,  x, y,  x+ASIZE, y+ASIZE, lblue );
         }
     }  
      for( x = 0 ; x < WIDE ; x += ASIZE ) {
         if( (y/ASIZE)%3 == 0 ) {
            int xd = XM2 + x;
            gdImageFilledRectangle( im, xd, y, xd+ASIZE, y+ASIZE, gray );
         } 
         if( nioh[x][y] > ffrac ) {
            int xn = XM3 + x;
	    gdImageFilledRectangle( im, xn, y, xn+ASIZE, y+ASIZE, brown );
         }
         if( wo3[x][y]  > ffrac ) {
            int xw = XM1 + x;
            gdImageFilledRectangle( im, xw, y, xw+ASIZE, y+ASIZE, dblue );
}  }  }  }

//----------------------------------------
void draw_frame() {
 
   int ffx, fx1, fx2, ffy, fww, fw2, xcc ;
   int yb1, yb2, yb3, yb4, yb5      ;
   int xb0, xb1, xb2, xb3, xb4, xb5 ;
   int xc0, xc1, xc2, xc3, xc4, xc5 ;
   int yc1, yc2           ;

   ffx = (int)      FSZ   ;
   fx1 = (int) (0.7*FSZ)  ; 
   fx2 = (int) (0.5*FSZ)  ;
   ffy = (int) (0.7*FSZ)  ;
   fww = (int)    (WIDE)  ; 
   fw2 = (int)  (WIDE/2)  ; 

   // battery coordinates

   yb1 = ybot +     ffy   ;
   yb2 = ybot + 2 * ffy   ;
   yb3 = ybot + 3 * ffy   ;  // center
   yb4 = ybot + 4 * ffy   ;
   yb5 = ybot + 5 * ffy   ;
 
   xb0 = XCC  - 7*fw2     ;
   xb1 = XM2              ;
   xb2 = xb1  +       fx2 ;
   xb3 = xb2  +       fx2 ;
   xb4 = xb3  +       fx2 ;
   xb5 = XCC  + fw2       ;

   xc0 = XCC  - 5*fww     ;  
   xc1 = xc0  + ffx       ;
   xc2 = xc1  + ffx       ;
   xc3 = XCC  + 2*fww     ;
   xc4 = xc3  - ffx       ;
   xc5 = xc4  - ffx       ;
   yc1 = yb3  - ffx       ;
   yc2 = yb3  + ffx       ;

   gdImageSetThickness( im, 3 );

   gdImageRectangle( im,       XM3, ytop, XM2, ybot, brown ); // NIOH
   gdImageRectangle( im,       XM1, ytop, XCC, ybot, dblue ); // WO3
   gdImageRectangle( im,       XM4, ytop, XM3, ybot, white ); //front glass
   gdImageFilledRectangle( im, XCC, ytop, XP1, ybot, white ); //back reflector

   // battery
 
   gdImageLine(      im,     xb0, ybot,       xb0,  yb3, white );
   gdImageLine(      im,     xb0,  yb3,       xb1,  yb3, white );
   gdImageLine(      im,     xb1,  yb1,       xb1,  yb5, white );
   gdImageLine(      im,     xb2,  yb2,       xb2,  yb4, white );
   gdImageLine(      im,     xb3,  yb1,       xb3,  yb5, white );
   gdImageLine(      im,     xb4,  yb2,       xb4,  yb4, white );
   gdImageLine(      im,     xb5,  yb3,       xb4,  yb3, white );
   gdImageLine(      im,     xb5, ybot,       xb5,  yb3, white );

   // draw plusminus 

   gdImageSetThickness( im, 7 );

   if(      sign == -1 ) {
      gdImageLine(   im,     xc0, yb3,        xc2,  yb3, red   );
      gdImageLine(   im,     xc1, yc1,        xc1,  yc2, red   );
      gdImageLine(   im,     xc3, yb3,        xc5,  yb3, green );
   }
   else if( sign ==  1 ) {
      gdImageLine(   im,     xc0, yb3,        xc2,  yb3, green );
      gdImageLine(   im,     xc4, yc1,        xc4,  yc2, red   );
      gdImageLine(   im,     xc3, yb3,        xc5,  yb3, red   );
   }
}

// draw arrow  -------------------------------------------------------------

void arrow( int x0, int y0, int x1, int y1, int color ) {
   gdPoint ap[3] ;
   double asize = (double) FSZ ;
   double ang   = atan2( y0-y1 , x0-x1 );

   ap[0].x = x0;
   ap[0].y = y0;
   ap[1].x = x0+(int)( asize*(     -cos(ang) - 0.5*sin(ang) ));
   ap[1].y = y0+(int)( asize*(  0.5*cos(ang) -     sin(ang) ));
   ap[2].x = x0+(int)( asize*(     -cos(ang) + 0.5*sin(ang) ));
   ap[2].y = y0+(int)( asize*( -0.5*cos(ang) -     sin(ang) ));

   gdImageLine( im, x0, y0, x1, y1, color ) ;
   gdImageFilledPolygon( im, ap, 3, color ) ;
}

//----------------------------------------
void draw_labels() {

   // boxes with material labels
   gdImageFilledRectangle(im, XL1, YL1, XL2, YL2, brown ) ;// NIOH Label
   gdImageFilledRectangle(im, XL3, YL1, XL4, YL2, dblue ) ;// WO3  Label
   gdImageSetThickness(im, 5 );
   gdImageRectangle(im, XL1, YL3, XL2, YL4, brown ) ;// NIOH Label
   gdImageRectangle(im, XL3, YL3, XL4, YL4, dblue ) ;// WO3  Label
   gdImageStringFT( im, NULL,  white, FNT, FSZ, 0.0,
                    XL1, YL4-FSZ, " Ni(OH)₂     WO₃ " ) ;
   gdImageStringFT( im, NULL,  white, FNT, FSZ, 0.0,
                    XL1, YL2-FSZ, "  NIOOH     H+WO₃" ) ;

   int xl1 = (XL1+XL2)/2 ;
   int xl2 = (XL3+XL4)/2 ;
   int yl1 = YL2 + 20 ;
   int yl2 = YL3 - 20 ;
   int xh  = (XL2+XL3)/2 - 2*FSZ ;
   int yh  = (YL2+YL3)/2 - 4 ;

   int lpr1 = (int)(201.0-200.0*ffrac) ;
   int lpr2 = (int)(  1.0+250.0*ffrac) ;

   gdImageStringFT( im, NULL, light[lpr1], FNT, FSZ, 0.0,
            XL1-FSZ, YL1-3*FSZ, "4.6μPa Light Pressure" );
   gdImageStringFT( im, NULL, light[lpr2], FNT, FSZ, 0.0,
            XL1-FSZ, YL4+4*FSZ, "7.8μPa Light Pressure" );

   if(      sign == -1 ) {
      if( active == 1 ) {
         arrow( xl1, yl1, xl1, yl2,  white );
         arrow( xl2, yl1, xl2, yl2,  white );
         arrow( xl2-15, yl1, xl1+15, yl2,  lblue );
         gdImageStringFT( im, NULL, lblue, FNT, 1.5*FSZ, 0.0, xh, yh, "H+" );
      }
      gdImageStringFT( im, NULL, red  , FNT, FSZ, 0.0,
                       XL1, YL1-9 , "  Anode " );
      gdImageStringFT( im, NULL, green, FNT, FSZ, 0.0,
                       XL3, YL1-9 , " Cathode" );
   }
   else if( sign ==  1 ) {
      if( active == 1 ) {
         arrow( xl1, yl2, xl1, yl1,  white );
         arrow( xl2, yl2, xl2, yl1,  white );
         arrow( xl1+15, yl2, xl2-15, yl1,  lblue );
         gdImageStringFT( im, NULL, lblue, FNT, 1.5*FSZ, 0.0, xh, yh, "H+" );
      }
      gdImageStringFT( im, NULL, green, FNT, FSZ, 0.0,
                       XL1, YL4+FSZ+9 , " Cathode" );
      gdImageStringFT( im, NULL, red  , FNT, FSZ, 0.0,
                       XL3, YL4+FSZ+9 , "  Anode " );
   }

   // title
   gdImageStringFT( im, NULL,  white, FNT, 1.5*FSZ, 0.0,       // TITLE
                        (int)(2.0*FSZ), (int)(2.0*FSZ),
                       " Electrochromic Light Shutter" );
}
      
//=====================================================================

int main () {
   FILE   *pngout              ;
   char   comment[80]          ;
   char   dirname[80]          ;
   char   framename[80]        ;
   int    frame                ; // frame count
   int    npics                ; // last frame count
   int    i                    ; 
   pi2    = 8.0*atan(1.0)      ; // pi
   npics  = (double)NPICS      ;

   sprintf( rmmkdir, "rm -rf %sdir ; mkdir %sdir", NAME, NAME );

   system( rmmkdir )           ; // make directory
   atom_init()                 ; // set up atom points
 
   for( frame = 0 ; frame <= npics ; frame++ ) {

      // sinusoidal fraction variation
      double frang = pi2*((double)frame)/((double)npics );
      double offs  = acos(5.0/6.0) ;
   
      ffrac = 0.6*(0.8333-cos( frang ) );
      if(           ffrac < 0.0 )   { ffrac=0.0 ; active=0 ; }
      else if(      ffrac > 1.0 )   { ffrac=1.0 ; active=0 ; }
      else                          {             active=1 ; }

      if( sin( frang-offs ) > 0.0 ) { sign =  1 ; }
      else                          { sign = -1 ; }

      printf( "%04d/%04d\r", frame, npics );   fflush( stdout );

      frame_init()             ; // set up colors
      light_init()             ;

      draw_background()        ; // draw sunw background
      draw_light()             ; // draw light
      draw_sun()               ; // draw sun
      draw_atoms()             ; // draw atoms
      draw_frame()             ; // draw frame and battery
      draw_labels()            ; // draw title and reaction labels

      sprintf( framename, "%sdir/a%04d.png", NAME , frame );
      pngout = fopen ( framename , "wb");
      gdImagePngEx( im, pngout, 1 );
      gdImageDestroy (im);
      fclose (pngout);
   } // end of frame loop

   sprintf( png2swf, "png2swf -o %s.swf -r%3d %sdir/*.png", NAME, rate, NAME );
   system(  png2swf );
}  
