#include "driver2.h"

#include "draw.h"
#include "main.h"
#include "map.h"
#include "models.h"
#include "system.h"
#include "camera.h"
#include "mission.h"
#include "cell.h"
#include "tile.h"
#include "objanim.h"
#include "texture.h"
#include "cars.h"
#include "debris.h"
#include "ASM/rndrasm.h"
#include "event.h"


MATRIX aspect =
{
	{
		{ 6553, 0, 0 },
		{ 0, 4096, 0 },
		{ 0, 0, 4096 }
	},
	{ 0, 0, 0 }
};

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

int PolySizes[56] = {
	8,  12, 16, 24,
	20, 20, 28, 32,
	8,  12, 16, 16,
	0,  0,  0,  0,
	12, 12, 12, 16,
	20, 20, 24, 24,
	0,  0,  0,  0,
	0,  0,  0,  0,
	8,  12, 16, 24,
	20, 24, 28, 32,
	0,  0,  0,  0,
	0,  0,  0,  0,
	12, 12, 12, 16,
	20, 20, 24, 24,
};

SVECTOR day_vectors[4] =
{
  { -3132, 2520, 783, 0 },
  { -3132, 2520, 783, 0 },
  { -2364, 2364, 2364, 0 },
  { -3132, 2520, 783, 0 }
};

SVECTOR night_vectors[4] =
{
  { 3132, 2520, 783, 0 },
  { 3132, 2520, 783, 0 },
  { -2364, 2364, -2364, 0 },
  { 3132, 2520, 783, 0 }
};

SVECTOR day_colours[4] =
{
  { 3200, 3200, 3200, 0 },
  { 3200, 3200, 3200, 0 },
  { 3200, 3200, 3200, 0 },
  { 3200, 3200, 3200, 0 }
};

SVECTOR night_colours[4] =
{
  { 880, 880, 905, 0 },
  { 880, 880, 905, 0 },
  { 880, 880, 905, 0 },
  { 880, 880, 905, 0 }
};

void* model_object_ptrs[MAX_DRAWN_BUILDINGS];
void* model_tile_ptrs[MAX_DRAWN_TILES];
void* anim_obj_buffer[MAX_DRAWN_ANIMATING];
void* spriteList[MAX_DRAWN_SPRITES];

u_int planeColours[8];

MATRIX inv_camera_matrix;
MATRIX face_camera;
MATRIX2 CompoundMatrix[64];

u_int farClip2Player = 36000;

int goFaster = 0;	// [A] was 1
int fasterToggle = 0;

//int current_object_computed_value = 0;

int combointensity;

char CurrentPVS[444]; // 20*20+4
MATRIX2 matrixtable[64];
int setupYet = 0;

int gDrawDistance = 441;

#ifndef PSX
_pct& plotContext = *(_pct*)((u_char*)getScratchAddr(0) + 1024 - sizeof(_pct));	// orig offset: 0x1f800020
#endif

struct MVERTEX5x5
{
	MVERTEX verts[5][5];
};

// [D] [T] [A]
void addSubdivSpriteShadow(POLYFT4* src, SVECTOR* verts, int z)
{
	int m;

	m = 4;

#if 0 //def PSX
	MVERTEX5x5& subdiVerts = *(MVERTEX5x5*)(u_char*)getScratchAddr(0);
#else
	MVERTEX5x5 subdiVerts;
#endif

	plotContext.colour = 0x2E000000;
	plotContext.flags = PLOT_INV_CULL;
	plotContext.clut = texture_cluts[src->texture_set][src->texture_id] << 0x10;
	plotContext.tpage = texture_pages[src->texture_set] << 0x10;

	if (z > 3200)
		m = 1; //2;

	plotContext.ot += 28;

	copyVector(&subdiVerts.verts[0][0], &verts[src->v0]);
	subdiVerts.verts[0][0].uv.val = *(ushort*)&src->uv0;

	copyVector(&subdiVerts.verts[0][1], &verts[src->v1]);
	subdiVerts.verts[0][1].uv.val = *(ushort*)&src->uv1;

	copyVector(&subdiVerts.verts[0][2], &verts[src->v3]);
	subdiVerts.verts[0][2].uv.val = *(ushort*)&src->uv3;

	copyVector(&subdiVerts.verts[0][3], &verts[src->v2]);
	subdiVerts.verts[0][3].uv.val = *(ushort*)&src->uv2;

	makeMesh((MVERTEX(*)[5][5])subdiVerts.verts, m, m);
	drawMesh((MVERTEX(*)[5][5])subdiVerts.verts, m, m, &plotContext);

	plotContext.ot -= 28;
}


MATRIX shadowMatrix;

