/*
	File	:	hwinsert.cpp
	Author	:	Richard Flower (c) 1998 Core Design
	----------------------------------------------------------------------------------
	Functions
	----------------------------------------------------------------------------------

	ColorFromRGB	-	Create D3D Color From RGB Components
	GETR			-	Get Red Component From D3D RGB
	GETG			-	Get Green Component From D3D RGB
	GETB			-	Get Blue Component From D3D RGB
	VertexRGB  		-	Convert 555 To 888 D3D RGB (Takes into account bBlueEffect)

  
	
	----------------------------------------------------------------------------------
*/

#include "..\specific\standard.h"
#include "..\specific\winmain.h"
#include "..\specific\drawprimitive.h"
#include "..\specific\hwinsert.h"
#include "..\specific\hwrender.h"
#include "..\specific\global.h"

#define UV_MUL			(1.0f/65536.0f)
#define SHADOW_BIAS		131072
#define RED_SHADE		128
#define GREEN_SHADE		224	
#define ALPHA_OPAQUE	0xff000000
//#define NOLIGHTING

template <class T> inline void swap(T &a,T &b) {a^=b;b^=a;a^=b;};

uint8 G_GouraudPalette[256*4];
VERTEX_INFO	v_buffer[40];
float   one  = (256.0f * 8.0f * 16384.0f);   

extern bool ThreadRender;


float UVTable[65536];

void InitUVTable()
{
	int n;
	for(n=0;n<65536;n++)
		UVTable[n] = float(n+1)*UV_MUL;
}


#define LimitBuffer(){if((CurrentTLVertex-VertexBuffer)>(MAX_TLVERTICES-32)) return;}


TEXTUREBUCKET Bucket[MAXBUCKETS];

void InitBuckets()
{
	int n;
	for(n=0;n<MAXBUCKETS;n++)
	{
		Bucket[n].tpage = 0xffffffff;
		Bucket[n].cnt = 0;
	}
}


inline int FindBucket(DWORD tpage)
{
	int n;

	// Find Bucket Already In Use
	
	for(n=0;n<MAXBUCKETS;n++)
	{
		if(Bucket[n].tpage == tpage && Bucket[n].cnt < (VERTSPERBUCKET-32))		
			return n;
	}

	// No Bucket In Use/Or Full Find Next Available

	for(n=0;n<MAXBUCKETS;n++)
	{
		if(Bucket[n].tpage == -1)		
		{			
			Bucket[n].tpage = tpage;
			return n;
		}
		
	}

	return -1;
}




void DrawBuckets(void* t)
{
	for(int n=0;n<MAXBUCKETS;n++)
	{
		if(Bucket[n].cnt != 0)
		{
			HWR_SetCurrentTexture(Bucket[n].tpage);
			DrawPrimitive(D3DPT_TRIANGLELIST,D3DVT_TLVERTEX,&Bucket[n].Vertex[0],Bucket[n].cnt,D3DDP_DONOTCLIP|D3DDP_DONOTUPDATEEXTENTS);
		}
	}
}






int visible_zclip(PHD_VBUF *vn1, PHD_VBUF *vn2, PHD_VBUF *vn3)
{
	// Cross product of point 1 and 3 with the origin to get plane through origin, 
	// then see which side of this plane point 2 lies on 
	float a,b,c;
	
	a = (vn1->yv * vn3->zv) - (vn3->yv * vn1->zv);
	b = (vn1->zv * vn3->xv) - (vn3->zv * vn1->xv);
	c = (vn1->xv * vn3->yv) - (vn3->xv * vn1->yv);
	
	if ((a * vn2->xv) + (b * vn2->yv) + (c * vn2->zv) < 0.0f)
		return(1);

	return(0);
}




/*
	Function	:	ColorFromRGB
	Useage		:	Create D3D Color From RGB Components

*/

inline DWORD ColorFromRGB(int nR,int nG,int nB)
{
	if (bBlueEffect)
	{
		nR*=RED_SHADE;
		nR>>=8;
		nG*=GREEN_SHADE;
		nG>>=8;
	}
	return RGB_MAKE(nR,nG,nB)|ALPHA_OPAQUE;
}

// EOF : ColorFromRGB


/*
	Function	:	GETR
	Useage		:	Get Red Component From D3D RGB
*/

inline int GETR(int rgb) 
{
	int r;
	r = ((rgb>>10)&0x1f)<<3;
	if(bBlueEffect)
	{
		r *= RED_SHADE;
		r>>=8;
	}

#ifdef NOLIGHTING
	return 0xff;
#else
	return r;
#endif

}

// EOF : GETR


/*
	Function	:	GETG
	Useage		:	Get Green Component From D3D RGB
*/

inline int GETG(int rgb) 
{
	int g;
	g = ((rgb>>5)&0x1f)<<3;
	if(bBlueEffect)
	{
		g *= GREEN_SHADE;
		g>>=8;
	}
	
#ifdef NOLIGHTING	
	return 0xff;
#else
	return g;
#endif

}

// EOF : GETG



/*
	Function	:	GETB
	Useage		:	Get Blue Component From D3D RGB
*/

inline int GETB(int rgb)
{
#ifdef NOLIGHTING	
	return 0xff;
#else
	return (((rgb)&0x1f)<<3);	
#endif
}

// EOF : GETB



/*
	Function	:	VertexRGB
	Useage		:	Convert 555 To 888 D3D RGB (Takes into account bBlueEffect)
*/

inline unsigned long VertexRGB(int rgb)
{
	int r,g,b;   
	r = GETR(rgb);
	g = GETG(rgb);
	b = GETB(rgb);
	return	RGBA_MAKE(r,g,b,0xff);
}

// EOF : VertexRGB



/*
	Function	:	SetBufferPtrs
	Useage		:	Set Correct Sort Buffer Ptrs For Poly & Increments 
*/

#define	SetBufferPtrs(sort,info){\
		LimitBuffer();\
		sort    = sort3dptrbf;\
		info    = info3dptrbf;\
		surfacenumbf++;\
		sort3dptrbf += 10;\
		info3dptrbf += 5;\
		};


// EOF : SetBufferPtrs




/*
	Function	:	HWI_InsertClippedPoly_Textured
	Useage		:	Add a textured poly from v_buffer to sort list
*/

void HWI_InsertClippedPoly_Textured(int nPoints,float zdepth,int nDrawType,int nTPage)
{
	float newz;
	D3DTLVERTEX* pTLV;
	VERTEX_INFO* pVI;
	sint32* sort;
	sint16* info;
	double fconv;

	pVI = v_buffer;
	
	if(App.lpZBuffer == 0 || nDrawType == DRAW_TLV_GTA || nDrawType == DRAW_TLV_WGT)
	{
		// Add Verts To Sort List
		
		SetBufferPtrs(sort,info);
	
		sort[0] = (sint32) info;
		sort[1] = (sint32) FTOL(zdepth);
		info[0] = nDrawType;
		info[1] = nTPage;
		info[2] = nPoints;
		pTLV    = CurrentTLVertex;
	
		*((D3DTLVERTEX**)(info+3)) = pTLV;
		
	
		for (int nPoint=nPoints;nPoint--;)
		{
			pTLV->sx	= pVI->x;
			pTLV->sy	= pVI->y;
			pTLV->sz	= f_a-f_boo*pVI->ooz;
			pTLV->rhw	= pVI->ooz;
			pTLV->color = RGB_MAKE(pVI->vr,pVI->vg,pVI->vb);
		
			newz		= UV_MUL/pVI->ooz;
			
			pTLV->tu	= pVI->u*newz;
			pTLV->tv	= pVI->v*newz;
		
			pVI++;
			pTLV++;
		}
	
		CurrentTLVertex=pTLV;
	
	}
	else
	{
		
		// Put Verts Into Texture Bucket
		// Fans So Do The First 3 Verts Then Add Remainder
		
		int b;
		int n,v;
		b = FindBucket(GahTextureHandle[nTPage]);
		if(b == -1) return; 
	
		for(n=0;n<3;n++)
		{
			v = Bucket[b].cnt;			
			
			Bucket[b].Vertex[v].sx    = pVI->x;
			Bucket[b].Vertex[v].sy    = pVI->y;
			Bucket[b].Vertex[v].sz    = f_a-f_boo*pVI->ooz;
			Bucket[b].Vertex[v].rhw   = pVI->ooz;
			Bucket[b].Vertex[v].color = RGB_MAKE(pVI->vr,pVI->vg,pVI->vb);
			newz = UV_MUL/pVI->ooz;
			Bucket[b].Vertex[v].tu	  = pVI->u*newz;
			Bucket[b].Vertex[v].tv    = pVI->v*newz;
			
			pVI++;
			Bucket[b].cnt++;
		}

		nPoints -= 3;
		
		pVI--;

		for(n=nPoints;n>0;n--)
		{
			v = Bucket[b].cnt;
			
			Bucket[b].Vertex[v+0].sx    = v_buffer->x;
			Bucket[b].Vertex[v+0].sy    = v_buffer->y;
			Bucket[b].Vertex[v+0].sz    = f_a-f_boo*v_buffer->ooz;
			Bucket[b].Vertex[v+0].rhw     = v_buffer->ooz;
			Bucket[b].Vertex[v+0].color = RGB_MAKE(v_buffer->vr,v_buffer->vg,v_buffer->vb);

			newz = UV_MUL/v_buffer->ooz;
			Bucket[b].Vertex[v+0].tu	= v_buffer->u*newz;
			Bucket[b].Vertex[v+0].tv    = v_buffer->v*newz;
		
			Bucket[b].Vertex[v+1].sx    = pVI->x;
			Bucket[b].Vertex[v+1].sy    = pVI->y;
			Bucket[b].Vertex[v+1].sz    = f_a-f_boo*pVI->ooz;
			Bucket[b].Vertex[v+1].rhw   = pVI->ooz;
			
			Bucket[b].Vertex[v+1].color = RGB_MAKE(pVI->vr,pVI->vg,pVI->vb);
			newz = UV_MUL/pVI->ooz;
			Bucket[b].Vertex[v+1].tu	= pVI->u*newz;
			Bucket[b].Vertex[v+1].tv    = pVI->v*newz;
			
			pVI++;

			Bucket[b].Vertex[v+2].sx    = pVI->x;
			Bucket[b].Vertex[v+2].sy    = pVI->y;
			Bucket[b].Vertex[v+2].sz    = f_a-f_boo*pVI->ooz;
			Bucket[b].Vertex[v+2].rhw   = pVI->ooz;

			Bucket[b].Vertex[v+2].color = RGB_MAKE(pVI->vr,pVI->vg,pVI->vb);
			newz = UV_MUL/pVI->ooz;
			Bucket[b].Vertex[v+2].tu	= pVI->u*newz;
			Bucket[b].Vertex[v+2].tv    = pVI->v*newz;

			Bucket[b].cnt+= 3;
		}

	}
}


