#include "interiorMap.h"

#include "dgl/dgl.h"
#include "core/bitStream.h"
#include "game/gameConnection.h"
#include "math/mathIO.h"
#include "console/consoleTypes.h"
#include "collision/concretePolyList.h"
#include "dgl/gBitmap.h"
#include "math/mPlaneTransformer.h"

#define FRONTEPSILON 0.00001

//------------------------------------------------------------------------------
IMPLEMENT_CO_NETOBJECT_V1(InteriorMap);

// Return the bounding box transformed into world space
Box3F InteriorMapConvex::getBoundingBox() const
{
	return getBoundingBox(mObject->getTransform(), mObject->getScale());
}

// Transform and scale the bounding box by the inputs
Box3F InteriorMapConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const
{
	Box3F newBox = box;
	newBox.min.convolve(scale);
	newBox.max.convolve(scale);
	mat.mul(newBox);
	return newBox;
}

// Return the point furthest from the input vector
Point3F InteriorMapConvex::support(const VectorF& v) const
{
	Point3F ret(0.0f, 0.0f, 0.0f);
	F32 dp = 0.0f;
	F32 tp = 0.0f;

	// Loop through the points and save the furthest one
	//for (U32 i = 0; i < 8; i++)
	//{
	//	tp = mDot(v, pOwner->mPoints[i]);

	//	if (tp > dp)
	//	{
	//		dp = tp;
	//		ret = pOwner->mPoints[i];
	//	}
	//}

	return ret;
}

// This function simply checks to see if the edge already exists on the edgelist
// If it doesn't the edge gets added
void InteriorMapConvex::addEdge(U32 zero, U32 one, U32 base, ConvexFeature* cf)
{
   U32 newEdge0, newEdge1;

   // Sort the vertex indexes by magnitude (lowest number first, largest second)
   newEdge0 = getMin(zero, one) + base;
   newEdge1 = getMax(zero, one) + base;

   // Assume that it isn't found
   bool found = false;

   // Loop through the edgelist
   // Start with base so we don't search *all* of the edges if there already are some on the list
   for (U32 k = base; k < cf->mEdgeList.size(); k++)
   {
      // If we find a match flag found and break out (no need to keep searching)
      if (cf->mEdgeList[k].vertex[0] == newEdge0 && cf->mEdgeList[k].vertex[1] == newEdge1)
      {
         found = true;
         break;
      }
   }

   // If we didn't find it then add it
   // Otherwise we return without doing anything
   if (!found)
   {
      cf->mEdgeList.increment();
      cf->mEdgeList.last().vertex[0] = newEdge0;
      cf->mEdgeList.last().vertex[1] = newEdge1;
   }
};

// Return the vertices, faces, and edges of the convex
// Used by the vehicle collisions
void InteriorMapConvex::getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf)
{

}


// Return list(s) of convex faces
// Used by player collisions
void InteriorMapConvex::getPolyList(AbstractPolyList* list)
{
	// Be sure to transform the list into model space
	list->setTransform(&mObject->getTransform(), mObject->getScale());
   // Set the object
	list->setObject(mObject);

   // Get the brush
   ConvexBrush* brush = pOwner->mInteriorRes->mBrushes[brushIndex];

   brush->getPolyList(list);
}

InteriorMap::InteriorMap(void)
{
	// Setup NetObject.
   // Note that we set this as a TutorialObjectType object
   // TutorialObjectType is defined in game/objectTypes.h in the SimObjectTypes enum
   // You should also notify the scripting engine of it existance in game/main.cc in initGame()
	mTypeMask = StaticObjectType | StaticRenderedObjectType | InteriorMapObjectType;
	mNetFlags.set(Ghostable);

   // Haven't loaded yet
   mLoaded = false;

	// Give it a nonexistant bounding box
	mObjBox.min.set(0, 0, 0);
	mObjBox.max.set(0, 0, 0);

   mConvexList = new Convex;
   mTexPath = NULL;
   mRenderMode = TexShaded;
   mEnableLights = true;
   mTexHandles = NULL;
}

