// 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();
}