#define VERSION LABEL,"Water0.24", 0, 0, 0, ""

/* Blender Texture plugin : Water : Waves and ripples etc.
 * By:
 * 1. Donn Ingle - Initial idea and first hacks at the code.
 * donn@telkomsa.net
 * Linux, Fedora Core 1 using Kate.
 * Tested on Blender 2.34
 * 2. Another mysterious person, selflessly helping out.
 * 
 * References:
 * http://freespace.virgin.net/hugo.elias/graphics/x_water.htm
 * http://www.libsdl.org/projects/water/
 * 
 * Credit to:
 * Scott Scriven : libsdl, this is where I ripped all the effects code. 
 * L.Guillaume : The idea for start and end frame.
 *               Help with compiling Windows dll's
 * Thanks to:
 * Mike Moller (CSIR South Africa) for teaching me about pointers...
 * 
 * Please forgive my coding and algorithms - I am not a real programmer, nor a maths boffin.
 * 
 * Any suggestions for improvements should be accompanied by code!
 * 
 * GPL.
 * 
 * Special Request: If you work on this plugin, please comment all your steps
 * (even the dumb, stupid, bloody easy ones) thoroughly -
 * I would like this to be a tutorial for those new to textures and plugins
 * (like me! I wish those sample textures where better remarked).
 * 
 * Use at own risk!
 */

/* #include <stdio.h> */
#include <math.h>
#include "plugin.h"


char name[]= "Water"; /* Some kind of ID */

/* Subtype names must be less than 15 characters */
#define NR_TYPES   5
#define MAXCHANCE 500
#define SPACER LABEL,"",0,0,0,""
char stnames[NR_TYPES][16] = {"Rain","Trails", "Mixture", "Swirl", "Drip"};
/*            "12345678901" <- 11 chars for labels */
VarStruct varstr[]= {
	{VERSION},
	{TOG|INT, "Static"		,0,0,1 ,"Static texture (be sure you are within the start:end frame range)"},
	{TOG|INT, "Seamless"	,0,0,1, "Makes the texture ... seamless ;)"},
	{TOG|INT, "Alpha"		,1,0,1 ,"Use alpha - handy for lights and world texture"},
	{NUM|INT, "Type:"		,1,1,5, "1=Fine 2=Heavy 3=Fuzzy 4=Warp 5=Box"},
	{NUM|INT, "Seed:"		,1,0,1000,"Choose a different random seed"},
	{NUM|INT, "Age:"		,4,1,2000, "Starting 'age' of the waves, BEWARE bigger numbers slow the sim"},
	{NUM|INT, "Speed:"		,1,1,10, "Sets the speed of the water"},	
	{NUM|INT, "Odds:"		,100,0,MAXCHANCE, "Chances of new drops (Maximum setting = no chance)"},
	{NUM|INT, "Dens:"		,4,1,10,"Density of the water. Waves fade slower at higher values"},
	{NUM|INT, "Rad:"	    ,20,0,100, "Radius of drops (Not for drop type 1)"},
	{NUM|FLO, "Int:" 		,1.f, 0.f, 2.f, "Scales the intensity of the output"},
	{NUM|FLO, "Noise:"		,0.0, 0.0, 10.0, "Set the Voodoo level. 0 = off"},
	{NUM|FLO, "Wob:"	    ,0.05,0.0,2.0, "When noise is on, this will wobble the water! 0=off 0.05=big 0.1=insane etc."},
	{NUM|INT, "Res:"		,1,1,10,"BEWARE SLOW DOWN! Resolution of the water map; multiply by 200 'pixels'"},
	{LABEL,   "Frame range" ,0,0,0,"Set the frame range below"},
	{NUM|INT, "Start:"		,1,1,18000,"Frame when drops begin to fall"},
	{NUM|INT, "Stop:"		,100,1,18000,"Frame when drops stop falling, cannot be less than start"},
	{LABEL,   "Drip control",0,0,0, "To control a single Drip (\"Drip\" button above)"},
	{TOG|INT, "Random"		,0,0,1, "Random time intervals between drops, \"Wait\" controls the random range"},
	{NUM|INT, "Wait:"	 	,10,1,18000, "For regular or random drips, this sets the frames between drops"},
	{NUM|INT, "X:"			,100,1,200, "X coordinate of your drip"},
	{NUM|INT, "Y:"			,100,1,200, "Y coordinate of your drip"},
	{TOG|INT, "Redraw"		,0,0,1, "Press to redraw, use after you change to another frame!"}
};

