//////////////////////////////////////////////////////////////////////
//
//                             Pixie
//
// Copyright © 1999 - 2003, Okan Arikan
//
// Contact: okan@cs.berkeley.edu
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
//  File				:	init.cpp
//  Classes				:	-
//  Description			:	This file implements the shading language interpreter that runs the init code
//
////////////////////////////////////////////////////////////////////////
#include <math.h>

#include "renderer.h"
#include "shader.h"
#include "slcode.h"
#include "noise.h"
#include "shading.h"
#include "stats.h"
#include "memory.h"
#include "output.h"
#include "error.h"


////////////////////////////////////////////////////////////////////////////////////////////////
//	Prototypes for shading language functions
//	These functions are defined in interpreter.cpp
////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////
// Shading language functions ... (implemented in interpreter.cpp)
CShaderCache					*newCache(CShader *,CShaderCache *c=NULL);										// Allocate a new cache

#define shaderDiffusePoint(a,b)				scripterror("Invalid environment function call during init\n")
#define shaderDiffuse(a,b,c,d)				scripterror("Invalid environment function call during init\n")
#define shaderSpecular(a,b,c,d,e)			scripterror("Invalid environment function call during init\n")
#define shaderPhong(a,b,c,d,e)				scripterror("Invalid environment function call during init\n")
#define	specularBRDF(a,b,c,d,e)				scripterror("Invalid environment function call during init\n")
#define	rendererInfo(a,b)					scripterror("Invalid environment function call during init\n")
#define	emission(a,b)						scripterror("Invalid environment function call during init\n")
#define	clipMin								0
#define	clipMax								1
#define debugFunction(a)
#define	illuminateBegin(a,b,c,d,e,f)

