//
// SPERMPLT - swimming sperm plotter
//
// Version 1.0 12/18/92 Copyright (C) 1992 Hutchins Software
// Author: Edward Hutchins &:
//
// The animation guts from a freely distributable X program:
// xsperm.c
// Drew Olbrich, Febrary 1991
// Note --  This code originally served as a demonstration
// of how to do animation under X.  The "guts" of the program
// which draws the sperm are consequently located in one huge
// chunk in the update_display() routine, and can be easily
// cut out.
// The animation function wrapped in a NeXTstep View subclass
// by Ali Ozer, May 91
// Very minor changes so this thing works as a screen saver module
// by sam streeper, August 91
// The "oneStep" method computes new locations.
//
// Revisions:
// 12/18/92 ported to Windows as an SPX - Ed.
//

#include <math.h>
#include "sperm.h"

//
// defines
//

// fixed point scale factor
#define VEC_1 (256L)
#define NORM(x) ((INT)((x)/VEC_1))

typedef LONG VECTOR[2];

#define VEC_COPY( y, x )        y[0] = x[0], y[1] = x[1]
#define VEC_ADD( z, x, y )      z[0] = x[0] + y[0], z[1] = x[1] + y[1]
#define VEC_SUB( z, x, y )      z[0] = x[0] - y[0], z[1] = x[1] - y[1]
#define VEC_MULT( x, a )        (x[0] = (x[0] * a) / VEC_1, x[1] = (x[1] * a) / VEC_1)
#define VEC_DIV( x, a )         (x[0] = (x[0] * VEC_1) / a, x[1] = (x[1] * VEC_1) / a)
#define VEC_ADDS( z, x, a, y )  (z[0] = x[0] + (((a)*y[0])/VEC_1), z[1] = x[1] + (((a)*y[1])/VEC_1))
#define VEC_NORM( x )           { LONG _nLen = IntDist( x[0], x[1] ); VEC_DIV( x, _nLen ); }

#define MINRAD      (VEC_1 / 10)
#define RADSTEP     2
#define MAXRAD      (MINRAD * 6 * RADSTEP)
#define INITRAD     (MINRAD * 3 * RADSTEP)

// RandInt(n) returns an integer 0..n-1
#define RandInt(n) (arand( n ))

// degrees scaled to integer math
#define DEGREE_SIZE 256
#define DEGREE_MASK 255
#define DEGREE_MAX 0x4000 // this is now hardcoded into the lookup table...

// faster than MulDiv!
#define MULDEG(x,y) (((x)*(y))/DEGREE_MAX)

// DEG - convert an integer into a degree lookup index
#define DEG(x) ((WORD)(x)&DEGREE_MASK)

//
// imports
//

IMPORT INT          nSpermCnt FROM( sperm.c );
IMPORT TRI          triBlank FROM( sperm.c );

//
// locals
//

