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;
         }
      }
   }
}
bool TSShapeLoader::processNode(AppNode* node)
{
   // Detect bounds node
   if ( node->isBounds() )
   {
      if ( boundsNode )
      {
         Con::warnf( "More than one bounds node found" );
         return false;
      }
      boundsNode = node;

      // Process bounds geometry
      MatrixF boundsMat(boundsNode->getNodeTransform(DefaultTime));
      boundsMat.inverse();
      zapScale(boundsMat);
      for (S32 iMesh = 0; iMesh < boundsNode->getNumMesh(); iMesh++)
      {
         AppMesh* mesh = boundsNode->getMesh(iMesh);
         MatrixF transform = mesh->getMeshTransform(DefaultTime);
         transform.mulL(boundsMat);
         mesh->lockMesh(DefaultTime, transform);
      }
      return true;
   }

   // Detect sequence markers
   if ( node->isSequence() )
   {
      //appSequences.push_back(new AppSequence(node));
      return false;
   }

   // Add this node to the subshape (create one if needed)
   if ( subshapes.size() == 0 )
      subshapes.push_back( new TSShapeLoader::Subshape );

   subshapes.last()->branches.push_back( node );

   return true;
}
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::computeBounds(Box3F& bounds)
{
   // Compute the box that encloses the model geometry
   bounds = Box3F::Invalid;

   // Use bounds node geometry if present
   if ( boundsNode && boundsNode->getNumMesh() )
   {
      for (S32 iMesh = 0; iMesh < boundsNode->getNumMesh(); iMesh++)
      {
         AppMesh* mesh = boundsNode->getMesh( iMesh );
         if ( !mesh )
            continue;

         Box3F meshBounds;
         mesh->computeBounds( meshBounds );
         if ( meshBounds.isValidBox() )
            bounds.intersect( meshBounds );
      }
   }
   else
   {
      // Compute bounds based on all geometry in the model
      for (S32 iMesh = 0; iMesh < appMeshes.size(); iMesh++)
      {
         AppMesh* mesh = appMeshes[iMesh];
         if ( !mesh )
            continue;

         Box3F meshBounds;
         mesh->computeBounds( meshBounds );
         if ( meshBounds.isValidBox() )
            bounds.intersect( meshBounds );
      }
   }
}
void TSShapeLoader::generateFrame(TSShape::Object& obj, F32 t, bool addFrame, bool addMatFrame)
{
   for (int iMesh = 0; iMesh < obj.numMeshes; iMesh++)
   {
      AppMesh* appMesh = appMeshes[obj.startMeshIndex + iMesh];

      U32 oldNumPoints = appMesh->points.size();
      U32 oldNumUvs = appMesh->uvs.size();

      // Get the mesh geometry at time, 't'
      // Geometry verts, normals and tverts can be animated (different set for
      // each frame), but the TSDrawPrimitives stay the same, so the way lockMesh
      // works is that it will only generate the primitives once, then after that
      // will just append verts, normals and tverts each time it is called.
      appMesh->lockMesh(t, appMesh->objectOffset);

      // Calculate vertex normals if required
      if (appMesh->normals.size() != appMesh->points.size())
         appMesh->computeNormals();

      // If this is the first call, set the number of points per frame
      if (appMesh->numFrames == 0)
      {
         appMesh->vertsPerFrame = appMesh->points.size();
      }
      else
      {
         // Check frame topology => ie. that the right number of points, normals
         // and tverts was added
         if ((appMesh->points.size() - oldNumPoints) != appMesh->vertsPerFrame)
         {
            Con::warnf("Wrong number of points (%d) added at time=%f (expected %d)",
               appMesh->points.size() - oldNumPoints, t, appMesh->vertsPerFrame);
            addFrame = false;
         }
         if ((appMesh->normals.size() - oldNumPoints) != appMesh->vertsPerFrame)
         {
            Con::warnf("Wrong number of normals (%d) added at time=%f (expected %d)",
               appMesh->normals.size() - oldNumPoints, t, appMesh->vertsPerFrame);
            addFrame = false;
         }
         if ((appMesh->uvs.size() - oldNumUvs) != appMesh->vertsPerFrame)
         {
            Con::warnf("Wrong number of tverts (%d) added at time=%f (expected %d)",
               appMesh->uvs.size() - oldNumUvs, t, appMesh->vertsPerFrame);
            addMatFrame = false;
         }
      }

      // Because lockMesh adds points, normals AND tverts each call, if we didn't
      // actually want another frame or matFrame, we need to remove them afterwards.
      // In the common case (we DO want the frame), we can do nothing => the
      // points/normals/tverts are already in place!
      if (addFrame)
      {
         appMesh->numFrames++;
      }
      else
      {
         appMesh->points.setSize(oldNumPoints);
         appMesh->normals.setSize(oldNumPoints);
      }

      if (addMatFrame)
      {
         appMesh->numMatFrames++;
      }
      else
      {
         appMesh->uvs.setSize(oldNumPoints);
      }
   }
}
void TSShapeLoader::generateObjects()
{
   for (S32 iSub = 0; iSub < subshapes.size(); iSub++)
   {
      Subshape* subshape = subshapes[iSub];
      shape->subShapeFirstObject.push_back(shape->objects.size());

      // Get the names and sizes of the meshes for this subshape
      Vector<String> meshNames;
      for (S32 iMesh = 0; iMesh < subshape->objMeshes.size(); iMesh++)
      {
         AppMesh* mesh = subshape->objMeshes[iMesh];
         mesh->detailSize = 2;
         String name = String::GetTrailingNumber( mesh->getName(), mesh->detailSize );
         name = getUniqueName( name, cmpMeshNameAndSize, meshNames, &(subshape->objMeshes), (void*)mesh->detailSize );
         meshNames.push_back( name );

         // Fix up any collision details that don't have a negative detail level.
         if (  dStrStartsWith(meshNames[iMesh], "Collision") ||
               dStrStartsWith(meshNames[iMesh], "LOSCol") )
         {
            if (mesh->detailSize > 0)
               mesh->detailSize = -mesh->detailSize;
         }
      }

      // An 'object' is a collection of meshes with the same base name and
      // different detail sizes. The object is attached to the node of the
      // highest detail mesh.

      // Sort the 3 arrays (objMeshes, objNodes, meshNames) by name and size
      for (S32 i = 0; i < subshape->objMeshes.size()-1; i++)
      {
         for (S32 j = i+1; j < subshape->objMeshes.size(); j++)
         {
            if ((meshNames[i].compare(meshNames[j]) < 0) ||
               ((meshNames[i].compare(meshNames[j]) == 0) &&
               (subshape->objMeshes[i]->detailSize < subshape->objMeshes[j]->detailSize)))
            {
               {
                  AppMesh* tmp = subshape->objMeshes[i];
                  subshape->objMeshes[i] = subshape->objMeshes[j];
                  subshape->objMeshes[j] = tmp;
               }
               {
                  S32 tmp = subshape->objNodes[i];
                  subshape->objNodes[i] = subshape->objNodes[j];
                  subshape->objNodes[j] = tmp;
               }
               {
                  String tmp = meshNames[i];
                  meshNames[i] = meshNames[j];
                  meshNames[j] = tmp;
               }
            }
         }
      }

      // Now create objects
      const String* lastName = 0;
      for (S32 iMesh = 0; iMesh < subshape->objMeshes.size(); iMesh++)
      {
         AppMesh* mesh = subshape->objMeshes[iMesh];

         if (!lastName || (meshNames[iMesh] != *lastName))
         {
            shape->objects.increment();
            shape->objects.last().nameIndex = shape->addName(meshNames[iMesh]);
            shape->objects.last().nodeIndex = subshape->objNodes[iMesh];
            shape->objects.last().startMeshIndex = appMeshes.size();
            shape->objects.last().numMeshes = 0;
            lastName = &meshNames[iMesh];
         }

         // Add this mesh to the object
         appMeshes.push_back(mesh);
         shape->objects.last().numMeshes++;

         // Set mesh flags
         mesh->flags = 0;
         if (mesh->isBillboard())
         {
            mesh->flags |= TSMesh::Billboard;
            if (mesh->isBillboardZAxis())
               mesh->flags |= TSMesh::BillboardZAxis;
         }

         // Set the detail name... do fixups for collision details.
         const char* detailName = "detail";
         if ( mesh->detailSize < 0 )
         {
            if (  dStrStartsWith(meshNames[iMesh], "Collision") ||
                  dStrStartsWith(meshNames[iMesh], "Col") )
               detailName = "Collision";
            else if (dStrStartsWith(meshNames[iMesh], "LOSCol"))
               detailName = "LOS";
         }

         // Attempt to add the detail (will fail if it already exists)
         S32 oldNumDetails = shape->details.size();
         shape->addDetail(detailName, mesh->detailSize, iSub);
         if (shape->details.size() > oldNumDetails)
         {
            Con::warnf("Object mesh \"%s\" has no matching detail (\"%s%d\" has"
               " been added automatically)", mesh->getName(false), detailName, mesh->detailSize);
         }
      }

      // Get object count for this subshape
      shape->subShapeNumObjects.push_back(shape->objects.size() - shape->subShapeFirstObject.last());
   }
}
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;
   }