#include <stdlib.h>
#include <pmode.h>
#include <iostream.h>
#include <math.h>
// Fixed point variables class
#include "d:\watcom\h\general\fixpt.h"
// Graphics Library
#include "d:\watcom\h\graphics\gfx386.h"

// These are the vertices that make up the warp grid
struct PARTICLE
{
	Fixpt x,y,xv,yv,xint,yint,xacc,yacc,c;
}offset[21][13];

// This make it quick to multiply by 320
int Mul320[200];

//This makes it fast to calculate the distance between points in the grid
Fixpt norms[3][3] = { 	Fixpt(0), Fixpt(16),         Fixpt(32),
			Fixpt(16),Fixpt(16*1.414214),Fixpt(16*2.236068),
			Fixpt(32),Fixpt(16*2.236068),Fixpt(32*1.414214)  };
// Images are stored here
char Texture1[64000];
char Texture2[64000];

void copymem(long src, long dest, long numchars);
#pragma aux copymem=\
"                  "\
"shr ecx,2         "\
"cld					 "\
"rep movsd         "\
"                  "\
parm caller [esi] [edi] [ecx]\
modify [esi edi ecx];


// Smooth an image pointed to by ptr1,  and stores the result in image pointed 
// to by ptr2
void smooth(long ptr1, long ptr2, long NumChars);
#pragma aux smooth=\
"                    "\
"                    "\
"	xor eax, eax      "\
"	xor edx, edx      "\
"top:                "\
"  xor edx, edx      "\
"  mov al, [esi+ecx] "\
"                    "\
"  add edx, eax      "\
"  mov al, [esi-1   +ecx] "\
"                    "\
"  add edx, eax      "\
"  mov al, [esi+320 +ecx] "\
"                    "\
"  add edx, eax      "\
"  mov al, [esi+1   +ecx] "\
"                    "\
"  add edx, eax      "\
"  mov al, [esi-320+ecx] "\
"                    "\
"  shr edx, 2        "\
"  mov al, [esi+ecx] "\
"                    "\
"  add edx, eax      "\
"  shr edx,1         "\
"NoOverflow:         "\
"  mov [edi+ecx], dl "\
"                    "\
"  dec ecx           "\
"  jnz top           "\
"                    "\
parm caller [esi] [edi] [ecx]\
modify [eax ebx ecx edx esi edi];

// warps a single block
inline void TextureBlock(int xo, int yo)
{

	fixpt VLDx, VRDx, HDx;
	fixpt VLDy, VRDy, HDy;

	fixpt TX1, TY1, TX2, TY2, tx, ty;
	int x, y, xi=(xo<<4), yi=(yo<<4);
	fixpt a,b,c,d;
	char *Tptr;

	Tptr = &(Texture1[xi + (yi*320)]);

	VLDx = (offset[xo]  [yo+1].xint - offset[xo]  [yo].xint) >> 4;
	VRDx = (offset[xo+1][yo+1].xint - offset[xo+1][yo].xint) >> 4;
	VLDy = (offset[xo]  [yo+1].yint - offset[xo]  [yo].yint) >> 4;
	VRDy = (offset[xo+1][yo+1].yint - offset[xo+1][yo].yint) >> 4;

	TX1  = offset[xo]  [yo].xint;
	TY1  = offset[xo]  [yo].yint;
	TX2  = offset[xo+1][yo].xint;
	TY2  = offset[xo+1][yo].yint;

	for (y=yi; y < (yi+16); y++)
	{
		HDx  = (TX2-TX1) >> 4;
		HDy  = ((TY2-TY1) >> 4);
		tx = TX1;
		ty = TY1;

		for (x=xi; x < (xi+16); x++)
		{
			*Tptr++ = Texture2[int(tx) + Mul320[int(ty)] ];
			tx += HDx;
			ty += HDy;
		}

		Tptr += (320-16);

		TX1 += VLDx;
		TY1 += VLDy;
		TX2 += VRDx;
		TY2 += VRDy;
	}
}


// Calls TextureBlock, warping the entire screen
void TextureWarp()
{
	fixpt yf;
	int x,y;

	yf = Fixpt(0);
	for (y=0; y<=11; y++)
	{
		for (x=0; x<=19; x++)
			TextureBlock(x, y);
	}
}

