//////////////////////////////////////////////////////////////////////
//
//                             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				:	output.cpp
//  Classes				:	COutput
//  Description			:
//
////////////////////////////////////////////////////////////////////////
#include <string.h>

#include "output.h"
#include "renderer.h"
#include "stats.h"
#include "memory.h"
#include "error.h"

static	COutput				*currentOutput			=	NULL;
static	COptions::CDisplay	*currentDisplay			=	NULL;

///////////////////////////////////////////////////////////////////////
// Function				:	findParameter
// Description			:	This function can be used by the display server to probe for parameters
// Return Value			:	-
// Comments				:
// Date last edited		:	7/4/2001
void	*findParameter(const char *name,ParameterType type,int numItems) {
	if (currentDisplay != NULL) {
		int	i;

		for (i=0;i<currentDisplay->numParameters;i++) {
			if (strcmp(name,currentDisplay->parameters[i].name) == 0) {
				if (numItems == currentDisplay->parameters[i].numItems) {
					if (type == currentDisplay->parameters[i].type) {
						return	currentDisplay->parameters[i].data;
					}
				}
			}
		}
	}

	if (currentOutput != NULL) {
		if (strcmp(name,"quantize") == 0) {
			if ((numItems == 4) && (type == FLOAT_PARAMETER))	{
				if (currentDisplay->quantizer[0] == -1) {
					return	currentOutput->colorQuantizer;
				} else {
					return	currentDisplay->quantizer;
				}
			}
		} else if (strcmp(name,"dither") == 0) {
			if ((numItems == 1) && (type == FLOAT_PARAMETER)) {
				if (currentDisplay->quantizer[0] == -1) {
					return	currentOutput->colorQuantizer + 4;
				} else {
					return	currentDisplay->quantizer + 4;
				}
			}
		} else if (strcmp(name,"near") == 0) {
			if ((numItems == 1) && (type == FLOAT_PARAMETER))		return	&currentOutput->clipMin;
		} else if (strcmp(name,"far") == 0) {
			if ((numItems == 1) && (type == FLOAT_PARAMETER))		return	&currentOutput->clipMax;
		} else if (strcmp(name,"Nl") == 0) {
			if ((numItems == 16) && (type == FLOAT_PARAMETER))		return	&currentOutput->world->from;
		} else if (strcmp(name,"NP") == 0) {
			if ((numItems == 16) && (type == FLOAT_PARAMETER))		return	&currentOutput->worldToNDC;
		} else if (strcmp(name,"gamma") == 0) {
			if ((numItems == 1) && (type == FLOAT_PARAMETER))		return	&currentOutput->gamma;
		} else if (strcmp(name,"gain") == 0) {
			if ((numItems == 1) && (type == FLOAT_PARAMETER))		return	&currentOutput->gain;
		} else if (strcmp(name,"Software") == 0) {
			if ((numItems == 1) && (type == STRING_PARAMETER))		return	(void *) "Pixie";
		}
	}

	return	NULL;
}

