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

#include "ts/tsShape.h"
#include "ts/tsLastDetail.h"
#include "core/stringTable.h"
#include "console/console.h"
#include "ts/tsShapeInstance.h"
#include "collision/convex.h"
#include "platform/platformGL.h"
#include "util/safeDelete.h"

/// most recent version -- this is the version we write
S32 TSShape::smVersion = 25;
/// the version currently being read...valid only during a read
S32 TSShape::smReadVersion = -1;
const U32 TSShape::smMostRecentExporterVersion = DTS_EXPORTER_CURRENT_VERSION;

F32 TSShape::smAlphaOutLastDetail = -1.0f;
F32 TSShape::smAlphaInBillboard = 0.15f;
F32 TSShape::smAlphaOutBillboard = 0.1f;
F32 TSShape::smAlphaInDefault = -1.0f;
F32 TSShape::smAlphaOutDefault = -1.0f;

// don't bother even loading this many of the highest detail levels (but
// always load last renderable detail)
S32 TSShape::smNumSkipLoadDetails = 0;

bool TSShape::smInitOnRead = true;


TSShape::TSShape()
{
   materialList = NULL;
   mReadVersion = -1; // -1 means constructed from scratch (e.g., in exporter or no read yet)
   mMemoryBlock = NULL;

   mSequencesConstructed = false;

   mVertexBuffer = (U32)-1;
   mCallbackKey = (U32)-1;

   VECTOR_SET_ASSOCIATION(sequences);
   VECTOR_SET_ASSOCIATION(billboardDetails);
   VECTOR_SET_ASSOCIATION(detailCollisionAccelerators);
   VECTOR_SET_ASSOCIATION(names);
}

TSShape::~TSShape()
{
   clearDynamicData();
   delete materialList;

   S32 i;

   // before deleting meshes, we have to delete decals and get them out
   // of the mesh list...this is a legacy issue from when decals were meshes
   for (i=0; i<decals.size(); i++)
   {
      for (S32 j=0; j<decals[i].numMeshes; j++)
      {
         if ((TSDecalMesh*)meshes[decals[i].startMeshIndex+j])
            destructInPlace((TSDecalMesh*)meshes[decals[i].startMeshIndex+j]);
         meshes[decals[i].startMeshIndex+j]=NULL;
      }
   }

   // everything left over here is a legit mesh
   for (i=0; i<meshes.size(); i++)
      if (meshes[i])
         destructInPlace(meshes[i]);

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

   for (i=0; i<billboardDetails.size(); i++)
   {
      SAFE_DELETE(billboardDetails[i]);
   }
   billboardDetails.clear();

   // Delete any generated accelerators
   S32 dca;
   for (dca = 0; dca < detailCollisionAccelerators.size(); dca++)
   {
      ConvexHullAccelerator* accel = detailCollisionAccelerators[dca];
      if (accel != NULL) 
      {
         delete [] accel->vertexList;
         delete [] accel->normalList;
         for (S32 j = 0; j < accel->numVerts; j++)
            delete [] accel->emitStrings[j];
         delete [] accel->emitStrings;
         delete accel;
      }
   }
   for (dca = 0; dca < detailCollisionAccelerators.size(); dca++)
      detailCollisionAccelerators[dca] = NULL;

   delete [] mMemoryBlock;
   mMemoryBlock = NULL;

   if (mVertexBuffer != -1)
      if (dglDoesSupportVertexBuffer())
         glFreeVertexBufferEXT(mVertexBuffer);
      else
         AssertFatal(false,"Vertex buffer should have already been freed!");

   if (mCallbackKey != -1)
      TextureManager::unregisterEventCallback(mCallbackKey);
}

void TSShape::clearDynamicData()
{
}

const char * TSShape::getName(S32 nameIndex) const
{
   AssertFatal(nameIndex>=0 && nameIndex<names.size(),"TSShape::getName");
   return names[nameIndex];
}

S32 TSShape::findName(const char * name) const
{
   for (S32 i=0; i<names.size(); i++)
      if (!dStricmp(name,names[i]))
         return i;
   return -1;
}

S32 TSShape::findNode(S32 nameIndex) const
{
   for (S32 i=0; i<nodes.size(); i++)
      if (nodes[i].nameIndex==nameIndex)
         return i;
   return -1;
}

S32 TSShape::findObject(S32 nameIndex) const
{
   for (S32 i=0; i<objects.size(); i++)
      if (objects[i].nameIndex==nameIndex)
         return i;
   return -1;
}

S32 TSShape::findDecal(S32 nameIndex) const
{
   for (S32 i=0; i<decals.size(); i++)
      if (decals[i].nameIndex==nameIndex)
         return i;
   return -1;
}

S32 TSShape::findIflMaterial(S32 nameIndex) const
{
   for (S32 i=0; i<iflMaterials.size(); i++)
      if (iflMaterials[i].nameIndex==nameIndex)
         return i;
   return -1;
}

S32 TSShape::findDetail(S32 nameIndex) const
{
   for (S32 i=0; i<details.size(); i++)
      if (details[i].nameIndex==nameIndex)
         return i;
   return -1;
}

S32 TSShape::findSequence(S32 nameIndex) const
{
   for (S32 i=0; i<sequences.size(); i++)
      if (sequences[i].nameIndex==nameIndex)
         return i;
   return -1;
}

