

// the "depend" batch doesn't like non-existant headers
#include "global.h"
#include <islpsi.h>
#include <psitypes.h>

#include "main.h"

#include "bff_load.h"
#include "map_draw.h"
#include "maths.h"
#include "debris.h"

// Keep in an included .h file, jim
//#ifdef _WINDOWS
//#include "pcHardware3D.h"
//#endif




//#define MAPDRAW_SHOW_VIS_SPHERES

//#ifndef _WINDOWS
//#define DCACHE  0x1f800000
//#else
char pcDCacheData[1024*4];
#define DCACHE  pcDCacheData
//#endif


extern GsRVIEW2	newcamera;

#if MASTER==NO
int lscape_just_one_model = -1;
int lscape_jom_ticker = 0;
#endif

#ifdef _WINDOWS
extern int scrOffsetX, scrOffsetY;
extern int runningHard;
#endif


extern void transformVertexList_NoDepth(VERT *vertPtr, long numVerts, long *transformedVerts);
extern void transformVertexListLong(long *p);
extern void transformVertexListA_ZeroCam(long *p);

// Scale-Depth-Down deals with shifting large depth-coordinates in a scaled-up map,
// shifting down to a smaller range of OT positions.

// Scale-Adjust allows a scaled-up map to be integrated into an otherwise NON-scaled-up
// coordinate system (probably a temporary measure), by increasing the object-camera
// distance for the duration of the map-plotting calls.

#define MAP_SCALE_DEPTH_DOWN 3

// Remove the safety check on the landscape model depth check
#define NO_LANDSCAPE_LIMITS


// A couple of the optimisations in here are enablable by "#define". Once this is sourcesafed, it
//may be worth tidying them out to make the source neater.

/*

  Routines to RENDER frogger2 maps.

  Essentially, an optimised model printer that only deals with
  
	Textured Tris & Quads
	Single-Sided only.
	Non-Transparent.

*/




// PSI.C variables rqd for the draw prims.
// They're defined in the latest islpsi header, so that's ok.
//extern long *transformedVertices;
//extern VERT *transformedNormals;
//extern long *transformedDepths;

// I dunno what part of the ISL lib these are in. Part of the psi lib, I think,
// but it's not in the headers yet
/*
// custom.h
typedef struct{
	SHORT x,y,z,w;
}SHORTQUAT;
*/
//void ShortquaternionGetMatrix(SHORTQUAT *squat, MATRIX *dmatrix);
/*
typedef struct
{
	short x,y;
}SHORTXY;
*/



#ifndef ADD2POINTER
	#define ADD2POINTER(a,b) ( (void *) ( (int)(a) + (b))  )
#endif



// "addPrimLen" is faster than doing "setPolyGT3/4","addPrim",
// becuase a fair few of the numbers are already known / filled in

// In fact, IF we can guarantee that we're linking into an OT address (as we can, here),
// and NOT the tail end of another primitive, then the "length" part of the P_TAG will be zero,
// so we can just read/write the OT address as a "u_long" instead of using "getaddr/setaddr"

// The "<<24" is optimised to a constant by the compiler if "len" is constant.

// this version also assumes we've got a spare register for use as "tempvar" to get rid of a NOP

#define addPrimLen(ot,p,len,tempvar) \
{\
	tempvar = *(u_long *)(ot); \
    (*(u_long *)(ot) = (u_long)(p)); \
	tempvar |= (len<<24); \
	*(unsigned long *)(p) = tempvar; \
}


// (Previous, less-optimised, versions
/*
#define addPrimLen(ot,p,len)\
{\
	(*(unsigned long *)p = *(u_long *)(ot) | (len<<24));\
    (*(u_long *)(ot) = (u_long)p);\
}
*/
//#define addPrimLen(ot,p,len)	(*(unsigned long *)p = getaddr(ot) | (len<<24)), setaddr(ot,p)
//#define addPrimLen(ot,p,len)	{setlen(p,len); addPrim(ot,p);}



// ==== JS : These macros are now in PSX_PC.h which is included via custom.h.  Sorry, but I've
// =====     had to do this for the PC conversion.

// Extra bit of direct gte-access that doesn't then store the register into a stacked variable
// (ie, this gives a nice little speed improvement)
/*#define gte_stotz_cpu(r)\
asm(\
  "mfc2 %0, $7;"\
  :\
  : "r" (r)\
)*/
// Fred, checking whether the same speed improvement works with the backface cull.
// (Answer, yes it does. Hope I've got this bit of asm right!)
/*#define gte_stopz_cpu(r)\
asm(\
	"mfc2 %0, $24;"\
	:\
	: "r"(r)\
)*/


// Extra Map Depth is now part of the mesh header. The idea is that it's set to about 10, to ensure that
// the frog appears in front of the landscape. "Underwater" objects should have their extra depth set higher,
// so they don't sort badly with the water plane itself
//#define XTRA_MAP_DEPTH 10


// The MIN map depth is basically in here because when doing the perspective transform
// close to / overlapping the camera plane, the PSX switches to an isometric transform rather than a perspective one,
// which looks horrible!
#define MIN_MAP_DEPTH 10



// Must be under 1024 to work with the library's OT
// Use lower values still to define the far cut-off distance.