// EOF : HWI_InsertClippedPoly_Textured



inline void PHD_VBUF_To_POINT_INFO(PHD_VBUF* pV,POINT_INFO* p,uint16* pUV)
{
	p->xv = pV->xv;
	p->yv = pV->yv;
	p->zv = pV->zv;
	p->ooz= pV->ooz;
	p->xs = pV->xs;
	p->ys = pV->ys;
	p->vr = GETR(pV->g);
	p->vg = GETG(pV->g);
	p->vb = GETB(pV->g);
	p->u  = float(pUV[0]);
	p->v  = float(pUV[1]);
}



inline void PHD_VBUF_To_POINT_INFO(PHD_VBUF* pV,POINT_INFO* p)
{
	p->xv = pV->xv;
	p->yv = pV->yv;
	p->zv = pV->zv;
	p->ooz= pV->ooz;
	p->xs = pV->xs;
	p->ys = pV->ys;
	p->vr = GETR(pV->g);
	p->vg = GETG(pV->g);
	p->vb = GETB(pV->g);
}



inline void PHD_VBUF_To_VERTEX_INFO(PHD_VBUF* pV,VERTEX_INFO* p,uint16* pUV)
{
	p->x   = pV->xs;
	p->y   = pV->ys;
	p->ooz = pV->ooz;
	p->u   = pUV[0]*pV->ooz;
	p->v   = pUV[1]*pV->ooz;
	p->vr  = GETR(pV->g);
	p->vg  = GETG(pV->g);
	p->vb  = GETB(pV->g);
}



inline void PHD_VBUF_To_VERTEX_INFO(PHD_VBUF* pV,VERTEX_INFO* p)
{
	p->x   = pV->xs;
	p->y   = pV->ys;
	p->ooz = pV->ooz;
	p->vr  = GETR(pV->g);
	p->vg  = GETG(pV->g);
	p->vb  = GETB(pV->g);
}


inline void PHD_VBUF_To_TLVERT(PHD_VBUF* pV,D3DTLVERTEX* p,uint16* pUV)
{
	p->sx    = pV->xs;
	p->sy    = pV->ys;
	p->sz	 = f_a-f_boo*pV->ooz;
	p->rhw   = pV->ooz;
	p->color = VertexRGB(pV->g); 
	p->tu    = UVTable[pUV[0]]; //pUV[0]*UV_MUL;
	p->tv    = UVTable[pUV[1]]; //pUV[1]*UV_MUL;
}


inline void PHD_VBUF_To_TLVERT(PHD_VBUF* pV,D3DTLVERTEX* p)
{
	p->sx    = pV->xs;
	p->sy    = pV->ys;
	p->sz	 = f_a-f_boo*pV->ooz;
	p->rhw   = pV->ooz;
	p->color = VertexRGB(pV->g); 
}



void HWI_InsertGT3_Sorted(PHD_VBUF* pV1,PHD_VBUF* pV2,PHD_VBUF* pV3,PHDTEXTURESTRUCT* pTex,uint16* pUV1,uint16* pUV2,uint16* pUV3,enum sort_type nSortType,unsigned short dbl)
{
	int nPoints;
	float zdepth;
	D3DTLVERTEX* pTLV;
	POINT_INFO aPoint[3];
	int drawtype;

	if ((pV1->clip|pV2->clip|pV3->clip)<0)
	{
		PHD_VBUF_To_POINT_INFO(pV1,&aPoint[0],pUV1);
		PHD_VBUF_To_POINT_INFO(pV2,&aPoint[1],pUV2);
		PHD_VBUF_To_POINT_INFO(pV3,&aPoint[2],pUV3);
		
		nPoints = RoomZedClipper(3,aPoint,v_buffer);
		
		if (nPoints) goto doxyclip;
		
		return;
	}
	

	IF_NOT_VIS(pV1,pV2,pV3)
	{
		if(dbl)
		{
			swap((long&)pV2,(long&)pV3);
			swap((long&)pUV2,(long&)pUV3);
		}
		else
			return;
	}


	if (pV1->clip|pV2->clip|pV3->clip)
	{
		PHD_VBUF_To_VERTEX_INFO(pV1,&v_buffer[0],pUV1);
		PHD_VBUF_To_VERTEX_INFO(pV2,&v_buffer[1],pUV2);
		PHD_VBUF_To_VERTEX_INFO(pV3,&v_buffer[2],pUV3);
	
		nPoints = 3;

doxyclip:

		nPoints = RoomXYGUVClipper(nPoints,v_buffer);
		
		if(nPoints==0) return;
		
		switch(nSortType)
		{
			case FAR_SORT:
				TRIA_DEPTH_FAR(pV1,pV2,pV3);
				break;
			case MID_SORT:
				TRIA_DEPTH_MID(pV1,pV2,pV3);
				break;
			default:
				zdepth=BACK_DEPTH;
		}
	
	
		drawtype = pTex->drawtype;

		if(drawtype<=1)
			drawtype += DRAW_TLV_GT;
		else			
			drawtype = DRAW_TLV_GTA;

		HWI_InsertClippedPoly_Textured(nPoints,zdepth,drawtype,pTex->tpage);
	
		return;
	}
	
	switch(nSortType)
	{
		case FAR_SORT:
			TRIA_DEPTH_FAR(pV1,pV2,pV3);
			break;
		case MID_SORT:
			TRIA_DEPTH_MID(pV1,pV2,pV3);
			break;
		default:
			zdepth=BACK_DEPTH;
	}


	drawtype = pTex->drawtype;

	if(drawtype<=1)
		drawtype += DRAW_TLV_GT;
	else			
		drawtype =DRAW_TLV_GTA;
	

	if(App.lpZBuffer == 0 || drawtype == DRAW_TLV_GTA || drawtype == DRAW_TLV_WGT)
	{
		sint32* sort;
		sint16* info;
		double fconv;
		SetBufferPtrs(sort,info);
		sort[0]=(sint32)info;
		sort[1]=(sint32)FTOL(zdepth);
		info[0]=drawtype;
		info[1]=pTex->tpage;
		info[2]=3;
		pTLV = CurrentTLVertex;
		
		*((D3DTLVERTEX**)(info+3)) = pTLV;

		PHD_VBUF_To_TLVERT(pV1,pTLV,pUV1);
		pTLV++;
		PHD_VBUF_To_TLVERT(pV2,pTLV,pUV2);
		pTLV++;
		PHD_VBUF_To_TLVERT(pV3,pTLV,pUV3);
		pTLV++;

		CurrentTLVertex=pTLV;
	}
	else
	{
		int b;
		b = FindBucket(GahTextureHandle[pTex->tpage]);
		if(b == -1) return;
			
		PHD_VBUF_To_TLVERT(pV1,&Bucket[b].Vertex[Bucket[b].cnt],pUV1);
		Bucket[b].cnt++;
		PHD_VBUF_To_TLVERT(pV2,&Bucket[b].Vertex[Bucket[b].cnt],pUV2);
		Bucket[b].cnt++;
		PHD_VBUF_To_TLVERT(pV3,&Bucket[b].Vertex[Bucket[b].cnt],pUV3);
		Bucket[b].cnt++;
	}
}