void TSShape::init()
{
   clearDynamicData();

   S32 numSubShapes = subShapeFirstNode.size();
   AssertFatal(numSubShapes==subShapeFirstObject.size(),"TSShape::init");

   S32 i,j;

   // set up parent/child relationships on nodes and objects
   for (i=0; i<nodes.size(); i++)
      nodes[i].firstObject = nodes[i].firstChild = nodes[i].nextSibling = -1;

   for (i=0; i<nodes.size(); i++)
   {
      S32 parentIndex = nodes[i].parentIndex;
      if (parentIndex>=0)
      {
         if (nodes[parentIndex].firstChild<0)
            nodes[parentIndex].firstChild=i;
         else
         {
            S32 child = nodes[parentIndex].firstChild;
            while (nodes[child].nextSibling>=0)
               child = nodes[child].nextSibling;
            nodes[child].nextSibling = i;
         }
      }
   }

   for (i=0; i<objects.size(); i++)
   {
      objects[i].nextSibling = -1;
      objects[i].firstDecal  = -1;

      S32 nodeIndex = objects[i].nodeIndex;
      if (nodeIndex>=0)
      {
         if (nodes[nodeIndex].firstObject<0)
            nodes[nodeIndex].firstObject = i;
         else
         {
            S32 objectIndex = nodes[nodeIndex].firstObject;
            while (objects[objectIndex].nextSibling>=0)
               objectIndex = objects[objectIndex].nextSibling;
            objects[objectIndex].nextSibling = i;
         }
      }
   }

   for (i=0; i<decals.size(); i++)
   {
      decals[i].nextSibling = -1;
      S32 objectIndex = decals[i].objectIndex;
      if (objects[objectIndex].firstDecal<0)
         objects[objectIndex].firstDecal = i;
      else
      {
         S32 decalIndex = objects[objectIndex].firstDecal;
         while (decals[decalIndex].nextSibling>=0)
            decalIndex = decals[decalIndex].nextSibling;
         decals[decalIndex].nextSibling = i;
      }
   }

   mFlags = 0;
   for (i=0; i<sequences.size(); i++)
   {
      if (!sequences[i].animatesScale())
         continue;

      U32 curVal = mFlags & AnyScale;
      U32 newVal = sequences[i].flags & AnyScale;
      mFlags &= ~(AnyScale);
      mFlags |= getMax(curVal,newVal); // take the larger value (can only convert upwards)
   }

   // set up alphaIn and alphaOut vectors...
   #if defined(TORQUE_LIB)
   alphaIn.setSize(details.size());
   alphaOut.setSize(details.size());
   #endif

   for (i=0; i<details.size(); i++)
   {
      if (details[i].size<0)
      {
         // we don't care...
         alphaIn[i]  = 0.0f;
         alphaOut[i] = 0.0f;
      }
      else if (i+1==details.size() || details[i+1].size<0)
      {
         alphaIn[i]  = 0.0f;
         alphaOut[i] = smAlphaOutLastDetail;
      }
      else
      {
         if (details[i+1].subShapeNum<0)
         {
            // following detail is a billboard detail...treat special...
            alphaIn[i]  = smAlphaInBillboard;
            alphaOut[i] = smAlphaOutBillboard;
         }
         else
         {
            // next detail is normal detail
            alphaIn[i] = smAlphaInDefault;
            alphaOut[i] = smAlphaOutDefault;
         }
      }
   }

   for (i=mSmallestVisibleDL-1; i>=0; i--)
   {
      if (i<smNumSkipLoadDetails)
      {
         // this detail level renders when pixel size
         // is larger than our cap...zap all the meshes and decals
         // associated with it and use the next detail level
         // instead...
         S32 ss    = details[i].subShapeNum;
         S32 od    = details[i].objectDetailNum;

         if (ss==details[i+1].subShapeNum && od==details[i+1].objectDetailNum)
            // doh! already done this one (init can be called multiple times on same shape due
            // to sequence importing).
            continue;

         details[i].subShapeNum = details[i+1].subShapeNum;
         details[i].objectDetailNum = details[i+1].objectDetailNum;
      }
   }

   for (i=0; i<details.size(); i++)
   {
      S32 count = 0;
      S32 ss = details[i].subShapeNum;
      S32 od = details[i].objectDetailNum;
      if (ss<0)
      {
         // billboard detail...
         count += 2;
         continue;
      }
      S32 start = subShapeFirstObject[ss];
      S32 end   = start + subShapeNumObjects[ss];
      for (j=start; j<end; j++)
      {
         Object & obj = objects[j];
         if (od<obj.numMeshes)
         {
            TSMesh * mesh = meshes[obj.startMeshIndex+od];
            count += mesh ? mesh->getNumPolys() : 0;
         }
      }
      details[i].polyCount = count;
   }

   // Init the collision accelerator array.  Note that we don't compute the
   //  accelerators until the app requests them
   {
      S32 dca;
      for (dca = 0; dca < detailCollisionAccelerators.size(); dca++)
      {
         ConvexHullAccelerator* accel = detailCollisionAccelerators[dca];
         if (accel != NULL) 
         {
            delete [] accel->vertexList;
            delete [] accel->normalList;
            for (S32 j = 0; j < accel->numVerts; j++)
               delete [] accel->emitStrings[j];
            delete [] accel->emitStrings;
            delete accel;
         }
      }

      detailCollisionAccelerators.setSize(details.size());
      for (dca = 0; dca < detailCollisionAccelerators.size(); dca++)
         detailCollisionAccelerators[dca] = NULL;
   }

   mMergeBufferSize=0;
   for (i=0; i<objects.size(); i++)
   {
      TSObject * obj = &objects[i];
      S32 maxSize = 0;
      for (S32 dl=0; dl<obj->numMeshes; dl++)
      {
         TSMesh * mesh = meshes[obj->startMeshIndex+dl];
         if (mesh)
         {
            mesh->mergeBufferStart = mMergeBufferSize;
            maxSize = getMax((S32)maxSize,(S32)mesh->mergeIndices.size());
         }
      }
      mMergeBufferSize += maxSize;
   }

   initMaterialList();
}

void TSShape::setupBillboardDetails(TSShapeInstance *shape)
{
   // set up billboard details -- only do this once, meaning that
   // if we add a sequence to the shape we don't redo the billboard
   // details...
   S32 i;
   if (billboardDetails.empty())
   {
      for (i=0; i<details.size(); i++)
      {
         if (details[i].subShapeNum>=0)
            continue; // not a billboard detail
         while (billboardDetails.size()<=i)
            billboardDetails.push_back(NULL);
         U32 props = details[i].objectDetailNum;
         U32 numEquatorSteps = props & 0x7F; // bits 0..6
         U32 numPolarSteps = (props>>7) & 0x3F; // bits 7..12
         F32 polarAngle = 0.5f * M_PI_F * (1.0f/64.0f) * (F32) ((props>>13) & 0x3F); // bits 13..18
         S32 dl = (props>>19) & 0x0F;  // 19..22
         S32 dim = (props>>23) & 0xFF; // 23..30
         bool includePoles = (props & 0x80000000)!=0; // bit 31

         billboardDetails[i] = new TSLastDetail(shape,numEquatorSteps,numPolarSteps,polarAngle,includePoles,dl,dim);
      }
   }
}

void TSShape::initMaterialList()
{
   S32 numSubShapes = subShapeFirstObject.size();
   #if defined(TORQUE_LIB)
   subShapeFirstTranslucentObject.setSize(numSubShapes);
   #endif

   S32 i,j,k;
   // for each subshape, find the first translucent object
   // also, while we're at it, set mHasTranslucency
   for (S32 ss = 0; ss<numSubShapes; ss++)
   {
      S32 start = subShapeFirstObject[ss];
      S32 end = subShapeNumObjects[ss];
      subShapeFirstTranslucentObject[ss] = end;
      for (i=start; i<end; i++)
      {
         // check to see if this object has translucency
         Object & obj = objects[i];
         for (j=0; j<obj.numMeshes; j++)
         {
            TSMesh * mesh = meshes[obj.startMeshIndex+j];
            if (!mesh)
               continue;
            for (k=0; k<mesh->primitives.size(); k++)
            {
               if (mesh->primitives[k].matIndex & TSDrawPrimitive::NoMaterial)
                  continue;
               S32 flags = materialList->getFlags(mesh->primitives[k].matIndex & TSDrawPrimitive::MaterialMask);
               if (flags & TSMaterialList::AuxiliaryMap)
                  continue;
               if (flags & TSMaterialList::Translucent)
               {
                  mFlags |= HasTranslucency;
                  subShapeFirstTranslucentObject[ss] = i;
                  break;
               }
            }
            if (k!=mesh->primitives.size())
               break;
         }
         if (j!=obj.numMeshes)
            break;
      }
      if (i!=end)
         break;
   }
}

bool TSShape::preloadMaterialList()
{
   if(materialList)
      return materialList->load(MeshTexture, mSourceResource ? mSourceResource->path : "", true);
   return true;
}

bool TSShape::buildConvexHull(S32 dl) const
{
   AssertFatal(dl>=0 && dl<details.size(),"TSShape::buildConvexHull: detail out of range");

   bool ok = true;

   const Detail & detail = details[dl];
   S32 ss = detail.subShapeNum;
   S32 od = detail.objectDetailNum;

   S32 start = subShapeFirstObject[ss];
   S32 end   = subShapeNumObjects[ss];
   for (S32 i=start; i<end; i++)
   {
      TSMesh * mesh = meshes[objects[i].startMeshIndex+od];
      if (!mesh)
         continue;
      ok &= mesh->buildConvexHull();
   }
   return ok;
}

Vector<MatrixF> gTempNodeTransforms(__FILE__, __LINE__);

