2024-10-23 17:55:55 +08:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Spine Runtimes License Agreement
2024-11-21 09:35:48 +08:00
* Last updated July 28 , 2023. Replaces all prior versions .
2024-10-23 17:55:55 +08:00
*
2024-11-21 09:35:48 +08:00
* Copyright ( c ) 2013 - 2023 , Esoteric Software LLC
2024-10-23 17:55:55 +08:00
*
* 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
*
2024-11-21 09:35:48 +08:00
* Otherwise , it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes ( collectively ,
2024-10-23 17:55:55 +08:00
* "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
2024-11-21 09:35:48 +08:00
* ( 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 .
2024-10-23 17:55:55 +08:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
using System ;
using System.Collections ;
using System.Collections.Generic ;
namespace Spine {
/// <summary>Stores attachments by slot index and attachment name.
/// <para>See SkeletonData <see cref="Spine.SkeletonData.DefaultSkin"/>, Skeleton <see cref="Spine.Skeleton.Skin"/>, and
/// <a href="http://esotericsoftware.com/spine-runtime-skins">Runtime skins</a> in the Spine Runtimes Guide.</para>
/// </summary>
public class Skin {
internal string name ;
2024-11-21 09:35:48 +08:00
// Difference to reference implementation: using Dictionary<SkinKey, SkinEntry> instead of HashSet<SkinEntry>.
// Reason is that there is no efficient way to replace or access an already added element, losing any benefits.
private Dictionary < SkinKey , SkinEntry > attachments = new Dictionary < SkinKey , SkinEntry > ( SkinKeyComparer . Instance ) ;
2024-10-23 17:55:55 +08:00
internal readonly ExposedList < BoneData > bones = new ExposedList < BoneData > ( ) ;
internal readonly ExposedList < ConstraintData > constraints = new ExposedList < ConstraintData > ( ) ;
public string Name { get { return name ; } }
2024-11-21 09:35:48 +08:00
/// <summary>Returns all attachments contained in this skin.</summary>
public ICollection < SkinEntry > Attachments { get { return attachments . Values ; } }
2024-10-23 17:55:55 +08:00
public ExposedList < BoneData > Bones { get { return bones ; } }
public ExposedList < ConstraintData > Constraints { get { return constraints ; } }
public Skin ( string name ) {
if ( name = = null ) throw new ArgumentNullException ( "name" , "name cannot be null." ) ;
this . name = name ;
}
/// <summary>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.</summary>
public void SetAttachment ( int slotIndex , string name , Attachment attachment ) {
if ( attachment = = null ) throw new ArgumentNullException ( "attachment" , "attachment cannot be null." ) ;
2024-11-21 09:35:48 +08:00
attachments [ new SkinKey ( slotIndex , name ) ] = new SkinEntry ( slotIndex , name , attachment ) ;
2024-10-23 17:55:55 +08:00
}
2024-11-21 09:35:48 +08:00
/// <summary>Adds all attachments, bones, and constraints from the specified skin to this skin.</summary>
2024-10-23 17:55:55 +08:00
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 ) ;
2024-11-21 09:35:48 +08:00
foreach ( KeyValuePair < SkinKey , SkinEntry > item in skin . attachments ) {
SkinEntry entry = item . Value ;
SetAttachment ( entry . slotIndex , entry . name , entry . attachment ) ;
}
2024-10-23 17:55:55 +08:00
}
2024-11-21 09:35:48 +08:00
/// <summary>Adds all attachments from the specified skin to this skin. Attachments are deep copied.</summary>
2024-10-23 17:55:55 +08:00
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 ) ;
2024-11-21 09:35:48 +08:00
foreach ( KeyValuePair < SkinKey , SkinEntry > 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 ) ;
2024-10-23 17:55:55 +08:00
}
}
/// <summary>Returns the attachment for the specified slot index and name, or null.</summary>
/// <returns>May be null.</returns>
public Attachment GetAttachment ( int slotIndex , string name ) {
2024-11-21 09:35:48 +08:00
SkinEntry entry ;
bool containsKey = attachments . TryGetValue ( new SkinKey ( slotIndex , name ) , out entry ) ;
return containsKey ? entry . attachment : null ;
2024-10-23 17:55:55 +08:00
}
/// <summary> Removes the attachment in the skin for the specified slot index and name, if any.</summary>
public void RemoveAttachment ( int slotIndex , string name ) {
2024-11-21 09:35:48 +08:00
attachments . Remove ( new SkinKey ( slotIndex , name ) ) ;
2024-10-23 17:55:55 +08:00
}
/// <summary>Returns all attachments in this skin for the specified slot index.</summary>
2024-11-21 09:35:48 +08:00
/// <param name="slotIndex">The target slotIndex. To find the slot index, use <see cref="Spine.SkeletonData.FindSlot"/> and <see cref="Spine.SlotData.Index"/></param>.
2024-10-23 17:55:55 +08:00
public void GetAttachments ( int slotIndex , List < SkinEntry > attachments ) {
2024-11-21 09:35:48 +08:00
if ( slotIndex < 0 ) throw new ArgumentException ( "slotIndex must be >= 0." ) ;
if ( attachments = = null ) throw new ArgumentNullException ( "attachments" , "attachments cannot be null." ) ;
foreach ( KeyValuePair < SkinKey , SkinEntry > item in this . attachments ) {
SkinEntry entry = item . Value ;
if ( entry . slotIndex = = slotIndex ) attachments . Add ( entry ) ;
}
2024-10-23 17:55:55 +08:00
}
2024-11-21 09:35:48 +08:00
/// <summary>Clears all attachments, bones, and constraints.</summary>
2024-10-23 17:55:55 +08:00
public void Clear ( ) {
attachments . Clear ( ) ;
bones . Clear ( ) ;
constraints . Clear ( ) ;
}
override public string ToString ( ) {
return name ;
}
/// <summary>Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached.</summary>
internal void AttachAll ( Skeleton skeleton , Skin oldSkin ) {
2024-11-21 09:35:48 +08:00
Slot [ ] slots = skeleton . slots . Items ;
foreach ( KeyValuePair < SkinKey , SkinEntry > 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 ) ;
2024-10-23 17:55:55 +08:00
if ( attachment ! = null ) slot . Attachment = attachment ;
}
}
}
/// <summary>Stores an entry in the skin consisting of the slot index, name, and attachment.</summary>
public struct SkinEntry {
2024-11-21 09:35:48 +08:00
internal readonly int slotIndex ;
internal readonly string name ;
internal readonly Attachment attachment ;
2024-10-23 17:55:55 +08:00
public SkinEntry ( int slotIndex , string name , Attachment attachment ) {
this . slotIndex = slotIndex ;
this . name = name ;
this . attachment = attachment ;
}
public int SlotIndex {
get {
return slotIndex ;
}
}
/// <summary>The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor.</summary>
public String Name {
get {
return name ;
}
}
public Attachment Attachment {
get {
return attachment ;
}
}
}
2024-11-21 09:35:48 +08:00
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 < SkinKey > {
internal static readonly SkinKeyComparer Instance = new SkinKeyComparer ( ) ;
2024-10-23 17:55:55 +08:00
2024-11-21 09:35:48 +08:00
bool IEqualityComparer < SkinKey > . Equals ( SkinKey e1 , SkinKey e2 ) {
return e1 . slotIndex = = e2 . slotIndex & & string . Equals ( e1 . name , e2 . name , StringComparison . Ordinal ) ;
2024-10-23 17:55:55 +08:00
}
2024-11-21 09:35:48 +08:00
int IEqualityComparer < SkinKey > . GetHashCode ( SkinKey e ) {
return e . hashCode ;
2024-10-23 17:55:55 +08:00
}
}
}
}