void HWI_InsertGT4_Sorted(PHD_VBUF* pV1,PHD_VBUF* pV2,PHD_VBUF* pV3,PHD_VBUF* pV4,PHDTEXTURESTRUCT* pTex,enum sort_type nSortType,unsigned short dbl)
{
	float zdepth;
	D3DTLVERTEX* pTLV;
	int drawtype;

	if (pV1->clip&pV2->clip&pV3->clip&pV4->clip)
		return;
	
	if ((pV1->clip|pV2->clip|pV3->clip|pV4->clip)<0)
	{
		if(!dbl)
		{
			if (!visible_zclip(pV1,pV2,pV3))
				return;
		
			HWI_InsertGT3_Sorted(pV1,pV2,pV3,pTex,&pTex->u1,&pTex->u2,&pTex->u3,nSortType,0);
			HWI_InsertGT3_Sorted(pV1,pV3,pV4,pTex,&pTex->u1,&pTex->u3,&pTex->u4,nSortType,0);
		}
		else
		{
			if (visible_zclip(pV1,pV2,pV3))
			{
				HWI_InsertGT3_Sorted(pV1,pV2,pV3,pTex,&pTex->u1,&pTex->u2,&pTex->u3,nSortType,0);
				HWI_InsertGT3_Sorted(pV1,pV3,pV4,pTex,&pTex->u1,&pTex->u3,&pTex->u4,nSortType,0);
			}
						
			swap((long&)pV1,(long&)pV3);
			
			if (visible_zclip(pV1,pV2,pV3))
			{
				HWI_InsertGT3_Sorted(pV1,pV2,pV3,pTex,&pTex->u3,&pTex->u2,&pTex->u1,nSortType,0);
				HWI_InsertGT3_Sorted(pV1,pV3,pV4,pTex,&pTex->u3,&pTex->u1,&pTex->u4,nSortType,0);
			}
			
		}
		return;
	}
	
	IF_NOT_VIS(pV1,pV2,pV3)
	{
		if(dbl)
		{
			swap((long&)pV1,(long&)pV3);

			if (pV1->clip|pV2->clip|pV3->clip|pV4->clip)
			{
				HWI_InsertGT3_Sorted(pV1,pV2,pV3,pTex,&pTex->u3,&pTex->u2,&pTex->u1,nSortType,0);
				HWI_InsertGT3_Sorted(pV1,pV3,pV4,pTex,&pTex->u3,&pTex->u1,&pTex->u4,nSortType,0);
				return;
			}
		}
		else
			return;
	}
	else
		dbl = 0;

	
	if (pV1->clip|pV2->clip|pV3->clip|pV4->clip)
	{
		HWI_InsertGT3_Sorted(pV1,pV2,pV3,pTex,&pTex->u1,&pTex->u2,&pTex->u3,nSortType,dbl);
		HWI_InsertGT3_Sorted(pV1,pV3,pV4,pTex,&pTex->u1,&pTex->u3,&pTex->u4,nSortType,dbl);
		return;
	}
	
	switch (nSortType)
	{
		case FAR_SORT:
			QUAD_DEPTH_FAR(pV1,pV2,pV3,pV4);
			break;
		case MID_SORT:
			QUAD_DEPTH_MID(pV1,pV2,pV3,pV4);
		break;
		default:
			zdepth=BACK_DEPTH;
	}
	
	drawtype = pTex->drawtype;

	if(drawtype<=1)
		drawtype += DRAW_TLV_GT;
	else			
		drawtype = DRAW_TLV_GTA;


	if(App.lpZBuffer == 0 || drawtype == DRAW_TLV_GTA || drawtype == DRAW_TLV_WGT)
	{

		sint32* sort;
		sint16* info;
		double fconv;

		SetBufferPtrs(sort,info);

		sort[0]=(sint32)info;
		sort[1]=(sint32)FTOL(zdepth);

		info[0] = drawtype;
		info[1] = pTex->tpage;
		info[2] = 4;
	
		pTLV=CurrentTLVertex;
		*((D3DTLVERTEX**)(info+3))=pTLV;

		if(!dbl)
		{
			PHD_VBUF_To_TLVERT(pV1,pTLV);
			pTLV->tu = UVTable[pTex->u1]; //*UV_MUL;
			pTLV->tv = UVTable[pTex->v1];//*UV_MUL;
			pTLV++;
			PHD_VBUF_To_TLVERT(pV2,pTLV);
			pTLV->tu = UVTable[pTex->u2];//*UV_MUL;
			pTLV->tv = UVTable[pTex->v2];//*UV_MUL;
			pTLV++;
			PHD_VBUF_To_TLVERT(pV3,pTLV);
			pTLV->tu = UVTable[pTex->u3];//*UV_MUL;
			pTLV->tv = UVTable[pTex->v3];//*UV_MUL;
		}
		else
		{
			PHD_VBUF_To_TLVERT(pV1,pTLV);
			pTLV->tu=UVTable[pTex->u3];//*UV_MUL;
			pTLV->tv=UVTable[pTex->v3];//*UV_MUL;
			pTLV++;
			PHD_VBUF_To_TLVERT(pV2,pTLV);
			pTLV->tu=UVTable[pTex->u2];//*UV_MUL;
			pTLV->tv=UVTable[pTex->v2];//*UV_MUL;
			pTLV++;
			PHD_VBUF_To_TLVERT(pV3,pTLV);
			pTLV->tu=UVTable[pTex->u1];//*UV_MUL;
			pTLV->tv=UVTable[pTex->v1];//*UV_MUL;
		}

		pTLV++;
		PHD_VBUF_To_TLVERT(pV4,pTLV);
		pTLV->tu=UVTable[pTex->u4];//*UV_MUL;
		pTLV->tv=UVTable[pTex->v4];//*UV_MUL;
		pTLV++;

		CurrentTLVertex=pTLV;

	}	
	else
	{
		int b;
		b = FindBucket(GahTextureHandle[pTex->tpage]);
		if(b == -1) return;
		
		if(!dbl)
		{
			PHD_VBUF_To_TLVERT(pV1,&Bucket[b].Vertex[Bucket[b].cnt]);
			Bucket[b].Vertex[Bucket[b].cnt].tu=UVTable[pTex->u1]; //*UV_MUL;
			Bucket[b].Vertex[Bucket[b].cnt].tv=UVTable[pTex->v1];//*UV_MUL;
			Bucket[b].cnt++;

			PHD_VBUF_To_TLVERT(pV2,&Bucket[b].Vertex[Bucket[b].cnt]);
		
			Bucket[b].Vertex[Bucket[b].cnt].tu=UVTable[pTex->u2]; //*UV_MUL;
			Bucket[b].Vertex[Bucket[b].cnt].tv=UVTable[pTex->v2]; //*UV_MUL;
			
			Bucket[b].cnt++;
			
			PHD_VBUF_To_TLVERT(pV3,&Bucket[b].Vertex[Bucket[b].cnt]);
		
			Bucket[b].Vertex[Bucket[b].cnt].tu=UVTable[pTex->u3];//*UV_MUL;
			Bucket[b].Vertex[Bucket[b].cnt].tv=UVTable[pTex->v3];//*UV_MUL;
	
			Bucket[b].cnt++;


			PHD_VBUF_To_TLVERT(pV1,&Bucket[b].Vertex[Bucket[b].cnt]);
		
			Bucket[b].Vertex[Bucket[b].cnt].tu=UVTable[pTex->u1];//*UV_MUL;
			Bucket[b].Vertex[Bucket[b].cnt].tv=UVTable[pTex->v1];//*UV_MUL;
				
			Bucket[b].cnt++;

		
			PHD_VBUF_To_TLVERT(pV3,&Bucket[b].Vertex[Bucket[b].cnt]);
		
			Bucket[b].Vertex[Bucket[b].cnt].tu=UVTable[pTex->u3];//*UV_MUL;
			Bucket[b].Vertex[Bucket[b].cnt].tv=UVTable[pTex->v3];//*UV_MUL;
		
			Bucket[b].cnt++;

			PHD_VBUF_To_TLVERT(pV4,&Bucket[b].Vertex[Bucket[b].cnt]);
			
			Bucket[b].Vertex[Bucket[b].cnt].tu=UVTable[pTex->u4];//*UV_MUL;
			Bucket[b].Vertex[Bucket[b].cnt].tv=UVTable[pTex->v4];//*UV_MUL;
			
			Bucket[b].cnt++;
		}
		else
		{

			PHD_VBUF_To_TLVERT(pV1,&Bucket[b].Vertex[Bucket[b].cnt]);
		
			Bucket[b].Vertex[Bucket[b].cnt].tu=UVTable[pTex->u3];//*UV_MUL;
			Bucket[b].Vertex[Bucket[b].cnt].tv=UVTable[pTex->v3];//*UV_MUL;
				
			Bucket[b].cnt++;

			PHD_VBUF_To_TLVERT(pV2,&Bucket[b].Vertex[Bucket[b].cnt]);
		
			Bucket[b].Vertex[Bucket[b].cnt].tu=UVTable[pTex->u2];//*UV_MUL;
			Bucket[b].Vertex[Bucket[b].cnt].tv=UVTable[pTex->v2];//*UV_MUL;
			
			Bucket[b].cnt++;
			
			PHD_VBUF_To_TLVERT(pV3,&Bucket[b].Vertex[Bucket[b].cnt]);
		
			Bucket[b].Vertex[Bucket[b].cnt].tu=UVTable[pTex->u1];//*UV_MUL;
			Bucket[b].Vertex[Bucket[b].cnt].tv=UVTable[pTex->v1];//*UV_MUL;
		
			Bucket[b].cnt++;

			PHD_VBUF_To_TLVERT(pV1,&Bucket[b].Vertex[Bucket[b].cnt]);
		
			Bucket[b].Vertex[Bucket[b].cnt].tu=UVTable[pTex->u3];//*UV_MUL;
			Bucket[b].Vertex[Bucket[b].cnt].tv=UVTable[pTex->v3];//*UV_MUL;
				
			Bucket[b].cnt++;

		
			PHD_VBUF_To_TLVERT(pV3,&Bucket[b].Vertex[Bucket[b].cnt]);
		
			Bucket[b].Vertex[Bucket[b].cnt].tu=UVTable[pTex->u1];//*UV_MUL;
			Bucket[b].Vertex[Bucket[b].cnt].tv=UVTable[pTex->v1];//*UV_MUL;

			Bucket[b].cnt++;

			PHD_VBUF_To_TLVERT(pV4,&Bucket[b].Vertex[Bucket[b].cnt]);
		
			Bucket[b].Vertex[Bucket[b].cnt].tu=UVTable[pTex->u4];//*UV_MUL;
			Bucket[b].Vertex[Bucket[b].cnt].tv=UVTable[pTex->v4];//*UV_MUL;
			
			Bucket[b].cnt++;
		}
	}
}