///////////////////////////////////////////////////////////////////////
// Class				:	COutput
// Method				:	COutput
// Description			:	Ctor
// Return Value			:	-
// Comments				:
// Date last edited		:	7/4/2001
COutput::COutput(COptions *options,CXform *x,SOCKET s,unsigned int hf) : COptions(options) {
	matrix	mtmp;

	currentOutput			=	this;

	hiderFlags				=	hf;
	netClient				=	s;

	assert(pixelXsamples > 0);
	assert(pixelYsamples > 0);

	// Compute some stuff
	if (flags & OPTIONS_FLAGS_CUSTOM_FRAMEAR) {
		const float	ar	=	xres*pixelAR / (float) yres;

		// Update the resolution as necessary
		if (frameAR > ar) {
			yres	=	(int) (xres*pixelAR / frameAR);
		} else {
			xres	=	(int) (frameAR * yres / pixelAR);
		}
	} else {
		frameAR = xres*pixelAR / (float) yres;
	}

	if (flags & OPTIONS_FLAGS_CUSTOM_SCREENWINDOW) {
		// The user explicitly entered the screen window, so we don't have to make sure it matches the frame aspect ratio
	} else {
		if (frameAR > (float) 1.0) {
			screenTop			=	(float) 1.0;
			screenBottom		=	(float) -1.0;
			screenLeft			=	-frameAR;
			screenRight			=	frameAR;
		} else {
			screenTop			=	1/frameAR;
			screenBottom		=	-1/frameAR;
			screenLeft			=	(float) -1.0;
			screenRight			=	(float) 1.0;
		}
	}

	imagePlane		=	1;
	if (projection == OPTIONS_PROJECTION_PERSPECTIVE) {
		imagePlane	=	(float) (1/tan(radians(fov/(float) 2)));
	} else {
		imagePlane	=	1;
	}

	invImagePlane	=	1/imagePlane;

	assert(cropLeft < cropRight);
	assert(cropTop < cropBottom);

	// Rendering window in pixels
	renderLeft			=	(int) ceil(xres*cropLeft);
	renderRight			=	(int) ceil(xres*cropRight);
	renderTop			=	(int) ceil(yres*cropTop);
	renderBottom		=	(int) ceil(yres*cropBottom);

	assert(renderRight > renderLeft);
	assert(renderBottom > renderTop);

	// The resolution of the actual render window
	xPixels				=	renderRight		-	renderLeft;
	yPixels				=	renderBottom	-	renderTop;

	assert(xPixels >= 0);
	assert(yPixels >= 0);

	dxdPixel			=	(screenRight	- screenLeft) / (float) (xres);
	dydPixel			=	(screenBottom	- screenTop) / (float) (yres);
	dPixeldx			=	1	/	dxdPixel;
	dPixeldy			=	1	/	dydPixel;
	pixelLeft			=	(float) (screenLeft	+ renderLeft*dxdPixel);
	pixelTop			=	(float) (screenTop	+ renderTop*dydPixel);
	pixelRight			=	pixelLeft	+ dxdPixel*xPixels;
	pixelBottom			=	pixelTop	+ dydPixel*yPixels;

	xBuckets			=	(int) ceil(xPixels / (float) bucketWidth);
	yBuckets			=	(int) ceil(yPixels / (float) bucketHeight);

	aperture			=	focallength / (2*fstop);
	if ((aperture <= EPSILON) || (projection == OPTIONS_PROJECTION_ORTHOGRAPHIC)) {
		aperture			=	0;
		cocFactorScreen		=	0;
		cocFactorSamples	=	0;
		cocFactorPixels		=	0;
		invFocaldistance	=	0;
	} else {
		cocFactorScreen		=	(float) (aperture*focaldistance / (focaldistance + aperture));
		cocFactorSamples	=	cocFactorScreen*(float) sqrt(dPixeldx*dPixeldx*pixelXsamples*pixelXsamples + dPixeldy*dPixeldy*pixelYsamples*pixelYsamples);
		cocFactorPixels		=	cocFactorScreen*(float) sqrt(dPixeldx*dPixeldx + dPixeldy*dPixeldy);
		invFocaldistance	=	1 / focaldistance;
	}

	if (projection == OPTIONS_PROJECTION_ORTHOGRAPHIC) {
		lengthA			=	0;
		lengthB			=	(float) sqrt(dxdPixel*dxdPixel + dydPixel*dydPixel);
	} else {
		lengthA			=	(float) sqrt(dxdPixel*dxdPixel + dydPixel*dydPixel) / imagePlane;
		lengthB			=	0;
	}

	if (aperture		!= 0)			flags	|=	OPTIONS_FLAGS_FOCALBLUR;
	if (shutterClose	!= shutterOpen)	flags	|=	OPTIONS_FLAGS_MOTIONBLUR;

	// Init the world matrix
	world				=	x;
	world->attach();

	// Compute the matrices related to the camera transformation
	if (projection == OPTIONS_PROJECTION_PERSPECTIVE) {
		identitym(toScreen);
		toScreen[element(3,2)]	=	1;
		toScreen[element(3,3)]	=	0;
		identitym(fromScreen);	// The screen to camera is singular

		toNDC[element(0,0)]	=	imagePlane / (pixelRight - pixelLeft);
		toNDC[element(0,1)]	=	0;
		toNDC[element(0,2)]	=	-pixelLeft / (pixelRight - pixelLeft);
		toNDC[element(0,3)]	=	0;
		toNDC[element(1,0)]	=	0;
		toNDC[element(1,1)]	=	imagePlane / (pixelBottom - pixelTop);
		toNDC[element(1,2)]	=	-pixelTop / (pixelBottom - pixelTop);
		toNDC[element(1,3)]	=	0;
		toNDC[element(2,0)]	=	0;
		toNDC[element(2,1)]	=	0;
		toNDC[element(2,2)]	=	1;
		toNDC[element(2,3)]	=	0;
		toNDC[element(3,0)]	=	0;
		toNDC[element(3,1)]	=	0;
		toNDC[element(3,2)]	=	1;
		toNDC[element(3,3)]	=	0;

	} else {
		identitym(toScreen);
		identitym(fromScreen);

		toNDC[element(0,0)]	=	imagePlane / (pixelRight - pixelLeft);
		toNDC[element(0,1)]	=	0;
		toNDC[element(0,2)]	=	0;
		toNDC[element(0,3)]	=	-pixelLeft / (pixelRight - pixelLeft);
		toNDC[element(1,0)]	=	0;
		toNDC[element(1,1)]	=	imagePlane / (pixelBottom - pixelTop);
		toNDC[element(1,2)]	=	0;
		toNDC[element(1,3)]	=	-pixelTop / (pixelBottom - pixelTop);
		toNDC[element(2,0)]	=	0;
		toNDC[element(2,1)]	=	0;
		toNDC[element(2,2)]	=	1;
		toNDC[element(2,3)]	=	0;
		toNDC[element(3,0)]	=	0;
		toNDC[element(3,1)]	=	0;
		toNDC[element(3,2)]	=	0;
		toNDC[element(3,3)]	=	1;
	}


	invertm(fromNDC,toNDC);

	identitym(mtmp);
	mtmp[element(1,1)]	=	(float) xres;
	mtmp[element(2,2)]	=	(float) yres;

	mulmm(toRaster,mtmp,toNDC);

	mulmm(worldToNDC,toNDC,world->from);

	const float	minX		=	min(pixelLeft,pixelRight);	// The extend of the rendering window on the image
	const float	maxX		=	max(pixelLeft,pixelRight);	// plane
	const float	minY		=	min(pixelTop,pixelBottom);
	const float	maxY		=	max(pixelTop,pixelBottom);

	// Compute the equations of the clipping planes
	// The visible points are:
	// Px*leftX		+ Pz*leftZ		+ leftD		>=	0	&&
	// Px*rightX	+ Pz*rightZ		+ rightD	>=	0	&&
	// Py*topY		+ Pz*topZ		+ topD		>=	0	&&
	// Py*bottomY	+ Pz*bottomZ	+ bottomD	>=	0	&&
	// Pz >= clipMin									&&
	// Pz <= clipMax
	if (projection == OPTIONS_PROJECTION_PERSPECTIVE) {
		leftX			=	imagePlane;
		leftZ			=	-minX;
		leftD			=	0;
		rightX			=	-imagePlane;
		rightZ			=	maxX;
		rightD			=	0;
		topY			=	imagePlane;
		topZ			=	-minY;
		topD			=	0;
		bottomY			=	-imagePlane;
		bottomZ			=	maxY;
		bottomD			=	0;
	} else {
		leftX			=	1;
		leftZ			=	0;
		leftD			=	-minX;
		rightX			=	-1;
		rightZ			=	0;
		rightD			=	maxX;

		topY			=	-1;
		topZ			=	0;
		topD			=	maxY;
		bottomY			=	1;
		bottomZ			=	0;
		bottomD			=	-minY;
	}

	if (displays == NULL) {
		displays				=	new CDisplay;
		displays->next			=	NULL;
		displays->outDevice		=	strdup(RI_FILE);
		displays->outName		=	strdup("ri.tif");
		displays->outSamples	=	strdup(RI_RGBA);
	}

	marginalX		=	pixelFilterWidth / 2;
	marginalY		=	pixelFilterHeight / 2;
	marginX			=	(float) floor(marginalX);
	marginY			=	(float) floor(marginalY);
	marginXcoverage	=	max(marginalX - marginX,0);
	marginYcoverage	=	max(marginalY -	marginY,0);

	currentXBucket	=	0;
	currentYBucket	=	0;

	numDisplays			=	0;
	numActiveDisplays	=	0;
	datas				=	NULL;

	deepShadowFile		=	NULL;
	deepShadowIndex		=	NULL;
	deepShadowIndexStart=	0;

	if (netClient != INVALID_SOCKET) {
		numActiveDisplays		=	1;
	}

	if (!(hiderFlags & HIDER_NODISPLAY))
		computeDisplayData();
	else {
		numSamples		=	0;
		numExtraSamples	=	0;
	}
}

