#include "driver2.h"
#include "cars.h"
#include "texture.h"
#include "overmap.h"
#include "draw.h"
#include "mission.h"
#include "system.h"
#include "debris.h"
#include "main.h"
#include "camera.h"
#include "handling.h"
#include "cosmetic.h"
#include "shadow.h"
#include "civ_ai.h"
#include "mc_snd.h"
#include "gamesnd.h"
#include "players.h"
#include "cutscene.h"
#include "convert.h"
#include "glaunch.h"
#include "ASM/rndrasm.h"

struct plotCarGlobals
{
	u_char* primptr;
	OTTYPE* ot;
	u_int intensity;
	u_short* pciv_clut;
	u_int ShineyTPageASL16;
	u_int ShineyClutASL16;
	u_char* damageLevel;
	u_char* shineyTable;
	int ghost;
};


#ifndef PSX
#define CAR_LOD_SWITCH_DISTANCE switch_detail_distance
#else
#define CAR_LOD_SWITCH_DISTANCE 5500
#endif

MATRIX light_matrix =
{ { { 4096, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } }, { 0, 0, 0 } };

MATRIX colour_matrix =
{ { { 4032, 0, 0 }, { 3936, 0, 0 }, { 3520, 0, 0 } }, { 0, 0, 0 } };

// PHYSICS
CAR_DATA car_data[MAX_CARS + 2];	// all cars + Tanner cbox + Camera cbox

HUBCAP gHubcap;

MODEL* gHubcapModelPtr;
MODEL* gCleanWheelModelPtr;
MODEL* gFastWheelModelPtr;
MODEL* gDamWheelModelPtr;

// active carsg
CAR_DATA* active_car_list[MAX_CARS];
BOUND_BOX bbox[MAX_CARS];
u_char lightsOnDelay[MAX_CARS];
short FrontWheelRotation[MAX_CARS]; // offset 0x0
short BackWheelRotation[MAX_CARS]; // offset 0x30

SVECTOR gTempCarVertDump[MAX_CARS][MAX_DENTING_VERTS];

DENTUVS *gTempCarUVPtr;
DENTUVS gTempHDCarUVDump[MAX_CARS][MAX_DENTING_UVS];
DENTUVS gTempLDCarUVDump[MAX_CARS][MAX_DENTING_LOD_UVS];

CAR_MODEL NewCarModel[MAX_CAR_MODELS];
CAR_MODEL NewLowCarModel[MAX_CAR_MODELS];

MODEL* gCarLowModelPtr[MAX_CAR_MODELS];
MODEL* gCarDamModelPtr[MAX_CAR_MODELS];
MODEL* gCarCleanModelPtr[MAX_CAR_MODELS];

// pedestrian palette at 0 and next are cars
// model_id, texture_number, palette
u_short civ_clut[8][32][6];

int whichCP = 0;
int baseSpecCP = 0;
CAR_POLY carPolyBuffer[2001];

char LeftLight = 0;
char RightLight = 0;
char TransparentObject = 0;

// [D] [T]
void plotCarPolyB3(int numTris, CAR_POLY *src, SVECTOR *vlist, plotCarGlobals *pg)
{
	int Z;
	int indices;
	u_int FT3rgb;
	SVECTOR *v2;
	SVECTOR *v1;
	SVECTOR *v0;
	POLY_F3 *prim;
	OTTYPE *ot;

	prim = (POLY_F3 *)pg->primptr;
	FT3rgb = pg->intensity;
	ot = pg->ot;

	while (numTris > 0)
	{
		indices = src->vindices;
		v0 = vlist + (indices & 0xff);
		v1 = vlist + (indices >> 8 & 0xff);
		v2 = vlist + (indices >> 16 & 0xff);

		gte_ldv3(v0, v1, v2);

		gte_rtpt();

		gte_nclip();
		gte_stopz(&Z);
		gte_avsz3();

		if (Z > -1) 
		{
			*(u_int*)&prim->r0 = FT3rgb | 0x20000000;

			gte_stsxy3(&prim->x0, &prim->x1, &prim->x2);

			gte_stotz(&Z);

			setPolyF3(prim);
			addPrim(ot + (Z >> 1), prim);

			prim++;
		}

		numTris--;
		src++;
	}

	pg->primptr = (unsigned char*)prim;
}

// [D] [T]
void plotCarPolyFT3(int numTris, CAR_POLY *src, SVECTOR *vlist, plotCarGlobals *pg)
{
	int indices;
	int ofse;
	SVECTOR *v2;
	SVECTOR *v1;
	SVECTOR *v0;
	int Z;
	POLY_FT3 *prim;
	OTTYPE *ot;
	int FT3rgb;
	int reg;

	prim = (POLY_FT3 *)pg->primptr;
	FT3rgb = pg->intensity | 0x24000000;
	ot = pg->ot;

	gte_ldrgb(&FT3rgb);

	while (numTris > 0)
	{
		indices = src->vindices;
		v0 = vlist + (indices & 0xff);
		v1 = vlist + (indices >> 8 & 0xff);
		v2 = vlist + (indices >> 16 & 0xff);

		gte_ldv3(v0, v1, v2);

		gte_rtpt();

		gte_nclip();
		gte_stopz(&Z);
		gte_avsz3();

		if (Z > -1) 
		{
			ofse = pg->damageLevel[src->originalindex];
			*(u_int*)&prim->r0 = FT3rgb;
			*(u_int*)&prim->u0 = src->clut_uv0 + ofse;
			*(u_int*)&prim->u1 = src->tpage_uv1 + ofse;
			*(u_int*)&prim->u2 = src->uv3_uv2 + ofse;

			gte_stsxy3(&prim->x0, &prim->x1, &prim->x2);

			gte_stotz(&Z);

			setPolyFT3(prim);
			addPrim(ot + (Z >> 1), prim);

			prim++;
		}
		numTris--;
		src++;
	}

	pg->primptr = (unsigned char*)prim;
}

