/// <summary> /// This method extacts skinning information from a skin definition that /// refers to the given mesh node. If no skin definition is found jointWeights /// and jointIndices are assigned empty arrays. If there is a valid skin /// definition this method will output four joint indices per base vertex (Vector4) /// and three weights (Vector3). If there are more than four joints assigned to a /// vertex only the four most influencial are selected. The indices in the resulting /// jointIndices array are referring to positions within the given model's joint /// collection. 0 refers to the joint at model.Joints[0], 1 refers to the joint at /// model.Joints[1] and so on. If the model contains no joints this method outputs /// two empty arrays, just as if no skin defintion existed. /// </summary> /// <remarks>The outputted Vector3s for jointWeights represent four normalized weights /// in three components. The sum of all four weights is 1. The fourth weight is implicitly /// defined as (1 - X - Y -Z).</remarks> /// <param name="xmlMeshNode">XML mesh node</param> /// <param name="model">Model instance with non-empty joint collection</param> /// <param name="jointIndices">Array of 4-d vectors representing joint indices</param> /// <param name="jointWeights">Array of 3-d vectors representing their respective weights</param> protected static void GetJointWeightsAndIndices(XmlNode xmlMeshNode, ColladaModel model, out Vector4[] jointIndices, out Vector3[] jointWeights) { // Look for a skin definition that references this mesh XmlNode xmlSkin = xmlMeshNode.SelectSingleNode("/COLLADA/library_controllers/" + "controller/skin[@source='#" + xmlMeshNode.ParentNode.Attributes["id"].Value + "']"); if (xmlSkin == null || model.Joints == null || model.Joints.Count == 0) { // no skinning information found jointIndices = new Vector4[0]; jointWeights = new Vector3[0]; return; } // Read number of vertex weight assignments (this is equivalent to the number of base vertices) XmlNode xmlVertexWeights = xmlSkin.SelectSingleNode("vertex_weights"); int count = int.Parse(xmlVertexWeights.Attributes["count"].Value); // Read weight source XmlNode xmlWeightInput = xmlSkin.SelectSingleNode("vertex_weights/input[@semantic='WEIGHT']"); Source weightSource = Source.FromInput(xmlWeightInput, xmlSkin); var weights = weightSource.GetData <float>(); // Read assignments XmlNode xmlVertexCount = xmlSkin.SelectSingleNode("vertex_weights/vcount"); XmlNode xmlVertices = xmlSkin.SelectSingleNode("vertex_weights/v"); int[] vcount = XmlUtil.ParseInts(xmlVertexCount.InnerText); ContentAssert.AreEqual(vcount.Length, count, "vcount.Length"); int[] data = XmlUtil.ParseInts(xmlVertices.InnerText); // How many items per vertex (this corresponds to the maximum offset of // all inputs within vertex_weights) int stride = (from XmlNode node in xmlVertexWeights.SelectNodes("input/@offset") select int.Parse(node.Value)).Max() + 1; ContentAssert.IsTrue(stride >= 2, "Invalid weight data"); // It is assumed that joint indices are at offset 0 and their weights at offset 1 // For each base vertex there is one block of joint-weight assigments jointIndices = new Vector4[count]; jointWeights = new Vector3[count]; bool reachedEnd = false; for (int i = 0, k = 0; i < count; i++) { // There may be more than 4 weights defined List <JointWeightPair> pairs = new List <JointWeightPair>(); // Add all defined joint-weight pairs for (int j = 0; j < vcount[i]; j++) { int jointIndex = data[k + 0]; int weightIndex = data[k + 1]; pairs.Add(new JointWeightPair(jointIndex, weights[weightIndex])); k += stride; } // Take the four vertices with greatest influence JointWeightPair[] best = (from pair in pairs orderby pair.Weight descending select pair).Take(4).ToArray(); Vector4 curIndices = new Vector4(); Vector4 curWeights = new Vector4(); ContentAssert.IsTrue((vcount[i] <= 4 && best.Length == vcount[i]) || best.Length == 4, "Invalid weight data", true); if (best.Length >= 1) { curIndices.X = best[0].JointIndex; curWeights.X = best[0].Weight; } if (best.Length >= 2) { curIndices.Y = best[1].JointIndex; curWeights.Y = best[1].Weight; } if (best.Length >= 3) { curIndices.Z = best[2].JointIndex; curWeights.Z = best[2].Weight; } if (best.Length == 4) { curIndices.W = best[3].JointIndex; curWeights.W = best[3].Weight; } // Normalize weights (sum must be 1) float sum = curWeights.X + curWeights.Y + curWeights.Z + curWeights.Z; if (sum > 0) { curWeights.X = 1.0f / sum * curWeights.X; curWeights.Y = 1.0f / sum * curWeights.Y; curWeights.Z = 1.0f / sum * curWeights.Z; curWeights.W = 1.0f / sum * curWeights.W; } jointIndices[i] = curIndices; jointWeights[i] = curWeights.XYZ(); if (k == data.Length) { reachedEnd = true; } } ContentAssert.IsTrue(reachedEnd, "Not all weights were read", true); // JointIndices are referring to indices in the joint source // so every index refers to a name in the source which refers to the // actual bone XmlNode xmlInput = xmlSkin.SelectSingleNode("vertex_weights/input[@semantic='JOINT']"); Debug.Assert(xmlInput != null, "No joint input in skin found"); Source jointSource = Source.FromInput(xmlInput, xmlSkin); var names = jointSource.GetData <string>(); // Create dictionary of model bones with source reference type as key // (source refers to joints either by name, idref or sidref) Dictionary <string, Joint> modelJoints = model.Joints.ToDictionary(j => j.GetAddressPart(jointSource.ColladaType)); // Check if the names actually refer to the joints in the dictionary if (modelJoints.ContainsKey(names[0]) == false) { Debug.Assert(model.Joints.All(j => j.Name != null || j.GlobalID != null || j.ScopedID != null), "Joints cannot be referenced"); // As of COLLADA 1.4 "name" can refer to name OR sid attribute! // If the former didn't work try the latter);) if (jointSource.ColladaType == "name") { // Only elements that actually have a sid can be part of the dictionary. // Elements which don't have a SID usually aren't referenced, so they are // not needed in the dictionary anyway modelJoints = model.Joints.Where(j => j.GetAddressPart("sid") != null). ToDictionary(j => j.GetAddressPart("sid")); } // If that still didn't help it's hopeless if (modelJoints.ContainsKey(names[0]) == false) { throw new ApplicationException("Invalid joint references in skin definition"); } } try { // replace index that points to source with index that points to model's joint for (int i = 0; i < jointIndices.Length; i++) { Vector4 indices = jointIndices[i]; // Find indices in model's joint collection indices.X = modelJoints[names[(int)indices.X]].Index; indices.Y = modelJoints[names[(int)indices.Y]].Index; indices.Z = modelJoints[names[(int)indices.Z]].Index; indices.W = modelJoints[names[(int)indices.W]].Index; jointIndices[i] = indices; } } catch (IndexOutOfRangeException e) { throw new ApplicationException("Invalid joint indices read"); } // Check data bool valid = jointIndices.All(v => Math.Abs((v.X + v.Y + v.Z + (1 - v.X - v.Y - v.Z)) - 1) < 0.001f); ContentAssert.IsTrue(valid, "All joint weights must sum up to 1f"); }