void TSShape::computeBounds(S32 dl, Box3F & bounds) const
{
   // if dl==-1, nothing to do
   if (dl==-1)
      return;

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

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

   // set up temporary storage for non-local transforms...
   S32 i;
   S32 start = subShapeFirstNode[ss];
   S32 end   = subShapeNumNodes[ss] + start;
   gTempNodeTransforms.setSize(end-start);
   for (i=start; i<end; i++)
   {
      MatrixF mat;
      QuatF q;
      TSTransform::setMatrix(defaultRotations[i].getQuatF(&q),defaultTranslations[i],&mat);
      if (nodes[i].parentIndex>=0)
         gTempNodeTransforms[i-start].mul(gTempNodeTransforms[nodes[i].parentIndex-start],mat);
      else
         gTempNodeTransforms[i-start] = mat;
   }

   // run through objects and updating bounds as we go
   bounds.min.set( 10E30f, 10E30f, 10E30f);
   bounds.max.set(-10E30f,-10E30f,-10E30f);
   Box3F box;
   start = subShapeFirstObject[ss];
   end   = subShapeNumObjects[ss] + start;
   for (i=start; i<end; i++)
   {
      const Object * object = &objects[i];
      TSMesh * mesh = od<object->numMeshes ? meshes[object->startMeshIndex+od] : NULL;
      if (mesh)
      {
         static MatrixF idMat(true);
         if (object->nodeIndex<0)
            mesh->computeBounds(idMat,box);
         else
            mesh->computeBounds(gTempNodeTransforms[object->nodeIndex-subShapeFirstNode[ss]],box);
         bounds.min.setMin(box.min);
         bounds.max.setMax(box.max);
      }
   }
}

TSShapeAlloc TSShape::alloc;

#define alloc TSShape::alloc

// messy stuff: check to see if we should "skip" meshNum
// this assumes that meshes for a given object/decal are in a row
// skipDL is the lowest detail number we keep (i.e., the # of details we skip)
bool TSShape::checkSkip(S32 meshNum, S32 & curObject, S32 & curDecal, S32 skipDL)
{
   if (skipDL==0)
      // easy out...
      return false;

   // skip detail level exists on this subShape
   S32 skipSS = details[skipDL].subShapeNum;

   if (curObject<objects.size())
   {
      S32 start = objects[curObject].startMeshIndex;
      if (meshNum>=start)
      {
         // we are either from this object, the next object, or a decal
         if (meshNum < start + objects[curObject].numMeshes)
         {
            // this object...
            if (subShapeFirstObject[skipSS]>curObject)
               // haven't reached this subshape yet
               return true;
            if (skipSS+1==subShapeFirstObject.size() || curObject<subShapeFirstObject[skipSS+1])
               // curObject is on subshape of skip detail...make sure it's after skipDL
               return (meshNum-start<details[skipDL].objectDetailNum);
            // if we get here, then curObject ocurrs on subShape after skip detail (so keep it)
            return false;
         }
         else
            // advance object, try again
            return checkSkip(meshNum,++curObject,curDecal,skipDL);
      }
   }

   if (curDecal<decals.size())
   {
      S32 start = decals[curDecal].startMeshIndex;
      if (meshNum>=start)
      {
         // we are either from this decal, the next decal, or error
         if (meshNum < start + decals[curDecal].numMeshes)
         {
            // this object...
            if (subShapeFirstDecal[skipSS]>curDecal)
               // haven't reached this subshape yet
               return true;
            if (skipSS+1==subShapeFirstDecal.size() || curDecal<subShapeFirstDecal[skipSS+1])
               // curDecal is on subshape of skip detail...make sure it's after skipDL
               return (meshNum-start<details[skipDL].objectDetailNum);
            // if we get here, then curDecal ocurrs on subShape after skip detail (so keep it)
            return false;
         }
         else
            // advance decal, try again
            return checkSkip(meshNum,curObject,++curDecal,skipDL);
      }
   }
   AssertFatal(0,"TSShape::checkSkip: assertion failed");
   return false;
}