///////////////////////////////////////////////////////////////////////
// Class				:	CShadingContext
// Method				:	convertColorFrom
// Description			:	Do color conversion
// Return Value			:	-
// Comments				:
// Date last edited		:	8/25/2002
void		convertColorFrom(float *out,const float *in,ECoordinateSystem s)	{
	switch(s) {
	case COLOR_RGB:
		movvv(out,in);
		break;
	case COLOR_HSL:
		{

#define	HueToRGB(r,m1,m2,h )												\
	if (h<0) h+=1;															\
	if (h>1) h-=1;															\
	if (6.0*h < 1  )		r	= (m1+(m2-m1)*h*6);							\
	else if (2.0*h < 1  )	r	= m2;										\
	else if (3.0*h < 2.0)	r	= (m1+(m2-m1)*((float) (2.0/3.0)-h)*6);		\
	else					r	= m1;


			float m1,m2,h;

			if (in[1]==0) initv(out,in[2],in[2],in[2]);
			else {
				if (in[2] <=0.5)	m2 = in[2]*(1+in[1]);
				else				m2 = in[2]+in[1]-in[2]*in[1];
				m1	= 2*in[2]-m2;
				
				h	=	in[0] + (float) (1/3.0);	HueToRGB(out[0],m1,m2,h);
				h	=	in[0];						HueToRGB(out[1],m1,m2,h);
				h	=	in[0] - (float) (1/3.0);	HueToRGB(out[2],m1,m2,h);
			}
#undef HueToRGB

		}
		break;
	case COLOR_HSV:
		{
			if (in[1] == 0) {
				if (in[0] == 0) {
					out[0]	=	in[2];
					out[1]	=	in[2];
					out[2]	=	in[2];
				} else {
					out[0]	=	in[0];
					out[1]	=	in[1];
					out[2]	=	in[2];
				}
			} else {
				float	f,p,q,t,h;
				int		i;

				h			=	(float) fmod(in[0],1);
				if (h < 0)	h	+=	1;

				h		*=	6;

				i		=	(int) floor(h);
				f		=	h - i;
				p		=	in[2]*(1-in[1]);
				q		=	in[2]*(1-(in[1]*f));
				t		=	in[2]*(1-(in[1]*(1-f)));

				switch(i) {
				case 0:	out[COMP_R]	=	in[2];	out[COMP_G]	=	t;		out[COMP_B]	=	p;		break;
				case 1:	out[COMP_R]	=	q;		out[COMP_G]	=	in[2];	out[COMP_B]	=	p;		break;
				case 2:	out[COMP_R]	=	p;		out[COMP_G]	=	in[2];	out[COMP_B]	=	t;		break;
				case 3:	out[COMP_R]	=	p;		out[COMP_G]	=	q;		out[COMP_B]	=	in[2];	break;
				case 4:	out[COMP_R]	=	t;		out[COMP_G]	=	p;		out[COMP_B]	=	in[2];	break;
				case 5:	out[COMP_R]	=	in[2];	out[COMP_G]	=	p;		out[COMP_B]	=	q;		break;
				}
			}
		}
		break;
	case COLOR_XYZ:
		out[COMP_R]		=	(float)  (3.24079*in[0]		-	1.537150*in[1]	-	0.498535*in[2]);
		out[COMP_G]		=	(float) (-0.969256*in[0]	+	1.875992*in[1]	+	 0.041556*in[2]);
		out[COMP_B]		=	(float)   (0.055648*in[0]	-	0.204043*in[1]	+	1.057311*in[2]);
		break;
	case COLOR_CIE:
		out[COMP_R]		=	(float)  (3.24079*in[0]		-	1.537150*in[1]	-	0.498535*in[2]);
		out[COMP_G]		=	(float) (-0.969256*in[0]	+	1.875992*in[1]	+	 0.041556*in[2]);
		out[COMP_B]		=	(float)   (0.055648*in[0]	-	0.204043*in[1]	+	1.057311*in[2]);
		break;
	case COLOR_YIQ:
		out[COMP_R]		=	(float) (in[0]	+	0.956*in[1]	+	0.620*in[2]);
		out[COMP_G]		=	(float) (in[0]	-	0.272*in[1]	-	0.647*in[2]);
		out[COMP_B]		=	(float) (in[0]	-	1.108*in[1]	+	1.705*in[2]);
		break;
	case COLOR_XYY:
		vector	tin;

		if (in[2] == 0) {
			tin[0]	=	0;
			tin[1]	=	0;
			tin[2]	=	0;
		} else {
			tin[0]	=	max(in[0]*in[2]/in[1],0);
			tin[1]	=	in[2];
			tin[2]	=	max((1-in[0]-in[1])*in[2]/in[1],0);
		}

		out[COMP_R]		=	(float)  (3.24079*tin[0]		-	1.537150*tin[1]	-	0.498535*tin[2]);
		out[COMP_G]		=	(float) (-0.969256*tin[0]	+	1.875992*tin[1]	+	 0.041556*tin[2]);
		out[COMP_B]		=	(float)   (0.055648*tin[0]	-	0.204043*tin[1]	+	1.057311*tin[2]);

		break;
	default:
		break;
	}
}


