/// <summary>
            /// Creates the vertex channel using the MeshBuilder (no data is added yet).
            /// </summary>
            protected virtual void Create()
            {
                var usage      = _colladaVertexChannel.Description.VertexElementUsage;
                int usageIndex = _colladaVertexChannel.Description.UsageIndex;

                // Construct correct usage string
                String usageString = VertexChannelNames.EncodeName(usage, usageIndex);

                // Generic standard channel (TexCoord, Normal, Binormal, etc.)
                switch (_colladaVertexChannel.Description.VertexElementFormat)
                {
                case VertexElementFormat.Vector4:
                    _channelIndex = _meshBuilder.CreateVertexChannel <Vector4>(usageString);
                    break;

                case VertexElementFormat.Vector3:
                    _channelIndex = _meshBuilder.CreateVertexChannel <Vector3>(usageString);
                    break;

                case VertexElementFormat.Vector2:
                    _channelIndex = _meshBuilder.CreateVertexChannel <Vector2>(usageString);
                    break;

                case VertexElementFormat.Single:
                    _channelIndex = _meshBuilder.CreateVertexChannel <Single>(usageString);
                    break;

                default:
                    throw new Exception("Unexpected vertex element format");
                }
            }
Example #2
0
        private static void ProcessWeightsChannel(ContentProcessorContext context, string asset, GeometryContent geometry, int vertexChannelIndex, Dictionary <string, int> boneIndices, Dictionary <int, int> boneRemap)
        {
            if (boneIndices == null)
            {
                throw new InvalidContentException("Mesh has bone weights with no skeleton");
            }

            VertexChannelCollection channels             = geometry.Vertices.Channels;
            VertexChannel           channel2             = channels[vertexChannelIndex];
            VertexChannel <BoneWeightCollection> channel = channel2 as VertexChannel <BoneWeightCollection>;

            Byte4[]   outputIndices = new Byte4[channel.Count];
            Vector4[] outputWeights = new Vector4[channel.Count];
            for (int i = 0; i < channel.Count; i++)
            {
                BoneWeightCollection inputWeights = channel[i];
                ConvertVertexWeights(context, asset, inputWeights, boneIndices, outputIndices, outputWeights, i, geometry, boneRemap);
            }
            int    usageIndex = VertexChannelNames.DecodeUsageIndex(channel.Name);
            string name       = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, usageIndex);
            string str        = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, usageIndex);

            channels.Insert <Byte4>(vertexChannelIndex + 1, name, outputIndices);
            channels.Insert <Vector4>(vertexChannelIndex + 2, str, outputWeights);
            channels.RemoveAt(vertexChannelIndex);
        }
Example #3
0
        static void ProcessWeightsChannel(GeometryContent geometry, int vertexChannelIndex)
        {
            // create a map of Name->Index of the bones
            BoneContent skeleton = MeshHelper.FindSkeleton(geometry.Parent);
            Dictionary <string, int> boneIndices    = new Dictionary <string, int>();
            IList <BoneContent>      flattenedBones = MeshHelper.FlattenSkeleton(skeleton);

            for (int i = 0; i < flattenedBones.Count; i++)
            {
                boneIndices.Add(flattenedBones[i].Name, i);
            }

            // convert all of our bone weights into the correct indices and weight values
            VertexChannel <BoneWeightCollection> inputWeights = geometry.Vertices.Channels[vertexChannelIndex] as VertexChannel <BoneWeightCollection>;

            Vector4[] outputIndices = new Vector4[inputWeights.Count];
            Vector4[] outputWeights = new Vector4[inputWeights.Count];
            for (int i = 0; i < inputWeights.Count; i++)
            {
                ConvertWeights(inputWeights[i], boneIndices, outputIndices, outputWeights, i, geometry);
            }

            // create our new channel names
            int    usageIndex  = VertexChannelNames.DecodeUsageIndex(inputWeights.Name);
            string indicesName = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, usageIndex);
            string weightsName = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, usageIndex);

            // add in the index and weight channels
            geometry.Vertices.Channels.Insert(vertexChannelIndex + 1, indicesName, outputIndices);
            geometry.Vertices.Channels.Insert(vertexChannelIndex + 2, weightsName, outputWeights);

            // remove the original weights channel
            geometry.Vertices.Channels.RemoveAt(vertexChannelIndex);
        }
