Torque3D/Engine/source/ts/collada/colladaUtils.h
marauder2k7 efbe5e90f5 virtuals removed
virtuals removed and replaced with override where necessary, clang-tidy to the rescue.
2024-03-18 18:13:00 +00:00

986 lines
35 KiB
C++

//-----------------------------------------------------------------------------
// 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 <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.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<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(tinyxml2::XMLElement* rootNode);
void exportColladaMaterials(tinyxml2::XMLElement* rootNode, const OptimizedPolyList& mesh, Vector<String>& matNames, const Torque::Path& colladaFile);
void exportColladaTriangles(tinyxml2::XMLElement* meshNode, const OptimizedPolyList& mesh, const String& meshName, const Vector<String>& matNames);
void exportColladaMesh(tinyxml2::XMLElement* rootNode, const OptimizedPolyList& mesh, const String& meshName, const Vector<String>& matNames);
void exportColladaScene(tinyxml2::XMLElement* rootNode, const String& meshName, const Vector<String>& 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<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()[(U64)(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()[(U64)(index*stride())];
else if (source->getIDREF_array())
return source->getIDREF_array()->getValue()[(U64)(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() override { return primitive->getElementName(); }
const char* getMaterial() override { return (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImportMat, primitive->getMaterial(), false)) ? NULL : primitive->getMaterial(); }
const domInputLocalOffset_Array& getInputs() override { return primitive->getInput_array(); }
S32 getStride() const override { return stride; }
/// Each supported primitive needs to implement this method (and convert
/// to triangles if required)
const domListOfUInts *getTriangleData() override { 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]);
U64 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((U64)(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]);
U64 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]);
U64 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((U64)(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((U64)(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_