/* Bumps, Copyright (c) 2002 Shane Smit <CodeWeaver@DigitalLoom.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *
 * Module: "bumps.c"
 * Tab Size: 4
 *
 * Description:
 *  This is typical bump-mapping.  The actual bump map is generated by a screen
 *  grab.  The light source is represented by a spotlight of random color. This
 *  spotlight randomly traverses the bump map in a sinus pattern.
 *
 *  Essentially, it 3D-izes your desktop, based on color intensity.
 *
 * Modification History:
 *  [10/01/99] - Shane Smit: Creation
 *  [10/08/99] - Shane Smit: Port to C. (Ick)
 *  [03/08/02] - Shane Smit: New movement code.
 */


#include "bumps.h"


void CreateSpotLight( SSpotLight *pSpotLight, uint16_ iDiameter, uint16_ nColorCount )
{
	double nDelta;
	int16_ iHeight, iWidth;
	
	pSpotLight->nDiameter = iDiameter;
#ifdef VERBOSE
	printf( "%s: Light Diameter: %d\n", progclass, pSpotLight->nDiameter );
#endif

	pSpotLight->aLightMap = calloc( pSpotLight->nDiameter * pSpotLight->nDiameter, sizeof(uint8_) );
	memset( pSpotLight->aLightMap, 0, pSpotLight->nDiameter * pSpotLight->nDiameter );

	/* The falloff max values... 3/4 of the entire lightmap. */
	pSpotLight->nRadius = (uint16_)(pSpotLight->nDiameter / 2.5F);
	
	for( iHeight=-pSpotLight->nRadius; iHeight<pSpotLight->nRadius; iHeight++ )
		for( iWidth=-pSpotLight->nRadius; iWidth<pSpotLight->nRadius; iWidth++ )
		{
			nDelta = ( nColorCount * 2.5F ) - ( ( sqrt( pow( iWidth+0.5F, 2 ) + pow( iHeight+0.5F, 2 ) ) / pSpotLight->nRadius ) * ( nColorCount * 2.5F ) );
			nDelta += nColorCount;
			if( nDelta >= ( nColorCount * 2 ) ) nDelta = ( nColorCount * 2 ) - 1;
			if( nDelta >= nColorCount )
				pSpotLight->aLightMap[ ( ( iHeight + (pSpotLight->nDiameter/2) ) * pSpotLight->nDiameter ) + iWidth + (pSpotLight->nDiameter/2) ] = (uint8_)nDelta;
		}

	/* The actual lightmap... Use 1/4 to save time within the loop. */
	pSpotLight->nRadius = pSpotLight->nDiameter / 4;

	for( iHeight=-pSpotLight->nRadius; iHeight<pSpotLight->nRadius; iHeight++ )
		for( iWidth=-pSpotLight->nRadius; iWidth<pSpotLight->nRadius; iWidth++ )
		{
			nDelta = nColorCount - ( ( sqrt( pow( iWidth+0.5F, 2 ) + pow( iHeight+0.5F, 2 ) ) / pSpotLight->nRadius ) * ( nColorCount - 1 ) );
			if( nDelta >= 1 )
				pSpotLight->aLightMap[ ( ( iHeight + (pSpotLight->nDiameter/2) ) * pSpotLight->nDiameter ) + iWidth + (pSpotLight->nDiameter/2) ] = (uint8_)nDelta;
		}
		
	pSpotLight->nRadius = pSpotLight->nDiameter / 2;	/* Now set the radius back to what it should be. */
	pSpotLight->nAccelX = 0;
	pSpotLight->nAccelY = 0;
	pSpotLight->nVelocityX = ( RANDOM() % 2 ) ? pSpotLight->nVelocityMax : -pSpotLight->nVelocityMax;
	pSpotLight->nVelocityY = ( RANDOM() % 2 ) ? pSpotLight->nVelocityMax : -pSpotLight->nVelocityMax;
}