// [D] [T] [A]
void DrawSprites(PACKED_CELL_OBJECT** sprites, int numFound)
{
	int i;
	int z;
	u_int spriteColour, lightdd;
	u_char lightLevel;
	MODEL* model;
	PACKED_CELL_OBJECT* pco;
	PACKED_CELL_OBJECT** list;
	int numShadows;

#if 0 //def PSX
	MVERTEX5x5& subdiVerts = *(MVERTEX5x5*)(u_char*)getScratchAddr(0);
#else
	MVERTEX5x5 subdiVerts;
#endif

	lightdd =	FIXEDH(camera_matrix.m[2][0] * day_vectors[GameLevel].vx) +
				FIXEDH(camera_matrix.m[2][1] * day_vectors[GameLevel].vy) +
				FIXEDH(camera_matrix.m[2][2] * day_vectors[GameLevel].vz) + ONE * 3072;

	lightLevel = (lightdd >> 0x12) + 0x20U & 0xff;

	if (gWeather > 0 && gTimeOfDay == 1)
	{
		lightLevel = (lightLevel * 2 * NightAmbient) >> 8 & 0xff;
	}

	if (gTimeOfDay == 0)
	{
		lightLevel = (lightLevel * 2 * NightAmbient) >> 8;
	}
	else if (gTimeOfDay == 2)
	{
		lightLevel = (lightLevel * 2 * NightAmbient) >> 8;
	}
	else if (gTimeOfDay == 3)
	{
		if (GameLevel == 0)
			lightLevel *= 2;	// [A] level bug - Chicago trees lit wrong
		else
			lightLevel /= 3;
	}

	spriteColour = lightLevel << 0x10 | lightLevel << 8 | 0x2c000000 | lightLevel;

	i = 2;
	do {
		shadowMatrix.m[i][0] = inv_camera_matrix.m[i][2];
		shadowMatrix.m[i][1] = -inv_camera_matrix.m[i][0];
		shadowMatrix.m[i][2] = inv_camera_matrix.m[i][0];
	} while (i--);


	plotContext.primptr = current->primptr;
	plotContext.ptexture_pages = (ushort(*)[128])texture_pages;
	plotContext.ptexture_cluts = (ushort(*)[128][32])texture_cluts;
	plotContext.polySizes = PolySizes;
	plotContext.ot = current->ot;

	list = sprites;

	plotContext.colour = spriteColour;
	plotContext.current = current;

	numShadows = 0;

	while (numFound--)
	{
		pco = *list;
		list++;

		int modelnumber = (pco->value >> 6) | (pco->pos.vy & 1) << 10;

		model = modelpointers[modelnumber];
		plotContext.colour = spriteColour;

#ifndef PSX
		if ((pco->value & 0x3f) == 63 || (gTimeOfDay == 3 && modelnumber == 945)) // [A] Vegas tree fix
			plotContext.colour = 0x2c808080;
#endif
		plotContext.scribble[0] = pco->pos.vx;
		plotContext.scribble[1] = (pco->pos.vy << 0x10) >> 0x11;
		plotContext.scribble[2] = pco->pos.vz;

		z = Apply_InvCameraMatrixAndSetMatrix((VECTOR_NOPAD*)plotContext.scribble, (MATRIX2*)&face_camera);

		if (z < 1000)
		{
			POLYFT4* src;
			SVECTOR* verts;
			int numPolys;

			numPolys = (u_int)model->num_polys;
			src = (POLYFT4*)model->poly_block;
			verts = (SVECTOR*)model->vertices;

			plotContext.flags |= 4;

			while (numPolys--, numPolys >= 0)
			{
				plotContext.clut = texture_cluts[src->texture_set][src->texture_id] << 0x10;
				plotContext.tpage = texture_pages[src->texture_set] << 0x10;

				copyVector(&subdiVerts.verts[0][0], &verts[src->v0]);
				subdiVerts.verts[0][0].uv.val = *(ushort*)&src->uv0;

				copyVector(&subdiVerts.verts[0][1], &verts[src->v1]);
				subdiVerts.verts[0][1].uv.val = *(ushort*)&src->uv1;

				copyVector(&subdiVerts.verts[0][2], &verts[src->v3]);
				subdiVerts.verts[0][2].uv.val = *(ushort*)&src->uv3;

				copyVector(&subdiVerts.verts[0][3], &verts[src->v2]);
				subdiVerts.verts[0][3].uv.val = *(ushort*)&src->uv2;

				makeMesh((MVERTEX(*)[5][5])subdiVerts.verts, 4, 4);
				drawMesh((MVERTEX(*)[5][5])subdiVerts.verts, 4, 4, &plotContext);

				src++;
			}

			plotContext.flags &= ~4;
		}
		else
		{
			plotContext.ot -= 133;

			Tile1x1(model);

			plotContext.ot += 133;
		}

#ifdef PSX
#define MAX_TREE_SHADOW_DISTANCE 7000
#else
#define MAX_TREE_SHADOW_DISTANCE 14000
#endif
		
		if (wetness == 0 && gTimeOfDay != 3 &&
			(pco->value & 0x20) == 0 && 
			z < MAX_TREE_SHADOW_DISTANCE &&
			numShadows < 40)
		{
			gte_SetRotMatrix(&shadowMatrix);

			addSubdivSpriteShadow((POLYFT4*)model->poly_block, (SVECTOR*)model->vertices, z);

			if (model->num_polys == 2)
				addSubdivSpriteShadow((POLYFT4*)(model->poly_block + sizeof(POLYFT4)), (SVECTOR*)model->vertices, z);

			gte_SetRotMatrix(&face_camera);

			numShadows++;
		}
	}
	current->primptr = plotContext.primptr;
}

// [D] [T]
void SetupPlaneColours(u_int ambient)
{
	u_int r, g, b;

	if ((gWeather - 1U > 1) && gTimeOfDay != 0 && gTimeOfDay != 2)
	{
		if (gTimeOfDay == 1)
		{
			b = ambient & 0xff;
			g = ambient >> 8 & 0xff;
			r = ambient >> 0x10 & 0xff;

			planeColours[1] = (r * 0x78 >> 7) << 0x10 | (g * 0x78 >> 7) << 8 | b * 0x78 >> 7;
			planeColours[2] = (r * 0x67 >> 7) << 0x10 | (g * 0x67 >> 7) << 8 | b * 0x67 >> 7;
			planeColours[3] = (r * 0xd >> 5) << 0x10 | (g * 0xd >> 5) << 8 | b * 0xd >> 5;
			planeColours[0] = r << 0x10 | g << 8 | b;
			planeColours[4] = (r * 3 >> 3) << 0x10 | (g * 3 >> 3) << 8 | b * 3 >> 3;
			planeColours[5] = planeColours[3];
			planeColours[6] = planeColours[2];
			planeColours[7] = planeColours[1];
			return;
		}

		planeColours[0] = ambient;
		planeColours[1] = ambient;
		planeColours[2] = ambient;
		planeColours[3] = ambient;
		planeColours[4] = ambient;
		planeColours[5] = ambient;
		planeColours[6] = ambient;
		planeColours[7] = ambient;
		return;
	}

	planeColours[0] = ambient;
	planeColours[1] = ambient + 0x10101;
	planeColours[2] = ambient + 0x30303;
	planeColours[3] = ambient + 0x80808;
	planeColours[4] = ambient + 0xa0a0a;
	planeColours[5] = ambient + 0x80808;
	planeColours[6] = ambient + 0x30303;
	planeColours[7] = ambient + 0x10101;
}