Example #4
0
        static void ProcessWeightsChannel(GeometryContent geometry, int vertexChannelIndex, SerializableSkeleton skeleton)
        {
            Dictionary <string, int> boneIndices = skeleton.boneIndexByName;

            // convert all of our bone weights into the correct indices and weight values
            VertexChannel <BoneWeightCollection> inputWeights = geometry.Vertices.Channels[vertexChannelIndex] as VertexChannel <BoneWeightCollection>;

            Vector4[] outputIndices = new Vector4[inputWeights.Count];
            Vector4[] outputWeights = new Vector4[inputWeights.Count];
            for (int i = 0; i < inputWeights.Count; i++)
            {
                ConvertWeights(inputWeights[i], boneIndices, outputIndices, outputWeights, i, geometry);
            }

            // create our new channel names
            int    usageIndex  = VertexChannelNames.DecodeUsageIndex(inputWeights.Name);
            string indicesName = VertexChannelNames.EncodeName(Microsoft.Xna.Framework.Graphics.VertexElementUsage.BlendIndices, usageIndex);
            string weightsName = VertexChannelNames.EncodeName(Microsoft.Xna.Framework.Graphics.VertexElementUsage.BlendWeight, usageIndex);

            // add in the index and weight channels
            geometry.Vertices.Channels.Insert(vertexChannelIndex + 1, indicesName, outputIndices);
            geometry.Vertices.Channels.Insert(vertexChannelIndex + 2, weightsName, outputWeights);

            // remove the original weights channel
            geometry.Vertices.Channels.RemoveAt(vertexChannelIndex);
        }
        private static void ProcessWeightsChannel(
            GeometryContent geometry, int vertexChannelIndex, ContentIdentity identity)
        {
            // NOTE: Portions of this code is from the XNA CPU Skinning
            // sample under Ms-PL, (c) Microsoft Corporation.

            // create a map of Name->Index of the bones
            var skeleton = MeshHelper.FindSkeleton(geometry.Parent);

            if (skeleton == null)
            {
                throw new InvalidContentException(
                          "Skeleton not found. Meshes that contain a Weights vertex channel cannot " +
                          "be processed without access to the skeleton data.",
                          identity);
            }

            var boneIndices    = new Dictionary <string, byte>();
            var flattenedBones = MeshHelper.FlattenSkeleton(skeleton);

            if (flattenedBones.Count > byte.MaxValue)
            {
                throw new NotSupportedException("The flattened skeleton contains more than 255 bones.");
            }

            for (int i = 0; i < flattenedBones.Count; i++)
            {
                boneIndices.Add(flattenedBones[i].Name, (byte)i);
            }

            var vertexChannel = geometry.Vertices.Channels[vertexChannelIndex];

            if (!(vertexChannel is VertexChannel <BoneWeightCollection> inputWeights))
            {
                throw new InvalidContentException(
                          string.Format(
                              "Vertex channel \"{0}\" is the wrong type. It has element type {1}. Type {2} is expected.",
                              vertexChannel.Name, vertexChannel.ElementType.FullName, typeof(BoneWeightCollection).FullName),
                          identity);
            }
            var outputIndices = new Byte4[inputWeights.Count];
            var outputWeights = new Vector4[inputWeights.Count];

            for (var i = 0; i < inputWeights.Count; i++)
            {
                ConvertWeights(inputWeights[i], boneIndices, outputIndices, outputWeights, i);
            }

            // create our new channel names
            var usageIndex  = VertexChannelNames.DecodeUsageIndex(inputWeights.Name);
            var indicesName = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, usageIndex);
            var weightsName = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, usageIndex);

            // add in the index and weight channels
            geometry.Vertices.Channels.Insert(vertexChannelIndex + 1, indicesName, outputIndices);
            geometry.Vertices.Channels.Insert(vertexChannelIndex + 2, weightsName, outputWeights);

            // remove the original weights channel
            geometry.Vertices.Channels.RemoveAt(vertexChannelIndex);
        }