///////////////////////////////////////////////////////////////////////
// Class				:	COutput
// Method				:	~COutput
// Description			:	Dtor
// Return Value			:	-
// Comments				:
// Date last edited		:	7/4/2001
COutput::~COutput() {
	int			i;

	// Detach from the world transform
	world->detach();

	// Finish the out images
	for (i=0;i<numDisplays;i++) {
		if (datas[i].module != NULL) {
				datas[i].finish(datas[i].handle);
		}
	}

	if (datas != NULL) delete [] datas;

	if (deepShadowFile != NULL) {
		fseek(deepShadowFile,deepShadowIndexStart,SEEK_SET);
		fwrite(deepShadowIndex,sizeof(int),xBuckets*yBuckets,deepShadowFile);	// Override the deep shadow map index
		fclose(deepShadowFile);
	}

	if (deepShadowIndex != NULL)	delete [] deepShadowIndex;
}

///////////////////////////////////////////////////////////////////////
// Class				:	COutput
// Method				:	dispatch
// Description			:	Dispatch a rendered window to the out devices
// Return Value			:	-
// Comments				:
// Date last edited		:	8/26/2001
void	COutput::dispatch(int left,int top,int width,int height,float *pixels) {
	int		i;

	// Send the pixels to the output servers
	for (i=0;i<numDisplays;i++) {
		if (datas[i].module != NULL) {
			float			*dest,*dispatch;
			int				j,k,s;
			const float		*tmp			=	&pixels[datas[i].sampleStart];
			int				imageSamples	=	datas[i].numSamples;
			
			memBegin();

			dispatch	= dest	=	(float *) ralloc(width*height*imageSamples*sizeof(float));
			s			=	numSamples - imageSamples;

			for (j=width*height;j>0;j--) {
				for (k=imageSamples;k>0;k--) {
					*dest++	=	*tmp++;
				}
				tmp		+=	s;
			}

			if (datas[i].data(datas[i].handle,left,top,width,height,dispatch) == FALSE) {
				datas[i].handle	=	NULL;
				numActiveDisplays--;
				if (numActiveDisplays == 0)	hiderFlags	|=	HIDER_BREAK;
				osUnloadModule(datas[i].module);
				datas[i].module	=	NULL;
			}

			memEnd();
		}
	}
}

