TextureAtlas.lua 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. -------------------------------------------------------------------------------
  2. -- Spine Runtimes License Agreement
  3. -- Last updated May 1, 2019. Replaces all prior versions.
  4. --
  5. -- Copyright (c) 2013-2019, 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. -- THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
  19. -- OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  20. -- OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
  21. -- NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
  22. -- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  23. -- BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
  24. -- INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
  25. -- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  26. -- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  27. -- EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. -------------------------------------------------------------------------------
  29. local setmetatable = setmetatable
  30. local table_insert = table.insert
  31. local math_abs = math.abs
  32. local TextureAtlasRegion = require "spine-lua.TextureAtlasRegion"
  33. local TextureWrap = require "spine-lua.TextureWrap"
  34. local TextureFilter = require "spine-lua.TextureFilter"
  35. local TextureAtlasPage = {}
  36. TextureAtlasPage.__index = TextureAtlasPage
  37. function TextureAtlasPage.new ()
  38. local self = {
  39. name = nil,
  40. minFilter = nil,
  41. magFilter = nil,
  42. uWrap = nil,
  43. vWrap = nil,
  44. texture = nil,
  45. width = 0,
  46. height = 0
  47. }
  48. setmetatable(self, TextureAtlasPage)
  49. return self
  50. end
  51. local TextureAtlas = {}
  52. TextureAtlas.__index = TextureAtlas
  53. function TextureAtlas.new (atlasContent, imageLoader)
  54. local self = {
  55. pages = {},
  56. regions = {}
  57. }
  58. setmetatable(self, TextureAtlas)
  59. self:parse(atlasContent, imageLoader)
  60. return self
  61. end
  62. function TextureAtlas:parse (atlasContent, imageLoader)
  63. if not atlasContent then error("atlasContent cannot be nil.", 2) end
  64. if not imageLoader then error("imageLoader cannot be nil.", 2) end
  65. function lineIterator(s)
  66. if s:sub(-1)~="\n" then s=s.."\n" end
  67. return s:gmatch("(.-)\n")
  68. end
  69. local lines = {}
  70. local index = 0
  71. local numLines = 0
  72. for line in lineIterator(atlasContent) do
  73. lines[numLines] = line
  74. numLines = numLines + 1
  75. end
  76. local readLine = function ()
  77. if index >= numLines then return nil end
  78. local line = lines[index]
  79. index = index + 1
  80. return line
  81. end
  82. local readValue = function ()
  83. local line = readLine()
  84. local idx = line:find(":")
  85. if not idx then error("Invalid line: " .. line, 2) end
  86. return line:sub(idx + 1):match'^%s*(.*%S)' or ''
  87. end
  88. local readTuple = function ()
  89. local line = readLine()
  90. local idx = line:find(":")
  91. if not idx then
  92. error("Invalid line: " .. line, 2)
  93. end
  94. local i = 1
  95. local lastMatch = idx + 1
  96. local tuple = {}
  97. while i <= 3 do
  98. local comma = line:find(",", lastMatch)
  99. if not comma then break end
  100. tuple[i] = line:sub(lastMatch, comma - 1):match'^%s*(.*%S)' or ''
  101. lastMatch = comma + 1
  102. i = i + 1
  103. end
  104. tuple[i] = line:sub(lastMatch):match'^%s*(.*%S)' or ''
  105. return tuple
  106. end
  107. local parseInt = function (str)
  108. return tonumber(str)
  109. end
  110. local filterFromString = function (str)
  111. str = str:lower()
  112. if str == "nearest" then return TextureFilter.Nearest
  113. elseif str == "linear" then return TextureFilter.Linear
  114. elseif str == "mipmap" then return TextureFilter.MipMap
  115. elseif str == "mipmapnearestnearest" then return TextureFilter.MipMapNearestNearest
  116. elseif str == "mipmaplinearnearest" then return TextureFilter.MipMapLinearNearest
  117. elseif str == "mipmapnearestlinear" then return TextureFilter.MipMapNearestLinear
  118. elseif str == "mipmaplinearlinear" then return TextureFilter.MipMapLinearLinear
  119. else error("Unknown texture wrap: " .. str, 2)
  120. end
  121. end
  122. local page = nil
  123. while true do
  124. local line = readLine()
  125. if not line then break end
  126. line = line:match'^%s*(.*%S)' or ''
  127. if line:len() == 0 then
  128. page = nil
  129. elseif not page then
  130. page = TextureAtlasPage.new()
  131. page.name = line
  132. local tuple = readTuple()
  133. if #tuple == 2 then
  134. page.width = parseInt(tuple[1])
  135. page.height = parseInt(tuple[2])
  136. tuple = readTuple()
  137. else
  138. -- We only support atlases that have the page width/height
  139. -- encoded in them. That way we don't rely on any special
  140. -- wrapper objects for images to get the page size from
  141. error("Atlas must specify page width/height. Please export to the latest atlas format", 2)
  142. end
  143. tuple = readTuple()
  144. page.minFilter = filterFromString(tuple[1])
  145. page.magFilter = filterFromString(tuple[2])
  146. local direction = readValue()
  147. page.uWrap = TextureWrap.ClampToEdge
  148. page.vWrap = TextureWrap.ClampToEdge
  149. if direction == "x" then
  150. page.uWrap = TextureWrap.Repeat
  151. elseif direction == "y" then
  152. page.vWrap = TextureWrap.Repeat
  153. elseif direction == "xy" then
  154. page.uWrap = TextureWrap.Repeat
  155. page.vWrap = TextureWrap.Repeat
  156. end
  157. page.texture = imageLoader(line)
  158. -- FIXME page.texture:setFilters(page.minFilter, page.magFilter)
  159. -- FIXME page.texture:setWraps(page.uWrap, page.vWrap)
  160. table_insert(self.pages, page)
  161. else
  162. local region = TextureAtlasRegion.new()
  163. region.name = line
  164. region.page = page
  165. if readValue() == "true" then region.rotate = true end
  166. local tuple = readTuple()
  167. local x = parseInt(tuple[1])
  168. local y = parseInt(tuple[2])
  169. tuple = readTuple()
  170. local width = parseInt(tuple[1])
  171. local height = parseInt(tuple[2])
  172. region.u = x / page.width
  173. region.v = y / page.height
  174. if region.rotate then
  175. region.u2 = (x + height) / page.width
  176. region.v2 = (y + width) / page.height
  177. else
  178. region.u2 = (x + width) / page.width
  179. region.v2 = (y + height) / page.height
  180. end
  181. region.x = x
  182. region.y = y
  183. region.width = math_abs(width)
  184. region.height = math_abs(height)
  185. -- Read and skip optional splits
  186. tuple = readTuple()
  187. if #tuple == 4 then
  188. tuple = readTuple()
  189. if #tuple == 4 then
  190. readTuple()
  191. end
  192. end
  193. region.originalWidth = parseInt(tuple[1])
  194. region.originalHeight = parseInt(tuple[2])
  195. tuple = readTuple()
  196. region.offsetX = parseInt(tuple[1])
  197. region.offsetY = parseInt(tuple[2])
  198. region.index = parseInt(readValue())
  199. region.texture = page.texture
  200. table_insert(self.regions, region)
  201. end
  202. end
  203. end
  204. function TextureAtlas:findRegion(name)
  205. for _, region in ipairs(self.regions) do
  206. if region.name == name then return region end
  207. end
  208. return nil
  209. end
  210. function TextureAtlas:dispose()
  211. for _, page in ipairs(self.pairs) do
  212. -- FIXME implement disposing of pages
  213. -- love2d doesn't support manual disposing
  214. end
  215. end
  216. return TextureAtlas