//----------------------------------------------------------------------------- // 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 TINYXML2_INCLUDED #include "tinyxml2.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 element F32 unit; // Override for the collada 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 elements in 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.tscript 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 materialRefList; }; struct meshLODData { Vector 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 detailLevels; Vector meshData; Vector colMeshes; Vector 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 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 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(tinyxml2::XMLElement* rootNode); void exportColladaMaterials(tinyxml2::XMLElement* rootNode, const OptimizedPolyList& mesh, Vector& matNames, const Torque::Path& colladaFile); void exportColladaTriangles(tinyxml2::XMLElement* meshNode, const OptimizedPolyList& mesh, const String& meshName, const Vector& matNames); void exportColladaMesh(tinyxml2::XMLElement* rootNode, const OptimizedPolyList& mesh, const String& meshName, const Vector& matNames); void exportColladaScene(tinyxml2::XMLElement* rootNode, const String& meshName, const Vector& matNames); void exportColladaMaterials(tinyxml2::XMLElement* rootNode, const ExportData& exportData, const Torque::Path& colladaFile); void exportColladaMesh(tinyxml2::XMLElement* rootNode, const ExportData& exportData, const String& meshName); void exportColladaCollisionTriangles(tinyxml2::XMLElement* meshNode, const ExportData& exportData, const U32 collisionIdx); void exportColladaTriangles(tinyxml2::XMLElement* meshNode, const ExportData& exportData, const U32 detailLevel, const String& meshName); void exportColladaScene(tinyxml2::XMLElement* 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 inline MatrixF vecToMatrixF(const domListOfFloats& vec) { return MatrixF(true); } /// Collada : [x_translate, y_translate, z_translate] template<> inline MatrixF vecToMatrixF(const domListOfFloats& vec) { MatrixF mat(true); mat.setPosition(Point3F(vec[0], vec[1], vec[2])); return mat; } /// Collada : [x_scale, y_scale, z_scale] template<> inline MatrixF vecToMatrixF(const domListOfFloats& vec) { MatrixF mat(true); mat.scale(Point3F(vec[0], vec[1], vec[2])); return mat; } /// Collada : [rotation_axis, angle_in_degrees] template<> inline MatrixF vecToMatrixF(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 : same form as TGE (woohoo!) template<> inline MatrixF vecToMatrixF(const domListOfFloats& vec) { MatrixF mat; for (S32 i = 0; i < 16; i++) mat[i] = vec[i]; return mat; } /// Collada : [angle_in_degrees, rotation_axis, translation_axis] /// skew transform code adapted from GMANMatrix4 implementation template<> inline MatrixF vecToMatrixF(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 : [eye, target, up] template<> inline MatrixF vecToMatrixF(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 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 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 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 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 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 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 ColladaPrimitive : public BasePrimitive { T* primitive; domListOfUInts *pTriangleData; S32 stride; public: ColladaPrimitive(const daeElement* e) : pTriangleData(0) { // Cast to geometric primitive element primitive = daeSafeCast(const_cast(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; } }; //----------------------------------------------------------------------------- // template<> inline const domListOfUInts *ColladaPrimitive::getTriangleData() { // Return the

integer list directly return (primitive->getP() ? &(primitive->getP()->getValue()) : NULL); } //----------------------------------------------------------------------------- // template<> inline const domListOfUInts *ColladaPrimitive::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; } //----------------------------------------------------------------------------- // template<> inline const domListOfUInts *ColladaPrimitive::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; } //----------------------------------------------------------------------------- // template<> inline const domListOfUInts *ColladaPrimitive::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; } //----------------------------------------------------------------------------- // template<> inline const domListOfUInts *ColladaPrimitive::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(" element found with invalid

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 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(value); } //----------------------------------------------------------------------------- /// Collada animation data struct AnimChannels : public Vector { 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(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 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 profiles which // may or may not be present in the document) T value(defaultVal); if (const domAny* param = daeSafeCast(const_cast(element))) { // If the element is not animated, just use its current value value = convert(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 struct AnimatedElementList : public AnimatedElement { AnimatedElementList(const daeElement* e=0) : AnimatedElement(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(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 AnimatedFloat; typedef AnimatedElement AnimatedBool; typedef AnimatedElement AnimatedInt; typedef AnimatedElement AnimatedString; typedef AnimatedElementList AnimatedFloatList; #endif // _COLLADA_UTILS_H_