InteriorMap::~InteriorMap(void)
{
   delete mConvexList;
   mConvexList = NULL;
}

void InteriorMap::initPersistFields()
{
	// Initialise parents' persistent fields.
	Parent::initPersistFields();

   addField( "File",		TypeFilename,	Offset( mFileName,		InteriorMap ) );
}

bool InteriorMap::onAdd()
{
	if(!Parent::onAdd()) return(false);

   // Set our path info
   setPath(mFileName);

   // Load resource
   mInteriorRes = ResourceManager->load(mFileName, true);
   if (bool(mInteriorRes) == false)
   {
      Con::errorf(ConsoleLogEntry::General, "Unable to load interior: %s", mFileName);
      NetConnection::setLastError("Unable to load interior: %s", mFileName);
      return false;
   }
   else
      mLoaded = true;

   // Scale our brushes
   if (mInteriorRes->mBrushScale != 1.0f)
   {
      for (U32 i = 0; i < mInteriorRes->mBrushes.size(); i++)
         mInteriorRes->mBrushes[i]->setScale(mInteriorRes->mBrushScale);
   }

   loadTextures();

   mInteriorRes->mTexGensCalced = calcTexgenDiv();

   // Set the bounds
	mObjBox.min.set(1e10, 1e10, 1e10);
	mObjBox.max.set(-1e10, -1e10, -1e10);

   for (U32 i = 0; i < mInteriorRes->mBrushes.size(); i++)
   {
      if (mInteriorRes->mBrushes[i]->mStatus == ConvexBrush::Good)
      {
         // Transform the bounding boxes
         MatrixF& mat = mInteriorRes->mBrushes[i]->mTransform;
         Point3F& scale = mInteriorRes->mBrushes[i]->mScale;

         Point3F min = mInteriorRes->mBrushes[i]->mBounds.min;
         Point3F max = mInteriorRes->mBrushes[i]->mBounds.max;

         mat.mulP(min);
         mat.mulP(max);

         min.convolveInverse(scale);
         max.convolveInverse(scale);

         mObjBox.min.setMin(min);
		   mObjBox.max.setMax(max);
      }
   }

   mWhite = new TextureHandle("common/lighting/whiteAlpha255", MeshTexture);

	// Reset the World Box.
	resetWorldBox();
	// Set the Render Transform.
	setRenderTransform(mObjToWorld);

	// Add to Scene.
	addToScene();

	// Return OK.
	return true;
}

bool InteriorMap::setPath(const char* filename)
{
   // Save our filename just in case it hasn't already been done
   mFileName = StringTable->insert(filename);

   // Get the directory
   char dir[4096]; // FIXME: no hardcoded lengths
   if (dStrrchr(filename, '/'))
   {
	   dStrncpy(dir, filename, (int)(dStrrchr(filename, '/') - filename + 1));
      dir[(int)(dStrrchr(filename, '/') - filename + 1)] = 0;
   }
   else if (dStrrchr(filename, '\\'))
   {
      dStrncpy(dir, filename, (int)(dStrrchr(filename, '\\') - filename + 1));
      dir[(int)(dStrrchr(filename, '\\') - filename + 1)] = 0;
   }
   else
   {
      dSprintf(dir, dStrlen(Platform::getWorkingDirectory()) + 1, "%s/\0", Platform::getWorkingDirectory());
   }

   mFilePath = StringTable->insert(dir);
   if (!mTexPath)
      mTexPath = mFilePath;

   return true;
}

bool InteriorMap::loadTextures()
{
   if (mTexHandles)
      return true;

   // Load our textures
	mTexHandles = new TextureHandle[mInteriorRes->mMaterials.size()];

	for (int t = 0; t < mInteriorRes->mMaterials.size(); t++)
	{
      mTexHandles[t] = NULL;

     char fullname[8192];

      // First go for standard
      dSprintf(fullname, 8192, "%s%s\0", mTexPath, mInteriorRes->mMaterials[t]);

      mTexHandles[t] = TextureHandle(fullname, MeshTexture);
	}

   return true;
}

