private XmlElement MakeObjectNode(XmlDocument scml, Element element, FileIdProvider fileIdProvider)
        {
            var obj = scml.CreateElement(string.Empty, "object", string.Empty);

            obj.SetAttribute("folder", "0");
            obj.SetAttribute("file", GetThisOrPrecedingFile(element, fileIdProvider));
            return(obj);
        }
        private XmlElement MakeRootNode(XmlDocument scml)
        {
            var root = scml.CreateElement(string.Empty, "spriter_data", string.Empty);

            root.SetAttribute("scml_version", "1.0");
            root.SetAttribute("generator", "BrashMonkey Spriter");
            root.SetAttribute("generator_version", "r11");
            var fileIdProvider = new FileIdProvider(BuildFile);

            root.AppendChild(MakeFolderNode(scml, fileIdProvider));
            root.AppendChild(MakeEntityNode(scml, fileIdProvider));
            return(root);
        }
        private List <XmlElement> MakeTimelineNodes(XmlDocument scml, Bank bank, FileIdProvider fileIdProvider)
        {
            var rate             = (int)(MsPerS / bank.Rate);
            var spriteIdProvider = new SpriteIdProvider(bank, AnimationFile.AnimData.HashToName);
            var idToTimeline     = new Dictionary <int, XmlElement>();

            foreach (var name in spriteIdProvider.IdMap.Keys)
            {
                var timeline = scml.CreateElement(string.Empty, "timeline", string.Empty);
                var id       = spriteIdProvider.IdMap[name];
                timeline.SetAttribute("id", id.ToString());
                timeline.SetAttribute("name", name);
                idToTimeline.Add(id, timeline);
            }

            var symbolIdProvider = new SymbolIdProvider(bank, AnimationFile.AnimData.HashToName);

            foreach (var name in symbolIdProvider.IdMap.Keys)
            {
                var timeline = scml.CreateElement(string.Empty, "timeline", string.Empty);
                var id       = symbolIdProvider.IdMap[name];
                timeline.SetAttribute("id", (id + spriteIdProvider.IdMap.Count).ToString());
                timeline.SetAttribute("name", name);
                timeline.SetAttribute("object_type", "bone");
                idToTimeline.Add(id + spriteIdProvider.IdMap.Count, timeline);
            }

            var frameId = 0;

            foreach (var frame in bank.FramesList)
            {
                foreach (var element in frame.ElementsList)
                {
                    idToTimeline[spriteIdProvider.GetId(frame, element)].AppendChild(
                        MakeTimelineKeyNode(scml, element, frameId, rate, fileIdProvider, ChildType.Sprite));
                    idToTimeline[symbolIdProvider.GetId(frame, element) + spriteIdProvider.IdMap.Count]
                    .AppendChild(MakeTimelineKeyNode(scml, element, frameId, rate, fileIdProvider, ChildType.Bone));
                }

                frameId++;
            }

            var timelines = new List <XmlElement>();

            foreach (var timeline in idToTimeline.Values)
            {
                timelines.Add(timeline);
            }
            return(timelines);
        }
        private XmlElement MakeEntityNode(XmlDocument scml, FileIdProvider fileIdProvider)
        {
            var entity = scml.CreateElement(string.Empty, "entity", string.Empty);

            entity.SetAttribute("id", "0");
            entity.SetAttribute("name", BuildFile.BuildData.Build.Name);
            var animationId = 0;
            var boneNames   = new HashSet <string>();

            foreach (var bank in AnimationFile.AnimData.Animation.BanksList)
            {
                entity.AppendChild(MakeAnimationNode(scml, bank, animationId++, fileIdProvider, boneNames));
            }
            foreach (var boneName in boneNames)
            {
                entity.AppendChild(MakeObjectInfoNode(scml, boneName));
            }
            return(entity);
        }
        private XmlElement MakeTimelineKeyNode(XmlDocument scml, Element element, int frameId, int rate,
                                               FileIdProvider fileIdProvider, ChildType childType)
        {
            var key = scml.CreateElement(string.Empty, "key", string.Empty);

            key.SetAttribute("id", frameId.ToString());
            key.SetAttribute("time", (frameId * rate).ToString());
            switch (childType)
            {
            case ChildType.Sprite:
                key.AppendChild(MakeObjectNode(scml, element, fileIdProvider));
                break;

            case ChildType.Bone:
                key.AppendChild(MakeBoneNode(scml, element));
                break;
            }

            return(key);
        }
        private XmlElement MakeAnimationNode(XmlDocument scml, Bank bank, int animationId,
                                             FileIdProvider fileIdProvider, HashSet <string> boneNames)
        {
            var animation = scml.CreateElement(string.Empty, "animation", string.Empty);

            animation.SetAttribute("id", animationId.ToString());
            animation.SetAttribute("name", bank.Name);
            var rate = (int)(MsPerS / bank.Rate);

            animation.SetAttribute("length", (rate * bank.FrameCount).ToString());
            animation.SetAttribute("interval", rate.ToString());
            animation.AppendChild(MakeMainlineNode(scml, bank, boneNames));
            var timelines = MakeTimelineNodes(scml, bank, fileIdProvider);

            foreach (var timeline in timelines)
            {
                animation.AppendChild(timeline);
            }
            return(animation);
        }
        private XmlElement MakeFileNode(XmlDocument scml, Frame frame, string name, FileIdProvider fileIdProvider)
        {
            var imageWidth  = AtlasFile.Atlas.Width;
            var imageHeight = AtlasFile.Atlas.Height;
            var x           = frame.PivotX - frame.PivotWidth / 2f;
            var y           = frame.PivotY - frame.PivotHeight / 2f;
            var pivotX      = 0 - x / frame.PivotWidth;
            var pivotY      = 1 + y / frame.PivotHeight;
            var width       = (int)((frame.X2 - frame.X1) * imageWidth);
            var height      = (int)((frame.Y2 - frame.Y1) * imageHeight);

            var file = scml.CreateElement(string.Empty, "file", string.Empty);

            file.SetAttribute("id", fileIdProvider.NameToFileId[name].ToString());
            file.SetAttribute("name", name);
            file.SetAttribute("width", width.ToString());
            file.SetAttribute("height", height.ToString());
            file.SetAttribute("pivot_x", pivotX.ToString());
            file.SetAttribute("pivot_y", pivotY.ToString());
            return(file);
        }
        private string GetThisOrPrecedingFile(Animation.Element element, FileIdProvider fileIdProvider)
        {
            int index = element.Index;

            while (index >= 0)
            {
                string name = $"{AnimationFile.HashToName[element.Image]}_{index}";
                try
                {
                    return(fileIdProvider.NameToFileId[name].ToString());
                }
                catch
                {
                    index--;
                }
            }
            // If a file doesn't exist for the sprite then assume that this is intentional and return no corresponding file
            // Note that this is done because there are some sprites defined in Klei's animation files that do not have
            // any corresponding actual texture/file to go with.
            return("");
        }
        private XmlElement MakeFolderNode(XmlDocument scml, FileIdProvider fileIdProvider)
        {
            XmlElement folder = scml.CreateElement(string.Empty, "folder", string.Empty);

            folder.SetAttribute("id", "0");
            HashSet <string> names = new HashSet <string>();

            foreach (Build.Symbol symbol in BuildFile.Build.SymbolsList)
            {
                foreach (Build.Frame frame in symbol.FramesList)
                {
                    string name = BuildFile.HashToName[symbol.Hash] + '_' + frame.SourceFrameNum;
                    if (names.Contains(name))
                    {
                        continue;
                    }
                    names.Add(name);
                    folder.AppendChild(MakeFileNode(scml, symbol, frame, BuildFile, name, fileIdProvider));
                }
            }
            return(folder);
        }
        private XmlElement MakeFileNode(XmlDocument scml, Build.Symbol symbol, Build.Frame frame, Build.File BuildFile, string name, FileIdProvider fileIdProvider)
        {
            int   imageWidth  = AtlasFile.Atlas.Width;
            int   imageHeight = AtlasFile.Atlas.Height;
            float x           = frame.PivotX - frame.PivotWidth / 2f;
            float y           = frame.PivotY - frame.PivotHeight / 2f;
            float pivotX      = 0 - x / frame.PivotWidth;
            float pivotY      = 1 + y / frame.PivotHeight;
            int   width       = (int)((frame.X2 - frame.X1) * imageWidth);
            int   height      = (int)((frame.Y2 - frame.Y1) * imageHeight);

            XmlElement file = scml.CreateElement(string.Empty, "file", string.Empty);

            file.SetAttribute("id", fileIdProvider.NameToFileId[name].ToString());
            file.SetAttribute("name", name);
            file.SetAttribute("width", width.ToString());
            file.SetAttribute("height", height.ToString());
            file.SetAttribute("pivot_x", pivotX.ToString());
            file.SetAttribute("pivot_y", pivotY.ToString());
            return(file);
        }