// [D] [T]
void plotCarPolyGT3(int numTris, CAR_POLY *src, SVECTOR *vlist, SVECTOR *nlist, plotCarGlobals *pg, int palette)
{
	int Z;
	int otz;	
	SVECTOR* v2;
	SVECTOR *v1;
	SVECTOR *v0;
	u_int indices;
	POLY_GT3 *prim;
	u_int r0,r1,r2;
	int ofse;

	prim = (POLY_GT3 *)pg->primptr;

	int GT3rgb = pg->intensity | 0x34000000;
	gte_ldrgb(&GT3rgb);

	while (numTris > 0)
	{
		indices = src->vindices;

		v0 = vlist + (indices & 0xff);
		v1 = vlist + (indices >> 8 & 0xff);
		v2 = vlist + (indices >> 16 & 0xff);

		gte_ldv3(v0, v1, v2);

		gte_rtpt();
		gte_nclip();

		gte_stopz(&Z);

		gte_avsz3();
		
		gte_stotz(&otz);

		if (Z > -1 && otz > 0)
		{
			indices = src->nindices;

			r0 = (u_int)(ushort)nlist[indices & 0xff].pad;
			r1 = (u_int)(ushort)nlist[indices >> 8 & 0xff].pad;
			r2 = (u_int)(ushort)nlist[indices >> 16 & 0xff].pad;

			*(u_int*)&prim->r0 = (r0 & 0xff) << 0x10 | r0;
			*(u_int*)&prim->r1 = (r1 & 0xff) << 0x10 | r1;
			*(u_int*)&prim->r2 = (r2 & 0xff) << 0x10 | r2;

			ofse = pg->damageLevel[src->originalindex];

			*(u_int*)&prim->u0 = (src->clut_uv0 & 0xffffU | pg->pciv_clut[palette + (src->clut_uv0 >> 0x10)] << 0x10) + ofse;
			*(u_int*)&prim->u1 = src->tpage_uv1 + ofse;
			*(u_int*)&prim->u2 = src->uv3_uv2 + ofse;

			gte_stsxy3(&prim->x0, &prim->x1, &prim->x2);

			setPolyGT3(prim);
			addPrim(pg->ot + (otz >> 1), prim);

			prim++;
		}

		src++;
		numTris--;
	}

	pg->primptr = (unsigned char*)prim;
}

// [D] [T]
void plotCarPolyGT3nolight(int numTris, CAR_POLY *src, SVECTOR *vlist, plotCarGlobals *pg, int palette)
{
	int Z;
	int otz;
	SVECTOR* v2;
	SVECTOR* v1;
	SVECTOR* v0;
	u_int indices;
	POLY_FT3* prim;
	int ofse;

	prim = (POLY_FT3*)pg->primptr;

	int GT3rgb = pg->intensity | 0x34000000;
	gte_ldrgb(&GT3rgb);

	while (numTris > 0)
	{
		indices = src->vindices;

		v0 = vlist + (indices & 0xff);
		v1 = vlist + (indices >> 8 & 0xff);
		v2 = vlist + (indices >> 16 & 0xff);

		gte_ldv3(v0, v1, v2);

		gte_rtpt();
		gte_nclip();

		gte_stopz(&Z);

		gte_avsz3();

		gte_stotz(&otz);

		if (Z > -1 && otz > 0)
		{
			*(u_int*)&prim->r0 = GT3rgb;

			ofse = pg->damageLevel[src->originalindex];

			*(u_int*)&prim->u0 = (src->clut_uv0 & 0xffffU | pg->pciv_clut[palette + (src->clut_uv0 >> 0x10)] << 0x10) + ofse;
			*(u_int*)&prim->u1 = src->tpage_uv1 + ofse;
			*(u_int*)&prim->u2 = src->uv3_uv2 + ofse;

			gte_stsxy3(&prim->x0, &prim->x1, &prim->x2);

			setPolyFT3(prim);
			addPrim(pg->ot + (otz >> 1), prim);

			prim++;
		}

		src++;
		numTris--;
	}

	pg->primptr = (unsigned char*)prim;
}

MATRIX save_colour_matrix;
MATRIX save_light_matrix;

// [D] [T]
void setupLightingMatrices(void)
{
	gte_ReadColorMatrix(&save_colour_matrix);
	gte_ReadLightMatrix(&save_light_matrix);

	gte_SetColorMatrix(&colour_matrix);
	gte_SetLightMatrix(&light_matrix);

	if (gTimeOfDay == 3)
	{
		gte_SetBackColor(64, 64, 64);
	}
	else
	{
		gte_SetBackColor(140, 140, 140);
	}
}

// [D] [T]
void restoreLightingMatrices(void)
{
	gte_SetColorMatrix(&save_colour_matrix);
	gte_SetLightMatrix(&save_light_matrix);
}

// [D] [T]
void ComputeCarLightingLevels(CAR_DATA* cp, char detail)
{
	MATRIX& scratchPadMat = *(MATRIX*)((u_char*)getScratchAddr(0) + 0x344);

	int doLight;
	int orW;
	int orY;
	MODEL* model;
	int num_norms;
	int count;
	SVECTOR* ppads;
	SVECTOR* norms;
	SVECTOR lightsourcevector;
	SVECTOR colour;
	CVECTOR c0;
	CVECTOR c1;
	CVECTOR c2;
	u_int GT3rgb;

	if (gTimeOfDay > -1)
	{
		if (gTimeOfDay < 3)
		{
			lightsourcevector = day_vectors[GameLevel];
			colour = day_colours[GameLevel];
		}
		else if (gTimeOfDay == 3)
		{
			lightsourcevector = night_vectors[GameLevel];
			colour = night_colours[GameLevel];
		}
	}

	InvertMatrix(&cp->hd.where, &scratchPadMat);
	SetRotMatrix(&scratchPadMat);

	gte_ldv0(&lightsourcevector);
	gte_rtv0();

	gte_stsv(light_matrix.m[0]);

	doLight = 0;

	colour_matrix.m[0][0] = colour.vx;
	colour_matrix.m[1][0] = colour.vy;
	colour_matrix.m[2][0] = colour.vz;

	if (gTimeOfDay != 3)
	{
		orY = cp->st.n.orientation[1] - cp->ap.qy;

		if (orY < 1)
			orY = cp->ap.qy - cp->st.n.orientation[1];

		orW = cp->st.n.orientation[3] - cp->ap.qw;

		if (orW < 1)
			orW = cp->ap.qw - cp->st.n.orientation[3];

		if ((orY + orW > 200) || (cp->lowDetail != (detail | lightning)))
			doLight = 1;

		if ((gTimeOfDay == 0 || gTimeOfDay == 2) && (cp->id & 0xf) == (CameraCnt & 0xfU))
			doLight = 1;

		setupLightingMatrices();

		if (doLight)
		{
			GT3rgb = combointensity & 0xffffffU | 0x34000000;
			gte_ldrgb(&GT3rgb);

			cp->ap.qy = cp->st.n.orientation[1];
			cp->ap.qw = cp->st.n.orientation[3];
			cp->lowDetail = detail | lightning;

			if (detail == 0)
				model = gCarLowModelPtr[cp->ap.model];
			else
				model = gCarCleanModelPtr[cp->ap.model];

			num_norms = model->num_point_normals / 3;
			norms = (SVECTOR*)model->point_normals;

			ppads = gTempCarVertDump[cp->id];
			count = num_norms;// +1;

			while (count >= 0)
			{
				gte_ldv3(&norms[0], &norms[1], &norms[2]);

				gte_ncct();

				gte_strgb3(&c0, &c1, &c2);

				ppads[0].pad = *(short*)&c0;
				ppads[1].pad = *(short*)&c1;
				ppads[2].pad = *(short*)&c2;

				count--;
				norms += 3;
				ppads += 3;
			}
		}

		restoreLightingMatrices();
	}
}

