//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "ts/tsShapeInstance.h"
#include "dgl/dgl.h"
#include "ts/tsLastDetail.h"
#include "console/consoleTypes.h"
#include "ts/tsDecal.h"
#include "platform/profiler.h"
#include "core/frameAllocator.h"

TSShapeInstance::RenderData   TSShapeInstance::smRenderData;
MatrixF *                     TSShapeInstance::ObjectInstance::smTransforms = NULL;
S32                           TSShapeInstance::smMaxSnapshotScale = 2;
bool                          TSShapeInstance::smNoRenderTranslucent = false;
bool                          TSShapeInstance::smNoRenderNonTranslucent = false;
F32                           TSShapeInstance::smDetailAdjust = 1.0f;
F32                           TSShapeInstance::smScreenError = 5.0f;
bool                          TSShapeInstance::smFogExemptionOn = false;
S32                           TSShapeInstance::smNumSkipRenderDetails = 0;
bool                          TSShapeInstance::smSkipFirstFog = false;
bool                          TSShapeInstance::smSkipFog = false;

Vector<QuatF>                 TSShapeInstance::smNodeCurrentRotations(__FILE__, __LINE__);
Vector<Point3F>               TSShapeInstance::smNodeCurrentTranslations(__FILE__, __LINE__);
Vector<F32>                   TSShapeInstance::smNodeCurrentUniformScales(__FILE__, __LINE__);
Vector<Point3F>               TSShapeInstance::smNodeCurrentAlignedScales(__FILE__, __LINE__);
Vector<TSScale>               TSShapeInstance::smNodeCurrentArbitraryScales(__FILE__, __LINE__);

Vector<TSThread*>             TSShapeInstance::smRotationThreads(__FILE__, __LINE__);
Vector<TSThread*>             TSShapeInstance::smTranslationThreads(__FILE__, __LINE__);
Vector<TSThread*>             TSShapeInstance::smScaleThreads(__FILE__, __LINE__);

namespace {

void tsShapeTextureEventCB(const U32 eventCode, void *userData)
{
   TSShape* pShape = reinterpret_cast<TSShape*>(userData);

   if (eventCode == TextureManager::BeginZombification &&
      pShape->mVertexBuffer != -1)
   {
      // ideally we would de-register the callback here, but that would screw up the loop
      if (dglDoesSupportVertexBuffer())
         glFreeVertexBufferEXT(pShape->mVertexBuffer);
      else
         AssertFatal(false,"Vertex buffer should have already been freed!");
      pShape->mVertexBuffer = -1;
      for (S32 i = 0; i < pShape->objects.size(); ++i)
         pShape->mPreviousMerge[i] = -1;
   }
}
}

//-------------------------------------------------------------------------------------
// constructors, destructors, initialization
//-------------------------------------------------------------------------------------

TSShapeInstance::TSShapeInstance(const Resource<TSShape> & shape, bool loadMaterials)
{
   VECTOR_SET_ASSOCIATION(mMeshObjects);
   VECTOR_SET_ASSOCIATION(mDecalObjects);
   VECTOR_SET_ASSOCIATION(mIflMaterialInstances);
   VECTOR_SET_ASSOCIATION(mNodeTransforms);
   VECTOR_SET_ASSOCIATION(mNodeReferenceRotations);
   VECTOR_SET_ASSOCIATION(mNodeReferenceTranslations);
   VECTOR_SET_ASSOCIATION(mNodeReferenceUniformScales);
   VECTOR_SET_ASSOCIATION(mNodeReferenceScaleFactors);
   VECTOR_SET_ASSOCIATION(mNodeReferenceArbitraryScaleRots);
   VECTOR_SET_ASSOCIATION(mThreadList);
   VECTOR_SET_ASSOCIATION(mTransitionThreads);

   hShape = shape;
   mShape = hShape;
   buildInstanceData(mShape, loadMaterials);
}

TSShapeInstance::TSShapeInstance(TSShape * _shape, bool loadMaterials)
{
   VECTOR_SET_ASSOCIATION(mMeshObjects);
   VECTOR_SET_ASSOCIATION(mDecalObjects);
   VECTOR_SET_ASSOCIATION(mIflMaterialInstances);
   VECTOR_SET_ASSOCIATION(mNodeTransforms);
   VECTOR_SET_ASSOCIATION(mNodeReferenceRotations);
   VECTOR_SET_ASSOCIATION(mNodeReferenceTranslations);
   VECTOR_SET_ASSOCIATION(mNodeReferenceUniformScales);
   VECTOR_SET_ASSOCIATION(mNodeReferenceScaleFactors);
   VECTOR_SET_ASSOCIATION(mNodeReferenceArbitraryScaleRots);
   VECTOR_SET_ASSOCIATION(mThreadList);
   VECTOR_SET_ASSOCIATION(mTransitionThreads);

   mShape = _shape;
   buildInstanceData(mShape, loadMaterials);
}

TSShapeInstance::~TSShapeInstance()
{
   S32 i;
   for (i=0; i<mMeshObjects.size(); i++)
      destructInPlace(&mMeshObjects[i]);

   for (i=0; i<mDecalObjects.size(); i++)
      destructInPlace(&mDecalObjects[i]);

   while (mThreadList.size())
      destroyThread(mThreadList.last());

   setMaterialList(NULL);

   delete [] mDirtyFlags;
}

void TSShapeInstance::init()
{
   smRenderData.fogTexture = false;
   smRenderData.fogBitmap = NULL;
   smRenderData.fogHandle = NULL;
   smRenderData.fogMapHandle = NULL;
   smRenderData.renderDecals = true;

   Con::addVariable("$pref::TS::fogTexture",    TypeBool, &smRenderData.fogTexture);
   Con::addVariable("$pref::TS::detailAdjust",  TypeF32,  &smDetailAdjust);
   Con::addVariable("$pref::TS::skipLoadDLs",   TypeS32,  &TSShape::smNumSkipLoadDetails);
   Con::addVariable("$pref::TS::skipRenderDLs", TypeS32,  &smNumSkipRenderDetails);
   Con::addVariable("$pref::TS::skipFirstFog",  TypeBool, &smSkipFirstFog);
   Con::addVariable("$pref::TS::screenError",   TypeF32,  &smScreenError);
   Con::addVariable("$pref::TS::UseTriangles",  TypeBool, &TSMesh::smUseTriangles);
}

void TSShapeInstance::destroy()
{
   delete smRenderData.fogHandle;
}

