From 5d8bef5f6e42e2ad4455b8e76348d861ed54073e Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Tue, 12 Dec 2006 02:11:24 +0000 Subject: [PATCH] Semi-broken skeletally-animated model COLLADA->PMD converter. This was SVN commit r4691. --- source/collada/Converter.cpp | 330 +++++++++++++++++++++++++++--- source/collada/Maths.cpp | 48 +++++ source/collada/Maths.h | 8 + source/collada/StdSkeletons.cpp | 60 ++++++ source/collada/StdSkeletons.h | 10 + source/collada/precompiled.h | 4 +- source/collada/tests/tests.py | 27 ++- source/graphics/Model.cpp | 22 +- source/renderer/ModelRenderer.cpp | 14 +- 9 files changed, 475 insertions(+), 48 deletions(-) create mode 100644 source/collada/Maths.cpp create mode 100644 source/collada/Maths.h create mode 100644 source/collada/StdSkeletons.cpp create mode 100644 source/collada/StdSkeletons.h diff --git a/source/collada/Converter.cpp b/source/collada/Converter.cpp index 5f38ba65dc..4d7b13bd2b 100644 --- a/source/collada/Converter.cpp +++ b/source/collada/Converter.cpp @@ -4,17 +4,23 @@ #include "FCollada.h" #include "FCDocument/FCDocument.h" -#include "FCDocument/FCDSceneNode.h" +#include "FCDocument/FCDController.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 #include /** Throws a ColladaException unless the value is true. */ -#define REQUIRE(value, message) require_(__LINE__, value, "Failed assertion", message) +#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) @@ -37,16 +43,19 @@ template void write(OutputFn output, const T& data) output((char*)&data, sizeof(T)); } +const int maxInfluences = 4; struct VertexBlend { - uint8 bones[4]; - float weights[4]; + uint8 bones[maxInfluences]; + float weights[maxInfluences]; }; +VertexBlend defaultInfluences = { { 0xFF, 0xFF, 0xFF, 0xFF }, { 0, 0, 0, 0 } }; struct BoneTransform { float translation[3]; float orientation[4]; + FMMatrix44 matrix; // not output in the PMD, but useful for calculating bone transforms }; @@ -82,7 +91,7 @@ public: // 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); + xmlSetGenericErrorFunc(&xmlErrors, &errorHandler); std::auto_ptr doc (FCollada::NewTopDocument()); REQUIRE_SUCCESS(doc->LoadFromText("", input)); @@ -96,42 +105,135 @@ public: throw ColladaException("Couldn't find object to convert"); assert(instance); - Log(LOG_INFO, "Converting '%s'", instance->GetEntity()->GetName().c_str()); + Log(LOG_INFO, "Converting '%s'", instance->GetEntity()->GetName().c_str()); if (instance->GetEntity()->GetType() == FCDEntity::GEOMETRY) { Log(LOG_INFO, "Found static geometry"); - FCDGeometry* geom = (FCDGeometry*)instance->GetEntity(); - REQUIRE(geom->IsMesh(), "geometry is mesh"); - FCDGeometryMesh* mesh = geom->GetMesh(); - REQUIRE(mesh->IsTriangles(), "mesh is made of triangles"); - REQUIRE(mesh->GetPolygonsCount() == 1, "mesh has single set of polygons"); - FCDGeometryPolygons* polys = mesh->GetPolygons(0); + FCDGeometryPolygons* polys = GetPolysFromGeometry((FCDGeometry*)instance->GetEntity()); size_t vertices = polys->GetFaceVertexCount(); FloatList position, normal, texcoord; + DeindexInput(polys, FUDaeGeometryInput::POSITION, position, 3); DeindexInput(polys, FUDaeGeometryInput::NORMAL, normal, 3); if (polys->FindInput(FUDaeGeometryInput::TEXCOORD)) - { DeindexInput(polys, FUDaeGeometryInput::TEXCOORD, texcoord, 2); - } - else - { - // Accept untextured models + else // Accept untextured models texcoord.resize(vertices*2, 0.f); - } assert(position.size() == vertices*3); assert(normal.size() == vertices*3); assert(texcoord.size() == vertices*2); - TransformVertices(position, normal, transform); + TransformVertices(position, normal, transform); // TODO: optimise at least enough to merge identical vertices - - WritePMD(output, vertices, 0, &position[0], &normal[0], &texcoord[0], NULL, NULL); + + WritePMD(output, vertices, 0, &position[0], &normal[0], &texcoord[0], NULL, NULL); + } + else if (instance->GetEntity()->GetType() == FCDEntity::CONTROLLER) + { + FCDController* controller = (FCDController*)instance->GetEntity(); + + REQUIRE(controller->HasSkinController(), "has skin controller"); + FCDSkinController* skin = controller->GetSkinController(); + + // Get the skinned mesh for this entity + FCDEntity* baseTarget = controller->GetBaseTarget(); + REQUIRE(baseTarget->GetType() == FCDEntity::GEOMETRY, "base target is geometry"); + FCDGeometryPolygons* polys = GetPolysFromGeometry((FCDGeometry*)baseTarget); + + // Make sure it doesn't use more bones per vertex than the game can handle +// SkinReduceInfluences(skin, maxInfluences, 0.001f); + + // XXX The game is broken if there's >1 influence per vertex. Until + // that's fixed, just limit it to 1 and put up with the ugly blending... + SkinReduceInfluences(skin, 1, 0.001f); + + // Convert the bone influences into VertexBlend structures for the PMD + + std::vector vertexBlends; // one per vertex + + const FCDWeightedMatches& boneWeights = skin->GetVertexInfluences(); + for (size_t i = 0; i < boneWeights.size(); ++i) + { + VertexBlend influences = defaultInfluences; + + assert(boneWeights[i].size() <= maxInfluences); // guaranteed by ReduceInfluences + for (size_t j = 0; j < boneWeights[i].size(); ++j) + { + uint32 jointIdx = boneWeights[i][j].jointIndex; + REQUIRE(jointIdx <= 0xFF, "sensible number of joints"); + FCDSceneNode* joint = skin->GetJoint(jointIdx)->joint; + if (! joint) + { + Log(LOG_WARNING, "Vertexes influenced by nonexistent joint"); + continue; + } + int boneId = StdSkeletons::FindStandardBoneID(joint->GetName()); + REQUIRE(boneId >= 0, "recognised bone name"); + influences.bones[j] = (uint8)boneId; + influences.weights[j] = boneWeights[i][j].weight; + } + + vertexBlends.push_back(influences); + } + + BoneTransform boneDefault = { { 0, 0, 0 }, { 0, 0, 0, 1 } }; + std::vector bones (StdSkeletons::GetBoneCount(), boneDefault); + + transform = skin->GetBindShapeTransform(); + + for (size_t i = 0; i < skin->GetJointCount(); ++i) + { + FCDJointMatrixPair* joint = skin->GetJoint(i); + + if (! joint->joint) + { + Log(LOG_WARNING, "Skin has nonexistent joint"); + continue; + } + + FMMatrix44 bindPose = joint->invertedBindPose.Inverted(); + + HMatrix matrix; + memcpy(matrix, bindPose.Transposed().m, sizeof(matrix)); + // matrix = bindPose^T, to match what decomp_affine wants + + 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 }, + bindPose + }; + + int boneId = StdSkeletons::FindStandardBoneID(joint->joint->GetName()); + REQUIRE(boneId >= 0, "recognised bone name"); + bones[boneId] = b; + } + + size_t vertices = polys->GetFaceVertexCount(); + FloatList position, normal, texcoord; + std::vector blends; + DeindexInput(polys, FUDaeGeometryInput::POSITION, position, 3); + DeindexInput(polys, vertexBlends, blends); + DeindexInput(polys, FUDaeGeometryInput::NORMAL, normal, 3); + if (polys->FindInput(FUDaeGeometryInput::TEXCOORD)) + DeindexInput(polys, FUDaeGeometryInput::TEXCOORD, texcoord, 2); + else // Accept untextured models + texcoord.resize(vertices*2, 0.f); + + assert(position.size() == vertices*3); + assert(normal.size() == vertices*3); + assert(texcoord.size() == vertices*2); + + TransformVertices(position, normal, blends, bones, transform); + + WritePMD(output, vertices, bones.size(), &position[0], &normal[0], &texcoord[0], &blends[0], &bones[0]); } else { @@ -189,24 +291,33 @@ public: write(output, (uint32)boneCount); for (size_t i = 0; i < boneCount; ++i) { - write(output, boneTransforms[i]); + output((char*)&boneTransforms[i], 7*4); } // Prop points data write(output, 0); } + static FCDGeometryPolygons* GetPolysFromGeometry(FCDGeometry* geom) + { + REQUIRE(geom->IsMesh(), "geometry is mesh"); + FCDGeometryMesh* mesh = geom->GetMesh(); + REQUIRE(mesh->IsTriangles(), "mesh is made of triangles"); + REQUIRE(mesh->GetPolygonsCount() == 1, "mesh has single set of polygons"); + return mesh->GetPolygons(0); + } + /** * Converts from value-array plus indexes-into-array-per-vertex, into * values-per-vertex (because that's what PMD wants). */ - static void DeindexInput(FCDGeometryPolygons* polys, FUDaeGeometryInput::Semantic semantic, FloatList& out, size_t outStride) + static void DeindexInput(const FCDGeometryPolygons* polys, FUDaeGeometryInput::Semantic semantic, FloatList& out, size_t outStride) { - FCDGeometryPolygonsInput* input = polys->FindInput(semantic); - UInt32List* indices = polys->FindIndices(input); + const FCDGeometryPolygonsInput* input = polys->FindInput(semantic); + const UInt32List* indices = polys->FindIndices(input); REQUIRE(input && indices, "has expected polygon input"); - FCDGeometrySource* source = input->GetSource(); - FloatList& data = source->GetSourceData(); + const FCDGeometrySource* source = input->GetSource(); + const FloatList& data = source->GetSourceData(); size_t stride = source->GetSourceStride(); for (size_t i = 0; i < indices->size(); ++i) @@ -214,6 +325,18 @@ public: out.push_back(data[(*indices)[i]*stride + j]); } + static void DeindexInput(const FCDGeometryPolygons* polys, const std::vector& inBlends, std::vector& outBlends) + { + assert(outBlends.empty()); + + const FCDGeometryPolygonsInput* input = polys->FindInput(FUDaeGeometryInput::POSITION); + const UInt32List* indices = polys->FindIndices(input); + + for (size_t i = 0; i < indices->size(); ++i) + outBlends.push_back(inBlends[(*indices)[i]]); + } + + /** * Applies world-space transform to vertex data, and flips into other-handed * coordinate space. @@ -241,6 +364,86 @@ public: } } + static void TransformVertices(FloatList& position, FloatList& normal, std::vector& blends, std::vector& bones, const FMMatrix44& transform) + { + for (size_t vtxId = 0; vtxId < position.size()/3; ++vtxId) + { + // Skinned vertices need to be transformed by the inverse of their + // rest states: + + float zero16[16] = {0}; + FMMatrix44 bindPoseTransform (zero16); + + // Calculate the weighted sum of influence matrices + for (size_t j = 0; j < maxInfluences; ++j) + { + // Ignore unused bone influences + if (blends[vtxId].bones[j] == 0xff) + continue; + + float weight = blends[vtxId].weights[j]; + const BoneTransform& b = bones[blends[vtxId].bones[j]]; + + // The transformation matrix could be reconstructed with: + /* + FMMatrix44 R = QuatToMatrix(b.orientation[0], b.orientation[1], b.orientation[2], b.orientation[3]); + FMMatrix44 T = FMMatrix44::TranslationMatrix(FMVector3(b.translation, 0)); + FMMatrix44 boneMatrix = T * R; + */ + // but since we generated orientation/translation from a matrix, + // just use that matrix directly: + FMMatrix44 boneMatrix = b.matrix; + + bindPoseTransform = bindPoseTransform + weight * boneMatrix; + } + + FMVector3 pos (&position[vtxId*3], 0); + FMVector3 norm (&normal[vtxId*3], 0); + + // Apply the scene-node transforms first + pos = transform.TransformCoordinate(pos); + norm = transform.TransformVector(norm).Normalize(); + + // Apply the inverse bind pose transform, so the model will display + // correctly after it's been transform back again + FMMatrix44 bindPoseTransformInverse = bindPoseTransform.Inverted(); + pos = bindPoseTransformInverse.TransformCoordinate(pos); + norm = bindPoseTransformInverse.TransformVector(norm); + + // Switch from Max's coordinate system into the game's: + + std::swap(pos.y, pos.z); + std::swap(norm.y, norm.z); + + // and copy back into the original array + + position[vtxId*3+0] = pos.x; + position[vtxId*3+1] = pos.y; + position[vtxId*3+2] = pos.z; + + normal[vtxId*3+0] = norm.x; + normal[vtxId*3+1] = norm.y; + normal[vtxId*3+2] = norm.z; + } + + // We also need to change the bone rest states into the new coordinate + // system, so it'll look correct when displayed without any animation + // applied to the skeleton. + for (size_t i = 0; i < bones.size(); ++i) + { + // Convert bone translations from xyz into xzy axes: + std::swap(bones[i].translation[1], bones[i].translation[2]); + + // To convert the quaternions: imagine you're using the axis/angle + // representation, then swap the y,z basis vectors and change the + // direction of rotation by negating the angle ( => negating sin(angle) + // => negating x,y,z => changing (x,y,z,w) to (-x,-z,-y,w) + // but then (-x,-z,-y,w) == (x,z,y,-w) so do that instead) + std::swap(bones[i].orientation[1], bones[i].orientation[2]); + bones[i].orientation[3] = -bones[i].orientation[3]; + } + } + ////////////////////////////////////////////////////////////////////////// struct FoundInstance @@ -317,12 +520,85 @@ public: 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(); + } + }; diff --git a/source/collada/Maths.cpp b/source/collada/Maths.cpp new file mode 100644 index 0000000000..ac47f6cfcc --- /dev/null +++ b/source/collada/Maths.cpp @@ -0,0 +1,48 @@ +#include "precompiled.h" + +#include "Maths.h" + +FMMatrix44 operator+ (const FMMatrix44& a, const FMMatrix44& b) +{ + FMMatrix44 r; + for (int x = 0; x < 4; ++x) + for (int y = 0; y < 4; ++y) + r[x][y] = a[x][y] + b[x][y]; + return r; +} + +FMMatrix44 operator/ (const FMMatrix44& a, const float b) +{ + FMMatrix44 r; + for (int x = 0; x < 4; ++x) + for (int y = 0; y < 4; ++y) + r[x][y] = a[x][y] / b; + return r; +} + +FMMatrix44 QuatToMatrix(float x, float y, float z, float w) +{ + FMMatrix44 r; + + r[0][0] = 1.0f - (y*y*2 + z*z*2); + r[1][0] = x*y*2 - w*z*2; + r[2][0] = x*z*2 + w*y*2; + r[3][0] = 0; + + r[0][1] = x*y*2 + w*z*2; + r[1][1] = 1.0f - (x*x*2 + z*z*2); + r[2][1] = y*z*2 - w*x*2; + r[3][1] = 0; + + r[0][2] = x*z*2 - w*y*2; + r[1][2] = y*z*2 + w*x*2; + r[2][2] = 1.0f - (x*x*2 + y*y*2); + r[3][2] = 0; + + r[0][3] = 0; + r[1][3] = 0; + r[2][3] = 0; + r[3][3] = 1; + + return r; +} diff --git a/source/collada/Maths.h b/source/collada/Maths.h new file mode 100644 index 0000000000..7427fd9b13 --- /dev/null +++ b/source/collada/Maths.h @@ -0,0 +1,8 @@ +#ifndef MATHS_H__ +#define MATHS_H__ + +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); + +#endif // MATHS_H__ diff --git a/source/collada/StdSkeletons.cpp b/source/collada/StdSkeletons.cpp new file mode 100644 index 0000000000..97aeee5bdf --- /dev/null +++ b/source/collada/StdSkeletons.cpp @@ -0,0 +1,60 @@ +#include "precompiled.h" + +#include "StdSkeletons.h" + +namespace +{ + const char* standardBoneNames[] = { + /* */ "Bip01", + /* */ "Bip01_Pelvis", + /* */ "Bip01_Spine", + /* */ "Bip01_Spine1", + /* */ "Bip01_Neck", + /* */ "Bip01_Head", + /* */ "Bip01_HeadNub", + /* */ "Bip01_L_Clavicle", + /* */ "Bip01_L_UpperArm", + /* */ "Bip01_L_Forearm", + /* */ "Bip01_L_Hand", + /* */ "Bip01_L_Finger0", + /* */ "Bip01_L_Finger0Nub", + /* */ "Bip01_R_Clavicle", + /* */ "Bip01_R_UpperArm", + /* */ "Bip01_R_Forearm", + /* */ "Bip01_R_Hand", + /* */ "Bip01_R_Finger0", + /* */ "Bip01_R_Finger0Nub", + /* */ "Bip01_L_Thigh", + /* */ "Bip01_L_Calf", + /* */ "Bip01_L_Foot", + /* */ "Bip01_L_Toe0", + /* */ "Bip01_L_Toe0Nub", + /* */ "Bip01_R_Thigh", + /* */ "Bip01_R_Calf", + /* */ "Bip01_R_Foot", + /* */ "Bip01_R_Toe0", + /* */ "Bip01_R_Toe0Nub", + // (the above comments just stop the indentation being dropped by + // automatic code-formatting things...) + NULL + }; +} + +namespace StdSkeletons +{ + int GetBoneCount() + { + int i = 0; + while (standardBoneNames[i] != NULL) + ++i; + return i; + } + + int FindStandardBoneID(const std::string& name) + { + for (int i = 0; standardBoneNames[i] != NULL; ++i) + if (standardBoneNames[i] == name) + return i; + return -1; + } +} diff --git a/source/collada/StdSkeletons.h b/source/collada/StdSkeletons.h new file mode 100644 index 0000000000..5df43e30e3 --- /dev/null +++ b/source/collada/StdSkeletons.h @@ -0,0 +1,10 @@ +#ifndef STDSKELETONS_H__ +#define STDSKELETONS_H__ + +namespace StdSkeletons +{ + extern int GetBoneCount(); + extern int FindStandardBoneID(const std::string& name); +} + +#endif // STDSKELETONS_H__ diff --git a/source/collada/precompiled.h b/source/collada/precompiled.h index 8db20838bd..8ac1fd80d8 100644 --- a/source/collada/precompiled.h +++ b/source/collada/precompiled.h @@ -11,8 +11,10 @@ extern void Log(int severity, const char* fmt, ...); #include "FCollada.h" #include "FCDocument/FCDocument.h" -#include "FCDocument/FCDSceneNode.h" +#include "FCDocument/FCDController.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" diff --git a/source/collada/tests/tests.py b/source/collada/tests/tests.py index 2a968ebf1d..46b0edc84f 100644 --- a/source/collada/tests/tests.py +++ b/source/collada/tests/tests.py @@ -7,7 +7,7 @@ binaries = '../../../binaries' dll_filename = { 'posix': './libCollada_dbg.so', - 'nt': 'Collada.dll', + 'nt': 'Collada_dbg.dll', }[os.name] library = cdll.LoadLibrary('%s/system/%s' % (binaries, dll_filename)) @@ -42,6 +42,19 @@ def clean_dir(path): os.makedirs(path) except OSError: pass # (ignore errors if it already exists) + +def create_actor(mesh, texture, idleanim, corpseanim, gatheranim): + 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' + 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') + return ET.tostring(actor) ################################ @@ -51,7 +64,8 @@ test_mod = binaries + '/data/mods/_test.collada' clean_dir(test_mod + '/art/meshes') clean_dir(test_mod + '/art/actors') -for test_file in ['cube', 'jav2']: +for test_file in ['cube', 'jav2', 'teapot_basic', 'teapot_skin', 'plane_skin', 'dude_skin']: +#for test_file in ['dude_skin']: input_filename = '%s/%s.dae' % (test_data, test_file) output_filename = '%s/art/meshes/%s.pmd' % (test_mod, test_file) @@ -59,10 +73,5 @@ for test_file in ['cube', 'jav2']: output = convert_dae_to_pmd(input) open(output_filename, 'wb').write(output) - actor = ET.Element('actor', version='1') - group = ET.SubElement(actor, 'group') - variant = ET.SubElement(group, 'variant', frequency='100', name='Base') - ET.SubElement(variant, 'mesh').text = test_file+'.pmd' - ET.SubElement(variant, 'texture').text = 'male.dds' - - open('%s/art/actors/%s.xml' % (test_mod, test_file), 'w').write(ET.tostring(actor)) + xml = create_actor(test_file, 'male', 'dudeidle', 'dudecorpse', 'dudechop') + open('%s/art/actors/%s.xml' % (test_mod, test_file), 'w').write(xml) diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp index 93c7724c03..b074ddd727 100644 --- a/source/graphics/Model.cpp +++ b/source/graphics/Model.cpp @@ -108,22 +108,26 @@ bool CModel::InitModel(CModelDefPtr modeldef) ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // SkinPoint: skin the given point using the given blend and matrix data -static CVector3D SkinPoint(const CVector3D& pos,const SVertexBlend& blend, +static CVector3D SkinPoint(const CVector3D& pos, const SVertexBlend& blend, const CMatrix3D* bonestates) { CVector3D result,tmp; // must have at least one valid bone if we're using SkinPoint - debug_assert(blend.m_Bone[0]!=0xff); + if (blend.m_Bone[0] == 0xff) + { + ONCE( debug_warn("SkinPoint called for vertex with no bone weights") ); + return CVector3D(0, 0, 0); + } - const CMatrix3D& m=bonestates[blend.m_Bone[0]]; - m.Transform(pos,result); - result*=blend.m_Weight[0]; + const CMatrix3D& m = bonestates[blend.m_Bone[0]]; + m.Transform(pos, result); + result *= blend.m_Weight[0]; - for (int i=1;i