// Actually, 1000 * 8, remember... We're clipping objects to 900.
// Add a polygon dimension of 100, and an ickle bit of safety margin
// ta-da. let's say 1050
#define MAX_MAPOBJECT_DIMENSION 1050
// This ends up being somewhere around the 700->800 mark
#define MAX_MAP_DEPTH_UPPER_LIMIT (( 32768 - MAX_MAPOBJECT_DIMENSION * 8) >> 5)

int max_map_ot_depth = MAX_MAP_DEPTH_UPPER_LIMIT;

#define MAX_MAP_DEPTH max_map_ot_depth

#define MAX_MAP_DEPTH_CLIPCHECK 1023





void transformVertexListA_ZeroCam(long *p)
{
int i;
VERT tmp, *verts;
long *tfv, *tfd;

	verts	= (VERT *) p[0];		// Pointer to the source vertices
	i		= p[1];					// The number of verts to be transformed
	tfv		= (long*) p[2];			// Pointer to the transformedVerts array
	tfd		= (long*) p[3];			// Pointer to the transformedDepths array

	while(i--)
	{
		tmp.vx		= verts->vx + p[4];		// Adjust for the mesh instance-camera position
		tmp.vy		= verts->vy + p[5];		//
		tmp.vz		= verts->vz + p[6];		//
		verts++;

		gte_ldv0((&tmp));					// Load the vert into the gte

		gte_rtps();							// Do transform and projection
		gte_stsxy(tfv);						// Store the screen xy in tfv list
		tfv++;								//
		gte_stsz(tfd);						// Store the Z depth in the tfd list
		tfd++;								//
	} // end-while

}









// ===================================================
// New, mega-optimised landscape plotter
// This is the routine to draw a single mesh shape.
// Assumes that the matrix is already set up,
// and that any visibility tests have already been done

