// Based on Amiga 500 'tequila' fractal blitter zoomer written by Mark Saarinen (Dweezil/Stellar) (released in August 1993)
// Ported to TkScript by bsp in December 2010 

use tksdl;
use tkopengl;

DweezilZoomer fx;

class SHI {
   // Sample&Hold interpolator
   byte cval, nval;
   int  cd;
   int  to;
   int  tmin, tmax;

   init(int _tmin, _tmax) {
      tmin = _tmin;
      tmax = _tmax;
   }

   get() {
      if(++cd > to)
      {
         cval = nval;
         cd   = 0;
         nval = rand(255);
         to   = rand(tmax) + tmin;
      }
      return cval + ( (nval-cval) * (float(cd)/to) );
   }
}

class DweezilZoomer {

   // - fx visible size is 320x252
   // - original framebuffer size is 384x316
   // - original version used 4 colors, w/ slow palette fades

   define int FBW = 400;
   define int FBH = 316;

   protected int framecount;

   protected IntArray fb_tex;
   protected IntArray fb_fbo;
   protected int      fb_idx; // index of current destination buffer

   protected SHI seed_h, seed_s, seed_v;

   protected int bltscr;
   protected int bltscrlast;
   protected int bltscrto;
   protected int bltscrcd;
   protected int bltscroff;

   static protected IntArray jitter_table = [ 
      $00, $10, $08, $18, $04, $14, $0c, $1c,
      $02, $12, $0a, $1a, $06, $16, $0e, $1e,
      $01, $11, $09, $19, $05, $15, $0d, $1d,
      $03, $13, $0b, $1b, $07, $17, $0f, $1f
                                              ];

   static protected IntArray param_sets = [ // 6* ax/ay pairs 
       $2ff,  $301, // zoom+rotate CCW
      -$301,  $2ff, // zoom+rotate CW
      -$001,  $300, // zoom
      -$300, -$001, // shrink+rotate
      -$001, -$300, // zoomx, shrinky
      -$002,  $600  // zoom fast
                                           ];
   protected int param_idx = 1; // 0..5

   protected boolean b_randscreen_pulse = true;

   protected boolean b_clearscreen_pulse;


   public method init() {
      seed_h.init(130, 200);
      seed_s.init(4, 4);
      seed_v.init(14, 120);
   }

   static protected method HSVTOARGB32(float h, s, v) : int {
      if(h >= 360)
         h -= 360;
      h /= 60;
      int i = h;
      float f = h - i;
      
      int p = 255 * v * ( 1 - s );
      int q = 255 * v * ( 1 - s * f );
      int t = 255 * v * ( 1 - s * ( 1 - f ) );

      v *= 255;
      
      switch(i)
      {
         case 0:
            return rgb(v, t, p);
         case 1:
            return rgb(q, v, p);
         case 2:
            return rgb(p, v, t);
         case 3:
            return rgb(p, q, v);
         case 4:
            return rgb(t, p, v);
         default:
            return rgb(v, p, q);
      }
   }

   protected method calcShifts() {
      bltscrlast = bltscr;
      bltscr = jitter_table[framecount & 31] + bltscroff;
      if(--bltscrcd <= 0)
      {
         bltscrto = rand(4)+1;
         bltscrcd = bltscrto;
         bltscroff = rand(2) - 1;
      }
   }

   protected method clearScreen() {
      glClearColor(0,0,0,1);
      glClear(GL_COLOR_BUFFER_BIT);
   }

   protected method randomizeScreen() {
      glBegin(GL_POINTS);
      int ray = 0;
      loop(FBH)
      {
         int rax = 0;
         loop(FBW)
         {
            glColor3f(rnd(1.0), rnd(1.0), rnd(1.0));
            glVertex2i(rax, ray);
            rax++;
         }
         ray++;
      }
      glEnd();
   }

   protected method noise() {

      // Randomize a couple of pixels in the center of the FB
      float h = seed_h.get() * (360.0/255);
      float s = 0.5 + 0.5 * seed_s.get() * (1.0/255);
      float v = 0.1 + 0.9 * seed_v.get() * (1.0/255);

      int t = rand(2)-1 + bltscr;

      glBegin(GL_POINTS);

      zglColorARGB(HSVTOARGB32(h, s, v));
      glVertex2i(209+t, 145+t);

      if(rnd(100)>31)
      {
         zglColorARGB(HSVTOARGB32((h+30), s, v));
         glVertex2i(209+1+t, 145+t);
      }

      if(rnd(100)>11)
      {
         zglColorARGB(HSVTOARGB32((h+60), s, v));
         glVertex2i(209+1+t, 145+1+t);
      }

      if(rnd(100)>15)
      {
         zglColorARGB(HSVTOARGB32((h+90), s, v));
         glVertex2i(209+t, 145+1+t);
      }

      glEnd();
   }