/* The cast struct is for input in the main doit function
Varstr and Cast must have the same variables in the same order */ 
typedef struct Cast 
{
	int version;		/* 0 */					
	int butStatic;    	/* 1 */
	int butSeamless;  	/* 2 */ 
	int butAlpha;     	/* 3 */
	int dropType;  		/* 4 */
	int seed;      		/* 5 */
	int Age;       		/* 6 */
	int speed;			/* 7 */	
	int dropChance;		/* 8 */
	int density;   		/* 9 */
	int radius;    		/* 10 */
	float iscale;  		/* 11 */
	float noise;   		/* 12 */
	float wobble;  		/* 13 */
	int resolution;		/* 14 */
	int dud;			/* 15 */
	int frameStart;		/* 16 */
	int frameEnd;		/* 17 */
	int dud2;			/* 18 */
	int butRandomDrip; 	/* 19 */
	int dripWait;		/* 20 */
	int dripX;			/* 21 */
	int dripY;			/* 22 */
	int butRedraw;		/* 23 */
} Cast;

#include "water_maths.h" /* I plonked all the complicated stuff here, go look.
						  * Because the doWave() func uses Cast, I put this include below
						  * its' definition. Dunno if there is a better way to do this ? */

float result[8];

/* cfra: the current frame */
float cfra;
int plugin_tex_doit(int, Cast *, float *, float *, float *);

/* A wild stab in the dark : */
#define HEIGHT 768 
	
#define INTENSITY 0
#define R 1
#define G 2
#define B 3
#define A 4

/* Global vars */
int gTextureReset=1;	/* To allow pre-run stuff to happen */
float gLastFrame;		/* keeps record of last frame number. */
int gLastState=0; 		/* keeps record of last state button pressed.*/
int gDripCounter = 0;	/* Counts up to 'Wait' setting */
int gDripWait = 0;		/* The 'Wait' setting, from gui or from random */
int gHasBeenAged = 0;	/* To flag if water has been aged or not, saves time */

/* Global memory error flag */
int gMEMORYERROR=0;

/* ******************** Plugin functions ***************** */
int plugin_tex_getversion(void) 
{   
	return B_PLUGIN_VERSION;
}
void plugin_but_changed(int but) 
{
	/* Some buttons need to Reset the Texture
	   others don't - so I sort which is which here: */
	
	gTextureReset = 1; /* Flag defaults to on */
	
	/*Now decide which buttons can switch the flag off */
	switch(but){
		case 0	: /* Version label */
		case 3  : /*butAlpha*/
		case 11 : /*Intensity*/
		case 15 : /*Label*/
		case 18 : /*Label*/
			gTextureReset = 0; break;
	}
}

void plugin_init(void)
{
	/*plugin_init called only once on very first run */
	
	/* Well:
	It seems that initcount does increase when you add this plugin to new 
	meshes. What does this mean? That this entire plugin's code is re-used, but
	that the data is an instance for every use of the texture? Seems so.
	Not sure.... any help?
	*/
	
	/* These two are in the .h file */
	FCreateSines(); /* Create quick lookups for sin values. */
	InitNoise();	/* Initialize noise array */
}


/* this function should not be changed: */
void plugin_getinfo(PluginInfo *info)
{
	info->name= name;
	info->stypes= NR_TYPES;
	info->nvars= sizeof(varstr)/sizeof(VarStruct);
	
	info->snames= stnames[0];
	info->result= result;
	info->cfra= &cfra;
	info->varstr= varstr;
	
	info->init= plugin_init;
	info->tex_doit=  (TexDoit) plugin_tex_doit;
	info->callback= plugin_but_changed;
}

/* +:+:+:+:+:+:+:+:+: Water stuff +:+:+:+:+:+:+:+:+: */

/* Resizes the arrays */
int setmem(int res)
{
	int byteRes;
	/* res is 1 to 10, we want multiples of 200: To tell you the truth,
	 * I have no idea why I have chosen 200. Would 256 be better? 128 ? */
	
	byteRes = res * 200; /* so up-it to the actual number */
	
	/* E: only alloc when resolution changed */
	if (byteRes != gBufferWidth) {    
		gBufferWidth = byteRes; /* Set global to this new width */
	
		byteRes = byteRes * byteRes; /* Now get the area */
		gBufferLen = byteRes; /* set the global var for other funcs to use. */
	
		free(gArray1); /* Free the RAM before a resize */
		free(gArray2);
		/* Allocate the RAM and return a 1 if there's an error. */
		if ( ((gArray1 = malloc(sizeof(int)*gBufferLen)) == NULL) 
		||((gArray2 = malloc(sizeof(int)*gBufferLen)) == NULL) ) {
			free(gArray1);
			free(gArray2);
			gSource = NULL;
			gDest   = NULL;
			gArray1 = NULL;
			gArray2 = NULL;
			gTextureReset = 0;
			return 1;
		}
	}
	/*E: Zero the array */
	memset(gArray1, 0, gBufferLen*sizeof(int));
	memset(gArray2, 0, gBufferLen*sizeof(int));
	
	/*Now set the pointers to point to the arrays*/
	gSource=gArray1;
	gDest=gArray2;    
	
	return 0;
}

