// BallGfx.c

#include "ult_64.h"
#include "main.h"
#include "define.h"
#include "ml.h"

#include "misc.h"
#include "view.h"
#include "ballgfx.h"


#define CMD_END				0
#define CMD_TEXTURE		1
#define CMD_VERTICES	2
#define CMD_FACES			3

#define DISP_LIST_SIZE (16*1024)


typedef struct SMtxs
{
	Mtx
		Ball,
		HiLite,
		Reflect,
		RefHiLite;
} SMtxs;

typedef struct SBalls3D
{
	RGBA16
		NormPal[16],
		MoneyPal[16];
	u8
		NormTex[32*32/2],
		MoneyTex[32*32/2],
		HiLiteTex[32*32];
} SBalls3D;


extern int
	C3PT;


static matrix
	*BallMatrices;

static SMtxs
	*MtxPtr,
	*BallMtxs;

static Gfx
	*BallList,
	*DispLists[2];

static u16
	*BallPal;

static u8
	*BallsTex,
	*BallLight,
	*BallObj;

static SBalls3D
	*Balls3DTex;


#define BALL_FLIP_X 1
#define BALL_FLIP_Y 2

static s32 BallRotGetId( matrix *mat, s32 *flg, matrix *rollMat )
{
	s32 f,r;
	matrix tm;
	matrix tm2;
	s32 vy[3];
	s32 vz[3];
	s32 i,d,bd,bi;

	/*
	 * negate matrix
	 */

	tm = *mat;
	MatInvert( &tm );

	/*
	 * worry about flipping/symmetry...
	 */

	f=0;
	if (tm.zY > 0)
	{
		// rotate 180 deg in XY (roll)
		tm.xY = -tm.xY; tm.xX = -tm.xX;
		tm.yY = -tm.yY; tm.yX = -tm.yX;
		tm.zY = -tm.zY; tm.zX = -tm.zX;
	}
	if (tm.zZ < 0)
	{
		// rotate 180 deg in XZ (heading)
		tm.xX = -tm.xX; tm.xZ = -tm.xZ;
		tm.yX = -tm.yX; tm.yZ = -tm.yZ;
		tm.zX = -tm.zX; tm.zZ = -tm.zZ;
	}
	if (tm.zX < 0)
	{
		// flip X
		f |= BALL_FLIP_X;
		tm.xX = -tm.xX;
		tm.yX = -tm.yX;
		tm.zX = -tm.zX;
	}

	/*
	 * find best matching matrix out of this "row"
	 */

	bd = -99999999;
	for (i=0; i<56; i++)
	{
		d = BallMatrices[i].zX * tm.zX +
				BallMatrices[i].zY * tm.zY +
				BallMatrices[i].zZ * tm.zZ;

		if (d > bd)
		{
			bd = d;
			bi = i;
		}
	}

	/*
	 * remove inverse of "matching" matrix...
	 */

	tm2 = BallMatrices[bi];
	MatInvert( &tm );
	MatMulMat( &tm2, &tm, rollMat );

	if (f & BALL_FLIP_X)
	{
		rollMat->xX = -rollMat->xX;
		rollMat->xY = -rollMat->xY;
		rollMat->xZ = -rollMat->xZ;
	}

	*flg = f;

	return( bi );
}


