/*	--------------------------------------------------------
	planet.c

	General Graphics Interface demo
	A simple rotating planet

	Frederic Stark, 20 Nov 1997
   -------------------------------------------------------- */

#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>

#ifndef min
#define min(a,b) ((a)<(b)?(a):(b))
#endif

/*
   Size of the in-memory picture
   Can be reduced for low-end machines, but resulting planet won't rotate much faster
*/

#define TX_XPOW		10
#define TX_YPOW		9

#define TX_WIDTH	(1<<TX_XPOW)
#define TX_HEIGHT	(1<<TX_YPOW)

unsigned char gTexture[TX_HEIGHT][TX_WIDTH];

#define TX_RANDOM_STEP	512

/*	Color indexes used for texture color generation */

#define LOW_TEXTURE     1	
#define HIGH_TEXTURE	255

/* texel at x,y */

inline int GetTexture( int x, int y )
{	return gTexture[y%TX_HEIGHT][x%TX_WIDTH];
}

/* Random number between aMin and aMax included */

int Random( int aMin, int aMax )
{	return (aMax==aMin)?aMin:aMin+rand()/(RAND_MAX/(aMax-aMin+1)+1);
}

/* Clamp a texel in admited values */

int ClampTexel( int aTexel )
{	if (aTexel<LOW_TEXTURE) return LOW_TEXTURE;
	if (aTexel>HIGH_TEXTURE) return HIGH_TEXTURE;
	return aTexel;
}

/* Generate a texel randomly interpolated from v0 and v1 */

int GenTex2( int v0, int v1, int decal )
{	int v = (v0+v1)/2;
	return Random( ClampTexel( v-decal ), ClampTexel( v+decal ) );
}

/* Generate a texel randomly interpolated from v0, v1, v2, v3 */

int GenTex4( int v0, int v1, int v2, int v3, int step )
{	return GenTex2( GenTex2( v0, v1, step ), GenTex2( v2, v3, step ), step );
}

/* Builds a random texture */

void BuildTexture()
{	int x, y, theStep, theHalfStep;
	int theRandomStep;
	int theMax = min(TX_XPOW,TX_YPOW);
	float theHarsh;

		/* If theRandomStep is low, many texture points are fixed */
	theRandomStep = 1<<Random(theMax-2, theMax);

		/* If theHarsh is high, data will be more chaotic */
	theHarsh = Random(20,650)/500.0;

		//	Build the static part
	for (x=0;x!=TX_WIDTH;x+=theRandomStep)
		for (y=0;y!=TX_HEIGHT;y+=theRandomStep)
			gTexture[y][x] = Random(LOW_TEXTURE, HIGH_TEXTURE);

		//	Build the random part
	theStep = theRandomStep;
	while (theStep>1)
	{	theHalfStep = theStep/2;
		for (x=0;x!=TX_WIDTH;x+=theStep)
			for (y=0;y!=TX_HEIGHT;y+=theStep)
			{	gTexture[y][x+theHalfStep] = GenTex2( GetTexture(x,y), GetTexture(x+theStep,y), theStep*theHarsh );
				gTexture[y+theHalfStep][x] = GenTex2( GetTexture(x,y), GetTexture(x,y+theStep), theStep*theHarsh );
			}
		for (x=0;x!=TX_WIDTH;x+=theStep)
			for (y=0;y!=TX_HEIGHT;y+=theStep)
				gTexture[y+theHalfStep][x+theHalfStep] = GenTex4( GetTexture(x,y), GetTexture(x+theStep,y), GetTexture(x,y+theStep), GetTexture(x+theStep,y+theStep), theStep*theHarsh );
		theStep/=2;
	}
}

int SCREEN_WIDTH;
int SCREEN_HEIGHT;

#define gHALF_LINES 	HALF_LINES

int HALF_LINES;

int *gYOffsets;
int *gXPixelCount;
int **gOffsets;
#define KPI	3.1415926535897932385

/* Builds the projection tables */