// [D] [T]
void SetupDrawMapPSX(void)
{
	int region_x1;
	int region_z1;
	int current_barrel_region_x1;
	int current_barrel_region_z1;
	int theta;

	if (setupYet != 0)
		return;

	current_cell_x = (camera_position.vx + units_across_halved) / MAP_CELL_SIZE;
	current_cell_z = (camera_position.vz + units_down_halved) / MAP_CELL_SIZE;

	region_x1 = current_cell_x / MAP_REGION_SIZE;
	region_z1 = current_cell_z / MAP_REGION_SIZE;

	current_barrel_region_x1 = (region_x1 & 1);
	current_barrel_region_z1 = (region_z1 & 1);

	GetPVSRegionCell2(
		current_barrel_region_x1 + current_barrel_region_z1 * 2,
		region_x1 + region_z1 * regions_across,
		(current_cell_z % MAP_REGION_SIZE) * MAP_REGION_SIZE + (current_cell_x % MAP_REGION_SIZE),
		CurrentPVS);

	for (theta = 0; theta < 64; theta++)
		MulMatrix0(&inv_camera_matrix, (MATRIX*)&matrixtable[theta], (MATRIX*)&CompoundMatrix[theta]);

	InitFrustrumMatrix();
	SetFrustrumMatrix();

	setupYet = 1;
}

MATRIX frustrum_matrix;

// [D] [T]
void InitFrustrumMatrix(void)
{
	int a;

	a = -camera_angle.vy;

	frustrum_matrix.m[0][1] = 0;
	frustrum_matrix.m[0][0] = RSIN(a);
	frustrum_matrix.m[0][2] = RCOS(a);

	a = (FrAng - 1024) - camera_angle.vy;

	frustrum_matrix.m[1][1] = 0;
	frustrum_matrix.m[1][0] = RSIN(a);
	frustrum_matrix.m[1][2] = RCOS(a);

	a = -(FrAng - 1024) - camera_angle.vy;

	frustrum_matrix.m[2][1] = 0;
	frustrum_matrix.m[2][0] = RSIN(a);
	frustrum_matrix.m[2][2] = RCOS(a);
	frustrum_matrix.t[0] = -80;
}

// [D] [T]
void SetFrustrumMatrix(void)
{
	gte_SetLightMatrix(&frustrum_matrix);
}

// [D] [T]
void Set_Inv_CameraMatrix(void)
{
	gte_SetColorMatrix(&inv_camera_matrix);
}


// [D] [T] [A]
void CalcObjectRotationMatrices(void)
{
	int i;
	int angle;
	MATRIX* m;

	angle = 0;

	for (i = 0; i < 64; i++)
	{
		// simpler and faster method
		m = (MATRIX*)&matrixtable[i];

		InitMatrix(*m);
		RotMatrixY(angle, m);

		angle += 64;
	}
}

// [D] [T]
void PlotMDL_less_than_128(MODEL* model)
{
	RenderModel(model, NULL, NULL, 0, 0, 0, 0);
}

int gForceLowDetailCars = 0;
int num_cars_drawn = 0;

// [D] [T]
void DrawAllTheCars(int view)
{
	static int car_distance[MAX_CARS]; // offset 0x0
	CAR_DATA* cars_to_draw[MAX_CARS];

	int dx, dz;
	int dist;
	int i, j;
	CAR_DATA* cp;
	int num_cars_to_draw;
	int spacefree;
	

	num_cars_drawn = 0;
	num_cars_to_draw = 0;

	cp = &car_data[MAX_CARS - 1];
	do {
		if (cp->controlType != CONTROL_TYPE_NONE && 
			PositionVisible((VECTOR*)cp->hd.where.t))
		{
			// XZ distance estimation
			dx = ABS(camera_position.vx - cp->hd.where.t[0]);
			dz = ABS(camera_position.vz - cp->hd.where.t[2]);

			if (dx < dz)
				dist = dz + dx / 2;
			else
				dist = dx + dz / 2;

			if (dist < 16000)
			{
				car_distance[num_cars_to_draw] = dx + dz;
				cars_to_draw[num_cars_to_draw] = cp;
				num_cars_to_draw++;
			}
		}

		cp--;
	} while (cp >= car_data);

	if (num_cars_to_draw != 0)
	{
		gForceLowDetailCars = 0;

		// sort cars by distance
		i = 1;
		while (i < num_cars_to_draw)
		{
			cp = cars_to_draw[i];
			dist = car_distance[i];

			j = i - 1;

			while (dist < car_distance[j])
			{
				car_distance[i] = car_distance[j];
				cars_to_draw[i] = cars_to_draw[j];

				if (j == 0)
					break;
				
				j--;
			}
			
			cars_to_draw[i] = cp;
			car_distance[i] = dist;

			i++;
		}

		i = 0;
		spacefree = (num_cars_to_draw - 1) * 2000;

		while (i < num_cars_to_draw)
		{
			// Don't exceed draw buffers
			if ((int)(current->primtab + (-3000 - (int)(current->primptr - PRIMTAB_SIZE))) < 5800)
				return;

			// make cars look uglier
			if ((int)(current->primtab + (-3000 - (int)(current->primptr - PRIMTAB_SIZE)) - spacefree) < 5800)
				gForceLowDetailCars = 1;

			if (cars_to_draw[i]->controlType == CONTROL_TYPE_PLAYER)
				gForceLowDetailCars = 0;

			DrawCar(cars_to_draw[i], view);
			
			spacefree -= 2000;
			i++;
		}
	}
}