static Gfx *DrawBall( Gfx *gp, dyn *d )
{
	static Vtx
		verts[2*4] =
		{
			-120, +120, 0, 0, 0, 0, 255, 255, 255, 255,
			+120, +120, 0, 0, 0, 0, 255, 255, 255, 255,
			+120, -120, 0, 0, 0, 0, 255, 255, 255, 255,
			-120, -120, 0, 0, 0, 0, 255, 255, 255, 255,

			-126, +120, 0, 0, (00*64)-32, (00*64)-32, 255, 255, 255, 255,
			+126, +120, 0, 0, (31*64)+32, (00*64)-32, 255, 255, 255, 255,
			+126, -126, 0, 0, (31*64)+32, (31*64)+32, 255, 255, 255, 255,
			-126, -126, 0, 0, (00*64)-32, (31*64)+32, 255, 255, 255, 255,
		};
	matrix
		mat,
		rmat;
	s32
		i,
		id,
		ballFlags;
	Vtx_t
		*v;
	s16
		*ptr;

	gDPSetAlphaCompare( gp++, G_AC_THRESHOLD );
	gDPSetBlendColor( gp++, 0, 0, 0, 128 );


	MakeMatPRH( &rmat, ball_rotx, ball_roty, ball_rotz+16384 );
	MatMulMat( &rmat, &View->World2Cam, &mat );

	id = BallRotGetId( &mat, &ballFlags, &rmat );
	MatMulMat( &rmat, &View->Cam2World, &mat );
	MatToN64( &mat, &d->ballMats[0] );
	MatToN64( &View->Cam2World, &d->ballMats[2] );

	if (ballFlags & BALL_FLIP_X)
	{
		verts[0].v.tc[0] = (31*64)+32;
		verts[1].v.tc[0] = (00*64)-20;
		verts[2].v.tc[0] = (00*64)-20;
		verts[3].v.tc[0] = (31*64)+32;
	}
	else
	{
		verts[0].v.tc[0] = (00*64)-20;
		verts[1].v.tc[0] = (31*64)+32;
		verts[2].v.tc[0] = (31*64)+32;
		verts[3].v.tc[0] = (00*64)-20;
	}

	if (ballFlags & BALL_FLIP_Y)
	{
		verts[0].v.tc[1] = (31*64)+32;
		verts[1].v.tc[1] = (31*64)+32;
		verts[2].v.tc[1] = (00*64)-20;
		verts[3].v.tc[1] = (00*64)-20;
	}
	else
	{
		verts[0].v.tc[1] = (00*64)-20;
		verts[1].v.tc[1] = (00*64)-20;
		verts[2].v.tc[1] = (31*64)+32;
		verts[3].v.tc[1] = (31*64)+32;
	}


	ptr = (s16 *)&d->ballMats[0];
#if 0
	ptr[12] = 0;
	ptr[13] = BASKET_Z>>10;
	ptr[14] = -BASKET_Y>>10;
#else
	ptr[12] = ball.flip_xco>>10;
	ptr[13] = ball.flip_zco>>10;
	ptr[14] = ball.flip_yco>>10;
#endif

	ptr = (s16 *)&d->ballMats[2];
#if 0
	ptr[12] = 0;
	ptr[13] = BASKET_Z>>10;
	ptr[14] = -BASKET_Y>>10;
#else
	ptr[12] = ball.flip_xco>>10;
	ptr[13] = ball.flip_zco>>10;
	ptr[14] = ball.flip_yco>>10;
#endif


	d->ballMats[1] = d->ballMats[0];
	d->ballMats[3] = d->ballMats[2];

	ptr = (s16 *)&d->ballMats[1];
	ptr[13] = -ball.flip_zco>>10;

	ptr = (s16 *)&d->ballMats[3];
	ptr[13] = -ball.flip_zco>>10;


	gDPPipeSync( gp++ );
	gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON );
	gSPClearGeometryMode( gp++, G_CULL_BACK );


	gSPMatrix( gp++, &d->ballMats[0], G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH );
	gSPVertex( gp++, verts, 4, 0 );
	gDPSetCombineMode( gp++, G_CC_DECALRGBA, G_CC_DECALRGBA );
	gDPSetRenderMode( gp++, G_RM_AA_ZB_TEX_EDGE, G_RM_AA_ZB_TEX_EDGE2 );

	gDPSetTextureLUT( gp++, G_TT_RGBA16 );
	gDPLoadTLUT_pal16( gp++, 0, BallPal );

	gDPLoadTextureBlock_4b( gp++,
													&BallsTex[id*32*32/2],
													G_IM_FMT_CI,
													32,
													32,
													0,
													G_TX_CLAMP,
													G_TX_CLAMP,
													5, 5,
													G_TX_NOLOD, G_TX_NOLOD );

	gSP2Triangles( gp++, 0, 2, 1, 0, 0, 3, 2, 0 );

	gDPPipeSync( gp++ );
	gDPSetAlphaCompare( gp++, G_AC_NONE );
	gDPSetCombineMode( gp++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM );
i = 128 - (ball.flip_zco >> 13);
if (i < 0)
	i = 0;
	gDPSetPrimColor( gp++, 0, 0, 255, 255, 255, i );
	gDPSetRenderMode( gp++, G_RM_ZB_XLU_SURF, G_RM_ZB_XLU_SURF2 );
	gSPMatrix( gp++, &d->ballMats[1], G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH );
	gSPVertex( gp++, verts, 4, 0 );
	gSP2Triangles( gp++, 0, 2, 1, 0, 0, 3, 2, 0 );


	gDPPipeSync( gp++ );
	gSPMatrix( gp++, &d->ballMats[2], G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH );
	gDPSetPrimColor( gp++, 0, 0, 255, 255, 128, 255 );
	gDPSetCombineMode( gp++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM );
	gDPSetRenderMode( gp++, G_RM_ZB_XLU_SURF, G_RM_ZB_XLU_SURF2 );
	gDPSetTextureLUT( gp++, G_TT_NONE );

	gDPLoadTextureBlock( gp++,
											 BallLight,
											 G_IM_FMT_IA,
											 G_IM_SIZ_8b,
											 32,
											 32,
											 0,
											 G_TX_CLAMP,
											 G_TX_CLAMP,
											 5, 5,
											 G_TX_NOLOD, G_TX_NOLOD );

	gSPMatrix(gp++,&d->BallProject,G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH);

	gSPVertex( gp++, &verts[4], 4, 0 );
	gSP2Triangles( gp++, 0, 2, 1, 0, 0, 3, 2, 0 );

	gSPMatrix(gp++,&d->projection,G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH);

	gDPPipeSync( gp++ );
	gDPSetCombineMode( gp++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM );
i = 128 - (ball.flip_zco >> 13);
if (i < 0)
	i = 0;
	gDPSetPrimColor( gp++, 0, 0, 255, 255, 128, i );
	gDPSetRenderMode( gp++, G_RM_ZB_XLU_SURF, G_RM_ZB_XLU_SURF2 );
	gSPMatrix( gp++, &d->ballMats[3], G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH );
	gSPVertex( gp++, &verts[4], 4, 0 );
	gSP2Triangles( gp++, 0, 2, 1, 0, 0, 3, 2, 0 );

	gDPSetPrimColor( gp++, 0, 0, 0, 0, 0, 128 );
	gDPSetAlphaCompare( gp++, G_AC_NONE );

	return( gp );
}


static Gfx *DrawBallObj( Gfx *gp, u8 *object )
{
	s16
		*ptr,
		count,
		command;
	u8
		*vtx;

	ptr = (s16 *)&object[6*4];
	vtx = &object[*(s32 *)&object[5*4]];

	while (*ptr)
	{
		command = ptr[0];
		count = ptr[1];
		ptr += 2;

		switch (command)
		{
			case CMD_VERTICES:
				gSPVertex( gp++, vtx, count, 0 );
				vtx += (count * 16);
				break;

			case CMD_FACES:
				while (count >= 2)
				{
					gSP2Triangles( gp++,
												 ptr[0], ptr[1], ptr[2], 0,
												 ptr[3], ptr[4], ptr[5], 0 );
					ptr += 2*3;
					count -= 2;
				}
				if (count > 0)
				{
					gSP1Triangle( gp++, ptr[0], ptr[1], ptr[2], 0 );
					ptr += 3;
				}
				break;
		}
	}

	return( gp );
}


