Ejemplo n.º 1
0
        /// <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");
        }