void BuildProjTables()
{	int i,j;

	gYOffsets = malloc( sizeof(int)*HALF_LINES );
	gXPixelCount = malloc( sizeof(int)*HALF_LINES );
	gOffsets = malloc( sizeof(int*)*HALF_LINES );

	for (i=0;i!=HALF_LINES;i++)
	{	gXPixelCount[i] = 0;
		gYOffsets[i]=0;
		if (gOffsets[i]) free(gOffsets[i]);
		gOffsets[i] = 0;
	}
	for (i=0;i!=gHALF_LINES;i++)
	{		//	Y offsets
		gYOffsets[i] = asin( i/(float)gHALF_LINES )/KPI*2*TX_HEIGHT/2;

			//	Pixel count
		gXPixelCount[i] = sqrt( gHALF_LINES*gHALF_LINES-i*i );

			//	Offsets
		gOffsets[i] = malloc( sizeof(int)*gXPixelCount[i] );
		for (j=0;j!=gXPixelCount[i];j++)
			gOffsets[i][j] = asin( j/(float)gXPixelCount[i] )/KPI*2*TX_WIDTH/4;
	}

}

#include <ggi/ggi.h>

ggi_visual_t disp;
const ggi_directbuffer *dbuf;
ggi_pixellinearbuffer *plb;
char *fb;
ggi_color clut[256];

/* Make a color */

ggi_color MakeColor( int r, int g, int b )
{	ggi_color theColor;
	theColor.r = r;
	theColor.g = g;
	theColor.b = b;
	return theColor;
}

/* Returns a random color */

ggi_color RandomColor( )
{	return MakeColor( Random( 0, 65535 ), Random( 0, 65535 ), Random( 0, 65535 ) );
}

/* Set a color ramp in global palette */

void SetColorRamp( int aStart, int aEnd, ggi_color aStartColor, ggi_color aEndColor )
{	int i;
    for (i=aStart;i<=aEnd;i++)
    {  float pos = (i-aStart)/(float)(aEnd-aStart);
       clut[i].r = aStartColor.r*(1-pos)+aEndColor.r*pos; 
       clut[i].g = aStartColor.g*(1-pos)+aEndColor.g*pos; 
       clut[i].b = aStartColor.b*(1-pos)+aEndColor.b*pos; 
    }
}

/* Set a black palette */

void BlackPalette()
{	int i;
	for (i=0;i!=256;i++)
		clut[i].r = clut[i].g = clut[i].b = 0;
}

/* TEST: Build a earth looking planet (dark-blue->blue->white)*/

void EarthColor()
{	int theWaterLimit = (LOW_TEXTURE+HIGH_TEXTURE)/2;
	theWaterLimit = ClampTexel( Random( theWaterLimit-32, theWaterLimit+32 ) );
	SetColorRamp( LOW_TEXTURE, theWaterLimit, MakeColor( 237*256, 100*256, 23*256 ), MakeColor( 216*256, 96*256, 82*256 ) );
	SetColorRamp( theWaterLimit, HIGH_TEXTURE, MakeColor( 216*256, 96*256, 82*256 ), MakeColor( 65535, 65535, 65535 ) );
}

/* Set a random palette */

#define MAX_COLORS	5
void RandomPalette()
{	int i;
	int theColorCount;
	int theIndexes[MAX_COLORS+1];
	int theSum=0;

		/* Choose how much color we want to have */
	theColorCount = Random( 2, MAX_COLORS );

		/* Choose indexes */
	theIndexes[0] = 0;
	for (i=0;i!=theColorCount;i++)
		theIndexes[i+1] = theIndexes[i]+Random( 1, 100 );

		/* Normalize in range LOW_TEXTURE, HIGH_TEXTURE */
	theSum = theIndexes[theColorCount];
	for (i=0;i!=theColorCount+1;i++)
		theIndexes[i] = theIndexes[i]/(float)theSum*(HIGH_TEXTURE-LOW_TEXTURE)+.5+LOW_TEXTURE;

		/* Build the ramp */
	clut[LOW_TEXTURE] = RandomColor();
	for (i=0;i!=theColorCount;i++)
		SetColorRamp( theIndexes[i], theIndexes[i+1], clut[theIndexes[i]], RandomColor() );

		/* Sometimes we choose the last color to match the first
			to get nice(?) looking rotating palettes */
	if (Random(0,1)==0)
		SetColorRamp( theIndexes[theColorCount-1], theIndexes[theColorCount], clut[theIndexes[theColorCount-1]], clut[LOW_TEXTURE] );
	
}

