/* Copyright (c) 1999, TNCCI Inc.
 *
 * voronoi.c
 *
 * a procedural texture plugin for blender that generates Voronoi cell
 * structures.
 *
 * (source written/compiled by rob haarsma, between dec 95 and april 1999)
 *
 * Contact:      rob@captainvideo.nl   
 * Information:  http://www.captainvideo.nl/blender
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 */

#include "math.h"
#include "plugin.h"

#define NR_TYPES	1

#define FIELD     10
#define NORP      4
#define srand48(seed)  srand(seed)
#define drand48()  ((double) rand()/RAND_MAX)


float result[8];
float cfra;

static int firsttime = TRUE;
static int rseed = 1;

extern float hnoise(float noisesize, float x, float y, float z);

/* plugin menu and variables******************************************* */

char name[]= "Pattern";
char stnames[NR_TYPES][16]= {"Voronoi"};

/* butcode,	name,		default, min,   max,	tooltips comment */
VarStruct varstr[]= {
	{ NUM|FLO,	"distance",	0.030,	0.0,	0.5,	
	   "Voronoi plugin: Size of distance between cells"}, 
	{ NUM|FLO,	"border",	0.060,	0.0,	0.5,	
	   "Voronoi plugin: Size of the cel border"}, 
	{ NUM|FLO,	"grey1",	0.8,	0.0,	1.0,	
	   "Voronoi plugin: Greyvalue of space between cells"}, 
	{ NUM|FLO,	"grey2",	0.5,	0.0,	1.0,	
	   "Voronoi plugin: Greyvalue of cel border"}, 
	{ NUM|FLO,	"grey3",	0.2,	0.0,	1.0,	
	   "Voronoi plugin: Greyvalue of cell"},
	{ LABEL,		"",		0,	0,	0,	""},
	{ NUM|INT,	"seed",		1.0,	1.0,	1000.0,	
	   "Voronoi plugin: Cell pattern random number"}, 
	{ NUM|FLO,	"scale",	1.0,	1.0,	25.0,	
	   "Voronoi plugin: Size of cell pattern"}, 
	{ TOG|INT,	"bump it!",	-0.5,	-1.0,	1.0,	
	   "Voronoi plugin: Activate bump mapping"}, 
	{ NUM|FLO,	"bump size",	0.5,	-1.0,	1.0,	
	   "Voronoi plugin: Height of the bumpmap"}, 
	{ NUM|FLO,	"bumpmix",	0.5,	0.0,	1.0,	
	   "Voronoi plugin: Mix amount of greyvalues with bump effect"}, 
	{ LABEL,		"phaseIV  '99",	0,	0,	0,	""},
};

typedef struct Cast {
	float plateau, ridge, grey1, grey2, grey3, dum1;
	int   seed;
	float scale;
	int   bump_it;
	float bsize, bumpmix, dum2;
} Cast;

/* ******************************************************************* */

int plugin_tex_doit(int, Cast*, float*, float*, float*);


int plugin_tex_getversion(void) 
{	
	return B_PLUGIN_VERSION;
}


void plugin_but_changed(int but) 
{
}


void plugin_init(void)
{
}


void plugin_getinfo(PluginInfo *info)
{
	info->name= name;
	info->stypes= 1;
	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;
}


float Inpf(float *v1, float *v2)
{
	return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2];
}


float Normalise(float *n)
{
	register float d;

	d= n[0]*n[0]+n[1]*n[1]+n[2]*n[2];
	if(d>0.0) {
		d= sqrtf(d);

		n[0]/=d; 
		n[1]/=d; 
		n[2]/=d;
	} else {
		n[0]=n[1]=n[2]= 0.0;
	}
	return d;
}


float VecLenf(float *v1, float *v2)
{
	float x,y,z;

	x=v1[0]-v2[0];
	y=v1[1]-v2[1];
	z=v1[2]-v2[2];
	return sqrtf(x*x+y*y+z*z);
}