sint16* HWI_InsertObjectGT4_Sorted(sint16* pFaceInfo,int nFaces,enum sort_type nSortType)
{
	while (nFaces)
	{
		unsigned short dbl = (pFaceInfo[4]>>15)&1;

		HWI_InsertGT4_Sorted(vbuf+pFaceInfo[0],vbuf+pFaceInfo[1],vbuf+pFaceInfo[2],vbuf+pFaceInfo[3],
							 phdtextinfo+(pFaceInfo[4]&0x7fff),
							 nSortType,dbl);
		nFaces--;
		pFaceInfo+=5;
	}
	return pFaceInfo;
}



sint16* HWI_InsertObjectGT3_Sorted(sint16* pFaceInfo,int nFaces,enum sort_type nSortType)
{
	while(nFaces)
	{
		PHDTEXTURESTRUCT* pTex=phdtextinfo+(pFaceInfo[3]&0x7fff);
		unsigned short dbl = (pFaceInfo[3]>>15)&1;
		HWI_InsertGT3_Sorted(vbuf+pFaceInfo[0],vbuf+pFaceInfo[1],vbuf+pFaceInfo[2],
							 pTex,
							 &pTex->u1,&pTex->u2,&pTex->u3,
							 nSortType,dbl);
		nFaces--;
		pFaceInfo+=4;
	}
	return pFaceInfo;
}





void HWI_InsertPoly_Gouraud(int nPoints,float zdepth,int nR,int nG,int nB,int nDrawType)
{
	D3DTLVERTEX* pTLV;
	uint32 dwAnd;

	dwAnd=(nDrawType==DRAW_TLV_GA)?0x80ffffff:0xffffffff;

	VERTEX_INFO* pVI=v_buffer;
	
	if(App.lpZBuffer == 0 || nDrawType == DRAW_TLV_GA)
	{
		sint32* sort;
		sint16* info;
		double fconv;
		
		SetBufferPtrs(sort,info);

		sort[0]=(sint32)info;
		sort[1]=(sint32)FTOL(zdepth);
		info[0]=nDrawType;
		info[1]=0;
		info[2]=nPoints;
		pTLV=CurrentTLVertex;
		*((D3DTLVERTEX**)(info+3))=pTLV;
	
		for (int nPoint=nPoints;nPoint--;)
		{
			pTLV->sx=pVI->x;
			pTLV->sy=pVI->y;
			pTLV->sz=f_a-f_boo*pVI->ooz;
			pTLV->rhw=pVI->ooz;

			pVI->vr = (pVI->vr * nR)>>8;
			pVI->vg = (pVI->vg * nG)>>8;
			pVI->vb = (pVI->vb * nB)>>8;

			pTLV->color = RGBA_MAKE(pVI->vr,pVI->vg,pVI->vb,0xff)&dwAnd;
			pVI++;
			pTLV++;
		}

		CurrentTLVertex=pTLV;
	}
	else
	{
		// Put Verts Into Texture Bucket
		// Fans So Do The First 3 Verts Then Add Remainder
		
		int b;
		int n,v;
		b = FindBucket(0);
		if(b == -1) return;

		for(n=0;n<3;n++)
		{
			v = Bucket[b].cnt;			
		
			pVI->vr = (pVI->vr * nR)>>8;
			pVI->vg = (pVI->vg * nG)>>8;
			pVI->vb = (pVI->vb * nB)>>8;

			Bucket[b].Vertex[v].sx    = pVI->x;
			Bucket[b].Vertex[v].sy    = pVI->y;
			Bucket[b].Vertex[v].sz    = f_a-f_boo*pVI->ooz;
			Bucket[b].Vertex[v].rhw   = pVI->ooz;
			Bucket[b].Vertex[v].color = RGBA_MAKE(pVI->vr,pVI->vg,pVI->vb,0xff)&dwAnd;
		
			pVI++;
			Bucket[b].cnt++;
		}

		nPoints -= 3;
		
		pVI--;

		for(n=nPoints;n>0;n--)
		{
			v = Bucket[b].cnt;
						
			Bucket[b].Vertex[v+0].sx    = v_buffer->x;
			Bucket[b].Vertex[v+0].sy    = v_buffer->y;
			Bucket[b].Vertex[v+0].sz    = f_a-f_boo*v_buffer->ooz;
			Bucket[b].Vertex[v+0].rhw   = v_buffer->ooz;
			Bucket[b].Vertex[v+0].color = RGBA_MAKE(v_buffer->vr,v_buffer->vg,v_buffer->vb,0xff)&dwAnd;

			Bucket[b].Vertex[v+1].sx    = pVI->x;
			Bucket[b].Vertex[v+1].sy    = pVI->y;
			Bucket[b].Vertex[v+1].sz    = f_a-f_boo*pVI->ooz;
			Bucket[b].Vertex[v+1].rhw   = pVI->ooz;
			Bucket[b].Vertex[v+1].color = RGBA_MAKE(pVI->vr,pVI->vg,pVI->vb,0xff)&dwAnd;
			
			pVI++;

			pVI->vr = (pVI->vr * nR)>>8;
			pVI->vg = (pVI->vg * nG)>>8;
			pVI->vb = (pVI->vb * nB)>>8;
			
			Bucket[b].Vertex[v+2].sx    = pVI->x;
			Bucket[b].Vertex[v+2].sy    = pVI->y;
			Bucket[b].Vertex[v+2].sz    = f_a-f_boo*pVI->ooz;
			Bucket[b].Vertex[v+2].rhw   = pVI->ooz;

			Bucket[b].Vertex[v+2].color = RGBA_MAKE(pVI->vr,pVI->vg,pVI->vb,0xff)&dwAnd;

			Bucket[b].cnt+= 3;
		}

	}

}





sint16* HWI_InsertObjectG4_Sorted(sint16* pFaceInfo,int nFaces,enum sort_type nSortType)
{
	int nPoints;
	uint8* pPE;
	float zdepth;

	for (;nFaces;--nFaces,pFaceInfo+=5)
	{

		PHD_VBUF* pV1=vbuf+pFaceInfo[0];
		PHD_VBUF* pV2=vbuf+pFaceInfo[1];
		PHD_VBUF* pV3=vbuf+pFaceInfo[2];
		PHD_VBUF* pV4=vbuf+pFaceInfo[3];

		if (pV1->clip&pV2->clip&pV3->clip&pV4->clip)
			continue;
		
		if ((pV1->clip|pV2->clip|pV3->clip|pV4->clip)<0)
		{
			
			if (!visible_zclip(pV1,pV2,pV3))
				continue;
			
			POINT_INFO aPoint[4];

			PHD_VBUF_To_POINT_INFO(pV1,&aPoint[0]);
			PHD_VBUF_To_POINT_INFO(pV2,&aPoint[1]);
			PHD_VBUF_To_POINT_INFO(pV3,&aPoint[2]);
			PHD_VBUF_To_POINT_INFO(pV4,&aPoint[3]);

			nPoints=RoomZedClipper(4,aPoint,v_buffer);
			
			if (nPoints)
				nPoints=XYGClipper(nPoints,v_buffer);
		}
		else
		{
			IF_NOT_VIS(pV1,pV2,pV3)
				continue;
		
	
			PHD_VBUF_To_VERTEX_INFO(pV1,&v_buffer[0]);
			PHD_VBUF_To_VERTEX_INFO(pV2,&v_buffer[1]);
			PHD_VBUF_To_VERTEX_INFO(pV3,&v_buffer[2]);
			PHD_VBUF_To_VERTEX_INFO(pV4,&v_buffer[3]);
	
			nPoints=4;
			
			if (pV1->clip|pV2->clip|pV3->clip|pV4->clip)
				nPoints = XYGClipper(nPoints,v_buffer);
		}
		
		if (nPoints)
		{
			pPE=G_GouraudPalette+((pFaceInfo[4]>>6)&0x3fc);
	
			switch (nSortType)
			{
				case FAR_SORT:
					QUAD_DEPTH_FAR(pV1,pV2,pV3,pV4);
					break;
				case MID_SORT:
					QUAD_DEPTH_MID(pV1,pV2,pV3,pV4);
					break;
				default:
					zdepth=BACK_DEPTH;
			}
			HWI_InsertPoly_Gouraud(nPoints,zdepth,pPE[0],pPE[1],pPE[2],DRAW_TLV_G);
		}
	}
	return pFaceInfo;
}




