Torque3D Documentation / _generateds / tsShapeLoader.cpp

tsShapeLoader.cpp

Engine/source/ts/loader/tsShapeLoader.cpp

More...

Public Typedefs

bool(*
NameCmpFunc )(const String &, const Vector< String > &, void *, void *)

Public Variables

Public Functions

bool
cmpMeshNameAndSize(const String & key, const Vector< String > & names, void * arg1, void * arg2)
bool
cmpShapeName(const String & key, const Vector< String > & names, void * arg1, void * arg2)
DefineEngineFunction(getFormatExtensions , const char * , () , "Returns <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> list of supported shape format extensions separated by tabs." "Example output: *.dsq TAB *.dae TAB" )
DefineEngineFunction(getFormatFilters , const char * , () , "Returns <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> list of supported shape formats in filter <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">form.\n</a>" "Example output: DSQ Files|*.dsq|COLLADA Files|*.dae|" )
DefineEngineFunction(isSupportedFormat , bool , (const char *extension) , "" )
getUniqueName(const char * name, NameCmpFunc isNameUnique, const Vector< String > & names, void * arg1, void * arg2)

Detailed Description

Public Typedefs

typedef bool(* NameCmpFunc )(const String &, const Vector< String > &, void *, void *)

Public Variables

 MODULE_END 
 MODULE_INIT 

Public Functions

cmpMeshNameAndSize(const String & key, const Vector< String > & names, void * arg1, void * arg2)

cmpShapeName(const String & key, const Vector< String > & names, void * arg1, void * arg2)

DefineEngineFunction(getFormatExtensions , const char * , () , "Returns <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> list of supported shape format extensions separated by tabs." "Example output: *.dsq TAB *.dae TAB" )

DefineEngineFunction(getFormatFilters , const char * , () , "Returns <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> list of supported shape formats in filter <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">form.\n</a>" "Example output: DSQ Files|*.dsq|COLLADA Files|*.dae|" )

DefineEngineFunction(isSupportedFormat , bool , (const char *extension) , "" )

getUniqueName(const char * name, NameCmpFunc isNameUnique, const Vector< String > & names, void * arg1, void * arg2)

   1
   2//-----------------------------------------------------------------------------
   3// Copyright (c) 2012 GarageGames, LLC
   4//
   5// Permission is hereby granted, free of charge, to any person obtaining a copy
   6// of this software and associated documentation files (the "Software"), to
   7// deal in the Software without restriction, including without limitation the
   8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   9// sell copies of the Software, and to permit persons to whom the Software is
  10// furnished to do so, subject to the following conditions:
  11//
  12// The above copyright notice and this permission notice shall be included in
  13// all copies or substantial portions of the Software.
  14//
  15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21// IN THE SOFTWARE.
  22//-----------------------------------------------------------------------------
  23
  24#include "platform/platform.h"
  25#include "console/engineAPI.h"
  26#include "ts/loader/tsShapeLoader.h"
  27
  28#include "core/volume.h"
  29#include "materials/materialList.h"
  30#include "materials/matInstance.h"
  31#include "materials/materialManager.h"
  32#include "ts/tsShapeInstance.h"
  33#include "ts/tsMaterialList.h"
  34
  35MODULE_BEGIN( ShapeLoader )
  36   MODULE_INIT_AFTER( GFX )
  37   MODULE_INIT
  38   {
  39      TSShapeLoader::addFormat("Torque DTS", "dts");
  40      TSShapeLoader::addFormat("Torque DSQ", "dsq");
  41   }
  42MODULE_END;
  43
  44const F32 TSShapeLoader::DefaultTime = -1.0f;
  45const F64 TSShapeLoader::MinFrameRate = 15.0f;
  46const F64 TSShapeLoader::MaxFrameRate = 60.0f;
  47const F64 TSShapeLoader::AppGroundFrameRate = 10.0f;
  48Torque::Path TSShapeLoader::shapePath;
  49
  50Vector<TSShapeLoader::ShapeFormat> TSShapeLoader::smFormats;
  51
  52//------------------------------------------------------------------------------
  53// Utility functions
  54
  55void TSShapeLoader::zapScale(MatrixF& mat)
  56{
  57   Point3F invScale = mat.getScale();
  58   invScale.x = invScale.x ? (1.0f / invScale.x) : 0;
  59   invScale.y = invScale.y ? (1.0f / invScale.y) : 0;
  60   invScale.z = invScale.z ? (1.0f / invScale.z) : 0;
  61   mat.scale(invScale);
  62}
  63
  64//------------------------------------------------------------------------------
  65// Shape utility functions
  66
  67MatrixF TSShapeLoader::getLocalNodeMatrix(AppNode* node, F32 t)
  68{
  69   MatrixF m1 = node->getNodeTransform(t);
  70
  71   // multiply by inverse scale at t=0
  72   MatrixF m10 = node->getNodeTransform(DefaultTime);
  73   m1.scale(Point3F(1.0f/m10.getScale().x, 1.0f/m10.getScale().y, 1.0f/m10.getScale().z));
  74
  75   if (node->mParentIndex >= 0)
  76   {
  77      AppNode *parent = appNodes[node->mParentIndex];
  78
  79      MatrixF m2 = parent->getNodeTransform(t);
  80
  81      // multiply by inverse scale at t=0
  82      MatrixF m20 = parent->getNodeTransform(DefaultTime);
  83      m2.scale(Point3F(1.0f/m20.getScale().x, 1.0f/m20.getScale().y, 1.0f/m20.getScale().z));
  84
  85      // get local transform by pre-multiplying by inverted parent transform
  86      m1 = m2.inverse() * m1;
  87   }
  88   else if (boundsNode && node != boundsNode)
  89   {
  90      // make transform relative to bounds node transform at time=t
  91      MatrixF mb = boundsNode->getNodeTransform(t);
  92      zapScale(mb);
  93      m1 = mb.inverse() * m1;
  94   }
  95
  96   return m1;
  97}
  98
  99void TSShapeLoader::generateNodeTransform(AppNode* node, F32 t, bool blend, F32 referenceTime,
 100                                          QuatF& rot, Point3F& trans, QuatF& srot, Point3F& scale)
 101{
 102   MatrixF m1 = getLocalNodeMatrix(node, t);
 103   if (blend)
 104   {
 105      MatrixF m0 = getLocalNodeMatrix(node, referenceTime);
 106      m1 = m0.inverse() * m1;
 107   }
 108
 109   rot.set(m1);
 110   trans = m1.getPosition();
 111   srot.identity();        //@todo: srot not supported yet
 112   scale = m1.getScale();
 113}
 114
 115//-----------------------------------------------------------------------------
 116
 117void TSShapeLoader::updateProgress(S32 major, const char* msg, S32 numMinor, S32 minor)
 118{
 119   // Calculate progress value
 120   F32 progress = (F32)major / NumLoadPhases;
 121   const char *progressMsg = msg;
 122
 123   if (numMinor)
 124   {
 125      progress += (minor * (1.0f / NumLoadPhases) / numMinor);
 126      progressMsg = avar("%s (%d of %d)", msg, minor + 1, numMinor);
 127   }
 128
 129   Con::executef("updateTSShapeLoadProgress", Con::getFloatArg(progress), progressMsg);
 130}
 131
 132//-----------------------------------------------------------------------------
 133// Shape creation entry point
 134
 135TSShape* TSShapeLoader::generateShape(const Torque::Path& path)
 136{
 137   shapePath = path;
 138   shape = new TSShape();
 139
 140   shape->mExporterVersion = 124;
 141   shape->mSmallestVisibleSize = 999999;
 142   shape->mSmallestVisibleDL = 0;
 143   shape->mReadVersion = 24;
 144   shape->mFlags = 0;
 145   shape->mSequencesConstructed = 0;
 146
 147   // Get all nodes, objects and sequences in the shape
 148   updateProgress(Load_EnumerateScene, "Enumerating scene...");
 149   enumerateScene();
 150   if (!subshapes.size())
 151   {
 152      delete shape;
 153      Con::errorf("Failed to load shape \"%s\", no subshapes found", path.getFullPath().c_str());
 154      return NULL;
 155   }
 156
 157   // Create the TSShape::Node hierarchy
 158   generateSubshapes();
 159
 160   // Create objects (meshes and details)
 161   generateObjects();
 162
 163   // Generate initial object states and node transforms
 164   generateDefaultStates();
 165
 166   // Generate skins
 167   generateSkins();
 168
 169   // Generate material list
 170   generateMaterialList();
 171
 172   // Generate animation sequences
 173   generateSequences();
 174
 175   // Sort detail levels and meshes
 176   updateProgress(Load_InitShape, "Initialising shape...");
 177   sortDetails();
 178
 179   // Install the TS memory helper into a TSShape object.
 180   install();
 181
 182   return shape;
 183}
 184
 185bool TSShapeLoader::processNode(AppNode* node)
 186{
 187   // Detect bounds node
 188   if ( node->isBounds() )
 189   {
 190      if ( boundsNode )
 191      {
 192         Con::warnf( "More than one bounds node found" );
 193         return false;
 194      }
 195      boundsNode = node;
 196
 197      // Process bounds geometry
 198      MatrixF boundsMat(boundsNode->getNodeTransform(DefaultTime));
 199      boundsMat.inverse();
 200      zapScale(boundsMat);
 201      for (S32 iMesh = 0; iMesh < boundsNode->getNumMesh(); iMesh++)
 202      {
 203         AppMesh* mesh = boundsNode->getMesh(iMesh);
 204         MatrixF transform = mesh->getMeshTransform(DefaultTime);
 205         transform.mulL(boundsMat);
 206         mesh->lockMesh(DefaultTime, transform);
 207      }
 208      return true;
 209   }
 210
 211   // Detect sequence markers
 212   if ( node->isSequence() )
 213   {
 214      //appSequences.push_back(new AppSequence(node));
 215      return false;
 216   }
 217
 218   // Add this node to the subshape (create one if needed)
 219   if ( subshapes.size() == 0 )
 220      subshapes.push_back( new TSShapeLoader::Subshape );
 221
 222   subshapes.last()->branches.push_back( node );
 223
 224   return true;
 225}
 226
 227//-----------------------------------------------------------------------------
 228// Nodes, meshes and skins
 229
 230typedef bool (*NameCmpFunc)(const String&, const Vector<String>&, void*, void*);
 231
 232bool cmpShapeName(const String& key, const Vector<String>& names, void* arg1, void* arg2)
 233{
 234   for (S32 i = 0; i < names.size(); i++)
 235   {
 236      if (names[i].compare(key, 0, String::NoCase) == 0)
 237         return false;
 238   }
 239   return true;
 240}
 241
 242String getUniqueName(const char* name, NameCmpFunc isNameUnique, const Vector<String>& names, void* arg1=0, void* arg2=0)
 243{
 244   const S32 MAX_ITERATIONS = 0x10000;   // maximum of 4 characters (A-P) will be appended
 245
 246   String suffix;
 247   for (S32 i = 0; i < MAX_ITERATIONS; i++)
 248   {
 249      // Generate a suffix using the first 16 characters of the alphabet
 250      suffix.clear();
 251      for (S32 value = i; value != 0; value >>= 4)
 252         suffix = suffix + (char)('A' + (value & 0xF));
 253
 254      String uname = name + suffix;
 255      if (isNameUnique(uname, names, arg1, arg2))
 256         return uname;
 257   }
 258   return name;
 259}
 260
 261void TSShapeLoader::recurseSubshape(AppNode* appNode, S32 parentIndex, bool recurseChildren)
 262{
 263   // Ignore local bounds nodes
 264   if (appNode->isBounds())
 265      return;
 266
 267   S32 subShapeNum = shape->subShapeFirstNode.size()-1;
 268   Subshape* subshape = subshapes[subShapeNum];
 269
 270   // Check if we should collapse this node
 271   S32 myIndex;
 272   if (ignoreNode(appNode->getName()))
 273   {
 274      myIndex = parentIndex;
 275   }
 276   else
 277   {
 278      // Check that adding this node will not exceed the maximum node count
 279      if (shape->nodes.size() >= MAX_TS_SET_SIZE)
 280         return;
 281
 282      myIndex = shape->nodes.size();
 283      String nodeName = getUniqueName(appNode->getName(), cmpShapeName, shape->names);
 284
 285      // Create the 3space node
 286      shape->nodes.increment();
 287      TSShape::Node& lastNode = shape->nodes.last();
 288      lastNode.nameIndex = shape->addName(nodeName);
 289      lastNode.parentIndex = parentIndex;
 290      lastNode.firstObject = -1;
 291      lastNode.firstChild = -1;
 292      lastNode.nextSibling = -1;
 293
 294      // Add the AppNode to a matching list (so AppNodes can be accessed using 3space
 295      // node indices)
 296      appNodes.push_back(appNode);
 297      appNodes.last()->mParentIndex = parentIndex;
 298
 299      // Check for NULL detail or AutoBillboard nodes (no children or geometry)
 300      if ((appNode->getNumChildNodes() == 0) &&
 301          (appNode->getNumMesh() == 0))
 302      {
 303         S32 size = 0x7FFFFFFF;
 304         String dname(String::GetTrailingNumber(appNode->getName(), size));
 305
 306         if (dStrEqual(dname, "nulldetail") && (size != 0x7FFFFFFF))
 307         {
 308            shape->addDetail("detail", size, subShapeNum);
 309         }
 310         else if (appNode->isBillboard() && (size != 0x7FFFFFFF))
 311         {
 312            // AutoBillboard detail
 313            S32 numEquatorSteps = 4;
 314            S32 numPolarSteps = 0;
 315            F32 polarAngle = 0.0f;
 316            S32 dl = 0;
 317            S32 dim = 64;
 318            bool includePoles = true;
 319
 320            appNode->getInt("BB::EQUATOR_STEPS", numEquatorSteps);
 321            appNode->getInt("BB::POLAR_STEPS", numPolarSteps);
 322            appNode->getFloat("BB::POLAR_ANGLE", polarAngle);
 323            appNode->getInt("BB::DL", dl);
 324            appNode->getInt("BB::DIM", dim);
 325            appNode->getBool("BB::INCLUDE_POLES", includePoles);
 326
 327            S32 detIndex = shape->addDetail( "bbDetail", size, -1 );
 328
 329            TSShape::Detail& detIndexDetail = shape->details[detIndex];
 330            detIndexDetail.bbEquatorSteps = numEquatorSteps;
 331            detIndexDetail.bbPolarSteps = numPolarSteps;
 332            detIndexDetail.bbDetailLevel = dl;
 333            detIndexDetail.bbDimension = dim;
 334            detIndexDetail.bbIncludePoles = includePoles;
 335            detIndexDetail.bbPolarAngle = polarAngle;
 336         }
 337      }
 338   }
 339
 340   // Collect geometry
 341   for (U32 iMesh = 0; iMesh < appNode->getNumMesh(); iMesh++)
 342   {
 343      AppMesh* mesh = appNode->getMesh(iMesh);
 344      if (!ignoreMesh(mesh->getName()))
 345      {
 346         subshape->objMeshes.push_back(mesh);
 347         subshape->objNodes.push_back(mesh->isSkin() ? -1 : myIndex);
 348      }
 349   }
 350
 351   // Create children
 352   if (recurseChildren)
 353   {
 354      for (S32 iChild = 0; iChild < appNode->getNumChildNodes(); iChild++)
 355         recurseSubshape(appNode->getChildNode(iChild), myIndex, true);
 356   }
 357}
 358
 359void TSShapeLoader::generateSubshapes()
 360{
 361   for (U32 iSub = 0; iSub < subshapes.size(); iSub++)
 362   {
 363      updateProgress(Load_GenerateSubshapes, "Generating subshapes...", subshapes.size(), iSub);
 364
 365      Subshape* subshape = subshapes[iSub];
 366
 367      // Recurse through the node hierarchy, adding 3space nodes and
 368      // collecting geometry
 369      S32 firstNode = shape->nodes.size();
 370      shape->subShapeFirstNode.push_back(firstNode);      
 371
 372      for (U32 iBranch = 0; iBranch < subshape->branches.size(); iBranch++)
 373         recurseSubshape(subshape->branches[iBranch], -1, true);
 374
 375      shape->subShapeNumNodes.push_back(shape->nodes.size() - firstNode);
 376
 377      if (shape->nodes.size() >= MAX_TS_SET_SIZE)
 378      {
 379         Con::warnf("Shape exceeds the maximum node count (%d). Ignoring additional nodes.",
 380            MAX_TS_SET_SIZE);
 381      }
 382   }
 383}
 384
 385// Custom name comparison function to compare mesh name and detail size
 386bool cmpMeshNameAndSize(const String& key, const Vector<String>& names, void* arg1, void* arg2)
 387{
 388   const Vector<AppMesh*>& meshes = *(Vector<AppMesh*>*)arg1;
 389   S32                     meshSize = (intptr_t)arg2;
 390
 391   for (S32 i = 0; i < names.size(); i++)
 392   {
 393      if (names[i].compare(key, 0, String::NoCase) == 0)
 394      {
 395         if (meshes[i]->detailSize == meshSize)
 396            return false;
 397      }
 398   }
 399   return true;
 400}
 401
 402void TSShapeLoader::generateObjects()
 403{
 404   for (S32 iSub = 0; iSub < subshapes.size(); iSub++)
 405   {
 406      Subshape* subshape = subshapes[iSub];
 407      shape->subShapeFirstObject.push_back(shape->objects.size());
 408
 409      // Get the names and sizes of the meshes for this subshape
 410      Vector<String> meshNames;
 411      for (S32 iMesh = 0; iMesh < subshape->objMeshes.size(); iMesh++)
 412      {
 413         AppMesh* mesh = subshape->objMeshes[iMesh];
 414         mesh->detailSize = 2;
 415         String name = String::GetTrailingNumber( mesh->getName(), mesh->detailSize );
 416         name = getUniqueName( name, cmpMeshNameAndSize, meshNames, &(subshape->objMeshes), (void*)(uintptr_t)mesh->detailSize );
 417         meshNames.push_back( name );
 418
 419         // Fix up any collision details that don't have a negative detail level.
 420         if (  dStrStartsWith(meshNames[iMesh], "Collision") ||
 421               dStrStartsWith(meshNames[iMesh], "LOSCol") )
 422         {
 423            if (mesh->detailSize > 0)
 424               mesh->detailSize = -mesh->detailSize;
 425         }
 426      }
 427
 428      // An 'object' is a collection of meshes with the same base name and
 429      // different detail sizes. The object is attached to the node of the
 430      // highest detail mesh.
 431
 432      // Sort the 3 arrays (objMeshes, objNodes, meshNames) by name and size
 433      for (S32 i = 0; i < subshape->objMeshes.size()-1; i++)
 434      {
 435         for (S32 j = i+1; j < subshape->objMeshes.size(); j++)
 436         {
 437            if ((meshNames[i].compare(meshNames[j]) < 0) ||
 438               ((meshNames[i].compare(meshNames[j]) == 0) &&
 439               (subshape->objMeshes[i]->detailSize < subshape->objMeshes[j]->detailSize)))
 440            {
 441               {
 442                  AppMesh* tmp = subshape->objMeshes[i];
 443                  subshape->objMeshes[i] = subshape->objMeshes[j];
 444                  subshape->objMeshes[j] = tmp;
 445               }
 446               {
 447                  S32 tmp = subshape->objNodes[i];
 448                  subshape->objNodes[i] = subshape->objNodes[j];
 449                  subshape->objNodes[j] = tmp;
 450               }
 451               {
 452                  String tmp = meshNames[i];
 453                  meshNames[i] = meshNames[j];
 454                  meshNames[j] = tmp;
 455               }
 456            }
 457         }
 458      }
 459
 460      // Now create objects
 461      const String* lastName = 0;
 462      for (S32 iMesh = 0; iMesh < subshape->objMeshes.size(); iMesh++)
 463      {
 464         AppMesh* mesh = subshape->objMeshes[iMesh];
 465
 466         if (!lastName || (meshNames[iMesh] != *lastName))
 467         {
 468            shape->objects.increment();
 469            TSShape::Object& lastObject = shape->objects.last();
 470            lastObject.nameIndex = shape->addName(meshNames[iMesh]);
 471            lastObject.nodeIndex = subshape->objNodes[iMesh];
 472            lastObject.startMeshIndex = appMeshes.size();
 473            lastObject.numMeshes = 0;
 474            lastName = &meshNames[iMesh];
 475         }
 476
 477         // Add this mesh to the object
 478         appMeshes.push_back(mesh);
 479         shape->objects.last().numMeshes++;
 480
 481         // Set mesh flags
 482         mesh->flags = 0;
 483         if (mesh->isBillboard())
 484         {
 485            mesh->flags |= TSMesh::Billboard;
 486            if (mesh->isBillboardZAxis())
 487               mesh->flags |= TSMesh::BillboardZAxis;
 488         }
 489
 490         // Set the detail name... do fixups for collision details.
 491         const char* detailName = "detail";
 492         if ( mesh->detailSize < 0 )
 493         {
 494            if (  dStrStartsWith(meshNames[iMesh], "Collision") ||
 495                  dStrStartsWith(meshNames[iMesh], "Col") )
 496               detailName = "Collision";
 497            else if (dStrStartsWith(meshNames[iMesh], "LOSCol"))
 498               detailName = "LOS";
 499         }
 500
 501         // Attempt to add the detail (will fail if it already exists)
 502         S32 oldNumDetails = shape->details.size();
 503         shape->addDetail(detailName, mesh->detailSize, iSub);
 504         if (shape->details.size() > oldNumDetails)
 505         {
 506            Con::warnf("Object mesh \"%s\" has no matching detail (\"%s%d\" has"
 507               " been added automatically)", mesh->getName(false), detailName, mesh->detailSize);
 508         }
 509      }
 510
 511      // Get object count for this subshape
 512      shape->subShapeNumObjects.push_back(shape->objects.size() - shape->subShapeFirstObject.last());
 513   }
 514}
 515
 516void TSShapeLoader::generateSkins()
 517{
 518   Vector<AppMesh*> skins;
 519   for (S32 iObject = 0; iObject < shape->objects.size(); iObject++)
 520   {
 521      for (S32 iMesh = 0; iMesh < shape->objects[iObject].numMeshes; iMesh++)
 522      {
 523         AppMesh* mesh = appMeshes[shape->objects[iObject].startMeshIndex + iMesh];
 524         if (mesh->isSkin())
 525            skins.push_back(mesh);
 526      }
 527   }
 528
 529   for (S32 iSkin = 0; iSkin < skins.size(); iSkin++)
 530   {
 531      updateProgress(Load_GenerateSkins, "Generating skins...", skins.size(), iSkin);
 532
 533      // Get skin data (bones, vertex weights etc)
 534      AppMesh* skin = skins[iSkin];
 535      skin->lookupSkinData();
 536
 537      // Just copy initial verts and norms for now
 538      skin->initialVerts.set(skin->points.address(), skin->vertsPerFrame);
 539      skin->initialNorms.set(skin->normals.address(), skin->vertsPerFrame);
 540
 541      // Map bones to nodes
 542      skin->nodeIndex.setSize(skin->bones.size());
 543      for (S32 iBone = 0; iBone < skin->bones.size(); iBone++)
 544      {
 545         // Find the node that matches this bone
 546         skin->nodeIndex[iBone] = -1;
 547         for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
 548         {
 549            if (appNodes[iNode]->isEqual(skin->bones[iBone]))
 550            {
 551               delete skin->bones[iBone];
 552               skin->bones[iBone] = appNodes[iNode];
 553               skin->nodeIndex[iBone] = iNode;
 554               break;
 555            }
 556         }
 557
 558         if (skin->nodeIndex[iBone] == -1)
 559         {
 560            Con::warnf("Could not find bone %d. Defaulting to first node", iBone);
 561            skin->nodeIndex[iBone] = 0;
 562         }
 563      }
 564   }
 565}
 566
 567void TSShapeLoader::generateDefaultStates()
 568{
 569   // Generate default object states (includes initial geometry)
 570   for (S32 iObject = 0; iObject < shape->objects.size(); iObject++)
 571   {
 572      updateProgress(Load_GenerateDefaultStates, "Generating initial mesh and node states...",
 573         shape->objects.size(), iObject);
 574
 575      TSShape::Object& obj = shape->objects[iObject];
 576
 577      // Calculate the objectOffset for each mesh at T=0
 578      for (S32 iMesh = 0; iMesh < obj.numMeshes; iMesh++)
 579      {
 580         AppMesh* appMesh = appMeshes[obj.startMeshIndex + iMesh];
 581         AppNode* appNode = obj.nodeIndex >= 0 ? appNodes[obj.nodeIndex] : boundsNode;
 582
 583         MatrixF meshMat(appMesh->getMeshTransform(DefaultTime));
 584         MatrixF nodeMat(appMesh->isSkin() ? meshMat : appNode->getNodeTransform(DefaultTime));
 585
 586         zapScale(nodeMat);
 587
 588         appMesh->objectOffset = nodeMat.inverse() * meshMat;
 589      }
 590
 591      generateObjectState(shape->objects[iObject], DefaultTime, true, true);
 592   }
 593
 594   // Generate default node transforms
 595   for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
 596   {
 597      // Determine the default translation and rotation for the node
 598      QuatF rot, srot;
 599      Point3F trans, scale;
 600      generateNodeTransform(appNodes[iNode], DefaultTime, false, 0, rot, trans, srot, scale);
 601
 602      // Add default node translation and rotation
 603      addNodeRotation(rot, true);
 604      addNodeTranslation(trans, true);
 605   }
 606}
 607
 608void TSShapeLoader::generateObjectState(TSShape::Object& obj, F32 t, bool addFrame, bool addMatFrame)
 609{
 610   shape->objectStates.increment();
 611   TSShape::ObjectState& state = shape->objectStates.last();
 612
 613   state.frameIndex = 0;
 614   state.matFrameIndex = 0;
 615   state.vis = mClampF(appMeshes[obj.startMeshIndex]->getVisValue(t), 0.0f, 1.0f);
 616
 617   if (addFrame || addMatFrame)
 618   {
 619      generateFrame(obj, t, addFrame, addMatFrame);
 620
 621      // set the frame number for the object state
 622      state.frameIndex = appMeshes[obj.startMeshIndex]->numFrames - 1;
 623      state.matFrameIndex = appMeshes[obj.startMeshIndex]->numMatFrames - 1;
 624   }
 625}
 626
 627void TSShapeLoader::generateFrame(TSShape::Object& obj, F32 t, bool addFrame, bool addMatFrame)
 628{
 629   for (S32 iMesh = 0; iMesh < obj.numMeshes; iMesh++)
 630   {
 631      AppMesh* appMesh = appMeshes[obj.startMeshIndex + iMesh];
 632
 633      U32 oldNumPoints = appMesh->points.size();
 634      U32 oldNumUvs = appMesh->uvs.size();
 635
 636      // Get the mesh geometry at time, 't'
 637      // Geometry verts, normals and tverts can be animated (different set for
 638      // each frame), but the TSDrawPrimitives stay the same, so the way lockMesh
 639      // works is that it will only generate the primitives once, then after that
 640      // will just append verts, normals and tverts each time it is called.
 641      appMesh->lockMesh(t, appMesh->objectOffset);
 642
 643      // Calculate vertex normals if required
 644      if (appMesh->normals.size() != appMesh->points.size())
 645         appMesh->computeNormals();
 646
 647      // If this is the first call, set the number of points per frame
 648      if (appMesh->numFrames == 0)
 649      {
 650         appMesh->vertsPerFrame = appMesh->points.size();
 651      }
 652      else
 653      {
 654         // Check frame topology => ie. that the right number of points, normals
 655         // and tverts was added
 656         if ((appMesh->points.size() - oldNumPoints) != appMesh->vertsPerFrame)
 657         {
 658            Con::warnf("Wrong number of points (%d) added at time=%f (expected %d)",
 659               appMesh->points.size() - oldNumPoints, t, appMesh->vertsPerFrame);
 660            addFrame = false;
 661         }
 662         if ((appMesh->normals.size() - oldNumPoints) != appMesh->vertsPerFrame)
 663         {
 664            Con::warnf("Wrong number of normals (%d) added at time=%f (expected %d)",
 665               appMesh->normals.size() - oldNumPoints, t, appMesh->vertsPerFrame);
 666            addFrame = false;
 667         }
 668         if ((appMesh->uvs.size() - oldNumUvs) != appMesh->vertsPerFrame)
 669         {
 670            Con::warnf("Wrong number of tverts (%d) added at time=%f (expected %d)",
 671               appMesh->uvs.size() - oldNumUvs, t, appMesh->vertsPerFrame);
 672            addMatFrame = false;
 673         }
 674      }
 675
 676      // Because lockMesh adds points, normals AND tverts each call, if we didn't
 677      // actually want another frame or matFrame, we need to remove them afterwards.
 678      // In the common case (we DO want the frame), we can do nothing => the
 679      // points/normals/tverts are already in place!
 680      if (addFrame)
 681      {
 682         appMesh->numFrames++;
 683      }
 684      else
 685      {
 686         appMesh->points.setSize(oldNumPoints);
 687         appMesh->normals.setSize(oldNumPoints);
 688      }
 689
 690      if (addMatFrame)
 691      {
 692         appMesh->numMatFrames++;
 693      }
 694      else
 695      {
 696         appMesh->uvs.setSize(oldNumPoints);
 697      }
 698   }
 699}
 700
 701//-----------------------------------------------------------------------------
 702// Materials
 703
 704/// Convert all Collada materials into a single TSMaterialList
 705void TSShapeLoader::generateMaterialList()
 706{
 707   // Install the materials into the material list
 708   shape->materialList = new TSMaterialList;
 709   for (S32 iMat = 0; iMat < AppMesh::appMaterials.size(); iMat++)
 710   {
 711      updateProgress(Load_GenerateMaterials, "Generating materials...", AppMesh::appMaterials.size(), iMat);
 712
 713      AppMaterial* appMat = AppMesh::appMaterials[iMat];
 714      shape->materialList->push_back(appMat->getName(), appMat->getFlags(), U32(-1), U32(-1), U32(-1), 1.0f, appMat->getReflectance());
 715   }
 716}
 717
 718
 719//-----------------------------------------------------------------------------
 720// Animation Sequences
 721
 722void TSShapeLoader::generateSequences()
 723{
 724   for (S32 iSeq = 0; iSeq < appSequences.size(); iSeq++)
 725   {
 726      updateProgress(Load_GenerateSequences, "Generating sequences...", appSequences.size(), iSeq);
 727
 728      // Initialize the sequence
 729      appSequences[iSeq]->setActive(true);
 730
 731      shape->sequences.increment();
 732      TSShape::Sequence& seq = shape->sequences.last();
 733
 734      seq.nameIndex = shape->addName(appSequences[iSeq]->getName());
 735      seq.toolBegin = appSequences[iSeq]->getStart();
 736      seq.priority = appSequences[iSeq]->getPriority();
 737      seq.flags = appSequences[iSeq]->getFlags();
 738
 739      // Compute duration and number of keyframes (then adjust time between frames to match)
 740      seq.duration = appSequences[iSeq]->getEnd() - appSequences[iSeq]->getStart();
 741      seq.numKeyframes = (S32)(seq.duration * appSequences[iSeq]->fps + 0.5f) + 1;
 742
 743      seq.sourceData.start = 0;
 744      seq.sourceData.end = seq.numKeyframes-1;
 745      seq.sourceData.total = seq.numKeyframes;
 746
 747      // Set membership arrays (ie. which nodes and objects are affected by this sequence)
 748      setNodeMembership(seq, appSequences[iSeq]);
 749      setObjectMembership(seq, appSequences[iSeq]);
 750
 751      // Generate keyframes
 752      generateNodeAnimation(seq);
 753      generateObjectAnimation(seq, appSequences[iSeq]);
 754      generateGroundAnimation(seq, appSequences[iSeq]);
 755      generateFrameTriggers(seq, appSequences[iSeq]);
 756
 757      // Set sequence flags
 758      seq.dirtyFlags = 0;
 759      if (seq.rotationMatters.testAll() || seq.translationMatters.testAll() || seq.scaleMatters.testAll())
 760         seq.dirtyFlags |= TSShapeInstance::TransformDirty;
 761      if (seq.visMatters.testAll())
 762         seq.dirtyFlags |= TSShapeInstance::VisDirty;
 763      if (seq.frameMatters.testAll())
 764         seq.dirtyFlags |= TSShapeInstance::FrameDirty;
 765      if (seq.matFrameMatters.testAll())
 766         seq.dirtyFlags |= TSShapeInstance::MatFrameDirty;
 767
 768      // Set shape flags (only the most significant scale type)
 769      U32 curVal = shape->mFlags & TSShape::AnyScale;
 770      shape->mFlags &= ~(TSShape::AnyScale);
 771      shape->mFlags |= getMax(curVal, seq.flags & TSShape::AnyScale); // take the larger value (can only convert upwards)
 772
 773      appSequences[iSeq]->setActive(false);
 774   }
 775}
 776
 777void TSShapeLoader::setNodeMembership(TSShape::Sequence& seq, const AppSequence* appSeq)
 778{
 779   seq.rotationMatters.clearAll();     // node rotation (size = nodes.size())
 780   seq.translationMatters.clearAll();  // node translation (size = nodes.size())
 781   seq.scaleMatters.clearAll();        // node scale (size = nodes.size())
 782
 783   // This shouldn't be allowed, but check anyway...
 784   if (seq.numKeyframes < 2)
 785      return;
 786
 787   // Note: this fills the cache with current sequence data. Methods that get
 788   // called later (e.g. generateNodeAnimation) use this info (and assume it's set).
 789   fillNodeTransformCache(seq, appSeq);
 790
 791   // Test to see if the transform changes over the interval in order to decide
 792   // whether to animate the transform in 3space. We don't use app's mechanism
 793   // for doing this because it functions different in different apps and we do
 794   // some special stuff with scale.
 795   setRotationMembership(seq);
 796   setTranslationMembership(seq);
 797   setScaleMembership(seq);
 798}
 799
 800void TSShapeLoader::setRotationMembership(TSShape::Sequence& seq)
 801{
 802   for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
 803   {
 804      // Check if any of the node rotations are different to
 805      // the default rotation
 806      QuatF defaultRot;
 807      shape->defaultRotations[iNode].getQuatF(&defaultRot);
 808
 809      for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
 810      {
 811         if (nodeRotCache[iNode][iFrame] != defaultRot)
 812         {
 813            seq.rotationMatters.set(iNode);
 814            break;
 815         }
 816      }
 817   }
 818}
 819
 820void TSShapeLoader::setTranslationMembership(TSShape::Sequence& seq)
 821{
 822   for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
 823   {
 824      // Check if any of the node translations are different to
 825      // the default translation
 826      Point3F& defaultTrans = shape->defaultTranslations[iNode];
 827
 828      for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
 829      {
 830         if (!nodeTransCache[iNode][iFrame].equal(defaultTrans))
 831         {
 832            seq.translationMatters.set(iNode);
 833            break;
 834         }
 835      }
 836   }
 837}
 838
 839void TSShapeLoader::setScaleMembership(TSShape::Sequence& seq)
 840{
 841   Point3F unitScale(1,1,1);
 842
 843   U32 arbitraryScaleCount = 0;
 844   U32 alignedScaleCount = 0;
 845   U32 uniformScaleCount = 0;
 846
 847   for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
 848   {
 849      // Check if any of the node scales are not the unit scale
 850      for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
 851      {
 852         Point3F& scale = nodeScaleCache[iNode][iFrame];
 853         if (!unitScale.equal(scale))
 854         {
 855            // Determine what type of scale this is
 856            if (!nodeScaleRotCache[iNode][iFrame].isIdentity())
 857               arbitraryScaleCount++;
 858            else if (scale.x != scale.y || scale.y != scale.z)
 859               alignedScaleCount++;
 860            else
 861               uniformScaleCount++;
 862
 863            seq.scaleMatters.set(iNode);
 864            break;
 865         }
 866      }
 867   }
 868
 869   // Only one type of scale is animated
 870   if (arbitraryScaleCount)
 871      seq.flags |= TSShape::ArbitraryScale;
 872   else if (alignedScaleCount)
 873      seq.flags |= TSShape::AlignedScale;
 874   else if (uniformScaleCount)
 875      seq.flags |= TSShape::UniformScale;
 876}
 877
 878void TSShapeLoader::setObjectMembership(TSShape::Sequence& seq, const AppSequence* appSeq)
 879{
 880   seq.visMatters.clearAll();          // object visibility (size = objects.size())
 881   seq.frameMatters.clearAll();        // vert animation (morph) (size = objects.size())
 882   seq.matFrameMatters.clearAll();     // UV animation (size = objects.size())
 883
 884   for (S32 iObject = 0; iObject < shape->objects.size(); iObject++)
 885   {
 886      if (!appMeshes[shape->objects[iObject].startMeshIndex])
 887         continue;
 888
 889      if (appMeshes[shape->objects[iObject].startMeshIndex]->animatesVis(appSeq))
 890         seq.visMatters.set(iObject);
 891      // Morph and UV animation has been deprecated
 892      //if (appMeshes[shape->objects[iObject].startMeshIndex]->animatesFrame(appSeq))
 893         //seq.frameMatters.set(iObject);
 894      //if (appMeshes[shape->objects[iObject].startMeshIndex]->animatesMatFrame(appSeq))
 895         //seq.matFrameMatters.set(iObject);
 896   }
 897}
 898
 899void TSShapeLoader::clearNodeTransformCache()
 900{
 901   // clear out the transform caches
 902   for (S32 i = 0; i < nodeRotCache.size(); i++)
 903      delete [] nodeRotCache[i];
 904   nodeRotCache.clear();
 905   for (S32 i = 0; i < nodeTransCache.size(); i++)
 906      delete [] nodeTransCache[i];
 907   nodeTransCache.clear();
 908   for (S32 i = 0; i < nodeScaleRotCache.size(); i++)
 909      delete [] nodeScaleRotCache[i];
 910   nodeScaleRotCache.clear();
 911   for (S32 i = 0; i < nodeScaleCache.size(); i++)
 912      delete [] nodeScaleCache[i];
 913   nodeScaleCache.clear();
 914}
 915
 916void TSShapeLoader::fillNodeTransformCache(TSShape::Sequence& seq, const AppSequence* appSeq)
 917{
 918   // clear out the transform caches and set it up for this sequence
 919   clearNodeTransformCache();
 920
 921   nodeRotCache.setSize(appNodes.size());
 922   for (S32 i = 0; i < nodeRotCache.size(); i++)
 923      nodeRotCache[i] = new QuatF[seq.numKeyframes];
 924   nodeTransCache.setSize(appNodes.size());
 925   for (S32 i = 0; i < nodeTransCache.size(); i++)
 926      nodeTransCache[i] = new Point3F[seq.numKeyframes];
 927   nodeScaleRotCache.setSize(appNodes.size());
 928   for (S32 i = 0; i < nodeScaleRotCache.size(); i++)
 929      nodeScaleRotCache[i] = new QuatF[seq.numKeyframes];
 930   nodeScaleCache.setSize(appNodes.size());
 931   for (S32 i = 0; i < nodeScaleCache.size(); i++)
 932      nodeScaleCache[i] = new Point3F[seq.numKeyframes];
 933
 934   // get the node transforms for every frame
 935   for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
 936   {
 937      F32 time = appSeq->getStart() + seq.duration * iFrame / getMax(1, seq.numKeyframes - 1);
 938      for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
 939      {
 940         generateNodeTransform(appNodes[iNode], time, seq.isBlend(), appSeq->getBlendRefTime(),
 941                               nodeRotCache[iNode][iFrame], nodeTransCache[iNode][iFrame],
 942                               nodeScaleRotCache[iNode][iFrame], nodeScaleCache[iNode][iFrame]);
 943      }
 944   }
 945}
 946
 947void TSShapeLoader::addNodeRotation(QuatF& rot, bool defaultVal)
 948{
 949   Quat16 rot16;
 950   rot16.set(rot);
 951
 952   if (!defaultVal)
 953      shape->nodeRotations.push_back(rot16);
 954   else
 955      shape->defaultRotations.push_back(rot16);
 956}
 957
 958void TSShapeLoader::addNodeTranslation(Point3F& trans, bool defaultVal)
 959{
 960   if (!defaultVal)
 961      shape->nodeTranslations.push_back(trans);
 962   else
 963      shape->defaultTranslations.push_back(trans);
 964}
 965
 966void TSShapeLoader::addNodeUniformScale(F32 scale)
 967{
 968   shape->nodeUniformScales.push_back(scale);
 969}
 970
 971void TSShapeLoader::addNodeAlignedScale(Point3F& scale)
 972{
 973   shape->nodeAlignedScales.push_back(scale);
 974}
 975
 976void TSShapeLoader::addNodeArbitraryScale(QuatF& qrot, Point3F& scale)
 977{
 978   Quat16 rot16;
 979   rot16.set(qrot);
 980   shape->nodeArbitraryScaleRots.push_back(rot16);
 981   shape->nodeArbitraryScaleFactors.push_back(scale);
 982}
 983
 984void TSShapeLoader::generateNodeAnimation(TSShape::Sequence& seq)
 985{
 986   seq.baseRotation = shape->nodeRotations.size();
 987   seq.baseTranslation = shape->nodeTranslations.size();
 988   seq.baseScale = (seq.flags & TSShape::ArbitraryScale) ? shape->nodeArbitraryScaleRots.size() :
 989                   (seq.flags & TSShape::AlignedScale) ? shape->nodeAlignedScales.size() :
 990                   shape->nodeUniformScales.size();
 991
 992   for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
 993   {
 994      for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
 995      {
 996         if (seq.rotationMatters.test(iNode))
 997            addNodeRotation(nodeRotCache[iNode][iFrame], false);
 998         if (seq.translationMatters.test(iNode))
 999            addNodeTranslation(nodeTransCache[iNode][iFrame], false);
1000         if (seq.scaleMatters.test(iNode))
1001         {
1002            QuatF& rot = nodeScaleRotCache[iNode][iFrame];
1003            Point3F scale = nodeScaleCache[iNode][iFrame];
1004
1005            if (seq.flags & TSShape::ArbitraryScale)
1006               addNodeArbitraryScale(rot, scale);
1007            else if (seq.flags & TSShape::AlignedScale)
1008               addNodeAlignedScale(scale);
1009            else if (seq.flags & TSShape::UniformScale)
1010               addNodeUniformScale((scale.x+scale.y+scale.z)/3.0f);
1011         }
1012      }
1013   }
1014}
1015
1016void TSShapeLoader::generateObjectAnimation(TSShape::Sequence& seq, const AppSequence* appSeq)
1017{
1018   seq.baseObjectState = shape->objectStates.size();
1019
1020   for (S32 iObject = 0; iObject < shape->objects.size(); iObject++)
1021   {
1022      bool visMatters = seq.visMatters.test(iObject);
1023      bool frameMatters = seq.frameMatters.test(iObject);
1024      bool matFrameMatters = seq.matFrameMatters.test(iObject);
1025
1026      if (visMatters || frameMatters || matFrameMatters)
1027      {
1028         for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
1029         {
1030            F32 time = appSeq->getStart() + seq.duration * iFrame / getMax(1, seq.numKeyframes - 1);
1031            generateObjectState(shape->objects[iObject], time, frameMatters, matFrameMatters);
1032         }
1033      }
1034   }
1035}
1036
1037void TSShapeLoader::generateGroundAnimation(TSShape::Sequence& seq, const AppSequence* appSeq)
1038{
1039   seq.firstGroundFrame = shape->groundTranslations.size();
1040   seq.numGroundFrames = 0;
1041
1042   if (!boundsNode)
1043      return;
1044
1045   // Check if the bounds node is animated by this sequence
1046   seq.numGroundFrames = (S32)((seq.duration + 0.25f/<a href="/coding/class/classtsshapeloader/#classtsshapeloader_1a79866e12c8b88b2f5a4e6dc9e24a15c6">AppGroundFrameRate</a>) * AppGroundFrameRate);
1047
1048   seq.flags |= TSShape::MakePath;
1049
1050   // Get ground transform at the start of the sequence
1051   MatrixF invStartMat = boundsNode->getNodeTransform(appSeq->getStart());
1052   zapScale(invStartMat);
1053   invStartMat.inverse();
1054
1055   for (S32 iFrame = 0; iFrame < seq.numGroundFrames; iFrame++)
1056   {
1057      F32 time = appSeq->getStart() + seq.duration * iFrame / getMax(1, seq.numGroundFrames - 1);
1058
1059      // Determine delta bounds node transform at 't'
1060      MatrixF mat = boundsNode->getNodeTransform(time);
1061      zapScale(mat);
1062      mat = invStartMat * mat;
1063
1064      // Add ground transform
1065      Quat16 rotation;
1066      rotation.set(QuatF(mat));
1067      shape->groundTranslations.push_back(mat.getPosition());
1068      shape->groundRotations.push_back(rotation);
1069   }
1070}
1071
1072void TSShapeLoader::generateFrameTriggers(TSShape::Sequence& seq, const AppSequence* appSeq)
1073{
1074   // Initialize triggers
1075   seq.firstTrigger = shape->triggers.size();
1076   seq.numTriggers  = appSeq->getNumTriggers();
1077   if (!seq.numTriggers)
1078      return;
1079
1080   seq.flags |= TSShape::MakePath;
1081
1082   // Add triggers
1083   for (S32 iTrigger = 0; iTrigger < seq.numTriggers; iTrigger++)
1084   {
1085      shape->triggers.increment();
1086      appSeq->getTrigger(iTrigger, shape->triggers.last());
1087   }
1088
1089   // Track the triggers that get turned off by this shape...normally, triggers
1090   // aren't turned on/off, just on...if we are a trigger that does both then we
1091   // need to mark ourselves as such so that on/off can become off/on when sequence
1092   // is played in reverse...
1093   U32 offTriggers = 0;
1094   for (S32 iTrigger = 0; iTrigger < seq.numTriggers; iTrigger++)
1095   {
1096      U32 state = shape->triggers[seq.firstTrigger+iTrigger].state;
1097      if ((state & TSShape::Trigger::StateOn) == 0)
1098         offTriggers |= (state & TSShape::Trigger::StateMask);
1099   }
1100
1101   // We now know which states are turned off, set invert on all those (including when turned on)
1102   for (int iTrigger = 0; iTrigger < seq.numTriggers; iTrigger++)
1103   {
1104      if (shape->triggers[seq.firstTrigger + iTrigger].state & offTriggers)
1105         shape->triggers[seq.firstTrigger + iTrigger].state |= TSShape::Trigger::InvertOnReverse;
1106   }
1107}
1108
1109//-----------------------------------------------------------------------------
1110
1111void TSShapeLoader::sortDetails()
1112{
1113   // Sort objects by: transparency, material index and node index
1114
1115
1116   // Insert NULL meshes where required
1117   for (S32 iSub = 0; iSub < subshapes.size(); iSub++)
1118   {
1119      Vector<S32> validDetails;
1120      shape->getSubShapeDetails(iSub, validDetails);
1121
1122      for (S32 iDet = 0; iDet < validDetails.size(); iDet++)
1123      {
1124         TSShape::Detail &detail = shape->details[validDetails[iDet]];
1125         if (detail.subShapeNum >= 0)
1126            detail.objectDetailNum = iDet;
1127
1128         for (S32 iObj = shape->subShapeFirstObject[iSub];
1129            iObj < (shape->subShapeFirstObject[iSub] + shape->subShapeNumObjects[iSub]);
1130            iObj++)
1131         {
1132            TSShape::Object &object = shape->objects[iObj];
1133
1134            // Insert a NULL mesh for this detail level if required (ie. if the
1135            // object does not already have a mesh with an equal or higher detail)
1136            S32 meshIndex = (iDet < object.numMeshes) ? iDet : object.numMeshes-1;
1137
1138            if (appMeshes[object.startMeshIndex + meshIndex]->detailSize < shape->details[iDet].size)
1139            {
1140               // Add a NULL mesh
1141               appMeshes.insert(object.startMeshIndex + iDet, NULL);
1142               object.numMeshes++;
1143
1144               // Fixup the start index for the other objects
1145               for (S32 k = iObj+1; k < shape->objects.size(); k++)
1146                  shape->objects[k].startMeshIndex++;
1147            }
1148         }
1149      }
1150   }
1151}
1152
1153// Install into the TSShape, the shape is expected to be empty.
1154// Data is not copied, the TSShape is modified to point to memory
1155// managed by this object.  This object is also bound to the TSShape
1156// object and will be deleted when it's deleted.
1157void TSShapeLoader::install()
1158{
1159   // Arrays that are filled in by ts shape init, but need
1160   // to be allocated beforehand.
1161   shape->subShapeFirstTranslucentObject.setSize(shape->subShapeFirstObject.size());
1162
1163   // Construct TS sub-meshes
1164   shape->meshes.setSize(appMeshes.size());
1165   for (U32 m = 0; m < appMeshes.size(); m++)
1166      shape->meshes[m] = appMeshes[m] ? appMeshes[m]->constructTSMesh() : NULL;
1167
1168   // Remove empty meshes and objects
1169   for (S32 iObj = shape->objects.size()-1; iObj >= 0; iObj--)
1170   {
1171      TSShape::Object& obj = shape->objects[iObj];
1172      for (S32 iMesh = obj.numMeshes-1; iMesh >= 0; iMesh--)
1173      {
1174         TSMesh *mesh = shape->meshes[obj.startMeshIndex + iMesh];
1175
1176         if (mesh && !mesh->mPrimitives.size())
1177         {
1178            S32 oldMeshCount = obj.numMeshes;
1179            destructInPlace(mesh);
1180            shape->removeMeshFromObject(iObj, iMesh);
1181            iMesh -= (oldMeshCount - obj.numMeshes - 1);      // handle when more than one mesh is removed
1182         }
1183      }
1184
1185      if (!obj.numMeshes)
1186         shape->removeObject(shape->getName(obj.nameIndex));
1187   }
1188
1189   // Add a dummy object if needed so the shape loads and renders ok
1190   if (!shape->details.size())
1191   {
1192      shape->addDetail("detail", 2, 0);
1193      shape->subShapeNumObjects.last() = 1;
1194
1195      shape->meshes.push_back(NULL);
1196
1197      shape->objects.increment();
1198
1199      TSShape::Object& lastObject = shape->objects.last();
1200      lastObject.nameIndex = shape->addName("dummy");
1201      lastObject.nodeIndex = 0;
1202      lastObject.startMeshIndex = 0;
1203      lastObject.numMeshes = 1;
1204
1205      shape->objectStates.increment();
1206      shape->objectStates.last().frameIndex = 0;
1207      shape->objectStates.last().matFrameIndex = 0;
1208      shape->objectStates.last().vis = 1.0f;
1209   }
1210
1211   // Update smallest visible detail
1212   shape->mSmallestVisibleDL = -1;
1213   shape->mSmallestVisibleSize = 999999;
1214   for (S32 i = 0; i < shape->details.size(); i++)
1215   {
1216      if ((shape->details[i].size >= 0) &&
1217         (shape->details[i].size < shape->mSmallestVisibleSize))
1218      {
1219         shape->mSmallestVisibleDL = i;
1220         shape->mSmallestVisibleSize = shape->details[i].size;
1221      }
1222   }
1223
1224   computeBounds(shape->mBounds);
1225   if (!shape->mBounds.isValidBox())
1226      shape->mBounds = Box3F(1.0f);
1227
1228   shape->mBounds.getCenter(&shape->center);
1229   shape->mRadius = (shape->mBounds.maxExtents - shape->center).len();
1230   shape->tubeRadius = shape->mRadius;
1231
1232   shape->init();
1233   shape->finalizeEditable();
1234}
1235
1236void TSShapeLoader::computeBounds(Box3F& bounds)
1237{
1238   // Compute the box that encloses the model geometry
1239   bounds = Box3F::Invalid;
1240
1241   // Use bounds node geometry if present
1242   if ( boundsNode && boundsNode->getNumMesh() )
1243   {
1244      for (S32 iMesh = 0; iMesh < boundsNode->getNumMesh(); iMesh++)
1245      {
1246         AppMesh* mesh = boundsNode->getMesh( iMesh );
1247         if ( !mesh )
1248            continue;
1249
1250         Box3F meshBounds;
1251         mesh->computeBounds( meshBounds );
1252         if ( meshBounds.isValidBox() )
1253            bounds.intersect( meshBounds );
1254      }
1255   }
1256   else
1257   {
1258      // Compute bounds based on all geometry in the model
1259      for (S32 iMesh = 0; iMesh < appMeshes.size(); iMesh++)
1260      {
1261         AppMesh* mesh = appMeshes[iMesh];
1262         if ( !mesh )
1263            continue;
1264
1265         Box3F meshBounds;
1266         mesh->computeBounds( meshBounds );
1267         if ( meshBounds.isValidBox() )
1268            bounds.intersect( meshBounds );
1269      }
1270   }
1271}
1272
1273TSShapeLoader::~TSShapeLoader()
1274{
1275   clearNodeTransformCache();
1276
1277   // Clear shared AppMaterial list
1278   for (S32 iMat = 0; iMat < AppMesh::appMaterials.size(); iMat++)
1279      delete AppMesh::appMaterials[iMat];
1280   AppMesh::appMaterials.clear();
1281
1282   // Delete Subshapes
1283   delete boundsNode;
1284   for (S32 iSub = 0; iSub < subshapes.size(); iSub++)
1285      delete subshapes[iSub];
1286
1287   // Delete AppSequences
1288   for (S32 iSeq = 0; iSeq < appSequences.size(); iSeq++)
1289      delete appSequences[iSeq];
1290   appSequences.clear();   
1291}
1292
1293// Static functions to handle supported formats for shape loader.
1294void TSShapeLoader::addFormat(String name, String extension)
1295{
1296   ShapeFormat newFormat;
1297   newFormat.mName = name;
1298   newFormat.mExtension = extension;
1299   smFormats.push_back(newFormat);
1300}
1301
1302String TSShapeLoader::getFormatExtensions()
1303{
1304   // "*.dsq TAB *.dae TAB
1305   StringBuilder output;
1306   for(U32 n = 0; n < smFormats.size(); ++n)
1307   {
1308      output.append("*.");
1309      output.append(smFormats[n].mExtension);
1310      output.append("\t");
1311   }
1312   return output.end();
1313}
1314
1315String TSShapeLoader::getFormatFilters()
1316{
1317   // "DSQ Files|*.dsq|COLLADA Files|*.dae|"
1318   StringBuilder output;
1319   for(U32 n = 0; n < smFormats.size(); ++n)
1320   {
1321      output.append(smFormats[n].mName);
1322      output.append("|*.");
1323      output.append(smFormats[n].mExtension);
1324      output.append("|");
1325   }
1326   return output.end();
1327}
1328
1329bool TSShapeLoader::isSupportedFormat(String extension)
1330{
1331   String extLower = String::ToLower(extension);
1332   for (U32 n = 0; n < smFormats.size(); ++n)
1333   {
1334      if (smFormats[n].mExtension.equal(extLower))
1335         return true;
1336   }
1337   return false;
1338}
1339
1340DefineEngineFunction( getFormatExtensions, const char*, ( ),, 
1341  "Returns a list of supported shape format extensions separated by tabs."
1342  "Example output: *.dsq TAB *.dae TAB")
1343{
1344   return Con::getReturnBuffer(TSShapeLoader::getFormatExtensions());
1345}
1346
1347DefineEngineFunction( getFormatFilters, const char*, ( ),, 
1348  "Returns a list of supported shape formats in filter form.\n"
1349  "Example output: DSQ Files|*.dsq|COLLADA Files|*.dae|")
1350{
1351   return Con::getReturnBuffer(TSShapeLoader::getFormatFilters());
1352}
1353
1354DefineEngineFunction(isSupportedFormat, bool, (const char* extension), , "")
1355{
1356   return TSShapeLoader::isSupportedFormat(extension);
1357}
1358