using SFML.Graphics; using SFML.System; using Spine; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SpineViewerCLI { public static class Extension { /// /// 获取一个对象副本, 继承所有状态 /// public static SpineObject Copy(this SpineObject self, bool keepTrackTime = false) { var spineObject = new SpineObject(self, true); // 拷贝轨道动画, 但是仅拷贝第一个条目 foreach (var tr in self.AnimationState.IterTracks().Where(t => t is not null)) { var t = spineObject.AnimationState.SetAnimation(tr!.TrackIndex, tr.Animation, tr.Loop); t.TimeScale = tr.TimeScale; t.Alpha = tr.Alpha; if (keepTrackTime) t.TrackTime = tr.TrackTime; } // XXX(#105): 部分 3.4.02 版本模型在设置动画后出现附件残留, 因此强制进行一次 Setup if (spineObject.Version == SpineVersion.V34) { spineObject.Skeleton.SetSlotsToSetupPose(); } spineObject.Update(0); return spineObject; } /// /// 获取当前状态包围盒 /// public static FloatRect GetCurrentBounds(this SpineObject self) { self.Skeleton.GetBounds(out var x, out var y, out var w, out var h); return new(x, y, Math.Max(w, 1e-6f), Math.Max(h, 1e-6f)); } /// /// 计算所有轨道第一个条目的动画时长最大值 /// /// /// public static float GetAnimationMaxDuration(this SpineObject self) { return self.AnimationState.IterTracks().Select(t => t?.Animation.Duration ?? 0).DefaultIfEmpty(0).Max(); } /// /// 合并另一个矩形 /// public static FloatRect Union(this FloatRect self, FloatRect rect) { float left = Math.Min(self.Left, rect.Left); float top = Math.Min(self.Top, rect.Top); float right = Math.Max(self.Left + self.Width, rect.Left + rect.Width); float bottom = Math.Max(self.Top + self.Height, rect.Top + rect.Height); return new(left, top, right - left, bottom - top); } /// /// 按给定的帧率获取所有轨道第一个条目动画全时长包围盒大小, 是一个耗时操作, 如果可能的话最好缓存结果 /// public static FloatRect GetAnimationBounds(this SpineObject self, float fps = 30) { using var copy = self.Copy(); var bounds = copy.GetCurrentBounds(); var maxDuration = copy.GetAnimationMaxDuration(); for (float tick = 0, delta = 1 / fps; tick < maxDuration; tick += delta) { bounds = bounds.Union(copy.GetCurrentBounds()); copy.Update(delta); } return bounds; } } }