Example #6
0
        private void ProcessGeometry(GeometryContent xnaGeometry)
        {
            // find and process the geometry's bone weights
            for (int i = 0; i < xnaGeometry.Vertices.Channels.Count; i++)
            {
                string channelName = xnaGeometry.Vertices.Channels[i].Name;
                string baseName    = VertexChannelNames.DecodeBaseName(channelName);

                if (baseName == "Weights")
                {
                    ProcessWeightsChannel(xnaGeometry, i, outputModel.skeleton);
                }
            }


            // retrieve the four vertex channels we require for CPU skinning. we ignore any
            // other channels the model might have.
            string normalName      = VertexChannelNames.EncodeName(Microsoft.Xna.Framework.Graphics.VertexElementUsage.Normal, 0);
            string texCoordName    = VertexChannelNames.EncodeName(Microsoft.Xna.Framework.Graphics.VertexElementUsage.TextureCoordinate, 0);
            string blendWeightName = VertexChannelNames.EncodeName(Microsoft.Xna.Framework.Graphics.VertexElementUsage.BlendWeight, 0);
            string blendIndexName  = VertexChannelNames.EncodeName(Microsoft.Xna.Framework.Graphics.VertexElementUsage.BlendIndices, 0);

            VertexChannel <Vector3> normals      = xnaGeometry.Vertices.Channels[normalName] as VertexChannel <Vector3>;
            VertexChannel <Vector2> texCoords    = xnaGeometry.Vertices.Channels[texCoordName] as VertexChannel <Vector2>;
            VertexChannel <Vector4> blendWeights = xnaGeometry.Vertices.Channels[blendWeightName] as VertexChannel <Vector4>;
            VertexChannel <Vector4> blendIndices = xnaGeometry.Vertices.Channels[blendIndexName] as VertexChannel <Vector4>;

            // create our array of vertices
            int triangleCount = xnaGeometry.Indices.Count / 3;

            SerializableVertex[] vertices = new SerializableVertex[xnaGeometry.Vertices.VertexCount];
            for (int i = 0; i < vertices.Length; i++)
            {
                vertices[i] = new SerializableVertex
                {
                    position     = xnaGeometry.Vertices.Positions[i],
                    normal       = normals[i],
                    texture      = texCoords[i],
                    blendweights = blendWeights[i],
                    blendindices = blendIndices[i]
                };
            }

            int[] indices = new int[xnaGeometry.Indices.Count];
            for (int i = 0; i < xnaGeometry.Indices.Count; i++)
            {
                indices[i] = xnaGeometry.Indices[i];
            }

            SerializableMesh mesh = new SerializableMesh();

            mesh.name        = string.Format("mesh_{0}_{1}", outputModel.meshList.Count, xnaGeometry.Name);
            mesh.textureName = GetTextureName(xnaGeometry);
            mesh.vertices    = vertices;
            mesh.indices     = indices;
            outputModel.meshList.Add(mesh);
        }
Example #7
0
        void ProcessGeometry(GeometryContent geometry)
        {
            // find and process the geometry's bone weights
            for (int i = 0; i < geometry.Vertices.Channels.Count; i++)
            {
                string channelName = geometry.Vertices.Channels[i].Name;
                string baseName    = VertexChannelNames.DecodeBaseName(channelName);

                if (baseName == "Weights")
                {
                    ProcessWeightsChannel(geometry, i);
                }
            }

            // retrieve the four vertex channels we require for CPU skinning. we ignore any
            // other channels the model might have.
            string normalName      = VertexChannelNames.EncodeName(VertexElementUsage.Normal, 0);
            string texCoordName    = VertexChannelNames.EncodeName(VertexElementUsage.TextureCoordinate, 0);
            string blendWeightName = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, 0);
            string blendIndexName  = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, 0);

            VertexChannel <Vector3> normals      = geometry.Vertices.Channels[normalName] as VertexChannel <Vector3>;
            VertexChannel <Vector2> texCoords    = geometry.Vertices.Channels[texCoordName] as VertexChannel <Vector2>;
            VertexChannel <Vector4> blendWeights = geometry.Vertices.Channels[blendWeightName] as VertexChannel <Vector4>;
            VertexChannel <Vector4> blendIndices = geometry.Vertices.Channels[blendIndexName] as VertexChannel <Vector4>;

            // create our array of vertices
            int triangleCount = geometry.Indices.Count / 3;

            CpuVertex[] vertices = new CpuVertex[geometry.Vertices.VertexCount];
            for (int i = 0; i < vertices.Length; i++)
            {
                vertices[i] = new CpuVertex
                {
                    Position          = geometry.Vertices.Positions[i],
                    Normal            = normals[i],
                    TextureCoordinate = texCoords[i],
                    BlendWeights      = blendWeights[i],
                    BlendIndices      = blendIndices[i]
                };
            }

            // Convert the input material.
            MaterialContent material = ProcessMaterial(geometry.Material);

            // Add the new piece of geometry to our output model.
            outputModel.AddModelPart(triangleCount, geometry.Indices, vertices, material as BasicMaterialContent);
        }