void TSShape::assembleShape()
{
   S32 i,j;

   // get counts...
   S32 numNodes = alloc.get32();
   S32 numObjects = alloc.get32();
   S32 numDecals = alloc.get32();
   S32 numSubShapes = alloc.get32();
   S32 numIflMaterials = alloc.get32();
   S32 numNodeRots;
   S32 numNodeTrans;
   S32 numNodeUniformScales;
   S32 numNodeAlignedScales;
   S32 numNodeArbitraryScales;
   if (smReadVersion<22)
   {
      numNodeRots = numNodeTrans = alloc.get32() - numNodes;
      numNodeUniformScales = numNodeAlignedScales = numNodeArbitraryScales = 0;
   }
   else
   {
      numNodeRots = alloc.get32();
      numNodeTrans = alloc.get32();
      numNodeUniformScales = alloc.get32();
      numNodeAlignedScales = alloc.get32();
      numNodeArbitraryScales = alloc.get32();
   }
   S32 numGroundFrames = 0;
   if (smReadVersion>23)
      numGroundFrames = alloc.get32();
   S32 numObjectStates = alloc.get32();
   S32 numDecalStates = alloc.get32();
   S32 numTriggers = alloc.get32();
   S32 numDetails = alloc.get32();
   S32 numMeshes = alloc.get32();
   S32 numSkins = 0;
   if (smReadVersion<23)
      // in later versions, skins are kept with other meshes
      numSkins = alloc.get32();
   S32 numNames = alloc.get32();
   mSmallestVisibleSize = (F32)alloc.get32();
   mSmallestVisibleDL   = alloc.get32();
   S32 skipDL = getMin(mSmallestVisibleDL,smNumSkipLoadDetails);

   alloc.checkGuard();

   // get bounds...
   alloc.get32((S32*)&radius,1);
   alloc.get32((S32*)&tubeRadius,1);
   alloc.get32((S32*)&center,3);
   alloc.get32((S32*)&bounds,6);

   alloc.checkGuard();

   // copy various vectors...
   S32 * ptr32 = alloc.copyToShape32(numNodes*5);
   nodes.set(ptr32,numNodes);

   alloc.checkGuard();

   ptr32 = alloc.copyToShape32(numObjects*6,true);
   if (!ptr32)
      ptr32 = alloc.allocShape32(numSkins*6); // pre v23 shapes store skins and meshes separately...no longer
   else
      alloc.allocShape32(numSkins*6);
   objects.set(ptr32,numObjects);

   alloc.checkGuard();

   ptr32 = alloc.copyToShape32(numDecals*5,true);
   decals.set(ptr32,numDecals);

   alloc.checkGuard();

   ptr32 = alloc.copyToShape32(numIflMaterials*5);
   iflMaterials.set(ptr32,numIflMaterials);

   alloc.checkGuard();

   ptr32 = alloc.copyToShape32(numSubShapes,true);
   subShapeFirstNode.set(ptr32,numSubShapes);
   ptr32 = alloc.copyToShape32(numSubShapes,true);
   subShapeFirstObject.set(ptr32,numSubShapes);
   ptr32 = alloc.copyToShape32(numSubShapes,true);
   subShapeFirstDecal.set(ptr32,numSubShapes);

   alloc.checkGuard();

   ptr32 = alloc.copyToShape32(numSubShapes);
   subShapeNumNodes.set(ptr32,numSubShapes);
   ptr32 = alloc.copyToShape32(numSubShapes);
   subShapeNumObjects.set(ptr32,numSubShapes);
   ptr32 = alloc.copyToShape32(numSubShapes);
   subShapeNumDecals.set(ptr32,numSubShapes);

   alloc.checkGuard();

   ptr32 = alloc.allocShape32(numSubShapes);
   subShapeFirstTranslucentObject.set(ptr32,numSubShapes);

   // get meshIndexList...older shapes only
   S32 meshIndexListSize = 0;
   S32 * meshIndexList = NULL;
   if (smReadVersion<16)
   {
      meshIndexListSize = alloc.get32();
      meshIndexList = alloc.getPointer32(meshIndexListSize);
   }

   // get default translation and rotation
   S16 * ptr16 = alloc.allocShape16(0);
   for (i=0;i<numNodes;i++)
      alloc.copyToShape16(4);
   defaultRotations.set(ptr16,numNodes);
   alloc.align32();
   ptr32 = alloc.allocShape32(0);
   for (i=0;i<numNodes;i++)
   {
      alloc.copyToShape32(3);
      alloc.copyToShape32(sizeof(Point3F)-12); // handle alignment issues w/ point3f
   }
   defaultTranslations.set(ptr32,numNodes);

   // get any node sequence data stored in shape
   nodeTranslations.setSize(numNodeTrans);
   for (i=0;i<numNodeTrans;i++)
      alloc.get32((S32*)&nodeTranslations[i],3);
   nodeRotations.setSize(numNodeRots);
   for (i=0;i<numNodeRots;i++)
      alloc.get16((S16*)&nodeRotations[i],4);
   alloc.align32();

   alloc.checkGuard();

   if (smReadVersion>21)
   {
      // more node sequence data...scale
      nodeUniformScales.setSize(numNodeUniformScales);
      for (i=0;i<numNodeUniformScales;i++)
         alloc.get32((S32*)&nodeUniformScales[i],1);
      nodeAlignedScales.setSize(numNodeAlignedScales);
      for (i=0;i<numNodeAlignedScales;i++)
         alloc.get32((S32*)&nodeAlignedScales[i],3);
      nodeArbitraryScaleFactors.setSize(numNodeArbitraryScales);
      for (i=0;i<numNodeArbitraryScales;i++)
         alloc.get32((S32*)&nodeArbitraryScaleFactors[i],3);
      nodeArbitraryScaleRots.setSize(numNodeArbitraryScales);
      for (i=0;i<numNodeArbitraryScales;i++)
         alloc.get16((S16*)&nodeArbitraryScaleRots[i],4);
      alloc.align32();

      alloc.checkGuard();
   }

   // old shapes need ground transforms moved to ground arrays...but only do it once
   if (smReadVersion<22 && alloc.allocShape32(0))
   {
      for (i=0; i<sequences.size(); i++)
      {
         // move ground transform data to ground vectors
         Sequence & seq = sequences[i];
         S32 oldSz = groundTranslations.size();
         groundTranslations.setSize(oldSz+seq.numGroundFrames);
         groundRotations.setSize(oldSz+seq.numGroundFrames);
         for (S32 j=0;j<seq.numGroundFrames;j++)
         {
            groundTranslations[j+oldSz] = nodeTranslations[seq.firstGroundFrame+j-numNodes];
            groundRotations[j+oldSz] = nodeRotations[seq.firstGroundFrame+j-numNodes];
         }
         seq.firstGroundFrame = oldSz;
         seq.baseTranslation -= numNodes;
         seq.baseRotation -= numNodes;
         seq.baseScale = 0; // not used on older shapes...but keep it clean
      }
   }

   // version 22 & 23 shapes accidentally had no ground transforms, and ground for
   // earlier shapes is handled just above, so...
   if (smReadVersion>23)
   {
      groundTranslations.setSize(numGroundFrames);
      for (i=0;i<numGroundFrames;i++)
         alloc.get32((S32*)&groundTranslations[i],3);
      groundRotations.setSize(numGroundFrames);
      for (i=0;i<numGroundFrames;i++)
         alloc.get16((S16*)&groundRotations[i],4);
      alloc.align32();

      alloc.checkGuard();
   }

   // object states
   ptr32 = alloc.copyToShape32(numObjectStates*3);
   objectStates.set(ptr32,numObjectStates);
   alloc.allocShape32(numSkins*3); // provide buffer after objectStates for older shapes

   alloc.checkGuard();

   // decal states
   ptr32 = alloc.copyToShape32(numDecalStates);
   decalStates.set(ptr32,numDecalStates);

   alloc.checkGuard();

   // frame triggers
   ptr32 = alloc.getPointer32(numTriggers*2);
   triggers.setSize(numTriggers);
   dMemcpy(triggers.address(),ptr32,sizeof(S32)*numTriggers*2);

   alloc.checkGuard();

   // details
   ptr32 = alloc.copyToShape32(numDetails*7,true);
   details.set(ptr32,numDetails);

   alloc.checkGuard();

   // about to read in the meshes...first must allocate some scratch space
   S32 scratchSize = getMax(numSkins,numMeshes);
   TSMesh::smVertsList.setSize(scratchSize);
   TSMesh::smTVertsList.setSize(scratchSize);
   TSMesh::smNormsList.setSize(scratchSize);
   TSMesh::smEncodedNormsList.setSize(scratchSize);
   TSMesh::smDataCopied.setSize(scratchSize);
   TSSkinMesh::smInitTransformList.setSize(scratchSize);
   TSSkinMesh::smVertexIndexList.setSize(scratchSize);
   TSSkinMesh::smBoneIndexList.setSize(scratchSize);
   TSSkinMesh::smWeightList.setSize(scratchSize);
   TSSkinMesh::smNodeIndexList.setSize(scratchSize);
   for (i=0; i<numMeshes; i++)
   {
      TSMesh::smVertsList[i]=NULL;
      TSMesh::smTVertsList[i]=NULL;
      TSMesh::smNormsList[i]=NULL;
      TSMesh::smEncodedNormsList[i]=NULL;
      TSMesh::smDataCopied[i]=false;
      TSSkinMesh::smInitTransformList[i] = NULL;
      TSSkinMesh::smVertexIndexList[i] = NULL;
      TSSkinMesh::smBoneIndexList[i] = NULL;
      TSSkinMesh::smWeightList[i] = NULL;
      TSSkinMesh::smNodeIndexList[i] = NULL;
   }

   // read in the meshes (sans skins)...
   if (smReadVersion>15)
   {
      // straight forward read one at a time
      ptr32 = alloc.allocShape32(numMeshes + numSkins*numDetails); // leave room for skins on old shapes
      S32 curObject = 0, curDecal = 0; // for tracking skipped meshes
      for (i=0; i<numMeshes; i++)
      {
         bool skip = checkSkip(i,curObject,curDecal,skipDL); // skip this mesh?
         S32 meshType = alloc.get32();
         TSMesh * mesh = TSMesh::assembleMesh(meshType,skip);
         if (ptr32)
            ptr32[i] = skip ?  0 : (S32)mesh;

         // fill in location of verts, tverts, and normals for detail levels
         if (mesh && meshType!=TSMesh::DecalMeshType)
         {
            ToolVector<Point2F> diffuse;
            mesh->getUVs(TSMesh::tDiffuse, diffuse);

            TSMesh::smVertsList[i]  = mesh->verts.address();
            TSMesh::smTVertsList[i] = diffuse.address();
            TSMesh::smNormsList[i]  = mesh->norms.address();
            TSMesh::smEncodedNormsList[i] = mesh->encodedNorms.address();
            TSMesh::smDataCopied[i] = !skip; // as long as we didn't skip this mesh, the data should be in shape now
            if (meshType==TSMesh::SkinMeshType)
            {
               TSSkinMesh * skin = (TSSkinMesh*)mesh;
               TSMesh::smVertsList[i]  = skin->initialVerts.address();
               TSMesh::smNormsList[i]  = skin->initialNorms.address();
               TSSkinMesh::smInitTransformList[i] = skin->initialTransforms.address();
               TSSkinMesh::smVertexIndexList[i] = skin->vertexIndex.address();
               TSSkinMesh::smBoneIndexList[i] = skin->boneIndex.address();
               TSSkinMesh::smWeightList[i] = skin->weight.address();
               TSSkinMesh::smNodeIndexList[i] = skin->nodeIndex.address();
            }
         }
      }
      meshes.set(ptr32,numMeshes);
   }
   else
   {
      // use meshIndexList to contruct mesh list...
      ptr32 = alloc.allocShape32(meshIndexListSize + numSkins*numDetails);
      S32 next=0;
      S32 curObject = 0, curDecal = 0; // for tracking skipped meshes
      for (i=0; i<meshIndexListSize; i++)
      {
         bool skip = checkSkip(i,curObject,curDecal,skipDL);
         if (meshIndexList[i]>=0)
         {
            AssertFatal(meshIndexList[i]==next,"TSShape::read: assertion failed on obsolete shape");
            S32 meshType = alloc.get32();
            TSMesh * mesh = TSMesh::assembleMesh(meshType,skip);
            if (ptr32)
               ptr32[i] = skip ? 0 : (S32) mesh;
            next = meshIndexList[i]+1;

            // fill in location of verts, tverts, and normals for detail levels
            if (mesh && meshType!=TSMesh::DecalMeshType)
            {
               ToolVector<Point2F> diffuse;
               mesh->getUVs(TSMesh::tDiffuse, diffuse);

               TSMesh::smVertsList[i]  = mesh->verts.address();
               TSMesh::smTVertsList[i] = diffuse.address();
               TSMesh::smNormsList[i]  = mesh->norms.address();
               TSMesh::smEncodedNormsList[i]  = mesh->encodedNorms.address();
               TSMesh::smDataCopied[i] = !skip; // as long as we didn't skip this mesh, the data should be in shape now
               if (meshType==TSMesh::SkinMeshType)
               {
                  TSSkinMesh * skin = (TSSkinMesh*)mesh;
                  TSMesh::smVertsList[i]  = skin->initialVerts.address();
                  TSMesh::smNormsList[i]  = skin->initialNorms.address();
                  TSSkinMesh::smInitTransformList[i] = skin->initialTransforms.address();
                  TSSkinMesh::smVertexIndexList[i] = skin->vertexIndex.address();
                  TSSkinMesh::smBoneIndexList[i] = skin->boneIndex.address();
                  TSSkinMesh::smWeightList[i] = skin->weight.address();
                  TSSkinMesh::smNodeIndexList[i] = skin->nodeIndex.address();
               }
            }
         }
         else if (ptr32)
            ptr32[i] = 0; // no mesh
      }
      meshes.set(ptr32,meshIndexListSize);
   }

   alloc.checkGuard();

   // names
   bool namesInStringTable = (StringTable!=NULL);
   char * nameBufferStart = (char*)alloc.getPointer8(0);
   char * name = nameBufferStart;
   S32 nameBufferSize = 0;
   names.setSize(numNames);
   for (i=0; i<numNames; i++)
   {
      for (j=0; name[j]; j++)
         ;
      if (namesInStringTable)
         names[i] = StringTable->insert(name,false);
      nameBufferSize += j + 1;
      name += j + 1;
   }
   if (!namesInStringTable)
   {
      name = (char*)alloc.copyToShape8(nameBufferSize);
      if (name) // make sure we did copy (might just be getting size of buffer)
         for (i=0; i<numNames; i++)
         {
            for (j=0; name[j]; j++)
               ;
            names[i] = name;
            name += j + 1;
         }
   }
   else
      alloc.getPointer8(nameBufferSize);
   alloc.align32();

   alloc.checkGuard();

   if (smReadVersion<23)
   {
      // get detail information about skins...
      S32 * detailFirstSkin = alloc.getPointer32(numDetails);
      S32 * detailNumSkins = alloc.getPointer32(numDetails);

      alloc.checkGuard();

      // about to read in skins...clear out scratch space...
      if (numSkins)
      {
         TSSkinMesh::smInitTransformList.setSize(numSkins);
         TSSkinMesh::smVertexIndexList.setSize(numSkins);
         TSSkinMesh::smBoneIndexList.setSize(numSkins);
         TSSkinMesh::smWeightList.setSize(numSkins);
         TSSkinMesh::smNodeIndexList.setSize(numSkins);
      }
      for (i=0; i<numSkins; i++)
      {
         TSMesh::smVertsList[i]=NULL;
         TSMesh::smTVertsList[i]=NULL;
         TSMesh::smNormsList[i]=NULL;
         TSMesh::smEncodedNormsList[i]=NULL;
         TSMesh::smDataCopied[i]=false;
         TSSkinMesh::smInitTransformList[i] = NULL;
         TSSkinMesh::smVertexIndexList[i] = NULL;
         TSSkinMesh::smBoneIndexList[i] = NULL;
         TSSkinMesh::smWeightList[i] = NULL;
         TSSkinMesh::smNodeIndexList[i] = NULL;
      }

      // skins
      ptr32 = alloc.allocShape32(numSkins);
      for (i=0; i<numSkins; i++)
      {
         bool skip = i<detailFirstSkin[skipDL];
         TSSkinMesh * skin = (TSSkinMesh*)TSMesh::assembleMesh(TSMesh::SkinMeshType,skip);
         if (meshes.address())
         {
            // add pointer to skin in shapes list of meshes
            // we reserved room for this above...
            meshes.set(meshes.address(),meshes.size()+1);
            meshes[meshes.size()-1] = skip ? NULL : skin;
         }

         // fill in location of verts, tverts, and normals for shared detail levels
         if (skin)
         {
            ToolVector<Point2F> diffuse;
            skin->getUVs(TSMesh::tDiffuse, diffuse);

            TSMesh::smVertsList[i]  = skin->initialVerts.address();
            TSMesh::smTVertsList[i] = diffuse.address();
            TSMesh::smNormsList[i]  = skin->initialNorms.address();
            TSMesh::smEncodedNormsList[i]  = skin->encodedNorms.address();
            TSMesh::smDataCopied[i] = !skip; // as long as we didn't skip this mesh, the data should be in shape now
            TSSkinMesh::smInitTransformList[i] = skin->initialTransforms.address();
            TSSkinMesh::smVertexIndexList[i] = skin->vertexIndex.address();
            TSSkinMesh::smBoneIndexList[i] = skin->boneIndex.address();
            TSSkinMesh::smWeightList[i] = skin->weight.address();
            TSSkinMesh::smNodeIndexList[i] = skin->nodeIndex.address();
         }
      }

      alloc.checkGuard();

      // we now have skins in mesh list...add skin objects to object list and patch things up
      fixupOldSkins(numMeshes,numSkins,numDetails,detailFirstSkin,detailNumSkins);
   }

   // allocate storage space for some arrays (filled in during Shape::init)...
   ptr32 = alloc.allocShape32(numDetails);
   alphaIn.set(ptr32,numDetails);
   ptr32 = alloc.allocShape32(numDetails);
   alphaOut.set(ptr32,numDetails);

	mPreviousMerge.setSize(numObjects);
	for (i = 0; i < numObjects; ++i)
		mPreviousMerge[i] = -1;

	mExportMerge = smReadVersion >= 23;
}