static Gfx *Draw3DBall( Gfx *gp, ballt *bptr, SMtxs *mtxs )
{
	static Vtx
		verts[2*4] =
		{
			-110, +110, 0, 0, (00*64)-32, (00*64)-32, 255, 255, 255, 255,
			+110, +110, 0, 0, (31*64)+32, (00*64)-32, 255, 255, 255, 255,
			+110, -110, 0, 0, (31*64)+32, (31*64)+32, 255, 255, 255, 255,
			-110, -110, 0, 0, (00*64)-32, (31*64)+32, 255, 255, 255, 255,

			-110, +110, 0, 0, (00*64)-32, (32*64)-32, 255, 255, 255, 255,
			+110, +110, 0, 0, (31*64)+32, (32*64)-32, 255, 255, 255, 255,
			+110, -110, 0, 0, (31*64)+32, (00*64)+32, 255, 255, 255, 255,
			-110, -110, 0, 0, (00*64)-32, (00*64)+32, 255, 255, 255, 255,
		};
	s32
		i,
		a,
		dist,
		x,
		y,
		z,
		type = 0;
	matrix
		rotmat,
		camera;
	vector16
		vec,
		bpos;
	s16
		*ptr;

	bpos.x = bptr->xco>>10;
	bpos.y = bptr->zco>>10;
	bpos.z = bptr->yco>>10;


	MakeMatPRH( &rotmat, bptr->rotx, bptr->roty, bptr->rotz+16384 );


	MatToN64( &rotmat, &mtxs->Ball );
	SetTransN64( &mtxs->Ball, bpos.x, bpos.y, bpos.z );


	MatToN64( &View->Cam2World, &mtxs->HiLite );

	x = bpos.x - View->Camera.x;
	y = bpos.y - View->Camera.y;
	z = bpos.z - View->Camera.z;
	dist = (x * x) + (y * y) + (z * z);
	if (dist < 0)
		return( gp );
	dist = (s32)sqrtf( (float)dist );
	x = (x * (dist - 121)) / dist;
	y = (y * (dist - 121)) / dist;
	z = (z * (dist - 121)) / dist;

	ptr = (s16 *)&mtxs->HiLite;
	ptr[12] = View->Camera.x + x;
	ptr[13] = View->Camera.y + y;
	ptr[14] = View->Camera.z + z;


	rotmat.xY = -rotmat.xY;
	rotmat.yY = -rotmat.yY;
	rotmat.zY = -rotmat.zY;
	MatToN64( &rotmat, &mtxs->Reflect );
	SetTransN64( &mtxs->Reflect, bpos.x, -bpos.y, bpos.z );


	MatToN64( &View->Cam2World, &mtxs->RefHiLite );

	x = bpos.x - View->Camera.x;
	y = -bpos.y - View->Camera.y;
	z = bpos.z - View->Camera.z;
	dist = (x * x) + (y * y) + (z * z);
	if (dist < 0)
		return( gp );
	dist = (s32)sqrtf( (float)dist );
	x = (x * (dist - 121)) / dist;
	y = (y * (dist - 121)) / dist;
	z = (z * (dist - 121)) / dist;

	ptr = (s16 *)&mtxs->RefHiLite;
	ptr[12] = View->Camera.x + x;
	ptr[13] = View->Camera.y + y;
	ptr[14] = View->Camera.z + z;


	a = 128 - (bptr->zco >> 13);
	if (a > 0)
	{
		// Reflection...

		gDPPipeSync( gp++ );
		gSPClearGeometryMode( gp++, G_CULL_BACK );
		gSPSetGeometryMode( gp++, G_CULL_FRONT );
		gDPSetTextureLUT( gp++, G_TT_RGBA16 );
		gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE + bptr->type - 1, G_ON );
		gSPMatrix( gp++, &mtxs->Reflect, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH );
		gDPSetRenderMode( gp++, G_RM_ZB_XLU_SURF, G_RM_ZB_XLU_SURF2 );
		gDPSetCombineMode( gp++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM );
		gDPSetPrimColor( gp++, 0, 0, 255, 255, 255, a );
		gSPDisplayList( gp++, BallList );
		gSPClearGeometryMode( gp++, G_CULL_FRONT );
		gSPSetGeometryMode( gp++, G_CULL_BACK );


		// Reflected high-light...

		gDPPipeSync( gp++ );
		gDPSetTextureLUT( gp++, G_TT_NONE );
		gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE + 2, G_ON );
		gSPMatrix( gp++, &mtxs->RefHiLite, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH );
		gDPSetPrimColor( gp++, 0, 0, 255, 255, 128, a );
		gSPVertex( gp++, &verts[4], 4, 0 );
		gSP2Triangles( gp++, 0, 2, 1, 0, 0, 3, 2, 0 );
	}


	// Real thing...

	gDPPipeSync( gp++ );
	gDPSetTextureLUT( gp++, G_TT_RGBA16 );
	gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE + bptr->type - 1, G_ON );
	gSPMatrix( gp++, &mtxs->Ball, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH );
	gDPSetCombineMode( gp++, G_CC_DECALRGB, G_CC_DECALRGB );
	gDPSetRenderMode( gp++, G_RM_ZB_OPA_SURF, G_RM_ZB_OPA_SURF2 );
	gSPDisplayList( gp++, BallList );


	// Real high-light...

	gDPPipeSync( gp++ );
	gDPSetTextureLUT( gp++, G_TT_NONE );
	gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE + 2, G_ON );
	gSPMatrix( gp++, &mtxs->HiLite, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH );
	gDPSetPrimColor( gp++, 0, 0, 255, 255, 128, 255 );
	gDPSetCombineMode( gp++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM );
	gDPSetRenderMode( gp++, G_RM_ZB_XLU_SURF, G_RM_ZB_XLU_SURF2 );
	gSPVertex( gp++, verts, 4, 0 );
	gSP2Triangles( gp++, 0, 2, 1, 0, 0, 3, 2, 0 );


	return( gp );
}