Example #8
0
        protected virtual void ProcessGeometryUsingMaterial(MaterialContent material,
                                                            IEnumerable <GeometryContent> geometryCollection,
                                                            ContentProcessorContext context)
        {
            // If we don't get a material then assign a default one.
            if (material == null)
            {
                material = MaterialProcessor.CreateDefaultMaterial(DefaultEffect);
            }

            // Test requirements from the assigned material.
            int  textureChannels;
            bool vertexWeights = false;

            if (material is DualTextureMaterialContent)
            {
                textureChannels = 2;
            }
            else if (material is SkinnedMaterialContent)
            {
                textureChannels = 1;
                vertexWeights   = true;
            }
            else if (material is EnvironmentMapMaterialContent)
            {
                textureChannels = 1;
            }
            else if (material is AlphaTestMaterialContent)
            {
                textureChannels = 1;
            }
            else
            {
                // Just check for a "Texture" which should cover custom Effects
                // and BasicEffect which can have an optional texture.
                textureChannels = material.Textures.ContainsKey("Texture") ? 1 : 0;
            }

            // By default we must set the vertex color property
            // to match XNA behavior.
            material.OpaqueData["VertexColorEnabled"] = false;

            // If we run into a geometry that requires vertex
            // color we need a seperate material for it.
            var colorMaterial = material.Clone();

            colorMaterial.OpaqueData["VertexColorEnabled"] = true;

            foreach (var geometry in geometryCollection)
            {
                // Process the geometry.
                for (var i = 0; i < geometry.Vertices.Channels.Count; i++)
                {
                    ProcessVertexChannel(geometry, i, context);
                }

                // Verify we have the right number of texture coords.
                for (var i = 0; i < textureChannels; i++)
                {
                    if (!geometry.Vertices.Channels.Contains(VertexChannelNames.TextureCoordinate(i)))
                    {
                        throw new InvalidContentException(
                                  string.Format("The mesh \"{0}\", using {1}, contains geometry that is missing texture coordinates for channel {2}.",
                                                geometry.Parent.Name,
                                                MaterialProcessor.GetDefaultEffect(material),
                                                i),
                                  _identity);
                    }
                }

                // Do we need to enable vertex color?
                if (geometry.Vertices.Channels.Contains(VertexChannelNames.Color(0)))
                {
                    geometry.Material = colorMaterial;
                }
                else
                {
                    geometry.Material = material;
                }

                // Do we need vertex weights?
                if (vertexWeights)
                {
                    var weightsName = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, 0);
                    if (!geometry.Vertices.Channels.Contains(weightsName))
                    {
                        throw new InvalidContentException(
                                  string.Format("The skinned mesh \"{0}\" contains geometry without any vertex weights.", geometry.Parent.Name),
                                  _identity);
                    }
                }
            }
        }
        // Converts a channel of type BoneWeightCollection to two new channels:
        // Byte4 indices + Vector4 weights
        private void ProcessWeightsChannel(GeometryContent geometry, int vertexChannelIndex)
        {
            if (_skeleton == null)
            {
                // No skeleton? Remove BoneWeightCollection.
                geometry.Vertices.Channels.RemoveAt(vertexChannelIndex);
                return;
            }

            if (_skeleton.NumberOfBones > 255)
            {
                string message = String.Format(
                    CultureInfo.InvariantCulture,
                    "Too many bones in skeleton. Actual number of bones: {0}. Allowed number of bones: {1}.",
                    _skeleton.NumberOfBones, 255);
                throw new InvalidContentException(message, _rootBone.Identity);
            }

            var channels          = geometry.Vertices.Channels;
            var channel           = channels[vertexChannelIndex];
            var boneWeightChannel = channel as VertexChannel <BoneWeightCollection>;

            if (boneWeightChannel == null)
            {
                string message = String.Format(
                    CultureInfo.InvariantCulture,
                    "Vertex channel \"{0}\" has wrong content type. Actual type: {1}. Expected type: {2}.",
                    channel.Name, channel.ElementType, typeof(BoneWeightCollection));
                throw new InvalidContentException(message, geometry.Parent.Identity);
            }

            // Create two channels (Byte4 indices + Vector4 weights) from a BoneWeight channel.
            Byte4[]   boneIndices = new Byte4[boneWeightChannel.Count];
            Vector4[] boneWeights = new Vector4[boneWeightChannel.Count];
            for (int i = 0; i < boneWeightChannel.Count; i++)
            {
                // Convert bone weights for vertex i.
                var boneWeightCollection = boneWeightChannel[i];
                if (boneWeightCollection == null)
                {
                    string message = String.Format(
                        CultureInfo.InvariantCulture,
                        "NULL entry found in channel \"{0}\". Expected element type: {1}.",
                        boneWeightChannel.Name, typeof(BoneWeightCollection));
                    throw new InvalidContentException(message, geometry.Parent.Identity);
                }

                ConvertBoneWeights(boneWeightCollection, boneIndices, boneWeights, i, geometry);
            }

            // The current channel has the name "WeightsN", where N is the usage index.
            // Get the usage index.
            int usageIndex = VertexChannelNames.DecodeUsageIndex(boneWeightChannel.Name);

            // Store the converted bone information in two new channels called "BlendIndicesN"
            // and "BlendWeightsN".
            string blendIndices = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, usageIndex);

            if (channels.Contains(blendIndices))
            {
                string message = String.Format(
                    CultureInfo.InvariantCulture,
                    "Cannot store converted blend indices for vertex channel \"{0}\", because a vertex channel called \"{1}\" already exists.",
                    boneWeightChannel.Name, blendIndices);
                throw new InvalidContentException(message, geometry.Parent.Identity);
            }

            string blendWeights = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, usageIndex);

            if (channels.Contains(blendWeights))
            {
                string message = String.Format(
                    CultureInfo.InvariantCulture,
                    "Cannot store converted blend weights for vertex channel \"{0}\", because a vertex channel called \"{1}\" already exists.",
                    boneWeightChannel.Name, blendWeights);
                throw new InvalidContentException(message, geometry.Parent.Identity);
            }

            // Insert the new channels after "WeightsN" and remove "WeightsN".
            channels.Insert(vertexChannelIndex + 1, blendIndices, boneIndices);
            channels.Insert(vertexChannelIndex + 2, blendWeights, boneWeights);
            channels.RemoveAt(vertexChannelIndex);
        }
        private void ValidateMesh(MeshContent mesh)
        {
            foreach (var geometry in mesh.Geometry)
            {
                if (GetExternalMaterial(mesh, geometry) != null)
                {
                    // ----- External material.
                    // The material is defined in an external XML file!
                    // Ignore local material.
                    continue;
                }

                // ----- Local material.
                // Submesh uses the material included in the model.
                var material = geometry.Material;
                var channels = geometry.Vertices.Channels;

                // Check if the geometry vertices contain the right number of texture coordinates.
                if (material != null && material.Textures.ContainsKey("Texture"))
                {
                    if (!channels.Contains(VertexChannelNames.TextureCoordinate(0)))
                    {
                        string message = String.Format(
                            CultureInfo.InvariantCulture,
                            "Model \"{0}\" has texture but no texture coordinates.",
                            geometry.Parent.Name);
                        throw new InvalidContentException(message, geometry.Identity);
                    }
                }

                if (material is DualTextureMaterialContent)
                {
                    if (!channels.Contains(VertexChannelNames.TextureCoordinate(1)))
                    {
                        string message = String.Format(
                            CultureInfo.InvariantCulture,
                            "Model \"{0}\" uses DualTextureEffect but has only one set of texture coordinates.",
                            geometry.Parent.Name);
                        throw new InvalidContentException(message, geometry.Identity);
                    }
                }

                // Check if the geometry vertices contain blend weights for mesh skinning.
                if (material is SkinnedMaterialContent)
                {
                    // If the channel contains "Weights0", then we have a BoneWeightCollection that contains
                    // the necessary data.
                    if (!channels.Contains(VertexChannelNames.Weights()))
                    {
                        // Otherwise, we need "BlendIndices0" AND "BlendWeight0".
                        var blendIndicesName = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, 0);
                        var blendWeightsName = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, 0);

                        if (!channels.Contains(blendIndicesName) || !channels.Contains(blendWeightsName))
                        {
                            string message = String.Format(
                                CultureInfo.InvariantCulture,
                                "Model \"{0}\" uses mesh skinning but vertices do not have bone weights.",
                                geometry.Parent.Name);
                            throw new InvalidContentException(message, geometry.Identity);
                        }
                    }
                }
            }
        }