LOCAL DWORD         dwSeed;
LOCAL VECTOR        x[SPERM_MAX], xPrev[SPERM_MAX];
LOCAL VECTOR        v[SPERM_MAX];
LOCAL VECTOR        lCenter;
LOCAL LONG          lSin[SPERM_MAX];
LOCAL LONG          lCos[SPERM_MAX];
LOCAL LONG          lVel[SPERM_MAX];
LOCAL LONG          lRad;
LOCAL LONG          lDir;
GLOBAL INT          nCos[DEGREE_SIZE] =
{
 16384,  16379,  16364,  16339,  16305,  16260,  16206,  16142,
 16069,  15985,  15892,  15790,  15678,  15557,  15426,  15286,
 15136,  14978,  14810,  14634,  14449,  14255,  14053,  13842,
 13622,  13395,  13159,  12916,  12665,  12406,  12139,  11866,
 11585,  11297,  11002,  10701,  10393,  10079,  9759,  9434,
 9102,  8765,  8423,  8075,  7723,  7366,  7005,  6639,
 6269,  5896,  5519,  5139,  4756,  4369,  3980,  3589,
 3196,  2801,  2404,  2005,  1605,  1205,  803,  402,
 0, -403, -804, -1206, -1606, -2006, -2405, -2802,
-3197, -3590, -3981, -4370, -4757, -5140, -5520, -5897,
-6270, -6640, -7006, -7367, -7724, -8076, -8424, -8766,
-9103, -9435, -9760, -10080, -10394, -10702, -11003, -11298,
-11586, -11867, -12140, -12407, -12666, -12917, -13160, -13396,
-13623, -13843, -14054, -14256, -14450, -14635, -14811, -14979,
-15137, -15287, -15427, -15558, -15679, -15791, -15893, -15986,
-16070, -16143, -16207, -16261, -16306, -16340, -16365, -16380,
-16384, -16380, -16365, -16340, -16306, -16261, -16207, -16143,
-16070, -15986, -15893, -15791, -15679, -15558, -15427, -15287,
-15137, -14979, -14811, -14635, -14450, -14256, -14054, -13843,
-13623, -13396, -13160, -12917, -12666, -12407, -12140, -11867,
-11586, -11298, -11003, -10702, -10394, -10080, -9760, -9435,
-9103, -8766, -8424, -8076, -7724, -7367, -7006, -6640,
-6270, -5897, -5520, -5140, -4757, -4370, -3981, -3590,
-3197, -2802, -2405, -2006, -1606, -1206, -804, -403,
 0,  402,  803,  1205,  1605,  2005,  2404,  2801,
 3196,  3589,  3980,  4369,  4756,  5139,  5519,  5896,
 6269,  6639,  7005,  7366,  7723,  8075,  8423,  8765,
 9102,  9434,  9759,  10079,  10393,  10701,  11002,  11297,
 11585,  11866,  12139,  12406,  12665,  12916,  13159,  13395,
 13622,  13842,  14053,  14255,  14449,  14634,  14810,  14978,
 15136,  15286,  15426,  15557,  15678,  15790,  15892,  15985,
 16069,  16142,  16206,  16260,  16305,  16339,  16364,  16379
};
GLOBAL INT          nSin[DEGREE_SIZE] =
{
 0,  402,  803,  1205,  1605,  2005,  2404,  2801,
 3196,  3589,  3980,  4369,  4756,  5139,  5519,  5896,
 6269,  6639,  7005,  7366,  7723,  8075,  8423,  8765,
 9102,  9434,  9759,  10079,  10393,  10701,  11002,  11297,
 11585,  11866,  12139,  12406,  12665,  12916,  13159,  13395,
 13622,  13842,  14053,  14255,  14449,  14634,  14810,  14978,
 15136,  15286,  15426,  15557,  15678,  15790,  15892,  15985,
 16069,  16142,  16206,  16260,  16305,  16339,  16364,  16379,
 16383,  16379,  16364,  16339,  16305,  16260,  16206,  16142,
 16069,  15985,  15892,  15790,  15678,  15557,  15426,  15286,
 15136,  14978,  14810,  14634,  14449,  14255,  14053,  13842,
 13622,  13395,  13159,  12916,  12665,  12406,  12139,  11866,
 11585,  11297,  11002,  10701,  10393,  10079,  9759,  9434,
 9102,  8765,  8423,  8075,  7723,  7366,  7005,  6639,
 6269,  5896,  5519,  5139,  4756,  4369,  3980,  3589,
 3196,  2801,  2404,  2005,  1605,  1205,  803,  402,
 0, -403, -804, -1206, -1606, -2006, -2405, -2802,
-3197, -3590, -3981, -4370, -4757, -5140, -5520, -5897,
-6270, -6640, -7006, -7367, -7724, -8076, -8424, -8766,
-9103, -9435, -9760, -10080, -10394, -10702, -11003, -11298,
-11586, -11867, -12140, -12407, -12666, -12917, -13160, -13396,
-13623, -13843, -14054, -14256, -14450, -14635, -14811, -14979,
-15137, -15287, -15427, -15558, -15679, -15791, -15893, -15986,
-16070, -16143, -16207, -16261, -16306, -16340, -16365, -16380,
-16384, -16380, -16365, -16340, -16306, -16261, -16207, -16143,
-16070, -15986, -15893, -15791, -15679, -15558, -15427, -15287,
-15137, -14979, -14811, -14635, -14450, -14256, -14054, -13843,
-13623, -13396, -13160, -12917, -12666, -12407, -12140, -11867,
-11586, -11298, -11003, -10702, -10394, -10080, -9760, -9435,
-9103, -8766, -8424, -8076, -7724, -7367, -7006, -6640,
-6270, -5897, -5520, -5140, -4757, -4370, -3981, -3590,
-3197, -2802, -2405, -2006, -1606, -1206, -804, -403
};

//
// arand - pseudorandom number from 0 to x-1
//

LONG NEAR PASCAL arand( LONG x )
{
	dwSeed = dwSeed * 0x343fd + 0x269ec3;
	return( (LONG)((dwSeed >> 8) % x) );
}

//
// IntDist - find an approximate distance
//

LONG NEAR PASCAL IntDist( LONG dx, LONG dy )
{
	LONG            lDist;

	if (dx < 0) dx = -dx;
	if (dy < 0) dy = -dy;
	lDist = dx + dy - (((dx > dy) ? dy : dx) >> 1);
	return( (lDist > 0) ? lDist : 1 );
}

//
// Tri - convert a tri-state into a boolean (unset == random)
//

BOOL NEAR PASCAL Tri( TRI tri )
{
	switch (tri)
	{
	case TRI_FALSE:
		return( FALSE );
	case TRI_TRUE:
		return( TRUE );
	default:
		return( (BOOL)arand( 2 ) );
	}
}

//
// DoEffect - do a special effect
//

