/****************************************************************************** * Spine Runtimes License Agreement * Last updated July 28, 2023. Replaces all prior versions. * * Copyright (c) 2013-2023, 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; 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; // Difference to reference implementation: using Dictionary instead of HashSet. // Reason is that there is no efficient way to replace or access an already added element, losing any benefits. private Dictionary attachments = new Dictionary(SkinKeyComparer.Instance); internal readonly ExposedList bones = new ExposedList(); internal readonly ExposedList constraints = new ExposedList(); public string Name { get { return name; } } /// Returns all attachments contained in this skin. public ICollection Attachments { get { return attachments.Values; } } 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."); attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, 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 (KeyValuePair item in skin.attachments) { SkinEntry entry = item.Value; 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 (KeyValuePair item in skin.attachments) { SkinEntry entry = item.Value; 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) { SkinEntry entry; bool containsKey = attachments.TryGetValue(new SkinKey(slotIndex, name), out entry); return containsKey ? entry.attachment : null; } /// Removes the attachment in the skin for the specified slot index and name, if any. public void RemoveAttachment (int slotIndex, string name) { attachments.Remove(new SkinKey(slotIndex, name)); } /// Returns all attachments in this skin for the specified slot index. /// The target slotIndex. To find the slot index, use and . public void GetAttachments (int slotIndex, List attachments) { if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); foreach (KeyValuePair item in this.attachments) { SkinEntry entry = item.Value; 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) { Slot[] slots = skeleton.slots.Items; foreach (KeyValuePair item in oldSkin.attachments) { SkinEntry entry = item.Value; int slotIndex = entry.slotIndex; Slot slot = slots[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 { internal readonly int slotIndex; internal readonly string name; internal readonly Attachment attachment; public SkinEntry (int slotIndex, string name, Attachment attachment) { this.slotIndex = slotIndex; this.name = name; this.attachment = attachment; } 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; } } } private struct SkinKey { internal readonly int slotIndex; internal readonly string name; internal readonly int hashCode; public SkinKey (int slotIndex, string name) { if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); if (name == null) throw new ArgumentNullException("name", "name cannot be null"); this.slotIndex = slotIndex; this.name = name; this.hashCode = name.GetHashCode() + slotIndex * 37; } } class SkinKeyComparer : IEqualityComparer { internal static readonly SkinKeyComparer Instance = new SkinKeyComparer(); bool IEqualityComparer.Equals (SkinKey e1, SkinKey e2) { return e1.slotIndex == e2.slotIndex && string.Equals(e1.name, e2.name, StringComparison.Ordinal); } int IEqualityComparer.GetHashCode (SkinKey e) { return e.hashCode; } } } }