void TSShape::disassembleShape()
{
   S32 i;

   // set counts...
   S32 numNodes = alloc.set32(nodes.size());
   S32 numObjects = alloc.set32(objects.size());
   S32 numDecals = alloc.set32(decals.size());
   S32 numSubShapes = alloc.set32(subShapeFirstNode.size());
   S32 numIflMaterials = alloc.set32(iflMaterials.size());
   S32 numNodeRotations = alloc.set32(nodeRotations.size());
   S32 numNodeTranslations = alloc.set32(nodeTranslations.size());
   S32 numNodeUniformScales = alloc.set32(nodeUniformScales.size());
   S32 numNodeAlignedScales = alloc.set32(nodeAlignedScales.size());
   S32 numNodeArbitraryScales = alloc.set32(nodeArbitraryScaleFactors.size());
   S32 numGroundFrames = alloc.set32(groundTranslations.size());
   S32 numObjectStates = alloc.set32(objectStates.size());
   S32 numDecalStates = alloc.set32(decalStates.size());
   S32 numTriggers = alloc.set32(triggers.size());
   S32 numDetails = alloc.set32(details.size());
   S32 numMeshes = alloc.set32(meshes.size());
   S32 numNames = alloc.set32(names.size());
   alloc.set32((S32)mSmallestVisibleSize);
   alloc.set32(mSmallestVisibleDL);

   alloc.setGuard();

   // get bounds...
   alloc.copyToBuffer32((S32*)&radius,1);
   alloc.copyToBuffer32((S32*)&tubeRadius,1);
   alloc.copyToBuffer32((S32*)&center,3);
   alloc.copyToBuffer32((S32*)&bounds,6);

   alloc.setGuard();

   // copy various vectors...
   alloc.copyToBuffer32((S32*)nodes.address(),numNodes*5);
   alloc.setGuard();
   alloc.copyToBuffer32((S32*)objects.address(),numObjects*6);
   alloc.setGuard();
   alloc.copyToBuffer32((S32*)decals.address(),numDecals*5);
   alloc.setGuard();
   alloc.copyToBuffer32((S32*)iflMaterials.address(),numIflMaterials*5);
   alloc.setGuard();
   alloc.copyToBuffer32((S32*)subShapeFirstNode.address(),numSubShapes);
   alloc.copyToBuffer32((S32*)subShapeFirstObject.address(),numSubShapes);
   alloc.copyToBuffer32((S32*)subShapeFirstDecal.address(),numSubShapes);
   alloc.setGuard();
   alloc.copyToBuffer32((S32*)subShapeNumNodes.address(),numSubShapes);
   alloc.copyToBuffer32((S32*)subShapeNumObjects.address(),numSubShapes);
   alloc.copyToBuffer32((S32*)subShapeNumDecals.address(),numSubShapes);
   alloc.setGuard();

   // default transforms...
   alloc.copyToBuffer16((S16*)defaultRotations.address(),numNodes*4);
   alloc.copyToBuffer32((S32*)defaultTranslations.address(),numNodes*3);

   // animated transforms...
   alloc.copyToBuffer16((S16*)nodeRotations.address(),numNodeRotations*4);
   alloc.copyToBuffer32((S32*)nodeTranslations.address(),numNodeTranslations*3);

   alloc.setGuard();

   // ...with scale
   alloc.copyToBuffer32((S32*)nodeUniformScales.address(),numNodeUniformScales);
   alloc.copyToBuffer32((S32*)nodeAlignedScales.address(),numNodeAlignedScales*3);
   alloc.copyToBuffer32((S32*)nodeArbitraryScaleFactors.address(),numNodeArbitraryScales*3);
   alloc.copyToBuffer16((S16*)nodeArbitraryScaleRots.address(),numNodeArbitraryScales*4);

   alloc.setGuard();

   alloc.copyToBuffer32((S32*)groundTranslations.address(),3*numGroundFrames);
   alloc.copyToBuffer16((S16*)groundRotations.address(),4*numGroundFrames);

   alloc.setGuard();

   // object states..
   alloc.copyToBuffer32((S32*)objectStates.address(),numObjectStates*3);
   alloc.setGuard();

   // decal states...
   alloc.copyToBuffer32((S32*)decalStates.address(),numDecalStates);
   alloc.setGuard();

   // frame triggers
   alloc.copyToBuffer32((S32*)triggers.address(),numTriggers*2);
   alloc.setGuard();

   // details
   alloc.copyToBuffer32((S32*)details.address(),numDetails*7);
   alloc.setGuard();

   // read in the meshes (sans skins)...
   bool * isMesh = new bool[numMeshes]; // funny business because decals are pretend meshes (legacy issue)
   for (i=0;i<numMeshes;i++)
      isMesh[i]=false;
   for (i=0; i<objects.size(); i++)
   {
      for (S32 j=0; j<objects[i].numMeshes; j++)
         // even if an empty mesh, it's a mesh...
         isMesh[objects[i].startMeshIndex+j]=true;
   }
   for (i=0; i<numMeshes; i++)
   {
      TSMesh * mesh = NULL;
      TSDecalMesh * decalMesh = NULL;
      if (isMesh[i])
         mesh = meshes[i];
      else
         decalMesh = (TSDecalMesh*)meshes[i];
      alloc.set32(mesh ? mesh->getMeshType() : (decalMesh ? TSMesh::DecalMeshType : TSMesh::NullMeshType));
      if (mesh)
         mesh->disassemble();
      else if (decalMesh)
         decalMesh->disassemble();
   }
   delete [] isMesh;
   alloc.setGuard();

   // names
   for (i=0; i<numNames; i++)
      alloc.copyToBuffer8((S8*)names[i],dStrlen(names[i])+1);
   alloc.setGuard();
}