void TSShapeInstance::buildInstanceData(TSShape * _shape, bool loadMaterials)
{
   S32 i,dl;

   mShape = _shape;

   debrisRefCount = 0;

   mEnvironmentMapOn = false;
   mEnvironmentMapAlpha = 0.f;
   mAllowTwoPassEnvironmentMap = false;
   mAlphaIsReflectanceMap = true; // turn this off below if we find an exception
   mAllowTwoPassDetailMap = true;

   mMaxEnvironmentMapDL = 1;            // for shapes < version 23

   mMaxDetailMapDL = 0;

   // clear callback function and data
   mCallback = NULL;
   mCallbackData = 0;

   mCurrentDetailLevel = 0;
   mCurrentIntraDetailLevel = 1.0f;

   // all triggers off at start
   mTriggerStates = 0;

   //
   mAlphaAlways = false;
   mAlphaAlwaysValue = 1.0f;

   mBalloonShape = false;
   mBalloonValue = 1.0f;

   mUseOverrideTexture = false;

   // if never set, never draw fog -- do this here just in case
   smRenderData.fogOn = false;

   // material list...
   mMaterialList = NULL;
   mOwnMaterialList = false;

   //
   mData = 0;
   mScaleCurrentlyAnimated = false;

   if(loadMaterials)
   {
      setMaterialList(mShape->materialList);
   }

   // set up node data
   S32 numNodes = mShape->nodes.size();
   mNodeTransforms.setSize(numNodes);

   // add objects to trees
   S32 numObjects = mShape->objects.size();
   mMeshObjects.setSize(numObjects);
   for (i=0; i<numObjects; i++)
   {
      const TSObject * obj = &mShape->objects[i];
      MeshObjectInstance * objInst = &mMeshObjects[i];

      // call objInst constructor
      constructInPlace(objInst);

      // hook up the object to it's node
      objInst->nodeIndex = obj->nodeIndex;

      // set up list of meshes
      if (obj->numMeshes)
         objInst->meshList = &mShape->meshes[obj->startMeshIndex];
      else
         objInst->meshList = NULL;

      objInst->object = obj;
   }

   // set up decal objects
   mDecalObjects.setSize(mShape->decals.size());
   for (i=0; i<mShape->decals.size(); i++)
   {
      const TSShape::Decal * decal = &mShape->decals[i];
      DecalObjectInstance * decalInst = &mDecalObjects[i];

      // call constructor
      constructInPlace(decalInst);
      decalInst->decalObject = decal;

      // hook up to node
      decalInst->targetObject = &mMeshObjects[decal->objectIndex];
      decalInst->nodeIndex = decalInst->targetObject->nodeIndex;

      // set up list of decal meshes
      if (decal->numMeshes)
      {
         decalInst->decalList = (TSDecalMesh**)&mShape->meshes[decal->startMeshIndex];
         for (S32 j=0; j<decal->numMeshes; j++)
            if (decalInst->getDecalMesh(j))
            {
               // point the decal mesh at it's target...
               // this is safe since meshes aren't shared between shapes
               TSDecalMesh * decalMesh = const_cast<TSDecalMesh*>(decalInst->getDecalMesh(j));
               decalMesh->targetMesh = decalInst->targetObject->getMesh(j);
               if (!decalMesh->targetMesh)
               {
                  // detecting this a little late, but we don't need this decal since it isn't doing anything
                  // should only happen on shapes exported before dtsexp 1.18
                  delete decalMesh;
                  TSDecalMesh ** dm = const_cast<TSDecalMesh**>(decalInst->decalList+j);
                  *dm = NULL;
               }
            }
      }
      else
         decalInst->decalList = NULL;
      decalInst->frame = mShape->decalStates[i].frameIndex;
   }

   // construct ifl material objects
   if(loadMaterials)
   {
      for (i=0; i<mShape->iflMaterials.size(); i++)
      {
         mIflMaterialInstances.increment();
         mIflMaterialInstances.last().iflMaterial = &mShape->iflMaterials[i];
         mIflMaterialInstances.last().frame = -1;
      }
   }

   // check to see which dl's have detail texturing
   mMaxDetailMapDL = -1;
   if(loadMaterials)
   {
      for (dl=0; dl<mShape->details.size(); dl++)
      {
         // check meshes on this detail level...
         S32 ss = mShape->details[dl].subShapeNum;
         S32 od = mShape->details[dl].objectDetailNum;
         if (ss<0)
            continue; // this is a billboard detail level
         S32 start = mShape->subShapeFirstObject[ss];
         S32 end = mShape->subShapeNumObjects[ss] + start;
         for (i=start; i<end; i++)
         {
            TSMesh * mesh = mMeshObjects[i].getMesh(od);
            if (!mesh)
               continue;
            for (S32 j=0; j<mesh->primitives.size(); j++)
            {
               if (mesh->primitives[j].matIndex & TSDrawPrimitive::NoMaterial)
                  continue;
               if (mMaterialList->getDetailMap(mesh->primitives[j].matIndex & TSDrawPrimitive::MaterialMask))
               {
                  mesh->setFlags(TSMesh::HasDetailTexture);
                  if (dl>mMaxDetailMapDL)
                     mMaxDetailMapDL = dl;
               }
            }
         }
      }
   }

   // set up subtree data
   S32 ss = mShape->subShapeFirstNode.size(); // we have this many subtrees
   mDirtyFlags = new U32[ss];

   mGroundThread = NULL;
   mCurrentDetailLevel = 0;

   animateSubtrees();

   // Construct billboards if not done already
   if(loadMaterials)
      ((TSShape *) mShape)->setupBillboardDetails(this);

   // Scan out the collision hulls...
   for (U32 i = 0; i < 16; i++)
   {
      char buff[128];
      dSprintf(buff, sizeof(buff), "Collision-%d", i + 1);
      S32 colDetail = mShape->findDetail(buff);
      if (colDetail != -1)
      {
         S32 dl = colDetail;

         // get subshape and object detail
         const TSDetail * detail = &mShape->details[dl];
         S32 ss = detail->subShapeNum;
         S32 od = detail->objectDetailNum;

         S32 start = mShape->subShapeFirstObject[ss];
         S32 end   = mShape->subShapeNumObjects[ss] + start;
         if (start<end)
         {
            // run through objects and validate names...
            for (S32 i=start; i<end; i++)
            {
               MeshObjectInstance * mesh = &mMeshObjects[i];

               if (od >= mesh->object->numMeshes)
                  continue;

               // Yell at them if they named something!
               if (dStrnicmp(mShape->names[mesh->object->nameIndex], "Col", 3) != 0 && dStrnicmp(mShape->names[mesh->object->nameIndex], "LOSCol", 3) != 0)
               {
                  Con::errorf("%s.dts - Collision mesh names should start with Col or LOSCol, encountered '%s' in detail level %d", mShape->mSourceResource->path,mShape->names[mesh->object->nameIndex], dl);
                  continue;
               }
            }
         }

      }
   }
}

void TSShapeInstance::setMaterialList(TSMaterialList * ml)
{
   // get rid of old list
   if (mOwnMaterialList)
      delete mMaterialList;
   mMaterialList = ml;
   mOwnMaterialList = false;

   if (mMaterialList && StringTable) // material lists need the string table to load...
   {
      // read ifl materials if necessary -- this is here rather than in shape because we can't open 2 files at once :(
      if (mShape->materialList == mMaterialList)
         ((TSShape*)mShape)->readIflMaterials(hShape.getFilePath());

      mMaterialList->load(MeshTexture,hShape.getFilePath(),true);

      // check for reflectance map not in alpha of texture -- will require more work to emap
      for (U32 i=0; i<mMaterialList->getMaterialCount(); i++)
      {
         if (mMaterialList->getFlags(i) & (TSMaterialList::AuxiliaryMap|TSMaterialList::NeverEnvMap))
            continue;
         if (!mMaterialList->reflectionInAlpha(i))
         {
            mAlphaIsReflectanceMap = false;
            break; // found our exception
         }
      }
   }
}

void TSShapeInstance::cloneMaterialList()
{
   if (mOwnMaterialList)
      return;
   mMaterialList = new TSMaterialList(mMaterialList);
   mOwnMaterialList = true;
}

static bool makeSkinPath(char* buffer, U32 bufferLength, const char* resourcePath,
                         const char* oldSkin, const char* oldRoot, const char* newRoot)
{
   bool replacedRoot = true;

   dsize_t oldRootLen = 0;
   char* rootStart = NULL;

   if (oldRoot == NULL) {
      // Not doing any replacing.
      replacedRoot = false;
   }
   else {
      // See if original name has the old root in it.
      oldRootLen = dStrlen(oldRoot);
      AssertFatal((oldRootLen + 1) < bufferLength, "makeSkinPath: Error, skin root name too long");
      dStrcpy(buffer, oldRoot);
      dStrcat(buffer, ".");
      rootStart = dStrstr(oldSkin, buffer);
      if (rootStart == NULL) {
         replacedRoot = false;
      }
   }

   // Find out how long the total pathname will be.
   const dsize_t oldLen = dStrlen(oldSkin);
   dsize_t pathLen = 0;
   if (resourcePath != NULL) {
      pathLen = dStrlen(resourcePath);
   }
   if (replacedRoot) {
      const dsize_t newRootLen = dStrlen(newRoot);
      AssertFatal((pathLen + 1 + oldLen + newRootLen - oldRootLen) < bufferLength, "makeSkinPath: Error, pathname too long");
   }
   else {
      AssertFatal((pathLen + 1 + oldLen) < bufferLength, "makeSkinPath: Error, pathname too long");
   }

   // OK, now make the pathname.

   // Start with the resource path:
   if (resourcePath != NULL) {
      dStrcpy(buffer, resourcePath);
      dStrcat(buffer, "/");
   }
   else {
      buffer[0] = '\0';
   }
   if (replacedRoot) {
      // Then the pre-root part of the old name:
      dsize_t rootStartPos = rootStart - oldSkin;
      if (rootStartPos != 0) {
         dStrncat(buffer, oldSkin, rootStartPos);
      }
      // Then the new root:
      dStrcat(buffer, newRoot);
      dStrcat(buffer, ".");
      // Then the post-root part of the old name:
      dStrcat(buffer, oldSkin + rootStartPos + oldRootLen + 1);
   }
   else {
      // Then the old name:
      dStrcat(buffer, oldSkin);
   }

   return replacedRoot;
}


void TSShapeInstance::reSkin(StringHandle& newBaseHandle)
{
#define NAME_BUFFER_LENGTH 256
   static char pathName[NAME_BUFFER_LENGTH];
   const char* defaultBaseName = "base";
   const char* newBaseName;

   if (newBaseHandle.isValidString()) {
      newBaseName = newBaseHandle.getString();
      if (newBaseName == NULL) {
         return;
      }
   }
   else {
      newBaseName = defaultBaseName;
   }

   // Make our own copy of the materials list from the resource
   // if necessary.
   if (ownMaterialList() == false) {
      cloneMaterialList();
   }

   const char* resourcePath = hShape.getFilePath();

   // Cycle through the materials.
   TSMaterialList* pMatList = getMaterialList();
   for (S32 j = 0; j < pMatList->mMaterialNames.size(); j++) {
      // Get the name of this material.
      const char* pName = pMatList->mMaterialNames[j];
      // Bail if no name.
      if (pName == NULL) {
         continue;
      }
      // Make a texture file pathname with the new root if this name
      // has the old root in it; otherwise just make a path with the
      // original name.
      bool replacedRoot = makeSkinPath(pathName, NAME_BUFFER_LENGTH, resourcePath,
                          pName, defaultBaseName, newBaseName);

      if (!replacedRoot) {
         // If this wasn't in the desired format, set the material's
         // texture handle (since that wasn't copied over in the
         // cloning) and continue.
         pMatList->mMaterials[j] = TextureHandle(pathName, MeshTexture, false);
         continue;
      }

      // OK, it is a skin texture.  Get the handle.
      TextureHandle skinHandle = TextureHandle(pathName, MeshTexture, false);
      // Do a sanity check; if it fails, use the original skin instead.
      if (skinHandle.getGLName() != 0) {
         pMatList->mMaterials[j] = skinHandle;
      }
      else {
         makeSkinPath(pathName, NAME_BUFFER_LENGTH, resourcePath, pName, NULL, NULL);
         pMatList->mMaterials[j] = TextureHandle(pathName, MeshTexture, false);
      }
   }
}