void CalcLightPos( SBumps *pBumps )
{
	SSpotLight *pSpotLight = &pBumps->SpotLight;
	float nGravity;

	/* X */
	if( pSpotLight->nXPos < pSpotLight->nDiameter * 1 )									nGravity = 1.0f;
	else if( pSpotLight->nXPos > pBumps->iWinWidth - ( pSpotLight->nDiameter * 1 ) )	nGravity = -1.0f;
	else																				nGravity = ( ( RANDOM() % 201 ) / 100.0f ) - 1.0f;
		
	pSpotLight->nAccelX += nGravity * ( pSpotLight->nAccelMax / 10.0f );
	if( pSpotLight->nAccelX < -pSpotLight->nAccelMax )		pSpotLight->nAccelX = -pSpotLight->nAccelMax;
	else if( pSpotLight->nAccelX > pSpotLight->nAccelMax )	pSpotLight->nAccelX = pSpotLight->nAccelMax;

	pSpotLight->nVelocityX += pSpotLight->nAccelX;
	if( pSpotLight->nVelocityX < -pSpotLight->nVelocityMax )		pSpotLight->nVelocityX = -pSpotLight->nVelocityMax;
	else if( pSpotLight->nVelocityX > pSpotLight->nVelocityMax )	pSpotLight->nVelocityX = pSpotLight->nVelocityMax;

	pSpotLight->nXPos += pSpotLight->nVelocityX;

	/* Y */
	if( pSpotLight->nYPos < pSpotLight->nDiameter * 1 )									nGravity = 1.0f;
	else if( pSpotLight->nYPos > pBumps->iWinHeight - ( pSpotLight->nDiameter * 1 ) )	nGravity = -1.0f;
	else																				nGravity = ( ( RANDOM() % 201 ) / 100.0f ) - 1.0f;
		
	pSpotLight->nAccelY += nGravity * ( pSpotLight->nAccelMax / 10.0f );
	if( pSpotLight->nAccelY < -pSpotLight->nAccelMax )		pSpotLight->nAccelY = -pSpotLight->nAccelMax;
	else if( pSpotLight->nAccelY > pSpotLight->nAccelMax )	pSpotLight->nAccelY = pSpotLight->nAccelMax;

	pSpotLight->nVelocityY += pSpotLight->nAccelY;
	if( pSpotLight->nVelocityY < -pSpotLight->nVelocityMax )		pSpotLight->nVelocityY = -pSpotLight->nVelocityMax;
	else if( pSpotLight->nVelocityY > pSpotLight->nVelocityMax )	pSpotLight->nVelocityY = pSpotLight->nVelocityMax;

	pSpotLight->nYPos += pSpotLight->nVelocityY;
}


void CreateBumps( SBumps *pBumps, Display *pNewDisplay, Window NewWin )
{
	XWindowAttributes XWinAttribs;
	XGCValues GCValues;
	int32_ nGCFlags;
	uint16_ iWidth, iHeight;
	uint16_ iDiameter;
	
	XGetWindowAttributes( pNewDisplay, NewWin, &XWinAttribs );
	pBumps->iWinWidth = XWinAttribs.width;
	pBumps->iWinHeight = XWinAttribs.height;
	pBumps->SpotLight.nXPos = XWinAttribs.width / 2.0f;
	pBumps->SpotLight.nYPos = XWinAttribs.height / 2.0f;
	pBumps->SpotLight.nVelocityMax = ( ( XWinAttribs.width < XWinAttribs.height ) ? XWinAttribs.width : XWinAttribs.height ) / 128.0f;
	pBumps->SpotLight.nAccelMax = pBumps->SpotLight.nVelocityMax / 10.0f;
	pBumps->pDisplay = pNewDisplay;
	pBumps->Win = NewWin;

	pBumps->pXImage = XCreateImage( pBumps->pDisplay, XWinAttribs.visual, XWinAttribs.depth, ZPixmap, 0, NULL,
		pBumps->iWinWidth, pBumps->iWinHeight, BitmapPad( pBumps->pDisplay ), 0 );
	pBumps->pXImage->data = calloc( pBumps->pXImage->bytes_per_line * pBumps->pXImage->height, sizeof(int8_) );

	GCValues.function = GXcopy;
	GCValues.subwindow_mode = IncludeInferiors;
	nGCFlags = GCForeground | GCFunction;
	if( use_subwindow_mode_p( XWinAttribs.screen, pBumps->Win ) ) /* See grabscreen.c */
		nGCFlags |= GCSubwindowMode;
	pBumps->GraphicsContext = XCreateGC( pBumps->pDisplay, pBumps->Win, nGCFlags, &GCValues );
	
	SetPalette( pBumps, &XWinAttribs );
	iDiameter = ( ( pBumps->iWinWidth < pBumps->iWinHeight ) ? pBumps->iWinWidth : pBumps->iWinHeight ) / 3;
	CreateSpotLight( &pBumps->SpotLight, iDiameter, pBumps->nColorCount );
	InitBumpMap( pBumps, &XWinAttribs );

	/* Clear the image. */
  if (pBumps->aXColors[ 0 ].pixel == 0)
    memset (pBumps->pXImage->data, 0,
            pBumps->pXImage->bytes_per_line * pBumps->pXImage->height);
  else
    for( iHeight=0; iHeight<pBumps->iWinHeight; iHeight++ )
      for( iWidth=0; iWidth<pBumps->iWinWidth; iWidth++ )
        XPutPixel( pBumps->pXImage, iWidth, iHeight,
                   pBumps->aXColors[ 0 ].pixel );
  XSetWindowBackground( pBumps->pDisplay, pBumps->Win,
                        pBumps->aXColors[ 0 ].pixel );
  XClearWindow (pBumps->pDisplay, pBumps->Win);
}