//-------------------------------------------------
// write whole shape
//-------------------------------------------------
void TSShape::write(Stream * s)
{
   // write version
   s->write(smVersion | (mExporterVersion<<16));

   alloc.setWrite();
   disassembleShape();

   S32     * buffer32 = alloc.getBuffer32();
   S16     * buffer16 = alloc.getBuffer16();
   S8      * buffer8  = alloc.getBuffer8();

   S32 size32 = alloc.getBufferSize32();
   S32 size16 = alloc.getBufferSize16();
   S32 size8  = alloc.getBufferSize8();

   // convert sizes to dwords...
   if (size16 & 1)
      size16 += 2;
   size16 >>= 1;
   if (size8 & 3)
      size8 += 4;
   size8 >>= 2;

   S32 sizeMemBuffer, start16, start8;
   sizeMemBuffer = size32 + size16 + size8;
   start16 = size32;
   start8 = start16+size16;

   // in dwords -- write will properly endian-flip.
   s->write(sizeMemBuffer);
   s->write(start16);
   s->write(start8);

	// endian-flip the entire write buffers.
   fixEndian(buffer32,buffer16,buffer8,size32,size16,size8);

   // now write buffers
   s->write(size32*4,buffer32);
   s->write(size16*4,buffer16);
   s->write(size8 *4,buffer8);

   // write sequences - write will properly endian-flip.
   s->write(sequences.size());
   for (S32 i=0; i<sequences.size(); i++)
      sequences[i].write(s);

   // write material list - write will properly endian-flip.
   materialList->write(*s);

   delete [] buffer32;
   delete [] buffer16;
   delete [] buffer8;
}

//-------------------------------------------------
// read whole shape
//-------------------------------------------------