//-------------------------------------------------------------------------------------
// Render & detail selection
//-------------------------------------------------------------------------------------
void TSShapeInstance::render(const Point3F * objectScale)
{
   if (mCurrentDetailLevel<0)
      return;
   PROFILE_START(TSShapeInstanceRender);
   dglSetRenderPrimType(3);

   // alphaIn:  we start to alpha-in next detail level when intraDL > 1-alphaIn-alphaOut
   //           (finishing when intraDL = 1-alphaOut)
   // alphaOut: start to alpha-out this detail level when intraDL > 1-alphaOut
   // NOTE:
   //   intraDL is at 1 when if shape were any closer to us we'd be at dl-1,
   //   intraDL is at 0 when if shape were any farther away we'd be at dl+1
   F32 alphaOut = mShape->alphaOut[mCurrentDetailLevel];
   F32 alphaIn  = mShape->alphaIn[mCurrentDetailLevel];
   F32 saveAA = mAlphaAlways ? mAlphaAlwaysValue : 1.0f;

   if (mCurrentIntraDetailLevel>alphaIn+alphaOut)
      render(mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale);
   else if (mCurrentIntraDetailLevel>alphaOut)
   {
      // draw this detail level w/ alpha=1 and next detail level w/
      // alpha=1-(intraDl-alphaOut)/alphaIn

      // first draw next detail level
      if (mCurrentDetailLevel+1<mShape->details.size() && mShape->details[mCurrentDetailLevel+1].size>0.0f)
      {
         setAlphaAlways(saveAA * (alphaIn+alphaOut-mCurrentIntraDetailLevel)/alphaIn);
         render(mCurrentDetailLevel+1,0.0f,objectScale);
      }

      setAlphaAlways(saveAA);
      render(mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale);
   }
   else
   {
      // draw next detail level w/ alpha=1 and this detail level w/
      // alpha = 1-intraDL/alphaOut

      // first draw next detail level
      if (mCurrentDetailLevel+1<mShape->details.size() && mShape->details[mCurrentDetailLevel+1].size>0.0f)
         render(mCurrentDetailLevel+1,0.0f,objectScale);

      setAlphaAlways(saveAA * mCurrentIntraDetailLevel / alphaOut);
      render(mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale);
      setAlphaAlways(saveAA);
   }
   dglSetRenderPrimType(0);
   PROFILE_END();
}

bool TSShapeInstance::hasTranslucency()
{
   if(!mShape->details.size())
      return false;

   const TSDetail * detail = &mShape->details[0];
   S32 ss = detail->subShapeNum;

   return mShape->subShapeFirstTranslucentObject[ss] != mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];
}

bool TSShapeInstance::hasSolid()
{
   if(!mShape->details.size())
      return false;

   const TSDetail * detail = &mShape->details[0];
   S32 ss = detail->subShapeNum;

   return mShape->subShapeFirstTranslucentObject[ss] != mShape->subShapeFirstObject[ss];
}

void TSShapeInstance::render(S32 dl, F32 intraDL, const Point3F * objectScale)
{
   // if dl==-1, nothing to do
   if (dl==-1)
      return;

   AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::render");

   S32 i;

   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   S32 od = detail->objectDetailNum;

   // set up static data
   setStatics(dl,intraDL,objectScale);

   // if we're a billboard detail, draw it and exit
   PROFILE_START(TSShapeInstanceRenderBillboards);
   if (ss<0)
   {
      if (!smNoRenderTranslucent)
         mShape->billboardDetails[dl]->render(mAlphaAlways ? mAlphaAlwaysValue : 1.0f, smRenderData.fogOn);
      PROFILE_END();
      return;
   }
   PROFILE_END();

   PROFILE_START(TSShapeInstanceMaterials);
   // set up animating ifl materials
   for (i=0; i<mIflMaterialInstances.size(); i++)
   {
      IflMaterialInstance  * iflMaterialInstance = &mIflMaterialInstances[i];
      const TSShape::IflMaterial * iflMaterial = iflMaterialInstance->iflMaterial;
      mMaterialList->remap(iflMaterial->materialSlot, iflMaterial->firstFrame + iflMaterialInstance->frame);
   }

   // decide how to use gl resources
   setupTexturing(dl,intraDL);

   // set up gl environment for drawing mesh materials
   TSMesh::initMaterials();
   PROFILE_END();

   S32 start;
   S32 end;

   bool supportBuffers = dglDoesSupportVertexBuffer();
   if (!supportBuffers || !renderMeshesX(ss,od))
   {
      // run through the meshes
      smRenderData.currentTransform = NULL;
      S32 start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss];
      S32 end   = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];
      for (i=start; i<end; i++)
         mMeshObjects[i].render(od,mMaterialList);
   }

   // if we have a matrix pushed, pop it now
   if (smRenderData.currentTransform)
      glPopMatrix();

   // restore gl state
   TSMesh::resetMaterials();

   // render detail maps using a second pass?
   if (twoPassDetailMap())
      renderDetailMap();

   // render environment map using second pass?
   if (twoPassEnvironmentMap())
      renderEnvironmentMap();

   if (!supportBuffers || !renderDecalsX(ss,od))
   {
      start = mShape->subShapeFirstDecal[ss];
      end   = mShape->subShapeNumDecals[ss] + start;
      if( smRenderData.renderDecals && !smNoRenderTranslucent && start<end)
      {
         // set up gl environment for decals...
         TSDecalMesh::initDecalMaterials();

         // render decals...
         smRenderData.currentTransform = NULL;
         for (i=start; i<end; i++)
            mDecalObjects[i].render(od,mMaterialList);

         // if we have a matrix pushed, pop it now
         if (smRenderData.currentTransform)
            glPopMatrix();

         // restore gl state
         TSDecalMesh::resetDecalMaterials();
      }
   }

   // render fog if 2-passing it
   if (twoPassFog())
      renderFog();

   // restore gl state
   TSMesh::resetMaterials();

   clearStatics();
}

bool TSShapeInstance::fillVB()
{
   S32 i,start,end,vb;

   if ((vb = mShape->mVertexBuffer) == -1)
   {
      // find out before we calc the needed buffer size if there are any free
      if (!glAvailableVertexBufferEXT())
      {
         PROFILE_END();
         return false;
      }

      GLsizei size = 0;

      start = mShape->subShapeFirstObject[0];
      end = mShape->subShapeFirstObject[0] + mShape->subShapeNumObjects[0];
      for (i = start; i < end; ++i)
         size += mMeshObjects[i].getSizeVB(size);

      mShape->mMorphable = false;
      for (i = start; i < end; ++i)
         if (mMeshObjects[i].hasMergeIndices())
         {
            mShape->mMorphable = true;
            break;
         }

      vb = mShape->mVertexBuffer = glAllocateVertexBufferEXT(size,GL_V12MTNVFMT_EXT,true);
      if (vb == -1)
      {
         PROFILE_END();
         return false;
      }
      if (mShape->mCallbackKey == -1)
         mShape->mCallbackKey = TextureManager::registerEventCallback(tsShapeTextureEventCB, mShape);

      // run through the meshes -- fill vertex buffer
      glLockVertexBufferEXT(vb,0);
      for (i = start; i < end; ++i)
         mMeshObjects[i].fillVB(vb,mMaterialList);
      glUnlockVertexBufferEXT(vb);
   }

   return true;
}

bool TSShapeInstance::renderMeshesX(S32 ss, S32 od)
{
   // TODO: find out why this case doesn't work
   if (smRenderData.vertexAlpha.current < 1.0)
      return false;

   PROFILE_START(TSShapeInstanceMeshes);

   S32 i,start,end,vb;

   if ((vb = mShape->mVertexBuffer) == -1)
   {
      // find out before we calc the needed buffer size if there are any free
      if (!glAvailableVertexBufferEXT())
      {
         PROFILE_END();
         return false;
      }

      GLsizei size = 0;

      start = mShape->subShapeFirstObject[0];
      end = mShape->subShapeFirstObject[0] + mShape->subShapeNumObjects[0];
      for (i = start; i < end; ++i)
         size += mMeshObjects[i].getSizeVB(size);

      mShape->mMorphable = false;
      for (i = start; i < end; ++i)
         if (mMeshObjects[i].hasMergeIndices())
         {
            mShape->mMorphable = true;
            break;
         }

      vb = mShape->mVertexBuffer = glAllocateVertexBufferEXT(size,GL_V12MTNVFMT_EXT,true);
      if (vb == -1)
      {
         PROFILE_END();
         return false;
      }
      if (mShape->mCallbackKey == -1)
         mShape->mCallbackKey = TextureManager::registerEventCallback(tsShapeTextureEventCB, mShape);

      // run through the meshes -- fill vertex buffer
      glLockVertexBufferEXT(vb,0);
      for (i = start; i < end; ++i)
         mMeshObjects[i].fillVB(vb,mMaterialList);
      glUnlockVertexBufferEXT(vb);
   }

   // run through the meshes
   start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss];
   end   = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];

   if (mShape->mMorphable)
   {
      PROFILE_START(TSShapeInstanceMorphVB);
      glLockVertexBufferEXT(vb,0);
      for (i = start; i < end; ++i)
         mMeshObjects[i].morphVB(vb,mShape->mPreviousMerge[i],od,mMaterialList);
      glUnlockVertexBufferEXT(vb);
      PROFILE_END();
   }

   smRenderData.currentTransform = NULL;
   PROFILE_START(TSShapeInstanceRenderVB);
   for (i = start; i < end; ++i)
      mMeshObjects[i].renderVB(vb,od,mMaterialList);
   PROFILE_END();

   PROFILE_END();
   return true;
}

