Atlas.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated January 1, 2020. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2020, Esoteric Software LLC
  6. *
  7. * Integration of the Spine Runtimes into software or otherwise creating
  8. * derivative works of the Spine Runtimes is permitted under the terms and
  9. * conditions of Section 2 of the Spine Editor License Agreement:
  10. * http://esotericsoftware.com/spine-editor-license
  11. *
  12. * Otherwise, it is permitted to integrate the Spine Runtimes into software
  13. * or otherwise create derivative works of the Spine Runtimes (collectively,
  14. * "Products"), provided that each user of the Products must obtain their own
  15. * Spine Editor license and redistribution of the Products in any form must
  16. * include this license and copyright notice.
  17. *
  18. * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
  19. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
  24. * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  27. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. #if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
  30. #define IS_UNITY
  31. #endif
  32. using System;
  33. using System.Collections.Generic;
  34. using System.Globalization;
  35. using System.IO;
  36. using System.Reflection;
  37. #if WINDOWS_STOREAPP
  38. using System.Threading.Tasks;
  39. using Windows.Storage;
  40. #endif
  41. namespace Spine {
  42. public class Atlas : IEnumerable<AtlasRegion> {
  43. readonly List<AtlasPage> pages = new List<AtlasPage>();
  44. List<AtlasRegion> regions = new List<AtlasRegion>();
  45. TextureLoader textureLoader;
  46. #region IEnumerable implementation
  47. public IEnumerator<AtlasRegion> GetEnumerator () {
  48. return regions.GetEnumerator();
  49. }
  50. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
  51. return regions.GetEnumerator();
  52. }
  53. #endregion
  54. public List<AtlasRegion> Regions { get { return regions; } }
  55. public List<AtlasPage> Pages { get { return pages; } }
  56. #if !(IS_UNITY)
  57. #if WINDOWS_STOREAPP
  58. private async Task ReadFile(string path, TextureLoader textureLoader) {
  59. var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
  60. var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
  61. using (var reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) {
  62. try {
  63. Load(reader, Path.GetDirectoryName(path), textureLoader);
  64. } catch (Exception ex) {
  65. throw new Exception("Error reading atlas file: " + path, ex);
  66. }
  67. }
  68. }
  69. public Atlas(string path, TextureLoader textureLoader) {
  70. this.ReadFile(path, textureLoader).Wait();
  71. }
  72. #else
  73. public Atlas (string path, TextureLoader textureLoader) {
  74. #if WINDOWS_PHONE
  75. Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path);
  76. using (StreamReader reader = new StreamReader(stream)) {
  77. #else
  78. using (StreamReader reader = new StreamReader(path)) {
  79. #endif // WINDOWS_PHONE
  80. try {
  81. Load(reader, Path.GetDirectoryName(path), textureLoader);
  82. } catch (Exception ex) {
  83. throw new Exception("Error reading atlas file: " + path, ex);
  84. }
  85. }
  86. }
  87. #endif // WINDOWS_STOREAPP
  88. #endif
  89. public Atlas (TextReader reader, string dir, TextureLoader textureLoader) {
  90. Load(reader, dir, textureLoader);
  91. }
  92. public Atlas (List<AtlasPage> pages, List<AtlasRegion> regions) {
  93. this.pages = pages;
  94. this.regions = regions;
  95. this.textureLoader = null;
  96. }
  97. private void Load (TextReader reader, string imagesDir, TextureLoader textureLoader) {
  98. if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null.");
  99. this.textureLoader = textureLoader;
  100. string[] tuple = new string[4];
  101. AtlasPage page = null;
  102. while (true) {
  103. string line = reader.ReadLine();
  104. if (line == null) break;
  105. if (line.Trim().Length == 0)
  106. page = null;
  107. else if (page == null) {
  108. page = new AtlasPage();
  109. page.name = line;
  110. if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker.
  111. page.width = int.Parse(tuple[0], CultureInfo.InvariantCulture);
  112. page.height = int.Parse(tuple[1], CultureInfo.InvariantCulture);
  113. ReadTuple(reader, tuple);
  114. }
  115. page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false);
  116. ReadTuple(reader, tuple);
  117. page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false);
  118. page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false);
  119. string direction = ReadValue(reader);
  120. page.uWrap = TextureWrap.ClampToEdge;
  121. page.vWrap = TextureWrap.ClampToEdge;
  122. if (direction == "x")
  123. page.uWrap = TextureWrap.Repeat;
  124. else if (direction == "y")
  125. page.vWrap = TextureWrap.Repeat;
  126. else if (direction == "xy")
  127. page.uWrap = page.vWrap = TextureWrap.Repeat;
  128. textureLoader.Load(page, Path.Combine(imagesDir, line));
  129. pages.Add(page);
  130. } else {
  131. AtlasRegion region = new AtlasRegion();
  132. region.name = line;
  133. region.page = page;
  134. string rotateValue = ReadValue(reader);
  135. if (rotateValue == "true")
  136. region.degrees = 90;
  137. else if (rotateValue == "false")
  138. region.degrees = 0;
  139. else
  140. region.degrees = int.Parse(rotateValue);
  141. region.rotate = region.degrees == 90;
  142. ReadTuple(reader, tuple);
  143. int x = int.Parse(tuple[0], CultureInfo.InvariantCulture);
  144. int y = int.Parse(tuple[1], CultureInfo.InvariantCulture);
  145. ReadTuple(reader, tuple);
  146. int width = int.Parse(tuple[0], CultureInfo.InvariantCulture);
  147. int height = int.Parse(tuple[1], CultureInfo.InvariantCulture);
  148. region.u = x / (float)page.width;
  149. region.v = y / (float)page.height;
  150. if (region.rotate) {
  151. region.u2 = (x + height) / (float)page.width;
  152. region.v2 = (y + width) / (float)page.height;
  153. } else {
  154. region.u2 = (x + width) / (float)page.width;
  155. region.v2 = (y + height) / (float)page.height;
  156. }
  157. region.x = x;
  158. region.y = y;
  159. region.width = Math.Abs(width);
  160. region.height = Math.Abs(height);
  161. if (ReadTuple(reader, tuple) == 4) { // split is optional
  162. region.splits = new [] {int.Parse(tuple[0], CultureInfo.InvariantCulture),
  163. int.Parse(tuple[1], CultureInfo.InvariantCulture),
  164. int.Parse(tuple[2], CultureInfo.InvariantCulture),
  165. int.Parse(tuple[3], CultureInfo.InvariantCulture)};
  166. if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits
  167. region.pads = new [] {int.Parse(tuple[0], CultureInfo.InvariantCulture),
  168. int.Parse(tuple[1], CultureInfo.InvariantCulture),
  169. int.Parse(tuple[2], CultureInfo.InvariantCulture),
  170. int.Parse(tuple[3], CultureInfo.InvariantCulture)};
  171. ReadTuple(reader, tuple);
  172. }
  173. }
  174. region.originalWidth = int.Parse(tuple[0], CultureInfo.InvariantCulture);
  175. region.originalHeight = int.Parse(tuple[1], CultureInfo.InvariantCulture);
  176. ReadTuple(reader, tuple);
  177. region.offsetX = int.Parse(tuple[0], CultureInfo.InvariantCulture);
  178. region.offsetY = int.Parse(tuple[1], CultureInfo.InvariantCulture);
  179. region.index = int.Parse(ReadValue(reader), CultureInfo.InvariantCulture);
  180. regions.Add(region);
  181. }
  182. }
  183. }
  184. static string ReadValue (TextReader reader) {
  185. string line = reader.ReadLine();
  186. int colon = line.IndexOf(':');
  187. if (colon == -1) throw new Exception("Invalid line: " + line);
  188. return line.Substring(colon + 1).Trim();
  189. }
  190. /// <summary>Returns the number of tuple values read (1, 2 or 4).</summary>
  191. static int ReadTuple (TextReader reader, string[] tuple) {
  192. string line = reader.ReadLine();
  193. int colon = line.IndexOf(':');
  194. if (colon == -1) throw new Exception("Invalid line: " + line);
  195. int i = 0, lastMatch = colon + 1;
  196. for (; i < 3; i++) {
  197. int comma = line.IndexOf(',', lastMatch);
  198. if (comma == -1) break;
  199. tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim();
  200. lastMatch = comma + 1;
  201. }
  202. tuple[i] = line.Substring(lastMatch).Trim();
  203. return i + 1;
  204. }
  205. public void FlipV () {
  206. for (int i = 0, n = regions.Count; i < n; i++) {
  207. AtlasRegion region = regions[i];
  208. region.v = 1 - region.v;
  209. region.v2 = 1 - region.v2;
  210. }
  211. }
  212. /// <summary>Returns the first region found with the specified name. This method uses string comparison to find the region, so the result
  213. /// should be cached rather than calling this method multiple times.</summary>
  214. /// <returns>The region, or null.</returns>
  215. public AtlasRegion FindRegion (string name) {
  216. for (int i = 0, n = regions.Count; i < n; i++)
  217. if (regions[i].name == name) return regions[i];
  218. return null;
  219. }
  220. public void Dispose () {
  221. if (textureLoader == null) return;
  222. for (int i = 0, n = pages.Count; i < n; i++)
  223. textureLoader.Unload(pages[i].rendererObject);
  224. }
  225. }
  226. public enum Format {
  227. Alpha,
  228. Intensity,
  229. LuminanceAlpha,
  230. RGB565,
  231. RGBA4444,
  232. RGB888,
  233. RGBA8888
  234. }
  235. public enum TextureFilter {
  236. Nearest,
  237. Linear,
  238. MipMap,
  239. MipMapNearestNearest,
  240. MipMapLinearNearest,
  241. MipMapNearestLinear,
  242. MipMapLinearLinear
  243. }
  244. public enum TextureWrap {
  245. MirroredRepeat,
  246. ClampToEdge,
  247. Repeat
  248. }
  249. public class AtlasPage {
  250. public string name;
  251. public Format format;
  252. public TextureFilter minFilter;
  253. public TextureFilter magFilter;
  254. public TextureWrap uWrap;
  255. public TextureWrap vWrap;
  256. public object rendererObject;
  257. public int width, height;
  258. public AtlasPage Clone () {
  259. return MemberwiseClone() as AtlasPage;
  260. }
  261. }
  262. public class AtlasRegion {
  263. public AtlasPage page;
  264. public string name;
  265. public int x, y, width, height;
  266. public float u, v, u2, v2;
  267. public float offsetX, offsetY;
  268. public int originalWidth, originalHeight;
  269. public int index;
  270. public bool rotate;
  271. public int degrees;
  272. public int[] splits;
  273. public int[] pads;
  274. public AtlasRegion Clone () {
  275. return MemberwiseClone() as AtlasRegion;
  276. }
  277. }
  278. public interface TextureLoader {
  279. void Load (AtlasPage page, string path);
  280. void Unload (Object texture);
  281. }
  282. }