// Draws the colourful texture you see at the begining
void DrawTexture(int xs, int ys)
{

	fixpt VLDc, VRDc, HDc;

	fixpt C1, C2, c;
	int x, y, xi, yi;


	for (yi=0; yi<12; yi++)
		for (xi=0; xi<20; xi++)
		{

			VLDc = (offset[xi][yi+1].c-offset[xi][yi].c) / Fixpt(ys);
			VRDc = (offset[xi+1][yi+1].c-offset[xi+1][yi].c) / Fixpt(ys);

			C1 = offset[xi][yi].c;
			C2 = offset[xi+1][yi].c;


			for (y=yi*ys; y<(yi+1)*ys; y++)
			{
				HDc  = (C2-C1) / Fixpt(xs);
				c = C1;

				for (x=xi*xs; x<(xi+1)*xs; x++)
				{
					plot(x,y, char(int(c)));
					c += HDc;
				}

				C1 += VLDc;
				C2 += VRDc;
			}
		}
		copymem(long(&(screen[0])), long(&(Texture2[0])), 64000);
}

// Load the contents of a file into an array of characters
void LoadGFX(char FileName[], char ArrayPtr[], int xsize, int ysize)
{
	ifstream HFile;

	HFile.open(FileName, ios::in | ios::binary);
	if (!HFile)
		{
			setvidmode(0x03);
			cout << "let_flow.raw not found" << endl;
			exit(0);
		}

	HFile.read(&(ArrayPtr[0]), xsize*ysize);

	HFile.close();

}

void Initialise()
{
	int i, x, y;
	Fixpt t, t1, t2, t3, xf, yf;

	t  =Fixpt(0);
	t1 =Fixpt(0);
	t2 =Fixpt(0);
	t3 =Fixpt(0);

	InitSin();		// Initialise the sine/cos lookup table used to calculate
				// fixed point sines and cosines
	InitSQRT();		// Initialise the fixed point Square Root lookup table

	for (i=0; i<200; i++)
		Mul320[i] = i*320;

	// switch to mode 13h
	setvidmode(0x13);

	// setup garish palette
	for (i=0; i<64; i++)
	{
	  setpalette(i, i, 0, (63-i));
	  setpalette(i+64, (63-i), i, 0);
	  setpalette(i+128, 0, (63-i), i);
	  setpalette(i+192, i, 0, (63-i));
	}

	
	// Now initialise the positions of the vertices of the warp grid
	for (y=0; y<=12; y++)
	{
		yf = Fixpt(y);
		for (x=0; x<=20; x++)
		{
			xf = Fixpt(x);
			t +=Fixpt(0.081);
			t1+=Fixpt(0.083);
			t2+=Fixpt(0.085);
			t3+=Fixpt(0.087);
			if ((x==0) || (x==20) || (y==0) || (y==12))
			{
				offset[x][y].x  = Fixpt(x<<4);
				offset[x][y].y  = Fixpt(y<<4);
				offset[x][y].xv = 0;
			offset[x][y].yv = 0;
			}
			else
			{
				offset[x][y].x  = Fixpt(rand()-16384)/Fixpt(2000) + Fixpt(x<<4);
				offset[x][y].y  = Fixpt(rand()-16384)/Fixpt(2000) + Fixpt(y<<4);
				offset[x][y].xv = 0;
				offset[x][y].yv = 0;
			}
			offset[x][y].xint = offset[x][y].x;
			offset[x][y].yint = offset[x][y].y;

			offset[x][y].xacc = 0;
			offset[x][y].yacc = 0;
			offset[x][y].c    = abs(rand()) >> 7;
		}
	}
	DrawTexture(16, 16);
}