sint16* HWI_InsertObjectG3_Sorted(sint16* pFaceInfo,int nFaces,enum sort_type nSortType)
{
	int nPoints;
	uint8* pPE;
	float zdepth;

	for (;nFaces;--nFaces,pFaceInfo+=4)
	{

		PHD_VBUF* pV1=vbuf+pFaceInfo[0];
		PHD_VBUF* pV2=vbuf+pFaceInfo[1];
		PHD_VBUF* pV3=vbuf+pFaceInfo[2];
	
		if (pV1->clip&pV2->clip&pV3->clip)
			continue;
		
		if ((pV1->clip|pV2->clip|pV3->clip)<0)
		{
		
			if (!visible_zclip(pV1,pV2,pV3))
				continue;

			POINT_INFO aPoint[3];
		
			PHD_VBUF_To_POINT_INFO(pV1,&aPoint[0]);
			PHD_VBUF_To_POINT_INFO(pV2,&aPoint[1]);
			PHD_VBUF_To_POINT_INFO(pV3,&aPoint[2]);

			nPoints=RoomZedClipper(3,aPoint,v_buffer);
			
			if (nPoints)
				nPoints=XYGClipper(nPoints,v_buffer);

		}
		else
		{
			IF_NOT_VIS(pV1,pV2,pV3)
				continue;
		
			PHD_VBUF_To_VERTEX_INFO(pV1,&v_buffer[0]);
			PHD_VBUF_To_VERTEX_INFO(pV2,&v_buffer[1]);
			PHD_VBUF_To_VERTEX_INFO(pV3,&v_buffer[2]);

			nPoints = 3;
			 
			if (pV1->clip|pV2->clip|pV3->clip)
				nPoints=XYGClipper(nPoints,v_buffer);
		}
		
		if(nPoints)
		{
			pPE=G_GouraudPalette+((pFaceInfo[3]>>6)&0x3fc);
			switch (nSortType)
			{
				case FAR_SORT:
					TRIA_DEPTH_FAR(pV1,pV2,pV3);
					break;
				case MID_SORT:
					TRIA_DEPTH_MID(pV1,pV2,pV3);
					break;
				default:
					zdepth=BACK_DEPTH;
			}

			HWI_InsertPoly_Gouraud(nPoints,zdepth,pPE[0],pPE[1],pPE[2],DRAW_TLV_G);
		}
	}
	return pFaceInfo;
}





void HWI_InsertSprite_Sorted(int nZDepth,int nX1,int nY1,int nX2,int nY2,int nSprite,int nShade,int nShade1,int drawtype,int offset)
{

	if (nX2<=nX1 || nY2<=nY1 || nX2<=0 || nY2<=0 ||
		nX1>=phd_winxmax || nY1>=phd_winymax)
		return;

	nX1 += phd_winxmin;
	nX2 += phd_winxmin;
	nY1 += phd_winymin;
	nY2 += phd_winymin;

	if (nZDepth<phd_znear) nZDepth=phd_znear;
	if (nZDepth>=phd_zfar) return;
	
	float fZDepth=float(nZDepth);
	float ooz=one/fZDepth;
	float fShade=float(nShade);
	PHDSPRITESTRUCT* pSprite=phdsprinfo+nSprite;
	float fU1=float((pSprite->offset<<8)&0xff00);
	float fV1=float(pSprite->offset&0xff00);
	float fU2=fU1+pSprite->width;
	float fV2=fV1+pSprite->height;
	float fUVAdd=float(App.nUVAdd); 
	
	
	fU1+=fUVAdd;
	fU2-=fUVAdd;
	fV1+=fUVAdd;
	fV2-=fUVAdd;
	fU1*=ooz;
	fV1*=ooz;
	fU2*=ooz;
	fV2*=ooz;
	
	v_buffer[0].x=float(nX1+offset);
	v_buffer[0].y=float(nY1);
	v_buffer[0].ooz=ooz;
	v_buffer[0].vr = GETR(nShade);
	v_buffer[0].vg = GETG(nShade);
	v_buffer[0].vb = GETB(nShade);
	v_buffer[0].u=fU1;
	v_buffer[0].v=fV1;

	v_buffer[1].x=float(nX2+offset);
	v_buffer[1].y=float(nY1);
	v_buffer[1].ooz=ooz;
	v_buffer[1].u=fU2;
	v_buffer[1].v=fV1;
	v_buffer[1].vr = GETR(nShade);
	v_buffer[1].vg = GETG(nShade);
	v_buffer[1].vb = GETB(nShade);

	if(nShade1 != -1) nShade = nShade1;
	
	v_buffer[2].x=float(nX2);
	v_buffer[2].y=float(nY2);
	v_buffer[2].ooz=ooz;
	v_buffer[2].u=fU2;
	v_buffer[2].v=fV2;
	v_buffer[2].vr = GETR(nShade);
	v_buffer[2].vg = GETG(nShade);
	v_buffer[2].vb = GETB(nShade);


	v_buffer[3].x=float(nX1);
	v_buffer[3].y=float(nY2);
	v_buffer[3].ooz=ooz;
	v_buffer[3].u=fU1;
	v_buffer[3].v=fV2;
	v_buffer[3].vr = GETR(nShade);
	v_buffer[3].vg = GETG(nShade);
	v_buffer[3].vb = GETB(nShade);

	int nPoints=4;
	if (nX1<phd_winxmin || nX2>phd_winxmin+phd_winwidth || nY1<phd_winymin || nY2>phd_winymin+phd_winheight)
	{
		phd_leftfloat = (float)phd_winxmin;
		phd_rightfloat = (float)(phd_winxmin+phd_winwidth);
		phd_topfloat = (float)phd_winymin;
		phd_bottomfloat = (float)(phd_winymin+phd_winheight);
		nPoints=RoomXYGUVClipper(nPoints,v_buffer);
	}
	if (nPoints)
	{
		bool tOldBlueEffect=bBlueEffect;		// temporarily turn off blue effect for sprites
		bBlueEffect=false;
		HWI_InsertClippedPoly_Textured(nPoints,fZDepth,drawtype,pSprite->tpage);
		bBlueEffect=tOldBlueEffect;
	}
}





void HWI_InsertFlatRect_Sorted(sint32 nX1,sint32 nY1,sint32 nX2,sint32 nY2,int nZDepth,int nColour)
{
	sint32* sort;
	sint16* info;
	uint8*  pPE=game_palette+nColour*3;
	D3DTLVERTEX* pTLV;
	double fconv;

	if ((nX2<=nX1)||(nY2<=nY1))	return;
	if (nX1<phd_winxmin) nX1 = phd_winxmin;
	if (nY1<phd_winymin) nY1 = phd_winymin;
	if (nX2>(phd_winxmin+phd_winwidth))  nX2 = phd_winxmin+phd_winwidth;
	if (nY2>(phd_winymin+phd_winheight)) nX2 = phd_winymin+phd_winheight;
	
	if(App.lpZBuffer == 0)
	{
		SetBufferPtrs(sort,info);

		sort[0]=(sint32)info;
		sort[1]=(sint32)FTOL(nZDepth);
		info[0]=DRAW_TLV_G;
		info[1]=0;
		info[2]=4;

		pTLV = CurrentTLVertex;
		*((D3DTLVERTEX**)(info+3)) = pTLV;
		pTLV[0].sx=pTLV[3].sx=float(nX1);
		pTLV[0].sy=pTLV[1].sy=float(nY1);
		pTLV[1].sx=pTLV[2].sx=float(nX2);
		pTLV[3].sy=pTLV[2].sy=float(nY2);
		pTLV[0].color=pTLV[1].color=pTLV[2].color=pTLV[3].color=ColorFromRGB(pPE[0],pPE[1],pPE[2]);
		pTLV[0].sz = 0.0f;	
		pTLV[1].sz = 0.0f;	
		pTLV[2].sz = 0.0f;	
		pTLV[3].sz = 0.0f;	
	
		CurrentTLVertex=pTLV+4;
	
	}
	else
	{
		int b;
		int v;
		b = FindBucket(0);
		if(b == -1) return;

		v = Bucket[b].cnt;			
		
		float fooz=one/float(nZDepth);
		float z = f_a-f_boo*fooz;
		
		Bucket[b].Vertex[v+0].sx    = float(nX1);
		Bucket[b].Vertex[v+0].sy    = float(nY1);
		Bucket[b].Vertex[v+0].sz    = z;
		Bucket[b].Vertex[v+0].rhw   = fooz;
		Bucket[b].Vertex[v+0].color = ColorFromRGB(pPE[0],pPE[1],pPE[2]);

		Bucket[b].Vertex[v+1].sx    = float(nX2);
		Bucket[b].Vertex[v+1].sy    = float(nY1);
		Bucket[b].Vertex[v+1].sz    = z;
		Bucket[b].Vertex[v+1].rhw   = fooz;
		Bucket[b].Vertex[v+1].color = ColorFromRGB(pPE[0],pPE[1],pPE[2]);

		Bucket[b].Vertex[v+2].sx    = float(nX2);
		Bucket[b].Vertex[v+2].sy    = float(nY2);
		Bucket[b].Vertex[v+2].sz    = z;
		Bucket[b].Vertex[v+2].rhw   = fooz;
		Bucket[b].Vertex[v+2].color = ColorFromRGB(pPE[0],pPE[1],pPE[2]);

		Bucket[b].Vertex[v+3].sx    = float(nX1);
		Bucket[b].Vertex[v+3].sy    = float(nY1);
		Bucket[b].Vertex[v+3].sz    = z;
		Bucket[b].Vertex[v+3].rhw   = fooz;
		Bucket[b].Vertex[v+3].color = ColorFromRGB(pPE[0],pPE[1],pPE[2]);

		Bucket[b].Vertex[v+4].sx    = float(nX2);
		Bucket[b].Vertex[v+4].sy    = float(nY2);
		Bucket[b].Vertex[v+4].sz    = z;
		Bucket[b].Vertex[v+4].rhw   = fooz;
		Bucket[b].Vertex[v+4].color = ColorFromRGB(pPE[0],pPE[1],pPE[2]);

		Bucket[b].Vertex[v+5].sx    = float(nX1);
		Bucket[b].Vertex[v+5].sy    = float(nY2);
		Bucket[b].Vertex[v+5].sz    = z;
		Bucket[b].Vertex[v+5].rhw   = fooz;
		Bucket[b].Vertex[v+5].color = ColorFromRGB(pPE[0],pPE[1],pPE[2]);
	
		Bucket[b].cnt+=6;
	}

}