bool TSShape::read(Stream * s)
{
   // read version - read handles endian-flip
   s->read(&smReadVersion);
   mExporterVersion = smReadVersion >> 16;
   smReadVersion &= 0xFF;
   if (smReadVersion>smVersion)
   {
      // error -- don't support future versions yet :>
      Con::errorf(ConsoleLogEntry::General,
                  "Error: attempt to load a version %i dts-shape, can currently only load version %i and before.",
                   smReadVersion,smVersion);
      return false;
   }
   mReadVersion = smReadVersion;

   S32 * memBuffer32;
   S16 * memBuffer16;
   S8 * memBuffer8;
   S32 count32, count16, count8;
   if (mReadVersion<19)
   {
   	Con::printf("... Shape with old version.");
      readOldShape(s,memBuffer32,memBuffer16,memBuffer8,count32,count16,count8);
   }
   else
   {
      S32 i;
      U32 sizeMemBuffer, startU16, startU8;

      // in dwords. - read handles endian-flip
      s->read(&sizeMemBuffer);
      s->read(&startU16);
      s->read(&startU8);

      if (s->getStatus()!=Stream::Ok)
      {
         Con::errorf(ConsoleLogEntry::General, "Error: bad shape file.");
         return false;
      }

      S32 * tmp = new S32[sizeMemBuffer];
      s->read(sizeof(S32)*sizeMemBuffer,(U8*)tmp);
      memBuffer32 = tmp;
      memBuffer16 = (S16*)(tmp+startU16);
      memBuffer8  = (S8*)(tmp+startU8);

      count32 = startU16;
      count16 = startU8-startU16;
      count8  = sizeMemBuffer-startU8;

      // read sequences
      S32 numSequences;
      s->read(&numSequences);
      sequences.setSize(numSequences);
      for (i=0; i<numSequences; i++)
      {
         constructInPlace(&sequences[i]);
         sequences[i].read(s);
      }

      // read material list
      delete materialList; // just in case...
      materialList = new TSMaterialList;
      materialList->read(*s);
   }

	// since we read in the buffers, we need to endian-flip their entire contents...
   fixEndian(memBuffer32,memBuffer16,memBuffer8,count32,count16,count8);

   alloc.setRead(memBuffer32,memBuffer16,memBuffer8,true);
   assembleShape(); // determine size of buffer needed
   S32 buffSize = alloc.getSize();
   alloc.doAlloc();
   mMemoryBlock = alloc.getBuffer();
   alloc.setRead(memBuffer32,memBuffer16,memBuffer8,false);
   assembleShape(); // copy to buffer
   AssertFatal(alloc.getSize()==buffSize,"TSShape::read: shape data buffer size mis-calculated");

   if (smReadVersion<19)
   {
      delete [] memBuffer32;
      delete [] memBuffer16;
      delete [] memBuffer8;
   }
   else
      delete [] memBuffer32; // this covers all the buffers

   if (smInitOnRead)
      init();
   return true;
}

void TSShape::fixEndian(S32 * buff32, S16 * buff16, S8 *, S32 count32, S32 count16, S32)
{
	// if endian-ness isn't the same, need to flip the buffer contents.
   if (0x12345678!=convertLEndianToHost(0x12345678))
   {
      for (S32 i=0; i<count32; i++)
         buff32[i]=convertLEndianToHost(buff32[i]);
      for (S32 i=0; i<count16*2; i++)
         buff16[i]=convertLEndianToHost(buff16[i]);
   }
}

/** Read the .ifl material file
   The .ifl file (output by max) is essentially a keyframe list for
   the animation and contains the names of the textures along
   with a duration time.  This function parses the file to and appends
   the textures to the shape's master material list.
*/
void TSShape::readIflMaterials(const char* shapePath)
{
   if (mFlags & IflInit)
      return;

   for (S32 i=0; i<iflMaterials.size(); i++)
   {
      IflMaterial & iflMaterial = iflMaterials[i];

      iflMaterial.firstFrame = materialList->size(); // next material added will be our first frame
      iflMaterial.firstFrameOffTimeIndex = iflFrameOffTimes.size(); // next entry added will be our first
      iflMaterial.numFrames = 0; // we'll increment as we read the file

      U32 totalTime = 0;

      // Fix slashes that face the wrong way
      char * pName = (char*)getName(iflMaterial.nameIndex);
      S32 len = dStrlen(pName);
      for (S32 j=0; j<len; j++)
         if (pName[j]=='\\')
            pName[j]='/';

      // Open the file which should be located in the same directory
      // as the .dts shape.
      // Tg: I cut out a some backwards compatibilty code dealing
      // with the old file prefixing. If someone is having compatibilty
      // problems, you may want to check the previous version of this
      // file.

      char nameBuffer[256];
      dSprintf(nameBuffer,sizeof(nameBuffer),"%s/%s",shapePath,pName);

      Stream * s = ResourceManager->openStream(nameBuffer);
      if (!s || s->getStatus() != Stream::Ok)
      {
         iflMaterial.firstFrame = iflMaterial.materialSlot; // avoid over-runs
         if (s)
            ResourceManager->closeStream(s);
         continue;
      }

      // Parse the file, creat ifl "key" frames and append
      // texture names to the shape's material list.
      char buffer[512];
      U32 duration;
      while (s->getStatus() == Stream::Ok)
      {
         s->readLine((U8*)buffer,512);
         if (s->getStatus() == Stream::Ok || s->getStatus() == Stream::EOS)
         {
            char * pos = buffer;
            while (*pos)
            {
               if (*pos=='\t')
                  *pos=' ';
               pos++;
            }
            pos = buffer;

            // Find the name and duration start points
            while (*pos && *pos==' ')
               pos++;
            char * pos2 = dStrchr(pos,' ');
            if (pos2)
            {
               *pos2='\0';
               pos2++;
               while (*pos2 && *pos2==' ')
                  pos2++;
            }

            // skip line with no material
            if (!*pos)
               continue;

            // Extract frame duration
            duration = pos2 ? dAtoi(pos2) : 1;
            if (duration==0)
               // just in case...
               duration = 1;

            // Tg: I cut out a some backwards compatibilty code dealing
            // with the old file prefixing. If someone is having compatibilty
            // problems, you may want to check the previous version of this
            // file.
            // Strip off texture extension first.
            if ((pos2 = dStrchr(pos,'.')) != 0)
               *pos2 = '\0';
            materialList->push_back(pos,TSMaterialList::IflFrame);

            //
            totalTime += duration;
            iflFrameOffTimes.push_back((1.0f/30.0f) * (F32)totalTime); // ifl units are frames (1 frame = 1/30 sec)
            iflMaterial.numFrames++;
         }
      }

      // close the file...
      ResourceManager->closeStream(s);
   }
   initMaterialList();
   mFlags |= IflInit;
}

ResourceInstance *constructTSShape(Stream &stream)
{
   TSShape * ret = new TSShape;
   
   if (!ret->read(&stream))
   {
      // Failed, so clean up and set to NULL so we'll return NULL.
      SAFE_DELETE(ret);
   }

   return ret;
}


#if 1
TSShape::ConvexHullAccelerator* TSShape::getAccelerator(S32 dl)
{
   AssertFatal(dl < details.size(), "Error, bad detail level!");
   if (dl == -1)
      return NULL;

   if (detailCollisionAccelerators[dl] == NULL)
      computeAccelerator(dl);

   AssertFatal(detailCollisionAccelerators[dl] != NULL, "This should be non-null after computing it!");
   return detailCollisionAccelerators[dl];
}


