TextureAtlas.lua 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  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. local setmetatable = setmetatable
  31. local table_insert = table.insert
  32. local math_abs = math.abs
  33. local TextureAtlasRegion = require "spine-lua.TextureAtlasRegion"
  34. local TextureWrap = require "spine-lua.TextureWrap"
  35. local TextureFilter = require "spine-lua.TextureFilter"
  36. local TextureAtlasPage = {}
  37. TextureAtlasPage.__index = TextureAtlasPage
  38. function TextureAtlasPage.new ()
  39. local self = {
  40. name = nil,
  41. minFilter = nil,
  42. magFilter = nil,
  43. uWrap = nil,
  44. vWrap = nil,
  45. texture = nil,
  46. width = 0,
  47. height = 0
  48. }
  49. setmetatable(self, TextureAtlasPage)
  50. return self
  51. end
  52. local TextureAtlas = {}
  53. TextureAtlas.__index = TextureAtlas
  54. function TextureAtlas.new (atlasContent, imageLoader)
  55. local self = {
  56. pages = {},
  57. regions = {}
  58. }
  59. setmetatable(self, TextureAtlas)
  60. self:parse(atlasContent, imageLoader)
  61. return self
  62. end
  63. function TextureAtlas:parse (atlasContent, imageLoader)
  64. if not atlasContent then error("atlasContent cannot be nil.", 2) end
  65. if not imageLoader then error("imageLoader cannot be nil.", 2) end
  66. function lineIterator(s)
  67. if s:sub(-1)~="\n" then s=s.."\n" end
  68. return s:gmatch("(.-)\n")
  69. end
  70. local lines = {}
  71. local index = 0
  72. local numLines = 0
  73. for line in lineIterator(atlasContent) do
  74. lines[numLines] = line
  75. numLines = numLines + 1
  76. end
  77. local readLine = function ()
  78. if index >= numLines then return nil end
  79. local line = lines[index]
  80. index = index + 1
  81. return line
  82. end
  83. local readValue = function ()
  84. local line = readLine()
  85. local idx = line:find(":")
  86. if not idx then error("Invalid line: " .. line, 2) end
  87. return line:sub(idx + 1):match'^%s*(.*%S)' or ''
  88. end
  89. local readTuple = function ()
  90. local line = readLine()
  91. local idx = line:find(":")
  92. if not idx then
  93. error("Invalid line: " .. line, 2)
  94. end
  95. local i = 1
  96. local lastMatch = idx + 1
  97. local tuple = {}
  98. while i <= 3 do
  99. local comma = line:find(",", lastMatch)
  100. if not comma then break end
  101. tuple[i] = line:sub(lastMatch, comma - 1):match'^%s*(.*%S)' or ''
  102. lastMatch = comma + 1
  103. i = i + 1
  104. end
  105. tuple[i] = line:sub(lastMatch):match'^%s*(.*%S)' or ''
  106. return tuple
  107. end
  108. local parseInt = function (str)
  109. return tonumber(str)
  110. end
  111. local filterFromString = function (str)
  112. str = str:lower()
  113. if str == "nearest" then return TextureFilter.Nearest
  114. elseif str == "linear" then return TextureFilter.Linear
  115. elseif str == "mipmap" then return TextureFilter.MipMap
  116. elseif str == "mipmapnearestnearest" then return TextureFilter.MipMapNearestNearest
  117. elseif str == "mipmaplinearnearest" then return TextureFilter.MipMapLinearNearest
  118. elseif str == "mipmapnearestlinear" then return TextureFilter.MipMapNearestLinear
  119. elseif str == "mipmaplinearlinear" then return TextureFilter.MipMapLinearLinear
  120. else error("Unknown texture wrap: " .. str, 2)
  121. end
  122. end
  123. local page = nil
  124. while true do
  125. local line = readLine()
  126. if not line then break end
  127. line = line:match'^%s*(.*%S)' or ''
  128. if line:len() == 0 then
  129. page = nil
  130. elseif not page then
  131. page = TextureAtlasPage.new()
  132. page.name = line
  133. local tuple = readTuple()
  134. if #tuple == 2 then
  135. page.width = parseInt(tuple[1])
  136. page.height = parseInt(tuple[2])
  137. tuple = readTuple()
  138. else
  139. -- We only support atlases that have the page width/height
  140. -- encoded in them. That way we don't rely on any special
  141. -- wrapper objects for images to get the page size from
  142. error("Atlas must specify page width/height. Please export to the latest atlas format", 2)
  143. end
  144. tuple = readTuple()
  145. page.minFilter = filterFromString(tuple[1])
  146. page.magFilter = filterFromString(tuple[2])
  147. local direction = readValue()
  148. page.uWrap = TextureWrap.ClampToEdge
  149. page.vWrap = TextureWrap.ClampToEdge
  150. if direction == "x" then
  151. page.uWrap = TextureWrap.Repeat
  152. elseif direction == "y" then
  153. page.vWrap = TextureWrap.Repeat
  154. elseif direction == "xy" then
  155. page.uWrap = TextureWrap.Repeat
  156. page.vWrap = TextureWrap.Repeat
  157. end
  158. page.texture = imageLoader(line)
  159. -- FIXME page.texture:setFilters(page.minFilter, page.magFilter)
  160. -- FIXME page.texture:setWraps(page.uWrap, page.vWrap)
  161. table_insert(self.pages, page)
  162. else
  163. local region = TextureAtlasRegion.new()
  164. region.name = line
  165. region.page = page
  166. if readValue() == "true" then region.rotate = true end
  167. local tuple = readTuple()
  168. local x = parseInt(tuple[1])
  169. local y = parseInt(tuple[2])
  170. tuple = readTuple()
  171. local width = parseInt(tuple[1])
  172. local height = parseInt(tuple[2])
  173. region.u = x / page.width
  174. region.v = y / page.height
  175. if region.rotate then
  176. region.u2 = (x + height) / page.width
  177. region.v2 = (y + width) / page.height
  178. else
  179. region.u2 = (x + width) / page.width
  180. region.v2 = (y + height) / page.height
  181. end
  182. region.x = x
  183. region.y = y
  184. region.width = math_abs(width)
  185. region.height = math_abs(height)
  186. -- Read and skip optional splits
  187. tuple = readTuple()
  188. if #tuple == 4 then
  189. tuple = readTuple()
  190. if #tuple == 4 then
  191. readTuple()
  192. end
  193. end
  194. region.originalWidth = parseInt(tuple[1])
  195. region.originalHeight = parseInt(tuple[2])
  196. tuple = readTuple()
  197. region.offsetX = parseInt(tuple[1])
  198. region.offsetY = parseInt(tuple[2])
  199. region.index = parseInt(readValue())
  200. region.texture = page.texture
  201. table_insert(self.regions, region)
  202. end
  203. end
  204. end
  205. function TextureAtlas:findRegion(name)
  206. for _, region in ipairs(self.regions) do
  207. if region.name == name then return region end
  208. end
  209. return nil
  210. end
  211. function TextureAtlas:dispose()
  212. for _, page in ipairs(self.pairs) do
  213. -- FIXME implement disposing of pages
  214. -- love2d doesn't support manual disposing
  215. end
  216. end
  217. return TextureAtlas