// [D] [T]
u_int normalIndex(SVECTOR* verts, u_int vidx)
{
	SVECTOR* v0, *v1, *v2;
	int x, y;
	int th23;

	int nx, ny, nz;

	SVECTOR p, q;

	v0 = verts + (vidx & 0xff);
	v1 = verts + (vidx >> 8 & 0xff);
	v2 = verts + (vidx >> 16 & 0xff);

	p.vz = v1->vz - v0->vz;
	q.vz = v2->vz - v0->vz;

	p.vy = v1->vy - v0->vy;
	q.vy = v2->vy - v0->vy;

	p.vx = v1->vx - v0->vx;
	q.vx = v2->vx - v0->vx;

	nx = p.vy * q.vz - p.vz * q.vy;
	nz = p.vx * q.vy - p.vy * q.vx;

	x = nx + (nz >> 3);
	y = nz - (x >> 2);

	x += (y >> 3);

	if (x < y)
	{
		if (x + y < 1)
			th23 = y < 0 ? 5 : 4;
		else
			th23 = x < 0 ? 3 : 2;
	}
	else
	{
		if (x + y < 1)
			th23 = x < 0 ? 6 : 7;
		else
			th23 = 0 < y ? 1 : 0;
	}

	th23 *= 4;

	ny = p.vz * q.vx - p.vx * q.vz;

	nx = ABS(nx);
	nz = ABS(nz);

	if (nx + nz < ny)
		th23 += 1;

	if (nx + nz < -ny)
		th23 = th23 & 0x1f | 2;
	else
		th23 = th23 & 0x1f;

	return th23 | 0x80;
}

// [A]
void ConvertPolygonTypes(MODEL* model, _pct* pc)
{
	SVECTOR* srcVerts;
	PL_POLYFT4* polys;
	u_char temp, ptype;
	int i;

	// [A] we are storing the processing flag here
	if (model->tri_verts & 0x8000)
	{
		return;
	}
	
	model->tri_verts |= 0x8000;

	srcVerts = (SVECTOR*)model->vertices;
	polys = (PL_POLYFT4*)model->poly_block;
	i = model->num_polys;

	// pre-process vertices
	while (i-- > 0)
	{
		ptype = polys->id & 0x1f;

		// convert poly types
		if ((ptype & 0x1) == 0 && ptype != 8) // is FT3 triangle?
		{
			temp = polys->uv2.v;
			polys->uv3.u = polys->uv2.u;
			polys->uv3.v = temp;

			polys->v3 = polys->v2;

			polys->id |= 1;
			ptype |= 1;
		}

		if (ptype != 11 && ptype != 21 && ptype != 23)
		{
			polys = (PL_POLYFT4*)((char*)polys + pc->polySizes[ptype]);
			continue;
		}

		// also precalculate normal index
		if (ptype != 21 && (polys->th & 0x80) == 0)
		{
			polys->th = normalIndex(srcVerts, *(u_int*)&polys->v0);
		}
		polys = (PL_POLYFT4*)((char*)polys + pc->polySizes[ptype]);
	}
}

void PlotBuildingModel(MODEL* model, int rot, _pct* pc)
{
	int opz, Z, r;
	PL_POLYFT4* polys;
	int i;
	u_char ptype;
	POLY_FT4* prims;
	SVECTOR* srcVerts;
	int combo;

	srcVerts = (SVECTOR*)model->vertices;
	polys = (PL_POLYFT4*)model->poly_block;

	combo = combointensity;

	ConvertPolygonTypes(model, pc);

	i = model->num_polys;
	while (i-- > 0)
	{
		ptype = polys->id & 0x1f;

		// skip certain polygons
		if (ptype != 11 && ptype != 21 && ptype != 23)
		{
			polys = (PL_POLYFT4*)((char*)polys + pc->polySizes[ptype]);
			continue;
		}

		// perform transform
		gte_ldv3(&srcVerts[polys->v0], &srcVerts[polys->v1], &srcVerts[polys->v3]);
		gte_rtpt();

		// get culling value
		gte_nclip();
		gte_stopz(&opz);

		r = rot;

		if (ptype == 21)
		{
			pc->colour = combo & 0x2ffffffU | 0x2c000000;
		}
		else
		{
			pc->colour = pc->f4colourTable[(r >> 3) * 4 - polys->th & 31];
		}

		if (opz > 0)
		{
			pc->tpage = (*pc->ptexture_pages)[polys->texture_set];
			pc->clut = (*pc->ptexture_cluts)[polys->texture_set][polys->texture_id];

			prims = (POLY_FT4*)pc->primptr;

			setPolyFT4(prims);
			*(u_int*)&prims->r0 = pc->colour;

			// retrieve first three verts
			gte_stsxy3(&prims->x0, &prims->x1, &prims->x2);

			// translate 4th vert and get OT Z value
			gte_ldv0(&srcVerts[polys->v2]);
			gte_rtps();
			gte_avsz4();

			gte_stotz(&Z);

			gte_stsxy(&prims->x3);

			prims->tpage = pc->tpage;
			prims->clut = pc->clut;

			*(ushort*)&prims->u0 = *(ushort*)&polys->uv0;
			*(ushort*)&prims->u1 = *(ushort*)&polys->uv1;
			*(ushort*)&prims->u2 = *(ushort*)&polys->uv3;
			*(ushort*)&prims->u3 = *(ushort*)&polys->uv2;

			addPrim(pc->ot + (Z >> 1), prims);

			pc->primptr += sizeof(POLY_FT4);
		}

		polys = (PL_POLYFT4*)((char*)polys + pc->polySizes[ptype]);
	}
}