float structured(Cast *cast, float *texvec, float *dxt, float *dyt)
{
	/*// pt bol scale bfac bsize drempel seed var3 var4 var5 */

	float  factor = 1.0, normaal[3];
	static float  vor[FIELD][FIELD][FIELD][NORP * 3];
	float  point[3], point2[3], p1[3], p2[3], rp[3], vec[3], Nadd[3], rd = 2.0;
	float  dir_x, dir_y, dir_z, grens, t1, t2, t3;
	float  osa_x, osa_y, osa_d, osa_h;
	float  Result;
	int    floorx, floory, floorz, i, j, x, y, z;
	float  plateau, ridge, s;
	float  x1, x2, y1, y2, z1, z2, a, a1 = -1.0, a2 = -1.0, a3 = -1.0;

	float  buur[24]   =  {  0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
				0.0, 0.0, 1.0, 1.0, 0.0, 1.0,
				0.0, 1.0, 0.0, 1.0, 1.0, 0.0,
				0.0, 1.0, 1.0, 1.0, 1.0, 1.0 };

	if(firsttime){
		i = cast->seed;
		srand48(i);

		for(x = 0; x < FIELD; x++)
			for(y = 0; y < FIELD; y++)
				for(z = 0; z < FIELD; z++)
					for(j = 0; j < (NORP * 3); j++)
						vor[x][y][z][j] = drand48();

		firsttime = FALSE;
	}


	ridge   = cast->ridge + cast->plateau;
	plateau = cast->plateau;

	point[0] = texvec[0];
	point[1] = texvec[1];
	point[2] = texvec[2];

	point2[0] = texvec[0] * cast->scale;
	point2[1] = texvec[1] * cast->scale;
	point2[2] = texvec[2] * cast->scale;
	
	floorx = floor(point2[0]);
	floory = floor(point2[1]);
	floorz = floor(point2[2]);

	if((point2[0] - floorx) < 0.5) dir_x = -1.0; else dir_x = 1.0;
	if((point2[1] - floory) < 0.5) dir_y = -1.0; else dir_y = 1.0;
	if((point2[2] - floorz) < 0.5) dir_z = -1.0; else dir_z = 1.0;

	grens = 0.7;
	
	x1 = point[0] - grens;
	x2 = point[0] + grens;
	y1 = point[1] - grens;
	y2 = point[1] + grens;
	z1 = point[2] - grens;
	z2 = point[2] + grens;

	for(j = 0; j < 8; j++) {                            /* check omliggende cubes */
		for(i = 0; i < NORP; i++) {                 /* check allevier de random points */
         		t1 = dir_x * buur[j * 3];

			if(point2[0] + t1 < 0.0)
				x = (int)(FIELD + fmod(point2[0] + t1, FIELD))% FIELD;
			else
				x = (int)(fmod(point2[0] + t1, FIELD))% FIELD;

			t2 = dir_y * buur[j * 3 + 1];

			if(point2[1] + t2 < 0.0)
				y = (int)(FIELD + fmod(point2[1] + t2, FIELD ))% FIELD;
			else
				y = (int)(fmod(point2[1] + t2, FIELD))% FIELD;

			t3 = dir_z * buur[j * 3 + 2];

			if(point2[2] + t3 < 0.0)
				z = (int)(FIELD + fmod(point2[2] + t3, FIELD ))% FIELD;
			else
				z = (int)(fmod(point2[2] + t3, FIELD ))%FIELD;

        		vec[0] = (floorx + t1 + vor[x][y][z][i * 3]) / cast->scale;
         		vec[1] = (floory + t2 + vor[x][y][z][i * 3 + 1]) / cast->scale;
         		vec[2] = (floorz + t3 + vor[x][y][z][i * 3 + 2]) / cast->scale;

        		if((vec[0] > x1 && vec[0] < x2) && (vec[1] > y1 && vec[1] < y2) && (vec[2] > z1 && vec[2] < z2)){
				a = VecLenf(vec, point);

				if(a1 == -1.0) {
					p1[0] = vec[0];
					p1[1] = vec[1];
					p1[2] = vec[2];
					a1 = a;
				} else {
					if(a < a1) {
						a3 = a2;
	
						p2[0] = p1[0];
						p2[1] = p1[1];
						p2[2] = p1[2];
						a2 = a1;

						p1[0] = vec[0];
						p1[1] = vec[1];
						p1[2] = vec[2];
						a1 = a;
					} else {
						if(a2 == -1.0 || a < a2) {
							a3 = a2;

							p2[0] = vec[0];
							p2[1] = vec[1];
							p2[2] = vec[2];
							a2 = a;
						} else {
							if(a3 == -1.0 || a < a3) {
								a3 = a;
							}
						}
					}
				}
			}
		}
	}

	normaal[0] = 0; /*result[5];*/
	normaal[1] = 0; /*result[6];*/
	normaal[2] = 0; /*result[7];*/
	Normalise(normaal);

	rp[0] = (p2[0] - p1[0]);
	rp[1] = (p2[1] - p1[1]);
	rp[2] = (p2[2] - p1[2]);
	Normalise(rp);
	
	Nadd[0] = rp[0] - Inpf(normaal, rp) * normaal[0];
	Nadd[1] = rp[1] - Inpf(normaal, rp) * normaal[1];
	Nadd[2] = rp[2] - Inpf(normaal, rp) * normaal[2];
	
	Normalise(Nadd);

	rd = a2 - a1;
	
	if(dxt || dyt){
		osa_x = fsqrt(dxt[0]*dxt[0]+dxt[1]*dxt[1]+dxt[2]*dxt[2]);
		osa_y = fsqrt(dyt[0]*dyt[0]+dyt[1]*dyt[1]+dyt[2]*dyt[2]);
		osa_d = fsqrt(osa_x*osa_x+osa_y*osa_y);
		osa_h = osa_d / 2.0;
		if(rd < (plateau - osa_h))
			Result = cast->grey1;
		else
			if(rd >= ((plateau - osa_h) * factor) && rd < ((plateau + osa_h) * factor)){ /* OSA gebied I */
				Result = cast->grey2 + (cast->grey1 - cast->grey2) * (1.0 - (rd - (plateau - osa_h)) / osa_d);
				if(cast->bump_it){
					result[5] = normaal[0]  + (Nadd[0] * cast->bsize) * ((rd - (plateau - osa_h)) / osa_d);
					result[6] = normaal[1]  + (Nadd[1] * cast->bsize) * ((rd - (plateau - osa_h)) / osa_d);
					result[7] = normaal[2]  + (Nadd[2] * cast->bsize) * ((rd - (plateau - osa_h)) / osa_d);
					/* Normalise(result+5); */
				}
			}else
				if(rd < (ridge - osa_h)){
					Result = cast->grey2;
					if(cast->bump_it){
						s = 1.0;
						result[5] = normaal[0]  + s * Nadd[0] * cast->bsize;
						result[6] = normaal[1]  + s * Nadd[1] * cast->bsize;
						result[7] = normaal[2]  + s * Nadd[2] * cast->bsize;
						/* Normalise(result+5); */
					}
				} else
					if(rd >= ((ridge - osa_h) * factor)&& rd < ((ridge + osa_h) * factor)){   /* OSA gebied II */
						Result = cast->grey3 + (cast->grey2 - cast->grey3) * (1.0 - (rd - (ridge - osa_h)) / osa_d);
						if(cast->bump_it){
							result[5] = normaal[0]  + (Nadd[0] * cast->bsize) * (1.0 - (rd - (ridge - osa_h)) / osa_d);
							result[6] = normaal[1]  + (Nadd[1] * cast->bsize) * (1.0 - (rd - (ridge - osa_h)) / osa_d);
							result[7] = normaal[2]  + (Nadd[2] * cast->bsize) * (1.0 - (rd - (ridge - osa_h)) / osa_d);
							/* Normalise(result+5);*/
						}
					}else
						Result = cast->grey3;
	} else {
		if(rd < plateau)
			Result = cast->grey1;
		else
			if(rd > ridge)
				Result = cast->grey3;
		else {
			Result = cast->grey2;
			 
			if(cast->bump_it){
				s = 1.0;
				result[5] = normaal[0]  + s * Nadd[0] * cast->bsize;
				result[6] = normaal[1]  + s * Nadd[1] * cast->bsize;
				result[7] = normaal[2]  + s * Nadd[2] * cast->bsize;
				/* Normalise(result+5); */
			}
		}
	}

	if(cast->bump_it)
		return Result * cast->bumpmix;
	else
		return Result;
}

int plugin_tex_doit(int stype, Cast *cast, float *texvec, float *dxt, float *dyt)
{
	float base;

	if(rseed != cast->seed){
		rseed = cast->seed;
		firsttime = 1;
	}

	base = structured(cast, texvec, dxt, dyt);

	result[0]= base;
	result[1]= result[0];
	result[2]= result[0];
	result[3]= result[0];
	result[4]= 1.0;

	if(cast->bump_it)
		return 2;
	else
		return 1;
}