// We're working with a normal scaledown of 3
// However we might be doing 1 or 2 scaledowns within the matrix transform for large models
// in which case, reduce the depthscaler here to compensate
void MapDraw_DrawFMA_Mesh3(WORLD_MESH_INSTANCE *instance, int depthscaler)
{
	u_long t1;	// I've re-jigged the code, so "depth" is re-usable, & shares register with t1
	u_long t2;

	PACKET * packet;
	char *opcd;

	long *tfv;
	long *tfd;

	FMA_MESH_HEADER *mesh = instance->mesh;


	int h,w;
	TextureType *tex;

// These macris are for an optimisation that requires all our vertex numbers to be multiplied by four,
// which means they're byte offsets into the tfv/tfd table rather than int-array indices
// Essentially
// tfv[n] and tfd[n], where tfv/tfd are arrays of ints, have now been replaced by these macros,
// where "n" is pre-shifted at PC map-compile time. Thus saving a shift instruction per vertex per polygon.
#define GETX(n)( ((SHORTXY *)( (int)(tfv) +(n) ))->x )
#define GETY(n)( ((SHORTXY *)( (int)(tfv) +(n) ))->y )
#define GETV(n)(  *(u_long *)( (int)(tfv) +(n) ) )
#define GETD(n)(  *(u_long *)( (int)(tfd) +(n) ) )


// "depth" is forced to be a CPU register so we can use the fast gte macro
// (Also note, it's shared with a temporary variable "t1")
	long clipflag;
	long depth;

  //	unsigned long *ot = currentDisplayPage->ot + instance->sort_depth;

	int i;


	int min_depth = MIN_MAP_DEPTH - instance->sort_depth;
	int max_depth = MAX_MAP_DEPTH - instance->sort_depth;
	if(min_depth < 1)
	{
		min_depth = 1;
	}

	if(max_depth > 1024-instance->sort_depth)
		max_depth = 1024-instance->sort_depth;


	tfv = transformedVertices;
	tfd = transformedDepths;

						  
	{
		long p[9];
		p[0] = (int)mesh->verts;	// 0
		p[1] = mesh->n_verts;		// 4
		p[2] = (int)tfv;			// 8
		p[3] = (int)tfd;			// 12
		p[4] = instance->posx - mycamera.vpx;	// 16
		p[5] = instance->posy - mycamera.vpy;	// 20
		p[6] = instance->posz - mycamera.vpz;	// 24
		transformVertexListA_ZeroCam(&p[0]);
	}						




//#ifdef MAP_SCALE_DEPTH_DOWN
  	if(depthscaler)
	{
  		for(i = 0; i < mesh->n_verts; i++)
  		   	tfd[i] = tfd[i] >> depthscaler;
	}
//#endif

	//packet = (PACKET *)currentDisplayPage->primPtr;


// The MapDraw stores a count+list of quads and one of tris,
// rather than checking a "type" element for every polygon.
// (coz it only has those two types. No transparent. No double sided. No nothing)




#define si ((POLY_GT4*)packet)
#define op ((FMA_GT4 *)opcd)
	op = mesh->gt4s;
	for(i = mesh->n_gt4s; i != 0; i--,op++)
	{
		
 	  //	if((i&7)!=0)continue;

// Get average Z of the polygon & use as a depth pointer
		gte_ldsz4(GETD(op->vert0),GETD(op->vert1),GETD(op->vert2),GETD(op->vert3));
   		gte_avsz4();
		gte_stotz(&depth);
		if(depth > min_depth && depth < max_depth)
		{
// Load 1st three vertices for a backface cull check
// (Yes, this check is better when placed after the offscreen one - Checked on PA)
			gte_ldsxy3(GETV(op->vert0), GETV(op->vert1), GETV(op->vert2));
			gte_nclip();
			gte_stopz(&clipflag);

			if (clipflag<0)
			{
			 	BEGINPRIM(si,POLY_GT4);
				t1 = *(u_long *) (&op->u0);
				t2 = *(u_long *) (&op->u1);
				*(u_long *)  (&si->u0) = t1;
				*(u_long *)  (&si->u1) = t2;
				gte_stsxy3_gt4(si);	// The first 3 x's & y's are already in the gte, so we may as well use 'em
				t1 = *(u_long *) (&op->u2);
				t2 = *(u_long *) (&op->u3);
				*(u_long *)  (&si->u2) = t1;
				*(u_long *)  (&si->u3) = t2;
				*(u_long *)  (&si->x3) = *(u_long *) (&GETV(op->vert3));
				t1 = *(u_long *) (&op->r0);
				t2 = *(u_long *) (&op->r1);
				*(u_long *)  (&si->r0) = t1;
				*(u_long *)  (&si->r1) = t2;
				t1 = *(u_long *) (&op->r2);
				t2 = *(u_long *) (&op->r3);
				*(u_long *)  (&si->r2) = t1;
				*(u_long *)  (&si->r3) = t2;

				if(si->x0<0 && si->x1<0 && si->x2<0 && si->x3<0)continue;
				if(si->y0<0 && si->y1<0 && si->y2<0 && si->y3<0)continue;
				if(si->x0>640 && si->x1>640 && si->x2>640 && si->x3>640)continue;
				if(si->y0>480 && si->y1>480 && si->y2>480 && si->y3>480)continue;

			  /*	if(si->code&2)//strip the |2 and put the semi type in tag
				{
					si->code=0x3c;
					si->tag=si->tpage>>5;
					si->tag&=3;
					si->tag=SEMITRANS_ADD;
				}
				else
				{
					si->code=0x3c;
					si->tag=0;

				}*/
				ENDPRIMA(si,GETD(op->vert0),GETD(op->vert2),GETD(op->vert1),GETD(op->vert3), POLY_GT4);
			}
		}
	}
#undef si
#undef op

// That's the quads done, now let's do the triangles...

#define si ((POLY_GT3*)packet)
#define op ((FMA_GT3 *)opcd)
	   
	op = mesh->gt3s;
	for(i = mesh->n_gt3s; i != 0; i--,op++)
	{
// Get average Z of the polygon & use as a depth pointer
		gte_ldsz3(GETD(op->vert0),GETD(op->vert1),GETD(op->vert2));
   		gte_avsz3();
		gte_stotz(&depth);

		if(depth > min_depth && depth < max_depth)
		{
// Load 1st three vertices for a backface cull
			gte_ldsxy3(GETV(op->vert0), GETV(op->vert1), GETV(op->vert2));
			gte_nclip();
			gte_stopz(&clipflag);
			if(clipflag < 0)
			{
				BEGINPRIM(si,POLY_GT3);
				t1 = *(u_long *) (&op->u0);
				t2 = *(u_long *) (&op->u1);
				*(u_long *)  (&si->u0) = t1;
				*(u_long *)  (&si->u1) = t2;
				gte_stsxy3_gt3(si);
				t1 = *(u_long *) (&op->u2);
				t2 = *(u_long *) (&op->r0);
				*(u_long *)  (&si->u2) = t1;
				*(u_long *)  (&si->r0) = t2;
				t1 = *(u_long *) (&op->r1);
				t2 = *(u_long *) (&op->r2);
				*(u_long *)  (&si->r1) = t1;
				*(u_long *)  (&si->r2) = t2;

		/*		if(si->code&2)//strip the |2 and put the semi type in tag
				{
					si->code=0x3c;
					si->tag=si->tpage>>5;
					si->tag&=3;
				} */
				ENDPRIMA(si,GETD(op->vert0),GETD(op->vert2),GETD(op->vert1),0, POLY_GT4);
			  //	ENDPRIM(si,depth&1023, POLY_GT3);
			}
		}
	}
	   
#undef si
#undef op
//#undef xy

// New & improved landscape models can now have built-in skies...

#define si ((POLY_G4*)packet)
#define op ((FMA_G4 *)opcd)
		   
	op = mesh->g4s;

	for(i = mesh->n_g4s; i != 0; i--,op++)
	{
		gte_ldsz4(GETD(op->vert0),GETD(op->vert1),GETD(op->vert2),GETD(op->vert3));
   		gte_avsz4();
		gte_stotz(&depth);

		if(depth > min_depth && depth < max_depth)
		{

	// For sky models, backface cull is still necessary.
	// (coz of polys behind you)
	// depth is always right at the back

			gte_ldsxy3(GETV(op->vert0), GETV(op->vert1), GETV(op->vert2));

			gte_nclip();
			gte_stopz(&clipflag);
			if (clipflag<0)
			{
		// Put the polygon into the OT (Note that we re-use "depth" as one of the "t" variables)
//				addPrimLen(ot+ (depth), (si),8,t2);
  				BEGINPRIM(si,POLY_G4);
				// Copy the polygon data over into the OT

				t1 = *(u_long *) (&op->r0);
				t2 = *(u_long *) (&op->r1);
				*(u_long *)  (&si->r0) = t1;
				*(u_long *)  (&si->r1) = t2;
				gte_stsxy3_g4(si);	// The first 3 x's & y's are already in the gte, so we may as well use 'em
				t1 = *(u_long *) (&op->r2);
				t2 = *(u_long *) (&op->r3);
				*(u_long *)  (&si->r2) = t1;
				*(u_long *)  (&si->r3) = t2;
				*(u_long *)  (&si->x3) = *(u_long *) (&GETV(op->vert3));


				if(si->tag==1)si->tag=SEMITRANS_ADD;
				si->code  = 0x38;


   				ENDPRIMA(si,GETD(op->vert0),GETD(op->vert2),GETD(op->vert1),GETD(op->vert3), POLY_G4);
//				ENDPRIM(si,depth&1023, POLY_G4);
//				packet = ADD2POINTER(packet,sizeof(POLY_G4));
			}
		}
	}
	  	  	 
#undef si
#undef op

// That's the quads done, now let's do the triangles...

#define si ((POLY_G3*)packet)
#define op ((FMA_G3 *)opcd)
			   
	op = mesh->g3s;
	for(i = mesh->n_g3s; i != 0; i--,op++)
	{
		gte_ldsz3(GETD(op->vert0),GETD(op->vert1),GETD(op->vert2));
   		gte_avsz3();
		gte_stotz(&depth);

		if(depth > min_depth && depth < max_depth)
		{

			gte_ldsxy3(GETV(op->vert0), GETV(op->vert1), GETV(op->vert2));
			gte_nclip();
			gte_stopz(&clipflag);

			if (clipflag<0)
			{
		// Put the polygon into the OT (Note that we re-use "depth" as one of the "t" variables)
//				addPrimLen(ot+(depth), (si),6,t2);
  				BEGINPRIM(si, POLY_G3);
				// Copy the polygon data over into the OT
				t2 = *(u_long *) (&op->r0);
				*(u_long *)  (&si->r0) = t2;
				gte_stsxy3_g3(si);
				t1 = *(u_long *) (&op->r1);
				t2 = *(u_long *) (&op->r2);
				*(u_long *)  (&si->r1) = t1;
				*(u_long *)  (&si->r2) = t2;

				//si->code  = 0x30;
				
				ENDPRIMA(si,GETD(op->vert0),GETD(op->vert2),GETD(op->vert1),0, POLY_G3);
//				ENDPRIM(si,depth&1023,POLY_G3);
			  //	packet = ADD2POINTER(packet,sizeof(POLY_G3));
			}
		}
	}
		  	  	 
#undef si
#undef op



//	currentDisplayPage->primPtr = (char *)packet;

}