static Gfx *SetupGfx3D( Gfx *gp )
{
	gDPPipeSync( gp++ );
	gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON );
	gDPSetAlphaCompare( gp++, G_AC_NONE );
	gDPSetTextureLUT( gp++, G_TT_RGBA16 );

	gDPLoadTLUT( gp++, 2*16, 256+0, &Balls3DTex->NormPal[0] );

	gDPLoadMultiBlock_4b( gp++,
												Balls3DTex->NormTex,
												0,			// tmem
												G_TX_RENDERTILE+0,
												G_IM_FMT_CI,
												32,
												32,
												0,			// pal
												G_TX_CLAMP,
												G_TX_CLAMP,
												5,
												5,
												G_TX_NOLOD,
												G_TX_NOLOD );

	gDPLoadMultiBlock_4b( gp++,
												Balls3DTex->NormTex,
												0,			// tmem
												G_TX_RENDERTILE+1,
												G_IM_FMT_CI,
												32,
												32,
												1,			// pal
												G_TX_CLAMP,
												G_TX_CLAMP,
												5,
												5,
												G_TX_NOLOD,
												G_TX_NOLOD );

	gDPLoadMultiBlock( gp++,
										 Balls3DTex->MoneyTex,
										 (32*32/2)/8,
										 G_TX_RENDERTILE+2,
										 G_IM_FMT_IA,
										 G_IM_SIZ_8b,
										 32,
										 32,
										 0,
										 G_TX_CLAMP,
										 G_TX_CLAMP,
										 5,
										 5,
										 G_TX_NOLOD,
										 G_TX_NOLOD );

	return( gp );
}


static Gfx *DrawBalls( Gfx *gp, s32 which, s32 first )
{
	s32
		i,
		a,
		dist,
		x,
		y,
		z,
		type = 0;
	SMtxs
		*mtxs;
	matrix
		rotmat;
	vector16
		vec;
	s16
		*ptr;
	ballt
		*bptr;
	u8
		*tex = (u8 *)Balls3DTex;

	if (first)
		MtxPtr = &BallMtxs[which*NUM_BALLS];

	mtxs = MtxPtr;


	gDPPipeSync( gp++ );
	gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON );
	gDPSetAlphaCompare( gp++, G_AC_NONE );
	gDPSetTextureLUT( gp++, G_TT_RGBA16 );

	gDPLoadTLUT( gp++, 2*16, 256+0, tex );

	gDPLoadMultiBlock_4b( gp++,
												&tex[2*16*2],
												0,			// tmem
												G_TX_RENDERTILE+0,
												G_IM_FMT_CI,
												32,
												32,
												0,			// pal
												G_TX_CLAMP,
												G_TX_CLAMP,
												5,
												5,
												G_TX_NOLOD,
												G_TX_NOLOD );

	gDPLoadMultiBlock_4b( gp++,
												&tex[2*16*2],
												0,			// tmem
												G_TX_RENDERTILE+1,
												G_IM_FMT_CI,
												32,
												32,
												1,			// pal
												G_TX_CLAMP,
												G_TX_CLAMP,
												5,
												5,
												G_TX_NOLOD,
												G_TX_NOLOD );

	gDPLoadMultiBlock( gp++,
										 &tex[(2*16*2)+(32*32/2)],
										 (32*32/2)/8,
										 G_TX_RENDERTILE+2,
										 G_IM_FMT_IA,
										 G_IM_SIZ_8b,
										 32,
										 32,
										 0,
										 G_TX_CLAMP,
										 G_TX_CLAMP,
										 5,
										 5,
										 G_TX_NOLOD,
										 G_TX_NOLOD );

	for (i=0; i<NUM_BALLS; i++)
	{
		bptr = &Balls[i];

		if (bptr->type != BALLTYPE_NONE)
		{
			vec.x = bptr->xco>>10;
			vec.y = bptr->zco>>10;
			vec.z = bptr->yco>>10;

			if (View_Inside( &vec, 120 ))
			{
				gp = Draw3DBall( gp, bptr, mtxs );
				mtxs++;
			}
		}
	}

	gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON );

	MtxPtr = mtxs;

	return( gp );
}