bool TSShapeInstance::renderDecalsX(S32 ss, S32 od)
{
   return false;

   ss,od;
// I don't know why, but this doesn't quite work -- no time to fix
#if 0
   if (supportBuffers)
   {
      S32 i,start,end,vb;
      vb = mShape->mVertexBuffer;

      start = mShape->subShapeFirstDecal[ss];
      end   = mShape->subShapeNumDecals[ss] + start;
      if (smRenderData.renderDecals && start<end)
      {
         // set up gl environment for decals...
         TSDecalMesh::initDecalMaterials();

         // render decals...
         smRenderData.currentTransform = NULL;
         for (i=start; i<end; i++)
         {
            TSDecalMesh *decal0 = mDecalObjects[i].getDecalMesh(0);

            if (!decal0)
               continue;

            TSMesh *target0 = decal0->targetMesh;
            TSDecalMesh *decal;


            if (!target0 ||
                mDecalObjects[i].targetObject->visible <= 0.01f ||
                !(decal = mDecalObjects[i].getDecalMesh(od)) ||
                mDecalObjects[i].frame < 0 ||
                !decal->targetMesh ||
                decal->texgenS.empty() ||
                decal->texgenT.empty())
               continue;

            GLuint foffset = mDecalObjects[i].frame*target0->numMatFrames*target0->vertsPerFrame;

            glSetVertexBufferEXT(vb);
            glOffsetVertexBufferEXT(vb,target0->vbOffset+foffset);
            mDecalObjects[i].render(od,mMaterialList);
         }

         // if we have a matrix pushed, pop it now
         if (smRenderData.currentTransform)
            glPopMatrix();

         // restore gl state
         TSDecalMesh::resetDecalMaterials();
      }
   }
   else
#endif
}

void TSShapeInstance::setStatics(S32 dl, F32 intraDL, const Point3F * objectScale)
{
   ObjectInstance::smTransforms = mNodeTransforms.address();
   smRenderData.objectScale = objectScale;
   smRenderData.detailLevel = dl;
   smRenderData.intraDetailLevel = intraDL;
   smRenderData.alwaysAlpha = mAlphaAlways;
   smRenderData.alwaysAlphaValue = getAlphaAlwaysValue();
   smRenderData.balloonShape = mBalloonShape;
   smRenderData.balloonValue = getBalloonValue();

   smRenderData.useOverride = mUseOverrideTexture;
   smRenderData.override    = mOverrideTexture;

   S32 ss = mShape->details[dl].subShapeNum;
   S32 od = mShape->details[dl].objectDetailNum;
   TSMesh::smSaveVerts.setSize(mShape->mMergeBufferSize);
   TSMesh::smSaveTVerts.setSize(mShape->mMergeBufferSize);

   // If we have a billboard, skip the rest
   if (ss < 0)
      return;

   S32 start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss];
   S32 end   = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];

   for (S32 i=start; i<end; i++)
   {
      TSMesh * mesh = mMeshObjects[i].getMesh(od);
      if (mesh)
         mesh->saveMergeVerts();
   }
}

void TSShapeInstance::clearStatics()
{
   ObjectInstance::smTransforms = NULL;
   smRenderData.override = NULL;

   S32 ss = mShape->details[smRenderData.detailLevel].subShapeNum;
   S32 od = mShape->details[smRenderData.detailLevel].objectDetailNum;
   S32 start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss];
   S32 end   = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];
   for (S32 i=start; i<end; i++)
   {
      TSMesh * mesh = mMeshObjects[i].getMesh(od);
      if (mesh)
         mesh->restoreMergeVerts();
   }
}

void TSShapeInstance::setupTexturing(S32 dl, F32 intraDL)
{
   // first we'll decide what maps we want
   // then we'll decide how we can implement them (1-pass or 2-pass or not at all)

   // we need to set up these variables
   S32 & emapMethod = smRenderData.environmentMapMethod;
   S32 & dmapMethod = smRenderData.detailMapMethod;
   S32 & fogMethod  = smRenderData.fogMethod;
   S32 & dmapTE     = smRenderData.detailMapTE;
   S32 & emapTE     = smRenderData.environmentMapTE;
   S32 & baseTE     = smRenderData.baseTE;
   S32 & fogTE      = smRenderData.fogTE;

   baseTE = 0; // initially assume base texture will go in first TE

   // -------------------------------------------------
   // what do we want to do?

   bool wantEMap = ((mShape->mExportMerge && dl<=mShape->mSmallestVisibleDL/2) ||
                    (!mShape->mExportMerge && dl<=mMaxEnvironmentMapDL)) &&
                   mEnvironmentMapOn && (TextureObject*)mEnvironmentMap && mEnvironmentMapAlpha>0.01f;
   bool wantDMap = dl<=mMaxDetailMapDL;
   bool wantFog  = smRenderData.fogOn && !smSkipFog;
   smRenderData.detailMapAlpha = (dl<mMaxDetailMapDL || intraDL>0.5f) ? 1.0f : 2.0f * intraDL;
   smRenderData.environmentMapAlpha = mEnvironmentMapAlpha *
                                      (
                                        (((mShape->mExportMerge && dl<mShape->mSmallestVisibleDL/2) ||
                                            (!mShape->mExportMerge && dl<mMaxEnvironmentMapDL)) ||
                                           intraDL>0.5f) ? 1.0f : 2.0f * intraDL );
   smRenderData.environmentMapGLName = wantEMap ? mEnvironmentMap.getGLName() : 0;

   // -------------------------------------------------
   // what can we do?

   if (!dglDoesSupportARBMultitexture())
   {
      // we don't support multitexturing -- early out
      emapMethod = NO_ENVIRONMENT_MAP;
      dmapMethod = (wantDMap && mAllowTwoPassDetailMap) ? DETAIL_MAP_TWO_PASS : NO_DETAIL_MAP;
      fogMethod  = wantFog ? FOG_TWO_PASS : NO_FOG;

      return;
   }

   // how many texture environments (TE's) do we have?
   GLint numTE = 1, numUsedTE = 1;
   if (dglDoesSupportARBMultitexture())
      glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&numTE);

   // what we do with the TE's will depend on whether the following extension is supported
   if (dglDoesSupportTextureEnvCombine())
   {
      // environment map...
      if (wantEMap)
      {
         if (mAlphaIsReflectanceMap)
         {
            emapMethod = ENVIRONMENT_MAP_MULTI_1;
            emapTE = numUsedTE;
            numUsedTE++;
         }
         else if (numUsedTE+3<=numTE)
         {
            emapMethod = ENVIRONMENT_MAP_MULTI_3;
            emapTE = numUsedTE;
            numUsedTE += 3;
         }
         else if (mAllowTwoPassEnvironmentMap)
            emapMethod = ENVIRONMENT_MAP_TWO_PASS;
         else
            emapMethod = NO_ENVIRONMENT_MAP;
      }
      else
         emapMethod = NO_ENVIRONMENT_MAP;

      // detail map...
      if (wantDMap)
      {
         if (smRenderData.detailMapAlpha>0.99f && numTE>=numUsedTE+1)
         {
            dmapMethod = DETAIL_MAP_MULTI_1;
            dmapTE = numUsedTE;
            numUsedTE++;
         }
         else if (smRenderData.detailMapAlpha<=0.9f && numTE>=numUsedTE+2)
         {
            dmapMethod = DETAIL_MAP_MULTI_2;
            dmapTE = 0;     // detail texture goes in first unit...
            baseTE++;       // so we bump this back one...
            emapTE++;       // this one gets bumped back 2...
            numUsedTE += 2; // end up using two additional units..
         }
         else
            dmapMethod = mAllowTwoPassDetailMap ? DETAIL_MAP_TWO_PASS : NO_DETAIL_MAP;
      }
      else
         dmapMethod = NO_DETAIL_MAP;

      // fog...
      if (wantFog)
      {
         // DMMUNDO!
         if (numTE>=numUsedTE+1 && emapMethod!=ENVIRONMENT_MAP_TWO_PASS)
         {
            fogMethod = smRenderData.fogMapHandle ? FOG_MULTI_1_TEXGEN : FOG_MULTI_1;
            fogTE = numUsedTE;
            numUsedTE++;
         }
         else
            fogMethod = smRenderData.fogMapHandle ? FOG_TWO_PASS_TEXGEN : FOG_TWO_PASS;
      }
      else
         fogMethod = NO_FOG;
   }
   else
   {
      // we can't single pass environment map without texture combine extension...
      wantEMap = wantEMap && mAllowTwoPassEnvironmentMap;
      emapMethod = wantEMap ? ENVIRONMENT_MAP_TWO_PASS : NO_ENVIRONMENT_MAP;
      // ditto for detail map...
      wantDMap = wantDMap && mAllowTwoPassDetailMap;
      dmapMethod = wantDMap ? DETAIL_MAP_TWO_PASS : NO_DETAIL_MAP;
      fogMethod = wantFog ? FOG_TWO_PASS : NO_FOG;
   }

   if (emapMethod == NO_ENVIRONMENT_MAP)
      smRenderData.environmentMapAlpha = 1.0f;
}