// Set up the PSX's transformation matrix for a landscape segment,
// or possibly for a NON-ROTATING platform.
void MapDraw_SetMatrix(WORLD_MESH_INSTANCE *instance, int shifts)
{
	MATRIX tx;

	tx.t[0] = 0;
	tx.t[1] = 0;
	tx.t[2] = 0;
	if(shifts != 3)
	{
		shifts = 3 - shifts;
		tx.m[0][0] = GsWSMATRIX.m[0][0] >> shifts;
		tx.m[0][1] = GsWSMATRIX.m[0][1] >> shifts;
		tx.m[0][2] = GsWSMATRIX.m[0][2] >> shifts;
		tx.m[1][0] = GsWSMATRIX.m[1][0] >> shifts;
		tx.m[1][1] = GsWSMATRIX.m[1][1] >> shifts;
		tx.m[1][2] = GsWSMATRIX.m[1][2] >> shifts;
		tx.m[2][0] = GsWSMATRIX.m[2][0] >> shifts;
		tx.m[2][1] = GsWSMATRIX.m[2][1] >> shifts;
		tx.m[2][2] = GsWSMATRIX.m[2][2] >> shifts;
		gte_SetRotMatrix(&tx);
	}
	else
	{
		gte_SetRotMatrix(&GsWSMATRIX);
	}

	gte_SetTransMatrix(&tx);

}

// =========================== Untextured Polygon Sky Mesh Version ====================================



void MapDraw_SetSkyMatrix(FMA_MESH_HEADER *mesh)
{
	MATRIX tx;

	gte_SetRotMatrix(&GsWSMATRIX);
	gte_SetTransMatrix(&GsWSMATRIX);

// Unnecessary maths for landscape segments, where pos is always zero.
//	gte_ldlvl( &mesh->posx);
//	gte_rtirtr();
//	gte_stlvl(&tx.t);
	tx.t[0] = 0;
	tx.t[1] = 0;
	tx.t[2] = 0;

	gte_SetRotMatrix(&GsWSMATRIX);
	gte_SetTransMatrix(&tx);

}