///////////////////////////////////////////////////////////////////////
// Class				:	CShadingContext
// Method				:	convertColorTo
// Description			:	Do color conversion
// Return Value			:	-
// Comments				:
// Date last edited		:	8/25/2002
void		convertColorTo(float *out,const float *in,ECoordinateSystem s) {
	switch(s) {
	case COLOR_RGB:
		movvv(out,in);
		break;
	case COLOR_HSL:
		{
			float	mi	=	min(in[0],min(in[1],in[2]));
			float	ma	=	max(in[0],max(in[1],in[2]));

			out[2]		=	(mi + ma) / 2;
			if (ma == mi) {
				out[0]	=	0;
				out[1]	=	0;
			} else {
				float	d	=	ma - mi;

				if (out[2] < 0.5) {
					out[1]	=	d / (ma + mi);
				} else {
					out[1]	=	d / (2 - (ma + mi));
				}

				if (in[COMP_R]	==	ma) {
					out[0]	=	(in[COMP_G] - in[COMP_B])/d;
				} else if (in[COMP_G] == ma) {
					out[0]	=	2 + (in[COMP_B] - in[COMP_R])/d;
				} else {
					out[0]	=	4 + (in[COMP_R] - in[COMP_G])/d;
				}

				out[0]	/=	(float) 6;

				if (out[0] < 0) out[0] +=	1;
			}
		}
		break;
	case COLOR_HSV:
		{
			float	ma	=	max(in[0],max(in[1],in[2]));
			float	mi	=	min(in[0],min(in[1],in[2]));

			out[2]		=	ma;
			out[1]		=	(ma - mi) / ma;
			if (ma == 0) {
				out[0]	=	0;
			} else {
				float	d	=	ma	-	mi;

				if (in[COMP_R]	==	ma) {
					out[0]	=	(in[COMP_G] - in[COMP_B])	/	d;
				} else if (in[COMP_G] == ma) {
					out[0]	=	2 + (in[COMP_B] - in[COMP_R])	/	d;
				} else {
					out[0]	=	4 + (in[COMP_R] - in[COMP_G])	/	d;
				}

				out[0]		/=	(float) 6;
				if (out[0] < 0)		out[0]	+=	1;
			}
		}
		break;
	case COLOR_XYZ:
		out[0]	=	(float) (0.412453 * in[COMP_R]	+	0.357580 * in[COMP_G]	+	0.180423 * in[COMP_B]);
		out[1]	=	(float) (0.212671 * in[COMP_R]	+	0.715160 * in[COMP_G]	+	0.072169 * in[COMP_B]);
		out[2]	=	(float) (0.019334 * in[COMP_R]	+	0.119193 * in[COMP_G]	+	0.950227 * in[COMP_B]);
		break;
	case COLOR_CIE:
		out[0]	=	(float) (0.412453 * in[COMP_R]	+	0.357580 * in[COMP_G]	+	0.180423 * in[COMP_B]);
		out[1]	=	(float) (0.212671 * in[COMP_R]	+	0.715160 * in[COMP_G]	+	0.072169 * in[COMP_B]);
		out[2]	=	(float) (0.019334 * in[COMP_R]	+	0.119193 * in[COMP_G]	+	0.950227 * in[COMP_B]);
		break;
	case COLOR_YIQ:
		out[0]	=	(float) (0.299 * in[COMP_R]	+	0.587 * in[COMP_G]	+	0.114 * in[COMP_B]);
		out[1]	=	(float) (0.596 * in[COMP_R]	-	0.275 * in[COMP_G]	-	0.321 * in[COMP_B]);
		out[2]	=	(float) (0.212 * in[COMP_R]	-	0.523 * in[COMP_G]	+	0.311 * in[COMP_B]);
		break;
	case COLOR_XYY:
		vector	tin;
		float	sum;

		tin[0]	=	(float) (0.412453 * in[COMP_R]	+	0.357580 * in[COMP_G]	+	0.180423 * in[COMP_B]);
		tin[1]	=	(float) (0.212671 * in[COMP_R]	+	0.715160 * in[COMP_G]	+	0.072169 * in[COMP_B]);
		tin[2]	=	(float) (0.019334 * in[COMP_R]	+	0.119193 * in[COMP_G]	+	0.950227 * in[COMP_B]);

		sum		=	tin[0] + tin[1] + tin[2];
		if (sum == 0) {
			initv(out,0,0,0);
		} else {
			out[0]	=	tin[0] / sum;
			out[1]	=	tin[1] / sum;
			out[2]	=	tin[2];
		}
		break;
	default:
		break;
	}
}


///////////////////////////////////////////////////////////////////////
// Function				:	sfInit
// Description			:	Execute the current shader's init code
// Return Value			:	-
// Comments				:
// Date last edited		:	11/28/2001
void	CRendererContext::init(CProgrammableShaderInstance *currentShaderInstance) {
// At this point, the shader sends us the arrays for parameters/constants/variables/uniforms for the shader
	
//	The maximum allowed string size that can be handled
#define		MAX_SCRIPT_STRING_SIZE			256



#define		scripterror(mes)				{																	\
												error(CODE_SCRIPT,"\"%s\", (nullified)\n",mes);					\
												currentShaderInstance->parent->codeEntryPoint	=	-1;			\
												currentShaderInstance->parent->initEntryPoint	=	-1;			\
												goto	execEnd;												\
											}