void TSShapeInstance::setupFog(F32 fogAmount, const ColorF & fogColor)
{
   smRenderData.fogOn = (fogAmount > 1.0 / 64.0f);
   smRenderData.fogMapHandle = NULL;

   bool refresh = false;

   if (!smRenderData.fogBitmap)
   {
      smRenderData.fogBitmap = new GBitmap(8,8,false,GBitmap::RGBA);

      // clear the bitmap (defaults to 0xff) so if fogColor is 0,0,0
      // we will have a valid bitmap
      dMemset(smRenderData.fogBitmap->getWritableBits(), 0, 256);
   }

   if (smRenderData.fogColor.x != fogColor.red ||
       smRenderData.fogColor.y != fogColor.green ||
       smRenderData.fogColor.z != fogColor.blue)
   {
      U8 *bits = smRenderData.fogBitmap->getWritableBits();
      U8 red = U8(255*fogColor.red);
      U8 green = U8(255*fogColor.green);
      U8 blue = U8(255*fogColor.blue);

      for (U8 i = 0; i < 64; ++i)
      {
         *bits++ = red;
         *bits++ = green;
         *bits++ = blue;
         bits++;
      }
      refresh = true;
   }

   // the ATI Rage 128 needs a forthcoming driver to do do constant alpha blend
   if (smRenderData.fogTexture)
   {
      if (smRenderData.fogColor.w != fogAmount)
      {
         U8 *bits = smRenderData.fogBitmap->getWritableBits();
         U8 fog = U8(255 * fogAmount);

         for (U8 i = 0; i < 64; ++i)
         {
            bits[3] = fog;
            bits += 4;
         }
         refresh = true;
      }
   }

   if (!smRenderData.fogHandle)
      smRenderData.fogHandle = new TextureHandle("fog_texture", smRenderData.fogBitmap);
   else
      if (refresh)
         smRenderData.fogHandle->refresh();

   smRenderData.fogColor.set(fogColor.red,fogColor.green,fogColor.blue,fogAmount);
}

void TSShapeInstance::setupFog(F32 fogAmount, TextureHandle * fogMap, Point4F & s, Point4F & t)
{
   smRenderData.fogColor.w = fogAmount;
   smRenderData.fogOn = true;
   smRenderData.fogMapHandle = fogMap;
   smRenderData.fogTexGenS = s;
   smRenderData.fogTexGenT = t;
}

bool TSShapeInstance::twoPassEnvironmentMap()
{
   return (smRenderData.environmentMapMethod==ENVIRONMENT_MAP_TWO_PASS);
}

bool TSShapeInstance::twoPassDetailMap()
{
   return (smRenderData.detailMapMethod==DETAIL_MAP_TWO_PASS);
}

bool TSShapeInstance::twoPassFog()
{
   return (smRenderData.fogMethod==FOG_TWO_PASS || smRenderData.fogMethod==FOG_TWO_PASS_TEXGEN);
}

void TSShapeInstance::renderEnvironmentMap()
{
   AssertFatal((void *)mEnvironmentMap!=NULL,"TSShapeInstance::renderEnvironmentMap (1)");
   AssertFatal(mEnvironmentMapOn,"TSShapeInstance::renderEnvironmentMap (2)");
   AssertFatal(dglDoesSupportARBMultitexture(),"TSShapeInstance::renderEnvironmentMap (3)");
   AssertFatal(smRenderData.environmentMapMethod==ENVIRONMENT_MAP_TWO_PASS,"TSShapeInstance::renderEnvironmentMap (4)");

   S32 dl = smRenderData.detailLevel;
   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   S32 od = detail->objectDetailNum;

   S32 start = mShape->subShapeFirstObject[ss];
   S32 end   = mShape->subShapeNumObjects[ss] + start;
   if (start>=end)
      return;

   // set up gl environment for emap...
   TSMesh::initEnvironmentMapMaterials();

   // run through objects and render
   smRenderData.currentTransform = NULL;
   for (S32 i=start; i<end; i++)
      mMeshObjects[i].renderEnvironmentMap(od,mMaterialList);

   // if we have a matrix pushed, pop it now
   if (smRenderData.currentTransform)
      glPopMatrix();

   // restore gl state
   TSMesh::resetEnvironmentMapMaterials();
}

void TSShapeInstance::renderFog()
{
   AssertFatal(smRenderData.fogMethod==FOG_TWO_PASS || smRenderData.fogMethod==FOG_TWO_PASS_TEXGEN,"TSShapeInstance::renderFog");

   S32 dl = smRenderData.detailLevel;
   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   S32 od = detail->objectDetailNum;

   GLboolean wasLit = glIsEnabled(GL_LIGHTING);
   glDisable(GL_LIGHTING);
   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glEnableClientState(GL_VERTEX_ARRAY);

   if (smRenderData.fogMethod==FOG_TWO_PASS_TEXGEN)
   {
      // set up fog map
      glEnable(GL_TEXTURE_2D);
      glBindTexture(GL_TEXTURE_2D, TSShapeInstance::smRenderData.fogMapHandle->getGLName());

      // set up texgen equations
      glTexGeni(GL_S,GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
      glTexGeni(GL_T,GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
      glEnable(GL_TEXTURE_GEN_S);
      glEnable(GL_TEXTURE_GEN_T);
      glTexGenfv(GL_S,GL_OBJECT_PLANE,&TSShapeInstance::smRenderData.fogTexGenS.x);
      glTexGenfv(GL_T,GL_OBJECT_PLANE,&TSShapeInstance::smRenderData.fogTexGenT.x);
   }
   else
   {
      // just one fog color per shape...
      glColor4fv(smRenderData.fogColor);
      // texture should be disabled already...
   }

   smRenderData.currentTransform = NULL;
   S32 start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss];
   S32 end   = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];
   for (S32 i=start; i<end; i++)
      mMeshObjects[i].renderFog(od, mMaterialList);

   // if we have a marix pushed, pop it now
   if (smRenderData.currentTransform)
      glPopMatrix();

   // reset gl state
   glDisable(GL_BLEND);
   glDisable(GL_TEXTURE_GEN_S);
   glDisable(GL_TEXTURE_GEN_T);
   glDisable(GL_TEXTURE_2D);
   glBlendFunc(GL_ONE, GL_ZERO);
   glDisableClientState(GL_VERTEX_ARRAY);
   if (wasLit)
      glEnable(GL_LIGHTING);
}

void TSShapeInstance::renderDetailMap()
{
   AssertFatal(smRenderData.detailMapMethod==DETAIL_MAP_TWO_PASS,"TSShapeInstance::renderDetailMap (1)");

   S32 dl = smRenderData.detailLevel;
   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   S32 od = detail->objectDetailNum;

   S32 start = mShape->subShapeFirstObject[ss];
   S32 end   = mShape->subShapeNumObjects[ss] + start;

   // set up gl environment for the detail map
   TSMesh::initDetailMapMaterials();

   // run through objects and render detail maps
   smRenderData.currentTransform = NULL;
   for (S32 i=start; i<end; i++)
      mMeshObjects[i].renderDetailMap(od,mMaterialList);

   // if we have a matrix pushed, pop it now
   if (smRenderData.currentTransform)
      glPopMatrix();

   // restore gl state
   TSMesh::resetDetailMapMaterials();
}

S32 TSShapeInstance::getCurrentDetail()
{
   return mCurrentDetailLevel;
}

F32 TSShapeInstance::getCurrentIntraDetail()
{
   return mCurrentIntraDetailLevel;
}

void TSShapeInstance::setCurrentDetail(S32 dl, F32 intraDL)
{
   mCurrentDetailLevel = dl;
   mCurrentIntraDetailLevel = intraDL>1.0f ? 1.0f : (intraDL<0.0f ? 0.0f : intraDL);

   // restrict chosen detail level by cutoff value
   S32 cutoff = getMin(smNumSkipRenderDetails,mShape->mSmallestVisibleDL);
   if (mCurrentDetailLevel>=0 && mCurrentDetailLevel<cutoff)
   {
      mCurrentDetailLevel = cutoff;
      mCurrentIntraDetailLevel = 1.0f;
   }
}