void MapDraw_DrawSkyMesh(FMA_SKYMESH_HEADER *mesh)
{  
// Note - t1 & t2 are shared with other variables
	u_long t1;
	u_long t2;
	PACKET * packet;
	char *opcd;
	long *tfv = transformedVertices;
	long *tfd = transformedDepths;

#define GETV(n)(  *(u_long *)( (int)(tfv) +(n) ) )
#define GETD(n)(  *(u_long *)( (int)(tfd) +(n) ) )
	unsigned long *ot = currentDisplayPage->ot + 1023;	// right at the back of the OT...
	int i;
	long clipflag;


	transformVertexListA(mesh->verts, mesh->n_verts, tfv, tfd);
//	transformVertexList_NoDepth(mesh->verts, mesh->n_verts, tfv);

//	packet = (PACKET *)currentDisplayPage->primPtr;

#define si ((POLY_G4*)packet)
#define op ((FMA_G4 *)opcd)

	op = mesh->g4s;

	for(i = mesh->n_g4s; i != 0; i--,op++)
	{
		/*
		if(
		((GETV(op->vert0) & 0xff80ff00) + 0x00800100)
		&
		((GETV(op->vert1) & 0xff80ff00) + 0x00800100)
		&
		((GETV(op->vert2) & 0xff80ff00) + 0x00800100)
		&
		((GETV(op->vert3) & 0xff80ff00) + 0x00800100)
		& 0x01000200
		)
		continue;
		  */
// For sky models, backface cull is still necessary.
// (coz of polys behind you)
// depth is always right at the back

		gte_ldsxy3(GETV(op->vert0), GETV(op->vert1), GETV(op->vert2));

		gte_nclip();
		gte_stopz(&clipflag);
		if (clipflag<0)
		{
	// Put the polygon into the OT (Note that we re-use "depth" as one of the "t" variables)
			//addPrimLen(ot, (si),8,t2);
			BEGINPRIM(si,POLY_G4);
	// Copy the polygon data over into the OT

			t1 = *(u_long *) (&op->r0);
			t2 = *(u_long *) (&op->r1);
			*(u_long *)  (&si->r0) = t1;
			*(u_long *)  (&si->r1) = t2;
			gte_stsxy3_g4(si);	// The first 3 x's & y's are already in the gte, so we may as well use 'em
			t1 = *(u_long *) (&op->r2);
			t2 = *(u_long *) (&op->r3);
			*(u_long *)  (&si->r2) = t1;
			*(u_long *)  (&si->r3) = t2;
			*(u_long *)  (&si->x3) = *(u_long *) (&GETV(op->vert3));

			ENDPRIM(si,GETD(op->vert0),POLY_G4);
//			packet = ADD2POINTER(packet,sizeof(POLY_G4));
		}
	}

#undef si
#undef op

// That's the quads done, now let's do the triangles...

#define si ((POLY_G3*)packet)
#define op ((FMA_G3 *)opcd)

	op = mesh->g3s;
	for(i = mesh->n_g3s; i != 0; i--,op++)
	{
// Optimisation - X & Y at the same time
/*		if(
		((GETV(op->vert0) & 0xff80ff00) + 0x00800100)
		&
		((GETV(op->vert1) & 0xff80ff00) + 0x00800100)
		&
		((GETV(op->vert2) & 0xff80ff00) + 0x00800100)
		& 0x01000200
		)
		continue;
  */
		gte_ldsxy3(GETV(op->vert0), GETV(op->vert1), GETV(op->vert2));
		gte_nclip();
		gte_stopz(&clipflag);

  		if (clipflag<0)
		{

	// Put the polygon into the OT (Note that we re-use "depth" as one of the "t" variables)
			//addPrimLen(ot, (si),6,t2);
			BEGINPRIM(si,POLY_G3);
	// Copy the polygon data over into the OT
			t2 = *(u_long *) (&op->r0);
			*(u_long *)  (&si->r0) = t2;
			gte_stsxy3_g3(si);
			t1 = *(u_long *) (&op->r1);
			t2 = *(u_long *) (&op->r2);
			*(u_long *)  (&si->r1) = t1;
			*(u_long *)  (&si->r2) = t2;
			ENDPRIM(si,GETD(op->vert0),POLY_G3);
//			packet = ADD2POINTER(packet,sizeof(POLY_G3));
		}
	}

#undef si
#undef op
//#undef xy

	//currentDisplayPage->primPtr = (char *)packet;
	 
}


#define OFFLEFT		1
#define OFFRIGHT	2
#define OFFUP		4
#define OFFDOWN		8
#define OFFFRONT	16
#define OFFBACK		32