///////////////////////////////////////////////////////////////////////
// Class				:	COutput
// Method				:	clear
// Description			:	Send a clear window to the out devices
// Return Value			:	-
// Comments				:
// Date last edited		:	8/26/2001
void	COutput::clear(int left,int top,int width,int height) {
	memBegin();

	float	* pixels	=	(float *) ralloc(width*height*numSamples*sizeof(float));
	int		i;

	for (i=0;i<width*height*numSamples;i++) {
		pixels[i]	=	0;
	}

	dispatch(left,top,width,height,pixels);

	memEnd();
}

///////////////////////////////////////////////////////////////////////
// Class				:	COutput
// Method				:	commit
// Description			:	Send a window of samples
// Return Value			:	-
// Comments				:
// Date last edited		:	8/26/2001
void	COutput::commit(int left,int top,int xpixels,int ypixels,float *pixels) {
	if (netClient != INVALID_SOCKET) {
		// We are rendering for a client, so just send the result to the waiting client
		T32	header[5];
		T32	a;

		header[0].integer	=	NET_READY;
		rcSend(netClient,(char *) header,	1*sizeof(T32));

		header[0].integer	=	left;
		header[1].integer	=	top;
		header[2].integer	=	xpixels;
		header[3].integer	=	ypixels;
		header[4].integer	=	xpixels*ypixels*numSamples;

		rcSend(netClient,(char *) header,	5*sizeof(T32));
		rcRecv(netClient,(char *) &a,		1*sizeof(T32));
		rcSend(netClient,(char *) pixels,xpixels*ypixels*numSamples*sizeof(T32));
		return;
	}

	if ((top == 0) && (left == 0)) {
		if (renderTop > 0)		clear(0,0,xres,renderTop);
	}

	if (left == 0) {
		if (renderLeft > 0)		clear(0,top+renderTop,renderLeft,ypixels);
	}

	if ((left+xpixels) == xPixels) {
		if (renderRight < xres)	clear(renderRight,top+renderTop,xres-renderRight,ypixels);
	}

	if (((top+ypixels) == yPixels) && ((left+xpixels) == xPixels)) {
		if (renderBottom < yres)	clear(0,renderBottom,xres,yres-renderBottom);
	}


	dispatch(left+renderLeft,top+renderTop,xpixels,ypixels,pixels);
}



