/* 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 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 element and process the inner element xmlNode* verticesNode = FindChildByType(skinNode, DAE_VERTICES_ELEMENT); bool isCollada1_3 = verticesNode != NULL; // Read in the 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 and 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 element for controller: ") + TO_FSTRING(parent->GetDaeId()), jointWeightSourceNode->line); } if (bindShapePositionSourceNode == NULL) { return status.Fail(FS("Cannot find 'BIND_SHAPE_POSITION' input in 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 element and the 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 element and the element jointsNode = FindChildByType(skinNode, DAE_JOINTS_ELEMENT); combinerNode = FindChildByType(skinNode, DAE_WEIGHTS_ELEMENT); } // Verify that we have the necessary data structures: bind-shape, elements, 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 element found in the skin weight source for controller: ") + TO_FSTRING(parent->GetDaeId()), skinNode->line); } if (combinerNode == NULL) { return status.Fail(FS("No node found in the skin weight source for controller: ") + TO_FSTRING(parent->GetDaeId()), skinNode->line); } // Gather the inputs for the element and the 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 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 node and its 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 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 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 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 and the elements UInt32List combinerVertexCounts; combinerVertexCounts.reserve(combinerValueCount); Int32List combinerVertexIndices; combinerVertexIndices.reserve(combinerValueCount * 5); if (!isCollada1_3) { // The and the elements are ordered. Read the element first. if (!IsEquivalent(firstCombinerValueNode->name, DAE_VERTEXCOUNT_ELEMENT)) { return status.Fail(FS("Expecting element in combiner for controller: ") + TO_FSTRING(parent->GetDaeId()), firstCombinerValueNode->line); } const char* content = ReadNodeContentDirect(firstCombinerValueNode); FUStringConversion::ToUInt32List(content, combinerVertexCounts); // Read the 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 element after 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 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 element list should contains the number of values determined by the '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 element xmlNode* skinNode = AddChild(parentNode, DAE_CONTROLLER_SKIN_ELEMENT); if (target != NULL) AddAttribute(skinNode, DAE_SOURCE_ATTRIBUTE, string("#") + target->GetDaeId()); // Create the 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 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 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 and 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; }