AttachmentTools.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  1. /******************************************************************************
  2. * Spine Runtimes Software License v2.5
  3. *
  4. * Copyright (c) 2013-2016, Esoteric Software
  5. * All rights reserved.
  6. *
  7. * You are granted a perpetual, non-exclusive, non-sublicensable, and
  8. * non-transferable license to use, install, execute, and perform the Spine
  9. * Runtimes software and derivative works solely for personal or internal
  10. * use. Without the written permission of Esoteric Software (see Section 2 of
  11. * the Spine Software License Agreement), you may not (a) modify, translate,
  12. * adapt, or develop new applications using the Spine Runtimes or otherwise
  13. * create derivative works or improvements of the Spine Runtimes or (b) remove,
  14. * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
  15. * or other intellectual property or proprietary rights notices on or in the
  16. * Software, including any copy thereof. Redistributions in binary or source
  17. * form must include this license and terms.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  20. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  21. * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  22. * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  24. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
  25. * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
  26. * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  27. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  28. * POSSIBILITY OF SUCH DAMAGE.
  29. *****************************************************************************/
  30. using UnityEngine;
  31. using System.Collections.Generic;
  32. namespace Spine.Unity.Modules.AttachmentTools {
  33. public static class AttachmentRegionExtensions {
  34. #region GetRegion
  35. /// <summary>
  36. /// Tries to get the region (image) of a renderable attachment. If the attachment is not renderable, it returns null.</summary>
  37. public static AtlasRegion GetRegion (this Attachment attachment) {
  38. var regionAttachment = attachment as RegionAttachment;
  39. if (regionAttachment != null)
  40. return regionAttachment.RendererObject as AtlasRegion;
  41. var meshAttachment = attachment as MeshAttachment;
  42. if (meshAttachment != null)
  43. return meshAttachment.RendererObject as AtlasRegion;
  44. return null;
  45. }
  46. /// <summary>Gets the region (image) of a RegionAttachment</summary>
  47. public static AtlasRegion GetRegion (this RegionAttachment regionAttachment) {
  48. return regionAttachment.RendererObject as AtlasRegion;
  49. }
  50. /// <summary>Gets the region (image) of a MeshAttachment</summary>
  51. public static AtlasRegion GetRegion (this MeshAttachment meshAttachment) {
  52. return meshAttachment.RendererObject as AtlasRegion;
  53. }
  54. #endregion
  55. #region SetRegion
  56. /// <summary>
  57. /// Tries to set the region (image) of a renderable attachment. If the attachment is not renderable, nothing is applied.</summary>
  58. public static void SetRegion (this Attachment attachment, AtlasRegion region, bool updateOffset = true) {
  59. var regionAttachment = attachment as RegionAttachment;
  60. if (regionAttachment != null)
  61. regionAttachment.SetRegion(region, updateOffset);
  62. var meshAttachment = attachment as MeshAttachment;
  63. if (meshAttachment != null)
  64. meshAttachment.SetRegion(region, updateOffset);
  65. }
  66. /// <summary>Sets the region (image) of a RegionAttachment</summary>
  67. public static void SetRegion (this RegionAttachment attachment, AtlasRegion region, bool updateOffset = true) {
  68. if (region == null) throw new System.ArgumentNullException("region");
  69. // (AtlasAttachmentLoader.cs)
  70. attachment.RendererObject = region;
  71. attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate);
  72. attachment.regionOffsetX = region.offsetX;
  73. attachment.regionOffsetY = region.offsetY;
  74. attachment.regionWidth = region.width;
  75. attachment.regionHeight = region.height;
  76. attachment.regionOriginalWidth = region.originalWidth;
  77. attachment.regionOriginalHeight = region.originalHeight;
  78. if (updateOffset) attachment.UpdateOffset();
  79. }
  80. /// <summary>Sets the region (image) of a MeshAttachment</summary>
  81. public static void SetRegion (this MeshAttachment attachment, AtlasRegion region, bool updateUVs = true) {
  82. if (region == null) throw new System.ArgumentNullException("region");
  83. // (AtlasAttachmentLoader.cs)
  84. attachment.RendererObject = region;
  85. attachment.RegionU = region.u;
  86. attachment.RegionV = region.v;
  87. attachment.RegionU2 = region.u2;
  88. attachment.RegionV2 = region.v2;
  89. attachment.RegionRotate = region.rotate;
  90. attachment.regionOffsetX = region.offsetX;
  91. attachment.regionOffsetY = region.offsetY;
  92. attachment.regionWidth = region.width;
  93. attachment.regionHeight = region.height;
  94. attachment.regionOriginalWidth = region.originalWidth;
  95. attachment.regionOriginalHeight = region.originalHeight;
  96. if (updateUVs) attachment.UpdateUVs();
  97. }
  98. #endregion
  99. #region Runtime RegionAttachments
  100. /// <summary>
  101. /// Creates a RegionAttachment based on a sprite. This method creates a real, usable AtlasRegion. That AtlasRegion uses a new AtlasPage with the Material provided./// </summary>
  102. public static RegionAttachment ToRegionAttachment (this Sprite sprite, Material material) {
  103. return sprite.ToRegionAttachment(material.ToSpineAtlasPage());
  104. }
  105. /// <summary>
  106. /// Creates a RegionAttachment based on a sprite. This method creates a real, usable AtlasRegion. That AtlasRegion uses the AtlasPage provided./// </summary>
  107. public static RegionAttachment ToRegionAttachment (this Sprite sprite, AtlasPage page) {
  108. if (sprite == null) throw new System.ArgumentNullException("sprite");
  109. if (page == null) throw new System.ArgumentNullException("page");
  110. var region = sprite.ToAtlasRegion(page);
  111. var unitsPerPixel = 1f / sprite.pixelsPerUnit;
  112. return region.ToRegionAttachment(sprite.name, unitsPerPixel);
  113. }
  114. /// <summary>
  115. /// 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"</summary>
  116. public static RegionAttachment ToRegionAttachmentPMAClone (this Sprite sprite, Shader shader, TextureFormat textureFormat = SpriteAtlasRegionExtensions.SpineTextureFormat, bool mipmaps = SpriteAtlasRegionExtensions.UseMipMaps, Material materialPropertySource = null) {
  117. if (sprite == null) throw new System.ArgumentNullException("sprite");
  118. if (shader == null) throw new System.ArgumentNullException("shader");
  119. var region = sprite.ToAtlasRegionPMAClone(shader, textureFormat, mipmaps, materialPropertySource);
  120. var unitsPerPixel = 1f / sprite.pixelsPerUnit;
  121. return region.ToRegionAttachment(sprite.name, unitsPerPixel);
  122. }
  123. public static RegionAttachment ToRegionAttachmentPMAClone (this Sprite sprite, Material materialPropertySource, TextureFormat textureFormat = SpriteAtlasRegionExtensions.SpineTextureFormat, bool mipmaps = SpriteAtlasRegionExtensions.UseMipMaps) {
  124. return sprite.ToRegionAttachmentPMAClone(materialPropertySource.shader, textureFormat, mipmaps, materialPropertySource);
  125. }
  126. /// <summary>
  127. /// Creates a new RegionAttachment from a given AtlasRegion.</summary>
  128. public static RegionAttachment ToRegionAttachment (this AtlasRegion region, string attachmentName, float scale = 0.01f) {
  129. if (string.IsNullOrEmpty(attachmentName)) throw new System.ArgumentException("attachmentName can't be null or empty.", "attachmentName");
  130. if (region == null) throw new System.ArgumentNullException("region");
  131. // (AtlasAttachmentLoader.cs)
  132. var attachment = new RegionAttachment(attachmentName);
  133. attachment.RendererObject = region;
  134. attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate);
  135. attachment.regionOffsetX = region.offsetX;
  136. attachment.regionOffsetY = region.offsetY;
  137. attachment.regionWidth = region.width;
  138. attachment.regionHeight = region.height;
  139. attachment.regionOriginalWidth = region.originalWidth;
  140. attachment.regionOriginalHeight = region.originalHeight;
  141. attachment.Path = region.name;
  142. attachment.scaleX = 1;
  143. attachment.scaleY = 1;
  144. attachment.rotation = 0;
  145. attachment.r = 1;
  146. attachment.g = 1;
  147. attachment.b = 1;
  148. attachment.a = 1;
  149. // pass OriginalWidth and OriginalHeight because UpdateOffset uses it in its calculation.
  150. attachment.width = attachment.regionOriginalWidth * scale;
  151. attachment.height = attachment.regionOriginalHeight * scale;
  152. attachment.SetColor(Color.white);
  153. attachment.UpdateOffset();
  154. return attachment;
  155. }
  156. /// <summary> Sets the scale. Call regionAttachment.UpdateOffset to apply the change.</summary>
  157. public static void SetScale (this RegionAttachment regionAttachment, Vector2 scale) {
  158. regionAttachment.scaleX = scale.x;
  159. regionAttachment.scaleY = scale.y;
  160. }
  161. /// <summary> Sets the scale. Call regionAttachment.UpdateOffset to apply the change.</summary>
  162. public static void SetScale (this RegionAttachment regionAttachment, float x, float y) {
  163. regionAttachment.scaleX = x;
  164. regionAttachment.scaleY = y;
  165. }
  166. /// <summary> Sets the position offset. Call regionAttachment.UpdateOffset to apply the change.</summary>
  167. public static void SetPositionOffset (this RegionAttachment regionAttachment, Vector2 offset) {
  168. regionAttachment.x = offset.x;
  169. regionAttachment.y = offset.y;
  170. }
  171. /// <summary> Sets the position offset. Call regionAttachment.UpdateOffset to apply the change.</summary>
  172. public static void SetPositionOffset (this RegionAttachment regionAttachment, float x, float y) {
  173. regionAttachment.x = x;
  174. regionAttachment.y = y;
  175. }
  176. /// <summary> Sets the rotation. Call regionAttachment.UpdateOffset to apply the change.</summary>
  177. public static void SetRotation (this RegionAttachment regionAttachment, float rotation) {
  178. regionAttachment.rotation = rotation;
  179. }
  180. #endregion
  181. }
  182. public static class SpriteAtlasRegionExtensions {
  183. internal const TextureFormat SpineTextureFormat = TextureFormat.RGBA32;
  184. internal const bool UseMipMaps = false;
  185. internal const float DefaultScale = 0.01f;
  186. public static AtlasRegion ToAtlasRegion (this Texture2D t, Material materialPropertySource, float scale = DefaultScale) {
  187. return t.ToAtlasRegion(materialPropertySource.shader, scale, materialPropertySource);
  188. }
  189. public static AtlasRegion ToAtlasRegion (this Texture2D t, Shader shader, float scale = DefaultScale, Material materialPropertySource = null) {
  190. var material = new Material(shader);
  191. if (materialPropertySource != null) {
  192. material.CopyPropertiesFromMaterial(materialPropertySource);
  193. material.shaderKeywords = materialPropertySource.shaderKeywords;
  194. }
  195. material.mainTexture = t;
  196. var page = material.ToSpineAtlasPage();
  197. float width = t.width;
  198. float height = t.height;
  199. var region = new AtlasRegion();
  200. region.name = t.name;
  201. region.index = -1;
  202. region.rotate = false;
  203. // World space units
  204. Vector2 boundsMin = Vector2.zero, boundsMax = new Vector2(width, height) * scale;
  205. // Texture space/pixel units
  206. region.width = (int)width;
  207. region.originalWidth = (int)width;
  208. region.height = (int)height;
  209. region.originalHeight = (int)height;
  210. region.offsetX = width * (0.5f - InverseLerp(boundsMin.x, boundsMax.x, 0));
  211. region.offsetY = height * (0.5f - InverseLerp(boundsMin.y, boundsMax.y, 0));
  212. // Use the full area of the texture.
  213. region.u = 0;
  214. region.v = 1;
  215. region.u2 = 1;
  216. region.v2 = 0;
  217. region.x = 0;
  218. region.y = 0;
  219. region.page = page;
  220. return region;
  221. }
  222. /// <summary>
  223. /// Creates a Spine.AtlasRegion that uses a premultiplied alpha duplicate of the Sprite's texture data.</summary>
  224. public static AtlasRegion ToAtlasRegionPMAClone (this Texture2D t, Material materialPropertySource, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps) {
  225. return t.ToAtlasRegionPMAClone(materialPropertySource.shader, textureFormat, mipmaps, materialPropertySource);
  226. }
  227. /// <summary>
  228. /// Creates a Spine.AtlasRegion that uses a premultiplied alpha duplicate of the Sprite's texture data.</summary>
  229. public static AtlasRegion ToAtlasRegionPMAClone (this Texture2D t, Shader shader, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, Material materialPropertySource = null) {
  230. var material = new Material(shader);
  231. if (materialPropertySource != null) {
  232. material.CopyPropertiesFromMaterial(materialPropertySource);
  233. material.shaderKeywords = materialPropertySource.shaderKeywords;
  234. }
  235. var newTexture = t.GetClone(false, textureFormat, mipmaps);
  236. newTexture.ApplyPMA(true);
  237. newTexture.name = t.name + "-pma-";
  238. material.name = t.name + shader.name;
  239. material.mainTexture = newTexture;
  240. var page = material.ToSpineAtlasPage();
  241. var region = newTexture.ToAtlasRegion(shader);
  242. region.page = page;
  243. return region;
  244. }
  245. /// <summary>
  246. /// Creates a new Spine.AtlasPage from a UnityEngine.Material. If the material has a preassigned texture, the page width and height will be set.</summary>
  247. public static AtlasPage ToSpineAtlasPage (this Material m) {
  248. var newPage = new AtlasPage {
  249. rendererObject = m,
  250. name = m.name
  251. };
  252. var t = m.mainTexture;
  253. if (t != null) {
  254. newPage.width = t.width;
  255. newPage.height = t.height;
  256. }
  257. return newPage;
  258. }
  259. /// <summary>
  260. /// Creates a Spine.AtlasRegion from a UnityEngine.Sprite.</summary>
  261. public static AtlasRegion ToAtlasRegion (this Sprite s, AtlasPage page) {
  262. 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.");
  263. var region = s.ToAtlasRegion();
  264. region.page = page;
  265. return region;
  266. }
  267. /// <summary>
  268. /// 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.</summary>
  269. public static AtlasRegion ToAtlasRegion (this Sprite s, Material material) {
  270. var region = s.ToAtlasRegion();
  271. region.page = material.ToSpineAtlasPage();
  272. return region;
  273. }
  274. /// <summary>
  275. /// Creates a Spine.AtlasRegion that uses a premultiplied alpha duplicate of the Sprite's texture data.</summary>
  276. public static AtlasRegion ToAtlasRegionPMAClone (this Sprite s, Shader shader, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, Material materialPropertySource = null) {
  277. var material = new Material(shader);
  278. if (materialPropertySource != null) {
  279. material.CopyPropertiesFromMaterial(materialPropertySource);
  280. material.shaderKeywords = materialPropertySource.shaderKeywords;
  281. }
  282. var tex = s.ToTexture(false, textureFormat, mipmaps);
  283. tex.ApplyPMA(true);
  284. tex.name = s.name + "-pma-";
  285. material.name = tex.name + shader.name;
  286. material.mainTexture = tex;
  287. var page = material.ToSpineAtlasPage();
  288. var region = s.ToAtlasRegion(true);
  289. region.page = page;
  290. return region;
  291. }
  292. public static AtlasRegion ToAtlasRegionPMAClone (this Sprite s, Material materialPropertySource, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps) {
  293. return s.ToAtlasRegionPMAClone(materialPropertySource.shader, textureFormat, mipmaps, materialPropertySource);
  294. }
  295. internal static AtlasRegion ToAtlasRegion (this Sprite s, bool isolatedTexture = false) {
  296. var region = new AtlasRegion();
  297. region.name = s.name;
  298. region.index = -1;
  299. region.rotate = s.packed && s.packingRotation != SpritePackingRotation.None;
  300. // World space units
  301. Bounds bounds = s.bounds;
  302. Vector2 boundsMin = bounds.min, boundsMax = bounds.max;
  303. // Texture space/pixel units
  304. Rect spineRect = s.rect.SpineUnityFlipRect(s.texture.height);
  305. region.width = (int)spineRect.width;
  306. region.originalWidth = (int)spineRect.width;
  307. region.height = (int)spineRect.height;
  308. region.originalHeight = (int)spineRect.height;
  309. region.offsetX = spineRect.width * (0.5f - InverseLerp(boundsMin.x, boundsMax.x, 0));
  310. region.offsetY = spineRect.height * (0.5f - InverseLerp(boundsMin.y, boundsMax.y, 0));
  311. if (isolatedTexture) {
  312. region.u = 0;
  313. region.v = 1;
  314. region.u2 = 1;
  315. region.v2 = 0;
  316. region.x = 0;
  317. region.y = 0;
  318. } else {
  319. Texture2D tex = s.texture;
  320. Rect uvRect = TextureRectToUVRect(s.textureRect, tex.width, tex.height);
  321. region.u = uvRect.xMin;
  322. region.v = uvRect.yMax;
  323. region.u2 = uvRect.xMax;
  324. region.v2 = uvRect.yMin;
  325. region.x = (int)spineRect.x;
  326. region.y = (int)spineRect.y;
  327. }
  328. return region;
  329. }
  330. #region Runtime Repacking
  331. /// <summary>
  332. /// 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.</summary>
  333. /// <remarks>No Spine.Atlas object is created so there is no way to find AtlasRegions except through the Attachments using them.</remarks>
  334. public static Skin GetRepackedSkin (this Skin o, string newName, Material materialPropertySource, out Material m, out Texture2D t, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps) {
  335. return GetRepackedSkin(o, newName, materialPropertySource.shader, out m, out t, maxAtlasSize, padding, textureFormat, mipmaps, materialPropertySource);
  336. }
  337. /// <summary>
  338. /// 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.</summary>
  339. /// <remarks>No Spine.Atlas object is created so there is no way to find AtlasRegions except through the Attachments using them.</remarks>
  340. public static Skin GetRepackedSkin (this Skin o, string newName, Shader shader, out Material m, out Texture2D t, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, Material materialPropertySource = null) {
  341. var skinAttachments = o.Attachments;
  342. var newSkin = new Skin(newName);
  343. // Use these to detect and use shared regions.
  344. var existingRegions = new Dictionary<AtlasRegion, int>();
  345. var regionIndexes = new List<int>();
  346. // Collect all textures from the attachments of the original skin.
  347. var repackedAttachments = new List<Attachment>();
  348. var texturesToPack = new List<Texture2D>();
  349. var originalRegions = new List<AtlasRegion>();
  350. int newRegionIndex = 0;
  351. foreach (var kvp in skinAttachments) {
  352. var newAttachment = kvp.Value.GetClone(true);
  353. if (IsRenderable(newAttachment)) {
  354. var region = newAttachment.GetAtlasRegion();
  355. int existingIndex;
  356. if (existingRegions.TryGetValue(region, out existingIndex)) {
  357. regionIndexes.Add(existingIndex); // Store the region index for the eventual new attachment.
  358. } else {
  359. originalRegions.Add(region);
  360. texturesToPack.Add(region.ToTexture()); // Add the texture to the PackTextures argument
  361. existingRegions.Add(region, newRegionIndex); // Add the region to the dictionary of known regions
  362. regionIndexes.Add(newRegionIndex); // Store the region index for the eventual new attachment.
  363. newRegionIndex++;
  364. }
  365. repackedAttachments.Add(newAttachment);
  366. }
  367. var key = kvp.Key;
  368. newSkin.AddAttachment(key.slotIndex, key.name, newAttachment);
  369. }
  370. // Fill a new texture with the collected attachment textures.
  371. var newTexture = new Texture2D(maxAtlasSize, maxAtlasSize, textureFormat, mipmaps);
  372. newTexture.anisoLevel = texturesToPack[0].anisoLevel;
  373. newTexture.name = newName;
  374. var rects = newTexture.PackTextures(texturesToPack.ToArray(), padding, maxAtlasSize);
  375. // Rehydrate the repacked textures as a Material, Spine atlas and Spine.AtlasAttachments
  376. var newMaterial = new Material(shader);
  377. if (materialPropertySource != null) {
  378. newMaterial.CopyPropertiesFromMaterial(materialPropertySource);
  379. newMaterial.shaderKeywords = materialPropertySource.shaderKeywords;
  380. }
  381. newMaterial.name = newName;
  382. newMaterial.mainTexture = newTexture;
  383. var page = newMaterial.ToSpineAtlasPage();
  384. page.name = newName;
  385. var repackedRegions = new List<AtlasRegion>();
  386. for (int i = 0, n = originalRegions.Count; i < n; i++) {
  387. var oldRegion = originalRegions[i];
  388. var newRegion = UVRectToAtlasRegion(rects[i], oldRegion.name, page, oldRegion.offsetX, oldRegion.offsetY, oldRegion.rotate);
  389. repackedRegions.Add(newRegion);
  390. }
  391. // Map the cloned attachments to the repacked atlas.
  392. for (int i = 0, n = repackedAttachments.Count; i < n; i++) {
  393. var a = repackedAttachments[i];
  394. a.SetRegion(repackedRegions[regionIndexes[i]]);
  395. }
  396. // Clean up
  397. foreach (var ttp in texturesToPack)
  398. UnityEngine.Object.Destroy(ttp);
  399. t = newTexture;
  400. m = newMaterial;
  401. return newSkin;
  402. }
  403. public static Sprite ToSprite (this AtlasRegion ar, float pixelsPerUnit = 100) {
  404. return Sprite.Create(ar.GetMainTexture(), ar.GetUnityRect(), new Vector2(0.5f, 0.5f), pixelsPerUnit);
  405. }
  406. /// <summary>Creates a new Texture2D object based on an AtlasRegion.
  407. /// If applyImmediately is true, Texture2D.Apply is called immediately after the Texture2D is filled with data.</summary>
  408. public static Texture2D ToTexture (this AtlasRegion ar, bool applyImmediately = true, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps) {
  409. Texture2D sourceTexture = ar.GetMainTexture();
  410. Rect r = ar.GetUnityRect(sourceTexture.height);
  411. int width = (int)r.width;
  412. int height = (int)r.height;
  413. Texture2D output = new Texture2D(width, height, textureFormat, mipmaps);
  414. output.name = ar.name;
  415. Color[] pixelBuffer = sourceTexture.GetPixels((int)r.x, (int)r.y, width, height);
  416. output.SetPixels(pixelBuffer);
  417. if (applyImmediately)
  418. output.Apply();
  419. return output;
  420. }
  421. static Texture2D ToTexture (this Sprite s, bool applyImmediately = true, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps) {
  422. var spriteTexture = s.texture;
  423. var r = s.textureRect;
  424. var spritePixels = spriteTexture.GetPixels((int)r.x, (int)r.y, (int)r.width, (int)r.height);
  425. var newTexture = new Texture2D((int)r.width, (int)r.height, textureFormat, mipmaps);
  426. newTexture.SetPixels(spritePixels);
  427. if (applyImmediately)
  428. newTexture.Apply();
  429. return newTexture;
  430. }
  431. static Texture2D GetClone (this Texture2D t, bool applyImmediately = true, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps) {
  432. var spritePixels = t.GetPixels(0, 0, (int)t.width, (int)t.height);
  433. var newTexture = new Texture2D((int)t.width, (int)t.height, textureFormat, mipmaps);
  434. newTexture.SetPixels(spritePixels);
  435. if (applyImmediately)
  436. newTexture.Apply();
  437. return newTexture;
  438. }
  439. static bool IsRenderable (Attachment a) {
  440. return a is RegionAttachment || a is MeshAttachment;
  441. }
  442. /// <summary>
  443. /// Get a rect with flipped Y so that a Spine atlas rect gets converted to a Unity Sprite rect and vice versa.</summary>
  444. static Rect SpineUnityFlipRect (this Rect rect, int textureHeight) {
  445. rect.y = textureHeight - rect.y - rect.height;
  446. return rect;
  447. }
  448. /// <summary>
  449. /// Gets the Rect of an AtlasRegion according to Unity texture coordinates (x-right, y-up).
  450. /// This overload relies on region.page.height being correctly set.</summary>
  451. static Rect GetUnityRect (this AtlasRegion region) {
  452. return region.GetSpineAtlasRect().SpineUnityFlipRect(region.page.height);
  453. }
  454. /// <summary>
  455. /// Gets the Rect of an AtlasRegion according to Unity texture coordinates (x-right, y-up).</summary>
  456. static Rect GetUnityRect (this AtlasRegion region, int textureHeight) {
  457. return region.GetSpineAtlasRect().SpineUnityFlipRect(textureHeight);
  458. }
  459. /// <summary>
  460. /// Returns a Rect of the AtlasRegion according to Spine texture coordinates. (x-right, y-down)</summary>
  461. static Rect GetSpineAtlasRect (this AtlasRegion region, bool includeRotate = true) {
  462. if (includeRotate && region.rotate)
  463. return new Rect(region.x, region.y, region.height, region.width);
  464. else
  465. return new Rect(region.x, region.y, region.width, region.height);
  466. }
  467. /// <summary>
  468. /// Denormalize a uvRect into a texture-space Rect.</summary>
  469. static Rect UVRectToTextureRect (Rect uvRect, int texWidth, int texHeight) {
  470. uvRect.x *= texWidth;
  471. uvRect.width *= texWidth;
  472. uvRect.y *= texHeight;
  473. uvRect.height *= texHeight;
  474. return uvRect;
  475. }
  476. /// <summary>
  477. /// Normalize a texture Rect into UV coordinates.</summary>
  478. static Rect TextureRectToUVRect (Rect textureRect, int texWidth, int texHeight) {
  479. textureRect.x = Mathf.InverseLerp(0, texWidth, textureRect.x);
  480. textureRect.y = Mathf.InverseLerp(0, texHeight, textureRect.y);
  481. textureRect.width = Mathf.InverseLerp(0, texWidth, textureRect.width);
  482. textureRect.height = Mathf.InverseLerp(0, texHeight, textureRect.height);
  483. return textureRect;
  484. }
  485. /// <summary>
  486. /// Creates a new Spine AtlasRegion according to a Unity UV Rect (x-right, y-up, uv-normalized).</summary>
  487. static AtlasRegion UVRectToAtlasRegion (Rect uvRect, string name, AtlasPage page, float offsetX, float offsetY, bool rotate) {
  488. var tr = UVRectToTextureRect(uvRect, page.width, page.height);
  489. var rr = tr.SpineUnityFlipRect(page.height);
  490. int x = (int)rr.x, y = (int)rr.y;
  491. int w, h;
  492. if (rotate) {
  493. w = (int)rr.height;
  494. h = (int)rr.width;
  495. } else {
  496. w = (int)rr.width;
  497. h = (int)rr.height;
  498. }
  499. return new AtlasRegion {
  500. page = page,
  501. name = name,
  502. u = uvRect.xMin,
  503. u2 = uvRect.xMax,
  504. v = uvRect.yMax,
  505. v2 = uvRect.yMin,
  506. index = -1,
  507. width = w,
  508. originalWidth = w,
  509. height = h,
  510. originalHeight = h,
  511. offsetX = offsetX,
  512. offsetY = offsetY,
  513. x = x,
  514. y = y,
  515. rotate = rotate
  516. };
  517. }
  518. /// <summary>
  519. /// Tries to get the backing AtlasRegion of an attachment if it is renderable. Returns null for non-renderable attachments.</summary>
  520. static AtlasRegion GetAtlasRegion (this Attachment a) {
  521. var regionAttachment = a as RegionAttachment;
  522. if (regionAttachment != null)
  523. return (regionAttachment.RendererObject) as AtlasRegion;
  524. var meshAttachment = a as MeshAttachment;
  525. if (meshAttachment != null)
  526. return (meshAttachment.RendererObject) as AtlasRegion;
  527. return null;
  528. }
  529. /// <summary>
  530. /// Convenience method for getting the main texture of the material of the page of the region.</summary>
  531. static Texture2D GetMainTexture (this AtlasRegion region) {
  532. var material = (region.page.rendererObject as Material);
  533. return material.mainTexture as Texture2D;
  534. }
  535. static void ApplyPMA (this Texture2D texture, bool applyImmediately = true) {
  536. var pixels = texture.GetPixels();
  537. for (int i = 0, n = pixels.Length; i < n; i++) {
  538. Color p = pixels[i];
  539. float a = p.a;
  540. p.r = p.r * a;
  541. p.g = p.g * a;
  542. p.b = p.b * a;
  543. pixels[i] = p;
  544. }
  545. texture.SetPixels(pixels);
  546. if (applyImmediately)
  547. texture.Apply();
  548. }
  549. #endregion
  550. static float InverseLerp (float a, float b, float value) {
  551. return (value - a) / (b - a);
  552. }
  553. }
  554. public static class SkinExtensions {
  555. #region Skeleton Skin Extensions
  556. /// <summary>
  557. /// Convenience method for duplicating a skeleton's current active skin so changes to it will not affect other skeleton instances. .</summary>
  558. public static Skin UnshareSkin (this Skeleton skeleton, bool includeDefaultSkin, bool unshareAttachments, AnimationState state = null) {
  559. // 1. Copy the current skin and set the skeleton's skin to the new one.
  560. var newSkin = skeleton.GetClonedSkin("cloned skin", includeDefaultSkin, unshareAttachments, true);
  561. skeleton.SetSkin(newSkin);
  562. // 2. Apply correct attachments: skeleton.SetToSetupPose + animationState.Apply
  563. if (state != null) {
  564. skeleton.SetToSetupPose();
  565. state.Apply(skeleton);
  566. }
  567. // 3. Return unshared skin.
  568. return newSkin;
  569. }
  570. public static Skin GetClonedSkin (this Skeleton skeleton, string newSkinName, bool includeDefaultSkin = false, bool cloneAttachments = false, bool cloneMeshesAsLinked = true) {
  571. var newSkin = new Skin(newSkinName); // may have null name. Harmless.
  572. var defaultSkin = skeleton.data.DefaultSkin;
  573. var activeSkin = skeleton.skin;
  574. if (includeDefaultSkin)
  575. defaultSkin.CopyTo(newSkin, true, cloneAttachments, cloneMeshesAsLinked);
  576. if (activeSkin != null)
  577. activeSkin.CopyTo(newSkin, true, cloneAttachments, cloneMeshesAsLinked);
  578. return newSkin;
  579. }
  580. #endregion
  581. /// <summary>
  582. /// Gets a shallow copy of the skin. The cloned skin's attachments are shared with the original skin.</summary>
  583. public static Skin GetClone (this Skin original) {
  584. var newSkin = new Skin(original.name + " clone");
  585. var newSkinAttachments = newSkin.Attachments;
  586. foreach (var a in original.Attachments)
  587. newSkinAttachments[a.Key] = a.Value;
  588. return newSkin;
  589. }
  590. /// <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>
  591. public static void SetAttachment (this Skin skin, string slotName, string keyName, Attachment attachment, Skeleton skeleton) {
  592. int slotIndex = skeleton.FindSlotIndex(slotName);
  593. if (skeleton == null) throw new System.ArgumentNullException("skeleton", "skeleton cannot be null.");
  594. if (slotIndex == -1) throw new System.ArgumentException(string.Format("Slot '{0}' does not exist in skeleton.", slotName), "slotName");
  595. skin.AddAttachment(slotIndex, keyName, attachment);
  596. }
  597. /// <summary>Gets an attachment from the skin for the specified slot index and name.</summary>
  598. public static Attachment GetAttachment (this Skin skin, string slotName, string keyName, Skeleton skeleton) {
  599. int slotIndex = skeleton.FindSlotIndex(slotName);
  600. if (skeleton == null) throw new System.ArgumentNullException("skeleton", "skeleton cannot be null.");
  601. if (slotIndex == -1) throw new System.ArgumentException(string.Format("Slot '{0}' does not exist in skeleton.", slotName), "slotName");
  602. return skin.GetAttachment(slotIndex, keyName);
  603. }
  604. /// <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>
  605. public static void SetAttachment (this Skin skin, int slotIndex, string keyName, Attachment attachment) {
  606. skin.AddAttachment(slotIndex, keyName, attachment);
  607. }
  608. /// <summary>Removes the attachment. Returns true if the element is successfully found and removed; otherwise, false.</summary>
  609. public static bool RemoveAttachment (this Skin skin, string slotName, string keyName, Skeleton skeleton) {
  610. int slotIndex = skeleton.FindSlotIndex(slotName);
  611. if (skeleton == null) throw new System.ArgumentNullException("skeleton", "skeleton cannot be null.");
  612. if (slotIndex == -1) throw new System.ArgumentException(string.Format("Slot '{0}' does not exist in skeleton.", slotName), "slotName");
  613. return skin.RemoveAttachment(slotIndex, keyName);
  614. }
  615. /// <summary>Removes the attachment. Returns true if the element is successfully found and removed; otherwise, false.</summary>
  616. public static bool RemoveAttachment (this Skin skin, int slotIndex, string keyName) {
  617. return skin.Attachments.Remove(new Skin.AttachmentKeyTuple(slotIndex, keyName));
  618. }
  619. public static void Clear (this Skin skin) {
  620. skin.Attachments.Clear();
  621. }
  622. public static void Append (this Skin destination, Skin source) {
  623. source.CopyTo(destination, true, false);
  624. }
  625. public static void CopyTo (this Skin source, Skin destination, bool overwrite, bool cloneAttachments, bool cloneMeshesAsLinked = true) {
  626. var sourceAttachments = source.Attachments;
  627. var destinationAttachments = destination.Attachments;
  628. if (cloneAttachments) {
  629. if (overwrite) {
  630. foreach (var e in sourceAttachments)
  631. destinationAttachments[e.Key] = e.Value.GetClone(cloneMeshesAsLinked);
  632. } else {
  633. foreach (var e in sourceAttachments) {
  634. if (destinationAttachments.ContainsKey(e.Key)) continue;
  635. destinationAttachments.Add(e.Key, e.Value.GetClone(cloneMeshesAsLinked));
  636. }
  637. }
  638. } else {
  639. if (overwrite) {
  640. foreach (var e in sourceAttachments)
  641. destinationAttachments[e.Key] = e.Value;
  642. } else {
  643. foreach (var e in sourceAttachments) {
  644. if (destinationAttachments.ContainsKey(e.Key)) continue;
  645. destinationAttachments.Add(e.Key, e.Value);
  646. }
  647. }
  648. }
  649. }
  650. }
  651. public static class AttachmentCloneExtensions {
  652. /// <summary>
  653. /// Clones the attachment.</summary>
  654. public static Attachment GetClone (this Attachment o, bool cloneMeshesAsLinked) {
  655. var regionAttachment = o as RegionAttachment;
  656. if (regionAttachment != null)
  657. return regionAttachment.GetClone();
  658. var meshAttachment = o as MeshAttachment;
  659. if (meshAttachment != null)
  660. return cloneMeshesAsLinked ? meshAttachment.GetLinkedClone() : meshAttachment.GetClone();
  661. var boundingBoxAttachment = o as BoundingBoxAttachment;
  662. if (boundingBoxAttachment != null)
  663. return boundingBoxAttachment.GetClone();
  664. var pathAttachment = o as PathAttachment;
  665. if (pathAttachment != null)
  666. return pathAttachment.GetClone();
  667. return null;
  668. }
  669. public static RegionAttachment GetClone (this RegionAttachment o) {
  670. return new RegionAttachment(o.Name + "clone") {
  671. x = o.x,
  672. y = o.y,
  673. rotation = o.rotation,
  674. scaleX = o.scaleX,
  675. scaleY = o.scaleY,
  676. width = o.width,
  677. height = o.height,
  678. r = o.r,
  679. g = o.g,
  680. b = o.b,
  681. a = o.a,
  682. Path = o.Path,
  683. RendererObject = o.RendererObject,
  684. regionOffsetX = o.regionOffsetX,
  685. regionOffsetY = o.regionOffsetY,
  686. regionWidth = o.regionWidth,
  687. regionHeight = o.regionHeight,
  688. regionOriginalWidth = o.regionOriginalWidth,
  689. regionOriginalHeight = o.regionOriginalHeight,
  690. uvs = o.uvs.Clone() as float[],
  691. offset = o.offset.Clone() as float[]
  692. };
  693. }
  694. public static BoundingBoxAttachment GetClone (this BoundingBoxAttachment o) {
  695. var ba = new BoundingBoxAttachment(o.Name);
  696. CloneVertexAttachment(o, ba);
  697. return ba;
  698. }
  699. public static MeshAttachment GetLinkedClone (this MeshAttachment o, bool inheritDeform = true) {
  700. return o.GetLinkedMesh(o.Name, o.RendererObject as AtlasRegion, inheritDeform);
  701. }
  702. /// <summary>
  703. /// Returns a clone of the MeshAttachment. This will cause Deform animations to stop working unless you explicity set the .parentMesh to the original.</summary>
  704. public static MeshAttachment GetClone (this MeshAttachment o) {
  705. var ma = new MeshAttachment(o.Name) {
  706. r = o.r,
  707. g = o.g,
  708. b = o.b,
  709. a = o.a,
  710. inheritDeform = o.inheritDeform,
  711. Path = o.Path,
  712. RendererObject = o.RendererObject,
  713. regionOffsetX = o.regionOffsetX,
  714. regionOffsetY = o.regionOffsetY,
  715. regionWidth = o.regionWidth,
  716. regionHeight = o.regionHeight,
  717. regionOriginalWidth = o.regionOriginalWidth,
  718. regionOriginalHeight = o.regionOriginalHeight,
  719. RegionU = o.RegionU,
  720. RegionV = o.RegionV,
  721. RegionU2 = o.RegionU2,
  722. RegionV2 = o.RegionV2,
  723. RegionRotate = o.RegionRotate,
  724. uvs = o.uvs.Clone() as float[]
  725. };
  726. // Linked mesh
  727. if (o.ParentMesh != null) {
  728. // bones, vertices, worldVerticesLength, regionUVs, triangles, HullLength, Edges, Width, Height
  729. ma.ParentMesh = o.ParentMesh;
  730. } else {
  731. CloneVertexAttachment(o, ma); // bones, vertices, worldVerticesLength
  732. ma.regionUVs = o.regionUVs.Clone() as float[];
  733. ma.triangles = o.triangles.Clone() as int[];
  734. ma.hulllength = o.hulllength;
  735. // Nonessential.
  736. ma.Edges = (o.Edges == null) ? null : o.Edges.Clone() as int[]; // Allow absence of Edges array when nonessential data is not exported.
  737. ma.Width = o.Width;
  738. ma.Height = o.Height;
  739. }
  740. return ma;
  741. }
  742. public static PathAttachment GetClone (this PathAttachment o) {
  743. var newPathAttachment = new PathAttachment(o.Name) {
  744. lengths = o.lengths.Clone() as float[],
  745. closed = o.closed,
  746. constantSpeed = o.constantSpeed
  747. };
  748. CloneVertexAttachment(o, newPathAttachment);
  749. return newPathAttachment;
  750. }
  751. static void CloneVertexAttachment (VertexAttachment src, VertexAttachment dest) {
  752. dest.worldVerticesLength = src.worldVerticesLength;
  753. if (src.bones != null)
  754. dest.bones = src.bones.Clone() as int[];
  755. if (src.vertices != null)
  756. dest.vertices = src.vertices.Clone() as float[];
  757. }
  758. #region Runtime Linked MeshAttachments
  759. /// <summary>
  760. /// Returns a new linked mesh linked to this MeshAttachment. It will be mapped to the AtlasRegion provided.</summary>
  761. public static MeshAttachment GetLinkedMesh (this MeshAttachment o, string newLinkedMeshName, AtlasRegion region, bool inheritDeform = true) {
  762. //if (string.IsNullOrEmpty(attachmentName)) throw new System.ArgumentException("attachmentName cannot be null or empty", "attachmentName");
  763. if (region == null) throw new System.ArgumentNullException("region");
  764. // If parentMesh is a linked mesh, create a link to its parent. Preserves Deform animations.
  765. if (o.ParentMesh != null)
  766. o = o.ParentMesh;
  767. // 1. NewMeshAttachment (AtlasAttachmentLoader.cs)
  768. var mesh = new MeshAttachment(newLinkedMeshName);
  769. mesh.SetRegion(region, false);
  770. // 2. (SkeletonJson.cs::ReadAttachment. case: LinkedMesh)
  771. mesh.Path = newLinkedMeshName;
  772. mesh.r = 1f;
  773. mesh.g = 1f;
  774. mesh.b = 1f;
  775. mesh.a = 1f;
  776. //mesh.ParentMesh property call below sets mesh.Width and mesh.Height
  777. // 3. Link mesh with parent. (SkeletonJson.cs)
  778. mesh.inheritDeform = inheritDeform;
  779. mesh.ParentMesh = o;
  780. mesh.UpdateUVs();
  781. return mesh;
  782. }
  783. /// <summary>
  784. /// 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.
  785. /// For better caching and batching, use GetLinkedMesh(string, AtlasRegion, bool)</summary>
  786. public static MeshAttachment GetLinkedMesh (this MeshAttachment o, Sprite sprite, Shader shader, bool inheritDeform = true, Material materialPropertySource = null) {
  787. var m = new Material(shader);
  788. if (materialPropertySource != null) {
  789. m.CopyPropertiesFromMaterial(materialPropertySource);
  790. m.shaderKeywords = materialPropertySource.shaderKeywords;
  791. }
  792. return o.GetLinkedMesh(sprite.name, sprite.ToAtlasRegion(), inheritDeform);
  793. }
  794. /// <summary>
  795. /// 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.
  796. /// For better caching and batching, use GetLinkedMesh(string, AtlasRegion, bool)</summary>
  797. public static MeshAttachment GetLinkedMesh (this MeshAttachment o, Sprite sprite, Material materialPropertySource, bool inheritDeform = true) {
  798. return o.GetLinkedMesh(sprite, materialPropertySource.shader, inheritDeform, materialPropertySource);
  799. }
  800. #endregion
  801. #region RemappedClone Convenience Methods
  802. /// <summary>
  803. /// Gets a clone of the attachment remapped with a sprite image.</summary>
  804. /// <returns>The remapped clone.</returns>
  805. /// <param name="o">The original attachment.</param>
  806. /// <param name="sprite">The sprite whose texture to use.</param>
  807. /// <param name="sourceMaterial">The source material used to copy the shader and material properties from.</param>
  808. /// <param name="premultiplyAlpha">If <c>true</c>, a premultiply alpha clone of the original texture will be created.</param>
  809. /// <param name="cloneMeshAsLinked">If <c>true</c> MeshAttachments will be cloned as linked meshes and will inherit animation from the original attachment.</param>
  810. /// <param name="useOriginalRegionSize">If <c>true</c> the size of the original attachment will be followed, instead of using the Sprite size.</param>
  811. public static Attachment GetRemappedClone (this Attachment o, Sprite sprite, Material sourceMaterial, bool premultiplyAlpha = true, bool cloneMeshAsLinked = true, bool useOriginalRegionSize = false) {
  812. var atlasRegion = premultiplyAlpha ? sprite.ToAtlasRegionPMAClone(sourceMaterial) : sprite.ToAtlasRegion(false);
  813. return o.GetRemappedClone(atlasRegion, cloneMeshAsLinked, useOriginalRegionSize, 1f/sprite.pixelsPerUnit);
  814. }
  815. /// <summary>
  816. /// Gets a clone of the attachment remapped with an atlasRegion image.</summary>
  817. /// <returns>The remapped clone.</returns>
  818. /// <param name="o">The original attachment.</param>
  819. /// <param name="atlasRegion">Atlas region.</param>
  820. /// <param name="cloneMeshAsLinked">If <c>true</c> MeshAttachments will be cloned as linked meshes and will inherit animation from the original attachment.</param>
  821. /// <param name="useOriginalRegionSize">If <c>true</c> the size of the original attachment will be followed, instead of using the Sprite size.</param>
  822. /// <param name="scale">Unity units per pixel scale used to scale the atlas region size when not using the original region size.</param>
  823. public static Attachment GetRemappedClone (this Attachment o, AtlasRegion atlasRegion, bool cloneMeshAsLinked = true, bool useOriginalRegionSize = false, float scale = 0.01f) {
  824. var regionAttachment = o as RegionAttachment;
  825. if (regionAttachment != null) {
  826. RegionAttachment newAttachment = regionAttachment.GetClone();
  827. newAttachment.SetRegion(atlasRegion, false);
  828. if (!useOriginalRegionSize) {
  829. newAttachment.width = atlasRegion.width * scale;
  830. newAttachment.height = atlasRegion.height * scale;
  831. }
  832. newAttachment.UpdateOffset();
  833. return newAttachment;
  834. } else {
  835. var meshAttachment = o as MeshAttachment;
  836. if (meshAttachment != null) {
  837. MeshAttachment newAttachment = cloneMeshAsLinked ? meshAttachment.GetLinkedClone(cloneMeshAsLinked) : meshAttachment.GetClone();
  838. newAttachment.SetRegion(atlasRegion);
  839. return newAttachment;
  840. }
  841. }
  842. return o.GetClone(true); // Non-renderable Attachments will return as normal cloned attachments.
  843. }
  844. #endregion
  845. }
  846. }