// Check to see if a frogger map shape is on-screen
// (Assumes the projection matrix has already been set up for this shape)
// Normal value for shifts = 3
int MapDraw_ClipCheck(WORLD_MESH_INSTANCE *instance, int shifts)
{
	ULONG check,totalchecks;
	SHORTXY			*xy;
	int v,i;
	long	p[7];
	FMA_MESH_HEADER *mesh = instance->mesh;
	int nearobject;


	static SVECTOR Box[9];	// "9", coz the transformer only deals in multiples of three, and this array is on the stack, so overrunning would be a seriously bad idea.
// Get the 8 corners of the object's bounding box, & transform them.
	for(v=0,i=0; v<8; v++)
	{
		Box[i].vx=(v&1)?mesh->minx:mesh->maxx;
		Box[i].vy=(v&2)?mesh->miny:mesh->maxy;
		Box[i].vz=(v&4)?mesh->minz:mesh->maxz;
		i++;
	}
// We're transforming 9 verts anyway, so we may as well make use of that, and
// tx the box centre
	Box[i].vx=(mesh->minx + mesh->maxx) >>1 ;
	Box[i].vy=(mesh->miny + mesh->maxy) >>1;
	Box[i].vz=(mesh->minz + mesh->maxz) >>1;

	p[0]=(long)(&Box[0]);
	p[1]=9;
	p[2]=(long)transformedVertices;
	p[3]=(long)transformedDepths;

// correct version
	p[4] = instance->posx - mapCamData.gameCam.vpx;	// 16
	p[5] = instance->posy - mapCamData.gameCam.vpy;	// 20
	p[6] = instance->posz - mapCamData.gameCam.vpz;	// 24

	transformVertexListA_ZeroCam(&p[0]);

// Check to see whether ALL of the vertices are on the outisde of ANY of the 6 planes of the view-frustrum
	(ULONG*)xy=transformedVertices;
	totalchecks=0xffff;
	nearobject = 0;
	for(v=0; v<8; v++)
	{
		check=0;

#ifndef _WINDOWS
		if(xy[v].x<0)
			check|=OFFLEFT;
		if(xy[v].x>640 )
			check|=OFFRIGHT;
		if(xy[v].y<0)
			check|=OFFUP;
		if(xy[v].y>480 )
			check|=OFFDOWN;
#else
		if(xy[v].x< -scrOffsetX)
			check|=OFFLEFT;
		if(xy[v].x> scrOffsetX)
			check|=OFFRIGHT;
		if(xy[v].y< -scrOffsetY)
			check|=OFFUP;
		if(xy[v].y> scrOffsetY)
			check|=OFFDOWN;
#endif

#ifndef _WINDOWS	// =========================================================================

#ifdef MAP_SCALE_DEPTH_DOWN
	  	if((transformedDepths[v]) <= (MIN_MAP_DEPTH<<(2+shifts)))
			check|=OFFFRONT;

		if((transformedDepths[v]) >= (MAX_MAP_DEPTH_CLIPCHECK<<(2+shifts)))
			check |= OFFBACK;
#else
	  	if((transformedDepths[v]) <= (MIN_MAP_DEPTH<<2))
			check|=OFFFRONT;

		if((transformedDepths[v]) >= (MAX_MAP_DEPTH_CLIPCHECK<<2))
			check |= OFFBACK;
#endif
		if(transformedDepths[v] == 0)
			nearobject  = 1;

#else	// =========================================================================

#ifdef MAP_SCALE_DEPTH_DOWN
	  	if((transformedDepths[v]) <= (MIN_MAP_DEPTH<<(4)))
			check|=OFFFRONT;

		if((transformedDepths[v]) >= (MAX_MAP_DEPTH_CLIPCHECK<<(4)))
			check |= OFFBACK;
#else
	  	if((transformedDepths[v]) <= (MIN_MAP_DEPTH<<4))
			check|=OFFFRONT;

		if((transformedDepths[v]) >= (MAX_MAP_DEPTH_CLIPCHECK<<4))
			check |= OFFBACK;
#endif
		if(transformedDepths[v] == 0)
			nearobject  = 1;
#endif	// =========================================================================

		totalchecks&=check;
	}

// There's an additional test, which is if the centrepoint's behind us, but there's a corner that's managed to wrap to "way in front"
	if(nearobject || transformedDepths[8] <= (MAX_MAP_DEPTH << (2+shifts)) / 4)
	{
		for(v=0; v<8; v++)
		{
			if((transformedDepths[v]) >= ((MAX_MAP_DEPTH_CLIPCHECK<<(2+shifts))) * 3 / 4)
			{
//				utilPrintf("spanning\n");
				return 0;
			}
		}
	}

	return(totalchecks==0);
}

WORLD_MESH_INSTANCE *mapdrawFindInstance(FMA_WORLD2 *world, int tag)
{
	int i;
	WORLD_MESH_INSTANCE *entry;

	entry = ADD2POINTER(world,sizeof(FMA_WORLD2));
	for(i = world->n_meshes; i != 0; i--, entry++)
	{
		if(entry->tag == tag)
		{
			return entry;
		}
	}
	return NULL;
}