void zero(Cast *cast)
{
	gDripCounter = 0; /* force a drip to be drawn on first frame */	
	gHasBeenAged = 0; /* No, it hasn't */
	gLastFrame = cfra + 1; /* We want it to re-draw the waves etc 
							* cfra = 1 on start, ergo gLastFrame = 2*/	
		
	/* E: since it could be a bit annoying that the texture changes every time a button is pressed,
	use a seed set by the user instead. */
	srand(cast->seed);
	/* E: reset x & y angles as well,
		otherwise this will also change the texture when trails is enabled and a button is pressed.
		Using % or & to generate good random values is not really a good idea,
		but doesn't really matter much for this type of plugin. */
	gXang = rand() & FSINMAX;
	gYang = rand() & FSINMAX;
	/*E: Zero the array */
	memset(gArray1, 0, gBufferLen*sizeof(int));
	memset(gArray2, 0, gBufferLen*sizeof(int));
}

/* Draw any kind of drop we need */
void drop(int x, int y, int dropType,int radius, int pheight)
{
	/* All "drops" are is a series of dots drawn into the array
	There is a central one at x,y that is usually the brightest (highest value),
	around that come others at various compass points of lesser intensity. */
	
	/* What kind of a drop are you? */
	switch(dropType) {
		case 1 : /* Delicate */
		lightDrop(x,y,pheight);
		break;
		case 2 : /* Heavy */
		heavyDrop(x, y, radius/2, pheight);
		break;
		case 3 : /* Fuzzy */
		fuzzyDrop(x,y,radius,pheight);
		break;
		case 4 : /* Warp */
		warpDrop(x,y,radius,pheight);
		break;
		case 5 : /* Box :) */
		boxDrop(x,y,radius,pheight);
		break;
	}
}

/* Draws a drop (of any type) at a random x,y */
void randomDrop(int dropType, int radius, int pheight)
{
	int x,y;
	/* Get random coordinates */
	x = (rand() % gBufferWidth) + 1;
	y = (rand() % gBufferWidth) + 1;
	drop(x, y, dropType, radius, pheight); /* decides what kind of drop */
}

/* E: adds the drops/trails */
void addDrops(int stype, Cast *cast)
{
	int randomNumber; /* One guess... :) */
	int dontbreak = 0; /* To mess with the switch-case command. */
	int x, y;  
	
	switch (stype) {
		case 2 : /* MIXED */
			dontbreak = 1;
		case 0 : /* RAIN */
			/*Add a random drop */
			randomNumber = rand()%MAXCHANCE+1;
			if (randomNumber > cast->dropChance)
				randomDrop(cast->dropType,cast->radius,HEIGHT);
			if (dontbreak == 0) break;
		case 1 : /* TRAILS */
			calcTrail(&x,&y);
			drop(x,y,cast->dropType,cast->radius,HEIGHT);
			break;
		case 3 : /* SWIRL */
			calcSwirl(&x,&y);
			drop(x,y,cast->dropType,cast->radius,HEIGHT);
			break;
		case 4:  /* DRIP */
			
			/*Has the counter hit the Wait limit? */
			if (gDripCounter == gDripWait) gDripCounter = 0;
			
			/* When counter is 0, it's time to draw a drop! */
			/* First time through here, it has been set to 0 by zero() routine */
			if (gDripCounter == 0) {
				/* The X, Y is kind-of flipped around, I still picture 0,0 at the top-left :) */
				drop(cast->dripY,cast->dripX,cast->dropType,cast->radius,HEIGHT);
				/*Set the DripWait depending on the button in the gui */
				if (cast->butRandomDrip == 1) {
					gDripWait = rand() % (int)(cast->dripWait)+1;			
				} else {
					gDripWait = cast->dripWait;
				}
			}
			
			/* inc the counter so it counts up to gDripWait */
			gDripCounter++;
			
			break; 
	}
	dontbreak = 0;  
}


/* +:+:+:+:+:+:+:+:+:+:+:+: MAIN LOOP :+:+:+:+:+:+:+:+:+:+:+:+: */

