Semi-broken skeletally-animated model COLLADA->PMD converter.

This was SVN commit r4691.
This commit is contained in:
Ykkrosh 2006-12-12 02:11:24 +00:00
parent 207d54e825
commit 5d8bef5f6e
9 changed files with 475 additions and 48 deletions

View File

@ -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 <cassert>
#include <vector>
/** 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<typename T> 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<FCDocument> 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<VertexBlend> 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<BoneTransform> 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<VertexBlend> 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<uint32>(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<uint32>(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<VertexBlend>& inBlends, std::vector<VertexBlend>& 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<VertexBlend>& blends, std::vector<BoneTransform>& 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();
}
};

48
source/collada/Maths.cpp Normal file
View File

@ -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;
}

8
source/collada/Maths.h Normal file
View File

@ -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__

View File

@ -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;
}
}

View File

@ -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__

View File

@ -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"

View File

@ -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)

View File

@ -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<SVertexBlend::SIZE && blend.m_Bone[i]!=0xff;i++) {
const CMatrix3D& m=bonestates[blend.m_Bone[i]];
m.Transform(pos,tmp);
result+=tmp*blend.m_Weight[i];
for (int i = 1; i < SVertexBlend::SIZE && blend.m_Bone[i] != 0xff; i++) {
const CMatrix3D& m = bonestates[blend.m_Bone[i]];
m.Transform(pos, tmp);
result += tmp*blend.m_Weight[i];
}
return result;

View File

@ -39,7 +39,12 @@ static void SkinPoint(const SModelVertex& vertex,const CMatrix3D* matrices,CVect
const SVertexBlend& blend=vertex.m_Blend;
// 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)
{
// (CModel should have already complained about this)
result = CVector3D(0, 0, 0);
return;
}
const CMatrix3D& m=matrices[blend.m_Bone[0]];
m.Transform(vertex.m_Coords,result);
@ -60,7 +65,12 @@ static void SkinNormal(const SModelVertex& vertex, const CMatrix3D* invtranspmat
const SVertexBlend& blend=vertex.m_Blend;
// must have at least one valid bone if we're using SkinNormal
debug_assert(blend.m_Bone[0]!=0xff);
if (blend.m_Bone[0] == 0xff)
{
// (CModel should have already complained about this)
result = CVector3D(0, 0, 0);
return;
}
const CMatrix3D& m = invtranspmatrices[blend.m_Bone[0]];
m.Rotate(vertex.m_Norm, result);