void MapDraw_DrawFMA_World(FMA_WORLD2 *world)
{
	int i;
//	int removed_models;
	FMA_MESH_HEADER **mesh;
//	FMA_WORLDENTRY *entry;
	WORLD_MESH_INSTANCE *entry;

	if(world->id != BFF_FMA_WORLD2_ID)
	{
		printf("Only World2's supported now!\n");
		CRASH;
	}

// Quick bit of last-minute insurance...
#ifdef NO_LANDSCAPE_LIMITS
	if(max_map_ot_depth > MAX_MAP_DEPTH_UPPER_LIMIT)
	{
		static int warned = 0;
		if(!warned)
		{
			utilPrintf(" *** WARNING ***\n");
			utilPrintf(" Map-Printing depth has been set to a possibly overflowing value\n");
			utilPrintf(" Current value %d. Max safe %d\n",max_map_ot_depth,MAX_MAP_DEPTH_UPPER_LIMIT);

			warned = 1;
		}
	}
	if(max_map_ot_depth > 1023)
	{
		max_map_ot_depth = 1023;
	}

#else
	if(max_map_ot_depth > MAX_MAP_DEPTH_UPPER_LIMIT)
		max_map_ot_depth = MAX_MAP_DEPTH_UPPER_LIMIT;
#endif

	entry = ADD2POINTER(world,sizeof(FMA_WORLD));


#if MASTER==NO
	lscape_jom_ticker++;
	if((padData.debounce[4] & PAD_RIGHT) || ((padData.digital[4] & PAD_RIGHT) && lscape_jom_ticker > 5))
	{
		lscape_jom_ticker = 0;
		lscape_just_one_model++;
		if(lscape_just_one_model >= world->n_meshes)
			lscape_just_one_model = 0;
	}
	else if((padData.debounce[4] & PAD_LEFT) || ((padData.digital[4] & PAD_LEFT) && lscape_jom_ticker > 5))
	{
		lscape_jom_ticker = 0;
		lscape_just_one_model--;
		if(lscape_just_one_model <0)
			lscape_just_one_model = world->n_meshes-1;
	}
	else if(lscape_jom_ticker >= 30 * 6)
	{
		lscape_jom_ticker = 0;
		lscape_just_one_model = -1;
	}
	if(lscape_just_one_model != -1)
	{
		char str[50];
		sprintf(str,"Mesh %d/%d",lscape_just_one_model,world->n_meshes);
		fontPrint(font, 0,100,str, 128,128,64);

		sprintf(str,"x y z %d %d %d",
			entry[lscape_just_one_model].posx/8,
			-entry[lscape_just_one_model].posy/8,
			entry[lscape_just_one_model].posz/8
			);
		fontPrint(font, 0,112,str, 128,128,64);

	}


#endif
	
#if MASTER==NO
	for(i = 0; i < world->n_meshes; i++, entry++)
	{
		if(entry->enabled && (lscape_just_one_model == -1 || lscape_just_one_model == i))
#else
	for(i = world->n_meshes; i != 0; i--, entry++)
	{
		if(entry->enabled)
#endif
		{
			int dx,dy,dz;
			unsigned int m;

			dx = entry->posx - mycamera.vpx;
			dy = entry->posy - mycamera.vpy;
			dz = entry->posz - mycamera.vpz;


			if(   dx > -0x7fff && dx < 0x7fff
			   && dy > -0x7fff && dy < 0x7fff
			   && dz > -0x7fff && dz < 0x7fff
				)
			{
				int shifts;

				shifts = 3;
// changing shifts on the matrix size doesn't affect the clipping prob
// (points still overflow)
/*
				if(m > 0x3fff * 0x3fff)
				{
					if(m > 0x1fff * 0x1fff)
					{
						shifts = 1;
					}
					else
					{
						shifts = 2;
					}
				}
				else
				{
					shifts = 3;
				}
*/
				if(lscape_just_one_model == i)
				{
					utilPrintf("m=%d, dxyz = %d %d %d\n",m,dx,dy,dz);
				}

				{
					MapDraw_SetMatrix(entry,shifts);

					if(MapDraw_ClipCheck(entry,shifts))
					{
#ifdef _WINDOWS
						if (runningHard)
							PCH3D_MapDraw_DrawFMA_Mesh3(entry,shifts);
						else
							MapDraw_DrawFMA_Mesh3DC(entry,shifts);
#else
							MapDraw_DrawFMA_Mesh3DC(entry,shifts);
#endif
					}
				}
			}
		}
	}
}


// ===================================================
//              Platform-related code
// Note that this is ALL kept after the map code, in
// order that the map code scrunches itself into the
// instruction cache.
// (Ideally, this file should be 4K aligned in the
// linker)
// ===================================================

// Set up the PSX's transformation matrix for a platform segment,
// Calculating the rot-matrix is, of course, only neccesary if the thing's rotated
// In fact, it might be nicer to cache this thing somewhere rather than recalculating it all the time
void MapDraw_SetPlatformMatrix(WORLD_MESH_INSTANCE *instance)	//FMA_MESH_HEADER *mesh)
{
	MATRIX tx;
	MATRIX tx1;
	FMA_MESH_HEADER *mesh = instance->mesh;

// If we're using quaternions, get the matrix from (xyzw) this way
//	ShortquaternionGetMatrix((SHORTQUAT *)(&mesh->rotx), &tx1);

// If we're using xyz angles, get the matrix from (xyz) this way
	RotMatrixYXZ_gte((SVECTOR *)&instance->rotx,&tx1);


	gte_MulMatrix0(&GsWSMATRIX,&tx1,&tx);

	gte_SetRotMatrix(&GsWSMATRIX);
	gte_SetTransMatrix(&GsWSMATRIX);

	{
		VECTOR temp;
		temp.vx = instance->posx - mycamera.vpx;
		temp.vy = instance->posy - mycamera.vpy;
		temp.vz = instance->posz - mycamera.vpz;
		gte_ldlvl(&temp);
		gte_rtirtr();
		gte_stlvl(&tx.t);
	}

	instance->posx = mycamera.vpx;	// instance->pos is done as a pre-rotation add to coords
	instance->posy = mycamera.vpy;
	instance->posz = mycamera.vpz;

//	tx.t[0] = 0;
//	tx.t[1] = 0;
//	tx.t[2] = 0;

	gte_SetRotMatrix(&tx);
	gte_SetTransMatrix(&tx);
}


// This is what to call to clip and draw a moving platform
void MapDraw_DrawFMA_Mesh(WORLD_MESH_INSTANCE *instance)
{
	int dx,dy,dz;
	int m;
	int radius = instance->radius;	// was 1000
	FMA_MESH_HEADER *mesh = instance->mesh;



	dx = instance->posx - mycamera.vpx;
	dy = instance->posy - mycamera.vpy;
	dz = instance->posz - mycamera.vpz;
	if(   dx > -0x7fff && dx < 0x7fff
	   && dy > -0x7fff && dy < 0x7fff
	   && dz > -0x7fff && dz < 0x7fff
		)
	{
		m = dx * dx + dy * dy + dz * dz; 
		if(m < (0x7fff * 0x7fff))
		{
			if ((utilSqrt(m) >> 16) + radius < 0x7fff)
			{
				MapDraw_SetPlatformMatrix(instance);

				if(MapDraw_ClipCheck(instance,3))
				{
					MapDraw_DrawFMA_Mesh3(instance,3);
				}

				instance->posx = mycamera.vpx + dx;	// fix the instance pos, because setplatformmatrix zeroes it
				instance->posy = mycamera.vpy + dy;
				instance->posz = mycamera.vpz + dz;

			}
		}
	}
}












void MapDraw_Draw_SkyWorld(FMA_WORLD *world)
{
	int i;
	FMA_MESH_HEADER **mesh;
	mesh = ADD2POINTER(world,sizeof(FMA_WORLD));

	for(i = world->n_meshes; i != 0; i--, mesh++)
	{
		MapDraw_SetSkyMatrix(*mesh);

//		if(MapDraw_ClipCheckS(*mesh))
		{
			MapDraw_DrawSkyMesh((void *)*mesh);
		}
	}
}


MAPCAMDATATYPE mapCamData;

void mapCamSetRefView2L(GsRVIEW2 *pView)
{
#if MASTER==NO
	{
		static int ohead = 0;
		if(padData.debounce[4] & PAD_UP)
		{
			if(ohead)
				ohead = 0;
			else
				ohead = 1;
		}
		if(padData.debounce[4] & PAD_DOWN)
		{
			if(ohead)
				ohead = 0;
			else
				ohead = 2;
		}


		switch(ohead)
		{
		case 1:
			pView->vpy -= 12000;	//8000;
			break;
		case 2:
			pView->vpy = -500;
			break;
		}
	}
#endif
	// make a copy of requested camera position, so other routines can refer to it later
	COPYVECTOR((VECTOR *)&(mapCamData.gameCam.vpx), (VECTOR *)&(pView->vpx));
	COPYVECTOR((VECTOR *)&(mapCamData.gameCam.vrx), (VECTOR *)&(pView->vrx));
	mapCamData.realCam.rz=pView->rz;
	mapCamData.realCam.super=pView->super;

// work out camera position
// (p's the position, r's where you're pointing at, dude)
	SUBVECTOR((VECTOR *)&(mapCamData.realCam.vrx), (VECTOR *)&(pView->vrx), (VECTOR *)&(pView->vpx));
	ZEROVECTOR((VECTOR *)&(mapCamData.realCam.vpx));

	mapCamData.realCam.rz=pView->rz;
	mapCamData.realCam.super=pView->super;

	// set it
	GsSetRefView2L(&mapCamData.realCam);
}

// Sphere visibility check.
// (Adapted from Glover)
int mapdrawIsSphereVisible(VECTOR *vec, int radius)
{
	VERT vect;
	int	gteH;
	int temp;

	SHORT xy[2];
	LONG distancescale;
	LONG spritez;

	gte_SetRotMatrix(&GsWSMATRIX);
	gte_SetTransMatrix(&GsWSMATRIX);


	temp = vec->vx - mapCamData.gameCam.vpx;
	if(temp < -0x7fff || temp > 0x7fff)
		return 0;
	vect.vx=temp;
	temp = vec->vz - mapCamData.gameCam.vpz;
	if(temp < -0x7fff || temp > 0x7fff)
		return 0;
	vect.vz=temp;
	temp = vec->vy - mapCamData.gameCam.vpy;
	if(temp < -0x7fff || temp > 0x7fff)
		return 0;
	vect.vy=temp;

	gte_ldv0(&vect);
	gte_rtps();
	gte_stsxy(&xy[0]);

	gte_stszotz(&spritez);

	if(spritez <= 0)
		return 0;

	gte_ReadGeomScreen(&gteH);
	distancescale = (radius * gteH) / spritez / 4;

#ifdef MAPDRAW_SHOW_VIS_SPHERES
	overlayShowSphere(vec->vx, vec->vy, vec->vz, radius, 0x00ffff);
#endif

//	printf("x,y,d = %d %d %d\n",xy[0],xy[1],distancescale);

// actually, this never happens... you just get 1024's in the x/y
	if(distancescale < 4)
	{
//		printf("Radcheck %d %d %d Tiny\n", xy[0],xy[1],distancescale);
		return 0;
	}
	//MIKEY - change clip values
	if(xy[0]+distancescale < 0 || xy[0] - distancescale > 640)	// tbd - check "resolution"
	{
//		printf("Radcheck %d %d %d Off H\n", xy[0],xy[1],distancescale);
		return 0;
	}
	if(xy[1]+distancescale < 0 || xy[1] - distancescale > 480)
	{
//		printf("Radcheck %d %d %d Off V\n", xy[0],xy[1],distancescale);
		return 0;
	}

//	printf("** Radcheck %d %d %d on\n", xy[0],xy[1],distancescale);

	return 1;
}