// [D] [T]
void DrawWheelObject(MODEL* model, SVECTOR* verts, int transparent, int wheelnum)
{
	ushort clut;
	ushort tpage;
	int i;
	int Z;
	int otZ;
	int combo;
	POLY_FT4* poly;
	POLYFT4* src;
	u_int dim;
	u_int bright;

	src = (POLYFT4*)model->poly_block;
	poly = (POLY_FT4*)current->primptr;

	clut = texture_cluts[src->texture_set][src->texture_id];
	tpage = texture_pages[src->texture_set];

	if (gTimeOfDay > -1)
	{
		if (gTimeOfDay < 3)
		{
			bright = combointensity & 0xffffffU | 0x2c000000;
			dim = (combointensity & 0xfcfcfcU) >> 2 | 0x2c000000;
		}
		else if (gTimeOfDay == 3)
		{
			combo = (combointensity & 0xffU) / 3;
			combo = combo << 0x10 | combo << 8 | combo;

			bright = combo | 0x2c000000;
			dim = (combo & 0xfcfcfc) >> 2 | 0x2c000000;
		}
	}

	i = model->num_polys;

	while (i-- != -1)
	{
		gte_ldv3(&verts[src->v0], &verts[src->v1], &verts[src->v2]);

		gte_rtpt();
		gte_nclip();

		gte_stopz(&Z);
		gte_stsxy0(&poly->x0);

		gte_ldv0(&verts[src->v3]);

		gte_rtps();
		gte_avsz4();

		gte_stotz(&otZ);

		if (otZ > 2)
		{
			setPolyFT4(poly);
			addPrim(current->ot + (otZ >> 1) + 5, poly);

			if (i < 2 || Z < 0)
				*(u_int*)&poly->r0 = 0x2c000000;
			else if (((i ^ wheelnum >> 1) & 1) == 0)
				*(u_int*)&poly->r0 = dim;
			else
				*(u_int*)&poly->r0 = bright;

			setSemiTrans(poly, transparent);

			gte_stsxy3(&poly->x1, &poly->x3, &poly->x2);

			poly->u0 = src->uv0.u;
			poly->v0 = src->uv0.v;
			poly->clut = clut;

			poly->u1 = src->uv1.u;
			poly->v1 = src->uv1.v;
			poly->tpage = tpage | 0x20;

			poly->u2 = src->uv3.u;
			poly->v2 = src->uv3.v;

			poly->u3 = src->uv2.u;
			poly->v3 = src->uv2.v;

			poly++;
		}
		src++;
	}
	current->primptr = (char*)poly;
}