void SetPalette( SBumps *pBumps, XWindowAttributes *pXWinAttribs )
{
	XColor Color;
	char *sColor;			/* Spotlight Color */
	int16_ iColor;
	uint32_ *aPixels;
	
	sColor = get_string_resource( "color", "Color" );

	Color.red = RANDOM() % 0xFFFF; 
	Color.green = RANDOM() % 0xFFFF;
	Color.blue = RANDOM() % 0xFFFF;
	
	/* Make one color full intesity to avoid dark spotlights.	*/
	switch( RANDOM() % 3 )
	{
		case 0:	Color.red	= 0xFFFF;	break;
		case 1: Color.green	= 0xFFFF;	break;
		case 2: Color.blue	= 0xFFFF;	break;
	}

	if( strcasecmp( sColor, "random" ) && !XParseColor( pBumps->pDisplay, pXWinAttribs->colormap, sColor, &Color ) )
		fprintf( stderr, "%s: color %s not found in database. Choosing random...\n", progname, sColor );

#ifdef VERBOSE
	printf( "%s: Spotlight color is <%d,%d,%d> RGB.\n", progclass, Color.red, Color.green, Color.blue );
#endif  /*  VERBOSE */

	pBumps->nColorCount = get_integer_resource( "colorcount", "Integer" );
	if( pBumps->nColorCount < 2 )	pBumps->nColorCount = 2;
	if( pBumps->nColorCount > 128 )	pBumps->nColorCount = 128;

	pBumps->aXColors = calloc( pBumps->nColorCount, sizeof(XColor ) );
	        aPixels  = calloc( pBumps->nColorCount, sizeof(uint32_) );

	/* Creates a phong shade:                 / SpotColor  \                               Index/ColorCount 
	 *							PhongShade = | ------------ | Index + ( 65535 - SpotColor )^ 
	 *										  \ ColorCount /												*/
	pBumps->nColorCount--;
	for( iColor=0; iColor<=pBumps->nColorCount; iColor++ )
	{
		pBumps->aXColors[ iColor ].red   = (uint16_)( ( ( Color.red   / (double)pBumps->nColorCount ) * iColor ) + pow( 0xFFFF - Color.red,   iColor/(double)pBumps->nColorCount ) );
		pBumps->aXColors[ iColor ].green = (uint16_)( ( ( Color.green / (double)pBumps->nColorCount ) * iColor ) + pow( 0xFFFF - Color.green, iColor/(double)pBumps->nColorCount ) );
		pBumps->aXColors[ iColor ].blue  = (uint16_)( ( ( Color.blue  / (double)pBumps->nColorCount ) * iColor ) + pow( 0xFFFF - Color.blue,  iColor/(double)pBumps->nColorCount ) );

		if( !XAllocColor( pBumps->pDisplay, pXWinAttribs->colormap, &pBumps->aXColors[ iColor ] ) )
		{
			XFreeColors( pBumps->pDisplay, pXWinAttribs->colormap, aPixels, iColor, 0 );
			free( pBumps->aXColors );
			free(         aPixels );
			pBumps->nColorCount--;
			pBumps->aXColors = calloc( pBumps->nColorCount, sizeof(XColor) );
	                aPixels  = calloc( pBumps->nColorCount, sizeof(uint32_) );
			iColor = -1;
		}
		else
			aPixels[ iColor ] = pBumps->aXColors[ iColor ].pixel;
	}
	pBumps->nColorCount++;

#ifdef VERBOSE
	printf( "%s: Allocated %d colors.\n", progclass, pBumps->nColorCount );
#endif  /*  VERBOSE */

	XSetWindowBackground( pBumps->pDisplay, pBumps->Win, pBumps->aXColors[ 0 ].pixel );
}


