forked from 0ad/0ad
# Conversion of skeleton animations from COLLADA
This was SVN commit r4725.
This commit is contained in:
parent
24f06de815
commit
b5403bcfc7
180
source/collada/CommonConvert.cpp
Normal file
180
source/collada/CommonConvert.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "CommonConvert.h"
|
||||
|
||||
#include "FCollada.h"
|
||||
#include "FCDocument/FCDSceneNode.h"
|
||||
#include "FCDocument/FCDSkinController.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
/** 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<FoundInstance>& 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<FoundInstance> 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();
|
||||
}
|
77
source/collada/CommonConvert.h
Normal file
77
source/collada/CommonConvert.h
Normal file
@ -0,0 +1,77 @@
|
||||
#ifndef COMMONCONVERT_H__
|
||||
#define COMMONCONVERT_H__
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
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<typename T> 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__
|
@ -1,34 +0,0 @@
|
||||
#ifndef CONVERTER_H__
|
||||
#define CONVERTER_H__
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
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__
|
@ -1,6 +1,8 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "Converter.h"
|
||||
#include "CommonConvert.h"
|
||||
#include "PMDConvert.h"
|
||||
#include "PSAConvert.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cassert>
|
||||
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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 <cassert>
|
||||
|
||||
struct VertexData
|
||||
|
@ -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__
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include "Maths.h"
|
||||
|
||||
#include "FCollada.h"
|
||||
|
||||
FMMatrix44 operator+ (const FMMatrix44& a, const FMMatrix44& b)
|
||||
{
|
||||
FMMatrix44 r;
|
||||
|
@ -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);
|
||||
|
@ -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 <cassert>
|
||||
#include <vector>
|
||||
|
||||
/** 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<typename T> 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<FoundInstance> 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<FoundInstance>& 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);
|
||||
}
|
10
source/collada/PMDConvert.h
Normal file
10
source/collada/PMDConvert.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef PMDCONVERT_H__
|
||||
#define PMDCONVERT_H__
|
||||
|
||||
#include <string>
|
||||
|
||||
struct OutputCB;
|
||||
|
||||
void ColladaToPMD(const char* input, OutputCB& output, std::string& xmlErrors);
|
||||
|
||||
#endif // PMDCONVERT_H__
|
220
source/collada/PSAConvert.cpp
Normal file
220
source/collada/PSAConvert.cpp
Normal file
@ -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 <cassert>
|
||||
#include <vector>
|
||||
|
||||
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<FCDocument> 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<BoneTransform> 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<BoneTransform> 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<BoneTransform>& boneTransforms)
|
||||
{
|
||||
output("PSSA", 4); // magic number
|
||||
write<uint32>(output, 1); // version number
|
||||
write<uint32>(output, (uint32)(
|
||||
4 + 0 + // name
|
||||
4 + // frameLength
|
||||
4 + 4 + // numBones, numFrames
|
||||
7*4*boneCount*frameCount // boneStates
|
||||
)); // data size
|
||||
|
||||
// Name
|
||||
write<uint32>(output, 0);
|
||||
|
||||
// Frame length
|
||||
write<float>(output, 1000/30.f);
|
||||
|
||||
write<uint32>(output, (uint32)boneCount);
|
||||
write<uint32>(output, (uint32)frameCount);
|
||||
|
||||
for (size_t i = 0; i < boneCount*frameCount; ++i)
|
||||
{
|
||||
output((char*)&boneTransforms[i], 7*4);
|
||||
}
|
||||
}
|
||||
|
||||
static void TransformVertices(std::vector<BoneTransform>& 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);
|
||||
}
|
10
source/collada/PSAConvert.h
Normal file
10
source/collada/PSAConvert.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef PSACONVERT_H__
|
||||
#define PSACONVERT_H__
|
||||
|
||||
#include <string>
|
||||
|
||||
struct OutputCB;
|
||||
|
||||
void ColladaToPSA(const char* input, OutputCB& output, std::string& xmlErrors);
|
||||
|
||||
#endif // PSACONVERT_H__
|
@ -1,6 +1,8 @@
|
||||
#ifndef STDSKELETONS_H__
|
||||
#define STDSKELETONS_H__
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace StdSkeletons
|
||||
{
|
||||
extern int GetBoneCount();
|
||||
|
@ -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 <cassert>
|
||||
#include <string>
|
||||
|
||||
// FCollada pollutes the global namespace by defining these
|
||||
// to std::{min,max}, so undo its macros
|
||||
#undef min
|
||||
#undef max
|
||||
|
Loading…
Reference in New Issue
Block a user