internal SkinnedModelContent(ModelContent model, SkinnedModelBoneContentCollection skeleton, AnimationClipContentDictionary animationClips) { this.model = model; this.skeleton = skeleton; this.animationClips = animationClips; }
public HeightMapInfoContent(ModelContent model, MeshContent terrainMesh, float terrainScale, int terrainWidth, int terrainLength) { Model = model; if (terrainMesh == null) { throw new ArgumentNullException("terrainMesh"); } if (terrainWidth <= 0) { throw new ArgumentOutOfRangeException("terrainWidth"); } if (terrainLength <= 0) { throw new ArgumentOutOfRangeException("terrainLength"); } TerrainScale = terrainScale; Height = new float[terrainWidth, terrainLength]; Normals = new Vector3[terrainWidth, terrainLength]; GeometryContent item = terrainMesh.Geometry[0]; for (int i = 0; i < item.Vertices.VertexCount; i++) { Vector3 vector3 = item.Vertices.Positions[i]; Vector3 item1 = (Vector3)item.Vertices.Channels[VertexChannelNames.Normal()][i]; int x = (int)(vector3.X / terrainScale + (terrainWidth - 1) / 2f); int z = (int)(vector3.Z / terrainScale + (terrainLength - 1) / 2f); Height[x, z] = vector3.Y; Normals[x, z] = item1; } }
public override ModelContent Process(NodeContent input, ContentProcessorContext context) { CompileRegularExpressions(); context.Logger.LogMessage("Output Platform: {0}", context.TargetPlatform); maxScale_ = 0; maxOffset_ = 0; BoneContent skeleton = MeshHelper.FindSkeleton(input); FlattenTransforms(input, skeleton, context); SkinnedBone[] inverseBindPose = GetInverseBindPose(input, context, skeleton); context.Logger.LogMessage("Found {0} skinned bones in skeleton.", (inverseBindPose == null) ? 0 : inverseBindPose.Length); ModelContent output = base.Process(input, context); if (output.Tag == null) { output.Tag = new Dictionary <string, object>(); } if (FoundSkinning) { #if DEBUG StringBuilder strb = new StringBuilder(); #endif if (inverseBindPose == null) { throw new System.Exception("Could not find skeleton although there is skinned data."); } for (int i = 0; i != inverseBindPose.Length; ++i) { SkinnedBone sb = inverseBindPose[i]; int q = 0; sb.Index = -1; foreach (ModelBoneContent mbc in output.Bones) { if (mbc.Name == sb.Name) { sb.Index = mbc.Index; break; } ++q; } if (sb.Index == -1) { throw new System.ArgumentException( String.Format("Can't find the index for animated bone named {0}.", sb.Name)); } inverseBindPose[i] = sb; } ((Dictionary <string, object>)output.Tag).Add("InverseBindPose", inverseBindPose); } ((Dictionary <string, object>)output.Tag).Add("AnimationSet", BuildAnimationSet(input, ref output, context)); ((Dictionary <string, object>)output.Tag).Add("BoundsInfo", new BoundsInfo(maxScale_, maxOffset_)); return(output); }
public override ModelContent Process(NodeContent input, ContentProcessorContext context) { model = base.Process(input, context); AnimationClips clips = ProcessAnimations(model, input, context); model.Tag = clips; return model; }
/// <summary> /// Calculate a bounding box for the model. /// </summary> /// <param name="model">The model to calculate AABBs for</param> public static void CalculateBoundingBox(NodeContent input, ModelContent model) { BoundingBox box = new BoundingBox(); CalculateBoundingBox(input, ref box); if (model.Tag == null) model.Tag = new Dictionary<string, object>(); (model.Tag as Dictionary<string, object>).Add("BoundingBox", box); }
public override ModelContent Process(NodeContent input, ContentProcessorContext context) { BoneContent skeleton = ProcessSkeleton(input); SwapSkinMaterials(input); model = base.Process(input, context); ProcessAnimations(model, input, context); // Add the extra content to the model model.Tag = modelExtra; return model; }
/// <summary> /// The function to process a model from original content into model content for export /// </summary> /// <param name="input"></param> /// <param name="context"></param> /// <returns></returns> public override ModelContent Process(NodeContent input, ContentProcessorContext context) { // Process the skeleton for skinned character animation BoneContent skeleton = ProcessSkeleton(input); SwapSkinnedMaterial(input); model = base.Process(input, context); ProcessAnimations(model, input, context,input.Identity); // Add the extra content to the model model.Tag = modelExtra; return model; }
private BoundingBox GetBoundingBox(ModelContent model) { Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue); foreach (ModelMeshContent mesh in model.Meshes) { foreach (ModelMeshPartContent part in mesh.MeshParts) { for (int i = 0; i < part.NumVertices; i++) { Vector3 pos = GetVertexPosition(part.VertexBuffer, i); min = Vector3.Min(min, pos); max = Vector3.Max(max, pos); } } } return(new BoundingBox(min, max)); }
/// <summary> /// Entry point for animation processing. /// </summary> /// <param name="model"></param> /// <param name="input"></param> /// <param name="context"></param> private void ProcessAnimations(ModelContent model, NodeContent input, ContentProcessorContext context) { // First build a lookup table so we can determine the // index into the list of bones from a bone name. for (int i = 0; i < model.Bones.Count; i++) { bones[model.Bones[i].Name] = i; } // For saving the bone transforms boneTransforms = new Matrix[model.Bones.Count]; // // Collect up all of the animation data // ProcessAnimationsRecursive(input); // Ensure there is always a clip, even if none is included in the FBX // That way we can create poses using FBX files as one-frame // animation clips if (modelExtra.Clips.Count == 0) { AnimationClip clip = new AnimationClip(); modelExtra.Clips.Add(clip); string clipName = "Take 001"; // Retain by name clips[clipName] = clip; clip.Name = clipName; foreach (ModelBoneContent bone in model.Bones) { AnimationClip.Bone clipBone = new AnimationClip.Bone(); clipBone.Name = bone.Name; clip.Bones.Add(clipBone); } } // Ensure all animations have a first key frame for every bone foreach (AnimationClip clip in modelExtra.Clips) { foreach (int b in bones.Values) { List<AnimationClip.Keyframe> keyframes = clip.Bones[b].Keyframes; if (keyframes.Count == 0 || keyframes[0].Time > 0) { AnimationClip.Keyframe keyframe = new AnimationClip.Keyframe(); keyframe.Time = 0; keyframe.SetTransform(boneTransforms[b]); keyframes.Insert(0, keyframe); } } } }
private ModelContent SkinnedData(ModelContent model, NodeContent input, ContentProcessorContext context, MeshMetaData metadata) { // Find the skeleton BoneContent skeleton = MeshHelper.FindSkeleton(input); if (skeleton != null) { // Read the bind pose and skeleton hierarchy data. IList<BoneContent> bones = MeshHelper.FlattenSkeleton(skeleton); // Set up the bone matrices and index lists List<Matrix> bindPose = new List<Matrix>(); List<Matrix> inverseBindPose = new List<Matrix>(); List<int> skeletonHierarchy = new List<int>(); Dictionary<String, int> boneIndices = new Dictionary<String, int>(); Dictionary<String, AnimationClip> animationClips = new Dictionary<String, AnimationClip>(); // Extract the bind pose transforms, inverse bind pose transforms, // and parent bone index of each bone in order foreach (BoneContent bone in bones) { bindPose.Add(bone.Transform); inverseBindPose.Add(Matrix.Invert(bone.AbsoluteTransform)); skeletonHierarchy.Add(bones.IndexOf(bone.Parent as BoneContent)); boneIndices.Add(bone.Name, boneIndices.Count); } // Convert animation data to our runtime format. animationClips = ProcessAnimations(skeleton.Animations, bones); // Save skinndd data metadata.SkinningData = new SkinningData(animationClips, bindPose, inverseBindPose, skeletonHierarchy, boneIndices); } // Save meta data model.Tag = metadata; // Return the model with the custom animation data return model; }
/// <summary> /// The workhorse of the animation processor. It loops through all /// animations, all tracks, and all keyframes, and converts to the format /// expected by the runtime animation classes. /// </summary> /// <param name="input">The NodeContent to process. Comes from the base ModelProcessor.</param> /// <param name="output">The ModelContent that was produced. You don't typically change this.</param> /// <param name="context">The build context (logger, etc).</param> /// <returns>An allocated AnimationSet with the animations to include.</returns> public virtual AnimationSet BuildAnimationSet(NodeContent input, ref ModelContent output, ContentProcessorContext context) { AnimationSet ret = new AnimationSet(); if (!DoAnimations) { context.Logger.LogImportantMessage("DoAnimation is set to false for {0}; not generating animations.", input.Name); return(ret); } // go from name to index Dictionary <string, ModelBoneContent> nameToIndex = new Dictionary <string, ModelBoneContent>(); foreach (ModelBoneContent mbc in output.Bones) { nameToIndex.Add(GetBoneName(mbc), mbc); } AnimationContentDictionary adict = MergeAnimatedBones(input); if (adict == null || adict.Count == 0) { context.Logger.LogWarning("http://kwxport.sourceforge.net/", input.Identity, "Model processed with AnimationProcessor has no animations."); return(ret); } foreach (AnimationContent ac in adict.Values) { if (!IncludeAnimation(ac)) { context.Logger.LogImportantMessage(String.Format("Not including animation named {0}.", ac.Name)); continue; } context.Logger.LogImportantMessage( "Processing animation {0} duration {1} sample rate {2} reduction tolerance {3}.", ac.Name, ac.Duration, SampleRate, Tolerance); AnimationChannelDictionary acdict = ac.Channels; AnimationTrackDictionary tracks = new AnimationTrackDictionary(); foreach (string name in acdict.Keys) { if (!IncludeTrack(ac, name)) { context.Logger.LogImportantMessage(String.Format("Not including track named {0}.", name)); continue; } int ix = 0; AnimationChannel achan = acdict[name]; int bix = nameToIndex[name].Index; context.Logger.LogMessage("Processing bone {0}:{1}.", name, bix); AnimationTrack at; if (tracks.TryGetValue(bix, out at)) { throw new System.ArgumentException( String.Format("Bone index {0} is used by multiple animations in the same clip (name {1}).", bix, name)); } // Sample at given frame rate from 0 .. Duration List <Keyframe> kfl = new List <Keyframe>(); int nFrames = (int)Math.Floor(ac.Duration.TotalSeconds * SampleRate + 0.5); for (int i = 0; i < nFrames; ++i) { Keyframe k = SampleChannel(achan, i / SampleRate, ref ix); kfl.Add(k); } // Run keyframe elimitation Keyframe[] frames = kfl.ToArray(); int nReduced = 0; if (tolerance_ > 0) { nReduced = ReduceKeyframes(frames, tolerance_); } if (nReduced > 0) { context.Logger.LogMessage("Reduced '{2}' from {0} to {1} frames.", frames.Length, frames.Length - nReduced, name); } // Create an AnimationTrack at = new AnimationTrack(bix, frames); tracks.Add(bix, at); } Animation a = new Animation(ac.Name, tracks, SampleRate); ret.AddAnimation(a); } // build the special "identity" and "bind pose" animations AnimationTrackDictionary atd_id = new AnimationTrackDictionary(); AnimationTrackDictionary atd_bind = new AnimationTrackDictionary(); foreach (KeyValuePair <string, ModelBoneContent> nip in nameToIndex) { Keyframe[] frames_id = new Keyframe[2]; frames_id[0] = new Keyframe(); frames_id[1] = new Keyframe(); AnimationTrack at_id = new AnimationTrack(nip.Value.Index, frames_id); atd_id.Add(nip.Value.Index, at_id); Keyframe[] frames_bind = new Keyframe[2]; Matrix mat = nip.Value.Transform; frames_bind[0] = Keyframe.CreateFromMatrix(mat); frames_bind[1] = new Keyframe(); frames_bind[1].CopyFrom(frames_bind[0]); AnimationTrack at_bind = new AnimationTrack(nip.Value.Index, frames_bind); atd_bind.Add(nip.Value.Index, at_bind); } ret.AddAnimation(new Animation("$id$", atd_id, 1.0f)); ret.AddAnimation(new Animation("$bind$", atd_bind, 1.0f)); return(ret); }
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 SkinInfoContentCollection[] ProcessSkinInfo(ModelContent model) { SkinInfoContentCollection[] info = new SkinInfoContentCollection[model.Meshes.Count]; Dictionary<string, int> boneDict = new Dictionary<string,int>(); //if (model.Bones.Count > maximumNumberOfPCBones) //{ // throw new Exception("There are too many bones! You have " + model.Bones.Count + " and you can only have " + maximumNumberOfPCBones); //} 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(); try { content.BoneIndex = boneDict[name]; } catch (KeyNotFoundException knfe) { string error = "Could not find a bone by the name of " + name + ". This is skinned bone index " + j + "."; error += "\nThere are " + skinnedBoneNames.Count + " skinned bones:"; for (int boneIndex = 0; boneIndex < skinnedBoneNames.Count; boneIndex++) { error += "\n " + skinnedBoneNames[boneIndex]; } error += "\nThere are " + model.Bones.Count + " bones:"; foreach (ModelBoneContent b in model.Bones) { error += "\n " + b.Name; } throw new KeyNotFoundException(error); } content.PaletteIndex = indexer.GetBoneIndex(name); content.InverseBindPoseTransform = absoluteMeshTransform * Matrix.Invert(absoluteTransforms[boneDict[name]]); content.BoneName = name; info[i].Add(content); } } return info; }
/// <summary> /// The workhorse of the animation processor. It loops through all /// animations, all tracks, and all keyframes, and converts to the format /// expected by the runtime animation classes. /// </summary> /// <param name="input">The NodeContent to process. Comes from the base ModelProcessor.</param> /// <param name="output">The ModelContent that was produced. You don't typically change this.</param> /// <param name="context">The build context (logger, etc).</param> /// <returns>An allocated AnimationSet with the animations to include.</returns> public virtual AnimationSet BuildAnimationSet(NodeContent input, ref ModelContent output, ContentProcessorContext context) { AnimationSet ret = new AnimationSet(); if (!DoAnimations) { context.Logger.LogImportantMessage("DoAnimation is set to false for {0}; not generating animations.", input.Name); return ret; } // go from name to index Dictionary<string, ModelBoneContent> nameToIndex = new Dictionary<string, ModelBoneContent>(); foreach (ModelBoneContent mbc in output.Bones) nameToIndex.Add(GetBoneName(mbc), mbc); AnimationContentDictionary adict = MergeAnimatedBones(input); if (adict == null || adict.Count == 0) { context.Logger.LogWarning("http://kwxport.sourceforge.net/", input.Identity, "Model processed with AnimationProcessor has no animations."); return ret; } foreach (AnimationContent ac in adict.Values) { if (!IncludeAnimation(ac)) { context.Logger.LogImportantMessage(String.Format("Not including animation named {0}.", ac.Name)); continue; } context.Logger.LogImportantMessage( "Processing animation {0} duration {1} sample rate {2} reduction tolerance {3}.", ac.Name, ac.Duration, SampleRate, Tolerance); AnimationChannelDictionary acdict = ac.Channels; AnimationTrackDictionary tracks = new AnimationTrackDictionary(); TimeSpan longestUniqueDuration = new TimeSpan(0); foreach (string name in acdict.Keys) { if (!IncludeTrack(name)) { context.Logger.LogImportantMessage(String.Format("Not including track named {0}.", name)); continue; } int ix = 0; AnimationChannel achan = acdict[name]; int bix = nameToIndex[name].Index; context.Logger.LogMessage("Processing bone {0}:{1}.", name, bix); AnimationTrack at; if (tracks.TryGetValue(bix, out at)) { throw new System.ArgumentException( String.Format("Bone index {0} is used by multiple animations in the same clip (name {1}).", bix, name)); } // Sample at given frame rate from 0 .. Duration List<Keyframe> kfl = new List<Keyframe>(); int nFrames = (int)Math.Floor(ac.Duration.TotalSeconds * SampleRate + 0.5); for (int i = 0; i < nFrames; ++i) { Keyframe k = SampleChannel(achan, i / SampleRate, ref ix); kfl.Add(k); } // Run keyframe elimitation Keyframe[] frames = kfl.ToArray(); int nReduced = 0; if (tolerance_ > 0) nReduced = ReduceKeyframes(frames, tolerance_); if (nReduced > 0) context.Logger.LogMessage("Reduced '{2}' from {0} to {1} frames.", frames.Length, frames.Length - nReduced, name); // Create an AnimationTrack at = new AnimationTrack(bix, frames); Debug.Assert(name != null); at.Name = name; tracks.Add(bix, at); } if (ShouldTrimAnimation(ac)) { TrimAnimationTracks(ac.Name, tracks, context); } Animation a = new Animation(ac.Name, tracks, SampleRate); ret.AddAnimation(a); } // build the special "identity" and "bind pose" animations AnimationTrackDictionary atd_id = new AnimationTrackDictionary(); AnimationTrackDictionary atd_bind = new AnimationTrackDictionary(); foreach (KeyValuePair<string, ModelBoneContent> nip in nameToIndex) { if (!IncludeTrack(nip.Key)) continue; Keyframe[] frames_id = new Keyframe[2]; frames_id[0] = new Keyframe(); frames_id[1] = new Keyframe(); AnimationTrack at_id = new AnimationTrack(nip.Value.Index, frames_id); at_id.Name = nip.Key; atd_id.Add(nip.Value.Index, at_id); Keyframe[] frames_bind = new Keyframe[2]; Matrix mat = nip.Value.Transform; frames_bind[0] = Keyframe.CreateFromMatrix(mat); frames_bind[1] = new Keyframe(); frames_bind[1].CopyFrom(frames_bind[0]); AnimationTrack at_bind = new AnimationTrack(nip.Value.Index, frames_bind); at_bind.Name = nip.Key; atd_bind.Add(nip.Value.Index, at_bind); } ret.AddAnimation(new Animation("$id$", atd_id, 1.0f)); ret.AddAnimation(new Animation("$bind$", atd_bind, 1.0f)); return ret; }
public InstancedSkinnedModelContent(ModelContent model, InstancedSkinningDataContent instancedSkinningInfo) { this.modelContent = model; this.instancedSkinningData = instancedSkinningInfo; }
/// <summary> /// The function to process a model from original content into model content for export /// </summary> /// <param name="input"></param> /// <param name="context"></param> /// <returns></returns> public override ModelContent Process(NodeContent input, ContentProcessorContext context) { if (input == null) throw new ArgumentNullException("input"); directory = Path.GetDirectoryName(input.Identity.SourceFilename); LookUpTextures(input); // Process the skeleton for skinned character animation BoneContent skeleton = ProcessSkeleton(input); SwapSkinnedMaterial(input); model = base.Process(input, context); ProcessAnimations(model, input, context,input.Identity); List<Vector3> vertices = new List<Vector3>(); AddVerticesToList(input, vertices); modelExtra.boundingBox = BoundingBox.CreateFromPoints(vertices); modelExtra.boundingSphere = BoundingSphere.CreateFromPoints(vertices); // Add the extra content to the model model.Tag = modelExtra; List<Vector3[]> meshVertices = new List<Vector3[]>(); meshVertices = AddModelMeshVertexArrayToList(input, meshVertices); int i = 0; foreach (ModelMeshContent mesh in model.Meshes) { List<Vector3> modelMeshVertices = new List<Vector3>(); foreach (ModelMeshPartContent part in mesh.MeshParts) { if (i < meshVertices.Count) modelMeshVertices.AddRange(meshVertices[i++]); } mesh.Tag = modelMeshVertices.ToArray(); } return model; }
public override ModelContent Process(NodeContent input, ContentProcessorContext context) { if (skinned) { ProcessSkeleton(input); } model = base.Process(input, context); AnimationClips clips = ProcessAnimations(model, input, context); clips.SkelToBone = skelToBone; model.Tag = clips; return model; }
private AnimationClips ProcessAnimations(ModelContent model, NodeContent input, ContentProcessorContext context) { //first build a lookup table so we can determine the index into the list of bones from a bone name for (int i = 0; i < model.Bones.Count; i++) { bones[model.Bones[i].Name] = i; } AnimationClips animationClips = new AnimationClips(); ProcessAnimationRecursive(input, animationClips); return animationClips; }
/// <summary> /// Converts an intermediate format content pipeline AnimationContent /// object to our runtime AnimationClip format. /// </summary> static ModelAnimationClip ProcessAnimation( string animationName, Dictionary<string, int> boneMap, NodeContent input, ModelContent model) { List<ModelKeyframe> keyframes = new List<ModelKeyframe>(); TimeSpan duration = TimeSpan.Zero; AddTransformationNodes(animationName, boneMap, input, keyframes, ref duration); // Sort the merged keyframes by time. keyframes.Sort(CompareKeyframeTimes); if (keyframes.Count == 0) throw new InvalidContentException("Animation has no keyframes."); if (duration <= TimeSpan.Zero) throw new InvalidContentException("Animation has a zero duration."); return new ModelAnimationClip(duration, keyframes); }
/// <summary> /// Converts an intermediate format content pipeline AnimationContentDictionary /// object to our runtime AnimationClip format. /// </summary> static void ProcessAnimations( NodeContent input, ModelContent model, Dictionary<string, ModelAnimationClip> animationClips, Dictionary<string, ModelAnimationClip> rootClips) { // Build up a table mapping bone names to indices. Dictionary<string, int> boneMap = new Dictionary<string, int>(); for (int i = 0; i < model.Bones.Count; i++) { string boneName = model.Bones[i].Name; if (!string.IsNullOrEmpty(boneName)) boneMap.Add(boneName, i); } // Convert each animation in the root of the object foreach (KeyValuePair<string, AnimationContent> animation in input.Animations) { ModelAnimationClip processed = ProcessRootAnimation(animation.Value, model.Bones[0].Name); rootClips.Add(animation.Key, processed); } // Get the unique names of the animations on the mesh children List<string> animationNames = new List<string>(); AddAnimationNodes(animationNames, input); // Now create those animations foreach (string key in animationNames) { ModelAnimationClip processed = ProcessAnimation(key, boneMap, input, model); animationClips.Add(key, processed); } }
private AnimationClips ProcessAnimations(ModelContent model, NodeContent input, ContentProcessorContext context) { // First build a lookup table so we can determine the // index into the list of bones from a bone name. for (int i = 0; i < model.Bones.Count; i++) { bones[model.Bones[i].Name] = i; } AnimationClips animationClips = new AnimationClips(); ProcessAnimationsRecursive(input, animationClips); // Ensure all animations have a first key frame for every bone foreach (AnimationClips.Clip clip in animationClips.Clips.Values) { for (int b = 0; b < bones.Count; b++) { List<AnimationClips.Keyframe> keyframes = clip.Keyframes[b]; if (keyframes.Count == 0 || keyframes[0].Time > 0) { AnimationClips.Keyframe keyframe = new AnimationClips.Keyframe(); keyframe.Time = 0; Matrix transform = model.Bones[b].Transform; transform.Right = Vector3.Normalize(transform.Right); transform.Up = Vector3.Normalize(transform.Up); transform.Backward = Vector3.Normalize(transform.Backward); keyframe.Rotation = Quaternion.CreateFromRotationMatrix(transform); keyframe.Translation = transform.Translation; keyframes.Insert(0, keyframe); } } } return animationClips; }
private void FlattenNormals(ModelContent model) { //This stuff re-calculates surface normals to give a low-poly flat shading effect foreach (ModelMeshContent mesh in model.Meshes) { foreach (ModelMeshPartContent part in mesh.MeshParts) { IndexCollection indices = part.IndexBuffer; for (int i = 0; i < indices.Count; i += 3) { Vector3 p1 = GetVertexPosition(part.VertexBuffer, indices[i + 0]); Vector3 p2 = GetVertexPosition(part.VertexBuffer, indices[i + 1]); Vector3 p3 = GetVertexPosition(part.VertexBuffer, indices[i + 2]); Vector3 v1 = p2 - p1; Vector3 v2 = p3 - p1; Vector3 normal = Vector3.Cross(v1, v2); normal.Normalize(); for (int j = 0; j < 3; j++) { for (int k = 0; k < 3; k++) { SetVertexNormalCoord(part.VertexBuffer, i + j, normal, k); } } } /* * byte[] vertices = part.VertexBuffer.VertexData; * //We only have the packed vertex data available so we need to parse the individual bytes * for (int i = 0; i < part.NumVertices; i++) * { * float[] pos = new float[3]; * float[] normal = new float[3]; * * for (int j = 0; j < 3; j++) * { * int posOffset = i * (int)part.VertexBuffer.VertexDeclaration.VertexStride + j * 4; //4 bytes per float * int normalOffset = posOffset + 12; //Normal is after the 3 position words * * pos[j] = System.BitConverter.ToSingle(vertices, posOffset); * normal[j] = System.BitConverter.ToSingle(vertices, normalOffset); * } * * Console.WriteLine("====="); * }*/ //byte[] newArray = new[] { vertices[3], vertices[2], vertices[1], vertices[0] }; /*short[] indices = new short[part.IndexBuffer.IndexCount]; * part.IndexBuffer.GetData<short>(indices); * * for (int i = 0; i < indices.Length; i += 3) * { * Vector3 p1 = vertices[indices[i]].Position; * Vector3 p2 = vertices[indices[i + 1]].Position; * Vector3 p3 = vertices[indices[i + 2]].Position; * * Vector3 v1 = p2 - p1; * Vector3 v2 = p3 - p1; * Vector3 normal = Vector3.Cross(v1, v2); * * normal.Normalize(); * * vertices[indices[i]].Normal = normal; * vertices[indices[i + 1]].Normal = normal; * vertices[indices[i + 2]].Normal = normal; * } * * part.VertexBuffer.SetData<VertexPositionNormalTexture>(vertices);*/ } } }
/// <summary> /// Entry point for animation processing. /// </summary> /// <param name="model"></param> /// <param name="input"></param> /// <param name="context"></param> private void ProcessAnimations(ModelContent model, NodeContent input, ContentProcessorContext context, ContentIdentity sourceIdentity) { // First build a lookup table so we can determine the // index into the list of bones from a bone name. for (int i = 0; i < model.Bones.Count; i++) { bones[model.Bones[i].Name] = i; } // For saving the bone transforms boneTransforms = new Matrix[model.Bones.Count]; // // Collect up all of the animation data // ProcessAnimationsRecursive(input); // Check to see if there's an animation clip definition // Here, we're checking for a file with the _Anims suffix. // So, if your model is named dude.fbx, we'd add dude_Anims.xml in the same folder // and the pipeline will see the file and use it to override the animations in the // original model file. string SourceModelFile = sourceIdentity.SourceFilename; string SourcePath = Path.GetDirectoryName(SourceModelFile); string AnimFilename = Path.GetFileNameWithoutExtension(SourceModelFile); AnimFilename += "_Anims.xml"; string AnimPath = Path.Combine(SourcePath, AnimFilename); if (File.Exists(AnimPath)) { // Add the filename as a dependency, so if it changes, the model is rebuilt context.AddDependency(AnimPath); // Load the animation definition from the XML file AnimationDefinition AnimDef = context.BuildAndLoadAsset<XmlImporter, AnimationDefinition>(new ExternalReference<XmlImporter>(AnimPath), null); if (modelExtra.Clips.Count > 0) //if there are some animations in our model { foreach (AnimationDefinition.ClipPart Part in AnimDef.ClipParts) { // Grab the main clip that we are using and copy to MainClip AnimationClip MainClip = new AnimationClip(); float StartTime = GetTimeSpanForFrame(Part.StartFrame, AnimDef.OriginalFrameCount, modelExtra.Clips[AnimDef.OriginalClipName].Duration); float EndTime = GetTimeSpanForFrame(Part.EndFrame, AnimDef.OriginalFrameCount, modelExtra.Clips[AnimDef.OriginalClipName].Duration); MainClip.Duration = EndTime-StartTime; MainClip.Name = modelExtra.Clips[AnimDef.OriginalClipName].Name; // Process each of our new animation clip parts for (int i = 0; i < modelExtra.Clips[AnimDef.OriginalClipName].Bones.Count; i++) { AnimationClip.Bone clipBone = new AnimationClip.Bone(); clipBone.Name = modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Name; LinkedList<AnimationClip.Keyframe> keyframes = new LinkedList<AnimationClip.Keyframe>(); if (modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes.Count != 0) { for (int j = 0; j < modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes.Count; j++) { if ((modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes[j].Time >= StartTime) && (modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes[j].Time <= EndTime)) { AnimationClip.Keyframe frame = new AnimationClip.Keyframe(); frame.Rotation = modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes[j].Rotation; frame.Time = modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes[j].Time - StartTime; frame.Translation = modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes[j].Translation; keyframes.AddLast(frame); //clipBone.Keyframes.Add(frame); } } } // LinearKeyframeReduction(keyframes); clipBone.Keyframes = keyframes.ToList<AnimationClip.Keyframe>(); MainClip.Bones.Add(clipBone); } modelExtra.Clips.Add(Part.ClipName, MainClip); } } } // Ensure there is always a clip, even if none is included in the FBX // That way we can create poses using FBX files as one-frame // animation clips if (modelExtra.Clips.Count == 0) { AnimationClip clip = new AnimationClip(); modelExtra.Clips.Add("Take 001",clip); string clipName = "Take 001"; // Retain by name clips[clipName] = clip; clip.Name = clipName; foreach (ModelBoneContent bone in model.Bones) { AnimationClip.Bone clipBone = new AnimationClip.Bone(); clipBone.Name = bone.Name; clip.Bones.Add(clipBone); } } //Ensure all animations have a first key frame for every bone foreach (KeyValuePair<string,AnimationClip> clip in modelExtra.Clips) { for (int b = 0; b < bones.Count; b++) { List<AnimationClip.Keyframe> keyframes = clip.Value.Bones[b].Keyframes; if (keyframes.Count == 0 || keyframes[0].Time > 0) { AnimationClip.Keyframe keyframe = new AnimationClip.Keyframe(); keyframe.Time = 0; keyframe.Transform = boneTransforms[b]; keyframes.Insert(0, keyframe); } } } }
private static void FindTextureName(ModelContent model, ContentProcessorContext context, MeshData meshData) { string[] dirSeparators = { context.OutputDirectory }; int counter = 0; for (int i = 0; i < model.Meshes.Count; i++) { ModelMeshContent mesh = model.Meshes[i]; for (int j = 0; j < mesh.MeshParts.Count; j++) { ModelMeshPartContent part = mesh.MeshParts[j]; SubMeshData subData = meshData.SubMeshDatas[counter]; foreach (KeyValuePair<string, ExternalReference<TextureContent>> textureRef in part.Material.Textures) { ExternalReference<TextureContent> texture = textureRef.Value; string textureName = texture.Filename.Split(dirSeparators, StringSplitOptions.RemoveEmptyEntries)[0]; string finalName = textureName.Split(ExtensionSeparators, StringSplitOptions.RemoveEmptyEntries)[0]; subData.TexturesName.Add(textureRef.Key, finalName); } counter++; } } }