bool InteriorMap::calcTexgenDiv()
{
   // If we have already calculated our texgen scale then we don't need to do it again
   // This occurs when the client and server are on the same machine (they share Resources)
   if (mInteriorRes->mTexGensCalced)
      return true;

   if (mInteriorRes->mBrushFormat != InteriorMapResource::QuakeOld && mInteriorRes->mBrushFormat != InteriorMapResource::Valve220)
      return false;

   for (U32 i = 0; i < mInteriorRes->mMaterials.size(); i++)
	{
      F32 width  = 16.0f;
      F32 height = 16.0f;

      if (mTexHandles[i].getWidth() != 0UL)
      {
         width  = mTexHandles[i].getWidth();
         height = mTexHandles[i].getHeight();
      }

      for (U32 j = 0; j < mInteriorRes->mBrushes.size(); j++)
      {
         for (U32 k = 0; k < mInteriorRes->mBrushes[j]->mFaces.mPolyList.size(); k++)
         {
            if (i == mInteriorRes->mBrushes[j]->mFaces.mPolyList[k].material)
            {
               mInteriorRes->mBrushes[j]->mTexInfos[k].texDiv[0] = width;
               mInteriorRes->mBrushes[j]->mTexInfos[k].texDiv[1] = height;

               mInteriorRes->mBrushes[j]->mTexInfos[k].texGens[0].x /= width;
               mInteriorRes->mBrushes[j]->mTexInfos[k].texGens[0].y /= width;
               mInteriorRes->mBrushes[j]->mTexInfos[k].texGens[0].z /= width;
               mInteriorRes->mBrushes[j]->mTexInfos[k].texGens[0].d /= width;

               mInteriorRes->mBrushes[j]->mTexInfos[k].texGens[1].x /= height;
               mInteriorRes->mBrushes[j]->mTexInfos[k].texGens[1].y /= height;
               mInteriorRes->mBrushes[j]->mTexInfos[k].texGens[1].z /= height;
               mInteriorRes->mBrushes[j]->mTexInfos[k].texGens[1].d /= height;
            }
         }
      }
   }

   // MDFFIX: Probably should move this to its own function
   // Scale the texgens by the brushscale
   for (U32 j = 0; j < mInteriorRes->mBrushes.size(); j++)
   {
      for (U32 k = 0; k < mInteriorRes->mBrushes[j]->mFaces.mPolyList.size(); k++)
      {
         mInteriorRes->mBrushes[j]->mTexInfos[k].texGens[0] *= mInteriorRes->mBrushScale;
         mInteriorRes->mBrushes[j]->mTexInfos[k].texGens[1] *= mInteriorRes->mBrushScale;
      }
   }

   return true;
}

void InteriorMap::onRemove()
{
   mConvexList->nukeList();

	// Remove from Scene.
	removeFromScene();

	// Do Parent.
	Parent::onRemove();
}

void InteriorMap::inspectPostApply()
{
	// Set Parent.
	Parent::inspectPostApply();

	// Set Move Mask.
	setMaskBits(MoveMask);
}

//------------------------------------------------------------------------------

void InteriorMap::onEditorEnable()
{
}

//------------------------------------------------------------------------------

void InteriorMap::onEditorDisable()
{
}

bool InteriorMap::prepRenderImage(	SceneState* state, const U32 stateKey, const U32 startZone,
										const bool modifyBaseZoneState)
{
	// Return if last state.
	if (isLastState(state, stateKey)) return false;
	// Set Last State.
	setLastState(state, stateKey);

   // Is Object Rendered?
   if (state->isObjectRendered(this))
   {	
		// Yes, so get a SceneRenderImage.
		SceneRenderImage* image = new SceneRenderImage;
		// Populate it.
		image->obj = this;
		image->isTranslucent = false;
		image->sortType = SceneRenderImage::Normal;
		
		// Insert it into the scene images.
		state->insertRenderImage(image);
   }

   return false;
}