// Relax the grid
void Relax()
{
	int x,y,xi,yi,xh,xl,yh,yl;
	Fixpt xv,yv,Xspring,Yspring,ext,length,norm,scaler;

	// firstly calculate the forces on all points
	for (y=1; y<12; y++)
	{
		yh=y+2;
		yl=y-2;
		if (yl<0)	yl=0;
		if (yh>12)	yh=12;

		for (x=1; x<20; x++)
		{
			xh=x+2;
			xl=x-2;
			if (xl<0)	xl=0;
			if (xh>20)	xh=20;

			for (yi=yl; yi<=yh; yi++)
			{
				for (xi=xl; xi<=xh; xi++)
				{
					if ((xi != x) || (yi != y))
					{
						Xspring = offset[xi][yi].x - offset[x][y].x;
						Yspring = offset[xi][yi].y - offset[x][y].y;

						norm = norms[abs(xi-x)][abs(yi-y)];
						length = sqrt(Xspring*Xspring + Yspring*Yspring);
						scaler = (norm-length) * Fixpt(0.00002);

						Xspring *= scaler;
						Yspring *= scaler;

						offset[xi][yi].xv += Xspring;
						offset[xi][yi].yv += Yspring;
					}
				}
			}
		}
	}

	// now adjust the positions and velocities of the points
	for (y=1; y<12; y++)
		for (x=1; x<20; x++)
		{
			if (!((x==0) || (x==20) || (y==0) || (y==12)))
			{
				offset[x][y].x += offset[x][y].xv;
				offset[x][y].y += offset[x][y].yv;
				offset[x][y].xv *= Fixpt(.9999);
				offset[x][y].yv *= Fixpt(.9999);
			}

			offset[x][y].xint = trunc(offset[x][y].x);
			offset[x][y].yint = trunc(offset[x][y].y);


			// this bit allows it to handle non-integer values of points on the
			// grid
			offset[x][y].xacc += fracof2(offset[x][y].x);
			if (offset[x][y].xacc > Fixpt(1))
			{
				offset[x][y].xacc -= Fixpt(1);
				offset[x][y].xint += Fixpt(1);
			}
			if (offset[x][y].xacc < Fixpt(0))
			{
				offset[x][y].xacc += Fixpt(1);
				offset[x][y].xint -= Fixpt(1);
			}

			offset[x][y].yacc += fracof2(offset[x][y].y);
			if (offset[x][y].yacc > Fixpt(1))
			{
				offset[x][y].yacc -= Fixpt(1);
				offset[x][y].yint += Fixpt(1);
			}
			if (offset[x][y].yacc < Fixpt(0))
			{
				offset[x][y].yacc += Fixpt(1);
				offset[x][y].yint -= Fixpt(1);
			}

		}


	// Make sure points on the grid never stray outside the screen, causing a crash
	for (x=0; x<=20; x++)
	{
		if (offset[x][1].yint < Fixpt(0))
			offset[x][1].yint = Fixpt(0);
		if (offset[x][11].yint > Fixpt(199))
			offset[x][11].yint = Fixpt(199);
	}
	for (y=0; y<=12; y++)
	{
		if (offset[1][y].xint < Fixpt(0))
			offset[1][y].xint = Fixpt(0);
		if (offset[19][y].xint > Fixpt(319))
			offset[19][y].xint = Fixpt(319);
	}
}


void main()
{
	int i, up, down, s=4, x, y;
	Fixpt scaler;


	Initialise();
	setpalette(255,63,63,63);

	getch();

	while(!kbhit())
	{
		// relax the warp grid
		Relax();

		// warp the texture
		TextureWarp();

		// smooth the texture. except the top and bottom rows
		smooth(long(&(Texture1[320])), long(&(Texture2[320])),64000-640);

		// just copy the top and bottom rows
		copymem(long(&(Texture1[0])), long(&(Texture2[0])),320);
		copymem(long(&(Texture1[64000-320])), long(&(Texture2[64000-320])),320);

		// synchronise
		waitvsync();
		// update screen
		copymem(long(&(Texture1[0])), long(&(screen[0])), 64000);
	}

	// same as above but with fade out
	for (scaler=fixpt(1); scaler>fixpt(0); scaler-=fixpt(.05))
	{
		for (i=0; i<64; i++)
		{
			up   = int(fixpt(i)*scaler);
			down = int(fixpt(63-i)*scaler);

			setpalette(i,     up, 0, down);
			setpalette(i+64,  down, up, 0);
			setpalette(i+128, 0, down, up);
			setpalette(i+192, up, 0, down);
		}

		Relax();
		TextureWarp();
		smooth(long(&(Texture1[320])), long(&(Texture2[320])),64000-640);

		copymem(long(&(Texture1[0])), long(&(Texture2[0])),320);
		copymem(long(&(Texture1[64000-320])), long(&(Texture2[64000-320])),320);

		copymem(long(&(Texture1[0])), long(&(screen[0])), 64000);
	}

	getch();
	setvidmode(0x03);
	setpalette(7,0,0,0);
	cout <<endl<<endl<<endl<<endl<<endl<<endl<<endl<<endl<<endl<<endl<<endl<<endl << "              Hugo Elias: http://freespace.virgin.net/hugo.elias" <<endl<<endl<<endl<<endl<<endl<<endl<<endl<<endl<<endl<<endl<<endl;
	for (i=0; i<40; i+=2)
	{
		waitvsync();
		setpalette(7,i,i,i);
	}

}

