diff --git a/source/collada/CommonConvert.cpp b/source/collada/CommonConvert.cpp new file mode 100644 index 0000000000..93dc42f1c8 --- /dev/null +++ b/source/collada/CommonConvert.cpp @@ -0,0 +1,180 @@ +#include "precompiled.h" + +#include "CommonConvert.h" + +#include "FCollada.h" +#include "FCDocument/FCDSceneNode.h" +#include "FCDocument/FCDSkinController.h" + +#include + +/** Throws a ColladaException unless the value is true. */ +#define REQUIRE(value, message) require_(__LINE__, value, "Assertion not satisfied", message) + +/** Throws a ColladaException unless the status is successful. */ +#define REQUIRE_SUCCESS(status) require_(__LINE__, status) + +void require_(int line, bool value, const char* type, const char* message) +{ + if (value) return; + char linestr[16]; + sprintf(linestr, "%d", line); + throw ColladaException(std::string(type) + " (line " + linestr + "): " + message); +} + +void require_(int line, const FUStatus& status) +{ + require_(line, status, "FCollada error", status.GetErrorString()); +} + +/** Error handler for libxml2 */ +void errorHandler(void* ctx, const char* msg, ...) +{ + char buffer[1024]; + va_list ap; + va_start(ap, msg); + vsnprintf(buffer, sizeof(buffer), msg, ap); + buffer[sizeof(buffer)-1] = '\0'; + va_end(ap); + + *((std::string*)ctx) += buffer; +} + +////////////////////////////////////////////////////////////////////////// + +struct FoundInstance +{ + FCDEntityInstance* instance; + FMMatrix44 transform; +}; + +/** + * Recursively finds all entities under the current node. If onlyMarked is + * set, only matches entities where the user-defined property was set to + * "export" in the modelling program. + * + * @param node root of subtree to search + * @param instances output - appends matching entities + * @param transform transform matrix of current subtree + * @param onlyMarked only match entities with "export" property + */ +static void FindInstances(FCDSceneNode* node, std::vector& instances, const FMMatrix44& transform, bool onlyMarked) +{ + for (size_t i = 0; i < node->GetChildrenCount(); ++i) + { + FCDSceneNode* child = node->GetChild(i); + FindInstances(child, instances, transform * node->ToMatrix(), onlyMarked); + } + + for (size_t i = 0; i < node->GetInstanceCount(); ++i) + { + if (onlyMarked) + { + if (node->GetNote() != "export") + continue; + } + + // Only accept instances of appropriate types, and not e.g. lights + FCDEntity::Type type = node->GetInstance(i)->GetEntityType(); + if (! (type == FCDEntity::GEOMETRY || type == FCDEntity::CONTROLLER)) + continue; + + FoundInstance f; + f.transform = transform * node->ToMatrix(); + f.instance = node->GetInstance(i); + instances.push_back(f); + } +} + +bool FindSingleInstance(FCDSceneNode* node, FCDEntityInstance*& instance, FMMatrix44& transform) +{ + std::vector instances; + + FindInstances(node, instances, FMMatrix44::Identity, true); + if (instances.size() > 1) + { + Log(LOG_ERROR, "Found too many export-marked objects"); + return false; + } + if (instances.empty()) + { + FindInstances(node, instances, FMMatrix44::Identity, false); + if (instances.size() > 1) + { + Log(LOG_ERROR, "Found too many possible objects to convert - try adding the 'export' property to disambiguate one"); + return false; + } + if (instances.empty()) + { + Log(LOG_ERROR, "Didn't find any objects in the scene"); + return false; + } + } + + assert(instances.size() == 1); // if we got this far + instance = instances[0].instance; + transform = instances[0].transform; + return true; +} + +////////////////////////////////////////////////////////////////////////// + +static bool ReverseSortWeight(const FCDJointWeightPair& a, const FCDJointWeightPair& b) +{ + return (a.weight > b.weight); +} + +void SkinReduceInfluences(FCDSkinController* skin, size_t maxInfluenceCount, float minimumWeight) +{ + FCDWeightedMatches& weightedMatches = skin->GetWeightedMatches(); + for (FCDWeightedMatches::iterator itM = weightedMatches.begin(); itM != weightedMatches.end(); ++itM) + { + FCDJointWeightPairList& weights = (*itM); + + FCDJointWeightPairList newWeights; + for (FCDJointWeightPairList::iterator itW = weights.begin(); itW != weights.end(); ++itW) + { + // If this joint already has an influence, just add the weight + // instead of adding a new influence + bool done = false; + for (FCDJointWeightPairList::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW) + { + if (itW->jointIndex == itNW->jointIndex) + { + itNW->weight += itW->weight; + done = true; + break; + } + } + + if (done) + continue; + + // Not had this joint before, so add it + newWeights.push_back(*itW); + } + + // Put highest-weighted influences at the front of the list + sort(newWeights.begin(), newWeights.end(), ReverseSortWeight); + + // Limit the maximum number of influences + if (newWeights.size() > maxInfluenceCount) + newWeights.resize(maxInfluenceCount); + + // Enforce the minimum weight per influence + while (!newWeights.empty() && newWeights.back().weight < minimumWeight) + newWeights.pop_back(); + + // Renormalise, so sum(weights)=1 + float totalWeight = 0; + for (FCDJointWeightPairList::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW) + totalWeight += itNW->weight; + for (FCDJointWeightPairList::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW) + itNW->weight /= totalWeight; + + // Copy new weights into the skin + weights = newWeights; + } + + skin->SetDirtyFlag(); +} diff --git a/source/collada/CommonConvert.h b/source/collada/CommonConvert.h new file mode 100644 index 0000000000..419409dc2d --- /dev/null +++ b/source/collada/CommonConvert.h @@ -0,0 +1,77 @@ +#ifndef COMMONCONVERT_H__ +#define COMMONCONVERT_H__ + +#include +#include + +class FUStatus; +class FCDSceneNode; +class FCDEntityInstance; +class FMMatrix44; +class FCDSkinController; + +class ColladaException : public std::exception +{ +public: + ColladaException(const std::string& msg) + : msg(msg) + { + } + + ~ColladaException() throw() + { + } + + virtual const char* what() const throw() + { + return msg.c_str(); + } +private: + std::string msg; +}; + +struct OutputCB +{ + virtual void operator() (const char* data, unsigned int length)=0; +}; + +/** Throws a ColladaException unless the value is true. */ +#define REQUIRE(value, message) require_(__LINE__, value, "Assertion not satisfied", message) + +/** Throws a ColladaException unless the status is successful. */ +#define REQUIRE_SUCCESS(status) require_(__LINE__, status) + +void require_(int line, bool value, const char* type, const char* message); +void require_(int line, const FUStatus& status); + +/** Outputs a structure, using sizeof to get the size. */ +template void write(OutputCB& output, const T& data) +{ + output((char*)&data, sizeof(T)); +} + +/** Error handler for libxml2 */ +void errorHandler(void* ctx, const char* msg, ...); + + +/** + * Tries to find a single suitable entity instance in the scene. Fails if there + * are none, or if there are too many and it's not clear which one should + * be converted. + * + * @param node root scene node to search under + * @param instance output - the found entity instance (if any) + * @param transform - the world-space transform of the found entity + * + * @return true if one was found + */ +bool FindSingleInstance(FCDSceneNode* node, FCDEntityInstance*& instance, FMMatrix44& transform); + +/** + * Like FCDSkinController::ReduceInfluences but works correctly. + * Additionally, multiple influences for the same joint-vertex pair are + * collapsed into a single influence. + */ +void SkinReduceInfluences(FCDSkinController* skin, size_t maxInfluenceCount, float minimumWeight); + +#endif // COMMONCONVERT_H__ diff --git a/source/collada/Converter.h b/source/collada/Converter.h deleted file mode 100644 index c1d2f342e5..0000000000 --- a/source/collada/Converter.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef CONVERTER_H__ -#define CONVERTER_H__ - -#include -#include - -class ColladaException : public std::exception -{ -public: - ColladaException(const std::string& msg) - : msg(msg) - { - } - - ~ColladaException() throw() - { - } - - virtual const char* what() const throw() - { - return msg.c_str(); - } -private: - std::string msg; -}; - -struct OutputCB -{ - virtual void operator() (const char* data, unsigned int length)=0; -}; - -void ColladaToPMD(const char* input, OutputCB& output, std::string& xmlErrors); - -#endif // CONVERTER_H__ diff --git a/source/collada/DLL.cpp b/source/collada/DLL.cpp index e48e32f167..4dad5de2cd 100644 --- a/source/collada/DLL.cpp +++ b/source/collada/DLL.cpp @@ -1,6 +1,8 @@ #include "precompiled.h" -#include "Converter.h" +#include "CommonConvert.h" +#include "PMDConvert.h" +#include "PSAConvert.h" #include #include @@ -76,15 +78,15 @@ struct BufferedOutputCallback : public OutputCB } }; -int convert_dae_to_pmd(const char* dae, OutputFn pmd_writer, void* cb_data) +int convert_dae_to_whatever(const char* dae, OutputFn writer, void* cb_data, void(*conv)(const char*, OutputCB&, std::string&)) { Log(LOG_INFO, "Starting conversion"); std::string xmlErrors; - BufferedOutputCallback cb(pmd_writer, cb_data); + BufferedOutputCallback cb(writer, cb_data); try { - ColladaToPMD(dae, cb, xmlErrors); + conv(dae, cb, xmlErrors); } catch (ColladaException e) { @@ -105,3 +107,13 @@ int convert_dae_to_pmd(const char* dae, OutputFn pmd_writer, void* cb_data) return 0; } + +int convert_dae_to_pmd(const char* dae, OutputFn pmd_writer, void* cb_data) +{ + return convert_dae_to_whatever(dae, pmd_writer, cb_data, ColladaToPMD); +} + +int convert_dae_to_psa(const char* dae, OutputFn psa_writer, void* cb_data) +{ + return convert_dae_to_whatever(dae, psa_writer, cb_data, ColladaToPSA); +} diff --git a/source/collada/DLL.h b/source/collada/DLL.h index 18e24e9016..d654d1a71a 100644 --- a/source/collada/DLL.h +++ b/source/collada/DLL.h @@ -27,6 +27,7 @@ typedef void (*OutputFn) (void* cb_data, const char* data, unsigned int length); EXPORT void set_logger(LogFn logger); EXPORT int convert_dae_to_pmd(const char* dae, OutputFn pmd_writer, void* cb_data); +EXPORT int convert_dae_to_psa(const char* dae, OutputFn psa_writer, void* cb_data); #ifdef __cplusplus }; diff --git a/source/collada/GeomReindex.cpp b/source/collada/GeomReindex.cpp index 3c6b1dc8c4..270ce593b8 100644 --- a/source/collada/GeomReindex.cpp +++ b/source/collada/GeomReindex.cpp @@ -2,6 +2,12 @@ #include "GeomReindex.h" +#include "FCollada.h" +#include "FCDocument/FCDEntity.h" +#include "FCDocument/FCDGeometryPolygons.h" +#include "FCDocument/FCDGeometrySource.h" +#include "FCDocument/FCDSkinController.h" + #include struct VertexData diff --git a/source/collada/GeomReindex.h b/source/collada/GeomReindex.h index e626e646ef..28bd7a13b1 100644 --- a/source/collada/GeomReindex.h +++ b/source/collada/GeomReindex.h @@ -1 +1,9 @@ -void ReindexGeometry(FCDGeometryPolygons* polys, FCDSkinController* skin = NULL); +#ifndef GEOMREINDEX_H__ +#define GEOMREINDEX_H__ + +class FCDGeometryPolygons; +class FCDSkinController; + +void ReindexGeometry(FCDGeometryPolygons* polys, FCDSkinController* skin = 0); + +#endif // GEOMREINDEX_H__ diff --git a/source/collada/Maths.cpp b/source/collada/Maths.cpp index ac47f6cfcc..85383d2f16 100644 --- a/source/collada/Maths.cpp +++ b/source/collada/Maths.cpp @@ -2,6 +2,8 @@ #include "Maths.h" +#include "FCollada.h" + FMMatrix44 operator+ (const FMMatrix44& a, const FMMatrix44& b) { FMMatrix44 r; diff --git a/source/collada/Maths.h b/source/collada/Maths.h index 7427fd9b13..d52fc63e90 100644 --- a/source/collada/Maths.h +++ b/source/collada/Maths.h @@ -1,6 +1,8 @@ #ifndef MATHS_H__ #define MATHS_H__ +class FMMatrix44; + extern FMMatrix44 operator+ (const FMMatrix44& a, const FMMatrix44& b); extern FMMatrix44 operator/ (const FMMatrix44& a, const float b); extern FMMatrix44 QuatToMatrix(float x, float y, float z, float w); diff --git a/source/collada/Converter.cpp b/source/collada/PMDConvert.cpp similarity index 65% rename from source/collada/Converter.cpp rename to source/collada/PMDConvert.cpp index d7c03d6c10..8a5554a1bd 100644 --- a/source/collada/Converter.cpp +++ b/source/collada/PMDConvert.cpp @@ -1,6 +1,7 @@ #include "precompiled.h" -#include "Converter.h" +#include "PMDConvert.h" +#include "CommonConvert.h" #include "FCollada.h" #include "FCDocument/FCDocument.h" @@ -20,30 +21,6 @@ #include #include -/** Throws a ColladaException unless the value is true. */ -#define REQUIRE(value, message) require_(__LINE__, value, "Assertion not satisfied", message) - -/** Throws a ColladaException unless the status is successful. */ -#define REQUIRE_SUCCESS(status) require_(__LINE__, status) - -void require_(int line, bool value, const char* type, const char* message) -{ - if (value) return; - char linestr[16]; - sprintf(linestr, "%d", line); - throw ColladaException(std::string(type) + " (line " + linestr + "): " + message); -} -void require_(int line, const FUStatus& status) -{ - require_(line, status, "FCollada error", status.GetErrorString()); -} - -/** Outputs a structure, using sizeof to get the size. */ -template void write(OutputCB& output, const T& data) -{ - output((char*)&data, sizeof(T)); -} - const int maxInfluences = 4; struct VertexBlend { @@ -59,24 +36,11 @@ struct BoneTransform }; -/** Error handler for libxml2 */ -void errorHandler(void* ctx, const char* msg, ...) -{ - char buffer[1024]; - va_list ap; - va_start(ap, msg); - vsnprintf(buffer, sizeof(buffer), msg, ap); - buffer[sizeof(buffer)-1] = '\0'; - va_end(ap); - - *((std::string*)ctx) += buffer; -} - -class Converter +class PMDConvert { public: /** - * Converts a COLLADA XML document into the PMD format. + * Converts a COLLADA XML document into the PMD mesh format. * * @param input XML document to parse * @param output callback for writing the PMD data; called lots of times @@ -385,162 +349,6 @@ public: bones[i].orientation[3] = -bones[i].orientation[3]; } } - - ////////////////////////////////////////////////////////////////////////// - - struct FoundInstance - { - FCDEntityInstance* instance; - FMMatrix44 transform; - }; - - /** - * Tries to find a single suitable entity instance in the scene. Fails if there - * are none, or if there are too many and it's not clear which one should - * be converted. - * - * @param node root scene node to search under - * @param instance output - the found entity instance (if any) - * @param transform - the world-space transform of the found entity - * - * @return true if one was found - */ - static bool FindSingleInstance(FCDSceneNode* node, FCDEntityInstance*& instance, FMMatrix44& transform) - { - std::vector instances; - - FindInstances(node, instances, FMMatrix44::Identity, true); - if (instances.size() > 1) - { - Log(LOG_ERROR, "Found too many export-marked objects"); - return false; - } - if (instances.empty()) - { - FindInstances(node, instances, FMMatrix44::Identity, false); - if (instances.size() > 1) - { - Log(LOG_ERROR, "Found too many possible objects to convert - try adding the 'export' property to disambiguate one"); - return false; - } - if (instances.empty()) - { - Log(LOG_ERROR, "Didn't find any objects in the scene"); - return false; - } - } - - assert(instances.size() == 1); // if we got this far - instance = instances[0].instance; - transform = instances[0].transform; - return true; - } - - /** - * Recursively finds all entities under the current node. If onlyMarked is - * set, only matches entities where the user-defined property was set to - * "export" in the modelling program. - * - * @param node root of subtree to search - * @param instances output - appends matching entities - * @param transform transform matrix of current subtree - * @param onlyMarked only match entities with "export" property - */ - static void FindInstances(FCDSceneNode* node, std::vector& instances, const FMMatrix44& transform, bool onlyMarked) - { - for (size_t i = 0; i < node->GetChildrenCount(); ++i) - { - FCDSceneNode* child = node->GetChild(i); - FindInstances(child, instances, transform * node->ToMatrix(), onlyMarked); - } - - for (size_t i = 0; i < node->GetInstanceCount(); ++i) - { - if (onlyMarked) - { - if (node->GetNote() != "export") - continue; - } - - // Only accept instances of appropriate types, and not e.g. lights - FCDEntity::Type type = node->GetInstance(i)->GetEntityType(); - if (! (type == FCDEntity::GEOMETRY || type == FCDEntity::CONTROLLER)) - continue; - - FoundInstance f; - f.transform = transform * node->ToMatrix(); - f.instance = node->GetInstance(i); - instances.push_back(f); - } - } - - ////////////////////////////////////////////////////////////////////////// - - static bool ReverseSortWeight(const FCDJointWeightPair& a, const FCDJointWeightPair& b) - { - return (a.weight > b.weight); - } - - /** - * Like FCDSkinController::ReduceInfluences but works correctly. - * Additionally, multiple influences for the same joint-vertex pair are - * collapsed into a single influence. - */ - static void SkinReduceInfluences(FCDSkinController* skin, uint32 maxInfluenceCount, float minimumWeight) - { - FCDWeightedMatches& weightedMatches = skin->GetWeightedMatches(); - for (FCDWeightedMatches::iterator itM = weightedMatches.begin(); itM != weightedMatches.end(); ++itM) - { - FCDJointWeightPairList& weights = (*itM); - - FCDJointWeightPairList newWeights; - for (FCDJointWeightPairList::iterator itW = weights.begin(); itW != weights.end(); ++itW) - { - // If this joint already has an influence, just add the weight - // instead of adding a new influence - bool done = false; - for (FCDJointWeightPairList::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW) - { - if (itW->jointIndex == itNW->jointIndex) - { - itNW->weight += itW->weight; - done = true; - break; - } - } - - if (done) - continue; - - // Not had this joint before, so add it - newWeights.push_back(*itW); - } - - // Put highest-weighted influences at the front of the list - sort(newWeights.begin(), newWeights.end(), ReverseSortWeight); - - // Limit the maximum number of influences - if (newWeights.size() > maxInfluenceCount) - newWeights.resize(maxInfluenceCount); - - // Enforce the minimum weight per influence - while (!newWeights.empty() && newWeights.back().weight < minimumWeight) - newWeights.pop_back(); - - // Renormalise, so sum(weights)=1 - float totalWeight = 0; - for (FCDJointWeightPairList::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW) - totalWeight += itNW->weight; - for (FCDJointWeightPairList::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW) - itNW->weight /= totalWeight; - - // Copy new weights into the skin - weights = newWeights; - } - - skin->SetDirtyFlag(); - } - }; @@ -550,5 +358,5 @@ public: void ColladaToPMD(const char* input, OutputCB& output, std::string& xmlErrors) { - Converter::ColladaToPMD(input, output, xmlErrors); + PMDConvert::ColladaToPMD(input, output, xmlErrors); } diff --git a/source/collada/PMDConvert.h b/source/collada/PMDConvert.h new file mode 100644 index 0000000000..2675ce5154 --- /dev/null +++ b/source/collada/PMDConvert.h @@ -0,0 +1,10 @@ +#ifndef PMDCONVERT_H__ +#define PMDCONVERT_H__ + +#include + +struct OutputCB; + +void ColladaToPMD(const char* input, OutputCB& output, std::string& xmlErrors); + +#endif // PMDCONVERT_H__ diff --git a/source/collada/PSAConvert.cpp b/source/collada/PSAConvert.cpp new file mode 100644 index 0000000000..f80562da43 --- /dev/null +++ b/source/collada/PSAConvert.cpp @@ -0,0 +1,220 @@ +#include "precompiled.h" + +#include "PSAConvert.h" +#include "CommonConvert.h" + +#include "FCollada.h" +#include "FCDocument/FCDocument.h" +#include "FCDocument/FCDAnimated.h" +#include "FCDocument/FCDAnimationCurve.h" +#include "FCDocument/FCDController.h" +#include "FCDocument/FCDControllerInstance.h" +#include "FCDocument/FCDGeometry.h" +#include "FCDocument/FCDGeometryMesh.h" +#include "FCDocument/FCDGeometryPolygons.h" +#include "FCDocument/FCDGeometrySource.h" +#include "FCDocument/FCDSceneNode.h" +#include "FCDocument/FCDSkinController.h" + +#include "StdSkeletons.h" +#include "Decompose.h" +#include "Maths.h" +#include "GeomReindex.h" + +#include +#include + +struct BoneTransform +{ + float translation[3]; + float orientation[4]; +}; + +class PSAConvert +{ +public: + /** + * Converts a COLLADA XML document into the PSA animation format. + * + * @param input XML document to parse + * @param output callback for writing the PSA data; called lots of times + * with small strings + * @param xmlErrors output - errors reported by the XML parser + * @throws ColladaException on failure + */ + static void ColladaToPSA(const char* input, OutputCB& output, std::string& xmlErrors) + { + FUStatus ret; + + // Grab all the error output from libxml2. Be careful to never use + // libxml2 outside this function without having first set/reset the + // errorfunc (since xmlErrors won't be valid any more). + xmlSetGenericErrorFunc(&xmlErrors, &errorHandler); + + std::auto_ptr doc (FCollada::NewTopDocument()); + REQUIRE_SUCCESS(doc->LoadFromText("", input)); + + FCDSceneNode* root = doc->GetVisualSceneRoot(); + + // Find the instance to convert + FCDEntityInstance* instance; + FMMatrix44 entityTransform; + if (! FindSingleInstance(root, instance, entityTransform)) + throw ColladaException("Couldn't find object to convert"); + + assert(instance); + Log(LOG_INFO, "Converting '%s'", instance->GetEntity()->GetName().c_str()); + + if (instance->GetEntity()->GetType() == FCDEntity::CONTROLLER) + { + FCDController* controller = (FCDController*)instance->GetEntity(); + + REQUIRE(controller->HasSkinController(), "has skin controller"); + FCDSkinController* skin = controller->GetSkinController(); + + // Find the first and last times which have animations + float timeStart = FLT_MAX, timeEnd = -FLT_MAX; + for (size_t i = 0; i < skin->GetJointCount(); ++i) + { + FCDJointMatrixPair* joint = skin->GetJoint(i); + REQUIRE(joint->joint != NULL, "joint exists"); + + // Skip unanimated joints + if (joint->joint->GetTransformCount() == 0) + continue; + + REQUIRE(joint->joint->GetTransformCount() == 1, "joint has single transform"); + + FCDTransform* transform = joint->joint->GetTransform(0); + + // Skip unanimated joints again. (TODO: Which of these happens in practice?) + if (! transform->IsAnimated()) + continue; + + // Iterate over all curves + FCDAnimated* anim = transform->GetAnimated(); + FCDAnimationCurveListList& curvesList = anim->GetCurves(); + for (size_t j = 0; j < curvesList.size(); ++j) + { + FCDAnimationCurveList& curves = curvesList[j]; + for (size_t k = 0; k < curves.size(); ++k) + { + FCDAnimationCurve* curve = curves[k]; + timeStart = std::min(timeStart, curve->GetKeys().front()); + timeEnd = std::max(timeEnd, curve->GetKeys().back()); + } + } + } + + float frameLength = 1.f / 30.f; + + // Count frames; don't include the last keyframe + size_t frameCount = (size_t)((timeEnd - timeStart) / frameLength - 0.5f); + + size_t boneCount = StdSkeletons::GetBoneCount(); + + std::vector boneTransforms; + + for (size_t frame = 0; frame < frameCount; ++frame) + { + float time = timeStart + frameLength * frame; + + BoneTransform boneDefault = { { 0, 0, 0 }, { 0, 0, 0, 1 } }; + std::vector frameBoneTransforms (boneCount, boneDefault); + + // Move the model into the new animated pose + for (size_t i = 0; i < skin->GetJointCount(); ++i) + { + FCDTransform* transform = skin->GetJoint(i)->joint->GetTransform(0); + FCDAnimated* anim = transform->GetAnimated(); + anim->Evaluate(time); + } + + // Convert the pose into the form require by the game + for (size_t i = 0; i < skin->GetJointCount(); ++i) + { + FCDSceneNode* jointNode = skin->GetJoint(i)->joint; + FMMatrix44 worldTransform = jointNode->CalculateWorldTransform(); + + HMatrix matrix; + memcpy(matrix, worldTransform.Transposed().m, sizeof(matrix)); + + AffineParts parts; + decomp_affine(matrix, &parts); + + BoneTransform b = { + { parts.t.x, parts.t.y, parts.t.z }, + { parts.q.x, parts.q.y, parts.q.z, parts.q.w } + }; + + int boneId = StdSkeletons::FindStandardBoneID(jointNode->GetName()); + REQUIRE(boneId >= 0, "recognised bone name"); + frameBoneTransforms[boneId] = b; + } + + // Push frameBoneTransforms onto the back of boneTransforms + copy(frameBoneTransforms.begin(), frameBoneTransforms.end(), inserter(boneTransforms, boneTransforms.end())); + } + + // Convert into game's coordinate space + TransformVertices(boneTransforms); + + // Write out the file + WritePSA(output, frameCount, boneCount, boneTransforms); + } + else + { + throw ColladaException("Unrecognised object type"); + } + } + + /** + * Writes the animation data in the PSA format. + */ + static void WritePSA(OutputCB& output, size_t frameCount, size_t boneCount, const std::vector& boneTransforms) + { + output("PSSA", 4); // magic number + write(output, 1); // version number + write(output, (uint32)( + 4 + 0 + // name + 4 + // frameLength + 4 + 4 + // numBones, numFrames + 7*4*boneCount*frameCount // boneStates + )); // data size + + // Name + write(output, 0); + + // Frame length + write(output, 1000/30.f); + + write(output, (uint32)boneCount); + write(output, (uint32)frameCount); + + for (size_t i = 0; i < boneCount*frameCount; ++i) + { + output((char*)&boneTransforms[i], 7*4); + } + } + + static void TransformVertices(std::vector& bones) + { + // (See PMDConvert.cpp for explanatory comments) + for (size_t i = 0; i < bones.size(); ++i) + { + std::swap(bones[i].translation[1], bones[i].translation[2]); + std::swap(bones[i].orientation[1], bones[i].orientation[2]); + bones[i].orientation[3] = -bones[i].orientation[3]; + } + } +}; + + +// The above stuff is just in a class since I don't like having to bother +// with forward declarations of functions - but provide the plain function +// interface here: + +void ColladaToPSA(const char* input, OutputCB& output, std::string& xmlErrors) +{ + PSAConvert::ColladaToPSA(input, output, xmlErrors); +} diff --git a/source/collada/PSAConvert.h b/source/collada/PSAConvert.h new file mode 100644 index 0000000000..e4a5454979 --- /dev/null +++ b/source/collada/PSAConvert.h @@ -0,0 +1,10 @@ +#ifndef PSACONVERT_H__ +#define PSACONVERT_H__ + +#include + +struct OutputCB; + +void ColladaToPSA(const char* input, OutputCB& output, std::string& xmlErrors); + +#endif // PSACONVERT_H__ diff --git a/source/collada/StdSkeletons.h b/source/collada/StdSkeletons.h index 5df43e30e3..23a5720d99 100644 --- a/source/collada/StdSkeletons.h +++ b/source/collada/StdSkeletons.h @@ -1,6 +1,8 @@ #ifndef STDSKELETONS_H__ #define STDSKELETONS_H__ +#include + namespace StdSkeletons { extern int GetBoneCount(); diff --git a/source/collada/precompiled.h b/source/collada/precompiled.h index 8ac1fd80d8..315a450132 100644 --- a/source/collada/precompiled.h +++ b/source/collada/precompiled.h @@ -11,6 +11,7 @@ extern void Log(int severity, const char* fmt, ...); #include "FCollada.h" #include "FCDocument/FCDocument.h" +#include "FCDocument/FCDAnimated.h" #include "FCDocument/FCDController.h" #include "FCDocument/FCDGeometry.h" #include "FCDocument/FCDGeometryMesh.h" @@ -18,3 +19,10 @@ extern void Log(int severity, const char* fmt, ...); #include "FCDocument/FCDGeometrySource.h" #include "FCDocument/FCDSceneNode.h" #include "FCDocument/FCDSkinController.h" +#include +#include + +// FCollada pollutes the global namespace by defining these +// to std::{min,max}, so undo its macros +#undef min +#undef max