void InteriorMap::renderObject(SceneState* state, SceneRenderImage*)
{
	// Check we are in Canonical State.
	AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry");

	// Save state.
	RectI viewport;

   if (mEnableLights && mRenderMode != BrushColors && mRenderMode != FaceColors && mRenderMode != BspPolys && mRenderMode != CollisionHulls)
   {
      ColorF oneColor(1,1,1,1);
      glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, oneColor);
      installLights();
   }

	// Save Projection Matrix so we can restore Canonical state at exit.
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();

	// Save Viewport so we can restore Canonical state at exit.
	dglGetViewport(&viewport);

	// Setup the projection to the current frustum.
	//
	// NOTE:-	You should let the SceneGraph drive the frustum as it
	//			determines portal clipping etc.
	//			It also leaves us with the MODELVIEW current.
	//
	state->setupBaseProjection();

	// Save ModelView Matrix so we can restore Canonical state at exit.
	glPushMatrix();

	// Transform by the objects' transform e.g move it.
	dglMultMatrix(&getTransform());

   glScalef(mObjScale.x, mObjScale.y, mObjScale.z);

	// I separated out the actual render function to make this a little cleaner
   if (mRenderMode != Edges && mRenderMode != BspPolys && mRenderMode != CollisionHulls)
	   render();

   if (mEnableLights && mRenderMode != BrushColors && mRenderMode != FaceColors && mRenderMode != BspPolys && mRenderMode != CollisionHulls)
      uninstallLights();

	// Restore our canonical matrix state.
	glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	// Restore our canonical viewport state.
	dglSetViewport(viewport);

	// Check we have restored Canonical State.
	AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit");
}

bool InteriorMap::render(void)
{
   gRandGen.setSeed(1978);

   //glEnable(GL_CULL_FACE);

   ColorF oneColor;

   if (mRenderMode == TexShaded || mRenderMode == TexWireframe ||
       mRenderMode == Lighting || mRenderMode == TexLighting)
   {
	  glActiveTextureARB(GL_TEXTURE0_ARB);
      glEnable(GL_TEXTURE_2D);
	  glBindTexture(GL_TEXTURE_2D, mWhite->getGLName());
	
	  if(mRenderMode == Lighting || mRenderMode == TexLighting)
	  {
	     glActiveTextureARB(GL_TEXTURE1_ARB);
	     glEnable(GL_TEXTURE_2D);
		 glBindTexture(GL_TEXTURE_2D, mWhite->getGLName());
	
	     if(mRenderMode == TexLighting)
	        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	     else
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	
	     glActiveTextureARB(GL_TEXTURE0_ARB);
		 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	  }
	  else
      glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
   }

   S32 bound = -2;

   for (U32 i = 0; i < mInteriorRes->mBrushes.size(); i++)
   {
      if (mInteriorRes->mBrushes[i]->mType == InteriorMapResource::Portal || mInteriorRes->mBrushes[i]->mType == InteriorMapResource::Trigger)
         continue;

      if (mInteriorRes->mBrushes[i]->mStatus == ConvexBrush::Deleted)
         continue;

      if (mRenderMode == BrushColors)
      {
         //oneColor = ColorF(gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f), 1.0f);
         //glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, oneColor);
         glColor4f(gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f), 1.0f);
      }

      // Save ModelView Matrix so we can restore Canonical state at exit.
	   glPushMatrix();

	   // Transform by the objects' transform e.g move it.
	   dglMultMatrix(&mInteriorRes->mBrushes[i]->mTransform);

      // Scale
      //glScalef(mInteriorRes->mBrushes[i]->mScale.x, mInteriorRes->mBrushes[i]->mScale.y, mInteriorRes->mBrushes[i]->mScale.z);

      for (U32 j = 0; j < mInteriorRes->mBrushes[i]->mFaces.mPolyList.size(); j++)
      {
         S32 tx = mInteriorRes->mBrushes[i]->mFaces.mPolyList[j].material;

         if (tx == -2)
            continue;

         if (mRenderMode == TexShaded || mRenderMode == TexWireframe ||
			 mRenderMode == TexLighting)
         {
            if (tx != bound)
            {
			   // Really cheesy way to see if I have a TextureObject
               if (mTexHandles[tx].getWidth() != 0UL)
               {
			      glActiveTextureARB(GL_TEXTURE0_ARB);
                  glBindTexture(GL_TEXTURE_2D, mTexHandles[tx].getGLName());
                  bound = tx;
               }
               else
               {
			      glActiveTextureARB(GL_TEXTURE0_ARB);
                  glBindTexture(GL_TEXTURE_2D, mWhite->getGLName());
                  bound = -1;
               }
            }
         }

         if (mRenderMode == FaceColors)
         {
            //oneColor = ColorF(gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f), 1.0f);
            //glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, oneColor);
            glColor4f(gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f), 1.0f);
         }

         mInteriorRes->mBrushes[i]->renderFace(j, (mRenderMode == Lighting || mRenderMode == TexLighting));
      }

      glPopMatrix();
   }

   if (mRenderMode == TexShaded || mRenderMode == TexWireframe ||
       mRenderMode == Lighting || mRenderMode == TexLighting)
   {
      glActiveTextureARB(GL_TEXTURE1_ARB);
      glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
      glDisable(GL_TEXTURE_2D);
	
      glActiveTextureARB(GL_TEXTURE0_ARB);
      glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
      glDisable(GL_TEXTURE_2D);
   }

   //glDisable(GL_CULL_FACE);

	return true;
}

