PageRenderTime 36ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/XSpriterPipelineExtensions/SCMLProcessor.cs

https://bitbucket.org/dylanwolf/xspriter
C# | 294 lines | 239 code | 33 blank | 22 comment | 33 complexity | 60ca1a830fd617463d8889e6031671cf MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Diagnostics;
  5. using System.Globalization;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Threading;
  10. using System.Xml.Linq;
  11. using Microsoft.Xna.Framework;
  12. using Microsoft.Xna.Framework.Content.Pipeline;
  13. using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
  14. using Microsoft.Xna.Framework.Content.Pipeline.Processors;
  15. using System.Drawing;
  16. namespace FuncWorks.XNA.XSpriter
  17. {
  18. [ContentProcessor(DisplayName="XSpriter - Spriter SCML")]
  19. public class SCMLProcessor : ContentProcessor<XDocument, CharacterContent>
  20. {
  21. [DisplayName("Texture - Format")]
  22. [DefaultValue(TextureProcessorOutputFormat.Color)]
  23. [Description("Texture processor output format if loading textures")]
  24. public TextureProcessorOutputFormat TextureFormat { get; set; }
  25. [DisplayName("Texture - Premultiply Alpha")]
  26. [DefaultValue(true)]
  27. [Description("If true, texture is converted to premultiplied alpha format")]
  28. public Boolean PremultiplyAlpha { get; set; }
  29. [DisplayName("Animation FPS")]
  30. [DefaultValue(60)]
  31. [Description("Sets the number of frames per second that animations are processed into.")]
  32. public Int32 AnimationFPS { get; set; }
  33. public SCMLProcessor()
  34. {
  35. TextureFormat = TextureProcessorOutputFormat.Color;
  36. PremultiplyAlpha = true;
  37. AnimationFPS = 60;
  38. }
  39. public override CharacterContent Process(XDocument input, ContentProcessorContext context)
  40. {
  41. // Prevent parsing issues based on culture (i.e., decimals vs. commas in numbers)
  42. CultureInfo culture = Thread.CurrentThread.CurrentCulture;
  43. Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
  44. CharacterContent character = new CharacterContent();
  45. // Internal
  46. character.FramesPerSecond = AnimationFPS;
  47. // Textures
  48. // <folder> and <file> should be numbered in sequential order, so we shouldn't need
  49. // to get the id values here; <file> name attributes should also contain folder names
  50. //TODO: Future versions will support atlas images and sounds
  51. List<List<string>> textures = (from fld in input.Root.Elements("folder")
  52. select
  53. (
  54. from file in fld.Elements("file")
  55. select file.Attribute("name").Value
  56. ).ToList()
  57. ).ToList();
  58. character.Textures = (from fld in input.Root.Elements("folder")
  59. select
  60. (
  61. from file in fld.Elements("file")
  62. select XmlHelpers.FromImageXml(file, Path.GetDirectoryName(context.OutputFilename.Remove(0, context.OutputDirectory.Length)))
  63. ).ToArray()).ToArray();
  64. // Entities
  65. // Currently one entity per file, with no ID or name
  66. //TODO: Future versions may include more than one entity, with names
  67. // Animations
  68. List<AnimationContent> animations = new List<AnimationContent>();
  69. foreach (XElement xanim in input.Root.Element("entity").Elements("animation"))
  70. {
  71. // Make a dictionary of external timelines which get pulled into the mainline
  72. Dictionary<int, Dictionary<int, List<FrameImageContent>>> objCache = new Dictionary<int, Dictionary<int, List<FrameImageContent>>>();
  73. Dictionary<int, Dictionary<int, List<Bone>>> boneCache = new Dictionary<int, Dictionary<int, List<Bone>>>();
  74. foreach (XElement tl in xanim.Elements("timeline"))
  75. {
  76. int tlId = int.Parse(tl.Attribute("id").Value);
  77. if (!objCache.ContainsKey(tlId))
  78. {
  79. objCache[tlId] = new Dictionary<int, List<FrameImageContent>>();
  80. }
  81. if (!boneCache.ContainsKey(tlId))
  82. {
  83. boneCache[tlId] = new Dictionary<int, List<Bone>>();
  84. }
  85. foreach (XElement key in tl.Elements("key"))
  86. {
  87. int objId = int.Parse(key.Attribute("id").Value);
  88. objCache[tlId][objId] =
  89. (from obj in key.Elements("object") select XmlHelpers.FromObjectXml(obj, character.Textures, true)).ToList();
  90. boneCache[tlId][objId] =
  91. (from bone in key.Elements("bone") select XmlHelpers.FromBoneXml(bone)).ToList();
  92. }
  93. }
  94. // Build the animation itself
  95. AnimationContent anim = new AnimationContent()
  96. {
  97. Name = xanim.Attribute("name").Value,
  98. Length = long.Parse(xanim.Attribute("length").Value),
  99. Looping = XmlHelpers.GetBoolAttribute(xanim, "looping", true),
  100. };
  101. // Retrieve a list of keyframes from the mainline
  102. List<Keyframe> keyframes = new List<Keyframe>();
  103. foreach (XElement xkey in xanim.Element("mainline").Elements("key"))
  104. {
  105. Keyframe key = new Keyframe();
  106. key.Time = XmlHelpers.GetInt64Attribute(xkey, "time", 0);
  107. List<FrameImageContent> objs = new List<FrameImageContent>();
  108. List<Bone> bones = new List<Bone>();
  109. foreach (XElement xobj in xkey.DescendantNodes())
  110. {
  111. if (xobj.Name == "object")
  112. {
  113. objs.Add(XmlHelpers.FromObjectXml(xobj, character.Textures, false));
  114. }
  115. else if (xobj.Name == "bone")
  116. {
  117. bones.Add(XmlHelpers.FromBoneXml(xobj));
  118. }
  119. else if (xobj.Name == "object_ref")
  120. {
  121. int tlId = int.Parse(xobj.Attribute("timeline").Value);
  122. int keyId = int.Parse(xobj.Attribute("key").Value);
  123. int zindex = int.Parse(xobj.Attribute("z_index").Value);
  124. int parentId = XmlHelpers.GetInt32Attribute(xobj, "parent", -1);
  125. foreach (FrameImageContent fimg in objCache[tlId][keyId])
  126. {
  127. objs.Add(fimg.CloneFrameImage(zindex, tlId, parentId));
  128. }
  129. }
  130. else if (xobj.Name == "bone_ref")
  131. {
  132. int tlId = int.Parse(xobj.Attribute("timeline").Value);
  133. int keyId = int.Parse(xobj.Attribute("key").Value);
  134. int parentId = XmlHelpers.GetInt32Attribute(xobj, "parent", -1);
  135. foreach (Bone bone in boneCache[tlId][keyId])
  136. {
  137. bones.Add(bone.CloneBone(tlId, parentId));
  138. }
  139. }
  140. }
  141. // Save to object
  142. key.Objects = objs.ToArray();
  143. key.Bones = bones.OrderBy(x => x.Id).ToArray();
  144. keyframes.Add(key);
  145. }
  146. if (keyframes[keyframes.Count - 1].Time < anim.Length)
  147. {
  148. keyframes.Add(keyframes[0].Clone(anim.Length));
  149. }
  150. // Process into frames
  151. Int32 frameTimeStep = 1000/AnimationFPS;
  152. List<FrameContent> frames = new List<FrameContent>();
  153. for (int frameTime = 0; frameTime <= anim.Length; frameTime += frameTimeStep)
  154. {
  155. Keyframe currentFrame =
  156. (from kf in keyframes where kf.Time <= frameTime orderby kf.Time descending select kf).
  157. FirstOrDefault();
  158. Keyframe nextFrame =
  159. (from kf in keyframes where kf.Time > frameTime orderby kf.Time ascending select kf).
  160. FirstOrDefault();
  161. float timePct = (nextFrame != null) ?
  162. ((frameTime - currentFrame.Time) / (float)(nextFrame.Time - currentFrame.Time)) :
  163. 0;
  164. List<FrameImageContent> frameImages = new List<FrameImageContent>();
  165. foreach (FrameImageContent currentFrameImage in currentFrame.Objects)
  166. {
  167. FrameImageContent nextFrameImage = (currentFrameImage.Tweened && nextFrame != null && nextFrame.Objects.Where(x => x.TimelineId == currentFrameImage.TimelineId).Any())
  168. ? nextFrame.Objects.Where(x => x.TimelineId == currentFrameImage.TimelineId).Select(x => x).First()
  169. : null;
  170. // Tweening
  171. //TODO: Future versions will use tweening methods other than linear
  172. if (nextFrameImage != null)
  173. {
  174. frameImages.Add(currentFrameImage.Tween(nextFrameImage, timePct));
  175. }
  176. // Select last frame
  177. else
  178. {
  179. frameImages.Add(currentFrameImage.CloneFrameImage());
  180. }
  181. }
  182. List<Bone> bones = new List<Bone>();
  183. foreach (Bone currentBone in currentFrame.Bones)
  184. {
  185. Bone nextBone = (nextFrame != null &&
  186. nextFrame.Bones.Where(x => x.TimelineId == currentBone.TimelineId).Any())
  187. ? nextFrame.Bones.Where(x => x.TimelineId == currentBone.TimelineId)
  188. .Select(x => x)
  189. .First()
  190. : null;
  191. // Tweening
  192. if (nextBone != null)
  193. {
  194. bones.Add(currentBone.Tween(nextBone, timePct));
  195. }
  196. else
  197. {
  198. bones.Add(currentBone.CloneBone());
  199. }
  200. }
  201. FrameContent frame = new FrameContent() {Objects = frameImages.OrderBy(x => x.ZIndex).ToArray(), Bones = bones.OrderBy(x => x.Id).ToArray()};
  202. frames.Add(frame);
  203. }
  204. anim.Frames = frames.ToArray();
  205. // Build timeline/texture lookup
  206. anim.TextureTimelines = new Dictionary<string, int>();
  207. foreach (List<string> folder in textures)
  208. {
  209. foreach (string file in folder)
  210. {
  211. var textimeline = frames.SelectMany(x => x.Objects)
  212. .Where(x => x.TextureName != null && x.TextureName.Equals(file))
  213. .Select(x => x.TimelineId);
  214. if (textimeline.Any())
  215. {
  216. anim.TextureTimelines[file] = textimeline.First();
  217. }
  218. }
  219. }
  220. // Build timeline/bone lookup
  221. anim.BoneTimelines = new Dictionary<string, int>();
  222. foreach (Bone bone in frames.SelectMany(x => x.Bones))
  223. {
  224. if (!String.IsNullOrEmpty(bone.Name) && !anim.BoneTimelines.ContainsKey(bone.Name))
  225. {
  226. anim.BoneTimelines.Add(bone.Name, bone.TimelineId);
  227. }
  228. }
  229. animations.Add(anim);
  230. }
  231. character.Animations = new List<AnimationContent>();
  232. character.Animations.AddRange(animations);
  233. // Build external textures
  234. foreach (List<string> folder in textures)
  235. {
  236. foreach (string txfile in folder)
  237. {
  238. String assetName = Path.Combine(
  239. Path.GetDirectoryName(context.OutputFilename.Remove(0, context.OutputDirectory.Length)),
  240. Path.GetFileNameWithoutExtension(context.OutputFilename),
  241. textures.IndexOf(folder).ToString("00"),
  242. folder.IndexOf(txfile).ToString("00"));
  243. String sourceName = Path.Combine(
  244. Path.GetDirectoryName(context.OutputFilename.Remove(0, context.OutputDirectory.Length)),
  245. txfile
  246. );
  247. OpaqueDataDictionary data = new OpaqueDataDictionary();
  248. data.Add("GenerateMipmaps", false);
  249. data.Add("ResizeToPowerOfTwo", false);
  250. data.Add("PremultiplyAlpha", PremultiplyAlpha);
  251. data.Add("TextureFormat", TextureFormat);
  252. context.BuildAsset<TextureContent, TextureContent>(
  253. new ExternalReference<TextureContent>(sourceName),
  254. "TextureProcessor", data, "TextureImporter", assetName);
  255. }
  256. }
  257. Thread.CurrentThread.CurrentCulture = culture;
  258. return character;
  259. }
  260. }
  261. }