/* This baby is done once for EVERY SINGLE DOT of the texture!
I worked out (by painful printf trial and error, and I could be wrong)
that (at least on the texture preview) this is 139 calls per row (and 139 rows) so that's
193623 times this is called for a single texture to appear!

FIY : uv coords (texvec[0] and [1]) go from -0.978723 to +0.978723
So far I have no insight into how these figures differ when you choose
various map-to settings in the material. This is a fuzzy area.... :(
*/
int plugin_tex_doit(int stype, Cast *cast, float *texvec, float *dxt, float *dyt)
{
	int f;
	int totalAge = 0;
	float iscale, val;
	
	/* Has the state changed ? i.e. those buttons on top. */
	if (stype != gLastState) {
		gTextureReset = 1; /* Reset the sim.*/
		gLastState = stype; /* remember the current state. */
	}

	/* Are we resetting the texture ? */
	if ((gTextureReset == 1) && (gMEMORYERROR == 0)) {
		/* Resize the arrays and zero them (and other vars too) */
		if ((gMEMORYERROR = setmem(cast->resolution)) == 0) zero(cast);
				
		/* Let's make sure that the start and stop frames are making sense: */
		if (cast->frameStart > cast->frameEnd) cast->frameEnd = cast->frameStart + 1;
		
		gTextureReset = 0; /* Flag that this texture has been reset. */
	}
	
	/*This needs to run on EVERY LOOP in order to work */
	if (gMEMORYERROR == 1)
	{
		/* There was a memory error, return RED color */
		result[INTENSITY] = 1;  /* always return intensity */
		result[R] = 1;
		result[G] = 0;
		result[B] = 0;
		result[A] = 1;
		return 1;
	} else
	{  
	/* No Memory problems.
		
	 *  ARE WE ENTERING A NEW FRAME ?
	 *	
	 *	i.e. Has the current frame changed from last time the texture was drawn ? 
	 *
	 *	If so, it means the current frame has either advanced by 1 or
	 *	it has changed wildly forwards or backwards.
	 *	Either way, this signifies that an entire frame has been drawn and another is about
	 *	to begin.
	 */
		if (cfra != gLastFrame)
		{
			/* Now, check if cfra is in range */	
			if ((cfra >= cast->frameStart) && (cfra <= cast->frameEnd))
			{/* IN RANGE */
				if (gHasBeenAged == 0) 
				{ /* This has NOT been aged before */
					if (cast->butStatic == 0)
						totalAge = cast->Age + (cfra - cast->frameStart);/* Age plus the distance into range */
					else
						totalAge = cast->Age; /* Static texture only uses Age, not frame range */
					
					/*Do the initial aging: For animated and static textures */
					for (f=0; f < totalAge; f++)
					{
						addDrops(stype,cast);
						doWave(cast);
					}
					gHasBeenAged = 1;
					
				} else
				{	/* It HAS been aged
					 * I expect it to run this code for animation renderings.
					 * It's frame, boom, frame, boom, frame.. no aging needed
					 * because that's already been done. */
					if (cast->butStatic == 0) 
					{/*We only want to advance an animated texture. */
						addDrops(stype,cast);
						doWave(cast);
					}
				}
				
			} else
		 	{/* OUT OF RANGE */
				if (cfra > cast->frameEnd)
				{/* We are beyond the Stop Frame, so just run the sim, no new drops. */
					if (cast->butStatic == 0)
					{/*ONLY if we are animated! */
						doWave(cast);		
					}
				} else
		 		{/* We are before the Start Frame, so ensure no effect visible */
					if (cast->butStatic == 0) zero(cast);
				}
			} /* End: Decide if in range */
			
			gLastFrame = cfra; /* Flag that we are now busy on this frame */
		} 
		
		/* E: the intensity scale value set by the user, 256.0 is reasonable base value */
		iscale = cast->iscale/256.f;
	
		/* E wrote special noise function for this plugin */
		if (cast->noise > 0.0) {
			/* E: modified to slightly change over time for a more fluid like effect */
			float nx=(texvec[0]+1.f)/cast->noise + cfra*cast->wobble,
				  ny=(texvec[1]+1.f)/cast->noise + cfra*cast->wobble,
				  nz=(texvec[2]+1.f)/cast->noise + cfra*cast->wobble;
				  texvec[0]+= FNoise(nx, ny, nz);
				  texvec[1]+= FNoise(ny, nz, nx);
		}
	
		/* E: get the intensity value, calculated in function above.
		There is no need to make the intensity range (0,1), no clamping needed,
		just needs scaling down a bit. */
		val = getIntensity(texvec[0], texvec[1]) * iscale;
		
		val = CLAMP(val,0.f,1.f);
		result[INTENSITY] = val;
		result[A] = val; /* Also sending val to butAlpha */
		result[R] = val;
		result[G] = val;
		result[B] = val;
		
		/* E: these are the normal vectors which are used in blender for bumpmapping. */
		result[5] = val - (getIntensity(texvec[0]+0.025f, texvec[1]) * iscale);
		result[6] = val - (getIntensity(texvec[0], texvec[1]+0.025f) * iscale);
		result[7] = 0.f;
	
		/* Tell Blender that we are ready for next texture co-ord. */
		/*  1 = color value (and butAlpha) 
			2 = normal values only
			3 = everything.
			0 = I don't know
			*/
		if (cast->butAlpha) return 3; /* return butAlpha and colour */
		return 2; /* return intensity & normals */
	} /*mem error test*/
}
