void TSShapeLoader::generateSkins()
{
   Vector<AppMesh*> skins;
   for (int iObject = 0; iObject < shape->objects.size(); iObject++)
   {
      for (int iMesh = 0; iMesh < shape->objects[iObject].numMeshes; iMesh++)
      {
         AppMesh* mesh = appMeshes[shape->objects[iObject].startMeshIndex + iMesh];
         if (mesh->isSkin())
            skins.push_back(mesh);
      }
   }

   for (int iSkin = 0; iSkin < skins.size(); iSkin++)
   {
      updateProgress(Load_GenerateSkins, "Generating skins...", skins.size(), iSkin);

      // Get skin data (bones, vertex weights etc)
      AppMesh* skin = skins[iSkin];
      skin->lookupSkinData();

      // Just copy initial verts and norms for now
      skin->initialVerts.set(skin->points.address(), skin->vertsPerFrame);
      skin->initialNorms.set(skin->normals.address(), skin->vertsPerFrame);

      // Map bones to nodes
      skin->nodeIndex.setSize(skin->bones.size());
      for (int iBone = 0; iBone < skin->bones.size(); iBone++)
      {
         // Find the node that matches this bone
         skin->nodeIndex[iBone] = -1;
         for (int iNode = 0; iNode < appNodes.size(); iNode++)
         {
            if (appNodes[iNode]->isEqual(skin->bones[iBone]))
            {
               delete skin->bones[iBone];
               skin->bones[iBone] = appNodes[iNode];
               skin->nodeIndex[iBone] = iNode;
               break;
            }
         }

         if (skin->nodeIndex[iBone] == -1)
         {
            Con::warnf("Could not find bone %d. Defaulting to first node", iBone);
            skin->nodeIndex[iBone] = 0;
         }
      }
   }
}
void TSShapeLoader::generateDefaultStates()
{
   // Generate default object states (includes initial geometry)
   for (int iObject = 0; iObject < shape->objects.size(); iObject++)
   {
      updateProgress(Load_GenerateDefaultStates, "Generating initial mesh and node states...",
         shape->objects.size(), iObject);

      TSShape::Object& obj = shape->objects[iObject];

      // Calculate the objectOffset for each mesh at T=0
      for (int iMesh = 0; iMesh < obj.numMeshes; iMesh++)
      {
         AppMesh* appMesh = appMeshes[obj.startMeshIndex + iMesh];
         AppNode* appNode = obj.nodeIndex >= 0 ? appNodes[obj.nodeIndex] : boundsNode;

         MatrixF meshMat(appMesh->getMeshTransform(DefaultTime));
         MatrixF nodeMat(appMesh->isSkin() ? meshMat : appNode->getNodeTransform(DefaultTime));

         zapScale(nodeMat);

         appMesh->objectOffset = nodeMat.inverse() * meshMat;
      }

      generateObjectState(shape->objects[iObject], DefaultTime, true, true);
   }

   // Generate default node transforms
   for (int iNode = 0; iNode < appNodes.size(); iNode++)
   {
      // Determine the default translation and rotation for the node
      QuatF rot, srot;
      Point3F trans, scale;
      generateNodeTransform(appNodes[iNode], DefaultTime, false, 0, rot, trans, srot, scale);

      // Add default node translation and rotation
      addNodeRotation(rot, true);
      addNodeTranslation(trans, true);
   }
}
void TSShapeLoader::recurseSubshape(AppNode* appNode, S32 parentIndex, bool recurseChildren)
{
   // Ignore local bounds nodes
   if (appNode->isBounds())
      return;

   S32 subShapeNum = shape->subShapeFirstNode.size()-1;
   Subshape* subshape = subshapes[subShapeNum];

   // Check if we should collapse this node
   S32 myIndex;
   if (ignoreNode(appNode->getName()))
   {
      myIndex = parentIndex;
   }
   else
   {
      // Check that adding this node will not exceed the maximum node count
      if (shape->nodes.size() >= MAX_TS_SET_SIZE)
         return;

      myIndex = shape->nodes.size();
      String nodeName = getUniqueName(appNode->getName(), cmpShapeName, shape->names);

      // Create the 3space node
      shape->nodes.increment();
      shape->nodes.last().nameIndex = shape->addName(nodeName);
      shape->nodes.last().parentIndex = parentIndex;
      shape->nodes.last().firstObject = -1;
      shape->nodes.last().firstChild = -1;
      shape->nodes.last().nextSibling = -1;

      // Add the AppNode to a matching list (so AppNodes can be accessed using 3space
      // node indices)
      appNodes.push_back(appNode);
      appNodes.last()->mParentIndex = parentIndex;

      // Check for NULL detail or AutoBillboard nodes (no children or geometry)
      if ((appNode->getNumChildNodes() == 0) &&
          (appNode->getNumMesh() == 0))
      {
         S32 size = 0x7FFFFFFF;
         String dname(String::GetTrailingNumber(appNode->getName(), size));

         if (dStrEqual(dname, "nulldetail") && (size != 0x7FFFFFFF))
         {
            shape->addDetail("detail", size, subShapeNum);
         }
         else if (appNode->isBillboard() && (size != 0x7FFFFFFF))
         {
            // AutoBillboard detail
            S32 numEquatorSteps = 4;
            S32 numPolarSteps = 0;
            F32 polarAngle = 0.0f;
            S32 dl = 0;
            S32 dim = 64;
            bool includePoles = true;

            appNode->getInt("BB::EQUATOR_STEPS", numEquatorSteps);
            appNode->getInt("BB::POLAR_STEPS", numPolarSteps);
            appNode->getFloat("BB::POLAR_ANGLE", polarAngle);
            appNode->getInt("BB::DL", dl);
            appNode->getInt("BB::DIM", dim);
            appNode->getBool("BB::INCLUDE_POLES", includePoles);

            S32 detIndex = shape->addDetail( "bbDetail", size, -1 );
            shape->details[detIndex].bbEquatorSteps = numEquatorSteps;
            shape->details[detIndex].bbPolarSteps = numPolarSteps;
            shape->details[detIndex].bbDetailLevel = dl;
            shape->details[detIndex].bbDimension = dim;
            shape->details[detIndex].bbIncludePoles = includePoles;
            shape->details[detIndex].bbPolarAngle = polarAngle;
         }
      }
   }

   // Collect geometry
   for (U32 iMesh = 0; iMesh < appNode->getNumMesh(); iMesh++)
   {
      AppMesh* mesh = appNode->getMesh(iMesh);
      if (!ignoreMesh(mesh->getName()))
      {
         subshape->objMeshes.push_back(mesh);
         subshape->objNodes.push_back(mesh->isSkin() ? -1 : myIndex);
      }
   }

   // Create children
   if (recurseChildren)
   {
      for (int iChild = 0; iChild < appNode->getNumChildNodes(); iChild++)
         recurseSubshape(appNode->getChildNode(iChild), myIndex, true);
   }
}
   bool AppSceneEnum::processNode(AppNode * node)
   {
      // Helper method to help rot nodes that we find in the scene.

      // At this stage we do not need to collect all the nodes
      // because the tree structure will still be there when we
      // build the shape.  What we need to do right now is grab
      // the top of all the subtrees, any meshes hanging on the
      // root level (these will be lower detail levels, we don't
      // need to grab meshes on the sub-trees because they will
      // be found when we recurse into the sub-tree), the bounds
      // node, and any sequences.

      const char * name  = node->getName();
      const char * pname = node->getParentName();

      AppConfig::PrintDump(PDPass1,avar("Processing Node %s with parent %s\r\n", name, pname));

      AppSequence * seq = getSequence(node);
      if (seq)
      {         
         sequences.push_back(seq);
         return true;
      }

      if (node->isDummy())
         return false;

      if (isSubtree(node))
      {
         // Add this node to the subtree list...
         AppConfig::PrintDump(PDPass1,avar("Found subtree starting at Node \"%s\"\r\n",name));
         subtrees.push_back(node);
         return true;
      }

      // See if it is a bounding box.  If so, save it as THE bounding
      // box for the scene
      if (node->isBounds())
      {
         if (boundsNode)
         {
            setExportError("More than one bounds node found.");
            AppConfig::PrintDump(PDPass1,"More than one bounds node found.\r\n");
         }
         else
            AppConfig::PrintDump(PDPass1,"Bounding box found\r\n");
         boundsNode = node;
         return true;
      }

      // if we use this node, then be sure to return true so the caller doesn't delete it
      bool used = false;

      if (node->getNumMesh()!=0)
      {
         for (S32 i=0; i<node->getNumMesh(); i++)
         {
            AppMesh * mesh = node->getMesh(i);
            if (mesh->isSkin())
            {
               AppConfig::PrintDump(PDPass1,avar("Skin \"%s\" with parent \"%s\" added to entry list\r\n",mesh->getName(),pname));
               skins.push_back(mesh);
               used = true;
            }
            else
            {
               if (node->isParentRoot())
               {
                  AppConfig::PrintDump(PDPass1,avar("Mesh \"%s\" with parent \"%s\" added to entry list\r\n",mesh->getName(),pname));
                  meshNodes.push_back(node);
                  meshes.push_back(mesh);
                  used = true;
               }
            }
         }
         if (used)
            usedNodes.push_back(node);
      }
      return used;
   }