U32 InteriorMap::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
{
	// Pack Parent.
	U32 retMask = Parent::packUpdate(con, mask, stream);

	// Write fxPortal Mask Flag.
	if (stream->writeFlag(mask & MoveMask))
	{
		// Write Object Transform.
		stream->writeAffineTransform(mObjToWorld);

      // Write Object Scale.
      mathWrite(*stream, mObjScale);

      // Write the file name
      stream->writeString(mFileName);
	}

   if (stream->writeFlag(mask & RenderModeMask))
      stream->write(mRenderMode);

	// Were done ...
	return(retMask);
}

//------------------------------------------------------------------------------

void InteriorMap::unpackUpdate(NetConnection * con, BitStream * stream)
{
	// Unpack Parent.
	Parent::unpackUpdate(con, stream);

	// Read fxPortal Mask Flag.
	if (stream->readFlag())
	{
		MatrixF		ObjectMatrix;
      Point3F     scale;

		// Read Object Transform.
		stream->readAffineTransform(&ObjectMatrix);
		// Set Transform.
		setTransform(ObjectMatrix);

      // Read Object Scale
      mathRead(*stream, &scale);
      // Set Scale
      setScale(scale);

		// Reset the World Box.
		resetWorldBox();
		// Set the Render Transform.
		setRenderTransform(mObjToWorld);

       // Read the file name
      mFileName = stream->readSTString();
	}

   // New RenderMode?
   if (stream->readFlag())
      stream->read(&mRenderMode);
}