/* Set the needed graphic mode */

/* FIXME: if graph mode fails, we'll just core dump */

void SetupGraphics()
{
	int numbufs, i;
	int rc;

	ggiInit();
	disp = ggiOpen(NULL);
	//ggiSetFocus(disp);
	rc = ggiSetGraphMode( disp, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT, GT_8BIT );
	if (rc < 0) ggiPanic("setting up 8bit palette mode failed.\nTry GGI_DISPLAY=\"tile:0,0,640,400,palemu\" ./planet 640 400\n");

	fb = NULL;

	numbufs = ggiDBGetNumBuffers(disp);
	for(i = 0; i < numbufs; i++) {
		dbuf = ggiDBGetBuffer(disp, i);

		if(!(dbuf->type & GGI_DB_SIMPLE_PLB)) {
			/* We don't handle anything but simple pixel-linear buffers.
			 * Fall back to ggiPutBox() or something.
			 */
			continue;
		}

		//plb = ggiDBGetPLB(dbuf);
		fb = dbuf->write;
		break;
	}

	if (fb == NULL) ggiPanic("No linear directbuffer available.\n");
}

/* Set current palette */

void SetPalette()
{	ggiSetPalette(disp,0,256,clut);
}
	/* Current position of the planet */
int gOffset = 0;

	/* #### Set rotated palette */
void RotatePalette()
{	int theStart = gOffset%(HIGH_TEXTURE-LOW_TEXTURE+1);
	ggiSetPalette(disp,LOW_TEXTURE,HIGH_TEXTURE-LOW_TEXTURE+1-theStart,clut+LOW_TEXTURE+theStart);
	ggiSetPalette(disp,HIGH_TEXTURE-theStart+1,theStart,clut+LOW_TEXTURE);
}

	/* Draws the planet */