void InitBumpMap( SBumps *pBumps, XWindowAttributes *pXWinAttribs )
{
	XImage *pScreenImage;
	XColor *aColors;
	uint8_ nSoften;
	uint16_ iWidth, iHeight;
	BOOL bInvert = (BOOL)get_boolean_resource( "invert", "Boolean" );
	
	aColors = (XColor*)calloc( pBumps->iWinWidth, sizeof(XColor) );
	grab_screen_image( pXWinAttribs->screen, pBumps->Win );
	pScreenImage = XGetImage( pBumps->pDisplay, pBumps->Win, 0, 0, pBumps->iWinWidth, pBumps->iWinHeight, ~0L, ZPixmap );

        /* jwz: get the grabbed bits off the screen fast */
        XClearWindow (pBumps->pDisplay, pBumps->Win);
        XSync (pBumps->pDisplay, 0);

	pBumps->aBumpMap = calloc( pBumps->iWinWidth * pBumps->iWinHeight, sizeof(uint16_) );
	for( iHeight=0; iHeight<pBumps->iWinHeight; iHeight++ )
	{
		for( iWidth=0; iWidth<pBumps->iWinWidth; iWidth++ )
			aColors[ iWidth ].pixel = XGetPixel( pScreenImage, iWidth, iHeight );

		XQueryColors( pBumps->pDisplay, pXWinAttribs->colormap, aColors, pBumps->iWinWidth );
	
		if( bInvert )
			for( iWidth=0; iWidth<pBumps->iWinWidth; iWidth++ )
				pBumps->aBumpMap[ ( iHeight * pBumps->iWinWidth ) + iWidth ] = (uint16_)
					( ( aColors[ iWidth ].red + aColors[ iWidth ].green + aColors[ iWidth ].blue ) / ( 0x2FFFD / (double)pBumps->SpotLight.nDiameter ) );
		else
			for( iWidth=0; iWidth<pBumps->iWinWidth; iWidth++ )
				pBumps->aBumpMap[ ( iHeight * pBumps->iWinWidth ) + iWidth ] = (uint16_)
					( pBumps->SpotLight.nDiameter - ( ( aColors[ iWidth ].red + aColors[ iWidth ].green + aColors[ iWidth ].blue ) / ( 0x2FFFD / (double)pBumps->SpotLight.nDiameter ) ) );
	}

	XDestroyImage( pScreenImage );

	nSoften = get_integer_resource( "soften", "Integer" );
#ifdef VERBOSE
	if( nSoften )	printf( "%s: Softening Bump Map %d time(s)...\n", progclass, nSoften );
#endif
	while( nSoften-- )
		SoftenBumpMap( pBumps );
	free( aColors );
}


void SoftenBumpMap( SBumps *pBumps )
{
	uint16_ *pOffset;
	uint16_ nHeight;
	uint16_ iWidth, iHeight;
	uint16_ *pTempBuffer = calloc( pBumps->iWinWidth * pBumps->iWinHeight, sizeof(uint16_) );

	for( iHeight=1; iHeight<pBumps->iWinHeight-1; iHeight++ )
	{
		pOffset = pBumps->aBumpMap + ( iHeight * pBumps->iWinWidth );
		for( iWidth=1; iWidth<pBumps->iWinWidth-1; iWidth++ )
		{	
			nHeight = 0;
			nHeight += pOffset[ iWidth ];
			nHeight += pOffset[ iWidth - pBumps->iWinWidth ];
			nHeight += pOffset[ iWidth + 1 ];
			nHeight += pOffset[ iWidth + pBumps->iWinWidth ];
			nHeight += pOffset[ iWidth - 1 ];
			nHeight /= 5;
			pTempBuffer[ ( iHeight * pBumps->iWinWidth ) + iWidth ] = nHeight;
		}
	}						
	
	memcpy( pBumps->aBumpMap, pTempBuffer, pBumps->iWinWidth * pBumps->iWinHeight * 2 );
	free( pTempBuffer );
}