//	Allocate temporary memory for the string and save it
#define		savestring(r,n)					{																	\
												int		strLen	=	strlen(n) + 1;								\
												int		strSize	=	(strLen & ~3) + 4;							\
												char	*strmem	=	(char *) ralloc(strSize);					\
												strcpy(strmem,n);												\
												r				=	strmem;										\
											}

//	Begin a conditional block execution
#define		beginConditional()				if (conditionals == NULL) {											\
												conditionals		=	new CConditional;						\
												conditionals->next	=	NULL;									\
												conditionals->prev	=	NULL;									\
											}																	\
																												\
											conditionals->prev		=	lastConditional;						\
											lastConditional			=	conditionals;							\
											conditionals			=	lastConditional->next;
											

//	End a conditional block execution
#define		endConditional()				lastConditional->next	=	conditionals;							\
											conditionals			=	lastConditional;						\
											lastConditional			=	lastConditional->prev;

//	Retrieve a pointer to an operand and obtain it's size
#define		operand(i,n)					{																	\
												const TCode	ref	=	code[IP+i+2];								\
												n	= stuff[ref.reference.accessor][ref.reference.index];		\
											}

//	Retrieve an operand's size
#define		operandSize(i,n,s)				{																	\
												const TCode	ref	=	code[IP+i+2];								\
												n	= stuff[ref.reference.accessor][ref.reference.index];		\
												s	= ref.reference.numItems;									\
											}



//	Retrieve an integer operand (label references are integer)
#define		argument(i)						code[IP+i+2].integer;

//	Retrieve the number of arguments
#define		argumentcount(n)				n = code[IP+1].arguments.numArguments;

//	Control transfer
#define		jmp(n)							{																	\
												IP				=	n;											\
												goto execStart;													\
											}



//	Run the light source shaders for the lP
#define		runAmbientLights()				scripterror("Light source exec during init code\n");

//	Run the light source shaders for the lP
#define		runLights(lP,lN,lT)				scripterror("Light source exec during init code\n");


// The misc macros
#define		DEFLINKOPCODE(name,text,nargs)				case OPCODE_##name:		
#define		DEFLINKFUNC(name,text,prototype,par)		case FUNCTION_##name:

// Break the shader execution
#define		BREAK							goto execEnd;



//	The	shading variables and junk
	TCode						**stuff[6];			// Where we keep pointers to the variables
	int							IP;					// Isn't it obvious ?
	ESlCode						opcode;				// :)
	CConditional				*lastConditional;	// The last conditional
	int							numActive;
	int							numPassive;
	int							*tags;
	int							*tagStart;
	int							currentVertex			=	0;
	CShader						*currentShader			=	currentShaderInstance->parent;
	TCode						*code;
	int							tmpTags;
	int							numVertices;
	float						**varying;
	CShadedLight				**lights;
	CShadedLight				*alights;
	CShadedLight				**currentLights;
	CShaderCache				*cCache;
	CConditional				*conditionals			=	NULL;
	CConditional				*cConditional;
	int							i;

	code									=	currentShader->codeArea;

	IP										=	currentShader->initEntryPoint;
	numVertices								=	1;
	tagStart								=	&tmpTags;
	tmpTags									=	0;

	// Allocate the needed memory
	TCode	*sb								=	(TCode *) ralloc((numVertices*currentShader->totalVaryingSize + currentShader->totalUniformSize + currentShader->numVariables + currentShader->numUniforms)*sizeof(TCode));

	// Set the access arrays
	stuff[SL_VARYING_OPERAND]				=	(TCode **) sb;								// Shader varying variables
	sb										+=	currentShader->numVariables;
	stuff[SL_UNIFORM_OPERAND]				=	(TCode **) sb;								// Shader uniform variables
	sb										+=	currentShader->numUniforms;

	for (i=0;i<currentShader->numVariables;i++) {									// Allocate memory for every varying variable
		stuff[SL_VARYING_OPERAND][i]		=	sb;
		sb									+=	currentShader->varyingSizes[i]*numVertices;
	}

	for (i=0;i<currentShader->numUniforms;i++) {									// Allocate memory for every uniform variable
		stuff[SL_UNIFORM_OPERAND][i]		=	sb;
		sb									+=	currentShader->uniformSizes[i];
	}

	varying									=	NULL;
	lights									=	NULL;
	alights									=	NULL;
	currentLights							=	NULL;
	cCache									=	NULL;

	// Set the access arrays
	stuff[SL_IMMEDIATE_OPERAND]				=	currentShader->constantEntries;				// Immediate operands
	stuff[SL_PARAMETER_OPERAND]				=	currentShaderInstance->parameterEntries;	// Parameters
	stuff[SL_GLOBAL_OPERAND]				=	(TCode **) varying;							// Varying globals

	numActive								=	numVertices;
	numPassive								=	0;
	lastConditional							=	NULL;				// The last conditional block

	// Execute