// [D] [T] [A] custom
void PlotBuildingModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n)
{
	int opz, Z, r;
	int diff, minZ, maxZ;
	PL_POLYFT4* polys;
	int i;
	u_char ptype;
	POLY_FT4* prims;
	SVECTOR* srcVerts;
	int combo;

#if 0//def PSX
	MVERTEX5x5& subdiVerts = *(MVERTEX5x5*)(u_char*)getScratchAddr(0);
#else
	MVERTEX5x5 subdiVerts;
#endif

	srcVerts = (SVECTOR*)model->vertices;
	polys = (PL_POLYFT4*)model->poly_block;

	combo = combointensity;

	ConvertPolygonTypes(model, pc);

	i = model->num_polys;
	while (i-- > 0)
	{
		// iterate through polygons
		// with skipping
		ptype = polys->id & 0x1f;

		if (ptype != 11 && ptype != 21 && ptype != 23)
		{
			polys = (PL_POLYFT4*)((char*)polys + pc->polySizes[ptype]);
			continue;
		}

		// perform transform
		gte_ldv3(&srcVerts[polys->v0], &srcVerts[polys->v1], &srcVerts[polys->v3]);
		gte_rtpt();

		// get culling value
		gte_nclip();
		gte_stopz(&opz);

		r = rot;

		if (ptype == 21)
		{
			pc->colour = combo & 0x2ffffffU | 0x2c000000;
		}
		else
		{
			pc->colour = pc->f4colourTable[(r >> 3) * 4 - polys->th & 31];
		}

		if (opz > 0)
		{
			gte_stsz3(&pc->scribble[0], &pc->scribble[1], &pc->scribble[2]);

			pc->tpage = (*pc->ptexture_pages)[polys->texture_set] << 0x10;
			pc->clut = (*pc->ptexture_cluts)[polys->texture_set][polys->texture_id] << 0x10;

			minZ = pc->scribble[2];
			if (pc->scribble[1] < minZ)
				minZ = pc->scribble[1];

			if (pc->scribble[0] < minZ)
				minZ = pc->scribble[0];

			maxZ = pc->scribble[2];
			if (maxZ < pc->scribble[1])
				maxZ = pc->scribble[1];

			diff = maxZ - minZ;
			if (maxZ < pc->scribble[0])
				diff = pc->scribble[0] - minZ;

			ushort uv0, uv1, uv2, uv3;

			// [A] special case
			if(ptype == 23)
			{
				POLYGT4* pgt4 = (POLYGT4*)polys;
				uv0 = *(ushort*)&pgt4->uv0;
				uv1 = *(ushort*)&pgt4->uv1;
				uv2 = *(ushort*)&pgt4->uv2;
				uv3 = *(ushort*)&pgt4->uv3;
			}
			else
			{
				uv0 = *(ushort*)&polys->uv0;
				uv1 = *(ushort*)&polys->uv1;
				uv2 = *(ushort*)&polys->uv2;
				uv3 = *(ushort*)&polys->uv3;
			}

			if (n == 0 || diff << 2 <= minZ - 350)
			{
				prims = (POLY_FT4*)pc->primptr;

				setPolyFT4(prims);
				*(u_int*)&prims->r0 = pc->colour;

				// retrieve first three verts
				gte_stsxy3(&prims->x0, &prims->x1, &prims->x2);

				// translate 4th vert and get OT Z value
				gte_ldv0(&srcVerts[polys->v2]);
				gte_rtps();
				gte_avsz4();

				gte_stotz(&Z);

				gte_stsxy(&prims->x3);

				prims->tpage = pc->tpage >> 0x10;
				prims->clut = pc->clut >> 0x10;

				*(ushort*)&prims->u0 = uv0;
				*(ushort*)&prims->u1 = uv1;
				*(ushort*)&prims->u2 = uv3;
				*(ushort*)&prims->u3 = uv2;

				addPrim(pc->ot + (Z >> 1), prims);

				pc->primptr += sizeof(POLY_FT4);
			}
			else
			{
				r = n;
				if (n == 1)
				{
					if (minZ - 150 < (diff << 1))
						r = 4;
					else
						r = 2;
				}

				copyVector(&subdiVerts.verts[0][0], &srcVerts[polys->v0]);
				subdiVerts.verts[0][0].uv.val = uv0;

				copyVector(&subdiVerts.verts[0][1], &srcVerts[polys->v1]);
				subdiVerts.verts[0][1].uv.val = uv1;

				copyVector(&subdiVerts.verts[0][2], &srcVerts[polys->v3]);
				subdiVerts.verts[0][2].uv.val = uv3;

				copyVector(&subdiVerts.verts[0][3], &srcVerts[polys->v2]);
				subdiVerts.verts[0][3].uv.val = uv2;

				makeMesh((MVERTEX(*)[5][5])subdiVerts.verts, r, r);
				drawMesh((MVERTEX(*)[5][5])subdiVerts.verts, r, r, pc);
			}
		}

		polys = (PL_POLYFT4*)((char*)polys + pc->polySizes[ptype]);
	}
}


// [D] [T]
int DrawAllBuildings(CELL_OBJECT** objects, int num_buildings)
{
	int i, mat, prev_mat;
	int Z, zbias;
	int drawlimit;
	MODEL* model;
	OTTYPE* ot;
	CELL_OBJECT* cop;

	prev_mat = -1;
	
	for (i = 0; i < 8; i++)
	{
		plotContext.f4colourTable[i * 4 + 0] = planeColours[i] | 0x2C000000;
		plotContext.f4colourTable[i * 4 + 1] = planeColours[0] | 0x2C000000;
		plotContext.f4colourTable[i * 4 + 2] = planeColours[5] | 0x2C000000;
		plotContext.f4colourTable[i * 4 + 3] = planeColours[0] | 0x2C000000; // default: 0x2C00F0F0
	}

	plotContext.current = current;
	plotContext.ptexture_pages = &texture_pages;
	plotContext.ptexture_cluts = &texture_cluts;
	plotContext.polySizes = PolySizes;
	plotContext.flags = 0;
	plotContext.primptr = plotContext.current->primptr;

	ot = plotContext.current->ot + 8;

	while (num_buildings--)
	{
		cop = (CELL_OBJECT*)*objects;
		mat = cop->yang;

		if (prev_mat == mat)
		{
			Z = Apply_InvCameraMatrixSetTrans(&cop->pos);
		}
		else
		{
			Z = Apply_InvCameraMatrixAndSetMatrix(&cop->pos, &CompoundMatrix[prev_mat = mat]);
		}

		model = Z > DRAW_LOD_DIST_LOW ? pLodModels[cop->type] : modelpointers[cop->type];

		zbias = model->zBias - 64;

		if (zbias < 0)
			zbias = 0;

		plotContext.ot = ot + zbias * 4;

		if(Z <= DRAW_LOD_DIST_HIGH)
			PlotBuildingModelSubdivNxN(model, cop->yang, &plotContext, 1);
		else
			PlotBuildingModel(model, cop->yang, &plotContext);

		drawlimit = (int)current->primptr - (int)current->primtab;

		if (PRIMTAB_SIZE - drawlimit < 60000)
			break;
		objects++;
	}

	// advance primitive buffer
	current->primptr = plotContext.primptr;

	return 0;
}


