/****************************************************************************** * Spine Runtimes License Agreement * Last updated January 1, 2020. Replaces all prior versions. * * Copyright (c) 2013-2020, Esoteric Software LLC * * Integration of the Spine Runtimes into software or otherwise creating * derivative works of the Spine Runtimes is permitted under the terms and * conditions of Section 2 of the Spine Editor License Agreement: * http://esotericsoftware.com/spine-editor-license * * Otherwise, it is permitted to integrate the Spine Runtimes into software * or otherwise create derivative works of the Spine Runtimes (collectively, * "Products"), provided that each user of the Products must obtain their own * Spine Editor license and redistribution of the Products in any form must * include this license and copyright notice. * * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ using System; using System.Collections; using System.Collections.Generic; using Spine.Collections; namespace Spine { /// Stores attachments by slot index and attachment name. /// See SkeletonData , Skeleton , and /// Runtime skins in the Spine Runtimes Guide. /// public class Skin { internal string name; private OrderedDictionary attachments = new OrderedDictionary(SkinEntryComparer.Instance); internal readonly ExposedList bones = new ExposedList(); internal readonly ExposedList constraints = new ExposedList(); public string Name { get { return name; } } public OrderedDictionary Attachments { get { return attachments; } } public ExposedList Bones { get { return bones; } } public ExposedList Constraints { get { return constraints; } } public Skin (string name) { if (name == null) throw new ArgumentNullException("name", "name cannot be null."); this.name = name; } /// Adds an attachment to the skin for the specified slot index and name. /// If the name already exists for the slot, the previous value is replaced. public void SetAttachment (int slotIndex, string name, Attachment attachment) { if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); if (slotIndex < 0) throw new ArgumentNullException("slotIndex", "slotIndex must be >= 0."); attachments[new SkinEntry(slotIndex, name, attachment)] = attachment; } ///Adds all attachments, bones, and constraints from the specified skin to this skin. public void AddSkin (Skin skin) { foreach (BoneData data in skin.bones) if (!bones.Contains(data)) bones.Add(data); foreach (ConstraintData data in skin.constraints) if (!constraints.Contains(data)) constraints.Add(data); foreach (SkinEntry entry in skin.attachments.Keys) SetAttachment(entry.SlotIndex, entry.Name, entry.Attachment); } ///Adds all attachments from the specified skin to this skin. Attachments are deep copied. public void CopySkin (Skin skin) { foreach (BoneData data in skin.bones) if (!bones.Contains(data)) bones.Add(data); foreach (ConstraintData data in skin.constraints) if (!constraints.Contains(data)) constraints.Add(data); foreach (SkinEntry entry in skin.attachments.Keys) { if (entry.Attachment is MeshAttachment) SetAttachment(entry.SlotIndex, entry.Name, entry.Attachment != null ? ((MeshAttachment)entry.Attachment).NewLinkedMesh() : null); else SetAttachment(entry.SlotIndex, entry.Name, entry.Attachment != null ? entry.Attachment.Copy() : null); } } /// Returns the attachment for the specified slot index and name, or null. /// May be null. public Attachment GetAttachment (int slotIndex, string name) { var lookup = new SkinEntry(slotIndex, name, null); Attachment attachment = null; bool containsKey = attachments.TryGetValue(lookup, out attachment); return containsKey ? attachment : null; } /// Removes the attachment in the skin for the specified slot index and name, if any. public void RemoveAttachment (int slotIndex, string name) { if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0"); var lookup = new SkinEntry(slotIndex, name, null); attachments.Remove(lookup); } ///Returns all attachments contained in this skin. public ICollection GetAttachments () { return this.attachments.Keys; } /// Returns all attachments in this skin for the specified slot index. /// The target slotIndex. To find the slot index, use or public void GetAttachments (int slotIndex, List attachments) { foreach (SkinEntry entry in this.attachments.Keys) if (entry.SlotIndex == slotIndex) attachments.Add(entry); } ///Clears all attachments, bones, and constraints. public void Clear () { attachments.Clear(); bones.Clear(); constraints.Clear(); } override public string ToString () { return name; } /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. internal void AttachAll (Skeleton skeleton, Skin oldSkin) { foreach (SkinEntry entry in oldSkin.attachments.Keys) { int slotIndex = entry.SlotIndex; Slot slot = skeleton.slots.Items[slotIndex]; if (slot.Attachment == entry.Attachment) { Attachment attachment = GetAttachment(slotIndex, entry.Name); if (attachment != null) slot.Attachment = attachment; } } } /// Stores an entry in the skin consisting of the slot index, name, and attachment. public struct SkinEntry { private readonly int slotIndex; private readonly string name; private readonly Attachment attachment; internal readonly int hashCode; public SkinEntry (int slotIndex, string name, Attachment attachment) { this.slotIndex = slotIndex; this.name = name; this.attachment = attachment; this.hashCode = this.name.GetHashCode() + this.slotIndex * 37; } public int SlotIndex { get { return slotIndex; } } /// The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor. public String Name { get { return name; } } public Attachment Attachment { get { return attachment; } } } // Avoids boxing in the dictionary and is necessary to omit entry.attachment in the comparison. class SkinEntryComparer : IEqualityComparer { internal static readonly SkinEntryComparer Instance = new SkinEntryComparer(); bool IEqualityComparer.Equals (SkinEntry e1, SkinEntry e2) { if (e1.SlotIndex != e2.SlotIndex) return false; if (!string.Equals(e1.Name, e2.Name, StringComparison.Ordinal)) return false; return true; } int IEqualityComparer.GetHashCode (SkinEntry e) { return e.Name.GetHashCode() + e.SlotIndex * 37; } } } }