///////////////////////////////////////////////////////////////////////
// Class				:	COutput
// Method				:	getDisplayName
// Description			:	Create the display name
// Return Value			:
// Comments				:
// Date last edited		:	7/4/2001
void	COutput::getDisplayName(char *out,const char *in,const char *displayType) {
	char		*cOut	=	out;
	const char	*cIn	=	in;

	while(*cIn != '\0') {
		if (*cIn == '#') {
			int		width		=	0;
			char	widthString[256];

			cIn++;

			while((*cIn >= '0') && (*cIn <= '9')) {
				widthString[width++]	=	*cIn++;
			}

			if (width > 1) {
				widthString[width]	=	'\0';
				sscanf(widthString,"%d",&width);
				sprintf(widthString,"%%%dd",width);
			} else {
				sprintf(widthString,"%%d");
			}

			switch(*cIn) {
			case 'f':
				sprintf(cOut,widthString,(int) frame);
				while(*cOut != '\0')	cOut++;
				break;
			case 's':
				sprintf(cOut,widthString,stats.sequenceNumber);
				while(*cOut != '\0')	cOut++;
				break;
			case 'n':
				sprintf(cOut,widthString,stats.runningSequenceNumber);
				while(*cOut != '\0')	cOut++;
				break;
			case 'h':
				char	hostName[1024];
				gethostname(hostName,1024);
				sprintf(cOut,hostName);
				while(*cOut != '\0')	cOut++;
				break;
			case 'd':
				strcpy(cOut,displayType);
				while(*cOut != '\0')	cOut++;
				break;
			case 'p':
				sprintf(cOut,"0");
				while(*cOut != '\0')	cOut++;
				break;
			case 'P':
				sprintf(cOut,"0");
				while(*cOut != '\0')	cOut++;
				break;
			case '#':
				sprintf(cOut,"#");
				while(*cOut != '\0')	cOut++;
				break;
			default:
				error(CODE_BADTOKEN,"Unknown display stub %c\n",*cIn);
				cIn++;
				break;
			}
		} else {
			*cOut++	=	*cIn++;
		}
	}

	strcpy(out,in);
}




///////////////////////////////////////////////////////////////////////
// Class				:	COutput
// Method				:	computeDisplayData
// Description			:	Compute the display data
// Return Value			:
// Comments				:
// Date last edited		:	7/4/2001
void	COutput::computeDisplayData() {
	int			i,j;
	CDisplay	*cDisplay;
	int			*extraVariables;
	char		displayName[256];
	char		deviceFile[OS_MAX_PATH_LENGTH];

	for (i=0,cDisplay=displays;cDisplay!=NULL;i++,cDisplay=cDisplay->next);

	datas					=	new CDisplayData[i];
	extraVariables			=	(int *) alloca(i*sizeof(int));
	j						=	0;
	numSamples				=	5;	// rgbaz
	numExtraSamples			=	0;
	numDisplays				=	0;
	additionalParameters	=	0;
	numActiveDisplays		=	0;
	deepShadowFile			=	NULL;
	deepShadowIndex			=	NULL;

	for (cDisplay=displays;cDisplay!=NULL;cDisplay=cDisplay->next) {
		datas[numDisplays].outType		=	-1;
		datas[numDisplays].display		=	cDisplay;
		if (strcmp(cDisplay->outSamples,"rgb") == 0) {
			datas[numDisplays].sampleStart	=	0;
			datas[numDisplays].numSamples	=	3;
		} else if (strcmp(cDisplay->outSamples,"rgba") == 0) {
			datas[numDisplays].sampleStart	=	0;
			datas[numDisplays].numSamples	=	4;
		} else if (strcmp(cDisplay->outSamples,"a") == 0) {
			datas[numDisplays].sampleStart	=	3;
			datas[numDisplays].numSamples	=	1;
		} else if (strcmp(cDisplay->outSamples,"z") == 0) {
			datas[numDisplays].sampleStart	=	4;
			datas[numDisplays].numSamples	=	1;
		} else if (strcmp(cDisplay->outSamples,"rgbaz") == 0) {
			datas[numDisplays].sampleStart	=	0;
			datas[numDisplays].numSamples	=	5;
		} else {
			if (hiderFlags & HIDER_RGBAZ_ONLY) {
				error(CODE_BADTOKEN,"Hider %s can not handle arbitrary output variables\n",hider);
				continue;
			} else {
				CVariable	*cVar		=	currentRenderer->retrieveVariable(cDisplay->outSamples);

				if (cVar != NULL) {
					// Make sure the variable is global
					if (cVar->global == FALSE)	currentRenderer->makeGlobalVariable(cVar);

					extraVariables[j++]				=	cVar->entry;
					datas[numDisplays].sampleStart	=	numSamples;
					datas[numDisplays].numSamples	=	cVar->numFloats;
					datas[numDisplays].outType		=	cVar->entry;
					numSamples						+=	datas[numDisplays].numSamples;
					numExtraSamples					+=	datas[numDisplays].numSamples;
					additionalParameters			|=	cVar->usageMarker;
				} else {
					error(CODE_BADTOKEN,"Unable to find output variable %s\n",cDisplay->outSamples);
					continue;
				}
			}
		}

		if (netClient == INVALID_SOCKET) {
			getDisplayName(displayName,cDisplay->outName,cDisplay->outSamples);

			if (strcmp(cDisplay->outDevice,"tsm") == 0) {
				int					j;
				CDeepShadowHeader	header;

				// The TSM is hardcoded
				datas[numDisplays].module	=	NULL;
				datas[numDisplays].handle	=	NULL;

				// Set up the file header
				header.xres		=	xres;
				header.yres		=	yres;
				header.xTiles	=	xBuckets;
				header.yTiles	=	yBuckets;
				header.tileSize	=	bucketWidth;
				for (header.tileShift=1;(1 << header.tileShift) < bucketWidth;header.tileShift++);
				movmm(header.toNDC,worldToNDC);

				// The sanity check
				if ((1 << header.tileShift) != bucketWidth) {
					error(CODE_LIMIT,"Bucket width must be a power of 2 for tsm (%d).\n",bucketWidth);
				} else {
					if (bucketWidth != bucketHeight) {
						error(CODE_LIMIT,"Bucket width and height must be same for tsm (%d,%d).\n",bucketWidth,bucketHeight);
					} else {
						deepShadowFile		=	ropen(displayName,"wb",fileTransparencyShadow);

						if (deepShadowFile != NULL) {
							numActiveDisplays++;
							flags						|=	OPTIONS_FLAGS_DEEP_SHADOW_RENDERING;

							deepShadowIndex				=	new int[xBuckets*yBuckets];

							// Write the header
							fwrite(&header,sizeof(CDeepShadowHeader),1,deepShadowFile);

							// Save the index start
							deepShadowIndexStart	=	ftell(deepShadowFile);

							// Write the dummy index
							fwrite(deepShadowIndex,sizeof(int),xBuckets*yBuckets,deepShadowFile);

							// Parse the tsm parameters
							for (j=0;j<cDisplay->numParameters;j++) {
								if (strcmp(cDisplay->parameters[j].name,"threshold") == 0) {
									float	*val	=	(float *) cDisplay->parameters[j].data;

									tsmThreshold	=	val[0];
								}
							}
						}
					}
				}
			} else if (currentRenderer->locateFileEx(deviceFile,cDisplay->outDevice,osModuleExtension,displayPath)) {
				datas[numDisplays].module		=	osLoadModule(deviceFile);
				if (datas[numDisplays].module != NULL) {
					datas[numDisplays].start		=	(TDisplayStartFunction)		osResolve(datas[numDisplays].module,"displayStart");
					datas[numDisplays].data			=	(TDisplayDataFunction)		osResolve(datas[numDisplays].module,"displayData");
					datas[numDisplays].rawData		=	(TDisplayRawDataFunction)	osResolve(datas[numDisplays].module,"displayRawData");
					datas[numDisplays].finish		=	(TDisplayFinishFunction)	osResolve(datas[numDisplays].module,"displayFinish");

					if ((datas[numDisplays].start == NULL) || (datas[numDisplays].data == NULL) || (datas[numDisplays].finish == NULL)) {
						error(CODE_SYSTEM,"The module %s has missing implementation\n",deviceFile);
						osUnloadModule(datas[numDisplays].module);
						datas[numDisplays].module	=	NULL;
					} else {
						currentDisplay				=	cDisplay;
						datas[numDisplays].handle	=	datas[numDisplays].start(displayName,xres,yres,datas[numDisplays].numSamples,cDisplay->outSamples,findParameter);

						if (datas[numDisplays].handle != NULL) {
							numActiveDisplays++;
						} else {
							osUnloadModule(datas[numDisplays].module);
							datas[numDisplays].module	=	NULL;
						}
					}
				} else {
					datas[numDisplays].module		=	NULL;
					error(CODE_SYSTEM,"Unable to open out device \"%s\"\n",cDisplay->outDevice);
				}
			} else {
				datas[numDisplays].module		=	NULL;
				error(CODE_SYSTEM,"Unable to find out device \"%s\"\n",cDisplay->outDevice);
			}
		} else {
			datas[numDisplays].module	=	NULL;
			datas[numDisplays].handle	=	NULL;
		}

		numDisplays++;
	}

	if (numActiveDisplays == 0) hiderFlags	|=	HIDER_BREAK;
}





///////////////////////////////////////////////////////////////////////
// Class				:	COutput
// Method				:	inFrustrum
// Description			:	Check if the given box is inside the viewing frustrum
// Return Value			:	-
// Comments				:
// Date last edited		:	5/10/2002
int			COutput::inFrustrum(const float *bmin,const float *bmax) {
	vector	corners[8];
	int		i;

	initv(corners[0],bmin[COMP_X],bmin[COMP_Y],bmin[COMP_Z]);
	initv(corners[1],bmin[COMP_X],bmax[COMP_Y],bmin[COMP_Z]);
	initv(corners[2],bmin[COMP_X],bmax[COMP_Y],bmax[COMP_Z]);
	initv(corners[3],bmin[COMP_X],bmin[COMP_Y],bmax[COMP_Z]);
	initv(corners[4],bmax[COMP_X],bmin[COMP_Y],bmin[COMP_Z]);
	initv(corners[5],bmax[COMP_X],bmax[COMP_Y],bmin[COMP_Z]);
	initv(corners[6],bmax[COMP_X],bmax[COMP_Y],bmax[COMP_Z]);
	initv(corners[7],bmax[COMP_X],bmin[COMP_Y],bmax[COMP_Z]);

	// Check against the left bounding plane
	for (i=0;i<8;i++) {
		float	*corner	=	corners[i];

		if ((corner[COMP_X]*leftX + corner[COMP_Z]*leftZ + leftD) > 0) {
			break;
		}
	}

	if (i == 8)	return FALSE;


	// Check against the right bounding plane
	for (i=0;i<8;i++) {
		float	*corner	=	corners[i];

		if ((corner[COMP_X]*rightX + corner[COMP_Z]*rightZ + rightD) > 0) {
			break;
		}
	}

	if (i == 8)	return	FALSE;

	// Check against the top bounding plane
	for (i=0;i<8;i++) {
		float	*corner	=	corners[i];

		if ((corner[COMP_Y]*topY + corner[COMP_Z]*topZ + topD) > 0) {
			break;
		}
	}

	if (i == 8)	return	FALSE;


	// Check against the bottom bounding plane
	for (i=0;i<8;i++) {
		float	*corner	=	corners[i];

		if ((corner[COMP_Y]*bottomY + corner[COMP_Z]*bottomZ + bottomD) > 0) {
			break;
		}
	}

	if (i == 8)	return	FALSE;

	return	TRUE;
}


