Files
bullet3/Extras/FCollada/FCDocument/FCDSkinController.cpp
2006-05-25 19:18:29 +00:00

613 lines
23 KiB
C++

/*
Copyright (C) 2005-2006 Feeling Software Inc.
MIT License: http://www.opensource.org/licenses/mit-license.php
*/
/*
Based on the FS Import classes:
Copyright (C) 2005-2006 Feeling Software Inc
Copyright (C) 2005-2006 Autodesk Media Entertainment
MIT License: http://www.opensource.org/licenses/mit-license.php
*/
#include "StdAfx.h"
#include "FCDocument/FCDocument.h"
#include "FCDocument/FCDController.h"
#include "FCDocument/FCDGeometry.h"
#include "FCDocument/FCDGeometryMesh.h"
#include "FCDocument/FCDGeometryPolygons.h"
#include "FCDocument/FCDGeometrySource.h"
#include "FCDocument/FCDGeometrySpline.h"
#include "FCDocument/FCDSceneNode.h"
#include "FCDocument/FCDSkinController.h"
#include "FUtils/FUStringConversion.h"
#include "FUtils/FUDaeParser.h"
#include "FUtils/FUDaeWriter.h"
using namespace FUDaeParser;
using namespace FUDaeWriter;
FCDSkinController::FCDSkinController(FCDocument* document, FCDController* _parent) : FCDObject(document, "FCDSkinController")
{
parent = _parent;
target = NULL;
ownsTarget = false;
bindShapeTransform = FMMatrix44::Identity;
}
FCDSkinController::~FCDSkinController()
{
if (ownsTarget) { SAFE_DELETE(target); }
else target = NULL;
parent = NULL;
}
void FCDSkinController::SetTarget(FCDEntity* _target)
{
target = NULL;
weightedMatches.clear();
// Retrieve the actual base entity, as you can chain controllers.
FCDEntity* baseEntity = _target;
while (baseEntity != NULL && baseEntity->GetType() == FCDEntity::CONTROLLER)
{
baseEntity = ((FCDController*) baseEntity)->GetBaseTarget();
}
if (baseEntity == NULL || baseEntity->GetType() != FCDEntity::GEOMETRY)
{
// The new target is no good!
return;
}
target = _target;
FCDGeometry* geometry = (FCDGeometry*) baseEntity;
// Retrieve the new vertex count
size_t vertexCount = 0;
if (geometry->IsMesh())
{
FCDGeometryMesh* mesh = geometry->GetMesh();
FCDGeometrySource* positionSource = mesh->GetPositionSource();
if (positionSource != NULL)
{
vertexCount = positionSource->GetSourceData().size() / positionSource->GetSourceStride();
}
}
else if (geometry->IsSpline())
{
FCDGeometrySpline* spline = geometry->GetSpline();
vertexCount = spline->GetCVCount();
}
// Modify the list of influences to match the new target's vertex count.
weightedMatches.resize(vertexCount);
}
// Adds a joint and its bind-pose to the list of joint influencing the skin.
void FCDSkinController::AddJoint(FCDSceneNode* joint, const FMMatrix44& bindPose)
{
FCDJointMatrixPair pair;
pair.joint = joint;
pair.invertedBindPose = bindPose.Inverted();
joints.push_back(pair);
// Also set the scene node's joint flag.
joint->SetJointFlag(true);
}
// Removes a joint from the list of joints influencing the skin.
void FCDSkinController::RemoveJoint(FCDSceneNode* joint)
{
// Find the joint within the joint list.
size_t index = 0;
for (index = 0; index < joints.size(); ++index)
{
if (joints[index].joint == joint) break;
}
if (index == joints.size()) return;
// Look through the per-vertex influences and remove the influences
// that use this joint. Also, shift down the influences that use
// the joints that appear after this one in the list.
for (FCDWeightedMatches::iterator itM = weightedMatches.begin(); itM != weightedMatches.end(); ++itM)
{
FCDJointWeightPairList& pairs = (*itM);
for (FCDJointWeightPairList::iterator itP = pairs.begin(); itP != pairs.end();)
{
FCDJointWeightPair& p = (*itP);
if (p.jointIndex == index)
{
pairs.erase(itP);
}
else if (p.jointIndex > index)
{
--p.jointIndex;
++itP;
}
else
{
++itP;
}
}
}
}
// Look for the information on a given joint
FCDJointMatrixPair* FCDSkinController::FindJoint(FCDSceneNode* joint)
{
if (joint == NULL) return NULL;
for (FCDJointList::iterator itJ = joints.begin(); itJ != joints.end(); ++itJ)
{
if ((*itJ).joint == joint) return &(*itJ);
}
return NULL;
}
const FCDJointMatrixPair* FCDSkinController::FindJoint(const FCDSceneNode* joint) const
{
if (joint == NULL) return NULL;
for (FCDJointList::const_iterator itJ = joints.begin(); itJ != joints.end(); ++itJ)
{
if ((*itJ).joint == joint) return &(*itJ);
}
return NULL;
}
// Reduce the number of joints influencing each vertex to a maximum count
void FCDSkinController::ReduceInfluences(uint32 maxInfluenceCount, float minimumWeight)
{
// Pre-cache an empty weight list to the reduced count
FCDJointWeightPairList reducedWeights;
reducedWeights.reserve(maxInfluenceCount + 1);
for (FCDWeightedMatches::iterator itM = weightedMatches.begin(); itM != weightedMatches.end(); ++itM)
{
FCDJointWeightPairList& weights = (*itM);
size_t oldWeightCount = weights.size();
// Reduce the weights, keeping only the more important ones using a sorting algorithm.
// Also, calculate the current total of the weights, to re-normalize the reduced weights
float oldTotal = 0.0f;
reducedWeights.clear();
for (FCDJointWeightPairList::iterator itW = weights.begin(); itW != weights.end(); ++itW)
{
FCDJointWeightPairList::iterator itRW = reducedWeights.begin();
if ((*itW).weight >= minimumWeight)
{
for (; itRW != reducedWeights.end() && (*itRW).weight < (*itW).weight; ++itRW) {}
if (itRW != reducedWeights.end() || reducedWeights.size() <= maxInfluenceCount)
{
reducedWeights.insert(itRW, (*itW));
if (reducedWeights.size() > maxInfluenceCount) reducedWeights.pop_back();
}
}
oldTotal += (*itW).weight;
}
if (oldWeightCount > reducedWeights.size())
{
// Replace the old weights and re-normalize to their old total
weights = reducedWeights;
float newTotal = 0.0f;
for (FCDJointWeightPairList::iterator itW = weights.begin(); itW != weights.end(); ++itW) newTotal += (*itW).weight;
float renormalizingFactor = oldTotal / newTotal;
for (FCDJointWeightPairList::iterator itW = weights.begin(); itW != weights.end(); ++itW) (*itW).weight *= renormalizingFactor;
}
}
}
// Load this controller from a COLLADA <controller> node
FUStatus FCDSkinController::LoadFromXML(xmlNode* skinNode)
{
FUStatus status;
if (!IsEquivalent(skinNode->name, DAE_CONTROLLER_SKIN_ELEMENT))
{
return status.Warning(FS("Unexpected node in controller library: ") + TO_FSTRING((const char*) skinNode->name), skinNode->line);
}
// Get the <skin> element and process the inner <vertices> element
xmlNode* verticesNode = FindChildByType(skinNode, DAE_VERTICES_ELEMENT);
bool isCollada1_3 = verticesNode != NULL;
// Read in the <bind_shape_matrix> element
xmlNode* bindShapeTransformNode = FindChildByType(skinNode, DAE_BINDSHAPEMX_SKIN_PARAMETER);
if (bindShapeTransformNode == NULL) bindShapeTransform = FMMatrix44::Identity;
else
{
const char* content = ReadNodeContentDirect(bindShapeTransformNode);
FUStringConversion::ToMatrix(&content, bindShapeTransform, GetDocument()->GetLengthUnitConversion());
}
// Find the target geometry
string targetId = (isCollada1_3) ? parent->GetTargetId() : ReadNodeProperty(skinNode, DAE_SOURCE_ATTRIBUTE);
target = GetDocument()->FindGeometry(targetId);
if (target == NULL) target = GetDocument()->FindController(targetId);
if (target == NULL)
{
return status.Warning(FS("Target not found for controller: ") + TO_FSTRING(parent->GetDaeId()), skinNode->line);
}
// Retrieve the <joints> and <combiner> elements
xmlNode* jointsNode = NULL,* combinerNode = NULL,* containerNode = NULL;
if (isCollada1_3)
{
// COLLADA 1.3 backward compatibility: read in the bind-shape positions/normals
xmlNodeList vertexInputNodes;
FindChildrenByType(verticesNode, DAE_INPUT_ELEMENT, vertexInputNodes);
xmlNode* bindShapePositionSourceNode = NULL,* jointWeightSourceNode = NULL,* bindShapeNormalSourceNode = NULL;
for (xmlNodeList::iterator it = vertexInputNodes.begin(); it != vertexInputNodes.end(); ++it)
{
string semantic = ReadNodeSemantic(*it);
string sourceId = ReadNodeSource(*it);
if (semantic == DAE_BINDPOS_SKIN_INPUT) bindShapePositionSourceNode = FindChildById(skinNode, sourceId);
else if (semantic == DAE_BINDNORMAL_SKIN_INPUT) bindShapeNormalSourceNode = FindChildById(skinNode, sourceId);
else if (semantic == DAE_JOINTWEIGHT_SKIN_INPUT) jointWeightSourceNode = FindChildById(skinNode, sourceId);
else
{
status.Warning(FS("Unknown vertex input in skin controller: ") + TO_FSTRING(parent->GetDaeId()), (*it)->line);
}
}
if (jointWeightSourceNode == NULL)
{
return status.Fail(FS("Cannot find 'JOINTS_AND_WEIGHTS' input in <vertices> element for controller: ") + TO_FSTRING(parent->GetDaeId()), jointWeightSourceNode->line);
}
if (bindShapePositionSourceNode == NULL)
{
return status.Fail(FS("Cannot find 'BIND_SHAPE_POSITION' input in <vertices> element for controller: ") + TO_FSTRING(parent->GetDaeId()), jointWeightSourceNode->line);
}
// Read in the bindshape positions/normals and override the original mesh's positions/normals
FloatList bindShapePositions, bindShapeNormals;
uint32 positionStride = ReadSource(bindShapePositionSourceNode, bindShapePositions);
uint32 normalsStride = ReadSource(bindShapeNormalSourceNode, bindShapeNormals);
if (target->GetType() != FCDEntity::GEOMETRY)
{
return status.Fail(FS("COLLADA 1.3 only supports geometric bind shapes for skin controller: ") + TO_FSTRING(parent->GetDaeId()), skinNode->line);
}
target = ((FCDGeometry*)target)->Clone(bindShapePositions, positionStride, bindShapeNormals, normalsStride);
ownsTarget = true;
// Retrieve the <joints> element and the <combiner> element
xmlNode* jointWeightTechniqueNode = FindTechnique(jointWeightSourceNode, DAE_COMMON_PROFILE);
jointsNode = FindChildByType(jointWeightTechniqueNode, DAE_JOINTS_ELEMENT);
combinerNode = FindChildByType(jointWeightTechniqueNode, DAE_COMBINER_ELEMENT);
containerNode = jointWeightSourceNode;
}
else
{
// COLLADA 1.4: use the target geometry directly as the bind-shape.
// Retrieve the <joints> element and the <vertex_weights> element
jointsNode = FindChildByType(skinNode, DAE_JOINTS_ELEMENT);
combinerNode = FindChildByType(skinNode, DAE_WEIGHTS_ELEMENT);
}
// Verify that we have the necessary data structures: bind-shape, <joints> elements, <combiner> element
if (target == NULL)
{
return status.Warning(FS("Unable to clone/find the target geometry for controller: ") + TO_FSTRING(parent->GetDaeId()), skinNode->line);
}
if (jointsNode == NULL)
{
return status.Fail(FS("No <joints> element found in the skin weight source for controller: ") + TO_FSTRING(parent->GetDaeId()), skinNode->line);
}
if (combinerNode == NULL)
{
return status.Fail(FS("No <combiner> node found in the skin weight source for controller: ") + TO_FSTRING(parent->GetDaeId()), skinNode->line);
}
// Gather the inputs for the <joints> element and the <combiner> element
xmlNode* firstCombinerValueNode = NULL;
xmlNodeList skinningInputNodes;
FindChildrenByType(jointsNode, DAE_INPUT_ELEMENT, skinningInputNodes);
uint32 combinerValueCount = ReadNodeCount(combinerNode);
for (xmlNode* child = combinerNode->children; child != NULL; child = child->next)
{
if (child->type != XML_ELEMENT_NODE) continue;
if (IsEquivalent(child->name, DAE_INPUT_ELEMENT)) skinningInputNodes.push_back(child);
else if (IsEquivalent(child->name, DAE_VERTEX_ELEMENT) || IsEquivalent(child->name, DAE_VERTEXCOUNT_ELEMENT))
{
firstCombinerValueNode = child;
break;
}
}
if (firstCombinerValueNode == NULL)
{
return status.Fail(FS("Unable to find <combiner> element's value nodes for controller: ") + TO_FSTRING(parent->GetDaeId()), combinerNode->line);
}
// Process these inputs
FloatList weights;
FMMatrix44List invertedBindPoses;
int32 jointIdx = 0, weightIdx = 1;
for (xmlNodeList::iterator it = skinningInputNodes.begin(); it != skinningInputNodes.end(); ++it)
{
string semantic = ReadNodeSemantic(*it);
string sourceId = ReadNodeSource(*it);
const char* arrayContent = NULL;
uint32 count = 0;
xmlNode* sourceNode = NULL;
if (isCollada1_3)
{
// COLLADA 1.3 backward compatibility: retrieve the <accessor> node and its <array>
xmlNode* techniqueNode = FindTechnique(containerNode, DAE_COMMON_PROFILE);
xmlNode* accessorNode = FindChildById(techniqueNode, sourceId);
string arrayId = ReadNodeSource(accessorNode);
xmlNode* arrayNode = FindChildById(containerNode, arrayId);
arrayContent = ReadNodeContentDirect(arrayNode);
count = ReadNodeCount(accessorNode);
}
else
{
// Find the source node for this source id
sourceNode = FindChildById(skinNode, sourceId);
}
if (semantic == DAE_JOINT_SKIN_INPUT)
{
string idx = ReadNodeProperty(*it, DAE_OFFSET_ATTRIBUTE);
if (idx.empty()) idx = ReadNodeProperty(*it, DAE_IDX_ATTRIBUTE); // COLLADA 1.3 Backward-compatibility
if (!idx.empty()) jointIdx = FUStringConversion::ToInt32(idx);
if (!jointIds.empty()) continue;
// Read in joint ids <source> element
if (isCollada1_3)
{
// COLLADA 1.3 backward compatibility: read in the joint Ids directly
jointIds.resize(count);
FUStringConversion::ToStringList(arrayContent, jointIds);
}
else ReadSource(sourceNode, jointIds);
}
else if (semantic == DAE_BINDMATRIX_SKIN_INPUT)
{
if (!invertedBindPoses.empty())
{
return status.Fail(FS("No inverted bind matrix input in controller: ") + TO_FSTRING(parent->GetDaeId()), (*it)->line);
}
// Read in the bind-pose matrices <source> element
if (isCollada1_3)
{
// COLLADA 1.3 backward compatibility: read in the bind-pose matrices directly
invertedBindPoses.resize(count);
FUStringConversion::ToMatrixList(arrayContent, invertedBindPoses, GetDocument()->GetLengthUnitConversion());
}
else ReadSource(sourceNode, invertedBindPoses, GetDocument()->GetLengthUnitConversion());
}
else if (semantic == DAE_WEIGHT_SKIN_INPUT)
{
string idx = ReadNodeProperty(*it, DAE_OFFSET_ATTRIBUTE);
if (idx.empty()) idx = ReadNodeProperty(*it, DAE_IDX_ATTRIBUTE); // COLLADA 1.3 Backward-compatibility
if (!idx.empty()) weightIdx = FUStringConversion::ToInt32(idx);
// Read in the weights <source> element
if (isCollada1_3)
{
// COLLADA 1.3 backward compatibility: read in the weights directly
weights.resize(count);
FUStringConversion::ToFloatList(arrayContent, weights);
}
else ReadSource(sourceNode, weights);
}
}
// Parse the <vcount> and the <v> elements
UInt32List combinerVertexCounts; combinerVertexCounts.reserve(combinerValueCount);
Int32List combinerVertexIndices; combinerVertexIndices.reserve(combinerValueCount * 5);
if (!isCollada1_3)
{
// The <vcount> and the <v> elements are ordered. Read the <vcount> element first.
if (!IsEquivalent(firstCombinerValueNode->name, DAE_VERTEXCOUNT_ELEMENT))
{
return status.Fail(FS("Expecting <vcount> element in combiner for controller: ") + TO_FSTRING(parent->GetDaeId()), firstCombinerValueNode->line);
}
const char* content = ReadNodeContentDirect(firstCombinerValueNode);
FUStringConversion::ToUInt32List(content, combinerVertexCounts);
// Read the <v> element second.
xmlNode* vNode = firstCombinerValueNode->next;
while (vNode != NULL && vNode->type != XML_ELEMENT_NODE) vNode = vNode->next;
if (vNode == NULL || !IsEquivalent(vNode->name, DAE_VERTEX_ELEMENT))
{
return status.Fail(FS("Expecting <v> element after <vcount> element in combiner for controller: ") + TO_FSTRING(parent->GetDaeId()), vNode->line);
}
content = ReadNodeContentDirect(vNode);
FUStringConversion::ToInt32List(content, combinerVertexIndices);
}
else
{
// COLLADA 1.3 backward compatibility: Read in the many <v> elements,
// creating the vertex counts array along the way
Int32List indices; indices.reserve(32);
for (xmlNode* valueNode = firstCombinerValueNode; valueNode != NULL; valueNode = valueNode->next)
{
if (valueNode->type != XML_ELEMENT_NODE) continue;
indices.clear();
const char* valueNodeContent = ReadNodeContentDirect(valueNode);
FUStringConversion::ToInt32List(valueNodeContent, indices);
size_t indexCount = indices.size() / 2;
combinerVertexCounts.push_back((uint32) indexCount);
combinerVertexIndices.insert(combinerVertexIndices.end(), indices.begin(), indices.end());
}
}
size_t combinerVertexIndexCount = combinerVertexIndices.size();
// Validate the inputs
if (jointIds.size() != invertedBindPoses.size())
{
return status.Fail(FS("Joint count and bind pose matrix count aren't equal for controller: ") + TO_FSTRING(parent->GetDaeId()), skinNode->line);
}
if (combinerVertexCounts.size() != combinerValueCount)
{
return status.Fail(FS("The <vcount> element list should contains the number of values determined by the <vertex_weights>'s 'count' attribute: ") + TO_FSTRING(parent->GetDaeId()), skinNode->line);
}
// Setup the joint-weight-vertex matches
weightedMatches.resize(combinerValueCount);
size_t jointCount = jointIds.size(), weightCount = weights.size(), offset = 0;
for (size_t j = 0; j < combinerValueCount; ++j)
{
FCDJointWeightPairList& pairList = weightedMatches[j];
uint32 localValueCount = combinerVertexCounts[j];
pairList.resize(localValueCount);
for (size_t i = 0; i < localValueCount && offset < combinerVertexIndexCount - 1; ++i)
{
pairList[i].jointIndex = combinerVertexIndices[offset + jointIdx];
if (pairList[i].jointIndex >= jointCount)
{
status.Warning(FS("Joint index out of bounds in combiner for controller: ") + TO_FSTRING(parent->GetDaeId()));
pairList[i].jointIndex = 0;
}
uint32 weightIndex = combinerVertexIndices[offset + weightIdx];
if (weightIndex >= weightCount)
{
status.Warning(FS("Weight index out of bounds in combiner for controller: ") + TO_FSTRING(parent->GetDaeId()));
weightIndex = 0;
}
pairList[i].weight = weights[weightIndex];
offset += 2;
}
}
// Normalize the weights, per-vertex, to 1 (or 0)
// This step is still being debated as necessary or not, for COLLADA 1.4.
for (FCDWeightedMatches::iterator it = weightedMatches.begin(); it != weightedMatches.end(); ++it)
{
FCDJointWeightPairList& pair = (*it);
float weightSum = 0.0f;
for (FCDJointWeightPairList::iterator itP = pair.begin(); itP != pair.end(); ++itP)
{
weightSum += (*itP).weight;
}
if (IsEquivalent(weightSum, 0.0f) || IsEquivalent(weightSum, 1.0f)) continue;
float invWeightSum = 1.0f / weightSum;
for (FCDJointWeightPairList::iterator itP = pair.begin(); itP != pair.end(); ++itP)
{
(*itP).weight *= invWeightSum;
}
}
// Setup the bind poses.
// Joints are linked later, as they are loaded last: with the scene graph
joints.resize(jointCount);
for (uint32 i = 0; i < jointCount; ++i)
{
FCDJointMatrixPair& joint = joints[i];
joint.invertedBindPose = invertedBindPoses[i];
joint.joint = NULL;
}
return status;
}
// Write out this controller to a COLLADA xml node tree
xmlNode* FCDSkinController::WriteToXML(xmlNode* parentNode) const
{
// Create the <skin> element
xmlNode* skinNode = AddChild(parentNode, DAE_CONTROLLER_SKIN_ELEMENT);
if (target != NULL) AddAttribute(skinNode, DAE_SOURCE_ATTRIBUTE, string("#") + target->GetDaeId());
// Create the <bind_shape_matrix> element
string bindShapeMatrixString = FUStringConversion::ToString(bindShapeTransform);
AddChild(skinNode, DAE_BINDSHAPEMX_SKIN_PARAMETER, bindShapeMatrixString);
// Create the joint source
FUSStringBuilder jointSourceId(parent->GetDaeId()); jointSourceId += "-joints";
StringList jointIds;
for (FCDJointList::const_iterator itJ = joints.begin(); itJ != joints.end(); ++itJ)
{
if ((*itJ).joint != NULL) jointIds.push_back((*itJ).joint->GetDaeId());
else jointIds.push_back(DAEERR_UNKNOWN_INPUT);
}
AddSourceIDRef(skinNode, jointSourceId.ToCharPtr(), jointIds, DAE_JOINT_SKIN_INPUT);
// Create the joint bind matrix source
FUSStringBuilder jointBindSourceId(parent->GetDaeId()); jointBindSourceId += "-bind_poses";
FMMatrix44List jointBindPoses;
for (FCDJointList::const_iterator itJ = joints.begin(); itJ != joints.end(); ++itJ)
{
jointBindPoses.push_back((*itJ).invertedBindPose);
}
AddSourceMatrix(skinNode, jointBindSourceId.ToCharPtr(), jointBindPoses);
// Create the weight source
FloatList weights;
weights.push_back(1.0f);
for (FCDWeightedMatches::const_iterator itW = weightedMatches.begin(); itW != weightedMatches.end(); ++itW)
{
const FCDJointWeightPairList& pairs = (*itW);
for (FCDJointWeightPairList::const_iterator itP = pairs.begin(); itP != pairs.end(); ++itP)
{
float w = (*itP).weight;
if (!IsEquivalent(w, 1.0f)) weights.push_back(w);
}
}
FUSStringBuilder weightSourceId(parent->GetDaeId()); weightSourceId += "-weights";
AddSourceFloat(skinNode, weightSourceId.ToCharPtr(), weights, DAE_WEIGHT_SKIN_INPUT);
// Create the <joints> element
xmlNode* jointsNode = AddChild(skinNode, DAE_JOINTS_ELEMENT);
AddInput(jointsNode, jointSourceId.ToCharPtr(), DAE_JOINT_SKIN_INPUT);
AddInput(jointsNode, jointBindSourceId.ToCharPtr(), DAE_BINDMATRIX_SKIN_INPUT);
// Create the <vertex_weights> element
xmlNode* matchesNode = AddChild(skinNode, DAE_WEIGHTS_ELEMENT);
AddInput(matchesNode, jointSourceId.ToCharPtr(), DAE_JOINT_SKIN_INPUT, 0);
AddInput(matchesNode, weightSourceId.ToCharPtr(), DAE_WEIGHT_SKIN_INPUT, 1);
AddAttribute(matchesNode, DAE_COUNT_ATTRIBUTE, weightedMatches.size());
// Generate the vertex count and match value strings and export the <v> and <vcount> elements
FUSStringBuilder vertexCounts; vertexCounts.reserve(1024);
FUSStringBuilder vertexMatches; vertexMatches.reserve(1024);
uint32 weightOffset = 1;
for (FCDWeightedMatches::const_iterator itW = weightedMatches.begin(); itW != weightedMatches.end(); ++itW)
{
const FCDJointWeightPairList& pairs = (*itW);
vertexCounts.append((uint32) pairs.size()); vertexCounts.append(' ');
for (FCDJointWeightPairList::const_iterator itP = pairs.begin(); itP != pairs.end(); ++itP)
{
vertexMatches.append((*itP).jointIndex); vertexMatches.append(' ');
if (!IsEquivalent((*itP).weight, 1.0f)) vertexMatches.append(weightOffset++);
else vertexMatches.append('0');
vertexMatches.append(' ');
}
}
if (!vertexMatches.empty()) vertexMatches.pop_back();
AddChild(matchesNode, DAE_VERTEXCOUNT_ELEMENT, vertexCounts);
AddChild(matchesNode, DAE_VERTEX_ELEMENT, vertexMatches);
return skinNode;
}
// Done after the scene graph is loaded, link this controller to its joints
FUStatus FCDSkinController::Link()
{
FUStatus status;
// Look for each joint, by COLLADA id, within the scene graph
size_t jointCount = joints.size();
if (jointCount != jointIds.size())
{
return status.Fail(FS("Parsing programming error in controller: ") + TO_FSTRING(parent->GetDaeId()));
}
for (size_t i = 0; i < jointCount; ++i)
{
FCDJointMatrixPair& joint = joints[i];
joint.joint = GetDocument()->FindSceneNode(jointIds[i]);
if (joint.joint != NULL)
{
joint.joint->SetJointFlag(true);
}
else
{
status.Warning(FS("Unknown joint '") + TO_FSTRING(jointIds[i]) + FS("' referenced in controller: ") + TO_FSTRING(parent->GetDaeId()));
}
}
jointIds.clear();
return status;
}