VOID NEAR PASCAL DoEffect( INT nVal )
{
	INT             i;

	switch (nVal)
	{
	case 0:
		lDir *= -1;
		for (i = 0; i < nSpermCnt; i++)
		{
			VECTOR y;
			VEC_COPY( y, v[i] );
			if (lDir < 0) v[i][0] = y[1], v[i][1] = -y[0];
			else v[i][0] = -y[1], v[i][1] = y[0];
		}
		break;
	case 1:
		for (i = 0; i < nSpermCnt; i++) v[i][0] = -v[i][0];
		break;
	case 2:
		for (i = 0; i < nSpermCnt; i++) v[i][1] = -v[i][1];
		break;
	case 3:
		DoEffect( 1 );
		DoEffect( 2 );
		break;
	case 4:
		DoEffect( 0 );
		DoEffect( 3 );
	case 5:
		lRad = lRad * RADSTEP;
		if (lRad > MAXRAD) lRad = MAXRAD;
		break;
	case 6:
		lRad = lRad / RADSTEP;
		if (lRad < MINRAD) lRad = MINRAD;
		break;
	default:
		break;
	}
}

//
// SaverDraw - the main drawing routine
//

VOID FAR PASCAL EXPORT SaverDraw( HWND hwnd, HDC hdc, HANDLE hinst,
								  BOOL (FAR PASCAL *lpfnYield)( VOID ) )
{
	INT             cx, cy, i;
	INT             nRndCnt1, nRndCnt2;
	RECT            rect;

	dwSeed += GetTickCount();

	GetWindowRect( hwnd, &rect );
	cx = rect.right - rect.left;
	cy = rect.bottom - rect.top;

	if (Tri( triBlank )) FillRect( hdc, &rect, GetStockBrush( BLACK_BRUSH ) );

	lDir = 1;
	lRad = INITRAD;
	if (nSpermCnt < 1) nSpermCnt = 1;
	else if (nSpermCnt >= SPERM_MAX) nSpermCnt = SPERM_MAX - 1;
	nRndCnt1 = 100;
	nRndCnt2 = 200;
	lCenter[0] = (cx / 2) * VEC_1;
	lCenter[1] = (cy / 2) * VEC_1;

	for (i = 0; i < nSpermCnt; i++)
	{
		INT nAngle = (INT)RandInt( 10 ) + 5;
		xPrev[i][0] = x[i][0] = RandInt( cx * VEC_1 );
		xPrev[i][1] = x[i][1] = RandInt( cy * VEC_1 );
		v[i][0] = RandInt( 256 * VEC_1 ) - 128 * VEC_1;
		v[i][1] = RandInt( 256 * VEC_1 ) - 128 * VEC_1;
		VEC_NORM( v[i] );
		lSin[i] = nSin[ DEG(nAngle) ];
		lCos[i] = nCos[ DEG(nAngle) ];
		lVel[i] = RandInt( 4 * VEC_1 ) + 4 * VEC_1;
	}

	while ((*lpfnYield)())
	{
		for (i = 0; i < nSpermCnt; i++)
		{
			VECTOR      w, y;
			VECTOR      p;
			VECTOR      xOld;
			LONG        lTmp;

			VEC_COPY( xOld, xPrev[i] );
			VEC_COPY( xPrev[i], x[i] );

			VEC_SUB( w, x[i], lCenter );
			VEC_NORM( w );
			VEC_COPY( y, w );
			w[0] = MULDEG( y[0], lCos[i] ) - (lDir * MULDEG( y[1], lSin[i] )) / VEC_1;
			w[1] = MULDEG( y[1], lCos[i] ) + (lDir * MULDEG( y[0], lSin[i] )) / VEC_1;
			lTmp = (lRad * (160 * VEC_1 - lVel[i] * 20)) / VEC_1;
			VEC_ADDS( p, lCenter, lTmp, w );
			VEC_SUB( w, p, x[i] );
			VEC_NORM( w );
			VEC_ADD( v[i], v[i], w );
			VEC_NORM( v[i] );
			VEC_MULT( v[i], lVel[i] );
			VEC_ADD( x[i], x[i], v[i] );

			SelectObject( hdc, GetStockPen( BLACK_PEN ) );
			MoveTo( hdc, NORM( xOld[0] ), NORM( xOld[1] ) );
			LineTo( hdc, NORM( xPrev[i][0] ), NORM( xPrev[i][1] ) );
			SelectObject( hdc, GetStockPen( WHITE_PEN ) );
			LineTo( hdc, NORM( x[i][0] ), NORM( x[i][1] ) );
		}

		if (--nRndCnt1 < 0)
		{
			nRndCnt1 = (INT)RandInt( 700 );
			lCenter[0] = RandInt( cx * VEC_1 );
			lCenter[1] = RandInt( cy * VEC_1 );
		}

		if (--nRndCnt2 < 0)
		{
			nRndCnt2 = (INT)RandInt( 600 );
			DoEffect( nRndCnt2 % 7 );
		}
	}
}