void TSShape::computeAccelerator(S32 dl)
{
   AssertFatal(dl < details.size(), "Error, bad detail level!");

   // Have we already computed this?
   if (detailCollisionAccelerators[dl] != NULL)
      return;

   // Create a bogus features list...
   ConvexFeature cf;
   MatrixF mat(true);
   Point3F n(0, 0, 1);

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

   S32 start = subShapeFirstObject[ss];
   S32 end   = subShapeNumObjects[ss] + start;
   if (start < end)
   {
      // run through objects and collide
      // DMMNOTE: This assumes that the transform of the collision hulls is
      //  identity...
      U32 surfaceKey = 0;
      for (S32 i = start; i < end; i++)
      {
         const TSObject* obj = &objects[i];

         if (obj->numMeshes && od < obj->numMeshes) {
            TSMesh* mesh = meshes[obj->startMeshIndex + od];
            if (mesh)
               mesh->getFeatures(0, mat, n, &cf, surfaceKey);
         }
      }
   }

   Vector<Point3F> fixedVerts;
   VECTOR_SET_ASSOCIATION(fixedVerts);
   S32 i;
   for (i = 0; i < cf.mVertexList.size(); i++) {
      S32 j;
      bool found = false;
      for (j = 0; j < cf.mFaceList.size(); j++) {
         if (cf.mFaceList[j].vertex[0] == i ||
             cf.mFaceList[j].vertex[1] == i ||
             cf.mFaceList[j].vertex[2] == i) {
            found = true;
            break;
         }
      }
      if (!found)
         continue;

      found = false;
      for (j = 0; j < fixedVerts.size(); j++) {
         if (fixedVerts[j] == cf.mVertexList[i]) {
            found = true;
            break;
         }
      }
      if (found == true) {
         // Ok, need to replace any references to vertex i in the facelists with
         //  a reference to vertex j in the fixed list
         for (S32 k = 0; k < cf.mFaceList.size(); k++) {
            for (S32 l = 0; l < 3; l++) {
               if (cf.mFaceList[k].vertex[l] == i)
                  cf.mFaceList[k].vertex[l] = j;
            }
         }
      } else {
         for (S32 k = 0; k < cf.mFaceList.size(); k++) {
            for (S32 l = 0; l < 3; l++) {
               if (cf.mFaceList[k].vertex[l] == i)
                  cf.mFaceList[k].vertex[l] = fixedVerts.size();
            }
         }
         fixedVerts.push_back(cf.mVertexList[i]);
      }
   }
   cf.mVertexList.setSize(0);
   cf.mVertexList = fixedVerts;

   // Ok, so now we have a vertex list.  Lets copy that out...
   ConvexHullAccelerator* accel = new ConvexHullAccelerator;
   detailCollisionAccelerators[dl] = accel;
   accel->numVerts    = cf.mVertexList.size();
   accel->vertexList  = new Point3F[accel->numVerts];
   dMemcpy(accel->vertexList, cf.mVertexList.address(), sizeof(Point3F) * accel->numVerts);

   accel->normalList = new PlaneF[cf.mFaceList.size()];
   for (i = 0; i < cf.mFaceList.size(); i++)
      accel->normalList[i] = cf.mFaceList[i].normal;

   accel->emitStrings = new U8*[accel->numVerts];
   dMemset(accel->emitStrings, 0, sizeof(U8*) * accel->numVerts);

   for (i = 0; i < accel->numVerts; i++) {
      S32 j;

      Vector<U32> faces;
      VECTOR_SET_ASSOCIATION(faces);
      for (j = 0; j < cf.mFaceList.size(); j++) {
         if (cf.mFaceList[j].vertex[0] == i ||
             cf.mFaceList[j].vertex[1] == i ||
             cf.mFaceList[j].vertex[2] == i) {
            faces.push_back(j);
         }
      }
      AssertFatal(faces.size() != 0, "Huh?  Vertex unreferenced by any faces");

      // Insert all faces that didn't make the first cut, but share a plane with
      //  a face that's on the short list.
      for (j = 0; j < cf.mFaceList.size(); j++) {
         bool found = false;
         S32 k;
         for (k = 0; k < faces.size(); k++) {
            if (faces[k] == j)
               found = true;
         }
         if (found)
            continue;

         found = false;
         for (k = 0; k < faces.size(); k++) {
            if (mDot(accel->normalList[faces[k]], accel->normalList[j]) > 0.999) {
               found = true;
               break;
            }
         }
         if (found)
            faces.push_back(j);
      }

      Vector<U32> vertRemaps;
      VECTOR_SET_ASSOCIATION(vertRemaps);
      for (j = 0; j < faces.size(); j++) {
         for (U32 k = 0; k < 3; k++) {
            U32 insert = cf.mFaceList[faces[j]].vertex[k];
            bool found = false;
            for (S32 l = 0; l < vertRemaps.size(); l++) {
               if (insert == vertRemaps[l]) {
                  found = true;
                  break;
               }
            }
            if (!found)
               vertRemaps.push_back(insert);
         }
      }

      Vector<Point2I> edges;
      VECTOR_SET_ASSOCIATION(edges);
      for (j = 0; j < faces.size(); j++) {
         for (U32 k = 0; k < 3; k++) {
            U32 edgeStart = cf.mFaceList[faces[j]].vertex[(k + 0) % 3];
            U32 edgeEnd   = cf.mFaceList[faces[j]].vertex[(k + 1) % 3];

            U32 e0 = getMin(edgeStart, edgeEnd);
            U32 e1 = getMax(edgeStart, edgeEnd);

            bool found = false;
            for (S32 l = 0; l < edges.size(); l++) {
               if (edges[l].x == e0 && edges[l].y == e1) {
                  found = true;
                  break;
               }
            }
            if (!found)
               edges.push_back(Point2I(e0, e1));
         }
      }

      AssertFatal(vertRemaps.size() < 256 && faces.size() < 256 && edges.size() < 256,
                  "Error, ran over the shapebase assumptions about convex hulls.");

      U32 emitStringLen = 1 + vertRemaps.size()  +
                          1 + (edges.size() * 2) +
                          1 + (faces.size() * 4);
      accel->emitStrings[i] = new U8[emitStringLen];

      U32 currPos = 0;

      accel->emitStrings[i][currPos++] = vertRemaps.size();
      for (j = 0; j < vertRemaps.size(); j++)
         accel->emitStrings[i][currPos++] = vertRemaps[j];

      accel->emitStrings[i][currPos++] = edges.size();
      for (j = 0; j < edges.size(); j++) {
         S32 l;
         U32 old = edges[j].x;
         bool found = false;
         for (l = 0; l < vertRemaps.size(); l++) {
            if (vertRemaps[l] == old) {
               found = true;
               accel->emitStrings[i][currPos++] = l;
               break;
            }
         }
         AssertFatal(found, "Error, couldn't find the remap!");

         old = edges[j].y;
         found = false;
         for (l = 0; l < vertRemaps.size(); l++) {
            if (vertRemaps[l] == old) {
               found = true;
               accel->emitStrings[i][currPos++] = l;
               break;
            }
         }
         AssertFatal(found, "Error, couldn't find the remap!");
      }

      accel->emitStrings[i][currPos++] = faces.size();
      for (j = 0; j < faces.size(); j++) {
         accel->emitStrings[i][currPos++] = faces[j];
         for (U32 k = 0; k < 3; k++) {
            U32 old = cf.mFaceList[faces[j]].vertex[k];
            bool found = false;
            for (S32 l = 0; l < vertRemaps.size(); l++) {
               if (vertRemaps[l] == old) {
                  found = true;
                  accel->emitStrings[i][currPos++] = l;
                  break;
               }
            }
            AssertFatal(found, "Error, couldn't find the remap!");
         }
      }
      AssertFatal(currPos == emitStringLen, "Error, over/underflowed the emission string!");
   }
}
#endif