// [D] [T]
void DrawCarWheels(CAR_DATA *cp, MATRIX *RearMatrix, VECTOR *pos, int zclip)
{
	short wheelSize;
	int FW1z;
	int FW2z;
	int BW1z;
	int BW2z;
	int FrontWheelIncrement;
	int BackWheelIncrement;
	int sizeScale;
	int wheelnum;
	SVECTOR* VertPtr;
	MODEL* model;
	SVECTOR* wheelDisp;
	WHEEL* wheel;
	int car_id;
	MODEL *WheelModelBack;
	MODEL *WheelModelFront;

#if 0 //def PSX
	MATRIX& FrontMatrix = *(MATRIX*)(u_char*)getScratchAddr(0);
	MATRIX& SteerMatrix = *(MATRIX*)((u_char*)getScratchAddr(0) + sizeof(MATRIX));
	VECTOR& WheelPos = *(VECTOR*)((u_char*)getScratchAddr(0) + sizeof(MATRIX) * 2);
	SVECTOR& sWheelPos = *(SVECTOR*)((u_char*)getScratchAddr(0) + sizeof(MATRIX) * 2 + sizeof(VECTOR));
#else
	MATRIX FrontMatrix;
	MATRIX SteerMatrix;
	VECTOR WheelPos;
	SVECTOR sWheelPos;
#endif

	D_CHECK_ERROR(cp < car_data, "Invalid car");

	car_id = CAR_INDEX(cp);

	BackWheelIncrement = FrontWheelIncrement = cp->hd.wheel_speed >> 8;

	if (cp->wheelspin != 0)
		BackWheelIncrement = 700;

	if (cp->hd.wheel[0].locked != 0)
		FrontWheelIncrement = 0;

	if (cp->hd.wheel[3].locked != 0)
		BackWheelIncrement = 0;

	if (FrontWheelIncrement + 400U < 801)
		WheelModelFront = gCleanWheelModelPtr;
	else
		WheelModelFront = gFastWheelModelPtr;

	if (BackWheelIncrement + 400U < 801)
		WheelModelBack = gCleanWheelModelPtr;
	else
		WheelModelBack = gFastWheelModelPtr;

	wheelSize = car_cosmetics[cp->ap.model].wheelSize;
	
	sizeScale = (wheelSize * 14142) / 10000;

	// rotate wheel verts

	FW1z = FIXEDH(RSIN(FrontWheelRotation[car_id]) * sizeScale);
	FW2z = FIXEDH(RCOS(FrontWheelRotation[car_id]) * sizeScale);

	VertPtr = (SVECTOR*)WheelModelFront->vertices;

	VertPtr[8].vz = FW1z;
	VertPtr[15].vz = FW1z;
	VertPtr[11].vy = FW1z;
	VertPtr[12].vy = FW1z;

	VertPtr[9].vy = -FW1z;
	VertPtr[14].vy = -FW1z;
	VertPtr[10].vz = -FW1z;
	VertPtr[13].vz = -FW1z;

	VertPtr[8].vy = FW2z;
	VertPtr[15].vy = FW2z;
	VertPtr[9].vz = FW2z;
	VertPtr[14].vz = FW2z;

	VertPtr[10].vy = -FW2z;
	VertPtr[13].vy = -FW2z;
	VertPtr[11].vz = -FW2z;
	VertPtr[12].vz = -FW2z;

	VertPtr[23].vz = 0;
	VertPtr[22].vz = 0;
	VertPtr[21].vy = 0;
	VertPtr[20].vy = 0;
	VertPtr[19].vy = 0;
	VertPtr[18].vy = 0;
	VertPtr[17].vz = 0;
	VertPtr[16].vz = 0;

	VertPtr[23].vy = wheelSize;
	VertPtr[22].vy = wheelSize;
	VertPtr[21].vz = wheelSize;
	VertPtr[20].vz = wheelSize;

	VertPtr[19].vz = -wheelSize;
	VertPtr[18].vz = -wheelSize;
	VertPtr[17].vy = -wheelSize;
	VertPtr[16].vy = -wheelSize;

	BW1z = FIXEDH(RSIN(BackWheelRotation[car_id]) * sizeScale);
	BW2z = FIXEDH(RCOS(BackWheelRotation[car_id]) * sizeScale);

	VertPtr = (SVECTOR *)WheelModelBack->vertices;
	
	VertPtr[8].vz = BW1z;
	VertPtr[15].vz = BW1z;
	VertPtr[11].vy = BW1z;
	VertPtr[12].vy = BW1z;

	VertPtr[9].vy = -BW1z;
	VertPtr[14].vy = -BW1z;
	VertPtr[10].vz = -BW1z;
	VertPtr[13].vz = -BW1z;

	VertPtr[8].vy = BW2z;
	VertPtr[15].vy = BW2z;
	VertPtr[9].vz = BW2z;
	VertPtr[14].vz = BW2z;

	VertPtr[10].vy = -BW2z;
	VertPtr[13].vy = -BW2z;
	VertPtr[11].vz = -BW2z;
	VertPtr[12].vz = -BW2z;

	VertPtr[23].vz = 0;
	VertPtr[22].vz = 0;
	VertPtr[21].vy = 0;
	VertPtr[20].vy = 0;
	VertPtr[19].vy = 0;
	VertPtr[18].vy = 0;
	VertPtr[17].vz = 0;
	VertPtr[16].vz = 0;

	VertPtr[23].vy = wheelSize;
	VertPtr[22].vy = wheelSize;
	VertPtr[21].vz = wheelSize;
	VertPtr[20].vz = wheelSize;

	VertPtr[19].vz = -wheelSize;
	VertPtr[18].vz = -wheelSize;
	VertPtr[17].vy = -wheelSize;
	VertPtr[16].vy = -wheelSize;

	SteerMatrix.m[0][0] = RCOS(cp->wheel_angle);
	SteerMatrix.m[0][2] = RSIN(cp->wheel_angle);
	SteerMatrix.m[1][1] = ONE;
	SteerMatrix.m[2][1] = 0;
	SteerMatrix.m[1][2] = 0;
	SteerMatrix.m[1][0] = 0;
	SteerMatrix.m[0][1] = 0;
	SteerMatrix.m[2][0] = -SteerMatrix.m[0][2];
	SteerMatrix.m[2][2] = SteerMatrix.m[0][0];

	MulMatrix0(RearMatrix, &SteerMatrix, &FrontMatrix);

	wheelDisp = car_cosmetics[cp->ap.model].wheelDisp;
	wheel = cp->hd.wheel;
	wheelnum = 0;

	do {
		if ((wheelnum & 1) != 0)
			model = WheelModelBack;
		else
			model = WheelModelFront;

		VertPtr = (SVECTOR*)model->vertices;

		if (cp->ap.flags & (1 << wheelnum)) // [A] used appearance flags to store hubcap presence
		{
			model = gDamWheelModelPtr;
		}

		if ((wheelnum & 2) == 0) 
			sWheelPos.vx = 17 - wheelDisp->vx;
		else
			sWheelPos.vx = -17 - wheelDisp->vx;

		sWheelPos.vz = -wheelDisp->vz;
		sWheelPos.vy = (-wheelSize - wheelDisp->vy) - wheel->susCompression + 14;

		gte_SetRotMatrix(RearMatrix);
		gte_ldv0(&sWheelPos);

		gte_rtv0();

		gte_stlvl(&WheelPos);

		WheelPos.vx += pos->vx;
		WheelPos.vy += pos->vy;
		WheelPos.vz += pos->vz;

		gte_SetTransVector(&WheelPos);

		if ((wheelnum & 1) == 0) 
		{
			gte_SetRotMatrix(&FrontMatrix);
		}

		DrawWheelObject(model, VertPtr, TransparentObject, wheelnum);

		wheelDisp++;
		wheel++;
		wheelnum++;
	} while (wheelnum < 4);
}

// [D] [T]
void PlayerCarFX(CAR_DATA *cp)
{
	int WheelSpeed;
	WheelSpeed = cp->hd.wheel_speed;

	if (WheelSpeed + 199U < 0x4b0c7)
	{
		if (cp->wheel_angle < -200)
			AddIndicatorLight(cp, 0);
		else if (cp->wheel_angle > 200)
			AddIndicatorLight(cp, 1);
	}

	if( WheelSpeed < 0 && cp->thrust > 0 ||
		WheelSpeed > 0 && cp->thrust < 0)
	{
		AddBrakeLight(cp);
	}

	if (WheelSpeed < 0 && cp->thrust < 0)
		AddReverseLight(cp);
}