Example #11
0
        } // Process

        #endregion

        #region Process Vertex Channel

        /// <summary>
        /// Processes geometry content vertex channels at the specified index.
        /// </summary>
        protected override void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context)
        {
            // Compressed Vertex Data
            VertexChannelCollection channels = geometry.Vertices.Channels;
            string name = channels[vertexChannelIndex].Name;

            if (name == VertexChannelNames.Normal())
            {
                channels.ConvertChannelContent <NormalizedShort4>(vertexChannelIndex);
            }
            else if (name == VertexChannelNames.TextureCoordinate(0))
            {
                // If the resource has texture coordinates outside the range [-1, 1] the values will be clamped.
                channels.ConvertChannelContent <HalfVector2>(vertexChannelIndex);
            }
            else if (name == VertexChannelNames.TextureCoordinate(1))
            {
                channels.Remove(VertexChannelNames.TextureCoordinate(1));
            }
            else if (name == VertexChannelNames.TextureCoordinate(2))
            {
                channels.Remove(VertexChannelNames.TextureCoordinate(2));
            }
            else if (name == VertexChannelNames.TextureCoordinate(3))
            {
                channels.Remove(VertexChannelNames.TextureCoordinate(3));
            }
            else if (name == VertexChannelNames.TextureCoordinate(4))
            {
                channels.Remove(VertexChannelNames.TextureCoordinate(4));
            }
            else if (name == VertexChannelNames.TextureCoordinate(5))
            {
                channels.Remove(VertexChannelNames.TextureCoordinate(5));
            }
            else if (name == VertexChannelNames.TextureCoordinate(6))
            {
                channels.Remove(VertexChannelNames.TextureCoordinate(6));
            }
            else if (name == VertexChannelNames.TextureCoordinate(7))
            {
                channels.Remove(VertexChannelNames.TextureCoordinate(7));
            }
            else if (name == VertexChannelNames.Color(0))
            {
                channels.Remove(VertexChannelNames.Color(0));
            }
            else if (name == VertexChannelNames.Tangent(0))
            {
                channels.ConvertChannelContent <NormalizedShort4>(vertexChannelIndex);
            }
            else if (name == VertexChannelNames.Binormal(0))
            {
                // Not need to get rid of the binormal data because the model will use more than 32 bytes per vertex.
                // We can actually try to align the data to 64 bytes per vertex.
                channels.ConvertChannelContent <NormalizedShort4>(vertexChannelIndex);
            }
            else
            {
                // Blend indices, blend weights and everything else.
                // Don't use "BlendWeight0" as a name, nor weights0. Both names don't work.
                base.ProcessVertexChannel(geometry, vertexChannelIndex, context);
                channels.ConvertChannelContent <Byte4>("BlendIndices0");
                channels.ConvertChannelContent <NormalizedShort4>(VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, 0));
            }
        } // ProcessVertexChannel