execStart:
	opcode	=	(ESlCode)	code[IP].integer;

	tags	=	tagStart;

#define		INIT_SHADING
#define		DEFOPCODE(name,text,nargs,expr_pre,expr,expr_update,expr_post,param)			\
	case OPCODE_##name:																		\
	{																						\
		expr_pre;																			\
		expr;																				\
		expr_post																			\
		IP	+=	code[IP+1].arguments.numCodes;												\
		goto execStart;																		\
	}																						\
	break;

#define		DEFSHORTOPCODE(name,text,nargs,expr_pre,expr,expr_update,expr_post,param)		\
	case OPCODE_##name:																		\
	{																						\
		expr_pre;																			\
		expr;																				\
		expr_post																			\
		IP	+=	code[IP+1].arguments.numCodes;												\
		goto execStart;																		\
	}																						\
	break;


#define		DEFFUNC(name,text,prototype,expr_pre,expr,expr_update,expr_post,par)			\
	case FUNCTION_##name:																	\
	{																						\
		expr_pre;																			\
		expr;																				\
		expr_post																			\
		IP	+=	code[IP+1].arguments.numCodes;												\
		goto execStart;																		\
	}																						\
	break;

#define		DEFSHORTFUNC(name,text,prototype,expr_pre,expr,expr_update,expr_post,par)		\
	case FUNCTION_##name:																	\
	{																						\
		expr_pre;																			\
		expr;																				\
		expr_post																			\
		IP	+=	code[IP+1].arguments.numCodes;												\
		goto execStart;																		\
	}																						\
	break;

	switch(opcode) {

#include "scriptOpcodes.h"

#include "scriptFunctions.h"

	default:
		goto execEnd;
	}

	IP	+=	code[IP+1].arguments.numCodes;
	goto execStart;
#undef	DEFOPCODE
#undef	DEFFUNC
#undef	INIT_SHADING



	goto execStart;
execEnd:

	// Delete the conditionals
	while((cConditional=conditionals) != NULL) {
		conditionals	=	cConditional->next;
		delete cConditional;
	}

	assert(numActive	== numVertices);
	assert(numPassive	== 0);

// Undefine junk
#undef		MAX_SCRIPT_STRING_SIZE
#undef		savestring
#undef		allocbuffer
#undef		freebuffer
#undef		beginConditional
#undef		endConditional
#undef		operand
#undef		argument
#undef		argumentcount
#undef		jmp
#undef		runAmbientLights
#undef		runLights
#undef		firstLight
#undef		nextLight
#undef		allLights
#undef		currentLight
#undef		saveLight
#undef		uniformGlobal
#undef		varyingGlobal
#undef		DEFOPCODE
#undef		DEFFUNC
#undef		DEFLINKOPCODE
#undef		DEFLINKFUNC
#undef		BREAK
}