S32 TSShapeInstance::selectCurrentDetail(bool ignoreScale)
{
   if (mShape->mSmallestVisibleDL>=0 && mShape->details[0].maxError>=0)
      // use new scheme
      return selectCurrentDetailEx(ignoreScale);

   MatrixF toCam;
   Point3F p;
   dglGetModelview(&toCam);
   toCam.mulP(mShape->center,&p);
   F32 dist = mDot(p,p);
   F32 scale = 1.0f;
   if (!ignoreScale)
   {
      // any scale?
      Point3F x,y,z;
      toCam.getRow(0,&x);
      toCam.getRow(1,&y);
      toCam.getRow(2,&z);
      F32 scalex = mDot(x,x);
      F32 scaley = mDot(y,y);
      F32 scalez = mDot(z,z);
      scale = scalex;
      if (scaley > scale)
         scale = scaley;
      if (scalez > scale)
         scale = scalez;
   }
   dist /= scale;
   dist = mSqrt(dist);

   F32 pixelRadius = dglProjectRadius(dist,mShape->radius) * dglGetPixelScale() * smDetailAdjust;

   return selectCurrentDetail(pixelRadius);
}

S32 TSShapeInstance::selectCurrentDetailEx(bool ignoreScale)
{
   MatrixF toCam;
   Point3F p;
   dglGetModelview(&toCam);
   toCam.mulP(mShape->center,&p);
   F32 dist = mDot(p,p);
   F32 scale = 1.0f;
   if (!ignoreScale)
   {
      // any scale?
      Point3F x,y,z;
      toCam.getRow(0,&x);
      toCam.getRow(1,&y);
      toCam.getRow(2,&z);
      F32 scalex = mDot(x,x);
      F32 scaley = mDot(y,y);
      F32 scalez = mDot(z,z);
      scale = scalex;
      if (scaley > scale)
         scale = scaley;
      if (scalez > scale)
         scale = scalez;
   }
   dist /= scale;
   dist = mSqrt(dist);

   // find tolerance
   F32 proj = dglProjectRadius(dist,1.0f) * dglGetPixelScale(); // pixel size of 1 meter at given distance
   if ( smFogExemptionOn )
      return selectCurrentDetailEx(F32(0.001)/proj);
   else
      return selectCurrentDetailEx(smScreenError/proj);
}

S32 TSShapeInstance::selectCurrentDetail(Point3F offset, F32 invScale)
{
   F32 dist = mSqrt(mDot(offset,offset));
   dist *= invScale;
   return selectCurrentDetail2(dist);
}

S32 TSShapeInstance::selectCurrentDetail2(F32 adjustedDist)
{
   if (mShape->mSmallestVisibleDL>=0 && mShape->details[0].maxError>=0)
      // use new scheme
      return selectCurrentDetail2Ex(adjustedDist);

   F32 pixelRadius = dglProjectRadius(adjustedDist,mShape->radius) * dglGetPixelScale();
   F32 adjustedPR = pixelRadius * smDetailAdjust;
   if(adjustedPR <= mShape->mSmallestVisibleSize)
      adjustedPR = mShape->mSmallestVisibleSize + 0.01f;

   return selectCurrentDetail(adjustedPR);
}

S32 TSShapeInstance::selectCurrentDetail2Ex(F32 adjustedDist)
{
   // find tolerance
   F32 proj = dglProjectRadius(adjustedDist,1.0f) * dglGetPixelScale(); // pixel size of 1 meter at given distance
   if ( smFogExemptionOn )
      return selectCurrentDetailEx(F32(0.001)/proj);
   else
      return selectCurrentDetailEx(smScreenError/proj);
}

S32 TSShapeInstance::selectCurrentDetail(F32 size)
{
   // check to see if not visible first...
   if (size<=mShape->mSmallestVisibleSize)
   {
      // don't render...
      mCurrentDetailLevel=-1;
      mCurrentIntraDetailLevel = 0.0f;
      return -1;
   }

   // same detail level as last time?
   // only search for detail level if the current one isn't the right one already
   if ( mCurrentDetailLevel<0 ||
        (mCurrentDetailLevel==0 && size<=mShape->details[0].size) ||
        (mCurrentDetailLevel>0  && (size<=mShape->details[mCurrentDetailLevel].size || size>mShape->details[mCurrentDetailLevel-1].size)))
   {
      // scan shape for highest detail size smaller than us...
      // shapes details are sorted from largest to smallest...
      // a detail of size <= 0 means it isn't a renderable detail level (utility detail)
      for (S32 i=0; i<mShape->details.size(); i++)
      {
         if (size>mShape->details[i].size)
         {
            mCurrentDetailLevel = i;
            break;
         }
         if (i+1>=mShape->details.size() || mShape->details[i+1].size<0)
         {
            // We've run out of details and haven't found anything?
            // Let's just grab this one.
            mCurrentDetailLevel = i;
            break;
         }
      }
   }

   F32 curSize = mShape->details[mCurrentDetailLevel].size;
   F32 nextSize = mCurrentDetailLevel==0 ? 2.0f * curSize : mShape->details[mCurrentDetailLevel-1].size;
   mCurrentIntraDetailLevel = nextSize-curSize>0.01f ? (size-curSize) / (nextSize-curSize) : 1.0f;
   mCurrentIntraDetailLevel = mCurrentIntraDetailLevel>1.0f ? 1.0f : (mCurrentIntraDetailLevel<0.0f ? 0.0f : mCurrentIntraDetailLevel);

   // now restrict chosen detail level by cutoff value
   S32 cutoff = getMin(smNumSkipRenderDetails,mShape->mSmallestVisibleDL);
   if (mCurrentDetailLevel>=0 && mCurrentDetailLevel<cutoff)
   {
      mCurrentDetailLevel = cutoff;
      mCurrentIntraDetailLevel = 1.0f;
   }

   return mCurrentDetailLevel;
}

S32 TSShapeInstance::selectCurrentDetailEx(F32 errorTOL)
{
   // note:  we use 10 time the average error as the metric...this is
   // more robust than the maxError...the factor of 10 is to put average error
   // on about the same scale as maxError.  The errorTOL is how much
   // error we are able to tolerate before going to a more detailed version of the
   // shape.  We look for a pair of details with errors bounding our errorTOL,
   // and then we select an interpolation parameter to tween betwen them.  Ok, so
   // this isn't exactly an error tolerance.  A tween value of 0 is the lower poly
   // model (higher detail number) and a value of 1 is the higher poly model (lower
   // detail number).

   // deal with degenerate case first...
   // if smallest detail corresponds to less than half tolerable error, then don't even draw
   F32 prevErr;
   if (mShape->mSmallestVisibleDL<0)
      prevErr=0.0f;
   else
      prevErr = 10.0f * mShape->details[mShape->mSmallestVisibleDL].averageError * 20.0f;
   if (mShape->mSmallestVisibleDL<0 || prevErr<errorTOL)
   {
      // draw last detail
      mCurrentDetailLevel=mShape->mSmallestVisibleDL;
      mCurrentIntraDetailLevel = 0.0f;
      return mCurrentDetailLevel;
   }

   // this function is a little odd
   // the reason is that the detail numbers correspond to
   // when we stop using a given detail level...
   // we search the details from most error to least error
   // until we fit under the tolerance (errorTOL) and then
   // we use the next highest detail (higher error)
   for (S32 i=mShape->mSmallestVisibleDL; i>=0; i--)
   {
      F32 err0 = 10.0f * mShape->details[i].averageError;
      if (err0 < errorTOL)
      {
         // ok, stop here

         // intraDL = 1 corresponds to fully this detail
         // intraDL = 0 corresponds to the next lower (higher number) detail
         mCurrentDetailLevel = i;
         mCurrentIntraDetailLevel = 1.0f - (errorTOL-err0)/(prevErr-err0);
         return mCurrentDetailLevel;
      }
      prevErr=err0;
   }

   // get here if we are drawing at DL==0
   mCurrentDetailLevel = 1;
   mCurrentIntraDetailLevel = 1.0f;
   return mCurrentDetailLevel;

}

GBitmap * TSShapeInstance::snapshot(TSShape * shape, U32 width, U32 height, bool mip, MatrixF & cameraPos, S32 dl, F32 intraDL, bool hiQuality)
{
   TSShapeInstance * shapeInstance = new TSShapeInstance(shape, true);
   shapeInstance->setCurrentDetail(dl,intraDL);
   shapeInstance->animate();
   GBitmap * bmp = shapeInstance->snapshot(width,height,mip,cameraPos,hiQuality);

   delete shapeInstance;

   return bmp;
}

GBitmap * TSShapeInstance::snapshot(U32 width, U32 height, bool mip, MatrixF & cameraPos, S32 dl, F32 intraDL, bool hiQuality)
{
   setCurrentDetail(dl,intraDL);
   animate();
   return snapshot(width,height,mip,cameraPos,hiQuality);
}