void HWI_InsertLine_Sorted(sint32 x1,sint32 y1,sint32 x2,sint32 y2,sint32 z,char color)
{
	sint32* sort;
	sint16* info;
	uint8* pPE=game_palette+((int)(unsigned char)color)*3;

	SetBufferPtrs(sort,info);
	
	sort[0]=(sint32) info;
	sort[1]=(sint32) z;
	info[0]=DRAW_TLV_L;
	info[1]=0;
	info[2]=2;

	D3DTLVERTEX* pTLV=CurrentTLVertex;
	*((D3DTLVERTEX**)(info+3))=pTLV;

	pTLV[0].sx=float(x1+phd_winxmin);
	pTLV[0].sy=float(y1+phd_winymin);
	pTLV[1].sx=float(x2+phd_winxmin);
	pTLV[1].sy=float(y2+phd_winymin);
	pTLV[0].color=pTLV[1].color=ColorFromRGB(pPE[0],pPE[1],pPE[2]);
	
	float fooz=one/float(z);
	pTLV[0].rhw=pTLV[1].rhw=fooz;
	pTLV[0].sz=pTLV[1].sz=f_a-f_boo*fooz;
	CurrentTLVertex = pTLV+2;

}



void HWI_InsertTransQuad_Sorted(sint32 sx,sint32 sy,sint32 w,sint32 h,sint32 z)
{
	sint32* sort;
	sint16* info;
	
	SetBufferPtrs(sort,info);
	
	sort[0]=(sint32) info;
	sort[1]=(sint32) z;
	info[0]=DRAW_TLV_GA;
	info[1]=0;
	info[2]=4;

	D3DTLVERTEX* pTLV=CurrentTLVertex;
	*((D3DTLVERTEX**)(info+3))=pTLV;
	
	pTLV[0].sx=pTLV[3].sx=float(sx);
	pTLV[0].sy=pTLV[1].sy=float(sy);
	pTLV[1].sx=pTLV[2].sx=float(sx+w);
	pTLV[3].sy=pTLV[2].sy=float(sy+h);
	pTLV[0].color=pTLV[1].color=pTLV[2].color=pTLV[3].color=0x80000000;

	float fooz=one/float(z);
	pTLV[0].rhw=pTLV[1].rhw=pTLV[2].rhw=pTLV[3].rhw=fooz;
	pTLV[0].sz=pTLV[1].sz=pTLV[2].sz=pTLV[3].sz=f_a-f_boo*fooz;

	CurrentTLVertex=pTLV+4;
}






void HWI_InsertTrans8_Sorted(PHD_VBUF *vbuf,sint16 shade)
{
	sint8 bClipOr=vbuf[0].clip|vbuf[1].clip|vbuf[2].clip|vbuf[3].clip|vbuf[4].clip|vbuf[5].clip|vbuf[6].clip|vbuf[7].clip;
	if ((bClipOr<0) || (vbuf[0].clip&vbuf[1].clip&vbuf[2].clip&vbuf[3].clip&vbuf[4].clip&vbuf[5].clip&vbuf[6].clip&vbuf[7].clip))
		return;

	IF_NOT_VIS((vbuf+0),(vbuf+1),(vbuf+2))
		return;

	for (int i=0;i<8;++i)
	{
		v_buffer[i].x=float(vbuf[i].xs);
		v_buffer[i].y=float(vbuf[i].ys);
		v_buffer[i].ooz=one/(vbuf[i].zv-SHADOW_BIAS);
		v_buffer[i].vr = 0;
		v_buffer[i].vg = 0;
		v_buffer[i].vb = 0;
	}

	int nPoints=8;

	if (bClipOr)
	{
		phd_leftfloat=float(phd_winxmin);
		phd_topfloat=float(phd_winymin);
		phd_rightfloat=float(phd_winxmin+phd_winwidth);
		phd_bottomfloat=float(phd_winymin+phd_winheight);
		nPoints=XYClipper(nPoints,v_buffer);
	}

	if (nPoints)
	{
		float zdepth=((vbuf[0].zv+vbuf[1].zv+vbuf[2].zv+vbuf[3].zv+vbuf[4].zv+vbuf[5].zv+vbuf[6].zv+vbuf[7].zv)*0.125f)-SHADOW_BIAS;
		HWI_InsertPoly_Gouraud(nPoints,zdepth,0,0,0,DRAW_TLV_GA);
	}
}






/******************************************************************************
*				Z-Clipper for X,Y,G,U,V
*****************************************************************************/

int RoomZedClipper(int number,POINT_INFO* input,VERTEX_INFO* output)
{
	POINT_INFO*	A,*B;
	VERTEX_INFO* pOut;
	float 		clipper,fADiff,fBDiff;
	int 		nVertices;
	double		fconv;


	A = input + number-1;
	B = input;

	pOut = output;
	nVertices = 0;
	
	for (;number--;A=B++)
	{
		fADiff=f_znear-A->zv;	// A out if fADiff>=0
		fBDiff=f_znear-B->zv;	// B out if fBDIff>=0

		if (((*(long*)&fADiff)|(*(long*)&fBDiff))>=0)	// if both sign bits clear, both out
			continue;
		
		if (((*(long*)&fADiff)^(*(long*)&fBDiff))<0)	// if sign bits are different, either A or B is out
		{
			clipper   = fBDiff/(A->zv-B->zv);

			pOut->x   = (B->xv+(A->xv-B->xv)*clipper)*f_perspoznear+f_centerx;
			pOut->y   = (B->yv+(A->yv-B->yv)*clipper)*f_perspoznear+f_centery;
			
			pOut->ooz = f_oneoznear;
			
			pOut->u   = (B->u+(A->u-B->u)*clipper)*f_oneoznear;
			pOut->v   = (B->v+(A->v-B->v)*clipper)*f_oneoznear;

			pOut->vr  = FTOL((B->vr+(A->vr-B->vr)*clipper));
			pOut->vg  = FTOL((B->vg+(A->vg-B->vg)*clipper));
			pOut->vb  = FTOL((B->vb+(A->vb-B->vb)*clipper));
			
			++pOut;
			++nVertices;
		}

		if ((*(long*)&fBDiff)<0)	// if B not out
		{
			pOut->x   = B->xs;
			pOut->y   = B->ys;
			pOut->ooz = B->ooz;
			pOut->u   = B->u*B->ooz;
			pOut->v   = B->v*B->ooz;
			pOut->vr  = B->vr;
			pOut->vg  = B->vg;
			pOut->vb  = B->vb;

			++pOut;
			++nVertices;
		}
	}

	if (nVertices<3)
		return 0;
	
	return nVertices;
}




#define CLIP_GUV(OUT, B, A) {\
	OUT.ooz = B->ooz + (A->ooz - B->ooz) * clipper;\
	OUT.vr = FTOL(B->vr + (A->vr - B->vr) * clipper);\
	OUT.u = B->u + (A->u - B->u) * clipper;\
	OUT.vg = FTOL(B->vg + (A->vg - B->vg) * clipper);\
	OUT.v = B->v + (A->v - B->v) * clipper;\
	OUT.vb = FTOL(B->vb + (A->vb - B->vb) * clipper);}

static VERTEX_INFO XYGUV_output[8];

