| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183 | //-----------------------------------------------------------------------------// Copyright (c) 2012 GarageGames, LLC//// Permission is hereby granted, free of charge, to any person obtaining a copy// of this software and associated documentation files (the "Software"), to// deal in the Software without restriction, including without limitation the// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or// sell copies of the Software, and to permit persons to whom the Software is// furnished to do so, subject to the following conditions://// The above copyright notice and this permission notice shall be included in// all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS// IN THE SOFTWARE.//-----------------------------------------------------------------------------#include "platform/platform.h"#include "console/consoleTypes.h"#include "core/resourceManager.h"#include "ts/tsShape.h"#include "ts/tsShapeInstance.h"#include "ts/tsLastDetail.h"#include "ts/tsMaterialList.h"#include "core/stream/fileStream.h"#include "core/volume.h"//-----------------------------------------------------------------------------S32 TSShape::addName(const String& name){   // Check for empty names   if (name.isEmpty())      return -1;   // Return the index of the new name (add if it is unique)   S32 index = findName(name);   if (index >= 0)      return index;   names.push_back(StringTable->insert(name));   return names.size()-1;}void TSShape::updateSmallestVisibleDL(){   // Update smallest visible detail   mSmallestVisibleDL = -1;   mSmallestVisibleSize = F32_MAX;   F32 maxSize = 0.0f;   for (S32 i = 0; i < details.size(); i++)   {      maxSize = getMax( maxSize, details[i].size );      if ((details[i].size >= 0) && (details[i].size < mSmallestVisibleSize))      {         mSmallestVisibleDL = i;         mSmallestVisibleSize = details[i].size;      }   }   // Initialize the detail level lod lookup table.   mDetailLevelLookup.setSize( (U32)( maxSize * 2.0f ) + 2 );   for ( U32 l=0; l < mDetailLevelLookup.size(); l++ )   {      F32 pixelSize = (F32)l;      S32 dl = -1;      for ( U32 d=0; d < details.size(); d++ )      {         // Break when we get to hidden detail          // levels like collision shapes.         if ( details[d].size < 0 )            break;         if ( pixelSize > details[d].size )         {            dl = d;            break;         }         if ( d + 1 >= details.size() || details[d+1].size < 0 )         {            // We've run out of details and haven't found anything?            // Let's just grab this one.            dl = d;            break;         }      }      // Calculate the intra detail level.      F32 intraDL = 0;      if ( dl > -1 )      {         F32 curSize = details[dl].size;         F32 nextSize = dl == 0 ? 2.0f * curSize : details[dl - 1].size;         intraDL = mClampF( nextSize - curSize > 0.01f ? (pixelSize - curSize) / (nextSize - curSize) : 1.0f, 0, 1 );      }      mDetailLevelLookup[l].set( dl, intraDL );   }   // Test for using the legacy screen error   // lod method here instead of runtime.   //   // See setDetailFromDistance().   //   mUseDetailFromScreenError =   mSmallestVisibleDL >= 0 &&                                  details.first().maxError >= 0;}S32 TSShape::addDetail(const String& dname, S32 size, S32 subShapeNum){   S32 nameIndex = addName(avar("%s%d", dname.c_str(), size));   // Check if this detail size has already been added   S32 index;   for (index = 0; index < details.size(); index++)   {      if ((details[index].size == size) &&         (details[index].subShapeNum == subShapeNum) &&         (details[index].nameIndex == nameIndex))         return index;      if (details[index].size < size)         break;   }   // Create a new detail level at the right index, so array   // remains sorted by detail size (from largest to smallest)   details.insert(index);   TSShape::Detail &detail = details[index];   // Clear the detail to ensure no garbage values   // are left in any vars we don't set.   dMemset( &detail, 0, sizeof( Detail ) );   // Setup the detail.   detail.nameIndex = nameIndex;   detail.size = size;   detail.subShapeNum = subShapeNum;   detail.objectDetailNum = 0;   detail.averageError = -1;   detail.maxError = -1;   detail.polyCount = 0;   // Resize alpha vectors   alphaIn.increment();   alphaOut.increment();   // Fixup objectDetailNum in other detail levels   for (S32 i = index+1; i < details.size(); i++)   {      if ((details[i].subShapeNum >= 0) &&         ((subShapeNum == -1) || (details[i].subShapeNum == subShapeNum)))         details[i].objectDetailNum++;   }   // Update smallest visible detail   updateSmallestVisibleDL();   return index;}S32 TSShape::addImposter(const String& cachePath, S32 size, S32 numEquatorSteps,                        S32 numPolarSteps, S32 dl, S32 dim, bool includePoles, F32 polarAngle){   // Check if the desired size is already in use   bool isNewDetail = false;   S32 detIndex = findDetailBySize( size );   if ( detIndex >= 0 )   {      // Size is in use. If the detail is already an imposter, we can just change      // the settings, otherwise quit      if ( details[detIndex].subShapeNum >= 0 )      {         Con::errorf( "TSShape::addImposter: A non-billboard detail already "            "exists at size %d", size );         return -1;      }   }   else   {      // Size is not in use. If an imposter already exists, change its size, otherwise      // create a new detail      for ( detIndex = 0; detIndex < details.size(); ++detIndex )      {         if ( details[detIndex].subShapeNum < 0 )         {            // Change the imposter detail size            setDetailSize( details[detIndex].size, size );            break;         }      }      if ( detIndex == details.size() )      {         isNewDetail = true;         detIndex = addDetail( "bbDetail", size, -1 );      }   }   // Now set the billboard properties.   Detail &detail = details[detIndex];   // In prior to DTS version 26 we would pack the autobillboard   // into this single 32bit value.  This was prone to overflows   // of parameters caused random bugs.   //   // Set the old autobillboard properties var to zero.   detail.objectDetailNum = 0;      // We now use the new vars.   detail.bbEquatorSteps = numEquatorSteps;   detail.bbPolarSteps = numPolarSteps;   detail.bbPolarAngle = polarAngle;   detail.bbDetailLevel = dl;   detail.bbDimension = dim;   detail.bbIncludePoles = includePoles;   // Rebuild billboard details or force an update of the modified detail   if ( isNewDetail )   {      // Add NULL meshes for this detail      for ( S32 iObj = 0; iObj < objects.size(); ++iObj )      {         if ( detIndex < objects[iObj].numMeshes )         {            objects[iObj].numMeshes++;            meshes.insert( objects[iObj].startMeshIndex + detIndex, NULL );            for (S32 j = iObj + 1; j < objects.size(); ++j )               objects[j].startMeshIndex++;         }      }      // Could be dedicated server.      if ( GFXDevice::devicePresent() )         setupBillboardDetails( cachePath );      while ( detailCollisionAccelerators.size() < details.size() )         detailCollisionAccelerators.push_back( NULL );   }   else   {      if ( billboardDetails.size() && GFXDevice::devicePresent() )      {         delete billboardDetails[detIndex];         billboardDetails[detIndex] = new TSLastDetail(                                          this,                                          cachePath,                                          detail.bbEquatorSteps,                                          detail.bbPolarSteps,                                          detail.bbPolarAngle,                                          detail.bbIncludePoles,                                          detail.bbDetailLevel,                                          detail.bbDimension );         billboardDetails[detIndex]->update( true );      }   }   return detIndex;}bool TSShape::removeImposter(){   // Find the imposter detail level   S32 detIndex;   for ( detIndex = 0; detIndex < details.size(); ++detIndex )   {      if ( details[detIndex].subShapeNum < 0 )         break;   }   if ( detIndex == details.size() )   {      Con::errorf( "TSShape::removeImposter: No imposter detail level found in shape" );      return false;   }   // Remove the detail level   details.erase( detIndex );   if ( detIndex < billboardDetails.size() )   {      // Delete old textures      TSLastDetail* bb = billboardDetails[detIndex];      bb->deleteImposterCacheTextures();      delete billboardDetails[detIndex];   }   billboardDetails.clear();   detailCollisionAccelerators.erase( detIndex );   // Remove the (NULL) meshes from each object   for ( S32 iObj = 0; iObj < objects.size(); ++iObj )   {      if ( detIndex < objects[iObj].numMeshes )      {         objects[iObj].numMeshes--;         meshes.erase( objects[iObj].startMeshIndex + detIndex );         for (S32 j = iObj + 1; j < objects.size(); ++j )            objects[j].startMeshIndex--;      }   }   // Update smallest visible size   updateSmallestVisibleDL();   return true;}//-----------------------------------------------------------------------------/// Get the index of the element in the group with a given nametemplate<class T> S32 findByName(Vector<T>& group, S32 nameIndex){   for (S32 i = 0; i < group.size(); i++)      if (group[i].nameIndex == nameIndex)         return i;   return -1;}/// Adjust the nameIndex for elements in the grouptemplate<class T> void adjustForNameRemoval(Vector<T>& group, S32 nameIndex){   for (S32 i = 0; i < group.size(); i++)      if (group[i].nameIndex > nameIndex)         group[i].nameIndex--;}bool TSShape::removeName(const String& name){   // Check if the name is still in use   S32 nameIndex = findName(name);   if ((findByName(nodes, nameIndex) >= 0)      ||       (findByName(objects, nameIndex) >= 0)    ||       (findByName(sequences, nameIndex) >= 0)  ||       (findByName(details, nameIndex) >= 0))       return false;   // Remove the name, then update nameIndex for affected elements   names.erase(nameIndex);   adjustForNameRemoval(nodes, nameIndex);   adjustForNameRemoval(objects, nameIndex);   adjustForNameRemoval(sequences, nameIndex);   adjustForNameRemoval(details, nameIndex);   return true;}//-----------------------------------------------------------------------------template<class T> bool doRename(TSShape* shape, Vector<T>& group, const String& oldName, const String& newName){   // Find the element in the group with the oldName   S32 index = findByName(group, shape->findName(oldName));   if (index < 0)   {      Con::errorf("TSShape::rename: Could not find '%s'", oldName.c_str());      return false;   }   // Ignore trivial renames   if (oldName.equal(newName, String::NoCase))      return true;   // Check that this name is not already in use   if (findByName(group, shape->findName(newName)) >= 0)   {      Con::errorf("TSShape::rename: '%s' is already in use", newName.c_str());      return false;   }   // Do the rename (the old name will be removed if it is no longer in use)   group[index].nameIndex = shape->addName(newName);   shape->removeName(oldName);   return true;}bool TSShape::renameNode(const String& oldName, const String& newName){   return doRename(this, nodes, oldName, newName);}bool TSShape::renameObject(const String& oldName, const String& newName){   return doRename(this, objects, oldName, newName);}bool TSShape::renameDetail(const String& oldName, const String& newName){   return doRename(this, details, oldName, newName);}bool TSShape::renameSequence(const String& oldName, const String& newName){   return doRename(this, sequences, oldName, newName);}//-----------------------------------------------------------------------------bool TSShape::addNode(const String& name, const String& parentName, const Point3F& pos, const QuatF& rot){   // Check that adding this node would not exceed the maximum count   if (nodes.size() >= MAX_TS_SET_SIZE)   {      Con::errorf("TSShape::addNode: Cannot add node, shape already has maximum (%d) nodes", MAX_TS_SET_SIZE);      return false;   }   // Check that there is not already a node with this name   if (findNode(name) >= 0)   {      Con::errorf("TSShape::addNode: %s already exists!", name.c_str());      return false;   }   // Find the parent node (OK for name to be empty => node is at root level)   S32 parentIndex = -1;   if (String::compare(parentName, ""))   {      parentIndex = findNode(parentName);      if (parentIndex < 0)      {         Con::errorf("TSShape::addNode: Could not find parent node '%s'", parentName.c_str());         return false;      }   }   // Need to make everything editable since node indexes etc will change   makeEditable();   // Insert node at the end of the subshape   S32 subShapeIndex = (parentIndex >= 0) ? getSubShapeForNode(parentIndex) : 0;   S32 nodeIndex = subShapeNumNodes[subShapeIndex];   // Adjust subshape node indices   subShapeNumNodes[subShapeIndex]++;   for (S32 i = subShapeIndex + 1; i < subShapeFirstNode.size(); i++)      subShapeFirstNode[i]++;   // Update animation sequences   for (S32 iSeq = 0; iSeq < sequences.size(); iSeq++)   {      // Update animation matters arrays (new node is not animated)      TSShape::Sequence& seq = sequences[iSeq];      seq.translationMatters.insert(nodeIndex, false);      seq.rotationMatters.insert(nodeIndex, false);      seq.scaleMatters.insert(nodeIndex, false);   }   // Insert the new node   TSShape::Node node;   node.nameIndex = addName(name);   node.parentIndex = parentIndex;   node.firstChild = -1;   node.firstObject = -1;   node.nextSibling = -1;   nodes.insert(nodeIndex, node);   // Insert node default translation and rotation   Quat16 rot16;   rot16.set(rot);   defaultTranslations.insert(nodeIndex, pos);   defaultRotations.insert(nodeIndex, rot16);   // Fixup node indices   for (S32 i = 0; i < nodes.size(); i++)   {      if (nodes[i].parentIndex >= nodeIndex)         nodes[i].parentIndex++;   }   for (S32 i = 0; i < objects.size(); i++)   {      if (objects[i].nodeIndex >= nodeIndex)         objects[i].nodeIndex++;   }   for (S32 i = 0; i < meshes.size(); i++)   {      if (meshes[i] && (meshes[i]->getMeshType() == TSMesh::SkinMeshType))      {         TSSkinMesh* skin = dynamic_cast<TSSkinMesh*>(meshes[i]);         for (S32 j = 0; j < skin->batchData.nodeIndex.size(); j++)         {            if (skin->batchData.nodeIndex[j] >= nodeIndex)               skin->batchData.nodeIndex[j]++;         }      }   }   initObjects();   return true;}/// Erase animation keyframes (translation, rotation etc)template<class T> S32 eraseStates(Vector<T>& vec, const TSIntegerSet& matters, S32 base, S32 numKeyframes, S32 index=-1){   S32 dest, count;   if (index == -1)   {      // Erase for all nodes/objects      dest = base;      count = numKeyframes * matters.count();   }   else   {      // Erase for the indexed node/object only      dest = base + matters.count(index)*numKeyframes;      count = numKeyframes;   }   // Erase the values   if (count)   {      if ((dest + count) < vec.size())         dCopyArray(&vec[dest], &vec[dest + count], vec.size() - (dest + count));      vec.decrement(count);   }   return count;}bool TSShape::removeNode(const String& name){   // Find the node to be removed   S32 nodeIndex = findNode(name);   if (nodeIndex < 0)   {      Con::errorf("TSShape::removeNode: Could not find node '%s'", name.c_str());      return false;   }   S32 nodeParentIndex = nodes[nodeIndex].parentIndex;   // Warn if there are objects attached to this node   Vector<S32> nodeObjects;   getNodeObjects(nodeIndex, nodeObjects);   if (nodeObjects.size())   {      Con::warnf("TSShape::removeNode: Node '%s' has %d objects attached, these "         "will be reassigned to the node's parent ('%s')", name.c_str(), nodeObjects.size(),         ((nodeParentIndex >= 0) ? getName(nodes[nodeParentIndex].nameIndex).c_str() : "null"));   }   // Need to make everything editable since node indexes etc will change   makeEditable();   // Update animation sequences   for (S32 iSeq = 0; iSeq < sequences.size(); iSeq++)   {      TSShape::Sequence& seq = sequences[iSeq];      // Remove animated node transforms      if (seq.translationMatters.test(nodeIndex))         eraseStates(nodeTranslations, seq.translationMatters, seq.baseTranslation, seq.numKeyframes, nodeIndex);      if (seq.rotationMatters.test(nodeIndex))         eraseStates(nodeRotations, seq.rotationMatters, seq.baseRotation, seq.numKeyframes, nodeIndex);      if (seq.scaleMatters.test(nodeIndex))      {         if (seq.flags & TSShape::ArbitraryScale)         {            eraseStates(nodeArbitraryScaleRots, seq.scaleMatters, seq.baseScale, seq.numKeyframes, nodeIndex);            eraseStates(nodeArbitraryScaleFactors, seq.scaleMatters, seq.baseScale, seq.numKeyframes, nodeIndex);         }         else if (seq.flags & TSShape::AlignedScale)            eraseStates(nodeAlignedScales, seq.scaleMatters, seq.baseScale, seq.numKeyframes, nodeIndex);         else            eraseStates(nodeUniformScales, seq.scaleMatters, seq.baseScale, seq.numKeyframes, nodeIndex);      }      seq.translationMatters.erase(nodeIndex);      seq.rotationMatters.erase(nodeIndex);      seq.scaleMatters.erase(nodeIndex);   }   // Remove the node   nodes.erase(nodeIndex);   defaultTranslations.erase(nodeIndex);   defaultRotations.erase(nodeIndex);   // Adjust subshape node indices   S32 subShapeIndex = getSubShapeForNode(nodeIndex);   subShapeNumNodes[subShapeIndex]--;   for (S32 i = subShapeIndex + 1; i < subShapeFirstNode.size(); i++)      subShapeFirstNode[i]--;   // Fixup node parent indices   for (S32 i = 0; i < nodes.size(); i++)   {      if (nodes[i].parentIndex == nodeIndex)         nodes[i].parentIndex = -1;      else if (nodes[i].parentIndex > nodeIndex)         nodes[i].parentIndex--;   }   if (nodeParentIndex > nodeIndex)      nodeParentIndex--;   // Fixup object node indices, and re-assign attached objects to node's parent   for (S32 i = 0; i < objects.size(); i++)   {      if (objects[i].nodeIndex == nodeIndex)         objects[i].nodeIndex = nodeParentIndex;      if (objects[i].nodeIndex > nodeIndex)         objects[i].nodeIndex--;   }   // Fixup skin weight node indices, and re-assign weights for deleted node to its parent   for (S32 i = 0; i < meshes.size(); i++)   {      if (meshes[i] && (meshes[i]->getMeshType() == TSMesh::SkinMeshType))      {         TSSkinMesh* skin = dynamic_cast<TSSkinMesh*>(meshes[i]);         for (S32 j = 0; j < skin->batchData.nodeIndex.size(); j++)         {            if (skin->batchData.nodeIndex[j] == nodeIndex)               skin->batchData.nodeIndex[j] = nodeParentIndex;            if (skin->batchData.nodeIndex[j] > nodeIndex)               skin->batchData.nodeIndex[j]--;         }      }   }   // Remove the sequence name if it is no longer in use   removeName(name);   initObjects();   return true;}//-----------------------------------------------------------------------------bool TSShape::setNodeTransform(const String& name, const Point3F& pos, const QuatF& rot){   // Find the node to be transformed   S32 nodeIndex = findNode(name);   if (nodeIndex < 0)   {      Con::errorf("TSShape::setNodeTransform: Could not find node '%s'", name.c_str());      return false;   }   // Update initial node position and rotation   defaultTranslations[nodeIndex] = pos;   defaultRotations[nodeIndex].set(rot);   return true;}//-----------------------------------------------------------------------------S32 TSShape::addObject(const String& objName, S32 subShapeIndex){   S32 objIndex = subShapeNumObjects[subShapeIndex];   // Add object to subshape   subShapeNumObjects[subShapeIndex]++;   for (S32 i = subShapeIndex + 1; i < subShapeFirstObject.size(); i++)      subShapeFirstObject[i]++;   TSShape::Object obj;   obj.nameIndex = addName(objName);   obj.nodeIndex = 0;   obj.numMeshes = 0;   obj.startMeshIndex = (objIndex == 0) ? 0 : objects[objIndex-1].startMeshIndex + objects[objIndex-1].numMeshes;   obj.firstDecal = 0;   obj.nextSibling = 0;   objects.insert(objIndex, obj);   // Add default object state   TSShape::ObjectState state;   state.frameIndex = 0;   state.matFrameIndex = 0;   state.vis = 1.0f;   objectStates.insert(objIndex, state);   // Fixup sequences   for (S32 i = 0; i < sequences.size(); i++)      sequences[i].baseObjectState++;   return objIndex;}void TSShape::addMeshToObject(S32 objIndex, S32 meshIndex, TSMesh* mesh){   TSShape::Object& obj = objects[objIndex];   // Pad with NULLs if required   S32 oldNumMeshes = obj.numMeshes;   if (mesh)   {      for (S32 i = obj.numMeshes; i < meshIndex; i++)      {         meshes.insert(obj.startMeshIndex + i, NULL);         obj.numMeshes++;      }   }   // Insert the new mesh   meshes.insert(obj.startMeshIndex + meshIndex, mesh);   obj.numMeshes++;   // Skinned meshes are not attached to any node   if (mesh && (mesh->getMeshType() == TSMesh::SkinMeshType))      obj.nodeIndex = -1;   // Fixup mesh indices for other objects   for (S32 i = 0; i < objects.size(); i++)   {      if ((i != objIndex) && (objects[i].startMeshIndex >= obj.startMeshIndex))         objects[i].startMeshIndex += (obj.numMeshes - oldNumMeshes);   }}void TSShape::removeMeshFromObject(S32 objIndex, S32 meshIndex){   TSShape::Object& obj = objects[objIndex];   // Remove the mesh, but do not destroy it (this must be done by the caller)   meshes[obj.startMeshIndex + meshIndex] = NULL;   // Check if there are any objects remaining that have a valid mesh at this   // detail size   bool removeDetail = true;   for (S32 i = 0; i < objects.size(); i++)   {      if ((meshIndex < objects[i].numMeshes) && meshes[objects[i].startMeshIndex + meshIndex])      {         removeDetail = false;         break;      }   }   // Remove detail level if possible   if (removeDetail)   {      for (S32 i = 0; i < objects.size(); i++)      {         if (meshIndex < objects[i].numMeshes)         {            U32 idxToRemove = objects[i].startMeshIndex + meshIndex;            meshes.erase(idxToRemove);            objects[i].numMeshes--;            // Clear invalid parent            for (U32 k = 0; k < meshes.size(); k++)            {               if (meshes[k] == NULL)                  continue;               if (meshes[k]->mParentMesh == idxToRemove)               {                  meshes[k]->mParentMesh = -1;               }               else if (meshes[k]->mParentMesh > idxToRemove)               {                  meshes[k]->mParentMesh--;               }            }            for (S32 j = 0; j < objects.size(); j++)            {               if (objects[j].startMeshIndex > objects[i].startMeshIndex)                  objects[j].startMeshIndex--;            }         }      }      Vector<S32> validDetails;      getSubShapeDetails(getSubShapeForObject(objIndex), validDetails);      for (S32 i = 0; i < validDetails.size(); i++)      {         TSShape::Detail& detail = details[validDetails[i]];         if (detail.objectDetailNum > meshIndex)            detail.objectDetailNum--;      }      details.erase(validDetails[meshIndex]);   }   // Remove trailing NULL meshes from the object   S32 oldNumMeshes = obj.numMeshes;   while (obj.numMeshes && !meshes[obj.startMeshIndex + obj.numMeshes - 1])   {      U32 idxToRemove = obj.startMeshIndex + obj.numMeshes - 1;      meshes.erase(idxToRemove);      // Clear invalid parent      for (U32 k = 0; k < meshes.size(); k++)      {         if (meshes[k] == NULL)            continue;         if (meshes[k]->mParentMesh == idxToRemove)         {            meshes[k]->mParentMesh = -1;         }         else if (meshes[k]->mParentMesh > idxToRemove)         {            meshes[k]->mParentMesh--;         }      }      obj.numMeshes--;   }   // Fixup mesh indices for other objects   for (S32 i = 0; i < objects.size(); i++)   {      if (objects[i].startMeshIndex > obj.startMeshIndex)         objects[i].startMeshIndex -= (oldNumMeshes - obj.numMeshes);   }}bool TSShape::setObjectNode(const String& objName, const String& nodeName){   // Find the object and node   S32 objIndex = findObject(objName);   if (objIndex < 0)   {      Con::errorf("TSShape::setObjectNode: Could not find object '%s'", objName.c_str());      return false;   }   S32 nodeIndex;   if (nodeName.isEmpty())      nodeIndex = -1;   else   {      nodeIndex = findNode(nodeName);      if (nodeIndex < 0)      {         Con::errorf("TSShape::setObjectNode: Could not find node '%s'", nodeName.c_str());         return false;      }   }   objects[objIndex].nodeIndex = nodeIndex;   return true;}bool TSShape::removeObject(const String& name){   // Find the object   S32 objIndex = findObject(name);   if (objIndex < 0)   {      Con::errorf("TSShape::removeObject: Could not find object '%s'", name.c_str());      return false;   }   // Need to make everything editable since node indexes etc will change   makeEditable();   // Destroy all meshes in the object   TSShape::Object& obj = objects[objIndex];   while ( obj.numMeshes )   {      destructInPlace(meshes[obj.startMeshIndex + obj.numMeshes - 1]);      removeMeshFromObject(objIndex, obj.numMeshes - 1);   }   // Remove the object from the shape   objects.erase(objIndex);   S32 subShapeIndex = getSubShapeForObject(objIndex);   subShapeNumObjects[subShapeIndex]--;   for (S32 i = subShapeIndex + 1; i < subShapeFirstObject.size(); i++)      subShapeFirstObject[i]--;   // Remove the object from all sequences   for (S32 i = 0; i < sequences.size(); i++)   {      TSShape::Sequence& seq = sequences[i];      TSIntegerSet objMatters(seq.frameMatters);      objMatters.overlap(seq.matFrameMatters);      objMatters.overlap(seq.visMatters);      if (objMatters.test(objIndex))         eraseStates(objectStates, objMatters, seq.baseObjectState, seq.numKeyframes, objIndex);      seq.frameMatters.erase(objIndex);      seq.matFrameMatters.erase(objIndex);      seq.visMatters.erase(objIndex);   }   // Remove the object name if it is no longer in use   removeName(name);   // Update smallest visible detail   updateSmallestVisibleDL();   initObjects();   return true;}//-----------------------------------------------------------------------------// Helper to copy a TSMesh ready for adding to this TSShape (with the right vertex format etc)TSMesh* TSShape::copyMesh( const TSMesh* srcMesh ) const{   TSMesh * mesh = 0;   if ( srcMesh && ( srcMesh->getMeshType() == TSMesh::SkinMeshType ) )   {      TSSkinMesh* skin = new TSSkinMesh;      // Copy skin elements      const TSSkinMesh *srcSkin = dynamic_cast<const TSSkinMesh*>(srcMesh);      skin->weight = srcSkin->weight;      skin->vertexIndex = srcSkin->vertexIndex;      skin->boneIndex = srcSkin->boneIndex;      skin->batchData.nodeIndex = srcSkin->batchData.nodeIndex;      skin->batchData.initialTransforms = srcSkin->batchData.initialTransforms;      skin->batchData.initialVerts = srcSkin->batchData.initialVerts;      skin->batchData.initialNorms = srcSkin->batchData.initialNorms;      mesh = static_cast<TSMesh*>(skin);   }   else   {      mesh = new TSMesh;   }   if ( !srcMesh )      return mesh;      // return an empty mesh   // Copy mesh elements   mesh->mIndices = srcMesh->mIndices;   mesh->mPrimitives = srcMesh->mPrimitives;   mesh->numFrames = srcMesh->numFrames;   mesh->numMatFrames = srcMesh->numMatFrames;   mesh->vertsPerFrame = srcMesh->vertsPerFrame;   mesh->setFlags(srcMesh->getFlags());   mesh->mNumVerts = srcMesh->mNumVerts;   // Copy vertex data in an *unpacked* form   mesh->copySourceVertexDataFrom(srcMesh);   mesh->createTangents(mesh->mVerts, mesh->mNorms);   mesh->mEncodedNorms.set(NULL, 0);   mesh->computeBounds();   return mesh;}bool TSShape::addMesh(TSMesh* mesh, const String& meshName){    // Ensure mesh is in editable state   mesh->makeEditable();   // Need to make everything editable since node indexes etc will change   makeEditable();   // Determine the object name and detail size from the mesh name   S32 detailSize = 999;   String objName(String::GetTrailingNumber(meshName, detailSize));   // Find the destination object (create one if it does not exist)   S32 objIndex = findObject(objName);   if (objIndex < 0)      objIndex = addObject(objName, 0);   AssertFatal(objIndex >= 0 && objIndex < objects.size(), "Invalid object index!");   // Determine the subshape this object belongs to   S32 subShapeIndex = getSubShapeForObject(objIndex);   AssertFatal(subShapeIndex < subShapeFirstObject.size(), "Could not find subshape for object!");   // Get the existing detail levels for the subshape   Vector<S32> validDetails;   getSubShapeDetails(subShapeIndex, validDetails);   // Determine where to add the new mesh, and whether this is a new detail   S32 detIndex;   bool newDetail = true;   for (detIndex = 0; detIndex < validDetails.size(); detIndex++)   {      const TSShape::Detail& det = details[validDetails[detIndex]];      if (detailSize >= det.size)      {         newDetail = (det.size != detailSize);         break;      }   }   // Insert the new detail level if required   if (newDetail)   {      // Determine a name for the detail level      const char* detailName;      if (dStrStartsWith(objName, "Col"))         detailName = "collision";      else if (dStrStartsWith(objName, "loscol"))         detailName = "los";      else         detailName = "detail";      S32 index = addDetail(detailName, detailSize, subShapeIndex);      details[index].objectDetailNum = detIndex;   }   // Adding a new mesh or detail level is a bit tricky, since each   // object potentially stores a different number of meshes, including   // NULL meshes for higher detail levels where required.   // For example, the following table shows 3 objects. Note how NULLs   // must be inserted for detail levels higher than the first valid   // mesh, but details after the the last valid mesh are left empty.   //   // Detail   |  Object1  |  Object2  |  Object3   // ---------+-----------+-----------+---------   // 128      |  128      |  NULL     |  NULL   // 64       |           |  NULL     |  64   // 32       |           |  32       |  NULL   // 2        |           |           |  2   // Add meshes as required for each object   for (S32 i = 0; i < subShapeNumObjects[subShapeIndex]; i++)   {      S32 index = subShapeFirstObject[subShapeIndex] + i;      const TSShape::Object& obj = objects[index];      if (index == objIndex)      {         // The target object: replace the existing mesh (if any) or add a new one         // if required.         if (!newDetail && (detIndex < obj.numMeshes))         {            if ( meshes[obj.startMeshIndex + detIndex] )               destructInPlace(meshes[obj.startMeshIndex + detIndex]);            meshes[obj.startMeshIndex + detIndex] = mesh;         }         else            addMeshToObject(index, detIndex, mesh);      }      else      {         // Other objects: add a NULL mesh only if inserting before a valid mesh         if (newDetail && (detIndex < obj.numMeshes))            addMeshToObject(index, detIndex, NULL);      }   }   initObjects();   return true;}bool TSShape::addMesh(TSShape* srcShape, const String& srcMeshName, const String& meshName){   // Find the mesh in the source shape   TSMesh* srcMesh = srcShape->findMesh(srcMeshName);   if (!srcMesh)   {      Con::errorf("TSShape::addMesh: Could not find mesh '%s' in shape", srcMeshName.c_str());      return false;   }   // Copy the source mesh   TSMesh *mesh = copyMesh( srcMesh );   if (srcMesh->getMeshType() == TSMesh::SkinMeshType)   {      TSSkinMesh *srcSkin = dynamic_cast<TSSkinMesh*>(srcMesh);      // Check that the source skin is compatible with our skeleton      Vector<S32> nodeMap(srcShape->nodes.size());      for (S32 i = 0; i < srcShape->nodes.size(); i++)         nodeMap.push_back( findNode( srcShape->getName(srcShape->nodes[i].nameIndex) ) );      for (S32 i = 0; i < srcSkin->boneIndex.size(); i++)      {         S32 srcNode = srcSkin->boneIndex[i];         if (nodeMap[srcNode] == -1)         {            const char* name = srcShape->getName(srcShape->nodes[srcNode].nameIndex).c_str();            Con::errorf("TSShape::addMesh: Skin is weighted to node (%s) that "               "does not exist in this shape", name);            return false;         }      }      TSSkinMesh *skin = dynamic_cast<TSSkinMesh*>(mesh);      // Remap node indices      skin->batchData.nodeIndex = srcSkin->batchData.nodeIndex;      for (S32 i = 0; i < skin->batchData.nodeIndex.size(); i++)         skin->batchData.nodeIndex[i] = nodeMap[skin->batchData.nodeIndex[i]];   }   // Add the copied mesh to the shape   if (!addMesh(mesh, meshName))   {      delete mesh;      return false;   }   // Copy materials used by the source mesh (only if from a different shape)   if (srcShape != this)   {      for (S32 i = 0; i < mesh->mPrimitives.size(); i++)      {         if (!(mesh->mPrimitives[i].matIndex & TSDrawPrimitive::NoMaterial))         {            S32 drawType = (mesh->mPrimitives[i].matIndex & (~TSDrawPrimitive::MaterialMask));            S32 srcMatIndex = mesh->mPrimitives[i].matIndex & TSDrawPrimitive::MaterialMask;            const String& matName = srcShape->materialList->getMaterialName(srcMatIndex);            // Add the material if it does not already exist            S32 destMatIndex = materialList->getMaterialNameList().find_next(matName);            if (destMatIndex < 0)            {               destMatIndex = materialList->size();               materialList->push_back(matName, srcShape->materialList->getFlags(srcMatIndex));            }            mesh->mPrimitives[i].matIndex = drawType | destMatIndex;         }      }   }   return true;}bool TSShape::setMeshSize(const String& meshName, S32 size){   S32 objIndex, meshIndex;   if (!findMeshIndex(meshName, objIndex, meshIndex) ||      !meshes[objects[objIndex].startMeshIndex + meshIndex])   {      Con::errorf("TSShape::setMeshSize: Could not find mesh '%s'", meshName.c_str());      return false;   }   // Need to make everything editable since node indexes etc will change   makeEditable();   // Remove the mesh from the object, but don't destroy it   TSShape::Object& obj = objects[objIndex];   TSMesh* mesh = meshes[obj.startMeshIndex + meshIndex];   removeMeshFromObject(objIndex, meshIndex);   // Add the mesh back at the new position   addMesh(mesh, avar("%s %d", getName(obj.nameIndex).c_str(), size));   // Update smallest visible detail   updateSmallestVisibleDL();   initObjects();   return true;}bool TSShape::removeMesh(const String& meshName){   S32 objIndex, meshIndex;   if (!findMeshIndex(meshName, objIndex, meshIndex) ||      !meshes[objects[objIndex].startMeshIndex + meshIndex])   {      Con::errorf("TSShape::removeMesh: Could not find mesh '%s'", meshName.c_str());      return false;   }   // Need to make everything editable since node indexes etc will change   makeEditable();   // Destroy and remove the mesh   TSShape::Object& obj = objects[objIndex];   destructInPlace(meshes[obj.startMeshIndex + meshIndex]);   removeMeshFromObject(objIndex, meshIndex);   // Remove the object if there are no meshes left   if (!obj.numMeshes)      removeObject(getName(obj.nameIndex));   // Update smallest visible detail   updateSmallestVisibleDL();   initObjects();   return true;}//-----------------------------------------------------------------------------// Helper function for dealing with some of the Vectors used in a TSShape. 'meshes'// for example contains a TSMesh* per-object, per-detail-level, with NULLs for// undefined details for each object. Trailing NULLs are not added to the Vector,// so you end up with a different number of pointers for each object, depending// on which detail levels it defines. This makes it tricky to move meshes around// since you have to know how many elements belong to each object.// To simplify things, this function pads the Vector up to a certain length (so// all objects can appear to have the same number of meshes), the moves a single// element to a new index, then trims trailing NULLs again.template<class T>static void _PadMoveAndTrim(Vector<T*>& vec, S32 offset, S32 count,                              S32 padLength, S32 oldIndex, S32 newIndex){   // Pad the array with NULLs   for ( S32 i = count; i < padLength; ++i )      vec.insert( offset + count, NULL );   // Move the element from the old to the new index   T* tmp = vec[offset + oldIndex];   vec.erase( offset + oldIndex );   vec.insert( offset + newIndex, tmp );   // Trim trailing NULLs from the vector   for ( S32 i = padLength - 1; i >= 0; --i )   {      if ( vec[offset + i] )         break;      else         vec.erase( offset + i );   }}S32 TSShape::setDetailSize(S32 oldSize, S32 newSize){   S32 oldIndex = findDetailBySize( oldSize );   if ( oldIndex < 0 )   {      Con::errorf( "TSShape::setDetailSize: Cannot find detail with size %d", oldSize );      return -1;   }   // Remove this detail from the list   TSShape::Detail tmpDetail = details[oldIndex];   tmpDetail.size = newSize;   details.erase(oldIndex);   // Determine the new position for the detail (details are sorted by size)   S32 newIndex = 0;   for ( newIndex = 0; newIndex < details.size(); ++newIndex )   {      if ( newSize > details[newIndex].size )         break;   }   // Add the detail at its new position   details.insert( newIndex, tmpDetail );   // Rename the detail so its trailing size value is correct   {      S32 tmp;      String oldName( getName( tmpDetail.nameIndex ) );      String newName( String::GetTrailingNumber( oldName, tmp ) );      newName += String::ToString( "%d", newSize );      renameDetail(oldName, newName);   }   if ( newIndex != oldIndex )   {      // Fixup details      for ( S32 iDet = 0; iDet < details.size(); iDet++ )      {         if ( details[iDet].subShapeNum < 0 )         {            if ( details[iDet].bbDetailLevel == oldIndex )               details[iDet].bbDetailLevel = newIndex;         }         else         {            details[iDet].objectDetailNum = iDet;         }      }      // Fixup Billboard details      _PadMoveAndTrim( billboardDetails, 0, billboardDetails.size(),                        details.size(), oldIndex, newIndex );      // Now move the mesh for each object in the subshape (adding and removing      // NULLs as appropriate)      for ( S32 iObj = 0; iObj < objects.size(); ++iObj )      {         TSShape::Object& obj = objects[iObj];         S32 oldMeshCount = meshes.size();         _PadMoveAndTrim( meshes, obj.startMeshIndex, obj.numMeshes,                           details.size(), oldIndex, newIndex );         obj.numMeshes += ( meshes.size() - oldMeshCount );         // Fixup startMeshIndex for remaining objects         for ( S32 j = iObj + 1; j < objects.size(); ++j )            objects[j].startMeshIndex += ( meshes.size() - oldMeshCount );      }   }   // Update smallest visible detail   updateSmallestVisibleDL();   // Nothing major, just reint object lists   initObjects();   return newIndex;}bool TSShape::removeDetail( S32 size ){   S32 dl = findDetailBySize( size );   if ( ( dl < 0 ) || ( dl >= details.size() ) )   {      Con::errorf( "TSShape::removeDetail: Invalid detail index (%d)", dl );      return false;   }   // Need to make everything editable since node indexes etc will change   makeEditable();   // Destroy and remove each mesh in the detail level   for ( S32 objIndex = objects.size()-1; objIndex >= 0; objIndex-- )   {      TSShape::Object& obj = objects[objIndex];      if ( dl < obj.numMeshes )      {         if ( meshes[obj.startMeshIndex + dl] )            destructInPlace( meshes[obj.startMeshIndex + dl] );         removeMeshFromObject(objIndex, dl);         // Remove the object if there are no meshes left         if (!obj.numMeshes)            removeObject( getName( obj.nameIndex ) );      }   }   // Destroy billboard detail level   if ( dl < billboardDetails.size() )   {      if ( billboardDetails[dl] )      {         // Delete old textures         billboardDetails[dl]->deleteImposterCacheTextures();         delete billboardDetails[dl];     }           billboardDetails.erase( dl );   }   // Update smallest visible detail   updateSmallestVisibleDL();   initObjects();   return true;}//-----------------------------------------------------------------------------bool TSShape::addSequence(const Torque::Path& path, const String& fromSeq,                          const String& name, S32 startFrame, S32 endFrame,                          bool padRotKeys, bool padTransKeys){   String oldName(fromSeq);   if (path.getExtension().equal("dsq", String::NoCase))   {      S32 oldSeqCount = sequences.size();      // DSQ source file      char filenameBuf[1024];      Con::expandScriptFilename(filenameBuf, sizeof(filenameBuf), path.getFullPath().c_str());      FileStream *f;      if((f = FileStream::createAndOpen( filenameBuf, Torque::FS::File::Read )) == NULL)      {         Con::errorf("TSShape::addSequence: Could not load DSQ file '%s'", filenameBuf);         return false;      }      if (!importSequences(f, filenameBuf) || (f->getStatus() != Stream::Ok))      {         delete f;         Con::errorf("TSShape::addSequence: Load sequence file '%s' failed", filenameBuf);         return false;      }      delete f;      // Rename the new sequence if required (avoid rename if name is not      // unique (this will be fixed up later, and we don't need 2 errors about it!)      if (oldName.isEmpty())         oldName = getName(sequences.last().nameIndex);      if (!oldName.equal(name))      {         if (findSequence(name) == -1)         {            // Use a dummy intermediate name since we might be renaming from an            // existing name (and we want to rename the right sequence!)            sequences.last().nameIndex = addName("__dummy__");            renameSequence("__dummy__", name);         }      }      // Check that sequences have unique names      bool lastSequenceRejected = false;      for (S32 i = sequences.size()-1; i >= oldSeqCount; i--)      {         S32 nameIndex = (i == sequences.size()-1) ? findName(name) : sequences[i].nameIndex;         S32 seqIndex = findSequence(nameIndex);         if ((seqIndex != -1) && (seqIndex != i))         {            Con::errorf("TSShape::addSequence: Failed to add sequence '%s' "               "(name already exists)", getName(nameIndex).c_str());            sequences[i].nameIndex = addName("__dummy__");            removeSequence("__dummy__");            if (i == sequences.size())               lastSequenceRejected = true;         }      }      // @todo:Need to remove keyframes if start!=0 and end!=-1      TSShape::Sequence& seq = sequences.last();      // Store information about how this sequence was created      seq.sourceData.from = String::ToString("%s\t%s", filenameBuf, name.c_str());      seq.sourceData.total = seq.numKeyframes;      seq.sourceData.start = ((startFrame < 0) || (startFrame >= seq.numKeyframes)) ? 0 : startFrame;      seq.sourceData.end = ((endFrame < 0) || (endFrame >= seq.numKeyframes)) ? seq.numKeyframes-1 : endFrame;      return (sequences.size() != oldSeqCount);   }   /* Check that sequence to be added does not already exist */   if (findSequence(name) != -1)   {      Con::errorf("TSShape::addSequence: Cannot add sequence '%s' (name already exists)", name.c_str());      return false;   }   Resource<TSShape> hSrcShape;   TSShape* srcShape = this;        // Assume we are copying an existing sequence   if (path.getExtension().equal("dts", String::NoCase) ||       path.getExtension().equal("dae", String::NoCase))   {      // DTS or DAE source file      char filenameBuf[1024];      Con::expandScriptFilename(filenameBuf, sizeof(filenameBuf), path.getFullPath().c_str());      hSrcShape = ResourceManager::get().load(filenameBuf);      if (!bool(hSrcShape))      {         Con::errorf("TSShape::addSequence: Could not load source shape '%s'", path.getFullPath().c_str());         return false;      }      srcShape = const_cast<TSShape*>((const TSShape*)hSrcShape);      if (!srcShape->sequences.size())      {         Con::errorf("TSShape::addSequence: Source shape '%s' does not contain any sequences", path.getFullPath().c_str());         return false;      }      // If no sequence name is specified, just use the first one      if (oldName.isEmpty())         oldName = srcShape->getName(srcShape->sequences[0].nameIndex);   }   else   {      // Source is an existing sequence      oldName = path.getFullPath();   }   // Find the sequence   S32 seqIndex = srcShape->findSequence(oldName);   if (seqIndex < 0)   {      Con::errorf("TSShape::addSequence (%s): Could not find sequence named '%s'", path.getFullPath().c_str(), oldName.c_str());      return false;   }   // Check keyframe range   const TSShape::Sequence* srcSeq = &srcShape->sequences[seqIndex];   if ((startFrame < 0) || (startFrame >= srcSeq->numKeyframes))   {      Con::warnf("TSShape::addSequence (%s): Start keyframe (%d) out of range (0-%d) for sequence '%s'",         path.getFullPath().c_str(), startFrame, srcSeq->numKeyframes-1, oldName.c_str());      startFrame = 0;   }   if (endFrame < 0)      endFrame = srcSeq->numKeyframes - 1;   else if (endFrame >= srcSeq->numKeyframes)   {      Con::warnf("TSShape::addSequence (%s): End keyframe (%d) out of range (0-%d) for sequence '%s'",         path.getFullPath().c_str(), endFrame, srcSeq->numKeyframes-1, oldName.c_str());      endFrame = srcSeq->numKeyframes - 1;   }   // Create array to map source nodes to our nodes   Vector<S32> nodeMap(srcShape->nodes.size());   for (S32 i = 0; i < srcShape->nodes.size(); i++)      nodeMap.push_back(findNode(srcShape->getName(srcShape->nodes[i].nameIndex)));   // Create array to map source objects to our objects   Vector<S32> objectMap(srcShape->objects.size());   for (S32 i = 0; i < srcShape->objects.size(); i++)      objectMap.push_back(findObject(srcShape->getName(srcShape->objects[i].nameIndex)));   // Copy the source sequence (need to do it this ugly way instead of just   // using push_back since srcSeq pointer may change if copying a sequence   // from inside the shape itself   sequences.increment();   TSShape::Sequence& seq = sequences.last();   srcSeq = &srcShape->sequences[seqIndex]; // update pointer as it may have changed!   seq = *srcSeq;   seq.nameIndex = addName(name);   seq.numKeyframes = endFrame - startFrame + 1;   if (seq.duration > 0)      seq.duration *= ((F32)seq.numKeyframes / srcSeq->numKeyframes);   // Add object states   // Note: only visibility animation is supported   seq.frameMatters.clearAll();   seq.matFrameMatters.clearAll();   seq.visMatters.clearAll();   for (S32 i = 0; i < objectMap.size(); i++)   {      if (objectMap[i] < 0)         continue;      if (srcSeq->visMatters.test(i))      {         // Check if visibility is animated within the frames to be copied         const F32 defaultVis = srcShape->objectStates[i].vis;         S32 objNum = srcSeq->visMatters.count(i);         for (S32 iFrame = startFrame; iFrame <= endFrame; iFrame++)         {            if (srcShape->getObjectState(*srcSeq, iFrame, objNum).vis != defaultVis)            {               seq.visMatters.set(objectMap[i]);               break;            }         }      }   }   TSIntegerSet srcObjectStateSet(srcSeq->frameMatters);   srcObjectStateSet.overlap(srcSeq->matFrameMatters);   srcObjectStateSet.overlap(srcSeq->visMatters);   TSIntegerSet objectStateSet(seq.frameMatters);   objectStateSet.overlap(seq.matFrameMatters);   objectStateSet.overlap(seq.visMatters);   seq.baseObjectState = objectStates.size();   objectStates.increment(objectStateSet.count()*seq.numKeyframes);   for (S32 i = 0; i < objectMap.size(); i++)   {      if (objectMap[i] < 0)         continue;      // Note: only visibility animation is supported      if (objectStateSet.test(objectMap[i]))      {         S32 src = srcSeq->baseObjectState + srcSeq->numKeyframes * srcObjectStateSet.count(i) + startFrame;         S32 dest = seq.baseObjectState + seq.numKeyframes * objectStateSet.count(objectMap[i]);         dCopyArray(&objectStates[dest], &srcShape->objectStates[src], seq.numKeyframes);      }   }   // Add ground frames   F32 ratio = (F32)seq.numKeyframes / srcSeq->numKeyframes;   S32 groundBase = srcSeq->firstGroundFrame + startFrame*ratio;   seq.numGroundFrames *= ratio;   seq.firstGroundFrame = groundTranslations.size();   groundTranslations.reserve(mMin(groundTranslations.size() + seq.numGroundFrames, srcShape->groundTranslations.size()));   groundRotations.reserve(mMin(groundRotations.size() + seq.numGroundFrames, srcShape->groundRotations.size()));   for (S32 i = 0; i < seq.numGroundFrames; i++)   {      S32 offset = groundBase + i;      if (offset >= srcShape->groundTranslations.size())      {         Con::errorf("%s  groundTranslations out of bounds! [%i/%i] ", path.getFullPath().c_str(), groundBase + i, srcShape->groundTranslations.size());         offset = srcShape->groundTranslations.size() - 1;      }      const Point3F pointValueToCopy = srcShape->groundTranslations[offset];      groundTranslations.push_back(pointValueToCopy);      S32 offset2 = groundBase + i;      if (offset2 >= srcShape->groundRotations.size())      {         Con::errorf("%s  groundRotations out of bounds! [%i/%i] ", path.getFullPath().c_str(), groundBase + i, srcShape->groundRotations.size());         offset2 = srcShape->groundRotations.size() - 1;      }      const Quat16 quatValueToCopy = srcShape->groundRotations[offset2];      groundRotations.push_back(quatValueToCopy);   }   // Add triggers   seq.numTriggers = 0;   seq.firstTrigger = triggers.size();   F32 seqStartPos = (F32)startFrame / seq.numKeyframes;   F32 seqEndPos = (F32)endFrame / seq.numKeyframes;   for (S32 i = 0; i < srcSeq->numTriggers; i++)   {      const TSShape::Trigger& srcTrig = srcShape->triggers[srcSeq->firstTrigger + i];      if ((srcTrig.pos >= seqStartPos) && (srcTrig.pos <= seqEndPos))      {         triggers.push_back(srcTrig);         triggers.last().pos -= seqStartPos;         seq.numTriggers++;      }   }   // Fixup node matters arrays   seq.translationMatters.clearAll();   seq.rotationMatters.clearAll();   seq.scaleMatters.clearAll();   for (S32 i = 0; i < nodeMap.size(); i++)   {      if (nodeMap[i] < 0)         continue;      if (srcSeq->translationMatters.test(i))      {         // Check if node position is animated within the frames to be copied         const Point3F& defaultTrans = srcShape->defaultTranslations[i];         S32 tranNum = srcSeq->translationMatters.count(i);         for (S32 iFrame = startFrame; iFrame <= endFrame; iFrame++)         {            if (srcShape->getTranslation(*srcSeq, iFrame, tranNum) != defaultTrans)            {               seq.translationMatters.set(nodeMap[i]);               break;            }         }      }      if (srcSeq->rotationMatters.test(i))      {         // Check if node rotation is animated within the frames to be copied         const QuatF defaultRot = srcShape->defaultRotations[i].getQuatF();         S32 rotNum = srcSeq->rotationMatters.count(i);         for (S32 iFrame = startFrame; iFrame <= endFrame; iFrame++)         {            QuatF temp;            if (srcShape->getRotation(*srcSeq, iFrame, rotNum, &temp) != defaultRot)            {               seq.rotationMatters.set(nodeMap[i]);               break;            }         }      }      if (srcSeq->scaleMatters.test(i))      {         S32 scaleNum = srcSeq->scaleMatters.count(i);         // Check if node scale is animated within the frames to be copied         if (srcSeq->animatesArbitraryScale())         {            TSScale defaultScale;            defaultScale.identity();            for (S32 iFrame = startFrame; iFrame <= endFrame; iFrame++)            {               TSScale temp;               if (!(srcShape->getArbitraryScale(*srcSeq, iFrame, scaleNum, &temp) == defaultScale))               {                  seq.scaleMatters.set(nodeMap[i]);                  break;               }            }         }         else if (srcSeq->animatesAlignedScale())         {            const Point3F defaultScale(Point3F::One);            for (S32 iFrame = startFrame; iFrame <= endFrame; iFrame++)            {               if (srcShape->getAlignedScale(*srcSeq, iFrame, scaleNum) != defaultScale)               {                  seq.scaleMatters.set(nodeMap[i]);                  break;               }            }         }         else if (srcSeq->animatesUniformScale())         {            const F32 defaultScale = 1.0f;            for (S32 iFrame = startFrame; iFrame <= endFrame; iFrame++)            {               if (srcShape->getUniformScale(*srcSeq, iFrame, scaleNum) != defaultScale)               {                  seq.scaleMatters.set(nodeMap[i]);                  break;               }            }         }      }   }   // Resize the node transform arrays   seq.baseTranslation = nodeTranslations.size();   nodeTranslations.increment(seq.translationMatters.count()*seq.numKeyframes);   seq.baseRotation = nodeRotations.size();   nodeRotations.increment(seq.rotationMatters.count()*seq.numKeyframes);   if (seq.flags & TSShape::ArbitraryScale)   {      S32 scaleCount = seq.scaleMatters.count();      seq.baseScale = nodeArbitraryScaleRots.size();      nodeArbitraryScaleRots.increment(scaleCount*seq.numKeyframes);      nodeArbitraryScaleFactors.increment(scaleCount*seq.numKeyframes);   }   else if (seq.flags & TSShape::AlignedScale)   {      seq.baseScale = nodeAlignedScales.size();      nodeAlignedScales.increment(seq.scaleMatters.count()*seq.numKeyframes);   }   else   {      seq.baseScale = nodeUniformScales.size();      nodeUniformScales.increment(seq.scaleMatters.count()*seq.numKeyframes);   }   // Add node transforms (remap from source node indices to our node indices). As   // well as copying animated node translations and rotations, also handle when the   // default translation and rotation are different between the source and   // destination shapes.   for (S32 i = 0; i < nodeMap.size(); i++)   {      if (nodeMap[i] < 0)         continue;      if (seq.translationMatters.test(nodeMap[i]))      {         S32 src = srcSeq->baseTranslation + srcSeq->numKeyframes * srcSeq->translationMatters.count(i) + startFrame;         S32 dest = seq.baseTranslation + seq.numKeyframes * seq.translationMatters.count(nodeMap[i]);         dCopyArray(&nodeTranslations[dest], &srcShape->nodeTranslations[src], seq.numKeyframes);      }      else if (padTransKeys && (defaultTranslations[nodeMap[i]] != srcShape->defaultTranslations[i]))      {         seq.translationMatters.set(nodeMap[i]);         S32 dest = seq.baseTranslation + seq.numKeyframes * seq.translationMatters.count(nodeMap[i]);         for (S32 j = 0; j < seq.numKeyframes; j++)            nodeTranslations.insert(dest, srcShape->defaultTranslations[i]);      }      if (seq.rotationMatters.test(nodeMap[i]))      {         S32 src = srcSeq->baseRotation + srcSeq->numKeyframes * srcSeq->rotationMatters.count(i) + startFrame;         S32 dest = seq.baseRotation + seq.numKeyframes * seq.rotationMatters.count(nodeMap[i]);         dCopyArray(&nodeRotations[dest], &srcShape->nodeRotations[src], seq.numKeyframes);      }      else if (padRotKeys && (defaultRotations[nodeMap[i]] != srcShape->defaultRotations[i]))      {         seq.rotationMatters.set(nodeMap[i]);         S32 dest = seq.baseRotation + seq.numKeyframes * seq.rotationMatters.count(nodeMap[i]);         for (S32 j = 0; j < seq.numKeyframes; j++)            nodeRotations.insert(dest, srcShape->defaultRotations[i]);      }      if (seq.scaleMatters.test(nodeMap[i]))      {         S32 src = srcSeq->baseScale + srcSeq->numKeyframes * srcSeq->scaleMatters.count(i)+ startFrame;         S32 dest = seq.baseScale + seq.numKeyframes * seq.scaleMatters.count(nodeMap[i]);         if (seq.flags & TSShape::ArbitraryScale)         {            dCopyArray(&nodeArbitraryScaleRots[dest], &srcShape->nodeArbitraryScaleRots[src], seq.numKeyframes);            dCopyArray(&nodeArbitraryScaleFactors[dest], &srcShape->nodeArbitraryScaleFactors[src], seq.numKeyframes);         }         else if (seq.flags & TSShape::AlignedScale)            dCopyArray(&nodeAlignedScales[dest], &srcShape->nodeAlignedScales[src], seq.numKeyframes);         else            dCopyArray(&nodeUniformScales[dest], &srcShape->nodeUniformScales[src], seq.numKeyframes);      }   }   // Set shape flags (only the most significant scale type)   U32 curVal = mFlags & AnyScale;   mFlags &= ~(AnyScale);   mFlags |= getMax(curVal, seq.flags & AnyScale);    // take the larger value (can only convert upwards)   // Set sequence flags   seq.dirtyFlags = 0;   if (seq.rotationMatters.testAll() || seq.translationMatters.testAll() || seq.scaleMatters.testAll())      seq.dirtyFlags |= TSShapeInstance::TransformDirty;   if (seq.visMatters.testAll())      seq.dirtyFlags |= TSShapeInstance::VisDirty;   if (seq.frameMatters.testAll())      seq.dirtyFlags |= TSShapeInstance::FrameDirty;   if (seq.matFrameMatters.testAll())      seq.dirtyFlags |= TSShapeInstance::MatFrameDirty;   // Store information about how this sequence was created   seq.sourceData.from = String::ToString("%s\t%s", path.getFullPath().c_str(), oldName.c_str());   seq.sourceData.total = srcSeq->numKeyframes;   seq.sourceData.start = startFrame;   seq.sourceData.end = endFrame;   return true;}bool TSShape::removeSequence(const String& name){   // Find the sequence to be removed   S32 seqIndex = findSequence(name);   if (seqIndex < 0)   {      Con::errorf("TSShape::removeSequence: Could not find sequence '%s'", name.c_str());      return false;   }   TSShape::Sequence& seq = sequences[seqIndex];   // Remove the node transforms for this sequence   S32 transCount = eraseStates(nodeTranslations, seq.translationMatters, seq.baseTranslation, seq.numKeyframes);   S32 rotCount = eraseStates(nodeRotations, seq.rotationMatters, seq.baseRotation, seq.numKeyframes);   S32 scaleCount = 0;   if (seq.flags & TSShape::ArbitraryScale)   {      scaleCount = eraseStates(nodeArbitraryScaleRots, seq.scaleMatters, seq.baseScale, seq.numKeyframes);      eraseStates(nodeArbitraryScaleFactors, seq.scaleMatters, seq.baseScale, seq.numKeyframes);   }   else if (seq.flags & TSShape::AlignedScale)      scaleCount = eraseStates(nodeAlignedScales, seq.scaleMatters, seq.baseScale, seq.numKeyframes);   else      scaleCount = eraseStates(nodeUniformScales, seq.scaleMatters, seq.baseScale, seq.numKeyframes);   // Remove the object states for this sequence   TSIntegerSet objMatters(seq.frameMatters);   objMatters.overlap(seq.matFrameMatters);   objMatters.overlap(seq.visMatters);   S32 objCount = eraseStates(objectStates, objMatters, seq.baseObjectState, seq.numKeyframes);   // Remove groundframes and triggers   TSIntegerSet dummy;   eraseStates(groundTranslations, dummy, seq.firstGroundFrame, seq.numGroundFrames, 0);   eraseStates(groundRotations, dummy, seq.firstGroundFrame, seq.numGroundFrames, 0);   eraseStates(triggers, dummy, seq.firstTrigger, seq.numTriggers, 0);   // Fixup the base indices of the other sequences   for (S32 i = seqIndex + 1; i < sequences.size(); i++)   {      sequences[i].baseTranslation -= transCount;      sequences[i].baseRotation -= rotCount;      sequences[i].baseScale -= scaleCount;      sequences[i].baseObjectState -= objCount;      sequences[i].firstGroundFrame -= seq.numGroundFrames;      sequences[i].firstTrigger -= seq.numTriggers;   }   // Remove the sequence itself   sequences.erase(seqIndex);   // Remove the sequence name if it is no longer in use   removeName(name);   return true;}//-----------------------------------------------------------------------------bool TSShape::addTrigger(const String& seqName, S32 keyframe, S32 state){   // Find the sequence   S32 seqIndex = findSequence(seqName);   if (seqIndex < 0)   {      Con::errorf("TSShape::addTrigger: Could not find sequence '%s'", seqName.c_str());      return false;   }   TSShape::Sequence& seq = sequences[seqIndex];   if (keyframe >= seq.numKeyframes)   {      Con::errorf("TSShape::addTrigger: Keyframe out of range (0-%d for sequence '%s')",         seq.numKeyframes-1, seqName.c_str());      return false;   }   // Encode the trigger state   if (state < 0)      state = 1 << (-state-1);   else if (state > 0)      state = (1 << (state-1)) | TSShape::Trigger::StateOn;   // Fixup seq.firstTrigger if this sequence does not have any triggers yet   if (seq.numTriggers == 0)   {      seq.firstTrigger = 0;      for (S32 i = 0; i < seqIndex; i++)         seq.firstTrigger += sequences[i].numTriggers;   }   // Find where to insert the trigger (sorted by keyframe)   S32 trigIndex;   for (trigIndex = seq.firstTrigger; trigIndex < (seq.firstTrigger + seq.numTriggers); trigIndex++)   {      const TSShape::Trigger& trig = triggers[trigIndex];      if ((S32)(trig.pos * seq.numKeyframes) > keyframe)         break;   }   // Create the new trigger   TSShape::Trigger trig;   trig.pos = (F32)keyframe / getMax(1, seq.numKeyframes-1);   trig.state = state;   triggers.insert(trigIndex, trig);   seq.numTriggers++;   // set invert for other triggers if needed   if ((trig.state & TSShape::Trigger::StateOn) == 0)   {      U32 offTrigger = (trig.state & TSShape::Trigger::StateMask);      for (S32 i = 0; i < seq.numTriggers; i++)      {         if (triggers[seq.firstTrigger + i].state & offTrigger)            triggers[seq.firstTrigger + i].state |= TSShape::Trigger::InvertOnReverse;      }   }   // fixup firstTrigger index for other sequences   for (S32 i = seqIndex + 1; i < sequences.size(); i++)   {      if (sequences[i].numTriggers > 0)         sequences[i].firstTrigger++;   }   // set MakePath flag so triggers will be animated   seq.flags |= TSShape::MakePath;   return true;}bool TSShape::removeTrigger(const String& seqName, S32 keyframe, S32 state){   // Find the sequence   S32 seqIndex = findSequence(seqName);   if (seqIndex < 0)   {      Con::errorf("TSShape::removeTrigger: Could not find sequence '%s'", seqName.c_str());      return false;   }   TSShape::Sequence& seq = sequences[seqIndex];   if (keyframe >= seq.numKeyframes)   {      Con::errorf("TSShape::removeTrigger: Keyframe out of range (0-%d for sequence '%s')",         seq.numKeyframes-1, seqName.c_str());      return false;   }   // Encode the trigger state   if (state < 0)      state = 1 << (-state-1);   else if (state > 0)      state = (1 << (state-1)) | TSShape::Trigger::StateOn;   // Find and remove the trigger   for (S32 trigIndex = seq.firstTrigger; trigIndex < (seq.firstTrigger + seq.numTriggers); trigIndex++)   {      TSShape::Trigger& trig = triggers[trigIndex];      S32 cmpFrame = (S32)(trig.pos * (seq.numKeyframes-1) + 0.5f);       S32 cmpState = trig.state & (~TSShape::Trigger::InvertOnReverse);      if ((cmpFrame == keyframe) && (cmpState == state))      {         triggers.erase(trigIndex);         seq.numTriggers--;         // Fix up firstTrigger for other sequences         for (S32 i = seqIndex + 1; i < sequences.size(); i++)         {            if (sequences[i].numTriggers > 0)               sequences[i].firstTrigger--;         }         // Clear MakePath flag if no more triggers         if ( seq.numTriggers == 0 )            seq.flags &= (~TSShape::MakePath);         return true;      }   }   Con::errorf("TSShape::removeTrigger: Could not find trigger (%d, %d) for sequence '%s'",      keyframe, state, seqName.c_str());   return false;}void TSShape::getNodeKeyframe(S32 nodeIndex, const TSShape::Sequence& seq, S32 keyframe, MatrixF* mat) const{   // Get the node rotation and translation   QuatF rot;   if (seq.rotationMatters.test(nodeIndex))   {      S32 index = seq.rotationMatters.count(nodeIndex) * seq.numKeyframes + keyframe;      nodeRotations[seq.baseRotation + index].getQuatF(&rot);   }      else      defaultRotations[nodeIndex].getQuatF(&rot);   Point3F trans;   if (seq.translationMatters.test(nodeIndex))   {      S32 index = seq.translationMatters.count(nodeIndex) * seq.numKeyframes + keyframe;      trans = nodeTranslations[seq.baseTranslation + index];   }   else      trans = defaultTranslations[nodeIndex];   // Set the keyframe matrix   rot.setMatrix(mat);   mat->setPosition(trans);}bool TSShape::setSequenceBlend(const String& seqName, bool blend, const String& blendRefSeqName, S32 blendRefFrame){   // Find the target sequence   S32 seqIndex = findSequence(seqName);   if (seqIndex < 0)   {      Con::errorf("TSShape::setSequenceBlend: Could not find sequence named '%s'", seqName.c_str());      return false;   }   TSShape::Sequence& seq = sequences[seqIndex];   // Ignore if blend flag is already correct   if (seq.isBlend() == blend)      return true;   // Find the sequence containing the reference frame   S32 blendRefSeqIndex = findSequence(blendRefSeqName);   if (blendRefSeqIndex < 0)   {      Con::errorf("TSShape::setSequenceBlend: Could not find reference sequence named '%s'", blendRefSeqName.c_str());      return false;   }   TSShape::Sequence& blendRefSeq = sequences[blendRefSeqIndex];   if ((blendRefFrame < 0) || (blendRefFrame >= blendRefSeq.numKeyframes))   {      Con::errorf("TSShape::setSequenceBlend: Reference frame out of range (0-%d)", blendRefSeq.numKeyframes-1);      return false;   }   // Set the new flag   if (blend)      seq.flags |= TSShape::Blend;   else      seq.flags &= (~TSShape::Blend);   // For each animated node in the target sequence, need to add or subtract the   // reference keyframe from each frame   TSIntegerSet nodeMatters(seq.rotationMatters);   nodeMatters.overlap(seq.translationMatters);   S32 end = nodeMatters.end();   for (S32 nodeIndex = nodeMatters.start(); nodeIndex < end; nodeMatters.next(nodeIndex))   {      MatrixF refMat;      getNodeKeyframe(nodeIndex, blendRefSeq, blendRefFrame, &refMat);      // Add or subtract the reference?      if (blend)         refMat.inverse();      bool updateRot(false), updateTrans(false);      S32 rotOffset(0), transOffset(0);      if (seq.rotationMatters.test(nodeIndex))      {         updateRot = true;         rotOffset = seq.baseRotation + seq.rotationMatters.count(nodeIndex) * seq.numKeyframes;      }      if (seq.translationMatters.test(nodeIndex))      {         updateTrans = true;         transOffset = seq.baseTranslation + seq.translationMatters.count(nodeIndex) * seq.numKeyframes;      }      for (S32 frame = 0; frame < seq.numKeyframes; frame++)      {         MatrixF oldMat;         getNodeKeyframe(nodeIndex, seq, frame, &oldMat);         MatrixF newMat;         newMat.mul(refMat, oldMat);         if (updateRot)            nodeRotations[rotOffset + frame].set(QuatF(newMat));         if (updateTrans)            nodeTranslations[transOffset + frame] = newMat.getPosition();      }   }   // Update sequence blend information   seq.sourceData.blendSeq = blendRefSeqName;   seq.sourceData.blendFrame = blendRefFrame;   return true;}bool TSShape::setSequenceGroundSpeed(const String& seqName, const Point3F& trans, const Point3F& rot){   // Find the sequence   S32 seqIndex = findSequence(seqName);   if (seqIndex < 0)   {      Con::errorf("setSequenceGroundSpeed: Could not find sequence named '%s'", seqName.c_str());      return false;   }   TSShape::Sequence& seq = sequences[seqIndex];   // Determine how many ground-frames to generate (FPS=10, at least 1 frame)   const F32 groundFrameRate = 10.0f;   S32 numFrames = getMax(1, (S32)(seq.duration * groundFrameRate));   // Allocate space for the frames (add/delete frames as required)   S32 frameAdjust = numFrames - seq.numGroundFrames;   for (S32 i = 0; i < mAbs(frameAdjust); i++)   {      if (frameAdjust > 0)      {         groundTranslations.insert(seq.firstGroundFrame);         groundRotations.insert(seq.firstGroundFrame);      }      else      {         groundTranslations.erase(seq.firstGroundFrame);         groundRotations.erase(seq.firstGroundFrame);      }   }   // Fixup ground frame indices   seq.numGroundFrames += frameAdjust;   for (S32 i = seqIndex + 1; i < sequences.size(); i++)      sequences[i].firstGroundFrame += frameAdjust;   // Generate the ground-frames   Point3F adjTrans = trans;   Point3F adjRot = rot;   if (seq.numGroundFrames > 0)   {      adjTrans /= seq.numGroundFrames;      adjRot /= seq.numGroundFrames;   }   QuatF rotSpeed(adjRot);   QuatF groundRot(rotSpeed);   for (S32 i = 0; i < seq.numGroundFrames; i++)   {      groundTranslations[seq.firstGroundFrame + i] = adjTrans * (i + 1);      groundRotations[seq.firstGroundFrame + i].set(groundRot);      groundRot *= rotSpeed;   }   // set MakePath flag so ground frames will be animated   seq.flags |= TSShape::MakePath;   return true;}void TSShape::makeEditable(){   mNeedReinit = true;   if (mShapeVertexData.base == NULL)      return;   for (U32 i = 0; i < meshes.size(); i++)   {      if (meshes[i])      {         meshes[i]->makeEditable();      }   }   mShapeVertexData.set(NULL, 0);}bool TSShape::needsReinit(){   return mVertexSize == 0 || mShapeVertexData.base == NULL || mNeedReinit;}
 |