# Updated COLLADA converter.

Now uses FCollada 3.02.

This was SVN commit r4933.
This commit is contained in:
Ykkrosh 2007-03-01 00:24:34 +00:00
parent fc111ecb08
commit ea29a1caeb
6 changed files with 204 additions and 71 deletions

View File

@ -8,12 +8,6 @@
#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;
@ -22,11 +16,6 @@ void require_(int line, bool value, const char* type, const char* message)
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, ...)
{
@ -40,8 +29,50 @@ void errorHandler(void* ctx, const char* msg, ...)
*((std::string*)ctx) += buffer;
}
FColladaErrorHandler::FColladaErrorHandler(std::string& xmlErrors_)
: xmlErrors(xmlErrors_)
{
// Grab all the error output from libxml2, for useful error reporting
xmlSetGenericErrorFunc(&xmlErrors, &errorHandler);
FUError::SetErrorCallback(FUError::DEBUG, new FUFunctor3<FColladaErrorHandler, FUError::Level, uint32, uint32, void>(this, &FColladaErrorHandler::OnError));
FUError::SetErrorCallback(FUError::WARNING, new FUFunctor3<FColladaErrorHandler, FUError::Level, uint32, uint32, void>(this, &FColladaErrorHandler::OnError));
FUError::SetErrorCallback(FUError::ERROR, new FUFunctor3<FColladaErrorHandler, FUError::Level, uint32, uint32, void>(this, &FColladaErrorHandler::OnError));
}
FColladaErrorHandler::~FColladaErrorHandler()
{
xmlSetGenericErrorFunc(NULL, NULL);
FUError::SetErrorCallback(FUError::DEBUG, NULL);
FUError::SetErrorCallback(FUError::WARNING, NULL);
FUError::SetErrorCallback(FUError::ERROR, NULL);
}
void FColladaErrorHandler::OnError(FUError::Level errorLevel, uint32 errorCode, uint32 UNUSED(lineNumber))
{
const char* errorString = FUError::GetErrorString((FUError::Code) errorCode);
if (! errorString)
errorString = "Unknown error code";
if (errorLevel == FUError::DEBUG)
Log(LOG_INFO, "FCollada message %d: %s", errorCode, errorString);
else if (errorLevel == FUError::WARNING)
Log(LOG_WARNING, "FCollada error %d: %s", errorCode, errorString);
else
throw ColladaException(errorString);
}
//////////////////////////////////////////////////////////////////////////
// These don't get exported properly from FCollada (3.02, DLL), so define them
// here instead of fixing it correctly.
const FMVector3 FMVector3::XAxis(1.0f, 0.0f, 0.0f);
static float identity[] = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
FMMatrix44 FMMatrix44::Identity(identity);
struct FoundInstance
{
FCDEntityInstance* instance;

View File

@ -35,14 +35,27 @@ struct OutputCB
virtual void operator() (const char* data, unsigned int length)=0;
};
class FColladaErrorHandler
{
public:
FColladaErrorHandler(std::string& xmlErrors);
~FColladaErrorHandler();
private:
void OnError(FUError::Level errorLevel, uint32 errorCode, uint32 lineNumber);
std::string& xmlErrors;
void operator=(FColladaErrorHandler);
};
/** 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)
#define REQUIRE_SUCCESS(status) require_(__LINE__, status, "FCollada error", "Line " STRINGIFY(__LINE__))
#define STRINGIFY(x) #x
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)

View File

@ -88,7 +88,7 @@ int convert_dae_to_whatever(const char* dae, OutputFn writer, void* cb_data, voi
{
conv(dae, cb, xmlErrors);
}
catch (ColladaException e)
catch (const ColladaException& e)
{
if (! xmlErrors.empty())
Log(LOG_ERROR, "%s", xmlErrors.c_str());

View File

@ -50,17 +50,13 @@ public:
*/
static void ColladaToPMD(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);
FColladaErrorHandler err (xmlErrors);
std::auto_ptr<FCDocument> doc (FCollada::NewTopDocument());
REQUIRE_SUCCESS(doc->LoadFromText("", input));
FCDSceneNode* root = doc->GetVisualSceneRoot();
REQUIRE(root != NULL, "has root object");
// Find the instance to convert
FCDEntityInstance* instance;
@ -101,12 +97,26 @@ public:
WritePMD(output, *indicesCombined, dataPosition, dataNormal, dataTexcoord, boneWeights, boneTransforms);
}
else if (instance->GetEntity()->GetType() == FCDEntity::CONTROLLER)
else if (instance->GetType() == FCDEntityInstance::CONTROLLER)
{
FCDControllerInstance* controllerInstance = (FCDControllerInstance*)instance;
// (NB: GetType is deprecated and should be replaced with HasType,
// except that has irritating linker errors when using a DLL, so don't
// bother)
assert(instance->GetEntity()->GetType() == FCDEntity::CONTROLLER); // assume this is always true?
FCDController* controller = (FCDController*)instance->GetEntity();
REQUIRE(controller->HasSkinController(), "has skin controller");
FCDSkinController* skin = controller->GetSkinController();
REQUIRE(skin != NULL, "is skin controller");
// Data for joints is stored in two places - avoid overflows by limiting
// to the minimum of the two sizes, and warn if they're different (which
// happens in practice for slightly-broken meshes)
size_t jointCount = std::min(skin->GetJointCount(), controllerInstance->GetJointCount());
if (skin->GetJointCount() != controllerInstance->GetJointCount())
Log(LOG_WARNING, "Mismatched bone counts");
// Get the skinned mesh for this entity
FCDEntity* baseTarget = controller->GetBaseTarget();
@ -135,7 +145,12 @@ public:
{
uint32 jointIdx = vertexInfluences[i][j].jointIndex;
REQUIRE(jointIdx <= 0xFF, "sensible number of joints");
FCDSceneNode* joint = skin->GetJoint(jointIdx)->joint;
// Find the joint on the skeleton, after checking it really exists
FCDSceneNode* joint = NULL;
if (jointIdx < controllerInstance->GetJointCount())
joint = controllerInstance->GetJoint(jointIdx);
if (! joint)
{
if (! hasComplainedAboutNonexistentJoints)
@ -159,17 +174,9 @@ public:
transform = skin->GetBindShapeTransform();
for (size_t i = 0; i < skin->GetJointCount(); ++i)
for (size_t i = 0; i < jointCount; ++i)
{
FCDJointMatrixPair* joint = skin->GetJoint(i);
if (! joint->joint)
{
Log(LOG_WARNING, "Skin has nonexistent joint");
continue;
}
FMMatrix44 bindPose = joint->invertedBindPose.Inverted();
FMMatrix44 bindPose = skin->GetBindPoses()[i].Inverted();
HMatrix matrix;
memcpy(matrix, bindPose.Transposed().m, sizeof(matrix));
@ -183,8 +190,14 @@ public:
{ parts.q.x, parts.q.y, parts.q.z, parts.q.w }
};
int boneId = StdSkeletons::FindStandardBoneID(joint->joint->GetName());
REQUIRE(boneId >= 0, "recognised bone name");
FCDSceneNode* joint = controllerInstance->GetJoint(i);
int boneId = StdSkeletons::FindStandardBoneID(joint->GetName());
if (boneId < 0)
{
Log(LOG_WARNING, "Unrecognised bone name '%s'", joint->GetName().c_str());
continue;
}
boneTransforms[boneId] = b;
}

View File

@ -14,7 +14,6 @@
#include "FCDocument/FCDGeometryPolygons.h"
#include "FCDocument/FCDGeometrySource.h"
#include "FCDocument/FCDSceneNode.h"
#include "FCDocument/FCDSkinController.h"
#include "StdSkeletons.h"
#include "Decompose.h"
@ -23,6 +22,7 @@
#include <cassert>
#include <vector>
#include <limits>
struct BoneTransform
{
@ -44,17 +44,13 @@ public:
*/
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);
FColladaErrorHandler err (xmlErrors);
std::auto_ptr<FCDocument> doc (FCollada::NewTopDocument());
REQUIRE_SUCCESS(doc->LoadFromText("", input));
FCDSceneNode* root = doc->GetVisualSceneRoot();
REQUIRE(root != NULL, "has root object");
// Find the instance to convert
FCDEntityInstance* instance;
@ -65,28 +61,33 @@ public:
assert(instance);
Log(LOG_INFO, "Converting '%s'", instance->GetEntity()->GetName().c_str());
if (instance->GetEntity()->GetType() == FCDEntity::CONTROLLER)
if (instance->GetType() == FCDEntityInstance::CONTROLLER)
{
FCDController* controller = (FCDController*)instance->GetEntity();
REQUIRE(controller->HasSkinController(), "has skin controller");
FCDSkinController* skin = controller->GetSkinController();
FCDControllerInstance* controllerInstance = (FCDControllerInstance*)instance;
// Find the first and last times which have animations
// TODO: use the FCOLLADA start_time/end_time where available
float timeStart = std::numeric_limits<float>::max();
float timeEnd = -std::numeric_limits<float>::max();
for (size_t i = 0; i < skin->GetJointCount(); ++i)
for (size_t i = 0; i < controllerInstance->GetJointCount(); ++i)
{
FCDJointMatrixPair* joint = skin->GetJoint(i);
REQUIRE(joint->joint != NULL, "joint exists");
FCDSceneNode* joint = controllerInstance->GetJoint(i);
REQUIRE(joint != NULL, "joint exists");
int boneId = StdSkeletons::FindStandardBoneID(joint->GetName());
if (boneId < 0)
{
Log(LOG_WARNING, "Unrecognised bone name '%s'", joint->GetName().c_str());
continue;
}
// Skip unanimated joints
if (joint->joint->GetTransformCount() == 0)
if (joint->GetTransformCount() == 0)
continue;
REQUIRE(joint->joint->GetTransformCount() == 1, "joint has single transform");
REQUIRE(joint->GetTransformCount() == 1, "joint has single transform");
FCDTransform* transform = joint->joint->GetTransform(0);
FCDTransform* transform = joint->GetTransform(0);
// Skip unanimated joints again. (TODO: Which of these happens in practice?)
if (! transform->IsAnimated())
@ -111,6 +112,7 @@ public:
// Count frames; don't include the last keyframe
size_t frameCount = (size_t)((timeEnd - timeStart) / frameLength - 0.5f);
// (TODO: sort out the timing/looping problems)
size_t boneCount = StdSkeletons::GetBoneCount();
@ -124,18 +126,47 @@ public:
std::vector<BoneTransform> frameBoneTransforms (boneCount, boneDefault);
// Move the model into the new animated pose
for (size_t i = 0; i < skin->GetJointCount(); ++i)
for (size_t i = 0; i < controllerInstance->GetJointCount(); ++i)
{
FCDTransform* transform = skin->GetJoint(i)->joint->GetTransform(0);
FCDSceneNode* joint = controllerInstance->GetJoint(i);
int boneId = StdSkeletons::FindStandardBoneID(joint->GetName());
if (boneId < 0)
continue; // already emitted a warning earlier
FCDTransform* transform = joint->GetTransform(0);
FCDAnimated* anim = transform->GetAnimated();
anim->Evaluate(time);
}
// As well as the joints, we need to update all the ancestors
// of the skeleton (e.g. the Bip01 node, since the skeleton is
// hanging off Bip01-Pelvis instead).
// So choose an arbitrary joint, which is hopefully actually the
// top-most joint but it doesn't really matter, and evaluate all
// its ancestors;
if (controllerInstance->GetJointCount() >= 1)
{
FCDSceneNode* node = controllerInstance->GetJoint(0);
while (node->GetParentCount() == 1) // (I guess this should be true in sensible models)
{
node = node->GetParent();
if (node->IsJoint() && node->GetTransformCount() == 1)
node->GetTransform(0)->GetAnimated()->Evaluate(time);
else
break;
}
}
// Convert the pose into the form require by the game
for (size_t i = 0; i < skin->GetJointCount(); ++i)
for (size_t i = 0; i < controllerInstance->GetJointCount(); ++i)
{
FCDSceneNode* jointNode = skin->GetJoint(i)->joint;
FMMatrix44 worldTransform = jointNode->CalculateWorldTransform();
FCDSceneNode* joint = controllerInstance->GetJoint(i);
int boneId = StdSkeletons::FindStandardBoneID(joint->GetName());
if (boneId < 0)
continue; // already emitted a warning earlier
FMMatrix44 worldTransform = joint->CalculateWorldTransform();
HMatrix matrix;
memcpy(matrix, worldTransform.Transposed().m, sizeof(matrix));
@ -148,13 +179,12 @@ public:
{ 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()));
copy(frameBoneTransforms.begin(), frameBoneTransforms.end(),
inserter(boneTransforms, boneTransforms.end()));
}
// Convert into game's coordinate space

View File

@ -5,11 +5,17 @@ import xml.etree.ElementTree as ET
binaries = '../../../binaries'
# Work out the platform-dependent library filename
dll_filename = {
'posix': './libCollada_dbg.so',
'nt': 'Collada_dbg.dll',
}[os.name]
# The DLL may need other DLLs which are in its directory, so set the path to that
# (Don't care about clobbering the old PATH - it doesn't have anything important)
os.environ['PATH'] = '%s/system/' % binaries
# Load the actual library
library = cdll.LoadLibrary('%s/system/%s' % (binaries, dll_filename))
def log(severity, message):
@ -19,16 +25,22 @@ clog = CFUNCTYPE(None, c_int, c_char_p)(log)
# (the CFUNCTYPE must not be GC'd, so try to keep a reference)
library.set_logger(clog)
def convert_dae_to_pmd(filename):
def _convert_dae(func, filename, expected_status=0):
output = []
def cb(cbdata, str, len):
output.append(string_at(str, len))
cbtype = CFUNCTYPE(None, POINTER(None), POINTER(c_char), c_uint)
status = library.convert_dae_to_pmd(filename, cbtype(cb), None)
assert(status == 0)
status = func(filename, cbtype(cb), None)
assert(status == expected_status)
return ''.join(output)
def convert_dae_to_pmd(*args, **kwargs):
return _convert_dae(library.convert_dae_to_pmd, *args, **kwargs)
def convert_dae_to_psa(*args, **kwargs):
return _convert_dae(library.convert_dae_to_psa, *args, **kwargs)
def clean_dir(path):
# Remove all files first
try:
@ -43,7 +55,7 @@ def clean_dir(path):
except OSError:
pass # (ignore errors if it already exists)
def create_actor(mesh, texture, idleanim, corpseanim, gatheranim):
def create_actor(mesh, texture, anims):
actor = ET.Element('actor', version='1')
ET.SubElement(actor, 'castshadow')
group = ET.SubElement(actor, 'group')
@ -51,21 +63,41 @@ def create_actor(mesh, texture, idleanim, corpseanim, gatheranim):
ET.SubElement(variant, 'mesh').text = mesh+'.pmd'
ET.SubElement(variant, 'texture').text = texture+'.dds'
animations = ET.SubElement(variant, 'animations')
ET.SubElement(animations, 'animation', file=idleanim+'.psa', name='Idle', speed='100')
ET.SubElement(animations, 'animation', file=corpseanim+'.psa', name='Corpse', speed='100')
ET.SubElement(animations, 'animation', file=gatheranim+'.psa', name='Build', speed='100')
for name, file in anims:
ET.SubElement(animations, 'animation', file=file+'.psa', name=name, speed='100')
return ET.tostring(actor)
def create_actor_static(mesh, texture):
actor = ET.Element('actor', version='1')
ET.SubElement(actor, 'castshadow')
group = ET.SubElement(actor, 'group')
variant = ET.SubElement(group, 'variant', frequency='100', name='Base')
ET.SubElement(variant, 'mesh').text = mesh+'.pmd'
ET.SubElement(variant, 'texture').text = texture+'.dds'
return ET.tostring(actor)
################################
# Error handling
convert_dae_to_pmd('This is not well-formed XML', expected_status=-2)
convert_dae_to_pmd('<html>This is not COLLADA</html>', expected_status=-2)
convert_dae_to_pmd('<COLLADA>This is still not valid COLLADA</COLLADA>', expected_status=-2)
# Do some real conversions, so the output can be tested in the Actor Viewer
test_data = binaries + '/data/tests/collada'
test_mod = binaries + '/data/mods/_test.collada'
clean_dir(test_mod + '/art/meshes')
clean_dir(test_mod + '/art/actors')
clean_dir(test_mod + '/art/animation')
for test_file in ['cube', 'jav2', 'jav2b', 'teapot_basic', 'teapot_skin', 'plane_skin', 'dude_skin', 'mergenonbone', 'densemesh']:
print "* Converting PMD %s" % (test_file)
#for test_file in ['cube', 'jav2', 'teapot_basic', 'teapot_skin', 'plane_skin', 'dude_skin']:
for test_file in ['sphere']:
input_filename = '%s/%s.dae' % (test_data, test_file)
output_filename = '%s/art/meshes/%s.pmd' % (test_mod, test_file)
@ -73,5 +105,19 @@ for test_file in ['sphere']:
output = convert_dae_to_pmd(input)
open(output_filename, 'wb').write(output)
xml = create_actor(test_file, 'male', 'dudeidle', 'dudecorpse', 'dudechop')
xml = create_actor(test_file, 'male', [('Idle','dudeidle'),('Corpse','dudecorpse'),('Melee','jav2b')])
open('%s/art/actors/%s.xml' % (test_mod, test_file), 'w').write(xml)
xml = create_actor_static(test_file, 'male')
open('%s/art/actors/%s_static.xml' % (test_mod, test_file), 'w').write(xml)
for test_file in ['jav2b']:
print "* Converting PSA %s" % (test_file)
input_filename = '%s/%s.dae' % (test_data, test_file)
output_filename = '%s/art/animation/%s.psa' % (test_mod, test_file)
input = open(input_filename).read()
output = convert_dae_to_psa(input)
open(output_filename, 'wb').write(output)