int RoomXYGUVClipper( int number, VERTEX_INFO *input)
	{
	/* Clips to X then to Y and returns new number of points: input must be able to hold extra points! */
	VERTEX_INFO		*A,*B;
	int 			i, j;
	float 			clipper;
	double			fconv;
	
	/* Clip against X sides */
	B = &input[number-1];
	for (i=j=0; i<number; i++)
		{
		A = B;
		B = &input[i];
		
		/* Clip if point A is off screen */
		if (A->x < phd_leftfloat )
			{
			if (B->x < phd_leftfloat )
				continue;
			
			clipper = (phd_leftfloat - B->x) / (A->x - B->x);
			CLIP_GUV(XYGUV_output[j], B, A);
			XYGUV_output[j].x = phd_leftfloat;
			XYGUV_output[j++].y = B->y + (A->y - B->y) * clipper;
			}
		else if (A->x > phd_rightfloat )
			{
			if (B->x > phd_rightfloat )
				continue;
			
			clipper = ( phd_rightfloat - B->x) / (A->x - B->x);
			CLIP_GUV(XYGUV_output[j], B, A);
			XYGUV_output[j].x = phd_rightfloat;
			XYGUV_output[j++].y = B->y + (A->y - B->y) * clipper;
			}
		
		/* Clip if point B is off screen, else add it's point unclipped */
		if (B->x < phd_leftfloat)
		{
			clipper = (phd_leftfloat - B->x) / (A->x - B->x);
			CLIP_GUV(XYGUV_output[j], B, A);
			XYGUV_output[j].x = phd_leftfloat;
			XYGUV_output[j++].y = B->y + (A->y - B->y) * clipper;
		}
		else if (B->x > phd_rightfloat)
		{
			clipper = (phd_rightfloat - B->x) / (A->x - B->x);
			CLIP_GUV(XYGUV_output[j], B, A);
			XYGUV_output[j].x = phd_rightfloat;
			XYGUV_output[j++].y = B->y + (A->y - B->y) * clipper;
		}
		else
		{
			/* Not clipped */
			XYGUV_output[j].x = B->x;
			XYGUV_output[j].y = B->y;
			//XYGUV_output[j].g = B->g;
			XYGUV_output[j].ooz = B->ooz;
			
			XYGUV_output[j].vr = B->vr;
			XYGUV_output[j].vg = B->vg;
			XYGUV_output[j].vb = B->vb;
			
			XYGUV_output[j].u = B->u;
			XYGUV_output[j++].v = B->v;
		}
	}
	
	/* New number of points in 'XYGUV_output': now clip these against Y */
	if (j < 3)
		return (0);
	number = j;
	
	/* Clip against Y sides */
	B = &XYGUV_output[number-1];
	for (i=j=0; i<number; i++)
		{
		A = B;
		B = &XYGUV_output[i];
		
		/* Clip if point A is off screen */
		if (A->y < phd_topfloat )
			{
			if (B->y < phd_topfloat )
				continue;
			
			clipper = (phd_topfloat - B->y) / (A->y - B->y);
			CLIP_GUV(input[j], B, A);
			input[j].x = B->x + (A->x - B->x) * clipper;
			input[j++].y = phd_topfloat;
			}
		else if (A->y > phd_bottomfloat )
			{
			if (B->y > phd_bottomfloat )
				continue;
			
			clipper = (phd_bottomfloat - B->y) / (A->y - B->y);
			CLIP_GUV(input[j], B, A);
			input[j].x = B->x + (A->x - B->x) * clipper;
			input[j++].y = phd_bottomfloat;
			}
		
		/* Clip if point B is off screen, else add its point unclipped */
		if (B->y < phd_topfloat )
			{
			clipper = (phd_topfloat - B->y) / (A->y - B->y);
			CLIP_GUV(input[j], B, A);
			input[j].x = B->x + (A->x - B->x) * clipper;
			input[j++].y = phd_topfloat;
			}
		else if (B->y > phd_bottomfloat )
			{
			clipper = (phd_bottomfloat - B->y) / (A->y - B->y);
			CLIP_GUV(input[j], B, A);
			input[j].x = B->x + (A->x - B->x) * clipper;
			input[j++].y = phd_bottomfloat;
			}
		else
			{
			/* Unclipped point */
			input[j].x = B->x;
			input[j].y = B->y;
			input[j].ooz = B->ooz;
			input[j].vr = B->vr;
			input[j].vg = B->vg;
			input[j].vb = B->vb;
			input[j].u = B->u;
			input[j++].v = B->v;
			}
		}
	
	/* Throw away polygons that have been reduced to lines or less */
	if (j<3)
		return (0);
	
	return (j);
}




local void HWI_InsertPoly_GouraudRGB(int nPoints,float zdepth,int nDrawType)
{
	D3DTLVERTEX* pTLV;
	uint32 dwAnd;

	dwAnd=(nDrawType==DRAW_TLV_GA)?0x80ffffff:0xffffffff;

	VERTEX_INFO* pVI=v_buffer;

	if(App.lpZBuffer == 0 || nDrawType == DRAW_TLV_GA)
	{
		sint32* sort;
		sint16* info;
		double fconv;
		SetBufferPtrs(sort,info);

		sort[0]=(sint32)info;
		sort[1]=(sint32)FTOL(zdepth);
		info[0]=nDrawType;
		info[1]=0;
		info[2]=nPoints;
		pTLV=CurrentTLVertex;
		*((D3DTLVERTEX**)(info+3))=pTLV;
	
		for (int nPoint=nPoints;nPoint--;)
		{
			pTLV->sx=pVI->x;
			pTLV->sy=pVI->y;
			pTLV->sz=f_a-f_boo*pVI->ooz;
			pTLV->rhw=pVI->ooz;
			pTLV->color = RGBA_MAKE(pVI->vr,pVI->vg,pVI->vb,0xff)&dwAnd;
			pVI++;
			pTLV++;
		}

		CurrentTLVertex=pTLV;
	}
	else
	{

		// Put Verts Into Texture Bucket
		// Fans So Do The First 3 Verts Then Add Remainder
		
		int b;
		int n,v;
		b = FindBucket(0);
		if(b == -1) return; 
		
		for(n=0;n<3;n++)
		{
			v = Bucket[b].cnt;			
			
			Bucket[b].Vertex[v].sx    = pVI->x;
			Bucket[b].Vertex[v].sy    = pVI->y;
			Bucket[b].Vertex[v].sz    = f_a-f_boo*pVI->ooz;
			Bucket[b].Vertex[v].rhw   = pVI->ooz;
			Bucket[b].Vertex[v].color = RGBA_MAKE(pVI->vr,pVI->vg,pVI->vb,0xff)&dwAnd;
		
			pVI++;
			Bucket[b].cnt++;
		}

		nPoints -= 3;
		
		pVI--;

		for(n=nPoints;n>0;n--)
		{
			v = Bucket[b].cnt;
			
			Bucket[b].Vertex[v+0].sx    = v_buffer->x;
			Bucket[b].Vertex[v+0].sy    = v_buffer->y;
			Bucket[b].Vertex[v+0].sz    = f_a-f_boo*v_buffer->ooz;
			Bucket[b].Vertex[v+0].rhw   = v_buffer->ooz;
			Bucket[b].Vertex[v+0].color = RGBA_MAKE(v_buffer->vr,v_buffer->vg,v_buffer->vb,0xff)&dwAnd;

	
			Bucket[b].Vertex[v+1].sx    = pVI->x;
			Bucket[b].Vertex[v+1].sy    = pVI->y;
			Bucket[b].Vertex[v+1].sz    = f_a-f_boo*pVI->ooz;
			Bucket[b].Vertex[v+1].rhw   = pVI->ooz;
			
			Bucket[b].Vertex[v+1].color = RGBA_MAKE(pVI->vr,pVI->vg,pVI->vb,0xff)&dwAnd;
			
			pVI++;

			Bucket[b].Vertex[v+2].sx    = pVI->x;
			Bucket[b].Vertex[v+2].sy    = pVI->y;
			Bucket[b].Vertex[v+2].sz    = f_a-f_boo*pVI->ooz;
			Bucket[b].Vertex[v+2].rhw   = pVI->ooz;

			Bucket[b].Vertex[v+2].color = RGBA_MAKE(pVI->vr,pVI->vg,pVI->vb,0xff)&dwAnd;

			Bucket[b].cnt+= 3;
		}

	}
}



void HWI_InsertAlphaSprite_Sorted(int nX1,int nY1,int nZ1,int nShade1,
								  int nX2,int nY2,int nZ2,int nShade2,
								  int nX3,int nY3,int nZ3,int nShade3,
								  int nX4,int nY4,int nZ4,int nShade4,
								  int nSprite,int type,int dbl)
{
	bool tOldBlueEffect=bBlueEffect;		// temporarily turn off blue effect for sprites
	bBlueEffect=false;

	PHDSPRITESTRUCT* pSprite;
	

	float u1,v1,u2,v2;
			
	if(nSprite != -1)
	{
		pSprite = phdsprinfo+nSprite;
		u1 =float((pSprite->offset<<8)&0xff00);
		v1 =float(pSprite->offset&0xff00);
		u2 =u1+pSprite->width;
		v2 =v1+pSprite->height;
	
		u1+=App.nUVAdd;
		u2-=App.nUVAdd;
		v1+=App.nUVAdd;
		v2-=App.nUVAdd;
	}
	

	
	v_buffer[0].x=float(nX1);
	v_buffer[0].y=float(nY1);
	v_buffer[0].ooz=one/float(nZ1);
	v_buffer[0].vr = GETR(nShade1);
	v_buffer[0].vg = GETG(nShade1);
	v_buffer[0].vb = GETB(nShade1);
	
	v_buffer[1].x=float(nX2);
	v_buffer[1].y=float(nY2);
	v_buffer[1].ooz=one/float(nZ2);;
	v_buffer[1].vr = GETR(nShade2);
	v_buffer[1].vg = GETG(nShade2);
	v_buffer[1].vb = GETB(nShade2);
	
	v_buffer[2].x=float(nX3);
	v_buffer[2].y=float(nY3);
	v_buffer[2].ooz=one/float(nZ3);
	v_buffer[2].vr = GETR(nShade3);
	v_buffer[2].vg = GETG(nShade3);
	v_buffer[2].vb = GETB(nShade3);

	v_buffer[3].x=float(nX4);
	v_buffer[3].y=float(nY4);
	v_buffer[3].ooz=one/float(nZ4);
	v_buffer[3].vr = GETR(nShade4);
	v_buffer[3].vg = GETG(nShade4);
	v_buffer[3].vb = GETB(nShade4);


	if(nSprite != -1)
	{
		
		if(dbl == 0)
		{
			v_buffer[0].u=u1*v_buffer[0].ooz;
			v_buffer[0].v=v1*v_buffer[0].ooz;
			
			v_buffer[1].u=u2*v_buffer[1].ooz;
			v_buffer[1].v=v1*v_buffer[1].ooz;
			
			v_buffer[2].u=u2*v_buffer[2].ooz;
			v_buffer[2].v=v2*v_buffer[2].ooz;

			v_buffer[3].u=u1*v_buffer[3].ooz;
			v_buffer[3].v=v2*v_buffer[3].ooz;
		}
		else
		{
			v_buffer[0].u=u1*v_buffer[0].ooz;
			v_buffer[0].v=v1*v_buffer[0].ooz;
			
			v_buffer[3].u=u2*v_buffer[3].ooz;
			v_buffer[3].v=v1*v_buffer[3].ooz;
			
			v_buffer[2].u=u2*v_buffer[2].ooz;
			v_buffer[2].v=v2*v_buffer[2].ooz;

			v_buffer[1].u=u1*v_buffer[1].ooz;
			v_buffer[1].v=v2*v_buffer[1].ooz;
		}
		
	}
	
	int nPoints=4;

//	if (nX1<phd_winxmin || nX2>phd_winxmin+phd_winwidth || nY1<phd_winymin || nY2>phd_winymin+phd_winheight)
//	{
		phd_leftfloat = (float)phd_winxmin;
		phd_rightfloat = (float)(phd_winxmin+phd_winwidth);
		phd_topfloat = (float)phd_winymin;
		phd_bottomfloat = (float)(phd_winymin+phd_winheight);

	if(nSprite == -1)
		nPoints=XYGClipper(nPoints,v_buffer);
	else
		nPoints=RoomXYGUVClipper(nPoints,v_buffer);
			
//	}
	if (nPoints)
	{
		float fZDepth = float((nZ1+nZ2+nZ3+nZ4)>>2);
	
		if(nSprite == -1)
			HWI_InsertPoly_GouraudRGB(nPoints,fZDepth,type);
		else
			HWI_InsertClippedPoly_Textured(nPoints,fZDepth,type,pSprite->tpage);
	}
	bBlueEffect=tOldBlueEffect;
}