   protected method blits() {

      // Bind current source FB texture
      glBindTexture(GL_TEXTURE_RECTANGLE, fb_tex[fb_idx^1]);
      glEnable(GL_TEXTURE_RECTANGLE);

      // Calc. diagonal pixel shift ($300=#pixels per two-bitplane scanline)
      int jitter = (bltscrlast - bltscr) * $301 + 15;

      int ax = param_sets[2*param_idx +0]; // e.g. $FCFF (-$0301)
      int ay = param_sets[2*param_idx +1]; // e.g. $02FF

      int ax5 = ax * 5; // (note) last iax used will be -ax5

      int ay4 = ay * 4; // (note) last ay4 used will be -ay4

      int dy = 16;

      int a0 = 0x62c; // 16 scanlines + 352 pixels 

      int h = 64;

      // Render 11x9 quads
      zglColorARGB(~0);
      glBegin(GL_QUADS);
      compile loop(9)
      {
         int iax = ax5;

         int dx = 352;

         loop(11)
         {
            // (note) (a0/$60) == dy

            int off = (ay4 + iax + jitter); // pixel offset (2*384=0x300 pixels per scanline)

            int ash = (15 - (off & 15)); // SRCA pixel delay

            int a3 = a0 + (off >> 3);

            int y = (a3 / $60);
            int x = (((a3&0xfffe) - (y*$60)) * 8);  // (note) amiga blitter ignores LSB of BLTAPT

            int w = 48 - ash;

            // Copy rectangular area from current source framebuffer (BLIT)
            glTexCoord2f(x, FBH-y);
            glVertex2i(dx+ash, dy);
            
            glTexCoord2f(x+w, FBH-y);
            glVertex2i(dx+ash+w, dy);
            
            glTexCoord2f(x+w, FBH-(y+h));
            glVertex2i(dx+ash+w, dy+h);
            
            glTexCoord2f(x, FBH-(y+h));
            glVertex2i(dx+ash, dy+h);
           
            // Next blit
            iax -= ax;

            dx -= 32;
            a0 -= 4;

         } // x-loop moves destpos 352 pixels to the left (11*4*8)

         ay4 -= ay;

         dy += 32;
         a0 += $c2c; // down 32 scanlines, and 352 pixels to the right
      }

      glEnd();
      glDisable(GL_TEXTURE_RECTANGLE);
   }

   protected method display() {

      // Render to backbuffer
      glBindFramebuffer(GL_FRAMEBUFFER, 0);

      glViewport(0, 0, Viewport.width, Viewport.height);
      zglInit2D(320, 252);

      // Bind current destination framebuffer
      //  (becomes source for final display() blit)
      glBindTexture(GL_TEXTURE_RECTANGLE, fb_tex[fb_idx]);
      glEnable(GL_TEXTURE_RECTANGLE);

      int y = 32 + bltscr;
      int x = 48 + bltscr;

      // Blit/zoom current destination framebuffer
      zglColorARGB(~0);

      glBegin(GL_QUADS);

      glTexCoord2f(x, FBH-y);
      glVertex2i(0, 0);

      glTexCoord2f(x+320, FBH-y);
      glVertex2i(320, 0);

      glTexCoord2f(x+320, FBH-(y+252));
      glVertex2i(320, 252);

      glTexCoord2f(x, FBH-(y+252));
      glVertex2i(0, 252);

      glEnd();

      glDisable(GL_TEXTURE_RECTANGLE);
   }

   public method onDraw() {

      // Draw to current destination framebuffer
      glBindFramebuffer(GL_FRAMEBUFFER, fb_fbo[fb_idx]);
      glDrawBuffer(GL_COLOR_ATTACHMENT0);

      glViewport(0, 0, FBW, FBH);
      zglInit2D(FBW, FBH);

      calcShifts();

      if(b_randscreen_pulse)
      {
         b_randscreen_pulse = false;
         randomizeScreen();
      }
      else if(b_clearscreen_pulse)
      {
         b_clearscreen_pulse = false;

         clearScreen();
      }
      else
      {
         blits();

         noise();
      }

      framecount++;

      // Blit current destination FB to the screen
      display();

      // Swap source/destination buffers
      fb_idx ^= 1;

      if( !(framecount&127) )
         trace "FPS.real="+FPS.real;
   }

   public method onReopen() {

      // Create feedback framebuffer objects and textures
      for(int i=0; i<2; i++)
      {
         fb_tex[i] = zglGenTexture();
         glBindTexture(GL_TEXTURE_RECTANGLE, fb_tex[i]);
         glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
         glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
         glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA8, FBW, FBH, 0, GL_BGRA, GL_UNSIGNED_BYTE, null);

         // Create framebuffer and attach texture (renderbuffer) to it
         fb_fbo[i] = zglGenFramebuffer();
         glBindFramebuffer(GL_FRAMEBUFFER, fb_fbo[i]);
         glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, fb_tex[i], 0);
      }
   }

   public method onKey(Key _k) {
      switch(_k.pressed)
      {
         case 'p':
            param_idx = (param_idx + 1) % 6;
            trace "[...] param_idx is now "+param_idx;
            break;

         case 'r':
            trace "[...] randomizing screen pixels..";
            b_randscreen_pulse = true;
            break;

         case 'c':
            trace "[...] clearing screen pixels..";
            b_clearscreen_pulse = true;
            break;
      }
   }

}

function onDraw() {
   fx.onDraw();
}

function onKeyboard(Key _k) {
   switch(_k.pressed)
   {
      case VKEY_ESCAPE:
         SDL.exitEventLoop();
         break;

      default:
         fx.onKey(_k);
         break;
   }
}

function onReopen() {
   fx.onReopen();
}

function main() {
   Viewport.openWindow(320, 252);
   zglLoadExtensions();
   use callbacks;
   Viewport.swapInterval(1);

   fx.init();

   onReopen();
   SDL.eventLoop();
}