Torque3D Documentation / _generateds / colladaAppMesh.cpp

colladaAppMesh.cpp

Engine/source/ts/collada/colladaAppMesh.cpp

More...

Classes:

Namespaces:

namespace

Public Defines

define
GET_VIS(node)       (dynamic_cast< *>(node)->nodeExt->visibility.getValue())
define
IS_VIS_ANIMATED(node)       (dynamic_cast< *>(node)->nodeExt->visibility.isAnimated(appSeq->getStart(), appSeq->getEnd()))

Public Typedefs

VertTupleMap 

Public Functions

daeElement *
findInputSource(const daeElement * input)

Detailed Description

Public Defines

GET_VIS(node)       (dynamic_cast< *>(node)->nodeExt->visibility.getValue())
IS_VIS_ANIMATED(node)       (dynamic_cast< *>(node)->nodeExt->visibility.isAnimated(appSeq->getStart(), appSeq->getEnd()))

Public Typedefs

typedef Map< VertTuple, S32 > VertTupleMap 

Public Functions

findInputSource(const daeElement * input)

   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
  26// Make GCC happy.  Needs to have seen this before processing the
  27// hash table template.
  28struct VertTuple;
  29namespace DictHash
  30{
  31   inline U32 hash( const VertTuple& data );
  32}
  33
  34#include "ts/collada/colladaExtensions.h"
  35#include "ts/collada/colladaAppMesh.h"
  36#include "ts/collada/colladaAppNode.h"
  37#include "ts/collada/colladaAppMaterial.h"
  38
  39#include "core/util/tDictionary.h"
  40#include "core/stringTable.h"
  41
  42using namespace ColladaUtils;
  43
  44bool ColladaAppMesh::fixedSizeEnabled = false;
  45S32 ColladaAppMesh::fixedSize = 2;
  46
  47//-----------------------------------------------------------------------------
  48// Define a VertTuple dictionary to allow fast tuple lookups
  49namespace DictHash
  50{
  51   inline U32 hash(const VertTuple& data)
  52   {
  53      return (U32)data.vertex;
  54   }
  55}
  56
  57typedef Map<VertTuple, S32> VertTupleMap;
  58
  59//-----------------------------------------------------------------------------
  60// Find a source with matching ID. Cannot use the DOM .getElement method since
  61// some lame Collada exporters generate <source>s with non-unique IDs.
  62daeElement* findInputSource(const daeElement* input)
  63{
  64   // Try using the DOM .getElement method => the resolved element's parent
  65   // should be the input's grandparent
  66   daeElement* parent = ((daeElement*)input)->getParentElement();
  67   daeElement* grandparent = parent ? parent->getParentElement() : 0;
  68   if (!grandparent)
  69      return NULL;
  70
  71   const domURIFragmentType* uri = 0;
  72   if (input->getElementType() == COLLADA_TYPE::INPUTLOCAL)
  73      uri = &daeSafeCast<domInputLocal>((daeElement*)input)->getSource();
  74   else if (input->getElementType() == COLLADA_TYPE::INPUTLOCALOFFSET)
  75      uri = &daeSafeCast<domInputLocalOffset>((daeElement*)input)->getSource();
  76   if (!uri)
  77      return NULL;
  78
  79   daeElement* element = uri->getElement();
  80   if (element && element->getParentElement() == grandparent)
  81      return element;
  82   else
  83   {
  84      // Probably a non-unique ID => search for the matching element manually
  85
  86      // Skip the leading '#' on source IDs
  87      const char* id = uri->originalStr().c_str();
  88      if (id && (id[0] == '#'))
  89         id++;
  90
  91      for (S32 iChild = 0; iChild < grandparent->getChildren().getCount(); iChild++)
  92      {
  93         element = grandparent->getChildren()[iChild];
  94         if ((element->getElementType() != COLLADA_TYPE::SOURCE) &&
  95             (element->getElementType() != COLLADA_TYPE::VERTICES))
  96             continue;
  97
  98         if (dStrEqual(id, element->getAttribute("id").c_str()))
  99            return element;
 100      }
 101   }
 102   return NULL;
 103}
 104
 105//-----------------------------------------------------------------------------
 106// Collada scatters the data required for geometry all over the place; this class
 107// helps to group it all together.
 108class MeshStreams
 109{
 110public:
 111   // The sources we want to read from the mesh stream. Can be any order, but
 112   // sources of the same type (eg. UVs and UV2s) must be sequential (to allow
 113   // ordering by set index)
 114   enum eSourceType {
 115      Points,
 116      Normals,
 117      Colors,
 118      UVs,
 119      UV2s,
 120      Joints,
 121      Weights,
 122      InvBindMatrices,
 123      NumStreams
 124   };
 125
 126   const char* SourceTypeToSemantic(eSourceType type)
 127   {
 128      switch ( type )
 129      {
 130         case Points:            return "POSITION";
 131         case Normals:           return "NORMAL";
 132         case Colors:            return "COLOR";
 133         case UVs:
 134         case UV2s:              return "TEXCOORD";
 135         case Joints:            return "JOINT";
 136         case Weights:           return "WEIGHT";
 137         case InvBindMatrices:   return "INV_BIND_MATRIX";
 138         default:                return "";
 139      }
 140   }
 141
 142private:
 143   /// Classify a single input
 144   template<class T>
 145   static void selectInput(T input, T sortedInputs[], S32 start, S32 end=-1)
 146   {
 147      if (end == -1)
 148         end = start;
 149
 150      // Get the set for this input
 151      const domInputLocalOffset* localOffset = daeSafeCast<domInputLocalOffset>(input);
 152      domUint newSet = localOffset ? localOffset->getSet() : 0;
 153
 154      // Add the input to the right place in the list (somewhere between start and end)
 155      for (S32 i = start; i <= end; i++) {
 156         localOffset = daeSafeCast<domInputLocalOffset>(sortedInputs[i]);
 157         domUint set = localOffset ? localOffset->getSet() : 0xFFFFFFFF;
 158         if (newSet < set) {
 159            for (S32 j = i + 1; j <= end; j++)
 160               sortedInputs[j] = sortedInputs[j-1];
 161            sortedInputs[i] = input;
 162            return;
 163         }
 164      }
 165   }
 166
 167   /// Attempt to initialise a _SourceReader
 168   template<class T>
 169   bool initSourceReader(T input, eSourceType type, _SourceReader& reader, const char* params[])
 170   {
 171      if (!input)
 172         return false;
 173
 174      // Try to get the source element
 175      const domSource* source = 0;
 176      daeElement *element = findInputSource(input);
 177      if (element->getElementType() == COLLADA_TYPE::SOURCE)
 178         source = daeSafeCast<domSource>(element);
 179      else if (element->getElementType() == COLLADA_TYPE::VERTICES) {
 180         const domVertices* vertices = daeSafeCast<domVertices>(element);
 181         // Search for the input with the desired semantic
 182         const char* semantic = SourceTypeToSemantic( type );
 183         for (S32 iInput = 0; iInput < vertices->getInput_array().getCount(); iInput++)
 184         {
 185            domInputLocal* vInput = vertices->getInput_array().get(iInput);
 186            if (dStrEqual(vInput->getSemantic(), semantic))
 187            {
 188               source = daeSafeCast<domSource>(findInputSource(vInput));
 189               break;
 190            }
 191         }
 192      }
 193      if (!source)
 194         return false;
 195
 196      return reader.initFromSource(source, params);
 197   }
 198
 199public:
 200
 201   _SourceReader     points;
 202   _SourceReader     normals;
 203   _SourceReader     colors;
 204   _SourceReader     uvs;
 205   _SourceReader     uv2s;
 206
 207   _SourceReader     joints;
 208   _SourceReader     weights;
 209   _SourceReader     invBindMatrices;
 210
 211   /// Clear the mesh streams
 212   void reset()
 213   {
 214      points.reset();
 215      normals.reset();
 216      colors.reset();
 217      uvs.reset();
 218      uv2s.reset();
 219      joints.reset();
 220      weights.reset();
 221      invBindMatrices.reset();
 222   }
 223
 224   /// Classify a set of inputs by type and set number (needs to be a template
 225   /// because Collada has two forms of input arrays that may be accessed in
 226   /// an identical fashion, but the classes are unrelated. Sigh.
 227   template<class T>
 228   static void classifyInputs(const daeTArray<T>& inputs, T sortedInputs[], U32 *maxOffset=0)
 229   {
 230      if (maxOffset)
 231         *maxOffset = 0;
 232
 233      // Clear output array
 234      for (S32 i = 0; i < NumStreams; i++)
 235         sortedInputs[i] = 0;
 236
 237      // Separate inputs by type, and sort by set (ie. lowest TEXCOORD set becomes UV,
 238      // next TEXCOORD set becomes UV2 etc)
 239      for (S32 iInput = 0; iInput < inputs.getCount(); iInput++) {
 240
 241         const T& input = inputs[iInput];
 242         const daeString semantic = input->getSemantic();
 243
 244         if (dStrEqual(semantic, "VERTEX"))
 245         {
 246            domVertices* vertices = daeSafeCast<domVertices>(findInputSource(input));
 247
 248            // The <vertices> element may contain multiple inputs (eg. POSITION, NORMAL etc)
 249            domInputLocalRef verticesInputs[NumStreams];
 250            classifyInputs(vertices->getInput_array(), verticesInputs);
 251            for (S32 iStream = 0; iStream < NumStreams; iStream++)
 252            {
 253               if (verticesInputs[iStream] != 0)
 254                  sortedInputs[iStream] = input;
 255            }
 256         }
 257         else if (dStrEqual(semantic, "POSITION"))          selectInput(input, sortedInputs, Points);
 258         else if (dStrEqual(semantic, "NORMAL"))            selectInput(input, sortedInputs, Normals);
 259         else if (dStrEqual(semantic, "COLOR"))             selectInput(input, sortedInputs, Colors);
 260         else if (dStrEqual(semantic, "TEXCOORD"))          selectInput(input, sortedInputs, UVs, UV2s);
 261         else if (dStrEqual(semantic, "JOINT"))             selectInput(input, sortedInputs, Joints);
 262         else if (dStrEqual(semantic, "WEIGHT"))            selectInput(input, sortedInputs, Weights);
 263         else if (dStrEqual(semantic, "INV_BIND_MATRIX"))   selectInput(input, sortedInputs, InvBindMatrices);
 264
 265         if (maxOffset)
 266         {
 267            const domInputLocalOffset* localOffset = daeSafeCast<domInputLocalOffset>(input);
 268            domUint offset = localOffset ? localOffset->getOffset() : 0;
 269            if (offset > (*maxOffset))
 270               *maxOffset = offset;
 271         }
 272      }
 273   }
 274
 275   /// Read a set of inputs into the named sources. There may be multiple 'sets'
 276   /// of COLOR or TEXCOORD (uvs) streams, but we are only interested in the
 277   /// first COLOR set (ie. smallest set value), and the first 2 TEXCOORDS sets.
 278   template<class T>
 279   bool readInputs(const daeTArray<T>& inputs)
 280   {
 281      // Sort inputs by type and set to find the ones we are interested in
 282      T sortedInputs[NumStreams];
 283      classifyInputs(inputs, sortedInputs);
 284
 285      // Attempt to initialise the SourceReaders
 286      const char* vertex_params[] = { "X", "Y", "Z", "" };
 287      initSourceReader(sortedInputs[Points], Points, points, vertex_params);
 288
 289      const char* normal_params[] = { "X", "Y", "Z", "" };
 290      initSourceReader(sortedInputs[Normals], Normals, normals, normal_params);
 291
 292      const char* color_params[] = { "R", "G", "B", "A", "" };
 293      initSourceReader(sortedInputs[Colors], Colors, colors, color_params);
 294
 295      const char* uv_params[] = { "S", "T", "" };
 296      const char* uv_params2[] = { "U", "V", "" }; // some files use the nonstandard U,V or X,Y param names
 297      const char* uv_params3[] = { "X", "Y", "" };
 298      if (!initSourceReader(sortedInputs[UVs], UVs, uvs, uv_params))
 299         if (!initSourceReader(sortedInputs[UVs], UVs, uvs, uv_params2))
 300            initSourceReader(sortedInputs[UVs], UVs, uvs, uv_params3);
 301      if (!initSourceReader(sortedInputs[UV2s], UV2s, uv2s, uv_params))
 302         if (!initSourceReader(sortedInputs[UV2s], UV2s, uv2s, uv_params2))
 303            initSourceReader(sortedInputs[UV2s], UV2s, uv2s, uv_params3);
 304
 305      const char* joint_params[] = { "JOINT", "" };
 306      initSourceReader(sortedInputs[Joints], Joints, joints, joint_params);
 307
 308      const char* weight_params[] = { "WEIGHT", "" };
 309      initSourceReader(sortedInputs[Weights], Weights, weights, weight_params);
 310
 311      const char* matrix_params[] = { "TRANSFORM", "" };
 312      initSourceReader(sortedInputs[InvBindMatrices], InvBindMatrices, invBindMatrices, matrix_params);
 313
 314      return true;
 315   }
 316};
 317
 318//------------------------------------------------------------------------------
 319
 320ColladaAppMesh::ColladaAppMesh(const domInstance_geometry* instance, ColladaAppNode* node)
 321   : appNode(node),instanceGeom(instance), instanceCtrl(0),  geomExt(0)
 322{
 323   flags = 0;
 324   numFrames = 0;
 325   numMatFrames = 0;
 326}
 327
 328ColladaAppMesh::ColladaAppMesh(const domInstance_controller* instance, ColladaAppNode* node)
 329   : appNode(node),instanceGeom(0), instanceCtrl(instance),  geomExt(0)
 330{
 331   flags = 0;
 332   numFrames = 0;
 333   numMatFrames = 0;
 334}
 335
 336const char* ColladaAppMesh::getName(bool allowFixed)
 337{
 338   // Some exporters add a 'PIVOT' or unnamed node between the mesh and the
 339   // actual object node. Detect this and return the object node name instead
 340   // of the pivot node.
 341   const char* nodeName = appNode->getName();
 342   if ( dStrEqual(nodeName, "null") || dStrEndsWith(nodeName, "PIVOT") )
 343      nodeName = appNode->getParentName();
 344
 345   // If all geometry is being fixed to the same size, append the size
 346   // to the name
 347   return allowFixed && fixedSizeEnabled ? avar("%s %d", nodeName, fixedSize) : nodeName;
 348}
 349
 350MatrixF ColladaAppMesh::getMeshTransform(F32 time)
 351{
 352   return appNode->getNodeTransform(time);
 353}
 354
 355bool ColladaAppMesh::animatesVis(const AppSequence* appSeq)
 356{
 357   #define IS_VIS_ANIMATED(node)    \
 358      (dynamic_cast<const ColladaAppNode*>(node)->nodeExt->visibility.isAnimated(appSeq->getStart(), appSeq->getEnd()))
 359
 360   // Check if the node visibility is animated within the sequence interval
 361   return IS_VIS_ANIMATED(appNode) || (appNode->appParent ? IS_VIS_ANIMATED(appNode->appParent) : false);
 362}
 363
 364bool ColladaAppMesh::animatesMatFrame(const AppSequence* appSeq)
 365{
 366   // Texture coordinates may be animated in two ways:
 367   // - by animating the MAYA profile texture transform (diffuse texture)
 368   // - by animating the morph weights for morph targets with different UVs
 369
 370   // Check if the MAYA profile texture transform is animated
 371   for (S32 iMat = 0; iMat < appMaterials.size(); iMat++) {
 372      ColladaAppMaterial* appMat = static_cast<ColladaAppMaterial*>(appMaterials[iMat]);
 373      if (appMat->effectExt &&
 374          appMat->effectExt->animatesTextureTransform(appSeq->getStart(), appSeq->getEnd()))
 375         return true;
 376   }
 377
 378   // Check that the morph weights are animated within the sequence interval, 
 379   // and that the morph targets have different UVs to the base geometry.
 380   bool animated = false;
 381   bool differentUVs = false;
 382   if (const domMorph* morph = getMorph()) {
 383      for (S32 iInput = 0; iInput < morph->getTargets()->getInput_array().getCount(); iInput++) {
 384         const domInputLocal* input = morph->getTargets()->getInput_array()[iInput];
 385         if (dStrEqual(input->getSemantic(), "MORPH_TARGET")) {
 386            // @todo: Check if morph targets have different UVs to base geometry
 387            differentUVs = false;
 388         }
 389         if (dStrEqual(input->getSemantic(), "MORPH_WEIGHT")) {
 390            const domSource* source = daeSafeCast<domSource>(findInputSource(input));
 391            AnimatedFloatList weights(source ? source->getFloat_array() : 0);
 392            animated = weights.isAnimated(appSeq->getStart(), appSeq->getEnd());
 393         }
 394      }
 395   }
 396
 397   return (animated && differentUVs);
 398}
 399
 400bool ColladaAppMesh::animatesFrame(const AppSequence* appSeq)
 401{
 402   // Collada <morph>s ALWAYS contain vert positions, so just need to check if
 403   // the morph weights are animated within the sequence interval
 404   bool animated = false;
 405   if (const domMorph* morph = getMorph()) {
 406      for (S32 iInput = 0; iInput < morph->getTargets()->getInput_array().getCount(); iInput++) {
 407         const domInputLocal* input = morph->getTargets()->getInput_array()[iInput];
 408         if (dStrEqual(input->getSemantic(), "MORPH_WEIGHT")) {
 409            const domSource* source = daeSafeCast<domSource>(findInputSource(input));
 410            AnimatedFloatList weights(source ? source->getFloat_array() : 0);
 411            animated = weights.isAnimated(appSeq->getStart(), appSeq->getEnd());
 412            break;
 413         }
 414      }
 415   }
 416   return animated;
 417}
 418
 419F32 ColladaAppMesh::getVisValue(F32 t)
 420{
 421   #define GET_VIS(node)   \
 422      (dynamic_cast<const ColladaAppNode*>(node)->nodeExt->visibility.getValue(t))
 423
 424   // Get the visibility of the mesh's node at time, 't'
 425   return GET_VIS(appNode) * (appNode->appParent ? GET_VIS(appNode->appParent) : 1.0f);
 426}
 427
 428S32 ColladaAppMesh::addMaterial(const char* symbol)
 429{
 430   if (!symbol)
 431      return TSDrawPrimitive::NoMaterial;
 432
 433   // Lookup the symbol in the materials already bound to this geometry/controller
 434   // instance
 435   Map<StringTableEntry,U32>::Iterator itr = boundMaterials.find(symbol);
 436   if (itr != boundMaterials.end())
 437      return itr->value;
 438
 439   // Find the Collada material that this symbol maps to
 440   U32 matIndex = TSDrawPrimitive::NoMaterial;
 441   const domBind_material* binds = instanceGeom ? instanceGeom->getBind_material() :
 442                                                  instanceCtrl->getBind_material();
 443   if (binds) {
 444      const domInstance_material_Array& matArray = binds->getTechnique_common()->getInstance_material_array();
 445      for (S32 iBind = 0; iBind < matArray.getCount(); iBind++) {
 446         if (dStrEqual(matArray[iBind]->getSymbol(), symbol)) {
 447
 448            // Find the index of the bound material in the shape global list
 449            const domMaterial* mat = daeSafeCast<domMaterial>(matArray[iBind]->getTarget().getElement());
 450            for (matIndex = 0; matIndex < appMaterials.size(); matIndex++) {
 451               if (static_cast<ColladaAppMaterial*>(appMaterials[matIndex])->mat == mat)
 452                  break;
 453            }
 454
 455            // Check if this material needs to be added to the shape global list
 456            if (matIndex == appMaterials.size()) {
 457               if (mat)
 458                  appMaterials.push_back(new ColladaAppMaterial(mat));
 459               else
 460                  appMaterials.push_back(new ColladaAppMaterial(symbol));
 461            }
 462
 463            break;
 464         }
 465      }
 466   }
 467   else
 468   {
 469      // No Collada material is present for this symbol, so just create an empty one
 470      appMaterials.push_back(new ColladaAppMaterial(symbol));
 471   }
 472
 473   // Add this symbol to the bound list for the mesh
 474   boundMaterials.insert(StringTable->insert(symbol), matIndex);
 475   return matIndex;
 476}
 477
 478void ColladaAppMesh::getPrimitives(const domGeometry* geometry)
 479{
 480   // Only do this once
 481   if (primitives.size())
 482      return;
 483
 484   // Read the <geometry> extension
 485   if (!geomExt)
 486      geomExt = new ColladaExtension_geometry(geometry);
 487
 488   // Get the supported primitive elements for this geometry, and warn
 489   // about unsupported elements
 490   Vector<BasePrimitive*> meshPrims;
 491   const daeElementRefArray& contents = geometry->getMesh()->getContents();
 492   for (S32 iElem = 0; iElem < contents.getCount(); iElem++) {
 493
 494      if (BasePrimitive::isPrimitive(contents[iElem])) {
 495         if (BasePrimitive::isSupportedPrimitive(contents[iElem]))
 496            meshPrims.push_back(BasePrimitive::get(contents[iElem]));
 497         else {
 498            daeErrorHandler::get()->handleWarning(avar("Collada <%s> element "
 499               "in %s is not supported.", contents[iElem]->getElementName(),
 500               _GetNameOrId(geometry)));
 501         }
 502      }
 503   }
 504
 505   MeshStreams streams;
 506   VertTupleMap tupleMap;
 507
 508   // Create Torque primitives
 509   for (S32 iPrim = 0; iPrim < meshPrims.size(); iPrim++) {
 510
 511      // Primitive element must have at least 1 triangle
 512      const domListOfUInts* pTriData = meshPrims[iPrim]->getTriangleData();
 513      if (!pTriData)
 514         continue;
 515
 516      U32 numTriangles = pTriData->getCount() / meshPrims[iPrim]->getStride() / 3;
 517      if (!numTriangles)
 518         continue;
 519
 520      // Create TSMesh primitive
 521      primitives.increment();
 522      TSDrawPrimitive& primitive = primitives.last();
 523      primitive.start = indices.size();
 524      primitive.matIndex = (TSDrawPrimitive::Triangles | TSDrawPrimitive::Indexed) |
 525                           addMaterial(meshPrims[iPrim]->getMaterial());
 526
 527      // Get the AppMaterial associated with this primitive
 528      ColladaAppMaterial* appMat = 0;
 529      if (!(primitive.matIndex & TSDrawPrimitive::NoMaterial))
 530         appMat = static_cast<ColladaAppMaterial*>(appMaterials[primitive.matIndex & TSDrawPrimitive::MaterialMask]);
 531
 532      // Force the material to be double-sided if this geometry is double-sided.
 533      if (geomExt->double_sided && appMat && appMat->effectExt)
 534         appMat->effectExt->double_sided = true;
 535
 536      // Pre-allocate triangle indices
 537      primitive.numElements = numTriangles * 3;
 538      indices.setSize(indices.size() + primitive.numElements);
 539      U32* dstIndex = indices.end() - primitive.numElements;
 540
 541      // Determine the offset for each element type in the stream, and also the
 542      // maximum input offset, which will be the number of indices per vertex we
 543      // need to skip.
 544      domInputLocalOffsetRef sortedInputs[MeshStreams::NumStreams];
 545      MeshStreams::classifyInputs(meshPrims[iPrim]->getInputs(), sortedInputs);
 546
 547      S32 offsets[MeshStreams::NumStreams];
 548      for (S32 i = 0; i < MeshStreams::NumStreams; i++)
 549         offsets[i] = sortedInputs[i] ? sortedInputs[i]->getOffset() : -1;
 550
 551      // Loop through indices
 552      const domUint* pSrcData = &(pTriData->get(0));
 553
 554      for (U32 iTri = 0; iTri < numTriangles; iTri++) {
 555
 556         // If the next triangle could cause us to index across a 16-bit
 557         // boundary, split this primitive and clear the tuple map to
 558         // ensure primitives only index verts within a 16-bit range.
 559         if (vertTuples.size() &&
 560            (((vertTuples.size()-1) ^ (vertTuples.size()+2)) & 0x10000))
 561         {
 562            // Pad vertTuples up to the next 16-bit boundary
 563            while (vertTuples.size() & 0xFFFF)
 564               vertTuples.push_back(VertTuple(vertTuples.last()));
 565
 566            // Split the primitive at the current triangle
 567            S32 indicesRemaining = (numTriangles - iTri) * 3;
 568            if (iTri > 0)
 569            {
 570               daeErrorHandler::get()->handleWarning(avar("Splitting primitive "
 571                  "in %s: too many verts for 16-bit indices.", _GetNameOrId(geometry)));
 572
 573               primitives.last().numElements -= indicesRemaining;
 574               primitives.push_back(TSDrawPrimitive(primitives.last()));
 575            }
 576
 577            primitives.last().numElements = indicesRemaining;
 578            primitives.last().start = indices.size() - indicesRemaining;
 579
 580            tupleMap.clear();
 581         }
 582
 583         streams.reset();
 584         streams.readInputs(meshPrims[iPrim]->getInputs());
 585
 586         for (U32 v = 0; v < 3; v++) {
 587            // Collect vert tuples into a single array so we can easily grab
 588            // vertex data later.
 589            VertTuple tuple;
 590            tuple.prim = iPrim;
 591            tuple.vertex = offsets[MeshStreams::Points]  >= 0 ? pSrcData[offsets[MeshStreams::Points]] : -1;
 592            tuple.normal = offsets[MeshStreams::Normals] >= 0 ? pSrcData[offsets[MeshStreams::Normals]] : -1;
 593            tuple.color  = offsets[MeshStreams::Colors]  >= 0 ? pSrcData[offsets[MeshStreams::Colors]] : -1;
 594            tuple.uv     = offsets[MeshStreams::UVs]     >= 0 ? pSrcData[offsets[MeshStreams::UVs]] : -1;
 595            tuple.uv2    = offsets[MeshStreams::UV2s]    >= 0 ? pSrcData[offsets[MeshStreams::UV2s]] : -1;
 596
 597            tuple.dataVertex = tuple.vertex > -1 ? streams.points.getPoint3FValue(tuple.vertex) : Point3F::Max;
 598            tuple.dataNormal = tuple.normal > -1 ? streams.normals.getPoint3FValue(tuple.normal) : Point3F::Max;
 599            tuple.dataColor  = tuple.color > -1  ? streams.colors.getColorIValue(tuple.color) : ColorI(0,0,0);
 600            tuple.dataUV     = tuple.uv > -1     ? streams.uvs.getPoint2FValue(tuple.uv) : Point2F::Max;
 601            tuple.dataUV2    = tuple.uv2 > -1    ? streams.uv2s.getPoint2FValue(tuple.uv2) : Point2F::Max;
 602
 603            VertTupleMap::Iterator itr = tupleMap.find(tuple);
 604            if (itr == tupleMap.end())
 605            {
 606               itr = tupleMap.insert(tuple, vertTuples.size());
 607               vertTuples.push_back(tuple);
 608            }
 609
 610            // Collada uses CCW for front face and Torque uses the opposite, so
 611            // for normal (non-inverted) meshes, the indices are flipped.
 612            if (appNode->invertMeshes)
 613               dstIndex[v] = itr->value;
 614            else
 615               dstIndex[2 - v] = itr->value;
 616
 617            pSrcData += meshPrims[iPrim]->getStride();
 618         }
 619         dstIndex += 3;
 620      }
 621   }
 622
 623   for (S32 iPrim = 0; iPrim < meshPrims.size(); iPrim++)
 624      delete meshPrims[iPrim];
 625}
 626
 627void ColladaAppMesh::getVertexData(const domGeometry* geometry, F32 time, const MatrixF& objOffset,
 628                                   Vector<Point3F>& v_points, 
 629                                   Vector<Point3F>& v_norms, 
 630                                   Vector<ColorI>& v_colors,
 631                                   Vector<Point2F>& v_uvs,
 632                                   Vector<Point2F>& v_uv2s,
 633                                   bool appendValues)
 634{
 635   if (!primitives.size())
 636      return;
 637
 638   MeshStreams streams;
 639   S32 lastPrimitive = -1;
 640   ColladaAppMaterial* appMat = 0;
 641
 642   // Get the supported primitive elements for this geometry
 643   Vector<BasePrimitive*> meshPrims;
 644   const daeElementRefArray& contents = geometry->getMesh()->getContents();
 645   for (S32 iElem = 0; iElem < contents.getCount(); iElem++) {
 646      if (BasePrimitive::isSupportedPrimitive(contents[iElem]))
 647         meshPrims.push_back(BasePrimitive::get(contents[iElem]));
 648   }
 649
 650   // If appending values, pre-allocate the arrays
 651   if (appendValues) {
 652      v_points.setSize(v_points.size() + vertTuples.size());
 653      v_uvs.setSize(v_uvs.size() + vertTuples.size());
 654   }
 655
 656   // Get pointers to arrays
 657   Point3F* points_array = &v_points[v_points.size() - vertTuples.size()];
 658   Point2F* uvs_array = &v_uvs[v_uvs.size() - vertTuples.size()];
 659   Point3F* norms_array = NULL;
 660   ColorI*  colors_array = NULL;
 661   Point2F* uv2s_array = NULL;
 662
 663   for (S32 iVert = 0; iVert < vertTuples.size(); iVert++) {
 664
 665      const VertTuple& tuple = vertTuples[iVert];
 666
 667      // Change primitives?
 668      if (tuple.prim != lastPrimitive) {
 669         if (meshPrims.size() <= tuple.prim) {
 670            daeErrorHandler::get()->handleError(avar("Failed to get vertex data "
 671               "for %s. Primitives do not match base geometry.", geometry->getID()));
 672            break;
 673         }
 674
 675         // Update vertex/normal/UV streams and get the new material index
 676         streams.reset();
 677         streams.readInputs(meshPrims[tuple.prim]->getInputs());
 678         S32 matIndex = addMaterial(meshPrims[tuple.prim]->getMaterial());
 679         if (matIndex != TSDrawPrimitive::NoMaterial)
 680            appMat = static_cast<ColladaAppMaterial*>(appMaterials[matIndex]);
 681         else
 682            appMat = 0;
 683
 684         lastPrimitive = tuple.prim;
 685      }
 686
 687      // If we are NOT appending values, only set the value if it actually exists
 688      // in the mesh data stream.
 689
 690      if (appendValues || ((tuple.vertex >= 0) && (tuple.vertex < streams.points.size()))) {
 691         points_array[iVert] = streams.points.getPoint3FValue(tuple.vertex);
 692
 693         // Flip verts for inverted meshes
 694         if (appNode->invertMeshes)
 695            points_array[iVert].z = -points_array[iVert].z;
 696
 697         objOffset.mulP(points_array[iVert]);
 698      }
 699
 700      if (appendValues || ((tuple.uv >= 0) && (tuple.uv < streams.uvs.size()))) {
 701         uvs_array[iVert] = streams.uvs.getPoint2FValue(tuple.uv);
 702         if (appMat && appMat->effectExt)
 703            appMat->effectExt->applyTextureTransform(uvs_array[iVert], time);
 704         uvs_array[iVert].y = 1.0f - uvs_array[iVert].y;   // Collada texcoords are upside down compared to TGE
 705      }
 706
 707      // The rest is non-required data... if it doesn't exist then don't append it.
 708
 709      if ( (tuple.normal >= 0) && (tuple.normal < streams.normals.size()) ) {
 710         if ( !norms_array && iVert == 0 )
 711         {
 712            v_norms.setSize(v_norms.size() + vertTuples.size());
 713            norms_array = &v_norms[v_norms.size() - vertTuples.size()];
 714         }
 715
 716         if ( norms_array ) {
 717            norms_array[iVert] = streams.normals.getPoint3FValue(tuple.normal);
 718
 719            // Flip normals for inverted meshes
 720            if (appNode->invertMeshes)
 721               norms_array[iVert].z = -norms_array[iVert].z;
 722         }
 723      }
 724
 725      if ( (tuple.color >= 0) && (tuple.color < streams.colors.size())) 
 726      {
 727         if ( !colors_array && iVert == 0 )
 728         {
 729            v_colors.setSize(v_colors.size() + vertTuples.size());
 730            colors_array = &v_colors[v_colors.size() - vertTuples.size()];
 731         }
 732
 733         if ( colors_array )
 734            colors_array[iVert] = streams.colors.getColorIValue(tuple.color);
 735      }
 736
 737      if ( (tuple.uv2 >= 0) && (tuple.uv2 < streams.uv2s.size()) ) 
 738      {
 739         if ( !uv2s_array && iVert == 0 )
 740         {
 741            v_uv2s.setSize(v_uv2s.size() + vertTuples.size());
 742            uv2s_array = &v_uv2s[v_uv2s.size() - vertTuples.size()];
 743         }
 744
 745         if ( uv2s_array )
 746         {
 747            uv2s_array[iVert] = streams.uv2s.getPoint2FValue(tuple.uv2);
 748            if (appMat && appMat->effectExt)
 749               appMat->effectExt->applyTextureTransform(uv2s_array[iVert], time);
 750            uv2s_array[iVert].y = 1.0f - uv2s_array[iVert].y;   // Collada texcoords are upside down compared to TGE
 751         }
 752      }
 753   }
 754
 755   for (S32 iPrim = 0; iPrim < meshPrims.size(); iPrim++)
 756      delete meshPrims[iPrim];
 757}
 758
 759void ColladaAppMesh::getMorphVertexData(const domMorph* morph, F32 time, const MatrixF& objOffset,
 760                                        Vector<Point3F>& v_points, 
 761                                        Vector<Point3F>& v_norms, 
 762                                        Vector<ColorI>& v_colors, 
 763                                        Vector<Point2F>& v_uvs,
 764                                        Vector<Point2F>& v_uv2s)
 765{
 766   // @todo: Could the base geometry (or any target geometry) also be a morph?
 767
 768   // Get the target geometries and weights (could be animated)
 769   Vector<const domGeometry*> targetGeoms;
 770   domListOfFloats targetWeights;
 771
 772   for (S32 iInput = 0; iInput < morph->getTargets()->getInput_array().getCount(); iInput++) {
 773      const domInputLocal* input = morph->getTargets()->getInput_array()[iInput];
 774      const domSource* source = daeSafeCast<domSource>(findInputSource(input));
 775
 776      if (dStrEqual(input->getSemantic(), "MORPH_TARGET")) {
 777         //  Get the morph targets
 778         _SourceReader srcTargets;
 779         srcTargets.initFromSource(source);
 780
 781         for (S32 iTarget = 0; iTarget < srcTargets.size(); iTarget++) {
 782            // Lookup the element and add to the targets list
 783            daeIDRef idref(srcTargets.getStringValue(iTarget));
 784            idref.setContainer(morph->getDocument()->getDomRoot());
 785            targetGeoms.push_back(daeSafeCast<domGeometry>(idref.getElement()));
 786         }
 787      }
 788      else if (dStrEqual(input->getSemantic(), "MORPH_WEIGHT")) {
 789         //  Get the (possibly animated) morph weight
 790         targetWeights = AnimatedFloatList(source->getFloat_array()).getValue(time);
 791      }
 792   }
 793
 794   // Check that we have a weight for each target
 795   if (targetGeoms.size() != targetWeights.getCount())
 796   {
 797      domController* ctrl = daeSafeCast<domController>(const_cast<domMorph*>(morph)->getParent());
 798      Con::warnf("Mismatched morph targets and weights in %s.", _GetNameOrId(ctrl));
 799
 800      // Set unused targets to zero weighting (unused weights are ignored)
 801      while (targetGeoms.size() > targetWeights.getCount())
 802         targetWeights.append(0.0f);
 803   }
 804
 805   // Get the base geometry and vertex data
 806   const domGeometry* baseGeometry = daeSafeCast<domGeometry>(morph->getSource().getElement());
 807   if (!baseGeometry)
 808      return;
 809
 810   getPrimitives(baseGeometry);
 811   getVertexData(baseGeometry, time, objOffset, v_points, v_norms, v_colors, v_uvs, v_uv2s, true);
 812
 813   // Get pointers to the arrays of base geometry data
 814   Point3F* points_array = &v_points[v_points.size() - vertTuples.size()];
 815   Point3F* norms_array = &v_norms[v_norms.size() - vertTuples.size()];
 816   Point2F* uvs_array = &v_uvs[v_uvs.size() - vertTuples.size()];
 817   ColorI* colors_array = v_colors.size() ? &v_colors[v_colors.size() - vertTuples.size()] : 0;
 818   Point2F* uv2s_array = v_uv2s.size() ? &v_uv2s[v_uv2s.size() - vertTuples.size()] : 0;
 819
 820   // Normalize base vertex data?
 821   if (morph->getMethod() == MORPHMETHODTYPE_NORMALIZED) {
 822
 823      F32 weightSum = 0.0f;
 824      for (S32 iWeight = 0; iWeight < targetWeights.getCount(); iWeight++) {
 825         weightSum += targetWeights[iWeight];
 826      }
 827
 828      // Result = Base*(1.0-w1-w2 ... -wN) + w1*Target1 + w2*Target2 ... + wN*TargetN
 829      weightSum = mClampF(1.0f - weightSum, 0.0f, 1.0f);
 830
 831      for (S32 iVert = 0; iVert < vertTuples.size(); iVert++) {
 832         points_array[iVert] *= weightSum;
 833         norms_array[iVert] *= weightSum;
 834         uvs_array[iVert] *= weightSum;
 835      }
 836
 837      if (uv2s_array) {
 838         for (S32 iVert = 0; iVert < vertTuples.size(); iVert++)
 839            uv2s_array[iVert] *= weightSum;
 840      }
 841   }
 842
 843   // Interpolate using the target geometry and weights
 844   for (S32 iTarget = 0; iTarget < targetGeoms.size(); iTarget++) {
 845
 846      // Ignore empty weights
 847      if (targetWeights[iTarget] == 0.0f)
 848         continue;
 849
 850      // Get target geometry data into temporary arrays
 851      Vector<Point3F> targetPoints;
 852      Vector<Point3F> targetNorms;
 853      Vector<Point2F> targetUvs;
 854      Vector<ColorI>  targetColors;
 855      Vector<Point2F> targetUv2s;
 856
 857      // Copy base geometry into target geometry (will be used if target does
 858      // not define normals or uvs)
 859      targetPoints.set(points_array, vertTuples.size());
 860      targetNorms.set(norms_array, vertTuples.size());
 861      targetUvs.set(uvs_array, vertTuples.size());
 862      if (colors_array)
 863         targetColors.set(colors_array, vertTuples.size());
 864      if (uv2s_array)
 865         targetUv2s.set(uv2s_array, vertTuples.size());
 866
 867      getVertexData(targetGeoms[iTarget], time, objOffset, targetPoints, targetNorms, targetColors, targetUvs, targetUv2s, false);
 868
 869      // Combine with base geometry
 870      for (S32 iVert = 0; iVert < vertTuples.size(); iVert++) {
 871         points_array[iVert] += targetPoints[iVert] * targetWeights[iTarget];
 872         norms_array[iVert] += targetNorms[iVert] * targetWeights[iTarget];
 873         uvs_array[iVert] += targetUvs[iVert] * targetWeights[iTarget];
 874      }
 875
 876      if (uv2s_array) {
 877         for (S32 iVert = 0; iVert < vertTuples.size(); iVert++)
 878            uv2s_array[iVert] += targetUv2s[iVert] * targetWeights[iTarget];
 879      }
 880      if (colors_array) {
 881         for (S32 iVert = 0; iVert < vertTuples.size(); iVert++)
 882         {
 883            LinearColorF tCol = colors_array[iVert];
 884            tCol += LinearColorF(targetColors[iVert]) * (F32)targetWeights[iTarget];
 885            colors_array[iVert] = tCol.toColorI();
 886         }
 887      }
 888   }
 889}
 890
 891void ColladaAppMesh::lockMesh(F32 t, const MatrixF& objOffset)
 892{
 893   // Find the geometry element for this mesh. Could be one of 3 things:
 894   // 1) a simple static mesh (Collada <geometry> element)
 895   // 2) a simple morph (some combination of static meshes)
 896   // 3) a skin (skin geometry could also be a morph!)
 897   daeElement* geometry = 0;
 898
 899   if (instanceGeom) {
 900      // Simple, static mesh
 901      geometry = instanceGeom->getUrl().getElement();
 902   }
 903   else if (instanceCtrl) {
 904      const domController* ctrl = daeSafeCast<domController>(instanceCtrl->getUrl().getElement());
 905      if (!ctrl) {
 906         daeErrorHandler::get()->handleWarning(avar("Failed to find <controller> "
 907            "element for %s", getName()));
 908         return;
 909      }
 910      else if (ctrl->getMorph()) {
 911         // Morph controller
 912         geometry = ctrl->getMorph();
 913      }
 914      else {
 915         // Skinned mesh: source geometry could be static geometry or a morph controller.
 916         geometry = ctrl->getSkin()->getSource().getElement();
 917         if (geometry && geometry->getElementType() == COLLADA_TYPE::CONTROLLER)
 918            geometry = daeSafeCast<domController>(geometry)->getMorph();
 919      }
 920   }
 921
 922   if (!geometry) {
 923      daeErrorHandler::get()->handleWarning(avar("Failed to find source geometry "
 924         "for %s", getName()));
 925      return;
 926   }
 927
 928   // Now get the vertex data at the specified time
 929   if (geometry->getElementType() == COLLADA_TYPE::GEOMETRY) {
 930      getPrimitives(daeSafeCast<domGeometry>(geometry));
 931      getVertexData(daeSafeCast<domGeometry>(geometry), t, objOffset, points, normals, colors, uvs, uv2s, true);
 932   }
 933   else if (geometry->getElementType() == COLLADA_TYPE::MORPH) {
 934      getMorphVertexData(daeSafeCast<domMorph>(geometry), t, objOffset, points, normals, colors, uvs, uv2s);
 935   }
 936   else {
 937      daeErrorHandler::get()->handleWarning(avar("Unsupported geometry type "
 938         "'<%s>' for %s", geometry->getElementName(), getName()));
 939   }
 940}
 941
 942void ColladaAppMesh::lookupSkinData()
 943{
 944   // Only lookup skin data once
 945   if (!isSkin() || weight.size())
 946      return;
 947
 948   // Get the skin and vertex weight data
 949   const domSkin* skin = daeSafeCast<domController>(instanceCtrl->getUrl().getElement())->getSkin();
 950   const domSkin::domVertex_weights& weightIndices = *(skin->getVertex_weights());
 951   const domListOfInts& weights_v = weightIndices.getV()->getValue();
 952   const domListOfUInts& weights_vcount = weightIndices.getVcount()->getValue();
 953
 954   MeshStreams streams;
 955   streams.readInputs(skin->getJoints()->getInput_array());
 956   streams.readInputs(weightIndices.getInput_array());
 957
 958   MatrixF invObjOffset(objectOffset);
 959   invObjOffset.inverse();
 960
 961   // Get the bind shape matrix
 962   MatrixF bindShapeMatrix(true);
 963   if (skin->getBind_shape_matrix())
 964      bindShapeMatrix = vecToMatrixF<domMatrix>(skin->getBind_shape_matrix()->getValue());
 965   bindShapeMatrix.mul(invObjOffset);
 966
 967   // Determine the offset into the vindices array for each vertex (since each
 968   // vertex may have multiple [bone, weight] pairs in the array)
 969   Vector<U32> vindicesOffset;
 970   const domInt* vindices = (domInt*)weights_v.getRaw(0);
 971   for (S32 iWeight = 0; iWeight < weights_vcount.getCount(); iWeight++) {
 972      // Store the offset into the vindices array for this vertex
 973      vindicesOffset.push_back(vindices - (domInt*)weights_v.getRaw(0));
 974      vindices += (weights_vcount[iWeight]*2); // 2 indices [bone, weight] per vert
 975   }
 976
 977   // Set vertex weights
 978   bool tooManyWeightsWarning = false;
 979   for (S32 iVert = 0; iVert < vertsPerFrame; iVert++) {
 980      const domUint* vcount = (domUint*)weights_vcount.getRaw(0);
 981      vindices = (domInt*)weights_v.getRaw(0);
 982      vindices += vindicesOffset[vertTuples[iVert].vertex];
 983
 984      S32 nonZeroWeightCount = 0;
 985
 986      for (S32 iWeight = 0; iWeight < vcount[vertTuples[iVert].vertex]; iWeight++) {
 987
 988         S32 bIndex = vindices[iWeight*2];
 989         F32 bWeight = streams.weights.getFloatValue( vindices[iWeight*2 + 1] );
 990
 991         // Ignore empty weights
 992         if ( bIndex < 0 || bWeight == 0 )
 993            continue;
 994
 995         // Limit the number of weights per bone (keep the N largest influences)
 996         if ( nonZeroWeightCount >= TSSkinMesh::BatchData::maxBonePerVert )
 997         {
 998            if (vcount[vertTuples[iVert].vertex] > TSSkinMesh::BatchData::maxBonePerVert)
 999            {
1000               if (!tooManyWeightsWarning)
1001               {
1002                  tooManyWeightsWarning = true;
1003                  daeErrorHandler::get()->handleWarning(avar("At least one vertex has "
1004                     "too many bone weights. Limiting to the largest %d influences.",
1005                     TSSkinMesh::BatchData::maxBonePerVert));
1006               }
1007            }
1008
1009            // Too many weights => find and replace the smallest one
1010            S32 minIndex = weight.size() - TSSkinMesh::BatchData::maxBonePerVert;
1011            F32 minWeight = weight[minIndex];
1012            for (S32 i = minIndex + 1; i < weight.size(); i++)
1013            {
1014               if (weight[i] < minWeight)
1015               {
1016                  minWeight = weight[i];
1017                  minIndex = i;
1018               }
1019            }
1020
1021            boneIndex[minIndex] = bIndex;
1022            weight[minIndex] = bWeight;
1023         }
1024         else
1025         {
1026            vertexIndex.push_back( iVert );
1027            boneIndex.push_back( bIndex );
1028            weight.push_back( bWeight );
1029            nonZeroWeightCount++;
1030         }
1031      }
1032   }
1033
1034   // Normalize vertex weights (force weights for each vert to sum to 1)
1035   S32 iWeight = 0;
1036   while (iWeight < weight.size()) {
1037      // Find the last weight with the same vertex number, and sum all weights for
1038      // that vertex
1039      F32 invTotalWeight = 0;
1040      S32 iLast;
1041      for (iLast = iWeight; iLast < weight.size(); iLast++) {
1042         if (vertexIndex[iLast] != vertexIndex[iWeight])
1043            break;
1044         invTotalWeight += weight[iLast];
1045      }
1046
1047      // Then normalize the vertex weights
1048      invTotalWeight = 1.0f / invTotalWeight;
1049      for (; iWeight < iLast; iWeight++)
1050         weight[iWeight] *= invTotalWeight;
1051   }
1052
1053   // Add dummy AppNodes to allow Collada joints to be mapped to 3space nodes
1054   bones.setSize(streams.joints.size());
1055   initialTransforms.setSize(streams.joints.size());
1056   for (S32 iJoint = 0; iJoint < streams.joints.size(); iJoint++)
1057   {
1058      const char* jointName = streams.joints.getStringValue(iJoint);
1059
1060      // Lookup the joint element
1061      const domNode* joint = 0;
1062      if (instanceCtrl->getSkeleton_array().getCount()) {
1063         // Search for the node using the <skeleton> as the base element
1064         for (S32 iSkel = 0; iSkel < instanceCtrl->getSkeleton_array().getCount(); iSkel++) {
1065            xsAnyURI skeleton = instanceCtrl->getSkeleton_array()[iSkel]->getValue();
1066            daeSIDResolver resolver(skeleton.getElement(), jointName);
1067            joint = daeSafeCast<domNode>(resolver.getElement());
1068            if (joint)
1069               break;
1070         }
1071      }
1072      else {
1073         // Search for the node from the root level
1074         daeSIDResolver resolver(skin->getDocument()->getDomRoot(), jointName);
1075         joint = daeSafeCast<domNode>(resolver.getElement());
1076      }
1077
1078      if (!joint) {
1079         daeErrorHandler::get()->handleWarning(avar("Failed to find bone '%s', "
1080            "defaulting to instance_controller parent node '%s'", jointName, appNode->getName()));
1081         joint = appNode->getDomNode();
1082      }
1083      bones[iJoint] = new ColladaAppNode(joint);
1084
1085      initialTransforms[iJoint] = objectOffset;
1086
1087      // Bone scaling is generally ignored during import, since 3space only
1088      // stores default node transform and rotation. Compensate for this by
1089      // removing the scaling from the inverse bind transform as well
1090      MatrixF invBind = streams.invBindMatrices.getMatrixFValue(iJoint);
1091      if (!ColladaUtils::getOptions().ignoreNodeScale)
1092      {
1093         Point3F invScale = invBind.getScale();
1094         invScale.x = invScale.x ? (1.0f / invScale.x) : 0;
1095         invScale.y = invScale.y ? (1.0f / invScale.y) : 0;
1096         invScale.z = invScale.z ? (1.0f / invScale.z) : 0;
1097         initialTransforms[iJoint].scale(invScale);
1098      }
1099
1100      // Inverted node coordinate spaces (negative scale factor) are corrected
1101      // in ColladaAppNode::getNodeTransform, so need to apply the same operation
1102      // here to match
1103      if (m_matF_determinant(invBind) < 0.0f)
1104         initialTransforms[iJoint].scale(Point3F(1, 1, -1));
1105
1106      initialTransforms[iJoint].mul(invBind);
1107      initialTransforms[iJoint].mul(bindShapeMatrix);
1108   }
1109}
1110