// [D] [T]
void plotNewCarModel(CAR_MODEL* car, int palette)
{
#if 0 //def PSX
	plotCarGlobals& _pg = *(plotCarGlobals*)(u_char*)getScratchAddr(0);
#else
	plotCarGlobals _pg;
#endif

	u_int lightlevel;
	u_int underIntensity;
	SVECTOR v = { 0, -4096, 0 };

	lightlevel = combointensity | 0x3000000;

	if (car == NULL)
		return;

	if (gTimeOfDay > -1)
	{
		if (gTimeOfDay < 3)
			lightlevel = combointensity | 0x30000000;
		else if (gTimeOfDay == 3)
			lightlevel = 0x302a2a2a;
	}

	setupLightingMatrices();

	gte_ldv0(&v);
	gte_ldrgb(&lightlevel);

	gte_nccs();

	_pg.primptr = (u_char*)current->primptr;
	_pg.intensity = 0;
	_pg.pciv_clut = (u_short*)&civ_clut[1];
	_pg.damageLevel = (u_char*)gTempCarUVPtr;

	_pg.ot = (OTTYPE*)(current->ot + 28);

	gte_strgb(&underIntensity);

	// draw wheel arcs
	plotCarPolyB3(car->numB3, car->pB3, car->vlist, &_pg);
	_pg.intensity = underIntensity & 0xffffff;

	// draw car bottom
	_pg.ot = (OTTYPE*)(current->ot + 16);
	plotCarPolyFT3(car->numFT3, car->pFT3, car->vlist, &_pg);

	// draw car body
	_pg.ot = (OTTYPE*)(current->ot + 4);
	if (gTimeOfDay == 3)
	{
		_pg.intensity = (combointensity & 0xfcfcf0U) >> 2;
		plotCarPolyGT3nolight(car->numGT3, car->pGT3, car->vlist, &_pg, palette);
	}
	else
	{
		_pg.intensity = combointensity & 0xffffff;
		plotCarPolyGT3(car->numGT3, car->pGT3, car->vlist, car->nlist, &_pg, palette);
	}

	current->primptr = (char*)_pg.primptr;

	restoreLightingMatrices();
}

// [D] [T]
void buildNewCars(void)
{
	buildNewCarFromModel(&NewCarModel[0], gCarCleanModelPtr[0], 1);
	buildNewCarFromModel(&NewLowCarModel[0], gCarLowModelPtr[0], 0);

	buildNewCarFromModel(&NewCarModel[1], gCarCleanModelPtr[1], 0);
	buildNewCarFromModel(&NewLowCarModel[1], gCarLowModelPtr[1], 0);

	buildNewCarFromModel(&NewCarModel[2], gCarCleanModelPtr[2], 0);
	buildNewCarFromModel(&NewLowCarModel[2], gCarLowModelPtr[2], 0);

	buildNewCarFromModel(&NewCarModel[3], gCarCleanModelPtr[3], 0);
	buildNewCarFromModel(&NewLowCarModel[3], gCarLowModelPtr[3], 0);

	// build special car
	baseSpecCP = whichCP;
	buildNewCarFromModel(&NewCarModel[4], gCarCleanModelPtr[4], 0);
	buildNewCarFromModel(&NewLowCarModel[4], gCarLowModelPtr[4], 0);
}