///////////////////////////////////////////////////////////////////////
// Class				:	COutput
// Method				:	inFrustrum
// Description			:	Check if the given box is inside the viewing frustrum
// Return Value			:	-
// Comments				:
// Date last edited		:	5/10/2002
int			COutput::inFrustrum(const float *P) {

	if ((P[COMP_X]*leftX + P[COMP_Z]*leftZ + leftD) < 0) {
		return FALSE;
	}

	if ((P[COMP_X]*rightX + P[COMP_Z]*rightZ + rightD) < 0) {
		return FALSE;
	}

	if ((P[COMP_Y]*topY + P[COMP_Z]*topZ + topD) < 0) {
		return FALSE;
	}

	if ((P[COMP_Y]*bottomY + P[COMP_Z]*bottomZ + bottomD) < 0) {
		return FALSE;
	}

	return	TRUE;
}




///////////////////////////////////////////////////////////////////////
// Class				:	COutput
// Method				:	clipCode
// Description			:	Compute the clipping codes for a point
// Return Value			:	-
// Comments				:
// Date last edited		:	5/10/2002
unsigned int			COutput::clipCode(const float *P) {
	unsigned int	code	=	0;

	if ((P[COMP_X]*leftX + P[COMP_Z]*leftZ + leftD) < 0) {
		code	|=	CLIP_LEFT;
	}

	if ((P[COMP_X]*rightX + P[COMP_Z]*rightZ + rightD) < 0) {
		code	|=	CLIP_RIGHT;
	}

	if ((P[COMP_Y]*topY + P[COMP_Z]*topZ + topD) < 0) {
		code	|=	CLIP_TOP;
	}

	if ((P[COMP_Y]*bottomY + P[COMP_Z]*bottomZ + bottomD) < 0) {
		code	|=	CLIP_BOTTOM;
	}

	if (P[COMP_Z] < clipMin)	code	|=	CLIP_NEAR;

	if (P[COMP_Z] > clipMax)	code	|=	CLIP_FAR;

	return	code;
}