// [D] [T] [A] custom
void PlotModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n)
{
	int opz;
	int diff, minZ, maxZ;
	int Z;
	PL_POLYFT4* polys;
	int i;
	int r;
	u_char temp;
	u_char ptype;
	POLY_FT4* prims;
	SVECTOR* srcVerts;
	int combo;

#if 0//def PSX
	MVERTEX5x5& subdiVerts = *(MVERTEX5x5*)(u_char*)getScratchAddr(0);
#else
	MVERTEX5x5 subdiVerts;
#endif

	srcVerts = (SVECTOR*)model->vertices;
	polys = (PL_POLYFT4*)model->poly_block;

	combo = combointensity;

	// transparent object flag
	if (pc->flags & PLOT_TRANSPARENT)
		combo |= 0x2000000;

	i = model->num_polys;
	while (i > 0)
	{
		// iterate through polygons
		// with skipping
		ptype = polys->id & 0x1f;

		if ((ptype & 0x1) == 0 && ptype != 8) // is FT3 triangle?
		{
			temp = polys->uv2.v;
			polys->uv3.u = polys->uv2.u;
			polys->uv3.v = temp;

			polys->v3 = polys->v2;

			polys->id |= 1;
			ptype |= 1;
		}

		if (ptype != 11 && ptype != 21 && ptype != 23)
		{
			polys = (PL_POLYFT4*)((char*)polys + pc->polySizes[ptype]);
			i--;
			continue;
		}

		// perform transform
		gte_ldv3(&srcVerts[polys->v0], &srcVerts[polys->v1], &srcVerts[polys->v3]);
		gte_rtpt();

		// get culling value
		gte_nclip();
		gte_stopz(&opz);

		r = rot;

		if (pc->flags & (PLOT_INV_CULL | PLOT_NO_CULL))
		{
			if (opz < 0)
				r = rot + 32 & 63;

			if (pc->flags & PLOT_NO_CULL)
				opz = 1;		// no culling
			else // PLOT_FRONT_CULL
				opz = -opz;		// front face
		}

		if (ptype == 21 || (pc->flags & PLOT_NO_SHADE))
		{
			pc->colour = combo & 0x2ffffffU | 0x2c000000;
		}
		else
		{
			temp = polys->th;

			if ((polys->th & 0x80) == 0) // cache normal index if it were not
				temp = polys->th = normalIndex(srcVerts, *(u_int*)&polys->v0);

			pc->colour = pc->f4colourTable[(r >> 3) * 4 - temp & 31];
		}

		if (opz > 0)
		{
			gte_stsz3(&pc->scribble[0], &pc->scribble[1], &pc->scribble[2]);

			pc->tpage = (*pc->ptexture_pages)[polys->texture_set] << 0x10;

			if ((pc->flags & PLOT_CUSTOM_PALETTE) == 0) // [A] custom palette flag - for pedestrian heads
				pc->clut = (*pc->ptexture_cluts)[polys->texture_set][polys->texture_id] << 0x10;

			minZ = pc->scribble[2];
			if (pc->scribble[1] < minZ)
				minZ = pc->scribble[1];

			if (pc->scribble[0] < minZ)
				minZ = pc->scribble[0];

			maxZ = pc->scribble[2];
			if (maxZ < pc->scribble[1])
				maxZ = pc->scribble[1];

			diff = maxZ - minZ;
			if (maxZ < pc->scribble[0])
				diff = pc->scribble[0] - minZ;

			ushort uv0, uv1, uv2, uv3;

			// [A] special case
			if (ptype == 23)
			{
				POLYGT4* pgt4 = (POLYGT4*)polys;
				uv0 = *(ushort*)&pgt4->uv0;
				uv1 = *(ushort*)&pgt4->uv1;
				uv2 = *(ushort*)&pgt4->uv2;
				uv3 = *(ushort*)&pgt4->uv3;
			}
			else
			{
				uv0 = *(ushort*)&polys->uv0;
				uv1 = *(ushort*)&polys->uv1;
				uv2 = *(ushort*)&polys->uv2;
				uv3 = *(ushort*)&polys->uv3;
			}

			if (n == 0 || diff << 2 <= minZ - 350)
			{
				prims = (POLY_FT4*)pc->primptr;

				setPolyFT4(prims);
				*(u_int*)&prims->r0 = pc->colour;

				// retrieve first three verts
				gte_stsxy3(&prims->x0, &prims->x1, &prims->x2);

				// translate 4th vert and get OT Z value
				gte_ldv0(&srcVerts[polys->v2]);
				gte_rtps();
				gte_avsz4();

				gte_stotz(&Z);

				gte_stsxy(&prims->x3);

				prims->tpage = pc->tpage >> 0x10;
				prims->clut = pc->clut >> 0x10;

				*(ushort*)&prims->u0 = uv0;
				*(ushort*)&prims->u1 = uv1;
				*(ushort*)&prims->u2 = uv3;
				*(ushort*)&prims->u3 = uv2;

				addPrim(pc->ot + (Z >> 1), prims);

				pc->primptr += sizeof(POLY_FT4);
			}
			else
			{
				r = n;
				if (n == 1)
				{
					if (minZ - 150 < (diff << 1))
						r = 4;
					else
						r = 2;
				}

				copyVector(&subdiVerts.verts[0][0], &srcVerts[polys->v0]);
				subdiVerts.verts[0][0].uv.val = uv0;

				copyVector(&subdiVerts.verts[0][1], &srcVerts[polys->v1]);
				subdiVerts.verts[0][1].uv.val = uv1;

				copyVector(&subdiVerts.verts[0][2], &srcVerts[polys->v3]);
				subdiVerts.verts[0][2].uv.val = uv3;

				copyVector(&subdiVerts.verts[0][3], &srcVerts[polys->v2]);
				subdiVerts.verts[0][3].uv.val = uv2;

				makeMesh((MVERTEX(*)[5][5])subdiVerts.verts, r, r);
				drawMesh((MVERTEX(*)[5][5])subdiVerts.verts, r, r, pc);
			}
		}

		polys = (PL_POLYFT4*)((char*)polys + pc->polySizes[ptype]);
		i--;
	}
}