// [D] [T]
void buildNewCarFromModel(CAR_MODEL *car, MODEL *model, int first)
{
	u_char ptype, clut;
	u_char *polyList;
	CAR_POLY *cp;

	int newNumPolys;

	int i, pass;

	if (first) 
		whichCP = 0;

	if (model == NULL || (model->shape_flags & 0xfffffff) > 0x800000)
	{
		car->numGT3 = 0;
		car->numFT3 = 0;
		car->numB3 = 0;
	}
	else 
	{
		car->vlist = (SVECTOR *)model->vertices;
		car->nlist = (SVECTOR *)model->point_normals;

		for (pass = 0; pass < 3; pass++)
		{
			polyList = (u_char *)model->poly_block;

			if (pass == 1)
				car->pFT3 = carPolyBuffer + whichCP;
			else if (pass == 0)
				car->pGT3 = carPolyBuffer + whichCP;
			else if (pass == 2)
				car->pB3 = carPolyBuffer + whichCP;

			newNumPolys = whichCP;

			for (i = 0; newNumPolys < 2000 && i < model->num_polys; i++)
			{
				ptype = *polyList;

				cp = carPolyBuffer + newNumPolys;

				switch (ptype & 0x1f) 
				{
					case 0:
					case 18:
						if (pass == 2)	// F3
						{
							cp->vindices = M_INT_4R(polyList[1], polyList[2], polyList[3], 0);
							cp->originalindex = i;

							newNumPolys++;
						}
						break;
					case 1:
					case 19:
						if (pass == 2)	// F4
						{
							cp->vindices = M_INT_4R(polyList[4], polyList[5], polyList[6], 0); 
							cp->originalindex = i;

							cp++;

							cp->vindices = M_INT_4R(polyList[4], polyList[6], polyList[7], 0);
							cp->originalindex = i;

							newNumPolys += 2;
						}
						break;
					case 20:
						if (pass == 1)	// FT3
						{
							POLYFT3* pft3 = (POLYFT3*)polyList;
									
							cp->vindices = M_INT_4R(pft3->v0, pft3->v1, pft3->v2, 0);
							cp->clut_uv0 = M_INT_2(texture_cluts[pft3->texture_set][pft3->texture_id], *(ushort*)&pft3->uv0);
							cp->tpage_uv1 = M_INT_2(texture_pages[pft3->texture_set], *(ushort*)&pft3->uv1);
							cp->uv3_uv2 = *(ushort*)&pft3->uv2;
							cp->originalindex = i;

							newNumPolys++;
						}
						break;
					case 21:
						if (pass == 1)	// FT4
						{
							POLYFT4* pft4 = (POLYFT4*)polyList;

							cp->vindices = M_INT_4R(pft4->v0, pft4->v1, pft4->v2, 0);
							cp->clut_uv0 = M_INT_2(texture_cluts[pft4->texture_set][pft4->texture_id], *(ushort *)&pft4->uv0);
							cp->tpage_uv1 = M_INT_2(texture_pages[pft4->texture_set], *(ushort*)&pft4->uv1);
							cp->uv3_uv2 = *(ushort*)&pft4->uv2;
							cp->originalindex = i;

							cp++;
						
							cp->vindices = M_INT_4R(pft4->v0, pft4->v2, pft4->v3, 0);
							cp->clut_uv0 = M_INT_2(texture_cluts[polyList[1]][polyList[2]], *(ushort*)&pft4->uv0);
							cp->tpage_uv1 = M_INT_2(texture_pages[polyList[1]], *(ushort*)&pft4->uv2);
							cp->uv3_uv2 = *(ushort*)&pft4->uv3;
							cp->originalindex = i;

							newNumPolys += 2;
						}
						break;
					case 22:
						if (pass == 0) // GT3
						{
							POLYGT3* pgt3 = (POLYGT3*)polyList;
							
							clut = GetCarPalIndex(pgt3->texture_set);
							civ_clut[clut][pgt3->texture_id][0] = texture_cluts[pgt3->texture_set][pgt3->texture_id];
						
							cp->vindices = M_INT_4R(pgt3->v0, pgt3->v1, pgt3->v2, 0);
							cp->nindices = M_INT_4R(pgt3->n0, pgt3->n1, pgt3->n2, 0);
							cp->clut_uv0 = M_INT_2((clut * 384 + pgt3->texture_id * 12 - 384) >> 1, * (ushort*)&pgt3->uv0);
							cp->tpage_uv1 = M_INT_2(texture_pages[pgt3->texture_set], *(ushort *)&pgt3->uv1);
							cp->uv3_uv2 = *(ushort *)&pgt3->uv2;
							cp->originalindex = i;

							newNumPolys++;
						}
						break;
					case 23:
						if (pass == 0)  // GT4
						{
							POLYGT4* pgt4 = (POLYGT4*)polyList;

							clut = GetCarPalIndex(pgt4->texture_set);
							civ_clut[clut][pgt4->texture_id][0] = texture_cluts[pgt4->texture_set][pgt4->texture_id];

							cp->vindices = M_INT_4R(pgt4->v0, pgt4->v1, pgt4->v2, 0);
							cp->nindices = M_INT_4R(pgt4->n0, pgt4->n1, pgt4->n2, 0);
							cp->clut_uv0 = M_INT_2((clut * 384 + pgt4->texture_id * 12 - 384) >> 1, *(ushort*)&pgt4->uv0);
							cp->tpage_uv1 = M_INT_2(texture_pages[pgt4->texture_set], *(ushort*)&pgt4->uv1);
							cp->uv3_uv2 = *(ushort*)&pgt4->uv2;
							cp->originalindex = i;

							cp++;

							cp->vindices = M_INT_4R(pgt4->v0, pgt4->v2, pgt4->v3, 0);
							cp->nindices = M_INT_4R(pgt4->n0, pgt4->n2, pgt4->n3, 0);
							cp->clut_uv0 = M_INT_2((clut * 384 + pgt4->texture_id * 12 - 384) >> 1, *(ushort*)&pgt4->uv0);
							cp->tpage_uv1 = M_INT_2(texture_pages[pgt4->texture_set], *(ushort *)&pgt4->uv2);
							cp->uv3_uv2 = *(ushort *)&pgt4->uv3;
							cp->originalindex = i;

							newNumPolys += 2;
						}
				}

				polyList += PolySizes[ptype & 0x1f];
			}

			if (pass == 1) 
				car->numFT3 = newNumPolys - whichCP;
			else if (pass == 0)
				car->numGT3 = newNumPolys - whichCP;
			else if (pass == 2) 
				car->numB3 = newNumPolys - whichCP;

			whichCP = newNumPolys;
		}
	}
}

// [D] [T]
void MangleWheelModels(void)
{
	UV_INFO tmpUV2;
	u_char tmpUV;
	u_int v0, v1, v2;
	POLYFT4*src;
	MODEL *m;
	int i, j;

	for (i = 0; i < 3; i++)
	{
		if (i == 1)
			m = gFastWheelModelPtr;
		else if (i == 2)
			m = gDamWheelModelPtr;
		else
			m = gCleanWheelModelPtr;

		// do some fuckery swaps
		src = (POLYFT4*)m->poly_block;
		
		v0 = *(u_int *)&src[2].v0;
		v1 = *(u_int *)&src[2].uv0;
		v2 = *(u_int *)&src[2].uv2;
		*(u_int *)src = *(u_int *)(src + 2);
		*(u_int *)&src->v0 = v0;
		*(u_int *)&src->uv0 = v1;
		*(u_int *)&src->uv2 = v2;
		src->color = src[2].color;

		v0 = *(u_int *)&src[3].v0;
		v1 = *(u_int *)&src[3].uv0;
		v2 = *(u_int *)&src[3].uv2;
		*(u_int *)(src + 1) = *(u_int *)(src + 3);
		*(u_int *)&src[1].v0 = v0;
		*(u_int *)&src[1].uv0 = v1;
		*(u_int *)&src[1].uv2 = v2;
		src[1].color = src[3].color;

		v0 = *(u_int *)&src[4].v0;
		v1 = *(u_int *)&src[4].uv0;
		v2 = *(u_int *)&src[4].uv2;
		*(u_int *)(src + 2) = *(u_int *)(src + 4);
		*(u_int *)&src[2].v0 = v0;
		*(u_int *)&src[2].uv0 = v1;
		*(u_int *)&src[2].uv2 = v2;
		src[2].color = src[4].color;

		v0 = *(u_int *)&src[6].v0;
		v1 = *(u_int *)&src[6].uv0;
		v2 = *(u_int *)&src[6].uv2;
		*(u_int *)(src + 3) = *(u_int *)(src + 6);
		*(u_int *)&src[3].v0 = v0;
		*(u_int *)&src[3].uv0 = v1;
		*(u_int *)&src[3].uv2 = v2;
		src[3].color = src[6].color;

		src[2].v2 = 23;
		src[2].v3 = 22;
		src[3].v0 = 21;
		src[3].v1 = 20;
		src[3].v2 = 19;
		src[3].v3 = 18;
		src[2].v1 = 17;
		src[2].v0 = 16;

		tmpUV = (src->uv0.u + src->uv2.u) / 2;
		src[3].uv3.u = tmpUV;
		src[3].uv2.u = tmpUV;
		src[3].uv1.u = tmpUV;
		src[3].uv0.u = tmpUV;
		src[2].uv3.u = tmpUV;
		src[2].uv2.u = tmpUV;
		src[2].uv1.u = tmpUV;
		src[2].uv0.u = tmpUV;

		tmpUV = (src->uv0.v + src->uv2.v) / 2;
		src[3].uv3.v = tmpUV;
		src[3].uv2.v = tmpUV;
		src[3].uv1.v = tmpUV;
		src[3].uv0.v = tmpUV;
		src[2].uv3.v = tmpUV;
		src[2].uv2.v = tmpUV;
		src[2].uv1.v = tmpUV;
		src[2].uv0.v = tmpUV;

		m->num_polys = 4;

		for (j = 0; j < 2; j++)
		{
			tmpUV2 = src->uv0;
			
			src->uv0 = src->uv1;
			src->uv1 = src->uv2;
			src->uv2 = src->uv3;
			src->uv3 = tmpUV2;

			src++;
		}

	} while (++i < 3);

	// HACK: Show clean model only in Rio.
	//if (GameLevel == 3) 
	//	gFastWheelModelPtr = gCleanWheelModelPtr;
}