void InteriorMap::buildConvex(const Box3F& box, Convex* convex)
{
   Box3F realBox = box;
	mWorldToObj.mul(realBox);
	realBox.min.convolveInverse(mObjScale);
	realBox.max.convolveInverse(mObjScale);

   mConvexList->collectGarbage();

   for (U32 i = 0; i < mInteriorRes->mBrushes.size(); i++)
   {
      if (mInteriorRes->mBrushes[i]->mType == InteriorMapResource::Portal || mInteriorRes->mBrushes[i]->mType == InteriorMapResource::Trigger)
         continue;

      if (mInteriorRes->mBrushes[i]->mStatus == ConvexBrush::Deleted)
         continue;

      ConvexBrush* brush = mInteriorRes->mBrushes[i];

      if (realBox.isOverlapped(brush->mBounds))
      {
         // See if this brush exists in the working set already...
			Convex* cc = 0;
			CollisionWorkingList& wl = convex->getWorkingList();

			for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext)
         {
				if (itr->mConvex->getType() == InteriorMapConvexType &&
					(static_cast<InteriorMapConvex*>(itr->mConvex)->getObject() == this &&
                static_cast<InteriorMapConvex*>(itr->mConvex)->brushIndex  == i))
            {
				   cc = itr->mConvex;
				   break;
				}
			}

			if (!cc)
         {
			   // Got ourselves a new convex
			   InteriorMapConvex* cp = new InteriorMapConvex;
			   mConvexList->registerObject(cp);
			   convex->addToWorkingList(cp);
			   cp->mObject    = this;
			   cp->pOwner     = this;
            cp->brushIndex = i;
			   cp->box        = brush->mBounds;
         }
      }
   }
}

bool InteriorMap::castRay(const Point3F& s, const Point3F& e, RayInfo* info)
{
	// This assumes that the collision hulls are convex...otherwise it can return unpredictable results
	F32 outputFraction = 1.0f;
	VectorF outputNormal;

   // Loop through the collision hulls checking for the nearest ray collision
   // MDFFIX: Again I really should have the collision hulls sorted into a bsp
   for (U32 i = 0; i < mInteriorRes->mBrushes.size(); i++)
   {
      if (mInteriorRes->mBrushes[i]->mStatus == ConvexBrush::Deleted)
         continue;

      RayInfo test;

      if (mInteriorRes->mBrushes[i]->castRay(s, e, &test))
      {
         if (test.t < outputFraction)
         {
            outputFraction = test.t;
            outputNormal = test.normal;
         }
      }
   }

	// If we had a collision fill in the info structure and return
	if (outputFraction >= 0.0f && outputFraction < 1.0f)
	{
		info->t = outputFraction;
		info->normal = outputNormal;
		info->point.interpolate(s, e, outputFraction);
		info->face = -1;
		info->object = this;

		return true;
	}

	// Otherwise we didn't collide
	return false;
}

void InteriorMap::removeBrush(S32 brushIndex)
{
   if (brushIndex < 0 || brushIndex >= mInteriorRes->mBrushes.size())
      return;

   // Grab an iterator and point it at the beginning of the VectorPtr
   VectorPtr<ConvexBrush*>::iterator cbitr = mInteriorRes->mBrushes.begin();

   // Move the pointer up to our brush
   cbitr += brushIndex;

   // And remove it
   delete *cbitr;
   mInteriorRes->mBrushes.erase(cbitr);
}

S32 InteriorMap::getBrushIndex(U32 brushID)
{
   for (U32 i = 0; i < mInteriorRes->mBrushes.size(); i++)
   {
      if (mInteriorRes->mBrushes[i]->mID == brushID)
         return i;
   }

   return -1;
}

//****************************************************************************
// Map Texture Management
//****************************************************************************

// Returns the number of textures used in the map
S32 InteriorMap::getTextureCount()
{
   if(mInteriorRes)
      return mInteriorRes->mMaterials.size();

   return 0;
}

// Returns the name of the texture given the texture's index
const char* InteriorMap::getTextureName(S32 index)
{
   S32 count = getTextureCount();
   if(index >= 0 && index < count)
   {
      if(mInteriorRes)
         return ((const char*) mInteriorRes->getTextureName(index));
   }

   return "";
}

// Returns the texture for the map
const char* InteriorMap::getTexturePathway()
{
   return ((const char*) mTexPath);
}

// Returns the requested texture's dimensions
Point2I InteriorMap::getTextureSize(S32 index)
{
   S32 count = getTextureCount();
   if(index >= 0 && index < count)
   {
      if(mTexHandles[index])
         return Point2I(mTexHandles[index].getWidth(), mTexHandles[index].getHeight());
   }

   return Point2I(0,0);
}