void Draw()
{ int x;
  int y;

	/* Enable this define to see the texture instead of the palette */
#if 0
	for (x=0;x!=SCREEN_WIDTH;x++)
		for (y=0;y!=SCREEN_HEIGHT;y++)
			if (x<TX_WIDTH && y<TX_HEIGHT)
				fb[x+y*SCREEN_WIDTH] = gTexture[y][x];
	return;
#endif

	/* Enable this define to get the slow version */
#if 0
	for (y=0;y!=gHALF_LINES;y++)
		for (x=0;x!=gXPixelCount[y];x++)
		{	fb[x+SCREEN_WIDTH/2+(y+SCREEN_HEIGHT/2)*SCREEN_WIDTH] = GetTexture( gOffsets[y][x]+gOffset, TX_HEIGHT/2+gYOffsets[y] );
			fb[1+SCREEN_WIDTH/2-x+(y+SCREEN_HEIGHT/2)*SCREEN_WIDTH] = GetTexture( TX_WIDTH+gOffset-gOffsets[y][x], TX_HEIGHT/2+gYOffsets[y] );
			fb[x+SCREEN_WIDTH/2+(SCREEN_HEIGHT/2-y)*SCREEN_WIDTH] = GetTexture( gOffsets[y][x]+gOffset, TX_HEIGHT/2-gYOffsets[y] );
			fb[1+SCREEN_WIDTH/2-x+(SCREEN_HEIGHT/2-y)*SCREEN_WIDTH] = GetTexture( TX_WIDTH+gOffset-gOffsets[y][x], TX_HEIGHT/2-gYOffsets[y] );
		}
#else
	for (y=0;y!=gHALF_LINES;y++)
	{	char *p;
		int count;
		int *inc;
		char *tex_line;
		int tex_ix;

		p = fb+(y+SCREEN_HEIGHT/2)*SCREEN_WIDTH+SCREEN_WIDTH/2;
		count = gXPixelCount[y];
		inc = gOffsets[y];
		tex_line = gTexture[TX_HEIGHT/2+gYOffsets[y]];
		tex_ix = gOffset;
		while (count--)
			*p++ = tex_line[(tex_ix+*inc++)%TX_WIDTH];
   
		p = fb+(y+SCREEN_HEIGHT/2)*SCREEN_WIDTH+SCREEN_WIDTH/2;
		count = gXPixelCount[y];
		inc = gOffsets[y];
		tex_line = gTexture[TX_HEIGHT/2+gYOffsets[y]];
		tex_ix = gOffset+TX_WIDTH;
		while (count--)
			*p-- = tex_line[(tex_ix-*inc++)%TX_WIDTH];

		p = fb+(SCREEN_HEIGHT/2-y)*SCREEN_WIDTH+SCREEN_WIDTH/2;
		count = gXPixelCount[y];
		inc = gOffsets[y];
		tex_line = gTexture[TX_HEIGHT/2-gYOffsets[y]];
		tex_ix = gOffset;
		while (count--)
			*p++ = tex_line[(tex_ix+*inc++)%TX_WIDTH];
   
		p = fb+(SCREEN_HEIGHT/2-y)*SCREEN_WIDTH+SCREEN_WIDTH/2;
		count = gXPixelCount[y];
		inc = gOffsets[y];
		tex_line = gTexture[TX_HEIGHT/2-gYOffsets[y]];
		tex_ix = gOffset+TX_WIDTH;
		while (count--)
				*p-- = tex_line[(tex_ix-*inc++)%TX_WIDTH];
	}
#endif
}

#include <unistd.h>  /* For getpid() */

int main( int argc, char **argv )
{	int c;
	int animFlag;
	int theSeed;

	if (argc < 3 || argc > 5) {
		fprintf( stderr, "%s WIDTH HEIGHT [RADIUS] [<number>]\n", argv[0] );
		fprintf( stderr, "  RADIUS have to be less or equal to the min of WIDTH and HEIGHT\n" );
		fprintf( stderr, "  NUMBER is a random seed\n" );
		fprintf( stderr, "During the demo: \n" );
		fprintf( stderr, "  'p': change palette\n" );
		fprintf( stderr, "  'a' to animate it\n" );
		fprintf( stderr, "   any other key to exit\n" );
		exit( EXIT_FAILURE );
	}

	SCREEN_WIDTH = atoi(argv[1]);
	SCREEN_HEIGHT = atoi(argv[2]);
	HALF_LINES = SCREEN_HEIGHT/2;

	if (argc>3)
		HALF_LINES = atoi(argv[3]);

	if (argc <= 4) {
		theSeed = getpid();
		printf( "SEED=%d\n", theSeed );
	} else {
		theSeed = atoi(argv[4]);
	}

	HALF_LINES = min( HALF_LINES, SCREEN_HEIGHT/2 );
	HALF_LINES = min( HALF_LINES, SCREEN_WIDTH/2 );

	srand(theSeed);

	animFlag = Random(0,1);

	SetupGraphics();
//	EarthColor();
	RandomPalette();
	SetPalette();
	BuildTexture();
	BuildProjTables();

	while(1)
	{	if (ggiKbhit(disp))
		switch (c = ggiGetc(disp))
		{	case 'p':
				RandomPalette();
				SetPalette();
				break;
			case 'a':
				animFlag = 1-animFlag;
				break;
			default:
				return EXIT_SUCCESS;
		}
		Draw();
		if (animFlag)
		RotatePalette();
		ggiFlush(disp);
		gOffset+=1;
	}

		/* Not reached */
	return EXIT_SUCCESS;
}