// [D] [T]
void ProcessPalletLump(char *lump_ptr, int lump_size)
{
	ushort clutValue;
	int *buffPtr;
	int texnum;
	int palette;
	u_short *clutTablePtr;
	u_short clutTable[320];
	int tpageindex;
	int total_cluts;
	int clut_number;

	total_cluts = *(int*)lump_ptr;
	
	if (total_cluts == 0)
		return;

	buffPtr = (int*)(lump_ptr + 4);
	clutTablePtr = (u_short*)clutTable;

	while (*buffPtr != -1)
	{
		palette = buffPtr[0];
		texnum = buffPtr[1];
		tpageindex = buffPtr[2];
		clut_number = buffPtr[3];

		if (clut_number == -1)
		{
			// store clut
			LoadImage(&clutpos, (u_long*)(buffPtr + 4));

			clutValue = GetClut(clutpos.x, clutpos.y);
			*clutTablePtr++ = clutValue;

			IncrementClutNum(&clutpos);

			buffPtr += 12;
		}
		else
		{
			// use stored clut
			clutValue = clutTable[clut_number];
			buffPtr += 4;
		}

		civ_clut[GetCarPalIndex(tpageindex)][texnum][palette + 1] = clutValue;
	}
}

// [D] [T]
void DrawCarObject(CAR_MODEL* car, MATRIX* matrix, VECTOR* pos, int palette, CAR_DATA* cp, int detail)
{
	static u_long savedSP;

	VECTOR modelLocation;
	SVECTOR cog;

	cog = cp->ap.carCos->cog;

	// [A] mini cars correct position
	if (ActiveCheats.cheat13 != 0)
	{
		cog.vx <<= 1;
		cog.vy <<= 1;
		cog.vz <<= 1;
	}

	gte_SetRotMatrix(matrix);
	gte_SetTransVector(pos);

	gte_ldv0(&cog);

	gte_rtv0tr();

	gte_stlvnl(&modelLocation);

	gte_SetTransVector(&modelLocation);

	savedSP = SetSp((u_long)((u_char*)getScratchAddr(0) + 0x308));

	plotNewCarModel(car, palette);

	SetSp(savedSP);
}

