light_world.lua/examples/vendor/sti/map.lua

1086 lines
25 KiB
Lua
Raw Permalink Normal View History

local Map = {}
local framework
-- https://github.com/stevedonovan/Penlight/blob/master/lua/pl/path.lua#L286
local function formatPath(path)
local np_gen1,np_gen2 = '[^SEP]+SEP%.%.SEP?','SEP+%.?SEP'
local np_pat1, np_pat2 = np_gen1:gsub('SEP','/'), np_gen2:gsub('SEP','/')
local k
repeat -- /./ -> /
path,k = path:gsub(np_pat2,'/')
until k == 0
repeat -- A/../ -> (empty)
path,k = path:gsub(np_pat1,'')
until k == 0
if path == '' then path = '.' end
return path
end
local function rotateVertex(v, x, y, cos, sin)
local vertex = {
x = v.x,
y = v.y,
}
vertex.x = vertex.x - x
vertex.y = vertex.y - y
local vx = cos * vertex.x - sin * vertex.y
local vy = sin * vertex.x + cos * vertex.y
return vx + x, vy + y
end
local function convertEllipseToPolygon(x, y, w, h, max_segments)
local function calc_segments(segments)
local function vdist(a, b)
local c = {
x = a.x - b.x,
y = a.y - b.y,
}
return c.x * c.x + c.y * c.y
end
segments = segments or 64
local vertices = {}
local v = { 1, 2, math.ceil(segments/4-1), math.ceil(segments/4) }
local m
if framework.getMeter then
m = framework.getMeter()
else
m = self.tilewidth + self.tileheight / 2
end
for _, i in ipairs(v) do
local angle = (i / segments) * math.pi * 2
local px = x + w / 2 + math.cos(angle) * w / 2
local py = y + h / 2 + math.sin(angle) * h / 2
table.insert(vertices, { x = px / m, y = py / m })
end
local dist1 = vdist(vertices[1], vertices[2])
local dist2 = vdist(vertices[3], vertices[4])
-- Box2D hard-coded threshold
if dist1 < 0.0025 or dist2 < 0.0025 then
return calc_segments(segments-2)
end
return segments
end
local segments = calc_segments(max_segments)
local vertices = {}
table.insert(vertices, { x = x + w / 2, y = y + h / 2 })
for i=0, segments do
local angle = (i / segments) * math.pi * 2
local px = x + w / 2 + math.cos(angle) * w / 2
local py = y + h / 2 + math.sin(angle) * h / 2
table.insert(vertices, { x = px, y = py })
end
return vertices
end
function Map:init(path, fw)
framework = fw
self.canvas = framework:newCanvas()
self.tiles = {}
self.tileInstances = {}
self.drawRange = {
sx = 1,
sy = 1,
ex = self.width,
ey = self.height,
}
-- Set tiles, images
local gid = 1
for i, tileset in ipairs(self.tilesets) do
local image = formatPath(path .. tileset.image)
tileset.image = framework.newImage(image)
gid = self:setTiles(i, tileset, gid)
end
-- Set layers
for i, layer in ipairs(self.layers) do
self:setLayer(layer, path)
end
end
function Map:initWorldCollision(world)
assert(framework.newBody, "To use the built-in collision system, please enable the physics module.")
local body = framework.newBody(world)
local collision = {
body = body,
}
local function addObjectToWorld(objshape, vertices)
local shape
if objshape == "polyline" then
shape = framework.newChainShape(false, unpack(vertices))
else
shape = framework.newPolygonShape(unpack(vertices))
end
local fixture = framework.newFixture(body, shape)
local obj = {
shape = shape,
fixture = fixture,
}
table.insert(collision, obj)
end
local function getPolygonVertices(object, tile, precalc)
local ox, oy = 0, 0
if not precalc then
ox = object.x
oy = object.y
end
local vertices = {}
for _, vertex in ipairs(object.polygon) do
table.insert(vertices, tile.x + ox + vertex.x)
table.insert(vertices, tile.y + oy + vertex.y)
end
return vertices
end
local function calculateObjectPosition(object, tile)
local o = {
shape = object.shape,
x = object.x,
y = object.y,
w = object.width,
h = object.height,
polygon = object.polygon or object.polyline or object.ellipse or object.rectangle
}
local t = tile or { x=0, y=0 }
if o.shape == "rectangle" then
o.r = object.rotation or 0
local cos = math.cos(math.rad(o.r))
local sin = math.sin(math.rad(o.r))
if object.gid then
local tileset = self.tiles[object.gid].tileset
local lid = object.gid - self.tilesets[tileset].firstgid
local tile = {}
-- This fixes a height issue
o.y = o.y + self.tiles[object.gid].offset.y
for _, t in ipairs(self.tilesets[tileset].tiles) do
if t.id == lid then
tile = t
break
end
end
if tile.objectGroup then
for _, obj in ipairs(tile.objectGroup.objects) do
-- Every object in the tile
calculateObjectPosition(obj, object)
end
return
else
o.w = self.tiles[object.gid].width
o.h = self.tiles[object.gid].height
end
end
o.polygon = {
{ x=o.x, y=o.y },
{ x=o.x + o.w, y=o.y },
{ x=o.x + o.w, y=o.y + o.h },
{ x=o.x, y=o.y + o.h },
}
for _, vertex in ipairs(o.polygon) do
if self.orientation == "isometric" then
vertex.x, vertex.y = self:convertIsometricToScreen(vertex.x, vertex.y)
end
vertex.x, vertex.y = rotateVertex(vertex, o.x, o.y, cos, sin)
end
local vertices = getPolygonVertices(o, t, true)
addObjectToWorld(o.shape, vertices)
elseif o.shape == "ellipse" then
if not o.polygon then
o.polygon = convertEllipseToPolygon(o.x, o.y, o.w, o.h)
end
local vertices = getPolygonVertices(o, t, true)
local triangles = framework.triangulate(vertices)
for _, triangle in ipairs(triangles) do
addObjectToWorld(o.shape, triangle)
end
elseif o.shape == "polygon" then
local precalc = false
if not t.gid then precalc = true end
local vertices = getPolygonVertices(o, t, precalc)
local triangles = framework.triangulate(vertices)
for _, triangle in ipairs(triangles) do
addObjectToWorld(o.shape, triangle)
end
elseif o.shape == "polyline" then
local precalc = false
if not t.gid then precalc = true end
local vertices = getPolygonVertices(o, t, precalc)
addObjectToWorld(o.shape, vertices)
end
end
for _, tileset in ipairs(self.tilesets) do
for _, tile in ipairs(tileset.tiles) do
local gid = tileset.firstgid + tile.id
if tile.objectGroup then
if self.tileInstances[gid] then
for _, instance in ipairs(self.tileInstances[gid]) do
for _, object in ipairs(tile.objectGroup.objects) do
-- Every object in every instance of a tile
calculateObjectPosition(object, instance)
end
end
end
elseif tile.properties.collidable == "true" then
for _, instance in ipairs(self.tileInstances[gid]) do
-- Every instance of a tile
local object = {
shape = "rectangle",
x = 0,
y = 0,
width = tileset.tilewidth,
height = tileset.tileheight,
}
calculateObjectPosition(object, instance)
end
end
end
end
for _, layer in ipairs(self.layers) do
if layer.properties.collidable == "true" then
-- Entire layer
if layer.type == "tilelayer" then
for y, tiles in ipairs(layer.data) do
for x, tile in pairs(tiles) do
local object = {
shape = "rectangle",
x = x * self.width + tile.offset.x,
y = y * self.height + tile.offset.y,
width = tile.width,
height = tile.height,
}
calculateObjectPosition(object)
end
end
elseif layer.type == "objectgroup" then
for _, object in ipairs(layer.objects) do
calculateObjectPosition(object)
end
elseif layer.type == "imagelayer" then
local object = {
shape = "rectangle",
x = layer.x or 0,
y = layer.y or 0,
width = layer.width,
height = layer.height,
}
calculateObjectPosition(object)
end
end
if layer.type == "objectgroup" then
for _, object in ipairs(layer.objects) do
if object.properties.collidable == "true" then
-- Individual objects
calculateObjectPosition(object)
end
end
end
end
return collision
end
function Map:setTiles(index, tileset, gid)
local function getTiles(i, t, m, s)
i = i - m
local n = 0
while i >= t do
i = i - t
if n ~= 0 then i = i - s end
if i >= 0 then n = n + 1 end
end
return n
end
local quad = framework.newQuad
local mw = self.tilewidth
local iw = tileset.imagewidth
local ih = tileset.imageheight
local tw = tileset.tilewidth
local th = tileset.tileheight
local s = tileset.spacing
local m = tileset.margin
local w = getTiles(iw, tw, m, s)
local h = getTiles(ih, th, m, s)
for y = 1, h do
for x = 1, w do
local id = gid - tileset.firstgid
local qx = (x - 1) * tw + m + (x - 1) * s
local qy = (y - 1) * th + m + (y - 1) * s
local properties
local terrain
local animation
for _, tile in pairs(tileset.tiles) do
if tile.id == id then
properties = tile.properties
animation = tile.animation
if tile.terrain then
terrain = {}
for i=1,#tile.terrain do
terrain[i] = tileset.terrains[tile.terrain[i] + 1]
end
end
end
end
local tile = {
id = id,
gid = gid,
tileset = index,
quad = quad(qx, qy, tw, th, iw, ih),
properties = properties,
terrain = terrain,
animation = animation,
frame = 1,
time = 0,
width = tileset.tilewidth,
height = tileset.tileheight,
sx = 1,
sy = 1,
r = 0,
offset = {
x = -mw + tileset.tileoffset.x,
y = -th + tileset.tileoffset.y,
},
}
if self.orientation == "isometric" then
tile.offset.x = -mw / 2
end
self.tiles[gid] = tile
gid = gid + 1
end
end
return gid
end
function Map:setLayer(layer, path)
layer.x = layer.x or 0
layer.y = layer.y or 0
layer.update = function(dt) return end
if layer.type == "tilelayer" then
self:setTileData(layer)
self:setSpriteBatches(layer)
layer.draw = function() self:drawTileLayer(layer) end
elseif layer.type == "objectgroup" then
self:setObjectCoordinates(layer)
self:setObjectSpriteBatches(layer)
layer.draw = function() self:drawObjectLayer(layer) end
elseif layer.type == "imagelayer" then
layer.draw = function() self:drawImageLayer(layer) end
if layer.image ~= "" then
local image = formatPath(path..layer.image)
layer.image = framework.newImage(image)
layer.width = layer.image:getWidth()
layer.height = layer.image:getHeight()
end
end
self.layers[layer.name] = layer
end
function Map:setTileData(layer)
local i = 1
local map = {}
for y = 1, layer.height do
map[y] = {}
for x = 1, layer.width do
local gid = layer.data[i]
if gid > 0 then
local tile = self.tiles[gid]
if tile then
map[y][x] = tile
else
local bit31 = 2147483648
local bit30 = 1073741824
local bit29 = 536870912
local flipX = false
local flipY = false
local flipD = false
local realgid = gid
if realgid >= bit31 then
realgid = realgid - bit31
flipX = not flipX
end
if realgid >= bit30 then
realgid = realgid - bit30
flipY = not flipY
end
if realgid >= bit29 then
realgid = realgid - bit29
flipD = not flipD
end
local tile = self.tiles[realgid]
local data = {
id = tile.id,
gid = tile.gid,
tileset = tile.tileset,
offset = tile.offset,
quad = tile.quad,
properties = tile.properties,
terrain = tile.terrain,
animation = tile.animation,
sx = tile.sx,
sy = tile.sy,
r = tile.r,
}
if flipX then
if flipY then
data.sx = -1
data.sy = -1
elseif flipD then
data.r = math.rad(90)
else
data.sx = -1
end
elseif flipY then
if flipD then
data.r = math.rad(-90)
else
data.sy = -1
end
elseif flipD then
data.r = math.rad(90)
data.sy = -1
end
self.tiles[gid] = data
map[y][x] = self.tiles[gid]
end
else
map[y][x] = false
end
i = i + 1
end
end
layer.data = map
end
function Map:setObjectCoordinates(layer)
local function updateVertex(vertex, x, y, cos, sin)
if self.orientation == "isometric" then
x, y = self:convertIsometricToScreen(x, y)
vertex.x, vertex.y = self:convertIsometricToScreen(vertex.x, vertex.y)
end
return rotateVertex(vertex, x, y, cos, sin)
end
for _, object in ipairs(layer.objects) do
local x = layer.x + object.x
local y = layer.y + object.y
local w = object.width
local h = object.height
local r = object.rotation
local cos = math.cos(math.rad(r))
local sin = math.sin(math.rad(r))
if object.shape == "rectangle" then
object.rectangle = {}
local vertices = {
{ x=x, y=y },
{ x=x + w, y=y },
{ x=x + w, y=y + h },
{ x=x, y=y + h },
}
for _, vertex in ipairs(vertices) do
vertex.x, vertex.y = updateVertex(vertex, x, y, cos, sin)
table.insert(object.rectangle, { x = vertex.x, y = vertex.y })
end
elseif object.shape == "ellipse" then
object.ellipse = {}
local vertices = convertEllipseToPolygon(x, y, w, h)
for _, vertex in ipairs(vertices) do
vertex.x, vertex.y = updateVertex(vertex, x, y, cos, sin)
table.insert(object.ellipse, { x = vertex.x, y = vertex.y })
end
elseif object.shape == "polygon" then
for _, vertex in ipairs(object.polygon) do
vertex.x = x + vertex.x
vertex.y = y + vertex.y
vertex.x, vertex.y = updateVertex(vertex, x, y, cos, sin)
end
elseif object.shape == "polyline" then
for _, vertex in ipairs(object.polyline) do
vertex.x = x + vertex.x
vertex.y = y + vertex.y
vertex.x, vertex.y = updateVertex(vertex, x, y, cos, sin)
end
end
end
end
function Map:setSpriteBatches(layer)
local newBatch = framework.newSpriteBatch
local w = framework.getWidth()
local h = framework.getHeight()
local tw = self.tilewidth
local th = self.tileheight
local bw = math.ceil(w / tw)
local bh = math.ceil(h / th)
-- Minimum of 400 tiles per batch
if bw < 20 then bw = 20 end
if bh < 20 then bh = 20 end
local size = bw * bh
local batches = {
width = bw,
height = bh,
data = {},
}
for y = 1, layer.height do
local by = math.ceil(y / bh)
for x = 1, layer.width do
local tile = layer.data[y][x]
local bx = math.ceil(x / bw)
local id
if tile then
local ts = tile.tileset
local image = self.tilesets[tile.tileset].image
batches.data[ts] = batches.data[ts] or {}
batches.data[ts][by] = batches.data[ts][by] or {}
batches.data[ts][by][bx] = batches.data[ts][by][bx] or newBatch(image, size)
local batch = batches.data[ts][by][bx]
local tx, ty
if self.orientation == "orthogonal" then
tx = x * tw + tile.offset.x
ty = y * th + tile.offset.y
-- Compensation for scale/rotation shift
if tile.sx < 0 then tx = tx + tw end
if tile.sy < 0 then ty = ty + th end
if tile.r > 0 then tx = tx + tw end
if tile.r < 0 then ty = ty + th end
elseif self.orientation == "isometric" then
tx = (x - y) * (tw / 2) + tile.offset.x + layer.width * tw / 2
ty = (x + y) * (th / 2) + tile.offset.y
elseif self.orientation == "staggered" then
if y % 2 == 0 then
tx = x * tw + tw / 2 + tile.offset.x
else
tx = x * tw + tile.offset.x
end
ty = y * th / 2 + tile.offset.y + th / 2
end
id = batch:add(tile.quad, tx, ty, tile.r, tile.sx, tile.sy)
self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
table.insert(self.tileInstances[tile.gid], { batch=batch, id=id, gid=tile.gid, x=tx, y=ty })
end
end
end
layer.batches = batches
end
function Map:setObjectSpriteBatches(layer)
local newBatch = framework.newSpriteBatch
local tw = self.tilewidth
local th = self.tileheight
local batches = {
}
for _, object in ipairs(layer.objects) do
if object.gid then
local tile = self.tiles[object.gid]
local ts = tile.tileset
local image = self.tilesets[tile.tileset].image
batches[ts] = batches[ts] or newBatch(image, 100)
local batch = batches[ts]
local tx = object.x + tw + tile.offset.x
local ty = object.y + tile.offset.y
-- Compensation for scale/rotation shift
if tile.sx < 0 then tx = tx + tw end
if tile.sy < 0 then ty = ty + th end
if tile.r > 0 then tx = tx + tw end
if tile.r < 0 then ty = ty + th end
id = batch:add(tile.quad, tx, ty, tile.r, tile.sx, tile.sy)
self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
table.insert(self.tileInstances[tile.gid], { batch=batch, id=id, gid=tile.gid, x=tx, y=ty })
end
end
layer.batches = batches
end
function Map:setDrawRange(tx, ty, w, h)
tx = -tx
ty = -ty
local tw, th = self.tilewidth, self.tileheight
local sx, sy, ex, ey
if self.orientation == "orthogonal" then
sx = math.ceil(tx / tw)
sy = math.ceil(ty / th)
ex = math.ceil(sx + w / tw)
ey = math.ceil(sy + h / th)
elseif self.orientation == "isometric" then
sx = math.ceil(((ty / (th / 2)) + (tx / (tw / 2))) / 2)
sy = math.ceil(((ty / (th / 2)) - (tx / (tw / 2))) / 2 - h / th)
ex = math.ceil(sx + (h / th) + (w / tw))
ey = math.ceil(sy + (h / th) * 2 + (w / tw))
elseif self.orientation == "staggered" then
sx = math.ceil(tx / tw - 1)
sy = math.ceil(ty / th)
ex = math.ceil(sx + w / tw + 1)
ey = math.ceil(sy + h / th * 2)
end
self.drawRange = {
sx = sx,
sy = sy,
ex = ex,
ey = ey,
}
end
function Map:addCustomLayer(name, index)
local layer = {
type = "customlayer",
name = name,
visible = true,
opacity = 1,
properties = {},
}
function layer:draw() return end
function layer:update(dt) return end
table.insert(self.layers, index, layer)
self.layers[name] = self.layers[index]
return layer
end
function Map:convertToCustomLayer(index)
local layer = assert(self.layers[index], "Layer not found: " .. index)
layer.type = "customlayer"
layer.x = nil
layer.y = nil
layer.width = nil
layer.height = nil
layer.encoding = nil
layer.data = nil
layer.objects = nil
layer.image = nil
function layer:draw() return end
function layer:update(dt) return end
end
function Map:removeLayer(index)
local layer = assert(self.layers[index], "Layer not found: " .. index)
if type(index) == "string" then
for i, layer in ipairs(self.layers) do
if layer.name == index then
table.remove(self.layers, i)
self.layers[index] = nil
break
end
end
else
local name = self.layers[index].name
table.remove(self.layers, index)
self.layers[name] = nil
end
end
function Map:update(dt)
for gid, tile in pairs( self.tiles ) do
local update
local t
if tile.animation then
update = false
tile.time = tile.time + dt * 1000
while tile.time > tonumber(tile.animation[tile.frame].duration) do
tile.time = tile.time - tonumber(tile.animation[tile.frame].duration)
tile.frame = tile.frame + 1
if tile.frame > #tile.animation then tile.frame = 1 end
update = true
end
if update == true and self.tileInstances[gid] ~= nil then
for _, j in pairs(self.tileInstances[gid]) do
t = self.tiles[tile.animation[tile.frame].tileid + self.tilesets[tile.tileset].firstgid]
j.batch:set( j.id, t.quad, j.x, j.y, 0 )
end
end
end
end
for _, layer in ipairs(self.layers) do
layer:update(dt)
end
end
function Map:draw(sx, sy)
local current_canvas = framework.getCanvas()
framework.setCanvas(self.canvas)
self.canvas:clear()
for _, layer in ipairs(self.layers) do
if layer.visible and layer.opacity > 0 then
self:drawLayer(layer)
end
end
framework.setCanvas(current_canvas)
framework.push()
framework.origin()
framework.draw(self.canvas, 0, 0, 0, sx, sy)
framework.pop()
end
function Map:drawLayer(layer)
framework.setColor(255, 255, 255, 255 * layer.opacity)
layer:draw()
framework.setColor(255, 255, 255, 255)
end
function Map:drawTileLayer(layer)
if type(layer) == "string" or type(layer) == "number" then
layer = self.layers[layer]
end
assert(layer.type == "tilelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: tilelayer")
local bw = layer.batches.width
local bh = layer.batches.height
local sx = math.ceil((self.drawRange.sx - layer.x / self.tilewidth - 1) / bw)
local sy = math.ceil((self.drawRange.sy - layer.y / self.tileheight - 1) / bh)
local ex = math.ceil((self.drawRange.ex - layer.x / self.tilewidth + 1) / bw)
local ey = math.ceil((self.drawRange.ey - layer.y / self.tileheight + 1) / bh)
local mx = math.ceil(self.width / bw)
local my = math.ceil(self.height / bh)
for by=sy, ey do
for bx=sx, ex do
if bx >= 1 and bx <= mx and by >= 1 and by <= my then
for _, batches in pairs(layer.batches.data) do
local batch = batches[by] and batches[by][bx]
if batch then
framework.draw(batch, math.floor(layer.x), math.floor(layer.y))
end
end
end
end
end
end
function Map:drawObjectLayer(layer)
if type(layer) == "string" or type(layer) == "number" then
layer = self.layers[layer]
end
assert(layer.type == "objectgroup", "Invalid layer type: " .. layer.type .. ". Layer must be of type: objectgroup")
local line = { 160, 160, 160, 255 * layer.opacity }
local fill = { 160, 160, 160, 255 * layer.opacity * 0.2 }
local shadow = { 0, 0, 0, 255 * layer.opacity }
local reset = { 255, 255, 255, 255 * layer.opacity }
local function sortVertices(obj)
local vertices = {{},{}}
for _, vertex in ipairs(obj) do
table.insert(vertices[1], vertex.x)
table.insert(vertices[1], vertex.y)
table.insert(vertices[2], vertex.x+1)
table.insert(vertices[2], vertex.y+1)
end
return vertices
end
local function drawShape(obj, shape)
local vertices = sortVertices(obj)
if shape == "polyline" then
framework.setColor(shadow)
framework.line(vertices[2])
framework.setColor(line)
framework.line(vertices[1])
return
elseif shape == "polygon" then
framework.setColor(fill)
if not framework.isConvex(vertices[1]) then
local triangles = framework.triangulate(vertices[1])
for _, triangle in ipairs(triangles) do
framework.polygon("fill", triangle)
end
else
framework.polygon("fill", vertices[1])
end
else
framework.setColor(fill)
framework.polygon("fill", vertices[1])
end
framework.setColor(shadow)
framework.polygon("line", vertices[2])
framework.setColor(line)
framework.polygon("line", vertices[1])
end
for _, object in ipairs(layer.objects) do
if object.shape == "rectangle" then
drawShape(object.rectangle, "rectangle")
elseif object.shape == "ellipse" then
drawShape(object.ellipse, "ellipse")
elseif object.shape == "polygon" then
drawShape(object.polygon, "polygon")
elseif object.shape == "polyline" then
drawShape(object.polyline, "polyline")
end
end
framework.setColor(reset)
for _, batch in pairs(layer.batches) do
framework.draw(batch, 0, 0)
end
end
function Map:drawImageLayer(layer)
if type(layer) == "string" or type(layer) == "number" then
layer = self.layers[layer]
end
assert(layer.type == "imagelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: imagelayer")
if layer.image ~= "" then
framework.draw(layer.image, layer.x, layer.y)
end
end
function Map:drawWorldCollision(collision)
for _, obj in ipairs(collision) do
framework.polygon("line", collision.body:getWorldPoints(obj.shape:getPoints()))
end
end
function Map:resize(w, h)
self.canvas = framework:newCanvas(w, h)
end
function Map:convertIsometricToScreen(x, y)
local mw = self.width
local tw, th = self.tilewidth, self.tileheight
local ox = mw * tw / 2
local sx = (x - y) + ox
local sy = (x + y) / 2
return sx, sy
end
function Map:convertScreenToIsometric(x, y)
local mw, mh = self.width, self.height
local tw, th = self.tilewidth, self.tileheight
local ox = mw * tw / 2
local oy = mh * th / 2
local tx = (x / 2 + y) - ox / 2
local ty = (-x / 2 + y) + oy
return tx, ty
end
function Map:convertTileToScreen(x, y)
local tw, th = self.tilewidth, self.tileheight
local sx = x * tw
local sy = y * th
return sx, sy
end
function Map:convertScreenToTile(x, y)
local tw, th = self.tilewidth, self.tileheight
local tx = x / tw
local ty = y / th
return tx, ty
end
function Map:convertIsometricTileToScreen(x, y)
local mw = self.width
local tw, th = self.tilewidth, self.tileheight
local ox = mw * tw / 2
local sx = (x - y) * tw / 2 + ox
local sy = (x + y) * th / 2
return sx, sy
end
function Map:convertScreenToIsometricTile(x, y)
local mw = self.width
local tw, th = self.tilewidth, self.tileheight
local ox = mw * tw / 2
local tx = y / th + (x - ox) / tw
local ty = y / th - (x - ox) / tw
return tx, ty
end
function Map:convertStaggeredTileToScreen(x, y)
local tw, th = self.tilewidth, self.tileheight
local sx = x * tw + math.abs(math.ceil(y) % 2) * (tw / 2) - (math.ceil(y) % 2 * tw/2)
local sy = y * (th / 2) + th/2
return sx, sy
end
function Map:convertScreenToStaggeredTile(x, y)
local function topLeft(x, y)
if (math.ceil(y) % 2) then
return x, y - 1
else
return x - 1, y - 1
end
end
local function topRight(x, y)
if (math.ceil(y) % 2) then
return x + 1, y - 1
else
return x, y - 1
end
end
local function bottomLeft(x, y)
if (math.ceil(y) % 2) then
return x, y + 1
else
return x - 1, y + 1
end
end
local function bottomRight(x, y)
if (math.ceil(y) % 2) then
return x + 1, y + 1
else
return x, y + 1
end
end
local tw, th = self.tilewidth, self.tileheight
local hh = th / 2
local ratio = th / tw
local tx = x / tw
local ty = y / th * 2
local ctx = math.ceil(x / tw)
local cty = math.ceil(y / th) * 2
local rx = x - ctx * tw
local ry = y - (cty / 2) * th
if (hh - rx * ratio > ry) then
return topLeft(tx, ty)
elseif (-hh + rx * ratio > ry) then
return topRight(tx, ty)
elseif (hh + rx * ratio < ry) then
return bottomLeft(tx, ty)
elseif (hh * 3 - rx * ratio < ry) then
return bottomRight(tx, ty)
end
return tx, ty
end
return Map