GBitmap * TSShapeInstance::snapshot(U32 width, U32 height, bool mip, MatrixF & cameraMatrix,bool hiQuality)
{
   U32 screenWidth = Platform::getWindowSize().x;
   U32 screenHeight = Platform::getWindowSize().y;
   U32 xcenter = screenWidth >> 1;
   U32 ycenter = screenHeight >> 1;

   if (screenWidth==0 || screenHeight==0)
      return NULL; // probably in exporter...

   AssertFatal(width<screenWidth && height<screenHeight,"TSShapeInstance::snapshot: bitmap cannot be larger than screen resolution");

   S32 scale = 1;
   if (hiQuality)
      while ((scale<<1)*width <= screenWidth && (scale<<1)*height <= screenHeight)
         scale <<= 1;
   if (scale>smMaxSnapshotScale)
      scale = smMaxSnapshotScale;

   // height and width of intermediate bitmaps
   U32 bmpWidth  = width*scale;
   U32 bmpHeight = height*scale;

   Point4F saveClearColor;
   glGetFloatv(GL_COLOR_CLEAR_VALUE,(F32*)&saveClearColor);

   // setup viewport and frustrum (do orthographic projection)
   dglSetViewport(RectI(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight));
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   dglSetFrustum(-mShape->radius, mShape->radius, -mShape->radius, mShape->radius, 1, 20.0f * mShape->radius,true);

   // position camera...
   glMatrixMode(GL_MODELVIEW);
   Point3F y;
   cameraMatrix.getColumn(1,&y);
   y *= -10.0f * mShape->radius;
   y += mShape->center;
   cameraMatrix.setColumn(3,y);
   cameraMatrix.inverse();
   dglLoadMatrix(&cameraMatrix);

   // set some initial gl states
   glDisable(GL_CULL_FACE);
   glDisable(GL_LIGHTING);
   glEnable(GL_DEPTH_TEST);
   glDepthFunc(GL_LEQUAL);

   // take a snapshot of the shape with a black background...
   glClearColor(0,0,0,0);
   glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
   GBitmap * blackBmp = new GBitmap;
   blackBmp->allocateBitmap(bmpWidth,bmpHeight,false,GBitmap::RGB);
   render(mCurrentDetailLevel,mCurrentIntraDetailLevel);
   glReadPixels(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight,GL_RGB,GL_UNSIGNED_BYTE,(void*)blackBmp->getBits(0));

   // take a snapshot of the shape with a white background...
   glClearColor(1,1,1,1);
   glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
   GBitmap * whiteBmp = new GBitmap;
   whiteBmp->allocateBitmap(bmpWidth,bmpHeight,false,GBitmap::RGB);
   render(mCurrentDetailLevel,mCurrentIntraDetailLevel);
   glReadPixels(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight,GL_RGB,GL_UNSIGNED_BYTE,(void*)whiteBmp->getBits(0));

   glDisable(GL_DEPTH_TEST);
   glClearColor(saveClearColor.x,saveClearColor.y,saveClearColor.z,saveClearColor.w);

   // now separate the color and alpha channels
   GBitmap * bmp = new GBitmap;
   bmp->allocateBitmap(width,height,mip,GBitmap::RGBA);
   U8 * wbmp = (U8*)whiteBmp->getBits(0);
   U8 * bbmp = (U8*)blackBmp->getBits(0);
   U8 * dst  = (U8*)bmp->getBits(0);
   U32 i,j;
   if (hiQuality)
   {
      for (i=0; i<height; i++)
      {
         for (j=0; j<width; j++)
         {
            F32 alphaTally = 0.0f;
            S32 alphaIntTally = 0;
            S32 alphaCount = 0;
            F32 rTally = 0.0f;
            F32 gTally = 0.0f;
            F32 bTally = 0.0f;
            for (S32 k=0; k<scale; k++)
            {
               for (S32 l=0; l<scale; l++)
               {
                  // shape on black background is alpha * color, shape on white background is alpha * color + (1-alpha) * 255
                  // we want 255 * alpha, or 255 - (white - black)
                  U32 pos = 3*((i*scale+k)*bmpWidth+j*scale+l);
                  U32 alpha = 255 - (wbmp[pos+0] - bbmp[pos+0]);
                  alpha    += 255 - (wbmp[pos+1] - bbmp[pos+1]);
                  alpha    += 255 - (wbmp[pos+2] - bbmp[pos+2]);
                  F32 floatAlpha = ((F32)alpha)/(1.0f*255.0f);
                  if (alpha != 0)
                  {
                     rTally += bbmp[pos+0];
                     gTally += bbmp[pos+1];
                     bTally += bbmp[pos+2];
                     alphaCount++;
                  }
                  alphaTally += floatAlpha;
                  alphaIntTally += alpha;
               }
            }
            F32 invAlpha = alphaTally > 0.01f ? 1.0f / alphaTally : 0.0f;
            U32 pos = 4*(i*width+j);
            dst[pos+0] = (U8)(rTally * invAlpha);
            dst[pos+1] = (U8)(gTally * invAlpha);
            dst[pos+2] = (U8)(bTally * invAlpha);
            dst[pos+3] = (U8)(((F32)alphaIntTally) / (F32) (3*alphaCount));
         }
      }
   }
   else
   {
      // simpler, probably faster...
      for (i=0; i<height*width; i++)
      {
         // shape on black background is alpha * color, shape on white background is alpha * color + (1-alpha) * 255
         // we want 255 * alpha, or 255 - (white - black)
         U32 alpha = 255 - (wbmp[i*3+0] - bbmp[i*3+0]);
         alpha    += 255 - (wbmp[i*3+1] - bbmp[i*3+1]);
         alpha    += 255 - (wbmp[i*3+2] - bbmp[i*3+2]);

         if (alpha != 0)
         {
            F32 floatAlpha = ((F32)alpha)/(3.0f*255.0f);
            dst[i*4+0] = (U8)(bbmp[i*3+0] / floatAlpha);
            dst[i*4+1] = (U8)(bbmp[i*3+1] / floatAlpha);
            dst[i*4+2] = (U8)(bbmp[i*3+2] / floatAlpha);
            dst[i*4+3] = (U8)(alpha/3);
         }
         else
         {
            dst[i*4+0] = dst[i*4+1] = dst[i*4+2] = dst[i*4+3] = 0;
         }
      }
   }

   delete blackBmp;
   delete whiteBmp;

   if (mip)
      bmp->extrudeMipLevels();

   return bmp;
}

void TSShapeInstance::renderShadow(S32 dl, const MatrixF & mat, S32 dim, U32 * bits)
{
   // if dl==-1, nothing to do
   if (dl==-1)
      return;

   AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::renderShadow");

   S32 i;

   const TSDetail * detail = &mShape->details[dl];
   S32 ss = detail->subShapeNum;
   S32 od = detail->objectDetailNum;

   // assert if we're a billboard detail
   AssertFatal(ss>=0,"TSShapeInstance::renderShadow: not with a billboard detail level");

   // set up render data
   setStatics(dl);

   // run through the meshes
   smRenderData.currentTransform = NULL;
   S32 start = mShape->subShapeFirstObject[ss];
   S32 end   = start + mShape->subShapeNumObjects[ss];
   for (i=start; i<end; i++)
      mMeshObjects[i].renderShadow(od,mat,dim,bits,mMaterialList);

   // if we have a marix pushed, pop it now
   if (smRenderData.currentTransform)
      glPopMatrix();

   clearStatics();
}

//-------------------------------------------------------------------------------------
// Object (MeshObjectInstance & PluginObjectInstance) render methods
//-------------------------------------------------------------------------------------

void TSShapeInstance::ObjectInstance::render(S32, TSMaterialList *)
{
   AssertFatal(0,"TSShapeInstance::ObjectInstance::render:  no default render method.");
}

void TSShapeInstance::ObjectInstance::renderEnvironmentMap(S32, TSMaterialList *)
{
   AssertFatal(0,"TSShapeInstance::ObjectInstance::renderEnvironmentMap:  no default render method.");
}

void TSShapeInstance::ObjectInstance::renderDetailMap(S32, TSMaterialList *)
{
   AssertFatal(0,"TSShapeInstance::ObjectInstance::renderDetailMap:  no default render method.");
}

void TSShapeInstance::ObjectInstance::renderFog(S32, TSMaterialList*)
{
   AssertFatal(0,"TSShapeInstance::ObjectInstance::renderFog:  no default render method.");
}

S32 TSShapeInstance::MeshObjectInstance::getSizeVB(S32 size)
{
   TSMesh *mesh = getMesh(0);

   if (mesh && mesh->getMeshType() == TSMesh::StandardMeshType && mesh->vertsPerFrame > 0)
   {
      mesh->vbOffset = size;

      return mesh->numFrames*mesh->numMatFrames*mesh->vertsPerFrame;
   }
   else
      return 0;
}

bool TSShapeInstance::MeshObjectInstance::hasMergeIndices()
{
   TSMesh *mesh = getMesh(0);

   return (mesh && mesh->getMeshType() == TSMesh::StandardMeshType && mesh->mergeIndices.size());
}

void TSShapeInstance::MeshObjectInstance::fillVB(S32 vb, TSMaterialList *materials)
{
   TSMesh *mesh = getMesh(0);

   if (!mesh || mesh->getMeshType() != TSMesh::StandardMeshType || mesh->vertsPerFrame <= 0)
      return;

   for (S32 f = 0; f < mesh->numFrames; ++f)
      for (S32 m = 0; m < mesh->numMatFrames; ++m)
         mesh->fillVB(vb,f,m,materials);
}