// [D] [T] [A]
void DrawCar(CAR_DATA* cp, int view)
{
	int yVal;
	int maxDamage;
	int WheelSpeed;
	int oboxLenSq;
	CAR_MODEL* CarModelPtr;
	int model;
	CVECTOR col;
	VECTOR pos;
	VECTOR corners[4];
	VECTOR d;
	VECTOR dist;
	MATRIX workmatrix;

	D_CHECK_ERROR(cp < car_data, "Invalid car");

	model = cp->ap.model;

	// draw car lights in for InCar camera
	if (player[view].cameraView == 2 && cp->id == player[view].cameraCarId)
	{
		if (cp->ap.damage[0] < 500)
			LeftLight = 2;
		else if (cp->ap.damage[0] < 1000)
			LeftLight = 1;
		else
			LeftLight = 0;

		if (cp->ap.damage[1] < 500)
			RightLight = 2;
		else if (cp->ap.damage[1] < 1000)
			RightLight = 1;
		else
			RightLight = 0;

		if (gLightsOn == 0)
			return;

		if (lightsOnDelay[cp->id])
			return;

		col.r = 128;
		col.g = 120;
		col.b = 110;

		PlacePoolForCar(cp, &col, 1, 1);
		return;
	}

	pos.vx = cp->hd.where.t[0];
	pos.vz = cp->hd.where.t[2];
	pos.vy = -cp->hd.where.t[1];

	SetFrustrumMatrix();

	if (FrustrumCheck(&pos, 800) == -1)
		return;

	// corners for frustrum checking of big cars
	corners[0].vx = corners[1].vx = pos.vx + cp->hd.oBox.radii[0].vx;
	corners[0].vx += cp->hd.oBox.radii[2].vx;
	corners[1].vx -= cp->hd.oBox.radii[2].vx;

	corners[2].vx = corners[3].vx = pos.vx - cp->hd.oBox.radii[0].vx;
	corners[2].vx += cp->hd.oBox.radii[2].vx;
	corners[3].vx -= cp->hd.oBox.radii[2].vx;

	corners[0].vz = corners[1].vz = pos.vz + cp->hd.oBox.radii[0].vz;
	corners[0].vz += cp->hd.oBox.radii[2].vz;
	corners[1].vz -= cp->hd.oBox.radii[2].vz;

	corners[2].vz = corners[3].vz = pos.vz - cp->hd.oBox.radii[0].vz;
	corners[2].vz += cp->hd.oBox.radii[2].vz;
	corners[3].vz -= cp->hd.oBox.radii[2].vz;

	corners[3].vy = pos.vy;
	corners[2].vy = pos.vy;
	corners[1].vy = pos.vy;
	corners[0].vy = pos.vy;

	if (FrustrumCheck(corners, 0) == -1 &&
		FrustrumCheck(corners + 1, 0) == -1 &&
		FrustrumCheck(corners + 2, 0) == -1 &&
		FrustrumCheck(corners + 3, 0) == -1)
	{
		return;
	}

	d.vx = cp->hd.oBox.location.vx - camera_position.vx;
	d.vy = -camera_position.vy - cp->hd.oBox.location.vy;
	d.vz = cp->hd.oBox.location.vz - camera_position.vz;

	dist.vx = d.vx * cp->hd.oBox.radii[0].vx + d.vy * cp->hd.oBox.radii[0].vy + d.vz * cp->hd.oBox.radii[0].vz;

	if (dist.vx < 0)
		dist.vx = -dist.vx;

	dist.vy = d.vx * cp->hd.oBox.radii[1].vx + d.vy * cp->hd.oBox.radii[1].vy + d.vz * cp->hd.oBox.radii[1].vz;
	if (dist.vy < 0)
		dist.vy = -dist.vy;

	dist.vz = d.vx * cp->hd.oBox.radii[2].vx + d.vy * cp->hd.oBox.radii[2].vy + d.vz * cp->hd.oBox.radii[2].vz;
	if (dist.vz < 0)
		dist.vz = -dist.vz;

	oboxLenSq = cp->hd.oBox.length[0];

	if (dist.vx < oboxLenSq * oboxLenSq)
	{
		oboxLenSq = cp->hd.oBox.length[1];

		if (dist.vy < oboxLenSq * oboxLenSq)
		{
			oboxLenSq = cp->hd.oBox.length[2];

			if (dist.vz < oboxLenSq * oboxLenSq)
				return;
		}
	}

	pos.vx -= camera_position.vx;
	pos.vy -= camera_position.vy;
	pos.vz -= camera_position.vz;

	Apply_Inv_CameraMatrix(&pos);

	num_cars_drawn++;

	MulMatrix0(&inv_camera_matrix, &cp->hd.drawCarMat, &workmatrix);

	// [A] there was mini cars cheat
	// we need full blown mini cars with physics support
	if (ActiveCheats.cheat13 != 0)
	{
		int i;
		for (i = 0; i < 3; i++)
		{
			workmatrix.m[i][0] >>= 1;
			workmatrix.m[i][1] >>= 1;
			workmatrix.m[i][2] >>= 1;
		}
	}

	// to check if car is flipped
	yVal = cp->hd.where.m[1][1];

	// LOD switching
	if (pos.vz <= CAR_LOD_SWITCH_DISTANCE && gForceLowDetailCars == 0 || cp->controlType == CONTROL_TYPE_PLAYER)
	{
		int doSmoke = 0;

		WheelSpeed = cp->hd.speed * 8192;
		maxDamage = MaxPlayerDamage[0];

		if (cp->controlType == CONTROL_TYPE_PLAYER)
		{
			if(*cp->ai.padid >= 0 && *cp->ai.padid < 2)
				maxDamage = MaxPlayerDamage[*cp->ai.padid];
		}

		if (cp->totalDamage >= maxDamage)
		{
			if (WheelSpeed + 59999U < 119999)
				AddFlamingEngine(cp);

			doSmoke = 2;
		}
		else
		{
			if (cp->ap.damage[0] > 2000 || cp->ap.damage[1] > 2000)
			{
				if (cp->ap.damage[0] > 3000 || cp->ap.damage[1] > 3000)
					doSmoke = 2;
				else
					doSmoke = 1;
			}
		}

		if (doSmoke && WheelSpeed + 399999U < 1199999)
			AddSmokingEngine(cp, doSmoke - 1, WheelSpeed);

		AddExhaustSmoke(cp, doSmoke > 1, WheelSpeed);

		SetShadowPoints(cp, corners);
		PlaceShadowForCar(corners, 4, 10, yVal < 0 ? 0 : 2);

		ComputeCarLightingLevels(cp, 1);

		gTempCarUVPtr = gTempHDCarUVDump[cp->id];
		CarModelPtr = &NewCarModel[model];
		CarModelPtr->vlist = gTempCarVertDump[cp->id];
		CarModelPtr->nlist = gTempCarVertDump[cp->id];

		FindCarLightFade(&workmatrix);

		DrawCarObject(CarModelPtr, &workmatrix, &pos, cp->ap.palette, cp, 1);

		if (ActiveCheats.cheat13 != 0)
		{
			MulMatrix0(&inv_camera_matrix, &cp->hd.drawCarMat, &workmatrix);
		}

		DrawCarWheels(cp, &workmatrix, &pos, view);
	}
	else
	{
		CarModelPtr = &NewLowCarModel[model];
		CarModelPtr->nlist = gTempCarVertDump[cp->id];

		gTempCarUVPtr = gTempLDCarUVDump[cp->id];

		if (pos.vz < 8000)
		{
			SetShadowPoints(cp, corners);
			PlaceShadowForCar(corners, 0, 0, yVal < 0 ? 0 : 2);
		}

		ComputeCarLightingLevels(cp, 0);

		FindCarLightFade(&workmatrix);

		DrawCarObject(CarModelPtr, &workmatrix, &pos, cp->ap.palette, cp, 0);
	}

	TransparentObject = 0;

	if (cp->controlType == CONTROL_TYPE_PLAYER)
		PlayerCarFX(cp);
	else if (cp->controlType == CONTROL_TYPE_CIV_AI)
		CivCarFX(cp);

	if (gLightsOn && !lightsOnDelay[cp->id])
	{
		if (cp->controlType == CONTROL_TYPE_CIV_AI)
		{
			if (cp->ai.c.thrustState != 3 || (cp->ai.c.ctrlState != 5 && cp->ai.c.ctrlState != 7 && cp->ai.c.ctrlState != 8))
				AddNightLights(cp);
		}
		else if (SilenceThisCar(cp->id) == 0)
			AddNightLights(cp);
	}

	if(CarHasSiren(cp->ap.model))
	{
		if ((IS_ROADBLOCK_CAR(cp) || cp->controlType == CONTROL_TYPE_PURSUER_AI) ||		// any regular cop car including roadblock
			gInGameCutsceneActive && cp->controlType == CONTROL_TYPE_CUTSCENE && force_siren[CAR_INDEX(cp)] != 0 ||		// any car with siren in cutscene
			gCurrentMissionNumber == 26 && cp->controlType == CONTROL_TYPE_CUTSCENE && cp->ap.model == 4)				// Vegas ambulance
		{
			if (cp->ai.p.dying < 75)
				AddCopCarLight(cp);
		}
	}
}

// [D] [T]
char GetCarPalIndex(int tpage)
{
	int i;

	for (i = 0; i < 8; i++)
	{
		if (tpage == carTpages[GameLevel][i])
			return i;
	}

	return 0;
}