// [D] [T]
void RenderModel(MODEL* model, MATRIX* matrix, VECTOR* pos, int zBias, int flags, int subdiv, int nrot)
{
	int i;

	if (matrix != NULL)
	{
		MATRIX comb;
		gte_SetTransVector(pos);

		MulMatrix0(&inv_camera_matrix, matrix, &comb);
		SetRotMatrix(&comb);
	}

	zBias /= 8;
	zBias += (model->zBias - 64);

	plotContext.ot = current->ot;

	if (zBias > 0)
		plotContext.ot += (zBias * 4);

	for (i = 0; i < 8; i++)
	{
		plotContext.f4colourTable[i * 4 + 0] = planeColours[i] | 0x2C000000;
		plotContext.f4colourTable[i * 4 + 1] = planeColours[0] | 0x2C000000;
		plotContext.f4colourTable[i * 4 + 2] = planeColours[5] | 0x2C000000;
		plotContext.f4colourTable[i * 4 + 3] = planeColours[0] | 0x2C000000; // default: 0x2C00F0F0
	}

	plotContext.ptexture_pages = &texture_pages;
	plotContext.ptexture_cluts = &texture_cluts;
	plotContext.polySizes = PolySizes;
	plotContext.flags = flags;
	plotContext.current = current;
	
	plotContext.primptr = plotContext.current->primptr;

	if ((current->primtab - (current->primptr - PRIMTAB_SIZE)) > 56000)
	{
		PlotModelSubdivNxN(model, nrot, &plotContext, subdiv);
	}

	// advance primitive buffer
	current->primptr = plotContext.primptr;
}

struct DrawMapData
{
	int rightPlane;
	int leftPlane;
	int backPlane;

	int farClipLimit;

	int current_object_computed_value;
	int cellLevel;

	int cellxpos, cellzpos;

	short backAng;
	short leftAng;
	short rightAng;

	short rightcos;
	short rightsin;
	short leftcos;
	short leftsin;
	short backcos;
	short backsin;

	u_short tiles_found;
	u_short other_models_found;
	u_short sprites_found;
	u_short anim_objs_found;
};