#define CLIP_G(OUT, B, A) {\
	OUT.ooz = B->ooz + (A->ooz - B->ooz) * clipper;\
	OUT.vr = FTOL(B->vr + (A->vr - B->vr) * clipper);\
	OUT.vg = FTOL(B->vg + (A->vg - B->vg) * clipper);\
	OUT.vb = FTOL(B->vb + (A->vb - B->vb) * clipper);}

static VERTEX_INFO XYG_output[8];

int XYGClipper( int number, VERTEX_INFO *input)
{

	/* Clips to X then to Y and returns new number of points: input must be able to hold extra points! */
	int 			i, j;
	VERTEX_INFO 	*A, *B;
	float 			clipper;
	double			fconv;

	/* Clip against X sides */
	B = &input[number-1];
	for (i=j=0; i<number; i++)
	{
		A = B;
		B = &input[i];
		
		/* Clip if point A is off screen */
		if (A->x < phd_leftfloat )
		{
			if (B->x < phd_leftfloat )
				continue;
			
			clipper = (phd_leftfloat - B->x) / (A->x - B->x);
			CLIP_G(XYG_output[j], B, A);
			XYG_output[j].x = phd_leftfloat;
			XYG_output[j++].y = B->y + (A->y - B->y) * clipper;
		}
		else if (A->x > phd_rightfloat )
		{
			if (B->x > phd_rightfloat )
				continue;
			
			clipper = ( phd_rightfloat - B->x) / (A->x - B->x);
			CLIP_G(XYG_output[j], B, A);
			XYG_output[j].x = phd_rightfloat;
			XYG_output[j++].y = B->y + (A->y - B->y) * clipper;
		}
		
		/* Clip if point B is off screen, else add it's point unclipped */
		if (B->x < phd_leftfloat)
		{
			clipper = (phd_leftfloat - B->x) / (A->x - B->x);
			CLIP_G(XYG_output[j], B, A);
			XYG_output[j].x = phd_leftfloat;
			XYG_output[j++].y = B->y + (A->y - B->y) * clipper;
		}
		else if (B->x > phd_rightfloat)
		{
			clipper = (phd_rightfloat - B->x) / (A->x - B->x);
			CLIP_G(XYG_output[j], B, A);
			XYG_output[j].x = phd_rightfloat;
			XYG_output[j++].y = B->y + (A->y - B->y) * clipper;
		}
		else
		{
			/* Not clipped */
			XYG_output[j].x = B->x;
			XYG_output[j].y = B->y;
			XYG_output[j].ooz = B->ooz;
			XYG_output[j].vr = B->vr;
			XYG_output[j].vg = B->vg;
			XYG_output[j].vb = B->vb;
			j++;
		}
	}
	
	/* New number of points in 'output': now clip these against Y */
	if (j < 3)
		return (0);
	number = j;
	
	/* Clip against Y sides */
	B = &XYG_output[number-1];
	for (i=j=0; i<number; i++)
	{
		A = B;
		B = &XYG_output[i];
		
		/* Clip if point A is off screen */
		if (A->y < phd_topfloat )
		{
			if (B->y < phd_topfloat )
				continue;
			
			clipper = (phd_topfloat - B->y) / (A->y - B->y);
			CLIP_G(input[j], B, A);
			input[j].x = B->x + (A->x - B->x) * clipper;
			input[j++].y = phd_topfloat;
		}
		else if (A->y > phd_bottomfloat )
		{
			if (B->y > phd_bottomfloat )
				continue;
			
			clipper = (phd_bottomfloat - B->y) / (A->y - B->y);
			CLIP_G(input[j], B, A);
			input[j].x = B->x + (A->x - B->x) * clipper;
			input[j++].y = phd_bottomfloat;
		}
		
		/* Clip if point B is off screen, else add its point unclipped */
		if (B->y < phd_topfloat )
		{
			clipper = (phd_topfloat - B->y) / (A->y - B->y);
			CLIP_G(input[j], B, A);
			input[j].x = B->x + (A->x - B->x) * clipper;
			input[j++].y = phd_topfloat;
		}
		else if (B->y > phd_bottomfloat )
		{
			clipper = (phd_bottomfloat - B->y) / (A->y - B->y);
			CLIP_G(input[j], B, A);
			input[j].x = B->x + (A->x - B->x) * clipper;
			input[j++].y = phd_bottomfloat;
		}
		else
		{
			/* Unclipped point */
			input[j].x = B->x;
			input[j].y = B->y;
			input[j].ooz = B->ooz;
			input[j].ooz = B->ooz;
			input[j].vr = B->vr;
			input[j].vg = B->vg;
			input[j].vb = B->vb;
			j++;
		}
	}
	
	/* Throw away polygons that have been reduced to lines or less */
	if (j<3)
		return (0);
	
	return (j);
}


int XYClipper( int number, VERTEX_INFO *input)
	{
	/* Clips to X then to Y and returns new number of points: input must be able to hold extra points! */
	int 			i, j;
	VERTEX_INFO 	output[20], *A, *B;
	float 			clipper;
	
	/* Clip against X sides */
	B = &input[number-1];
	for (i=j=0; i<number; i++)
		{
		A = B;
		B = &input[i];
		
		/* Clip if point A is off screen */
		if (A->x < phd_leftfloat )
			{
			if (B->x < phd_leftfloat )
				continue;
			
			clipper = (phd_leftfloat - B->x) / (A->x - B->x);
			output[j].x = phd_leftfloat;
			output[j++].y = B->y + (A->y - B->y) * clipper;
			}
		else if (A->x > phd_rightfloat )
			{
			if (B->x > phd_rightfloat )
				continue;
			
			clipper = ( phd_rightfloat - B->x) / (A->x - B->x);
			output[j].x = phd_rightfloat;
			output[j++].y = B->y + (A->y - B->y) * clipper;
			}
		
		/* Clip if point B is off screen, else add it's point unclipped */
		if (B->x < phd_leftfloat)
			{
			clipper = (phd_leftfloat - B->x) / (A->x - B->x);
			output[j].x = phd_leftfloat;
			output[j++].y = B->y + (A->y - B->y) * clipper;
			}
		else if (B->x > phd_rightfloat)
			{
			clipper = (phd_rightfloat - B->x) / (A->x - B->x);
			output[j].x = phd_rightfloat;
			output[j++].y = B->y + (A->y - B->y) * clipper;
			}
		else
			{
			/* Not clipped */
			output[j].x = B->x;
			output[j++].y = B->y;
			}
		}
	
	/* New number of points in 'output': now clip these against Y */
	if (j < 3)
		return (0);
	number = j;
	
	/* Clip against Y sides */
	B = &output[number-1];
	for (i=j=0; i<number; i++)
		{
		A = B;
		B = &output[i];
		
		/* Clip if point A is off screen */
		if (A->y < phd_topfloat )
			{
			if (B->y < phd_topfloat )
				continue;
			
			clipper = (phd_topfloat - B->y) / (A->y - B->y);
			input[j].x = B->x + (A->x - B->x) * clipper;
			input[j++].y = phd_topfloat;
			}
		else if (A->y > phd_bottomfloat )
			{
			if (B->y > phd_bottomfloat )
				continue;
			
			clipper = (phd_bottomfloat - B->y) / (A->y - B->y);
			input[j].x = B->x + (A->x - B->x) * clipper;
			input[j++].y = phd_bottomfloat;
			}
		
		/* Clip if point B is off screen, else add its point unclipped */
		if (B->y < phd_topfloat )
			{
			clipper = (phd_topfloat - B->y) / (A->y - B->y);
			input[j].x = B->x + (A->x - B->x) * clipper;
			input[j++].y = phd_topfloat;
			}
		else if (B->y > phd_bottomfloat )
			{
			clipper = (phd_bottomfloat - B->y) / (A->y - B->y);
			input[j].x = B->x + (A->x - B->x) * clipper;
			input[j++].y = phd_bottomfloat;
			}
		else
			{
			/* Unclipped point */
			input[j].x = B->x;
			input[j++].y = B->y;
			}
		}
	
	/* Throw away polygons that have been reduced to lines or less */
	if (j<3)
		return (0);
	
	return (j);
}