void Execute( SBumps *pBumps )
{
	uint16_ nLightXPos, nLightYPos;
	uint16_ iWidth, iHeight;
	uint16_ iLightWidth, iLightHeight;
	uint16_ *pBOffset;
	uint8_ *pLOffset;
	int16_ nX, nY;
	uint16_ nColor;
	CalcLightPos( pBumps );
	
	/* Offset to upper left hand corner. */
	nLightXPos = pBumps->SpotLight.nXPos - pBumps->SpotLight.nRadius;
	nLightYPos = pBumps->SpotLight.nYPos - pBumps->SpotLight.nRadius;

	for( iHeight=nLightYPos, iLightHeight=0; iLightHeight<pBumps->SpotLight.nDiameter; iHeight++, iLightHeight++ )
	{
		pBOffset = pBumps->aBumpMap + ( iHeight * pBumps->iWinWidth );
		pLOffset = pBumps->SpotLight.aLightMap + ( iLightHeight * pBumps->SpotLight.nDiameter );
		for( iWidth=nLightXPos, iLightWidth=0; iLightWidth<pBumps->SpotLight.nDiameter; iWidth++, iLightWidth++ )
		{
			if( pLOffset[ iLightWidth ] )
			{				
				nX = pBOffset[ iWidth + 1                 ] - pBOffset[ iWidth ] + iLightWidth;
				nY = pBOffset[ iWidth + pBumps->iWinWidth ] - pBOffset[ iWidth ] + iLightHeight;

				if( nX < 0 )					nX = 0;
				else if( nX >= pBumps->SpotLight.nDiameter )	nX = pBumps->SpotLight.nDiameter - 1;

				if( nY < 0 )					nY = 0;
				else if( nY >= pBumps->SpotLight.nDiameter )	nY = pBumps->SpotLight.nDiameter - 1;

				nColor = pBumps->SpotLight.aLightMap[ ( nY * pBumps->SpotLight.nDiameter ) + nX ];
				if( nColor >= pBumps->nColorCount )
					nColor = 1;

				if( pLOffset[ iLightWidth ] >= pBumps->nColorCount )
					if( nColor > pLOffset[ iLightWidth ] - pBumps->nColorCount )
						nColor = pLOffset[ iLightWidth ] - pBumps->nColorCount;
						
				XPutPixel( pBumps->pXImage, iWidth, iHeight, pBumps->aXColors[ nColor ].pixel );
			}
			else
				XPutPixel( pBumps->pXImage, iWidth, iHeight, pBumps->aXColors[ 0 ].pixel );
		}
	}	

	XPutImage( pBumps->pDisplay, pBumps->Win, pBumps->GraphicsContext, pBumps->pXImage, nLightXPos, nLightYPos, nLightXPos, nLightYPos, pBumps->SpotLight.nDiameter, pBumps->SpotLight.nDiameter );
	XSync( pBumps->pDisplay, False );
}


void DestroyBumps( SBumps *pBumps )
{
	DestroySpotLight( &pBumps->SpotLight );
	free( pBumps->aXColors );
	free( pBumps->aBumpMap );
	XDestroyImage( pBumps->pXImage );
}


/* All messages to the screensaver are processed here. */
void screenhack( Display *pDisplay, Window Win )
{
	SBumps Bumps;
	uint32_ iDelay;
#ifdef VERBOSE
	time_t Time = time( NULL );
	uint16_ iFrame = 0;
#endif  /*  VERBOSE */
	
	CreateBumps( &Bumps, pDisplay, Win );
	iDelay = get_integer_resource( "delay", "Integer" );

	while( 1 )
	{
		screenhack_handle_events( pDisplay );
		Execute( &Bumps );
		usleep( iDelay );

#ifdef VERBOSE
		iFrame++;
		if( Time - time( NULL ) )
		{
			printf( "FPS: %d\n", iFrame );
			Time = time( NULL );
			iFrame = 0;
		}
#endif  /*  VERBOSE */
	}

	DestroyBumps( &Bumps );
}

 
/* vim: ts=4
 */