// [D] [T]
void DrawMapPSX(int* comp_val)
{
	int dir;
	PACKED_CELL_OBJECT* ppco;
	int distScale;
	int cellx;
	int cellz;
	CELL_OBJECT* cop;
	MODEL* model;
	int hloop;
	int vloop;

#if 0 //def PSX
	CELL_ITERATOR& ci = *(CELL_ITERATOR*)(u_char*)getScratchAddr(0);
	MATRIX& mRotStore = *(MATRIX*)((u_char*)getScratchAddr(0) + sizeof(CELL_ITERATOR));
	DrawMapData& drawData = *(DrawMapData*)((u_char*)getScratchAddr(0) + sizeof(CELL_ITERATOR) + sizeof(MATRIX));
#else
	CELL_ITERATOR ci;
	MATRIX mRotStore;
	DrawMapData drawData;
#endif

	char* PVS_ptr;
	int i;

	static int treecount = 0;
	static int alleycount = 0;

	SetupDrawMapPSX();

	// clean cell cache
	ClearCopUsage();

	drawData.cellLevel = events.camera ? events.draw : -1;

	drawData.backPlane = 6144;
	drawData.rightPlane = -6144;
	drawData.leftPlane = 6144;

	// setup planes
	drawData.rightAng = camera_angle.vy - FrAng;
	drawData.leftAng = camera_angle.vy + FrAng;
	drawData.backAng = camera_angle.vy + 1024;

	drawData.rightcos = RCOS(drawData.rightAng);
	drawData.rightsin = RSIN(drawData.rightAng);

	drawData.leftcos = RCOS(drawData.leftAng);
	drawData.leftsin = RSIN(drawData.leftAng);

	drawData.backcos = RCOS(drawData.backAng);
	drawData.backsin = RSIN(drawData.backAng);

#ifdef PSX
	if (NumPlayers == 2)
		drawData.farClipLimit = farClip2Player;
	else
		drawData.farClipLimit = 60000;
#else
	drawData.farClipLimit = 125000;
#endif

	drawData.tiles_found = 0;
	drawData.sprites_found = 0;
	drawData.current_object_computed_value = *comp_val;
	drawData.other_models_found = 0;
	drawData.anim_objs_found = 0;
	
	drawData.cellzpos = current_cell_z;
	drawData.cellxpos = current_cell_x;

	PVS_ptr = CurrentPVS + 220;

	vloop = 0;
	hloop = 0;
	dir = 0;

	goFaster ^= fasterToggle;

	if (NumPlayers == 2)
		distScale = goFaster & 31 | 1;
	else
		distScale = goFaster & 31;

	i = (gDrawDistance >> distScale);		// [A]

	// walk through all cells
	do
	{
		if (ABS(hloop) + ABS(vloop) < 21)
		{
			// clamped vis values
			int vis_h = MIN(MAX(hloop, -9), 10);
			int vis_v = MIN(MAX(vloop, -9), 10);

			cellx = drawData.cellxpos + hloop;
			cellz = drawData.cellzpos + vloop;

			if (drawData.rightPlane < 0 &&
				drawData.leftPlane > 0 &&
				drawData.backPlane < drawData.farClipLimit &&  // check planes
				cellx > -1 && cellx < cells_across &&							// check cell ranges
				cellz > -1 && cellz < cells_down &&
				PVS_ptr[vis_v * pvs_square + vis_h]) // check PVS table
			{
				ppco = GetFirstPackedCop(cellx, cellz, &ci, 1, drawData.cellLevel);

				// walk each cell object in cell
				while (ppco != NULL)
				{
					model = modelpointers[(ppco->value >> 6) | ((ppco->pos).vy & 1) << 10];

					if (FrustrumCheck16(ppco, model->bounding_sphere) != -1)
					{
						// sprity type
						if (model->shape_flags & SHAPE_FLAG_SPRITE)
						{
							if (drawData.sprites_found < MAX_DRAWN_SPRITES)
								spriteList[drawData.sprites_found++] = ppco;

							if ((model->flags2 & MODEL_FLAG_ANIMOBJ) && drawData.anim_objs_found < MAX_DRAWN_ANIMATING)
							{
								cop = UnpackCellObject(ppco, &ci.nearCell);
								anim_obj_buffer[drawData.anim_objs_found++] = cop;
							}

							if (model->flags2 & MODEL_FLAG_TREE)
							{
								if (treecount == 0)
								{
									QuickUnpackCellObject(ppco, &ci.nearCell, &ground_debris[groundDebrisIndex]);

									if (groundDebrisIndex < MAX_GROUND_DEBRIS - 1)
										groundDebrisIndex++;
									else
										groundDebrisIndex = 0;
								}

								if (treecount < 15)
									treecount++;
								else
									treecount = 0;
							}
						}
						else
						{
							int modelNumber;
							modelNumber = ppco->value & 0x3f;

							if (modelNumber > 0)
							{
								MATRIX2* cmat;
								cmat = &CompoundMatrix[modelNumber];

								if (cmat->computed != drawData.current_object_computed_value)
								{
									cmat->computed = drawData.current_object_computed_value;

									gte_ReadRotMatrix(&mRotStore);
									gte_sttr(mRotStore.t);

									MulMatrix0(&inv_camera_matrix, (MATRIX*)&matrixtable[modelNumber], (MATRIX*)cmat);

									gte_SetRotMatrix(&mRotStore);
								}
							}

							if ((model->shape_flags & (SHAPE_FLAG_WATER | SHAPE_FLAG_TILE)) || 
								(model->flags2 & (MODEL_FLAG_PATH | MODEL_FLAG_GRASS)))
							{
								if (model->flags2 & MODEL_FLAG_ALLEY)
								{
									alleycount++;

									if (alleycount == 13)
									{
										QuickUnpackCellObject(ppco, &ci.nearCell, &ground_debris[groundDebrisIndex]);

										if (groundDebrisIndex < MAX_GROUND_DEBRIS - 1)
											groundDebrisIndex++;
										else
											groundDebrisIndex = 0;

										alleycount = 0;
									}
								}

								if (drawData.tiles_found < MAX_DRAWN_TILES)
									model_tile_ptrs[drawData.tiles_found++] = ppco;
							}
							else
							{
								cop = UnpackCellObject(ppco, &ci.nearCell);

								if (drawData.other_models_found < MAX_DRAWN_BUILDINGS)
									model_object_ptrs[drawData.other_models_found++] = cop;

								if ((model->flags2 & MODEL_FLAG_ANIMOBJ) && drawData.anim_objs_found < MAX_DRAWN_ANIMATING)
									anim_obj_buffer[drawData.anim_objs_found++] = cop;
							}
						}
					}

					ppco = GetNextPackedCop(&ci);
				}
			}
		}

		if (dir == 0)
		{
			drawData.leftPlane += drawData.leftcos;
			drawData.backPlane += drawData.backcos;
			drawData.rightPlane += drawData.rightcos;
			dir = (++hloop + vloop == 1) ? 1 : dir;
		}
		else if (dir == 1)
		{
			drawData.leftPlane += drawData.leftsin;
			drawData.backPlane += drawData.backsin;
			drawData.rightPlane += drawData.rightsin;
			//PVS_ptr += pvs_square;
			dir = (hloop == ++vloop) ? 2 : dir;
		}
		else if (dir == 2)
		{
			drawData.leftPlane -= drawData.leftcos;
			drawData.backPlane -= drawData.backcos;
			drawData.rightPlane -= drawData.rightcos;
			dir = (--hloop + vloop == 0) ? 3 : dir;
		}
		else
		{
			drawData.leftPlane -= drawData.leftsin;
			drawData.backPlane -= drawData.backsin;
			drawData.rightPlane -= drawData.rightsin;
			//PVS_ptr -= pvs_square;
			dir = (hloop == --vloop) ? 0 : dir;
		}
	}while (i-- > 0);

#if 0
	char tempBuf[512];

	SetTextColour(255, 255, 0);

	sprintf(tempBuf, "Buildings: %d", other_models_found);
	PrintString(tempBuf, 10, 60);

	sprintf(tempBuf, "Sprites: %d", sprites_found);
	PrintString(tempBuf, 10, 75);

	sprintf(tempBuf, "Tiles: %d", tiles_found);
	PrintString(tempBuf, 10, 90);

	sprintf(tempBuf, "Anims: %d", anim_objs_found);
	PrintString(tempBuf, 10, 105);

	sprintf(tempBuf, "TOTAL: %d", other_models_found + sprites_found + tiles_found + anim_objs_found);
	PrintString(tempBuf, 10, 120);
#endif

	SetupPlaneColours(combointensity);

	if (drawData.sprites_found)
		DrawSprites((PACKED_CELL_OBJECT**)spriteList, drawData.sprites_found);

	if (drawData.tiles_found)
		DrawTILES((PACKED_CELL_OBJECT**)model_tile_ptrs, drawData.tiles_found);

	if (drawData.other_models_found)
		DrawAllBuildings((CELL_OBJECT**)model_object_ptrs, drawData.other_models_found);

	if (drawData.anim_objs_found)
		DrawAllAnimatingObjects((CELL_OBJECT**)anim_obj_buffer, drawData.anim_objs_found);

	setupYet = 0;
}
