// returns true if the model contains meshes that have a parent bone as a child of // a bone in the skeleton attached to the mesh. private bool ValidateMeshSkeleton(MeshContent meshContent) { List <string> meshParentHierarchy = new List <string>(); int meshIndex = (int)meshContent.OpaqueData["MeshIndex"]; BoneIndexer indexer = indexers[meshIndex]; if (indexer.SkinnedBoneNames.Contains(meshContent.Parent.Name)) { // Warning return(false); } // skeleton is fine return(true); }
private SkinInfoContentCollection[] ProcessSkinInfo(ModelContent model) { SkinInfoContentCollection[] info = new SkinInfoContentCollection[model.Meshes.Count]; Dictionary <string, int> boneDict = new Dictionary <string, int>(); foreach (ModelBoneContent b in model.Bones) { if (b.Name != null && !boneDict.ContainsKey(b.Name)) { boneDict.Add(b.Name, b.Index); } } for (int i = 0; i < info.Length; i++) { info[i] = new SkinInfoContentCollection(); BoneIndexer indexer = indexers[i]; ReadOnlyCollection <string> skinnedBoneNames = indexer.SkinnedBoneNames; Matrix[] absoluteTransforms = new Matrix[model.Bones.Count]; CalculateAbsoluteTransforms(model.Bones[0], absoluteTransforms); Matrix absoluteMeshTransform; if (absoluteMeshTransforms == null) { absoluteMeshTransform = absoluteTransforms[model.Meshes[i].ParentBone.Index]; } else { absoluteMeshTransform = absoluteMeshTransforms[i]; } for (int j = 0; j < skinnedBoneNames.Count; j++) { string name = skinnedBoneNames[j]; SkinInfoContent content = new SkinInfoContent(); content.BoneIndex = boneDict[name]; content.PaletteIndex = indexer.GetBoneIndex(name); content.InverseBindPoseTransform = absoluteMeshTransform * Matrix.Invert(absoluteTransforms[boneDict[name]]); content.BoneName = name; info[i].Add(content); } } return(info); }
private void CreatePaletteIndices(MeshContent mesh) { foreach (GeometryContent meshPart in mesh.Geometry) { int meshIndex = (int)mesh.OpaqueData["MeshIndex"]; BoneIndexer indexer = indexers[meshIndex]; foreach (VertexChannel channel in meshPart.Vertices.Channels) { if (channel.Name == VertexChannelNames.Weights()) { VertexChannel <BoneWeightCollection> vc = (VertexChannel <BoneWeightCollection>)channel; foreach (BoneWeightCollection boneWeights in vc) { foreach (BoneWeight weight in boneWeights) { indexer.GetBoneIndex(weight.BoneName); } } } } } }
/// <summary> /// Go through the vertex channels in the geometry and replace the /// BoneWeightCollection objects with weight and index channels. /// </summary> /// <param name="geometry">The geometry to process.</param> /// <param name="vertexChannelIndex">The index of the vertex channel to process.</param> /// <param name="context">The processor context.</param> protected override void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context) { bool boneCollectionsWithZeroWeights = false; if (geometry.Vertices.Channels[vertexChannelIndex].Name == VertexChannelNames.Weights()) { int meshIndex = (int)geometry.Parent.OpaqueData["MeshIndex"]; BoneIndexer indexer = indexers[meshIndex]; // Skin channels are passed in from importers as BoneWeightCollection objects VertexChannel <BoneWeightCollection> vc = (VertexChannel <BoneWeightCollection>) geometry.Vertices.Channels[vertexChannelIndex]; int maxBonesPerVertex = 0; for (int i = 0; i < vc.Count; i++) { int count = vc[i].Count; if (count > maxBonesPerVertex) { maxBonesPerVertex = count; } } // Add weights as colors (Converts well to 4 floats) // and indices as packed 4byte vectors. Color[] weightsToAdd = new Color[vc.Count]; Byte4[] indicesToAdd = new Byte4[vc.Count]; // Go through the BoneWeightCollections and create a new // weightsToAdd and indicesToAdd array for each BoneWeightCollection. for (int i = 0; i < vc.Count; i++) { BoneWeightCollection bwc = vc[i]; if (bwc.Count == 0) { boneCollectionsWithZeroWeights = true; continue; } bwc.NormalizeWeights(4); int count = bwc.Count; if (count > maxBonesPerVertex) { maxBonesPerVertex = count; } // Add the appropriate bone indices based on the bone names in the // BoneWeightCollection Vector4 bi = new Vector4(); bi.X = count > 0 ? indexer.GetBoneIndex(bwc[0].BoneName) : (byte)0; bi.Y = count > 1 ? indexer.GetBoneIndex(bwc[1].BoneName) : (byte)0; bi.Z = count > 2 ? indexer.GetBoneIndex(bwc[2].BoneName) : (byte)0; bi.W = count > 3 ? indexer.GetBoneIndex(bwc[3].BoneName) : (byte)0; indicesToAdd[i] = new Byte4(bi); Vector4 bw = new Vector4(); bw.X = count > 0 ? bwc[0].Weight : 0; bw.Y = count > 1 ? bwc[1].Weight : 0; bw.Z = count > 2 ? bwc[2].Weight : 0; bw.W = count > 3 ? bwc[3].Weight : 0; weightsToAdd[i] = new Color(bw); } // Remove the old BoneWeightCollection channel geometry.Vertices.Channels.Remove(vc); // Add the new channels geometry.Vertices.Channels.Add <Byte4>(VertexElementUsage.BlendIndices.ToString(), indicesToAdd); geometry.Vertices.Channels.Add <Color>(VertexElementUsage.BlendWeight.ToString(), weightsToAdd); } else { // No skinning info, so we let the base class process the channel base.ProcessVertexChannel(geometry, vertexChannelIndex, context); } if (boneCollectionsWithZeroWeights) { context.Logger.LogWarning("", geometry.Identity, "BonesWeightCollections with zero weights found in geometry."); } }
/// <summary>Processes a SkinnedModelImporter NodeContent root</summary> /// <param name="input">The root of the X file tree</param> /// <param name="context">The context for this processor</param> /// <returns>A model with animation data on its tag</returns> public override ModelContent Process(NodeContent input, ContentProcessorContext context) { ModelSplitter splitter; if (context.TargetPlatform != TargetPlatform.Xbox360) { splitter = new ModelSplitter(input, 56); } else { splitter = new ModelSplitter(input, 40); } modelSplit = splitter.Split(); splitter = null; this.input = input; this.context = context; FindMeshes(input); indexers = new BoneIndexer[numMeshes]; for (int i = 0; i < indexers.Length; i++) { indexers[i] = new BoneIndexer(); } foreach (MeshContent meshContent in meshes) { CreatePaletteIndices(meshContent); } // Get the process model minus the animation data ModelContent c = base.Process(input, context); if (!modelSplit && input.OpaqueData.ContainsKey("AbsoluteMeshTransforms")) { absoluteMeshTransforms = (List <Matrix>)input.OpaqueData["AbsoluteMeshTransforms"]; } else { foreach (MeshContent mesh in meshes) { if (!ValidateMeshSkeleton(mesh)) { context.Logger.LogWarning(null, mesh.Identity, "Warning: Mesh found that has a parent that exists as " + "one of the bones in the skeleton attached to the mesh. Change the mesh " + "skeleton structure or use X - File Animation Library importer if transforms are incorrect."); } } } Dictionary <string, object> dict = new Dictionary <string, object>(); // Attach the animation and skinning data to the models tag FindAnimations(input); // Test to see if any animations have zero duration foreach (AnimationContent anim in animations.Values) { string errorMsg = "One or more AnimationContent objects have an extremely small duration. If the animation " + "was intended to last more than one frame, please add \n AnimTicksPerSecond \n{0} \nY; \n{1}\n to your .X " + "file, where Y is a positive integer."; if (anim.Duration.Ticks < ContentUtil.TICKS_PER_60FPS) { context.Logger.LogWarning("", anim.Identity, errorMsg, "{", "}"); break; } } XmlDocument xmlDoc = ReadAnimationXML(input); if (xmlDoc != null) { SubdivideAnimations(animations, xmlDoc); } AnimationContentDictionary processedAnims = new AnimationContentDictionary(); try { foreach (KeyValuePair <string, AnimationContent> animKey in animations) { AnimationContent processedAnim = ProcessAnimation(animKey.Value); processedAnims.Add(animKey.Key, processedAnim); } dict.Add("Animations", processedAnims); } catch { throw new Exception("Error processing animations."); } foreach (ModelMeshContent meshContent in c.Meshes) { ReplaceBasicEffects(meshContent); } skinInfo = ProcessSkinInfo(c); dict.Add("SkinInfo", skinInfo); c.Tag = dict; return(c); }