void TSShapeInstance::MeshObjectInstance::morphVB(S32 vb, S32 &previousMerge, S32 objectDetail, TSMaterialList *materials)
{
   if (visible > 0.01f)
   {
      TSMesh *m0 = getMesh(0);
      TSMesh *mesh = getMesh(objectDetail);

      if (m0 && mesh)
      {
         // render TSSortedMesh's standard
         if (m0->getMeshType() != TSMesh::StandardMeshType)
            return;

         GLuint foffset = (frame*m0->numMatFrames + matFrame)*m0->vertsPerFrame;
         U32 morphSize = mesh->mergeIndices.size();
         S32 merge = mesh->vertsPerFrame-morphSize;

         if (!morphSize)
            return;

         if (previousMerge != -1 && previousMerge < merge)
         {
            S32 tmp = merge;

            merge = previousMerge;
            previousMerge = tmp;
            morphSize = mesh->vertsPerFrame-merge;
         }
         else
            previousMerge = merge;

         glOffsetVertexBufferEXT(vb,m0->vbOffset + foffset + merge);
         mesh->morphVB(vb,morphSize,frame,matFrame,materials);
      }
   }
}

void TSShapeInstance::MeshObjectInstance::renderVB(S32 vb, S32 objectDetail, TSMaterialList *materials)
{
   if (visible > 0.01f)
   {
      TSMesh *m0 = getMesh(0);
      TSMesh *mesh = getMesh(objectDetail);

      if (m0 && mesh)
      {
         // render TSSortedMesh's standard
         if (m0->getMeshType() != TSMesh::StandardMeshType)
         {
            render(objectDetail, materials);
            return;
         }

         if (mesh->vertsPerFrame <= 0)
            return;

         MatrixF *transform = getTransform();

         if (transform != TSShapeInstance::smRenderData.currentTransform)
         {
            if (TSShapeInstance::smRenderData.currentTransform)
               glPopMatrix();
            if (transform)
            {
               glPushMatrix();
               dglMultMatrix(transform);
            }
            TSShapeInstance::smRenderData.currentTransform = transform;
         }

         GLuint foffset = (frame*m0->numMatFrames + matFrame)*m0->vertsPerFrame;

         glSetVertexBufferEXT(vb);
         glOffsetVertexBufferEXT(vb,m0->vbOffset + foffset);

         if (visible>0.99f)
         {
            if (TSShapeInstance::smRenderData.balloonShape)
            {
               glPushMatrix();

               F32 &bv = TSShapeInstance::smRenderData.balloonValue;

               glScalef(bv,bv,bv);
            }
            mesh->renderVB(frame,matFrame,materials);
            if (TSShapeInstance::smRenderData.balloonShape)
               glPopMatrix();
         }
         else
         {
            mesh->setFade(visible);
            mesh->renderVB(frame,matFrame,materials);
            mesh->clearFade();
         }
      }
   }
}

void TSShapeInstance::MeshObjectInstance::render(S32 objectDetail, TSMaterialList * materials)
{
   if (visible>0.01f)
   {
      TSMesh * mesh = getMesh(objectDetail);
      if (mesh)
      {
         MatrixF * transform = getTransform();
         if (transform != TSShapeInstance::smRenderData.currentTransform)
         {
            if (TSShapeInstance::smRenderData.currentTransform)
               glPopMatrix();
            if (transform)
            {
               glPushMatrix();
               dglMultMatrix(transform);
            }
            TSShapeInstance::smRenderData.currentTransform = transform;
         }
         if (visible>0.99f)
         {
            if (TSShapeInstance::smRenderData.balloonShape)
            {
               glPushMatrix();
               F32 & bv = TSShapeInstance::smRenderData.balloonValue;
               glScalef(bv,bv,bv);
            }
            mesh->render(frame,matFrame,materials);
            if (TSShapeInstance::smRenderData.balloonShape)
               glPopMatrix();
         }
         else
         {
            mesh->setFade(visible);
            mesh->render(frame,matFrame,materials);
            mesh->clearFade();
         }
      }
   }
}

void TSShapeInstance::DecalObjectInstance::render(S32 objectDetail, TSMaterialList * materials)
{
   if (targetObject->visible>0.01f)
   {
      TSDecalMesh * decalMesh = getDecalMesh(objectDetail);
      if (decalMesh && frame>=0)
      {
         MatrixF * transform = getTransform();
         if (transform != TSShapeInstance::smRenderData.currentTransform)
         {
            if (TSShapeInstance::smRenderData.currentTransform)
               glPopMatrix();
            if (transform)
            {
               glPushMatrix();
               dglMultMatrix(transform);
            }
            TSShapeInstance::smRenderData.currentTransform = transform;
         }

         if (TSShapeInstance::smRenderData.balloonShape)
         {
            glPushMatrix();
            F32 & bv = TSShapeInstance::smRenderData.balloonValue;
            glScalef(bv,bv,bv);
         }
         decalMesh->render(targetObject->frame,frame,materials);
         if (TSShapeInstance::smRenderData.balloonShape)
            glPopMatrix();
      }
   }
}

void TSShapeInstance::MeshObjectInstance::renderEnvironmentMap(S32 objectDetail, TSMaterialList * materials)
{
   if (visible>0.01f)
   {
      TSMesh * mesh = getMesh(objectDetail);
      if (mesh)
      {
         MatrixF * transform = getTransform();
         if (transform != TSShapeInstance::smRenderData.currentTransform)
         {
            if (TSShapeInstance::smRenderData.currentTransform)
               glPopMatrix();
            if (transform)
            {
               glPushMatrix();
               dglMultMatrix(transform);
            }
            TSShapeInstance::smRenderData.currentTransform = transform;
         }

         if (TSShapeInstance::smRenderData.balloonShape)
         {
            glPushMatrix();
            F32 & bv = TSShapeInstance::smRenderData.balloonValue;
            glScalef(bv,bv,bv);
         }
         mesh->renderEnvironmentMap(frame,matFrame,materials);
         if (TSShapeInstance::smRenderData.balloonShape)
            glPopMatrix();
      }
   }
}

void TSShapeInstance::MeshObjectInstance::renderDetailMap(S32 objectDetail, TSMaterialList * materials)
{
   if (visible>0.01f)
   {
      TSMesh * mesh = getMesh(objectDetail);
      if (mesh && mesh->getFlags(TSMesh::HasDetailTexture))
      {
         MatrixF * transform = getTransform();
         if (transform != TSShapeInstance::smRenderData.currentTransform)
         {
            if (TSShapeInstance::smRenderData.currentTransform)
               glPopMatrix();
            if (transform)
            {
               glPushMatrix();
               dglMultMatrix(transform);
            }
            TSShapeInstance::smRenderData.currentTransform = transform;
         }

         if (TSShapeInstance::smRenderData.balloonShape)
         {
            glPushMatrix();
            F32 & bv = TSShapeInstance::smRenderData.balloonValue;
            glScalef(bv,bv,bv);
         }
         mesh->renderDetailMap(frame,matFrame,materials);
         if (TSShapeInstance::smRenderData.balloonShape)
            glPopMatrix();
      }
   }
}

void TSShapeInstance::MeshObjectInstance::renderShadow(S32 objectDetail, const MatrixF & mat, S32 dim, U32 * bits, TSMaterialList * materialList)
{
   if (visible>0.01f)
   {
      TSMesh * mesh = getMesh(objectDetail);
      if (mesh)
      {
         MatrixF mat2;
         MatrixF * transform = getTransform();
         if (transform)
            mat2.mul(mat,*transform);
         else
            mat2=mat;
         mesh->renderShadow(frame,mat2,dim,bits,materialList);
      }
   }
}

void TSShapeInstance::MeshObjectInstance::renderFog(S32 objectDetail, TSMaterialList* materials)
{
   if (visible>0.01f)
   {
      TSMesh * mesh = getMesh(objectDetail);
      if (mesh)
      {
         MatrixF * transform = getTransform();
         if (transform != TSShapeInstance::smRenderData.currentTransform)
         {
            if (TSShapeInstance::smRenderData.currentTransform)
               glPopMatrix();
            if (transform)
            {
               glPushMatrix();
               dglMultMatrix(transform);
            }
            TSShapeInstance::smRenderData.currentTransform = transform;
         }

         if (TSShapeInstance::smRenderData.balloonShape)
         {
            glPushMatrix();
            F32 & bv = TSShapeInstance::smRenderData.balloonValue;
            glScalef(bv,bv,bv);
         }
         mesh->renderFog(frame, materials);
         if (TSShapeInstance::smRenderData.balloonShape)
            glPopMatrix();
      }
   }
}

void TSShapeInstance::incDebrisRefCount()
{
   ++debrisRefCount;
}

void TSShapeInstance::decDebrisRefCount()
{
   if( debrisRefCount == 0 ) return;
   --debrisRefCount;
}

U32 TSShapeInstance::getDebrisRefCount()
{
   return debrisRefCount;
}


U32 TSShapeInstance::getNumDetails()
{
   if( mShape )
   {
      return mShape->details.size();
   }

   return 0;
}
