diff --git a/spine-unity/Assets/spine-unity/Modules/AttachmentTools.meta b/spine-unity/Assets/spine-unity/Modules/AttachmentTools.meta
new file mode 100644
index 000000000..fee2e8cca
--- /dev/null
+++ b/spine-unity/Assets/spine-unity/Modules/AttachmentTools.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: f601b524e4da4704a9e0f6b7e05d6ca6
+folderAsset: yes
+timeCreated: 1478437840
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/spine-unity/Assets/spine-unity/Modules/AttachmentTools/AttachmentUtility.cs b/spine-unity/Assets/spine-unity/Modules/AttachmentTools/AttachmentUtility.cs
new file mode 100644
index 000000000..d60f8fbab
--- /dev/null
+++ b/spine-unity/Assets/spine-unity/Modules/AttachmentTools/AttachmentUtility.cs
@@ -0,0 +1,676 @@
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, Esoteric Software
+ * All rights reserved.
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable, and
+ * non-transferable license to use, install, execute, and perform the Spine
+ * Runtimes software and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * or other intellectual property or proprietary rights notices on or in the
+ * Software, including any copy thereof. Redistributions in binary or source
+ * form must include this license and terms.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEngine;
+using System.Collections.Generic;
+
+namespace Spine.Unity.Modules.AttachmentTools {
+ public static class AttachmentRegionExtensions {
+ ///
+ /// Tries to set the region (image) of a renderable attachment. If the attachment is not renderable, nothing is applied.
+ public static void SetRegion (this Attachment attachment, AtlasRegion region, bool updateOffset = true) {
+ var regionAttachment = attachment as RegionAttachment;
+ if (regionAttachment != null) {
+ regionAttachment.SetRegion(region, updateOffset);
+ } else {
+ var meshAttachment = attachment as MeshAttachment;
+ if (meshAttachment != null)
+ meshAttachment.SetRegion(region, updateOffset);
+ }
+ }
+
+ /// Sets the region (image) of a RegionAttachment
+ public static void SetRegion (this RegionAttachment attachment, AtlasRegion region, bool updateOffset = true) {
+ if (region == null) throw new System.ArgumentNullException("region");
+
+ // (AtlasAttachmentLoader.cs)
+ attachment.RendererObject = region;
+ attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate);
+ attachment.regionOffsetX = region.offsetX;
+ attachment.regionOffsetY = region.offsetY;
+ attachment.regionWidth = region.width;
+ attachment.regionHeight = region.height;
+ attachment.regionOriginalWidth = region.originalWidth;
+ attachment.regionOriginalHeight = region.originalHeight;
+
+ if (updateOffset) attachment.UpdateOffset();
+ }
+
+ /// Sets the region (image) of a MeshAttachment
+ public static void SetRegion (this MeshAttachment attachment, AtlasRegion region, bool updateUVs = true) {
+ if (region == null) throw new System.ArgumentNullException("region");
+
+ // (AtlasAttachmentLoader.cs)
+ attachment.RendererObject = region;
+ attachment.RegionU = region.u;
+ attachment.RegionV = region.v;
+ attachment.RegionU2 = region.u2;
+ attachment.RegionV2 = region.v2;
+ attachment.RegionRotate = region.rotate;
+ attachment.regionOffsetX = region.offsetX;
+ attachment.regionOffsetY = region.offsetY;
+ attachment.regionWidth = region.width;
+ attachment.regionHeight = region.height;
+ attachment.regionOriginalWidth = region.originalWidth;
+ attachment.regionOriginalHeight = region.originalHeight;
+
+ if (updateUVs) attachment.UpdateUVs();
+ }
+
+ #region Runtime RegionAttachments
+ ///
+ /// Creates a RegionAttachment based on a sprite. This method creates a real, usable AtlasRegion. That AtlasRegion uses a new AtlasPage with the Material provided.///
+ public static RegionAttachment ToRegionAttachment (this Sprite sprite, Material material) {
+ return sprite.ToRegionAttachment(material.ToSpineAtlasPage());
+ }
+
+ ///
+ /// Creates a RegionAttachment based on a sprite. This method creates a real, usable AtlasRegion. That AtlasRegion uses the AtlasPage provided.///
+ public static RegionAttachment ToRegionAttachment (this Sprite sprite, AtlasPage page) {
+ if (sprite == null) throw new System.ArgumentNullException("sprite");
+ if (page == null) throw new System.ArgumentNullException("page");
+ var region = sprite.ToAtlasRegion(page);
+ var unitsPerPixel = 1f / sprite.pixelsPerUnit;
+ return region.ToRegionAttachment(sprite.name, unitsPerPixel);
+ }
+
+ ///
+ /// Creates a Spine.AtlasRegion that uses a premultiplied alpha duplicate texture of the Sprite's texture data. Returns a RegionAttachment that uses it. Use this if you plan to use a premultiply alpha shader such as "Spine/Skeleton"
+ public static RegionAttachment ToRegionAttachmentPMAClone (this Sprite sprite, Shader shader) {
+ if (sprite == null) throw new System.ArgumentNullException("sprite");
+ if (shader == null) throw new System.ArgumentNullException("shader");
+ var region = sprite.ToAtlasRegionPMAClone(shader);
+ var unitsPerPixel = 1f / sprite.pixelsPerUnit;
+ return region.ToRegionAttachment(sprite.name, unitsPerPixel);
+ }
+
+ ///
+ /// Creates a new RegionAttachment from a given AtlasRegion.
+ public static RegionAttachment ToRegionAttachment (this AtlasRegion region, string attachmentName, float scale = 0.01f) {
+ if (string.IsNullOrEmpty(attachmentName)) throw new System.ArgumentException("attachmentName can't be null or empty.", "attachmentName");
+ if (region == null) throw new System.ArgumentNullException("region");
+
+ // (AtlasAttachmentLoader.cs)
+ var attachment = new RegionAttachment(attachmentName);
+
+ attachment.scaleX = 1;
+ attachment.scaleY = 1;
+ attachment.SetColor(Color.white);
+ attachment.width = region.width * scale;
+ attachment.height = region.height * scale;
+
+ attachment.RendererObject = region;
+ attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate);
+ attachment.regionOffsetX = region.offsetX;
+ attachment.regionOffsetY = region.offsetY;
+ attachment.regionWidth = region.width;
+ attachment.regionHeight = region.height;
+ attachment.regionOriginalWidth = region.originalWidth;
+ attachment.regionOriginalHeight = region.originalHeight;
+
+ attachment.UpdateOffset();
+ return attachment;
+ }
+ #endregion
+ }
+
+ public static class SpriteAtlasRegionExtensions {
+ ///
+ /// Creates a new Spine.AtlasPage from a UnityEngine.Material. If the material has a preassigned texture, the page width and height will be set.
+ public static AtlasPage ToSpineAtlasPage (this Material m) {
+ var newPage = new AtlasPage {
+ rendererObject = m,
+ name = m.name
+ };
+
+ var t = m.mainTexture;
+ if (t != null) {
+ newPage.width = t.width;
+ newPage.height = t.height;
+ }
+
+ return newPage;
+ }
+
+ ///
+ /// Creates a Spine.AtlasRegion from a UnityEngine.Sprite.
+ public static AtlasRegion ToAtlasRegion (this Sprite s, AtlasPage page) {
+ if (page == null) throw new System.ArgumentNullException("page", "page cannot be null. AtlasPage determines which texture region belongs and how it should be rendered. You can use material.ToSpineAtlasPage() to get a shareable AtlasPage from a Material, or use the sprite.ToAtlasRegion(material) overload.");
+ var region = s.ToAtlasRegion();
+ region.page = page;
+ return region;
+ }
+
+ ///
+ /// Creates a Spine.AtlasRegion from a UnityEngine.Sprite. This creates a new AtlasPage object for every AtlasRegion you create. You can centralize Material control by creating a shared atlas page using Material.ToSpineAtlasPage and using the sprite.ToAtlasRegion(AtlasPage) overload.
+ public static AtlasRegion ToAtlasRegion (this Sprite s, Material material) {
+ var region = s.ToAtlasRegion();
+ region.page = material.ToSpineAtlasPage();
+ return region;
+ }
+
+ ///
+ /// Creates a Spine.AtlasRegion that uses a premultiplied alpha duplicate of the Sprite's texture data.
+ public static AtlasRegion ToAtlasRegionPMAClone (this Sprite s, Shader shader) {
+ var material = new Material(shader);
+ var tex = s.ToTexture(false);
+ tex.ApplyPMA(true);
+
+ tex.name = s.name + "-pma-";
+ material.name = tex.name + shader.name;
+
+ material.mainTexture = tex;
+ var page = material.ToSpineAtlasPage();
+
+ var region = s.ToAtlasRegion(true);
+ region.page = page;
+
+ return region;
+ }
+
+ static AtlasRegion ToAtlasRegion (this Sprite s, bool isolatedTexture = false) {
+ var region = new AtlasRegion();
+ region.name = s.name;
+ region.index = -1;
+ region.rotate = s.packed && s.packingRotation != SpritePackingRotation.None;
+
+ // World space units
+ Bounds bounds = s.bounds;
+ Vector2 boundsMin = bounds.min, boundsMax = bounds.max;
+
+ // Texture space/pixel units
+ Rect spineRect = s.rect.SpineUnityFlipRect(s.texture.height);
+ region.width = (int)spineRect.width;
+ region.originalWidth = (int)spineRect.width;
+ region.height = (int)spineRect.height;
+ region.originalHeight = (int)spineRect.height;
+ region.offsetX = spineRect.width * (0.5f - InverseLerp(boundsMin.x, boundsMax.x, 0));
+ region.offsetY = spineRect.height * (0.5f - InverseLerp(boundsMin.y, boundsMax.y, 0));
+
+ if (isolatedTexture) {
+ region.u = 0;
+ region.v = 1;
+ region.u2 = 1;
+ region.v2 = 0;
+ region.x = 0;
+ region.y = 0;
+ } else {
+ Texture2D tex = s.texture;
+ Rect uvRect = TextureRectToUVRect(s.textureRect, tex.width, tex.height);
+ region.u = uvRect.xMin;
+ region.v = uvRect.yMax;
+ region.u2 = uvRect.xMax;
+ region.v2 = uvRect.yMin;
+ region.x = (int)spineRect.x;
+ region.y = (int)spineRect.y;
+ }
+
+ return region;
+ }
+
+ #region Runtime Repacking
+ ///
+ /// Creates and populates a duplicate skin with cloned attachments that are backed by a new packed texture atlas comprised of all the regions from the original skin.
+ /// No Spine.Atlas object is created so there is no way to find AtlasRegions except through the Attachments using them.
+ public static Skin GetRepackedSkin (this Skin o, string skinName, Shader shader, out Material m, out Texture2D t, int maxAtlasSize = 1024, int padding = 2) {
+ var skinAttachments = o.Attachments;
+ var newSkin = new Skin(skinName);
+
+ var repackedAttachments = new List();
+ var texturesToPack = new List();
+ foreach (var kvp in skinAttachments) {
+ var newAttachment = kvp.Value.GetClone(true);
+ if (IsRenderable(newAttachment)) {
+ texturesToPack.Add(newAttachment.GetAtlasRegion().ToTexture());
+ repackedAttachments.Add(newAttachment);
+ }
+ var key = kvp.Key;
+ newSkin.AddAttachment(key.slotIndex, key.name, newAttachment);
+ }
+
+ var newTexture = new Texture2D(maxAtlasSize, maxAtlasSize);
+ newTexture.name = skinName;
+ var rects = newTexture.PackTextures(texturesToPack.ToArray(), padding, maxAtlasSize);
+
+ var newMaterial = new Material(shader);
+ newMaterial.name = skinName;
+ newMaterial.mainTexture = newTexture;
+ var page = newMaterial.ToSpineAtlasPage();
+ page.name = skinName;
+
+ for (int i = 0, n = repackedAttachments.Count; i < n; i++) {
+ var a = repackedAttachments[i];
+ var r = rects[i];
+ var oldRegion = a.GetAtlasRegion();
+ var newRegion = UVRectToAtlasRegion(r, oldRegion.name, page, oldRegion.offsetX, oldRegion.offsetY, oldRegion.rotate);
+ a.SetRegion(newRegion);
+ }
+
+ t = newTexture;
+ m = newMaterial;
+ return newSkin;
+ }
+
+ public static Sprite ToSprite (this AtlasRegion ar, float pixelsPerUnit = 100) {
+ return Sprite.Create(ar.GetMainTexture(), ar.GetUnityRect(), new Vector2(0.5f, 0.5f), pixelsPerUnit);
+ }
+
+ static Texture2D ToTexture (this AtlasRegion ar, bool applyImmediately = true) {
+ Texture2D sourceTexture = ar.GetMainTexture();
+
+ Texture2D output = new Texture2D(ar.width, ar.height);
+ output.name = ar.name;
+
+ Rect r = ar.GetUnityRect(sourceTexture.height);
+ Color[] pixelBuffer = sourceTexture.GetPixels((int)r.x, (int)r.y, (int)r.width, (int)r.height);
+ output.SetPixels(pixelBuffer);
+
+ if (applyImmediately)
+ output.Apply();
+
+ return output;
+ }
+
+ static Texture2D ToTexture (this Sprite s, bool applyImmediately = true) {
+ var spriteTexture = s.texture;
+ var r = s.textureRect;
+ var spritePixels = spriteTexture.GetPixels((int)r.x, (int)r.y, (int)r.width, (int)r.height); // TODO: Test
+ var newTexture = new Texture2D((int)r.width, (int)r.height);
+ newTexture.SetPixels(spritePixels);
+
+ if (applyImmediately)
+ newTexture.Apply();
+
+ return newTexture;
+ }
+
+ static bool IsRenderable (Attachment a) {
+ return a is RegionAttachment || a is MeshAttachment;
+ }
+
+ ///
+ /// Get a rect with flipped Y so that a Spine atlas rect gets converted to a Unity Sprite rect and vice versa.
+ static Rect SpineUnityFlipRect (this Rect rect, int textureHeight) {
+ rect.y = textureHeight - rect.y - rect.height;
+ return rect;
+ }
+
+ ///
+ /// Gets the Rect of an AtlasRegion according to Unity texture coordinates (x-right, y-up).
+ /// This overload relies on region.page.height being correctly set.
+ static Rect GetUnityRect (this AtlasRegion region) {
+ return region.GetSpineAtlasRect().SpineUnityFlipRect(region.page.height);
+ }
+
+ ///
+ /// Gets the Rect of an AtlasRegion according to Unity texture coordinates (x-right, y-up).
+ static Rect GetUnityRect (this AtlasRegion region, int textureHeight) {
+ return region.GetSpineAtlasRect().SpineUnityFlipRect(textureHeight);
+ }
+
+ ///
+ /// Returns a Rect of the AtlasRegion according to Spine texture coordinates. (x-right, y-down)
+ static Rect GetSpineAtlasRect (this AtlasRegion region) {
+ return new Rect(region.x, region.y, region.width, region.height);
+ }
+
+ ///
+ /// Denormalize a uvRect into a texture-space Rect.
+ static Rect UVRectToTextureRect (Rect uvRect, int texWidth, int texHeight) {
+ uvRect.x *= texWidth;
+ uvRect.width *= texWidth;
+ uvRect.y *= texHeight;
+ uvRect.height *= texHeight;
+ return uvRect;
+ }
+
+ ///
+ /// Normalize a texture Rect into UV coordinates.
+ static Rect TextureRectToUVRect (Rect textureRect, int texWidth, int texHeight) {
+ textureRect.x = Mathf.InverseLerp(0, texWidth, textureRect.x);
+ textureRect.y = Mathf.InverseLerp(0, texHeight, textureRect.y);
+ textureRect.width = Mathf.InverseLerp(0, texWidth, textureRect.width);
+ textureRect.height = Mathf.InverseLerp(0, texHeight, textureRect.height);
+ return textureRect;
+ }
+
+ ///
+ /// Creates a new Spine AtlasRegion according to a Unity UV Rect (x-right, y-up, uv-normalized).
+ static AtlasRegion UVRectToAtlasRegion (Rect uvRect, string name, AtlasPage page, float offsetX, float offsetY, bool rotate) {
+ var tr = UVRectToTextureRect(uvRect, page.width, page.height);
+ var rr = tr.SpineUnityFlipRect(page.height);
+ int w = (int)rr.width, h = (int)rr.height, x = (int)rr.x, y = (int)rr.y;
+ return new AtlasRegion {
+ page = page,
+ name = name,
+
+ u = uvRect.xMin,
+ u2 = uvRect.xMax,
+ v = uvRect.yMax,
+ v2 = uvRect.yMin,
+
+ index = -1,
+
+ width = w,
+ originalWidth = w,
+ height = h,
+ originalHeight = h,
+ offsetX = offsetX,
+ offsetY = offsetY,
+ x = x,
+ y = y,
+
+ rotate = rotate
+ };
+ }
+
+ ///
+ /// Tries to get the backing AtlasRegion of an attachment if it is renderable. Returns null for non-renderable attachments.
+ static AtlasRegion GetAtlasRegion (this Attachment a) {
+ var regionAttachment = a as RegionAttachment;
+ if (regionAttachment != null)
+ return (regionAttachment.RendererObject) as AtlasRegion;
+
+ var meshAttachment = a as MeshAttachment;
+ if (meshAttachment != null)
+ return (meshAttachment.RendererObject) as AtlasRegion;
+
+ return null;
+ }
+
+ ///
+ /// Convenience method for getting the main texture of the material of the page of the region.
+ static Texture2D GetMainTexture (this AtlasRegion region) {
+ var material = (region.page.rendererObject as Material);
+ return material.mainTexture as Texture2D;
+ }
+
+ static void ApplyPMA (this Texture2D texture, bool applyImmediately = true) {
+ var pixels = texture.GetPixels();
+ for (int i = 0, n = pixels.Length; i < n; i++) {
+ Color p = pixels[i];
+ float a = p.a;
+ p.r = p.r * a;
+ p.g = p.g * a;
+ p.b = p.b * a;
+ pixels[i] = p;
+ }
+ texture.SetPixels(pixels);
+ if (applyImmediately)
+ texture.Apply();
+ }
+ #endregion
+
+ private static float InverseLerp (float a, float b, float value) {
+ return (value - a) / (b - a);
+ }
+ }
+
+ public static class SkinExtensions {
+ ///
+ /// Convenience method for duplicating a skeleton's current active skin so changes to it will not affect other skeleton instances. .
+ public static Skin UnshareSkin (this Skeleton skeleton, bool includeDefaultSkin, bool unshareAttachments, AnimationState state = null) {
+ // 1. Copy the current skin and set the skeleton's skin to the new one.
+ var newSkin = skeleton.GetClonedSkin("cloned skin", includeDefaultSkin, unshareAttachments, true);
+ skeleton.SetSkin(newSkin);
+
+ // 2. Apply correct attachments: skeleton.SetToSetupPose + animationState.Apply
+ if (state != null) {
+ skeleton.SetToSetupPose();
+ state.Apply(skeleton);
+ }
+
+ // 3. Return unshared skin.
+ return newSkin;
+ }
+
+ public static Skin GetClonedSkin (this Skeleton skeleton, string newSkinName, bool includeDefaultSkin = false, bool cloneAttachments = false, bool cloneMeshesAsLinked = true) {
+ var newSkin = new Skin(newSkinName); // may have null name. Harmless.
+ var defaultSkin = skeleton.data.DefaultSkin;
+ var activeSkin = skeleton.skin;
+
+ if (includeDefaultSkin)
+ defaultSkin.CopyTo(newSkin, true, cloneAttachments, cloneMeshesAsLinked);
+ activeSkin.CopyTo(newSkin, true, cloneAttachments, cloneMeshesAsLinked);
+
+ return newSkin;
+ }
+
+ ///
+ /// Gets a shallow copy of the skin. The cloned skin's attachments are shared with the original skin.
+ public static Skin GetClone (this Skin original) {
+ var newSkin = new Skin(original.name + " clone");
+ var newSkinAttachments = newSkin.Attachments;
+
+ foreach (var a in original.Attachments)
+ newSkinAttachments[a.Key] = a.Value;
+
+ return newSkin;
+ }
+
+ public static void CopyTo (this Skin source, Skin destination, bool overwrite, bool cloneAttachments, bool cloneMeshesAsLinked = true) {
+ var sourceAttachments = source.Attachments;
+ var destinationAttachments = destination.Attachments;
+
+ if (cloneAttachments) {
+ if (overwrite) {
+ foreach (var e in sourceAttachments)
+ destinationAttachments[e.Key] = e.Value.GetClone(cloneMeshesAsLinked);
+ } else {
+ foreach (var e in sourceAttachments) {
+ if (destinationAttachments.ContainsKey(e.Key)) continue;
+ destinationAttachments.Add(e.Key, e.Value.GetClone(cloneMeshesAsLinked));
+ }
+ }
+ } else {
+ if (overwrite) {
+ foreach (var e in sourceAttachments)
+ destinationAttachments[e.Key] = e.Value;
+ } else {
+ foreach (var e in sourceAttachments) {
+ if (destinationAttachments.ContainsKey(e.Key)) continue;
+ destinationAttachments.Add(e.Key, e.Value);
+ }
+ }
+ }
+ }
+
+
+ }
+
+ public static class AttachmentCloneExtensions {
+ ///
+ /// Clones the attachment.
+ public static Attachment GetClone (this Attachment o, bool cloneMeshesAsLinked) {
+ var regionAttachment = o as RegionAttachment;
+ if (regionAttachment != null)
+ return regionAttachment.GetClone();
+
+ var meshAttachment = o as MeshAttachment;
+ if (meshAttachment != null)
+ return cloneMeshesAsLinked ? meshAttachment.GetLinkedClone() : meshAttachment.GetClone();
+
+ var boundingBoxAttachment = o as BoundingBoxAttachment;
+ if (boundingBoxAttachment != null)
+ return boundingBoxAttachment.GetClone();
+
+ var pathAttachment = o as PathAttachment;
+ if (pathAttachment != null)
+ return pathAttachment.GetClone();
+
+ return null;
+ }
+
+ public static RegionAttachment GetClone (this RegionAttachment o) {
+ return new RegionAttachment(o.Name + "clone") {
+ x = o.x,
+ y = o.y,
+ rotation = o.rotation,
+ scaleX = o.scaleX,
+ scaleY = o.scaleY,
+ width = o.width,
+ height = o.height,
+
+ r = o.r,
+ g = o.g,
+ b = o.b,
+ a = o.a,
+
+ Path = o.Path,
+ RendererObject = o.RendererObject,
+ regionOffsetX = o.regionOffsetX,
+ regionOffsetY = o.regionOffsetY,
+ regionWidth = o.regionWidth,
+ regionHeight = o.regionHeight,
+ regionOriginalWidth = o.regionOriginalWidth,
+ regionOriginalHeight = o.regionOriginalHeight,
+ uvs = o.uvs.Clone() as float[],
+ offset = o.offset.Clone() as float[]
+ };
+ }
+
+ public static BoundingBoxAttachment GetClone (this BoundingBoxAttachment o) {
+ var ba = new BoundingBoxAttachment(o.Name);
+ CloneVertexAttachment(o, ba);
+ return o;
+ }
+
+ public static MeshAttachment GetLinkedClone (this MeshAttachment o, bool inheritDeform = true) {
+ return o.GetLinkedMesh(o.Name, o.RendererObject as AtlasRegion, inheritDeform);
+ }
+
+ ///
+ /// Returns a clone of the MeshAttachment. This will cause Deform animations to stop working unless you explicity set the .parentMesh to the original.
+ public static MeshAttachment GetClone (this MeshAttachment o) {
+ var ma = new MeshAttachment(o.Name) {
+ r = o.r,
+ g = o.g,
+ b = o.b,
+ a = o.a,
+
+ inheritDeform = o.inheritDeform,
+
+ Path = o.Path,
+ RendererObject = o.RendererObject,
+
+ regionOffsetX = o.regionOffsetX,
+ regionOffsetY = o.regionOffsetY,
+ regionWidth = o.regionWidth,
+ regionHeight = o.regionHeight,
+ regionOriginalWidth = o.regionOriginalWidth,
+ regionOriginalHeight = o.regionOriginalHeight,
+ RegionU = o.RegionU,
+ RegionV = o.RegionV,
+ RegionU2 = o.RegionU2,
+ RegionV2 = o.RegionV2,
+ RegionRotate = o.RegionRotate,
+ uvs = o.uvs.Clone() as float[]
+ };
+
+ // Linked mesh
+ if (o.parentMesh != null) {
+ // bones, vertices, worldVerticesLength, regionUVs, triangles, HullLength, Edges, Width, Height
+ ma.ParentMesh = o.parentMesh;
+ } else {
+ CloneVertexAttachment(o, ma); // bones, vertices, worldVerticesLength
+ ma.regionUVs = o.regionUVs.Clone() as float[];
+ ma.triangles = o.triangles.Clone() as int[];
+ ma.hulllength = o.hulllength;
+
+ // Nonessential.
+ ma.Edges = o.Edges.Clone() as int[];
+ ma.Width = o.Width;
+ ma.Height = o.Height;
+ }
+
+ return o;
+ }
+
+ public static PathAttachment GetClone (this PathAttachment o) {
+ var newPathAttachment = new PathAttachment(o.Name) {
+ lengths = o.lengths.Clone() as float[],
+ closed = o.closed,
+ constantSpeed = o.constantSpeed
+ };
+ CloneVertexAttachment(o, newPathAttachment);
+
+ return newPathAttachment;
+ }
+
+ static void CloneVertexAttachment (VertexAttachment src, VertexAttachment dest) {
+ dest.worldVerticesLength = src.worldVerticesLength;
+ if (src.bones != null)
+ dest.bones = src.vertices.Clone() as int[];
+
+ if (src.vertices != null)
+ dest.vertices = src.vertices.Clone() as float[];
+ }
+
+
+ #region Runtime Linked MeshAttachments
+ ///
+ /// Returns a new linked mesh linked to this MeshAttachment. It will be mapped to the AtlasRegion provided.
+ public static MeshAttachment GetLinkedMesh (this MeshAttachment o, string newLinkedMeshName, AtlasRegion region, bool inheritDeform = true) {
+ //if (string.IsNullOrEmpty(attachmentName)) throw new System.ArgumentException("attachmentName cannot be null or empty", "attachmentName");
+ if (region == null) throw new System.ArgumentNullException("region");
+
+ // If parentMesh is a linked mesh, create a link to its parent. Preserves Deform animations.
+ if (o.parentMesh != null)
+ o = o.parentMesh;
+
+ // 1. NewMeshAttachment (AtlasAttachmentLoader.cs)
+ var mesh = new MeshAttachment(newLinkedMeshName);
+ mesh.SetRegion(region, false);
+
+ // 2. (SkeletonJson.cs::ReadAttachment. case: LinkedMesh)
+ mesh.Path = newLinkedMeshName;
+ mesh.r = 1f;
+ mesh.g = 1f;
+ mesh.b = 1f;
+ mesh.a = 1f;
+ //mesh.ParentMesh property call below sets mesh.Width and mesh.Height
+
+ // 3. Link mesh with parent. (SkeletonJson.cs)
+ mesh.inheritDeform = inheritDeform;
+ mesh.ParentMesh = o;
+ mesh.UpdateUVs();
+
+ return mesh;
+ }
+
+ ///
+ /// Returns a new linked mesh linked to this MeshAttachment. It will be mapped to an AtlasRegion generated from a Sprite. The AtlasRegion will be mapped to a new Material based on the shader.
+ /// For better caching and batching, use GetLinkedMesh(string, AtlasRegion, bool)
+ public static MeshAttachment GetLinkedMesh (this MeshAttachment o, Sprite sprite, Shader shader, bool inheritDeform = true) {
+ return o.GetLinkedMesh(sprite.name, sprite.ToAtlasRegion(new Material(shader)), inheritDeform);
+ }
+ #endregion
+ }
+}
diff --git a/spine-unity/Assets/spine-unity/Modules/AttachmentTools/AttachmentUtility.cs.meta b/spine-unity/Assets/spine-unity/Modules/AttachmentTools/AttachmentUtility.cs.meta
new file mode 100644
index 000000000..68ad280ef
--- /dev/null
+++ b/spine-unity/Assets/spine-unity/Modules/AttachmentTools/AttachmentUtility.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 8dd46dbf979fcb7459246cd37aad09ef
+timeCreated: 1478437807
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: