text: //-----------------------------------------------------------------------------
  // Copyright (c) 2012 GarageGames, LLC
  //
  // Permission is hereby granted, free of charge, to any person obtaining a copy
  // of this software and associated documentation files (the "Software"), to
  // deal in the Software without restriction, including without limitation the
  // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  // sell copies of the Software, and to permit persons to whom the Software is
  // furnished to do so, subject to the following conditions:
  //
  // The above copyright notice and this permission notice shall be included in
  // all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  // IN THE SOFTWARE.
  //-----------------------------------------------------------------------------
  
  #ifndef _COLLADA_UTILS_H_
  #define _COLLADA_UTILS_H_
  
  #ifdef _MSC_VER
  #pragma warning(disable : 4786)  // disable warning about long debug symbol names
  #pragma warning(disable : 4355)  // disable "'this' : used in base member initializer list" warnings
  #endif
  
  #ifndef _MMATRIX_H_
  #include "math/mMatrix.h"
  #endif
  #ifndef _MQUAT_H_
  #include "math/mQuat.h"
  #endif
  #ifndef _TVECTOR_H_
  #include "core/util/tVector.h"
  #endif
  #ifndef _TSSHAPE_LOADER_H_
  #include "ts/loader/tsShapeLoader.h"
  #endif
  #ifndef _OPTIMIZEDPOLYLIST_H_
  #include "collision/optimizedPolyList.h"
  #endif
  #ifndef TINYXML_INCLUDED
  #include "tinyxml.h"
  #endif
  #ifndef _CONSOLE_H_
  #include "console/console.h"
  #endif
  
  #ifndef _TSSHAPEINSTANCE_H_
  #include "ts/tsShapeInstance.h"
  #endif
  
  #include "platform/tmm_off.h"
  
  #include "dae.h"
  #include "dae/daeErrorHandler.h"
  #include "dae/domAny.h"
  #include "dom/domProfile_COMMON.h"
  #include "dom/domMaterial.h"
  #include "dom/domGeometry.h"
  #include "dom/domMorph.h"
  #include "dom/domNode.h"
  #include "dom/domCOLLADA.h"
  
  #include "platform/tmm_on.h"
  #include "core/strings/findMatch.h"
  
  namespace ColladaUtils
  {
     struct ImportOptions
     {
        enum eLodType
        {
           DetectDTS = 0,
           SingleSize,
           TrailingNumber,
           NumLodTypes
        };
  
        enum eAnimTimingType
        {
           FrameCount = 0,
           Seconds = 1,
           Milliseconds = 1000
        };
  
        domUpAxisType  upAxis;           // Override for the collada <up_axis> element
        F32            unit;             // Override for the collada <unit> element
        eLodType       lodType;          // LOD type option
        S32            singleDetailSize; // Detail size for all meshes in the model
        String         matNamePrefix;    // Prefix to apply to collada material names
        String         alwaysImport;     // List of node names (with wildcards) to import, even if in the neverImport list
        String         neverImport;      // List of node names (with wildcards) to ignore on loading
        String         alwaysImportMesh; // List of mesh names (with wildcards) to import, even if in the neverImportMesh list
        String         neverImportMesh;  // List of mesh names (with wildcards) to ignore on loading
        String         neverImportMat;   // List of material names (with wildcards) to ignore on loading
        bool           ignoreNodeScale;  // Ignore <scale> elements in <node>s
        bool           adjustCenter;     // Translate model so origin is at the center
        bool           adjustFloor;      // Translate model so origin is at the bottom
        bool           forceUpdateMaterials;   // Force update of materials.cs
        bool           useDiffuseNames;  // Use diffuse texture as the material name
  
        // Assimp specific preprocess import options
        bool           convertLeftHanded;   // Convert to left handed coordinate system.
        bool           calcTangentSpace;    // Calculate tangents and bitangents, if possible.
        bool           genUVCoords;         // Convert spherical, cylindrical, box and planar mapping to proper UVs.
        bool           transformUVCoords;   // Preprocess UV transformations (scaling, translation ...)
        bool           flipUVCoords;        // This step flips all UV coordinates along the y-axis and adjusts material settings
                                            // and bitangents accordingly.\nAssimp uses TL(0,0):BR(1,1). T3D uses TL(0,1):BR(1,0).
        bool           findInstances;       // Search for instanced meshes and remove them by references to one master.
        bool           limitBoneWeights;    // Limit bone weights to 4 per vertex.
        bool           joinIdenticalVerts;  // Identifies and joins identical vertex data sets within all imported meshes.
        bool           reverseWindingOrder; // This step adjusts the output face winding order to be clockwise. The default face winding order is counter clockwise.
        bool           invertNormals;       // Reverse the normal vector direction for all normals.
        bool           removeRedundantMats; // Removes redundant materials.
        eAnimTimingType animTiming;         // How to import timing data as frames, seconds or milliseconds
        S32            animFPS;             // FPS value to use if timing is set in frames and the animations does not have an fps set
        F32            formatScaleFactor;   // Scale factor applied to convert the shape format default unit to meters
  
        ImportOptions()
        {
           reset();
        }
  
        void reset()
        {
           upAxis = UPAXISTYPE_COUNT;
           unit = -1.0f;
           lodType = TrailingNumber;
           singleDetailSize = 2;
           matNamePrefix = "";
           alwaysImport = "";
           neverImport = String(Con::getVariable("$TSShapeConstructor::neverImport"));
           alwaysImportMesh = "";
           neverImportMesh = String(Con::getVariable("$TSShapeConstructor::neverImportMesh"));
           neverImportMat = String(Con::getVariable("$TSShapeConstructor::neverImportMat"));
           ignoreNodeScale = false;
           adjustCenter = false;
           adjustFloor = false;
           forceUpdateMaterials = false;
           useDiffuseNames = false;
  
           convertLeftHanded = false;
           calcTangentSpace = false;
           genUVCoords = false;
           transformUVCoords = false;
           flipUVCoords = true;
           findInstances = false;
           limitBoneWeights = false;
           joinIdenticalVerts = true;
           reverseWindingOrder = true;
           invertNormals = false;
           removeRedundantMats = true;
           animTiming = Seconds;
           animFPS = 30;
           formatScaleFactor = 1.0f;
        }
     };
  
     ImportOptions& getOptions();
  
     struct ExportData
     {
        struct detailLevel
        {
           OptimizedPolyList mesh;
           S32 size;
           Map<int, int> materialRefList;
        };
  
        struct meshLODData
        {
           Vector<detailLevel> meshDetailLevels;
           TSShapeInstance* shapeInst;
           MatrixF meshTransform;
           SceneObject* originatingObject;
  
           Point3F scale;
  
           S32 hasDetailLevel(S32 size)
           {
              for (U32 i = 0; i < meshDetailLevels.size(); ++i)
              {
                 U32 mdlSize = meshDetailLevels[i].size;
  
                 if (mdlSize == size)
                    return i;
              }
  
              return -1;
           }
  
           meshLODData() : shapeInst(nullptr), meshTransform(true), originatingObject(nullptr), scale(0)
           {}
        };
  
        struct colMesh
        {
           OptimizedPolyList mesh;
           String colMeshName;
        };
  
        Vector<detailLevel> detailLevels;
        Vector<meshLODData> meshData;
        Vector<colMesh> colMeshes;
        Vector<BaseMatInstance*> materials;
  
        void processData();
  
        S32 hasDetailLevel(U32 dl)
        {
           for (U32 i = 0; i < detailLevels.size(); i++)
           {
              if (detailLevels[i].size == dl)
                 return i;
           }
  
           return -1;
        }
  
        S32 hasMaterialInstance(BaseMatInstance* matInst)
        {
           for (U32 i = 0; i < materials.size(); i++)
           {
              if (materials[i] == matInst)
                 return i;
           }
  
           return -1;
        }
  
        S32 numberOfDetailLevels()
        {
           Vector<S32> detailLevelIdxs;
  
           for (U32 i = 0; i < meshData.size(); ++i)
           {
              for (U32 d = 0; d < meshData[i].meshDetailLevels.size(); ++d)
              {
                 detailLevelIdxs.push_back_unique(meshData[i].meshDetailLevels[d].size);
              }
           }
  
           return detailLevelIdxs.size();
        }
  
        static S32 _Sort(const S32 *p1, const S32 *p2)
        {
           S32 e1 = (*p1);
           S32 e2 = (*p2);
  
           if (e1 > e2)
              return 1;
           else if (e1 < e2)
              return -1;
  
           return 0;
        }
  
        S32 getDetailLevelSize(U32 detailIdx)
        {
           Vector<S32> detailLevelIdxs;
  
           for (U32 i = 0; i < meshData.size(); ++i)
           {
              for (U32 d = 0; d < meshData[i].meshDetailLevels.size(); ++d)
              {
                 S32 mdlSize = meshData[i].meshDetailLevels[d].size;
                 detailLevelIdxs.push_back_unique(mdlSize);
              }
           }
  
           if (detailIdx >= detailLevelIdxs.size())
              return -1;
  
           detailLevelIdxs.sort(&_Sort);
  
           return detailLevelIdxs[detailIdx];
        }
     };
  
     void convertTransform(MatrixF& m);
  
     void collapsePath(std::string& path);
  
     // Apply the set of Collada conditioners (suited for loading Collada models into Torque)
     void applyConditioners(domCOLLADA* root);
  
     const domProfile_COMMON* findEffectCommonProfile(const domEffect* effect);
     const domCommon_color_or_texture_type_complexType* findEffectDiffuse(const domEffect* effect);
     const domCommon_color_or_texture_type_complexType* findEffectSpecular(const domEffect* effect);
     const domFx_sampler2D_common_complexType* getTextureSampler(const domEffect* effect, const domCommon_color_or_texture_type_complexType* texture);
     String getSamplerImagePath(const domEffect* effect, const domFx_sampler2D_common_complexType* sampler2D);
     String resolveImagePath(const domImage* image);
  
     // Collada export helper functions
     Torque::Path findTexture(const Torque::Path& diffuseMap);
     void exportColladaHeader(TiXmlElement* rootNode);
     void exportColladaMaterials(TiXmlElement* rootNode, const OptimizedPolyList& mesh, Vector<String>& matNames, const Torque::Path& colladaFile);
     void exportColladaTriangles(TiXmlElement* meshNode, const OptimizedPolyList& mesh, const String& meshName, const Vector<String>& matNames);
     void exportColladaMesh(TiXmlElement* rootNode, const OptimizedPolyList& mesh, const String& meshName, const Vector<String>& matNames);
     void exportColladaScene(TiXmlElement* rootNode, const String& meshName, const Vector<String>& matNames);
  
     void exportColladaMaterials(TiXmlElement* rootNode, const ExportData& exportData, const Torque::Path& colladaFile);
     void exportColladaMesh(TiXmlElement* rootNode, const ExportData& exportData, const String& meshName);
     void exportColladaCollisionTriangles(TiXmlElement* meshNode, const ExportData& exportData, const U32 collisionIdx);
     void exportColladaTriangles(TiXmlElement* meshNode, const ExportData& exportData, const U32 detailLevel, const String& meshName);
     void exportColladaScene(TiXmlElement* rootNode, const ExportData& exportData, const String& meshName);
  
     // Export an OptimizedPolyList to a simple Collada file
     void exportToCollada(const Torque::Path& colladaFile, const OptimizedPolyList& mesh, const String& meshName = String::EmptyString);
     void exportToCollada(const Torque::Path& colladaFile, const ExportData& exportData);
  };
  
  //-----------------------------------------------------------------------------
  // Helper Classes
  //
  // The Collada DOM uses a different class for each XML element, and there is very
  // little class inheritance, even though many elements have the same attributes
  // and children. This makes the DOM a bit ugly to work with, and the following
  // templates attempt to make this situation a bit nicer by providing a common way
  // to access common elements, while retaining the strong typing of the DOM classes.
  //-----------------------------------------------------------------------------
  
  /// Convert from the Collada transform types to a Torque MatrixF
  template<class T> inline MatrixF vecToMatrixF(const domListOfFloats& vec) { return MatrixF(true); }
  
  /// Collada <translate>: [x_translate, y_translate, z_translate]
  template<> inline MatrixF vecToMatrixF<domTranslate>(const domListOfFloats& vec)
  {
     MatrixF mat(true);
     mat.setPosition(Point3F(vec[0], vec[1], vec[2]));
     return mat;
  }
  
  /// Collada <scale>: [x_scale, y_scale, z_scale]
  template<> inline MatrixF vecToMatrixF<domScale>(const domListOfFloats& vec)
  {
     MatrixF mat(true);
     mat.scale(Point3F(vec[0], vec[1], vec[2]));
     return mat;
  }
  
  /// Collada <rotate>: [rotation_axis, angle_in_degrees]
  template<> inline MatrixF vecToMatrixF<domRotate>(const domListOfFloats& vec)
  {
     AngAxisF aaxis(Point3F(vec[0], vec[1], vec[2]), -(vec[3] * M_PI) / 180.0f);
     MatrixF mat(true);
     aaxis.setMatrix(&mat);
     return mat;
  }
  
  /// Collada <matrix>: same form as TGE (woohoo!)
  template<> inline MatrixF vecToMatrixF<domMatrix>(const domListOfFloats& vec)
  {
     MatrixF mat;
     for (S32 i = 0; i < 16; i++)
        mat[i] = vec[i];
     return mat;
  }
  
  /// Collada <skew>: [angle_in_degrees, rotation_axis, translation_axis]
  /// skew transform code adapted from GMANMatrix4 implementation
  template<> inline MatrixF vecToMatrixF<domSkew>(const domListOfFloats& vec)
  {
     F32 angle = -(vec[0] * M_PI) / 180.0f;
     Point3F rotAxis(vec[1], vec[2], vec[3]);
     Point3F transAxis(vec[4], vec[5], vec[6]);
  
     transAxis.normalize();
  
     Point3F a1 = transAxis * mDot(rotAxis, transAxis);
     Point3F a2 = rotAxis - a1;
     a2.normalize();
  
     F32 an1 = mDot(rotAxis, a2);
     F32 an2 = mDot(rotAxis, transAxis);
  
     F32 rx = an1 * mCos(angle) - an2 * mSin(angle);
     F32 ry = an1 * mSin(angle) + an2 * mCos(angle);
  
     // Check for rotation parallel to translation
     F32 alpha = (an1 == 0) ? 0 : (ry/rx - an2/an1);
  
     MatrixF mat(true);
     mat(0,0) = a2.x * transAxis.x * alpha + 1.0;
     mat(1,0) = a2.y * transAxis.x * alpha;
     mat(2,0) = a2.z * transAxis.x * alpha;
  
     mat(0,1) = a2.x * transAxis.y * alpha;
     mat(1,1) = a2.y * transAxis.y * alpha + 1.0;
     mat(2,1) = a2.z * transAxis.y * alpha;
  
     mat(0,2) = a2.x * transAxis.z * alpha;
     mat(1,2) = a2.y * transAxis.z * alpha;
     mat(2,2) = a2.z * transAxis.z * alpha + 1.0;
     return mat;
  }
  
  /// Collada <lookat>: [eye, target, up]
  template<> inline MatrixF vecToMatrixF<domLookat>(const domListOfFloats& vec)
  {
     Point3F eye(vec[0], vec[1], vec[2]);
     Point3F target(vec[3], vec[4], vec[5]);
     Point3F up(vec[6], vec[7], vec[8]);
  
     Point3F fwd = target - eye;
     fwd.normalizeSafe();
  
     Point3F right = mCross(fwd, up);
     right.normalizeSafe();
  
     up = mCross(right, fwd);
     up.normalizeSafe();
  
     MatrixF mat(true);
     mat.setColumn(0, right);
     mat.setColumn(1, fwd);
     mat.setColumn(2, up);
     mat.setColumn(3, eye);
     return mat;
  }
  
  //-----------------------------------------------------------------------------
  
  /// Try to get a name for the element using the following attributes (in order):
  /// name, sid, id, "null"
  template<class T> inline const char* _GetNameOrId(const T* element)
  {
     return element ? (element->getName() ? element->getName() : (element->getId() ? element->getId() : "null")) : "null";
  }
  
  template<> inline const char* _GetNameOrId(const domInstance_geometry* element)
  {
     return element ? (element->getName() ? element->getName() : (element->getSid() ? element->getSid() : "null")) : "null";
  }
  
  template<> inline const char* _GetNameOrId(const domInstance_controller* element)
  {
     return element ? (element->getName() ? element->getName() : (element->getSid() ? element->getSid() : "null")) : "null";
  }
  
  //-----------------------------------------------------------------------------
  // Collada <source>s are extremely flexible, and thus difficult to access in a nice
  // way. This class attempts to provide a clean interface to convert Collada source
  // data to the appropriate Torque data structure without losing any of the flexibility
  // of the underlying Collada DOM.
  //
  // Some of the conversions we need to handle are:
  // - daeString to const char*
  // - daeIDRef to const char*
  // - double to F32
  // - double to Point2F
  // - double to Point3F
  // - double to MatrixF
  //
  // The _SourceReader object is initialized with a list of parameter names that it
  // tries to match to <param> elements in the source accessor to figure out how to
  // pull values out of the 1D source array. Note that no type checking of any kind
  // is done until we actually try to extract values from the source.
  class _SourceReader
  {
     const domSource* source;      // the wrapped Collada source
     const domAccessor* accessor;  // shortcut to the source accessor
     Vector<U32> offsets;          // offset of each of the desired values to pull from the source array
  
  public:
     _SourceReader() : source(0), accessor(0) {}
  
     void reset()
     {
        source = 0;
        accessor = 0;
        offsets.clear();
     }
  
     //------------------------------------------------------
     // Initialize the _SourceReader object
     bool initFromSource(const domSource* src, const char* paramNames[] = 0)
     {
        source = src;
        accessor = source->getTechnique_common()->getAccessor();
        offsets.clear();
  
        // The source array has groups of values in a 1D stream => need to map the
        // input param names to source params to determine the offset within the
        // group for each desired value
        U32 paramCount = 0;
        while (paramNames && paramNames[paramCount][0]) {
           // lookup the index of the source param that matches the input param
           offsets.push_back(paramCount);
           for (U32 iParam = 0; iParam < accessor->getParam_array().getCount(); iParam++) {
              if (accessor->getParam_array()[iParam]->getName() &&
                 dStrEqual(accessor->getParam_array()[iParam]->getName(), paramNames[paramCount])) {
                 offsets.last() = iParam;
                 break;
              }
           }
           paramCount++;
        }
  
        // If no input params were specified, just map the source params directly
        if (!offsets.size()) {
           for (S32 iParam = 0; iParam < accessor->getParam_array().getCount(); iParam++)
              offsets.push_back(iParam);
        }
  
        return true;
     }
  
     //------------------------------------------------------
     // Shortcut to the size of the array (should be the number of destination objects)
     S32 size() const { return accessor ? accessor->getCount() : 0; }
  
     // Get the number of elements per group in the source
     S32 stride() const { return accessor ? accessor->getStride() : 0; }
  
     //------------------------------------------------------
     // Get a pointer to the start of a group of values (index advances by stride)
     //template<class T> T getArrayData(S32 index) const { return 0; }
  
     const double* getStringArrayData(S32 index) const
     {
        if ((index >= 0) && (index < size())) {
           if (source->getFloat_array())
              return &source->getFloat_array()->getValue()[index*stride()];
        }
        return 0;
     }
  
     //------------------------------------------------------
     // Read a single value from the source array
     //template<class T> T getValue(S32 index) const { return T; }
  
     const char* getStringValue(S32 index) const
     {
        if ((index >= 0) && (index < size())) {
           // could be plain strings or IDREFs
           if (source->getName_array())
              return source->getName_array()->getValue()[index*stride()];
           else if (source->getIDREF_array())
              return source->getIDREF_array()->getValue()[index*stride()].getID();
        }
        return "";
     }
  
     F32 getFloatValue(S32 index) const
     {
        F32 value(0);
        if (const double* data = getStringArrayData(index))
           return data[offsets[0]];
        return value;
     }
  
     Point2F getPoint2FValue(S32 index) const
     {
        Point2F value(0, 0);
        if (const double* data = getStringArrayData(index))
           value.set(data[offsets[0]], data[offsets[1]]);
        return value;
     }
  
     Point3F getPoint3FValue(S32 index) const
     {
        Point3F value(1, 0, 0);
        if (const double* data = getStringArrayData(index))
           value.set(data[offsets[0]], data[offsets[1]], data[offsets[2]]);
        return value;
     }
  
     ColorI getColorIValue(S32 index) const
     {
        ColorI value(255, 255, 255, 255);
        if (const double* data = getStringArrayData(index))
        {
           value.red = data[offsets[0]] * 255.0;
           value.green = data[offsets[1]] * 255.0;
           value.blue = data[offsets[2]] * 255.0;
           if ( stride() == 4 )
              value.alpha = data[offsets[3]] * 255.0;
        }
        return value;
     }
  
     MatrixF getMatrixFValue(S32 index) const
     {
        MatrixF value(true);
        if (const double* data = getStringArrayData(index)) {
           for (S32 i = 0; i < 16; i++)
              value[i] = data[i];
        }
        return value;
     }
  };
  
  //-----------------------------------------------------------------------------
  // Collada geometric primitives: Use the BasePrimitive class to access the
  // different primitive types in a nice way.
  class BasePrimitive
  {
  public:
     virtual ~BasePrimitive() { }
  
     /// Return true if the element is a geometric primitive type
     static bool isPrimitive(const daeElement* element)
     {
        switch (element->getElementType()) {
           case COLLADA_TYPE::TRIANGLES:          case COLLADA_TYPE::POLYLIST:
           case COLLADA_TYPE::POLYGONS:           case COLLADA_TYPE::TRIFANS:
           case COLLADA_TYPE::TRISTRIPS:          case COLLADA_TYPE::CAPSULE:
           case COLLADA_TYPE::CYLINDER:           case COLLADA_TYPE::LINES:
           case COLLADA_TYPE::LINESTRIPS:         case COLLADA_TYPE::PLANE:
           case COLLADA_TYPE::SPLINE:             case COLLADA_TYPE::SPHERE:
           case COLLADA_TYPE::TAPERED_CAPSULE:    case COLLADA_TYPE::TAPERED_CYLINDER:
              return true;
        }
        return false;
     }
  
     /// Return true if the element is a supported primitive type
     static bool isSupportedPrimitive(const daeElement* element)
     {
        switch (element->getElementType()) {
           case COLLADA_TYPE::TRIANGLES:
           case COLLADA_TYPE::TRISTRIPS:
           case COLLADA_TYPE::TRIFANS:
           case COLLADA_TYPE::POLYLIST:
           case COLLADA_TYPE::POLYGONS:
              return true;
        }
        return false;
     }
  
     /// Construct a child class based on the type of Collada element
     static BasePrimitive* get(const daeElement* element);
  
     /// Methods to be implemented for each supported Collada geometric element
     virtual const char* getElementName() = 0;
     virtual const char* getMaterial() = 0;
     virtual const domInputLocalOffset_Array& getInputs() = 0;
  
     virtual S32 getStride() const = 0;
     virtual const domListOfUInts *getTriangleData() = 0;
  };
  
  /// Template child class for supported Collada primitive elements
  template<class T> class ColladaPrimitive : public BasePrimitive
  {
     T* primitive;
     domListOfUInts *pTriangleData;
     S32 stride;
  public:
     ColladaPrimitive(const daeElement* e) : pTriangleData(0)
     {
        // Cast to geometric primitive element
        primitive = daeSafeCast<T>(const_cast<daeElement*>(e));
  
        // Determine stride
        stride = 0;
        for (S32 iInput = 0; iInput < getInputs().getCount(); iInput++) {
           if (getInputs()[iInput]->getOffset() >= stride)
              stride = getInputs()[iInput]->getOffset() + 1;
        }
     }
     ~ColladaPrimitive()
     {
        delete pTriangleData;
     }
  
     /// Most primitives can use these common implementations
     const char* getElementName() { return primitive->getElementName(); }
     const char* getMaterial() { return (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImportMat, primitive->getMaterial(), false)) ? NULL : primitive->getMaterial(); }
     const domInputLocalOffset_Array& getInputs() { return primitive->getInput_array(); }
     S32 getStride() const { return stride; }
  
     /// Each supported primitive needs to implement this method (and convert
     /// to triangles if required)
     const domListOfUInts *getTriangleData() { return NULL; }
  };
  
  //-----------------------------------------------------------------------------
  // <triangles>
  template<> inline const domListOfUInts *ColladaPrimitive<domTriangles>::getTriangleData()
  {
     // Return the <p> integer list directly
     return (primitive->getP() ? &(primitive->getP()->getValue()) : NULL);
  }
  
  //-----------------------------------------------------------------------------
  // <tristrips>
  template<> inline const domListOfUInts *ColladaPrimitive<domTristrips>::getTriangleData()
  {
     if (!pTriangleData)
     {
        // Convert strips to triangles
        pTriangleData = new domListOfUInts();
  
        for (S32 iStrip = 0; iStrip < primitive->getCount(); iStrip++) {
  
           domP* P = primitive->getP_array()[iStrip];
  
           // Ignore invalid P arrays
           if (!P || !P->getValue().getCount())
              continue;
  
           domUint* pSrcData = &(P->getValue()[0]);
           size_t numTriangles = (P->getValue().getCount() / stride) - 2;
  
           // Convert the strip back to a triangle list
           domUint* v0 = pSrcData;
           for (S32 iTri = 0; iTri < numTriangles; iTri++, v0 += stride) {
              if (iTri & 0x1)
              {
                 // CW triangle
                 pTriangleData->appendArray(stride, v0);
                 pTriangleData->appendArray(stride, v0 + 2*stride);
                 pTriangleData->appendArray(stride, v0 + stride);
              }
              else
              {
                 // CCW triangle
                 pTriangleData->appendArray(stride*3, v0);
              }
           }
        }
     }
     return pTriangleData;
  }
  
  //-----------------------------------------------------------------------------
  // <trifans>
  template<> inline const domListOfUInts *ColladaPrimitive<domTrifans>::getTriangleData()
  {
     if (!pTriangleData)
     {
        // Convert strips to triangles
        pTriangleData = new domListOfUInts();
  
        for (S32 iStrip = 0; iStrip < primitive->getCount(); iStrip++) {
  
           domP* P = primitive->getP_array()[iStrip];
  
           // Ignore invalid P arrays
           if (!P || !P->getValue().getCount())
              continue;
  
           domUint* pSrcData = &(P->getValue()[0]);
           size_t numTriangles = (P->getValue().getCount() / stride) - 2;
  
           // Convert the fan back to a triangle list
           domUint* v0 = pSrcData + stride;
           for (S32 iTri = 0; iTri < numTriangles; iTri++, v0 += stride) {
              pTriangleData->appendArray(stride, pSrcData);   // shared vertex
              pTriangleData->appendArray(stride, v0);         // previous vertex
              pTriangleData->appendArray(stride, v0+stride);  // current vertex
           }
        }
     }
     return pTriangleData;
  }
  
  //-----------------------------------------------------------------------------
  // <polygons>
  template<> inline const domListOfUInts *ColladaPrimitive<domPolygons>::getTriangleData()
  {
     if (!pTriangleData)
     {
        // Convert polygons to triangles
        pTriangleData = new domListOfUInts();
  
        for (S32 iPoly = 0; iPoly < primitive->getCount(); iPoly++) {
  
           domP* P = primitive->getP_array()[iPoly];
  
           // Ignore invalid P arrays
           if (!P || !P->getValue().getCount())
              continue;
  
           domUint* pSrcData = &(P->getValue()[0]);
           size_t numPoints = P->getValue().getCount() / stride;
  
           // Use a simple tri-fan (centered at the first point) method of
           // converting the polygon to triangles.
           domUint* v0 = pSrcData;
           pSrcData += stride;
           for (S32 iTri = 0; iTri < numPoints-2; iTri++) {
              pTriangleData->appendArray(stride, v0);
              pTriangleData->appendArray(stride*2, pSrcData);
              pSrcData += stride;
           }
        }
     }
     return pTriangleData;
  }
  
  //-----------------------------------------------------------------------------
  // <polylist>
  template<> inline const domListOfUInts *ColladaPrimitive<domPolylist>::getTriangleData()
  {
     if (!pTriangleData)
     {
        // Convert polygons to triangles
        pTriangleData = new domListOfUInts();
  
        // Check that the P element has the right number of values (this
        // has been seen with certain models exported using COLLADAMax)
        const domListOfUInts& vcount = primitive->getVcount()->getValue();
  
        U32 expectedCount = 0;
        for (S32 iPoly = 0; iPoly < vcount.getCount(); iPoly++)
           expectedCount += vcount[iPoly];
        expectedCount *= stride;
  
        if (!primitive->getP() || !primitive->getP()->getValue().getCount() ||
           (primitive->getP()->getValue().getCount() != expectedCount) )
        {
           Con::warnf("<polylist> element found with invalid <p> array. This primitive will be ignored.");
           return pTriangleData;
        }
  
        domUint* pSrcData = &(primitive->getP()->getValue()[0]);
        for (S32 iPoly = 0; iPoly < vcount.getCount(); iPoly++) {
  
           // Use a simple tri-fan (centered at the first point) method of
           // converting the polygon to triangles.
           domUint* v0 = pSrcData;
           pSrcData += stride;
           for (S32 iTri = 0; iTri < vcount[iPoly]-2; iTri++) {
              pTriangleData->appendArray(stride, v0);
              pTriangleData->appendArray(stride*2, pSrcData);
              pSrcData += stride;
           }
           pSrcData += stride;
        }
     }
     return pTriangleData;
  }
  
  //-----------------------------------------------------------------------------
  
  /// Convert a custom parameter string to a particular type
  template<typename T> inline T convert(const char* value) { return value; }
  template<> inline bool convert(const char* value) { return dAtob(value); }
  template<> inline S32 convert(const char* value) { return dAtoi(value); }
  template<> inline F64 convert(const char* value) { return dAtof(value); }
  template<> inline F32 convert(const char* value) { return convert<double>(value); }
  
  //-----------------------------------------------------------------------------
  /// Collada animation data
  struct AnimChannels : public Vector<struct AnimData*>
  {
     daeElement  *element;
     AnimChannels(daeElement* el) : element(el)
     {
        element->setUserData(this);
     }
     ~AnimChannels()
     {
        if (element)
           element->setUserData(0);
     }
  };
  
  struct AnimData
  {
     bool           enabled;       ///!< Used to select animation channels for the current clip
  
     _SourceReader  input;
     _SourceReader  output;
  
     _SourceReader  inTangent;
     _SourceReader  outTangent;
  
     _SourceReader  interpolation;
  
     U32 targetValueOffset;        ///< Offset into the target element (for arrays of values)
     U32 targetValueCount;         ///< Number of values animated (from OUTPUT source array)
  
     /// Get the animation channels for the Collada element (if any)
     static AnimChannels* getAnimChannels(const daeElement* element)
     {
        return element ? (AnimChannels*)const_cast<daeElement*>(element)->getUserData() : 0;
     }
  
     AnimData() : enabled(false), targetValueOffset(0), targetValueCount(0){ }
  
     void parseTargetString(const char* target, S32 fullCount, const char* elements[]);
  
     F32 invertParamCubic(F32 param, F32 x0, F32 x1, F32 x2, F32 x3) const;
     void interpValue(F32 t, U32 offset, double* value) const;
     void interpValue(F32 t, U32 offset, const char** value) const;
  };
  
  //-----------------------------------------------------------------------------
  // Collada allows any element with an SID or ID attribute to be the target of
  // an animation channel, which is very flexible, but awkward to work with. Some
  // examples of animated values are:
  // - single float
  // - single int
  // - single bool
  // - single string
  // - list of floats (transform elements or morph weights)
  //
  // This class provides a generic way to check if an element is animated, and
  // to get the value of the element at a given time.
  template<class T>
  struct AnimatedElement
  {
     const daeElement* element;    ///< The Collada element (can be NULL)
     T defaultVal;                 ///< Default value (used when element is NULL)
  
     AnimatedElement(const daeElement* e=0) : element(e) { }
  
     /// Check if the element has any animations channels
     bool isAnimated() { return (AnimData::getAnimChannels(element) != 0); }
     bool isAnimated(F32 start, F32 end) { return isAnimated(); }
  
     /// Get the value of the element at the specified time
     T getValue(F32 time)
     {
        // If the element is NULL, just use the default (handy for <extra> profiles which
        // may or may not be present in the document)
        T value(defaultVal);
        if (const domAny* param = daeSafeCast<domAny>(const_cast<daeElement*>(element))) {
           // If the element is not animated, just use its current value
           value = convert<T>(param->getValue());
  
           // Animate the value
           const AnimChannels* channels = AnimData::getAnimChannels(element);
           if (channels && (time >= 0)) {
              for (S32 iChannel = 0; iChannel < channels->size(); iChannel++) {
                 const AnimData* animData = (*channels)[iChannel];
                 if (animData->enabled)
                    animData->interpValue(time, 0, &value);
              }
           }
        }
        return value;
     }
  };
  
  template<class T> struct AnimatedElementList : public AnimatedElement<T>
  {
     AnimatedElementList(const daeElement* e=0) : AnimatedElement<T>(e) { }
  
     // @todo: Disable morph animations for now since they are not supported by T3D
     bool isAnimated() { return false; }
     bool isAnimated(F32 start, F32 end) { return false; }
  
     // Get the value of the element list at the specified time
     T getValue(F32 time)
     {
        T vec(this->defaultVal);
        if (this->element) {
           // Get a copy of the vector
           vec = *(T*)const_cast<daeElement*>(this->element)->getValuePointer();
  
           // Animate the vector
           const AnimChannels* channels = AnimData::getAnimChannels(this->element);
           if (channels && (time >= 0)) {
              for (S32 iChannel = 0; iChannel < channels->size(); iChannel++) {
                 const AnimData* animData = (*channels)[iChannel];
                 if (animData->enabled) {
                    for (S32 iValue = 0; iValue < animData->targetValueCount; iValue++)
                       animData->interpValue(time, iValue, &vec[animData->targetValueOffset + iValue]);
                 }
              }
           }
        }
        return vec;
     }
  };
  
  // Strongly typed animated values
  typedef AnimatedElement<double> AnimatedFloat;
  typedef AnimatedElement<bool> AnimatedBool;
  typedef AnimatedElement<S32> AnimatedInt;
  typedef AnimatedElement<const char*> AnimatedString;
  typedef AnimatedElementList<domListOfFloats> AnimatedFloatList;
  
  #endif // _COLLADA_UTILS_H_
  