static Gfx *DrawArcadeBall( Gfx *gp, s32 which )
{
	ballt
		balldata;
	vector16
		vec;

	Ball2Balls( &balldata );

	if (ArcadeMode)
		balldata.type = BALLTYPE_MONEY;
	else
		balldata.type = BALLTYPE_REGULAR;

	if (switch_ends_flag)
	{
		balldata.xco = -balldata.xco;
		balldata.yco = -balldata.yco;
	}

	vec.x = balldata.xco>>10;
	vec.y = balldata.zco>>10;
	vec.z = balldata.yco>>10;

	if (View_Inside( &vec, 120 ))
	{
		gp = SetupGfx3D( gp );
		gp = Draw3DBall( gp, &balldata, &BallMtxs[which] );

		gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON );
	}

	return( gp );
}


void BallGfx_Init( void *workspace )
{
	Gfx
		*gp;
	s16
		*ptr,
		*vecs;
	s32
		i,
		size;

	if (C3PT || ArcadeMode)
	{
		if (C3PT)
		{
			DispLists[0] = MemMalloc( DISP_LIST_SIZE, MEM_ANY );
			DispLists[1] = MemMalloc( DISP_LIST_SIZE, MEM_ANY );

			BallMtxs = MemMalloc( 2*NUM_BALLS*sizeof( SMtxs ), MEM_ANY );
		}
		else
			BallMtxs = MemMalloc( 2*sizeof( SMtxs ), MEM_ANY );

		Balls3DTex = AllocAndLoadFile( "Balls3D.TEX", MEM_ANY );
		Balls3DTex->NormPal[0].a = 1;
		Balls3DTex->MoneyPal[0].a = 1;

		BallObj = AllocAndLoadFile( "ball.obj", MEM_ANY );
		gp = DrawBallObj( workspace, BallObj );
		gSPEndDisplayList( gp++ );
		size = (s32)gp - (s32)workspace;
		BallList = MemMalloc( size, MEM_ANY );
		gp = DrawBallObj( BallList, BallObj );
		gSPEndDisplayList( gp++ );
	}
	else
	{
		BallPal = AllocAndLoadFile( "BALL.PAL", MEM_ANY );
		BallsTex = AllocAndLoadFile( "BALLS.TEX", MEM_ANY );
		BallLight = AllocAndLoadFile( "BALLLITE.TEX", MEM_ANY );


		BallMatrices = MemMalloc( 56 * sizeof( matrix ), MEM_ANY );

		vecs = AllocAndLoadFile( "BALLS.VEC", MEM_TMP );
		ptr = vecs;

		for (i=0; i<56; i++)
		{
			MakeMatPRH( &BallMatrices[i], ptr[0], ptr[1], ptr[2] );
			MatInvert( &BallMatrices[i] );
			ptr += 3;
		}

		MemFree( vecs );
	}
}


void BallGfx_Deinit()
{
	if (C3PT || ArcadeMode)
	{
		MemFree( Balls3DTex );
		MemFree( BallMtxs );
		MemFree( BallObj );
		MemFree( BallList );

		if (C3PT)
		{
			MemFree( DispLists[1] );
			MemFree( DispLists[0] );
		}
	}
	else
	{
		MemFree( BallPal );
		MemFree( BallsTex );
		MemFree( BallLight );
		MemFree( BallMatrices );
	}
}


Gfx *BallGfx_Draw( Gfx *gp, dyn *d, s32 which, s32 first )
{
	static Gfx
		*gl;

	if (C3PT)
	{
		if (first)
			gl = DispLists[which];

		gSPDisplayList( gp++, gl );

		gl = DrawBalls( gl, which, first );
		gSPEndDisplayList( gl++ );

		if (((u32)gl - (u32)DispLists[which]) > DISP_LIST_SIZE)
			FatalError( "ball glist too small", __FILE__, __LINE__ );
	}
	else if (AttractMode || ArcadeMode)
		gp = DrawArcadeBall( gp, which );
	else
		gp = DrawBall( gp, d );

	return( gp );
}
