format and stuff

main
Simon Kellet 2 months ago
parent 53aa6d698d
commit 99b420cc7f
  1. 8
      Game/GameKeyPressed.lua
  2. 32
      Game/UpdateGame.lua
  3. 1
      Menu/DrawMenu.lua
  4. 7
      Menu/MenuKeyPressed.lua
  5. 3
      Menu/UpdateMenu.lua
  6. 1
      Pause/DrawPause.lua
  7. 6
      Pause/PauseKeyPressed.lua
  8. 3
      Pause/UpdatePause.lua
  9. 12
      constants.lua
  10. BIN
      game.love
  11. 11
      libs/classic.lua
  12. 30
      libs/profile.lua
  13. 57
      libs/sti/atlas.lua
  14. 116
      libs/sti/init.lua
  15. 24
      libs/sti/plugins/box2d.lua
  16. 23
      libs/sti/plugins/bump.lua
  17. 44
      libs/sti/utils.lua
  18. 483
      libs/windfield/init.lua
  19. 411
      libs/windfield/mlib/mlib.lua
  20. 42
      main.lua
  21. 68
      player.lua

@ -1,8 +1,7 @@
function GameKeyPressed(key) function GameKeyPressed(key)
if key == "escape" then if key == "escape" then
musicBattle:setVolume(0) musicBattle:setVolume(0)
musicPause:setVolume(0.5) musicPause:setVolume(0.6)
_G.GAMESTATE = "PAUSE" _G.GAMESTATE = "PAUSE"
print("STATE CHANEGD: PAUSED!") print("STATE CHANEGD: PAUSED!")
@ -13,13 +12,8 @@ function GameKeyPressed(key)
DebugFlag = not DebugFlag DebugFlag = not DebugFlag
end end
--TODO: Move player movement code into here!
--[[
--TODO: Better restart --TODO: Better restart
if key == "r" and not _G.PAUSED then if key == "r" and not _G.PAUSED then
love.load() love.load()
end end
]]--
end end

@ -1,9 +1,31 @@
local function checkWinState()
-- Check P1's health
if UserPlayer1.health <= 0 then --P1 win
_G.P1_WIN = true
_G.P2_WIN = false
UserPlayer1.health = 0
return true
end
if UserPlayer2.health <= 0 then --P2 win
_G.P1_WIN = false
_G.P2_WIN = true
UserPlayer2.health = 0
return true
end
return false
end
local max = math.max -- optimisations
function UpdateGame(dt) function UpdateGame(dt)
--Check if anyone has won
if checkWinState() == true then
print("STATE CHNAGED: WIN!")
_G.GAMESTATE = "WIN"
end
--WindField --WindField
World:update(dt) World:update(dt)
local max = math.max
KeyPressTime1 = max(0, KeyPressTime1 - dt) KeyPressTime1 = max(0, KeyPressTime1 - dt)
if KeyPressTime1 <= 0 then if KeyPressTime1 <= 0 then
EnableKeyPress1 = true EnableKeyPress1 = true
@ -13,6 +35,7 @@ function UpdateGame(dt)
if KeyPressTime2 <= 0 then if KeyPressTime2 <= 0 then
EnableKeyPress2 = true EnableKeyPress2 = true
end end
for i, v in ipairs(Bullets1) do for i, v in ipairs(Bullets1) do
v:update(dt) v:update(dt)
if v.y < 0 then --top of screen if v.y < 0 then --top of screen
@ -69,7 +92,10 @@ function UpdateGame(dt)
end end
end end
end end
UserPlayer1:handleKeys("w", "s", "a", "d", dt)
UserPlayer2:handleKeys("up", "down", "left", "right", dt)
UserPlayer1:updateCol()
UserPlayer2:updateCol()
UserPlayer1:update(dt) UserPlayer1:update(dt)
UserPlayer2:update(dt) UserPlayer2:update(dt)
end end

@ -31,7 +31,6 @@ local function title()
love.graphics.print("MENU", 100, 100) love.graphics.print("MENU", 100, 100)
end end
function DrawMenu() function DrawMenu()
local bwidth, bheight = 300, 140 local bwidth, bheight = 300, 140
title() title()

@ -1,5 +1,5 @@
function MenuKeyPressed(key) function MenuKeyPressed(key)
if key == 'return' then if key == "return" then
-- 0 Start Game -- 0 Start Game
-- 1 ?? -- 1 ??
-- 2 ??? -- 2 ???
@ -9,17 +9,13 @@ function MenuKeyPressed(key)
_G.GAMESTATE = "GAME" _G.GAMESTATE = "GAME"
print("STATE CHANEGD: GAME!") print("STATE CHANEGD: GAME!")
musicMenu:stop() musicMenu:stop()
elseif MENU_POS == 1 then elseif MENU_POS == 1 then
print("STATE CHANEGD: DUNNO!") print("STATE CHANEGD: DUNNO!")
elseif MENU_POS == 2 then elseif MENU_POS == 2 then
print("STATE CHANEGD: DUNNO!") print("STATE CHANEGD: DUNNO!")
elseif MENU_POS == 3 then elseif MENU_POS == 3 then
love.event.quit() love.event.quit()
end end
end end
if love.keyboard.isDown("up") then if love.keyboard.isDown("up") then
@ -37,5 +33,4 @@ function MenuKeyPressed(key)
_G.MENU_POS = _G.MENU_POS + 1 _G.MENU_POS = _G.MENU_POS + 1
end end
end end
end end

@ -1,2 +1 @@
function UpdateMenu(dt) function UpdateMenu(dt) end
end

@ -22,7 +22,6 @@ local function button(x,y, w, h, text, selected)
love.graphics.print(text, textX, textY) love.graphics.print(text, textX, textY)
end end
function DrawPause() function DrawPause()
local opacity = 0.3 local opacity = 0.3
local height = love.graphics.getHeight() local height = love.graphics.getHeight()

@ -1,5 +1,5 @@
function PauseKeyPressed(key) function PauseKeyPressed(key)
if key == 'return' then if key == "return" then
musicBattle:setVolume(0.5) musicBattle:setVolume(0.5)
musicPause:setVolume(0) musicPause:setVolume(0)
-- 0 Return to game -- 0 Return to game
@ -11,21 +11,17 @@ function PauseKeyPressed(key)
_G.PAUSED = false _G.PAUSED = false
musicBattle:setVolume(0.5) musicBattle:setVolume(0.5)
musicPause:setVolume(0) musicPause:setVolume(0)
elseif PAUSE_POS == 1 then elseif PAUSE_POS == 1 then
_G.GAMESTATE = "MENU" _G.GAMESTATE = "MENU"
print("STATE CHANEGD: MENU!") print("STATE CHANEGD: MENU!")
_G.PAUSED = false _G.PAUSED = false
musicPause:stop() musicPause:stop()
musicBattle:stop() musicBattle:stop()
elseif PAUSE_POS == 2 then elseif PAUSE_POS == 2 then
love.event.quit() love.event.quit()
end end
end end
if love.keyboard.isDown("up") then if love.keyboard.isDown("up") then
if _G.PAUSE_POS == 0 then if _G.PAUSE_POS == 0 then
_G.PAUSE_POS = 0 _G.PAUSE_POS = 0

@ -1,2 +1 @@
function UpdatePause(dt) function UpdatePause(dt) end
end

@ -1,3 +1,11 @@
--[[
* Game states:
* - MENU
* - GAME
* - PAUSE
* - WIN
]]
--
GAMESTATE = "MENU" GAMESTATE = "MENU"
MENU_POS = 0 MENU_POS = 0
@ -6,3 +14,7 @@ MENU_MAX = 3 --0 play, 1 ?, 2 ?, 3 quit
PAUSED = false PAUSED = false
PAUSE_POS = 0 PAUSE_POS = 0
PAUSE_MAX = 2 -- 0 resume, 1 menu, 2 quit PAUSE_MAX = 2 -- 0 resume, 1 menu, 2 quit
-- WIN flags for P1 and P2
P1_WIN = false
P2_WIN = false

Binary file not shown.

@ -7,14 +7,10 @@
-- the terms of the MIT license. See LICENSE for details. -- the terms of the MIT license. See LICENSE for details.
-- --
local Object = {} local Object = {}
Object.__index = Object Object.__index = Object
function Object:new() end
function Object:new()
end
function Object:extend() function Object:extend()
local cls = {} local cls = {}
@ -29,7 +25,6 @@ function Object:extend()
return cls return cls
end end
function Object:implement(...) function Object:implement(...)
for _, cls in pairs({ ... }) do for _, cls in pairs({ ... }) do
for k, v in pairs(cls) do for k, v in pairs(cls) do
@ -40,7 +35,6 @@ function Object:implement(...)
end end
end end
function Object:is(T) function Object:is(T)
local mt = getmetatable(self) local mt = getmetatable(self)
while mt do while mt do
@ -52,17 +46,14 @@ function Object:is(T)
return false return false
end end
function Object:__tostring() function Object:__tostring()
return "Object" return "Object"
end end
function Object:__call(...) function Object:__call(...)
local obj = setmetatable({}, self) local obj = setmetatable({}, self)
obj:new(...) obj:new(...)
return obj return obj
end end
return Object return Object

@ -23,7 +23,7 @@ local _internal = {}
-- @tparam number line Line number -- @tparam number line Line number
-- @tparam[opt] table info Debug info table -- @tparam[opt] table info Debug info table
function profile.hooker(event, line, info) function profile.hooker(event, line, info)
info = info or debug.getinfo(2, 'fnS') info = info or debug.getinfo(2, "fnS")
local f = info.func local f = info.func
-- ignore the profiler itself -- ignore the profiler itself
if _internal[f] or info.what ~= "Lua" then if _internal[f] or info.what ~= "Lua" then
@ -45,10 +45,10 @@ function profile.hooker(event, line, info)
_tcalled[f] = nil _tcalled[f] = nil
end end
if event == "tail call" then if event == "tail call" then
local prev = debug.getinfo(3, 'fnS') local prev = debug.getinfo(3, "fnS")
profile.hooker("return", line, prev) profile.hooker("return", line, prev)
profile.hooker("call", line, info) profile.hooker("call", line, info)
elseif event == 'call' then elseif event == "call" then
_tcalled[f] = clock() _tcalled[f] = clock()
else else
_ncalls[f] = _ncalls[f] + 1 _ncalls[f] = _ncalls[f] + 1
@ -64,7 +64,7 @@ end
--- Starts collecting data. --- Starts collecting data.
function profile.start() function profile.start()
if rawget(_G, 'jit') then if rawget(_G, "jit") then
jit.off() jit.off()
jit.flush() jit.flush()
end end
@ -82,7 +82,7 @@ function profile.stop()
-- merge closures -- merge closures
local lookup = {} local lookup = {}
for f, d in pairs(_defined) do for f, d in pairs(_defined) do
local id = (_labeled[f] or '?')..d local id = (_labeled[f] or "?") .. d
local f2 = lookup[id] local f2 = lookup[id]
if f2 then if f2 then
_ncalls[f2] = _ncalls[f2] + (_ncalls[f] or 0) _ncalls[f2] = _ncalls[f2] + (_ncalls[f] or 0)
@ -93,7 +93,7 @@ function profile.stop()
lookup[id] = f lookup[id] = f
end end
end end
collectgarbage('collect') collectgarbage("collect")
end end
--- Resets all collected data. --- Resets all collected data.
@ -107,7 +107,7 @@ function profile.reset()
for f in pairs(_tcalled) do for f in pairs(_tcalled) do
_tcalled[f] = nil _tcalled[f] = nil
end end
collectgarbage('collect') collectgarbage("collect")
end end
--- This is an internal function. --- This is an internal function.
@ -144,7 +144,7 @@ function profile.query(limit)
if _tcalled[f] then if _tcalled[f] then
dt = clock() - _tcalled[f] dt = clock() - _tcalled[f]
end end
t[i] = { i, _labeled[f] or '?', _ncalls[f], _telapsed[f] + dt, _defined[f] } t[i] = { i, _labeled[f] or "?", _ncalls[f], _telapsed[f] + dt, _defined[f] }
end end
return t return t
end end
@ -165,22 +165,24 @@ function profile.report(n)
s = tostring(s) s = tostring(s)
local l1 = s:len() local l1 = s:len()
if l1 < l2 then if l1 < l2 then
s = s..(' '):rep(l2-l1) s = s .. (" "):rep(l2 - l1)
elseif l1 > l2 then elseif l1 > l2 then
s = s:sub(l1 - l2 + 1, l1) s = s:sub(l1 - l2 + 1, l1)
end end
row[j] = s row[j] = s
end end
out[i] = table.concat(row, ' | ') out[i] = table.concat(row, " | ")
end end
local row = " +-----+-------------------------------+-------------+--------------------------+----------------------------------+ \n" local row =
local col = " | # | Function | Calls | Time | Code | \n" " +-----+-------------------------------+-------------+--------------------------+----------------------------------+ \n"
local col =
" | # | Function | Calls | Time | Code | \n"
local sz = row .. col .. row local sz = row .. col .. row
if #out > 0 then if #out > 0 then
sz = sz..' | '..table.concat(out, ' | \n | ')..' | \n' sz = sz .. " | " .. table.concat(out, " | \n | ") .. " | \n"
end end
return '\n'..sz..row return "\n" .. sz .. row
end end
-- store all internal profiler functions -- store all internal profiler functions

@ -11,7 +11,6 @@ local module = {}
-- @param ids Array with ids of each file -- @param ids Array with ids of each file
-- @param pow2 If true, will force a power of 2 size -- @param pow2 If true, will force a power of 2 size
function module.Atlas(files, sort, ids, pow2) function module.Atlas(files, sort, ids, pow2)
local function Node(x, y, w, h) local function Node(x, y, w, h)
return { x = x, y = y, w = w, h = h } return { x = x, y = y, w = w, h = h }
end end
@ -29,14 +28,18 @@ function module.Atlas( files, sort, ids, pow2 )
for i = 1, #files do for i = 1, #files do
images[i] = {} images[i] = {}
--images[i].name = files[i] --images[i].name = files[i]
if ids then images[i].id = ids[i] end if ids then
images[i].id = ids[i]
end
images[i].img = love.graphics.newImage(files[i]) images[i].img = love.graphics.newImage(files[i])
images[i].w = images[i].img:getWidth() images[i].w = images[i].img:getWidth()
images[i].h = images[i].img:getHeight() images[i].h = images[i].img:getHeight()
images[i].area = images[i].w * images[i].h images[i].area = images[i].w * images[i].h
end end
if sort == "size" or sort == "id" then if sort == "size" or sort == "id" then
table.sort( images, function( a, b ) return ( a.area > b.area ) end ) table.sort(images, function(a, b)
return (a.area > b.area)
end)
end end
return images return images
end end
@ -46,16 +49,22 @@ function module.Atlas( files, sort, ids, pow2 )
if root.left or root.right then if root.left or root.right then
if root.left then if root.left then
local node = add(root.left, id, w, h) local node = add(root.left, id, w, h)
if node then return node end if node then
return node
end
end end
if root.right then if root.right then
local node = add(root.right, id, w, h) local node = add(root.right, id, w, h)
if node then return node end if node then
return node
end
end end
return nil return nil
end end
if w > root.w or h > root.h then return nil end if w > root.w or h > root.h then
return nil
end
local _w, _h = root.w - w, root.h - h local _w, _h = root.w - w, root.h - h
@ -75,7 +84,9 @@ function module.Atlas( files, sort, ids, pow2 )
end end
local function unmap(root) local function unmap(root)
if not root then return {} end if not root then
return {}
end
local tree = {} local tree = {}
if root.id then if root.id then
@ -105,8 +116,12 @@ function module.Atlas( files, sort, ids, pow2 )
local w, h = images[1].w, images[1].h local w, h = images[1].w, images[1].h
if pow2 then if pow2 then
if w % 1 == 0 then w = nextpow2(w) end if w % 1 == 0 then
if h % 1 == 0 then h = nextpow2(h) end w = nextpow2(w)
end
if h % 1 == 0 then
h = nextpow2(h)
end
end end
repeat repeat
@ -116,14 +131,24 @@ function module.Atlas( files, sort, ids, pow2 )
for i = 1, #images do for i = 1, #images do
node = add(root, i, images[i].w, images[i].h) node = add(root, i, images[i].w, images[i].h)
if not node then break end if not node then
break
end
end end
if not node then if not node then
if h <= w then if h <= w then
if pow2 then h = h * 2 else h = h + 1 end if pow2 then
h = h * 2
else else
if pow2 then w = w * 2 else w = w + 1 end h = h + 1
end
else
if pow2 then
w = w * 2
else
w = w + 1
end
end end
else else
break break
@ -142,12 +167,16 @@ function module.Atlas( files, sort, ids, pow2 )
for i = 1, #images do for i = 1, #images do
love.graphics.draw(images[i].img, coords[i].x, coords[i].y) love.graphics.draw(images[i].img, coords[i].x, coords[i].y)
if ids then coords[i].id = images[i].id end if ids then
coords[i].id = images[i].id
end
end end
love.graphics.setCanvas() love.graphics.setCanvas()
if sort == "ids" then if sort == "ids" then
table.sort( coords, function( a, b ) return ( a.id < b.id ) end ) table.sort(coords, function(a, b)
return (a.id < b.id)
end)
end end
return { image = map, coords = coords } return { image = map, coords = coords }

@ -9,12 +9,12 @@ local STI = {
_URL = "https://github.com/karai17/Simple-Tiled-Implementation", _URL = "https://github.com/karai17/Simple-Tiled-Implementation",
_VERSION = "1.2.3.0", _VERSION = "1.2.3.0",
_DESCRIPTION = "Simple Tiled Implementation is a Tiled Map Editor library designed for the *awesome* LÖVE framework.", _DESCRIPTION = "Simple Tiled Implementation is a Tiled Map Editor library designed for the *awesome* LÖVE framework.",
cache = {} cache = {},
} }
STI.__index = STI STI.__index = STI
local love = _G.love local love = _G.love
local cwd = (...):gsub('%.init$', '') .. "." local cwd = (...):gsub("%.init$", "") .. "."
local utils = require(cwd .. "utils") local utils = require(cwd .. "utils")
local ceil = math.ceil local ceil = math.ceil
local floor = math.floor local floor = math.floor
@ -31,10 +31,7 @@ local function new(map, plugins, ox, oy)
else else
-- Check for valid map type -- Check for valid map type
local ext = map:sub(-4, -1) local ext = map:sub(-4, -1)
assert(ext == ".lua", string.format( assert(ext == ".lua", string.format("Invalid file type: %s. File must be of type: lua.", ext))
"Invalid file type: %s. File must be of type: lua.",
ext
))
-- Get directory of map -- Get directory of map
dir = map:reverse():find("[/\\]") or "" dir = map:reverse():find("[/\\]") or ""
@ -86,7 +83,7 @@ function Map:init(path, plugins, ox, oy)
self.offsety = oy or 0 self.offsety = oy or 0
self.freeBatchSprites = {} self.freeBatchSprites = {}
setmetatable(self.freeBatchSprites, { __mode = 'k' }) setmetatable(self.freeBatchSprites, { __mode = "k" })
-- Set tiles, images -- Set tiles, images
local gid = 1 local gid = 1
@ -174,7 +171,7 @@ end
-- @param plugins A list of plugins to load -- @param plugins A list of plugins to load
function Map:loadPlugins(plugins) function Map:loadPlugins(plugins)
for _, plugin in ipairs(plugins) do for _, plugin in ipairs(plugins) do
local pluginModulePath = cwd .. 'plugins.' .. plugin local pluginModulePath = cwd .. "plugins." .. plugin
local ok, pluginModule = pcall(require, pluginModulePath) local ok, pluginModule = pcall(require, pluginModulePath)
if ok then if ok then
for k, func in pairs(pluginModule) do for k, func in pairs(pluginModule) do
@ -232,11 +229,7 @@ function Map:setTiles(index, tileset, gid)
gid = gid, gid = gid,
tileset = index, tileset = index,
type = type, type = type,
quad = quad( quad = quad(quadX, quadY, tileW, tileH, imageW, imageH),
quadX, quadY,
tileW, tileH,
imageW, imageH
),
properties = properties or {}, properties = properties or {},
terrain = terrain, terrain = terrain,
animation = animation, animation = animation,
@ -286,11 +279,7 @@ function Map:setAtlasTiles(index, tileset, coords, gid)
gid = firstgid + tile.id, gid = firstgid + tile.id,
tileset = index, tileset = index,
class = tile.class, class = tile.class,
quad = quad( quad = quad(coords[i].x, coords[i].y, tile.width, tile.height, imageW, imageH),
coords[i].x, coords[i].y,
tile.width, tile.height,
imageW, imageH
),
properties = tile.properties or {}, properties = tile.properties or {},
terrain = terrain, terrain = terrain,
animation = tile.animation, animation = tile.animation,
@ -318,13 +307,19 @@ end
function Map:setLayer(layer, path) function Map:setLayer(layer, path)
if layer.encoding then if layer.encoding then
if layer.encoding == "base64" then if layer.encoding == "base64" then
assert(require "ffi", "Compressed maps require LuaJIT FFI.\nPlease Switch your interperator to LuaJIT or your Tile Layer Format to \"CSV\".") assert(
require("ffi"),
'Compressed maps require LuaJIT FFI.\nPlease Switch your interperator to LuaJIT or your Tile Layer Format to "CSV".'
)
local fd = love.data.decode("string", "base64", layer.data) local fd = love.data.decode("string", "base64", layer.data)
if not layer.compression then if not layer.compression then
layer.data = utils.get_decompressed_data(fd) layer.data = utils.get_decompressed_data(fd)
else else
assert(love.data.decompress, "zlib and gzip compression require LOVE 11.0+.\nPlease set your Tile Layer Format to \"Base64 (uncompressed)\" or \"CSV\".") assert(
love.data.decompress,
'zlib and gzip compression require LOVE 11.0+.\nPlease set your Tile Layer Format to "Base64 (uncompressed)" or "CSV".'
)
if layer.compression == "zlib" then if layer.compression == "zlib" then
local data = love.data.decompress("string", "zlib", fd) local data = love.data.decompress("string", "zlib", fd)
@ -346,14 +341,20 @@ function Map:setLayer(layer, path)
if layer.type == "tilelayer" then if layer.type == "tilelayer" then
self:setTileData(layer) self:setTileData(layer)
self:setSpriteBatches(layer) self:setSpriteBatches(layer)
layer.draw = function() self:drawTileLayer(layer) end layer.draw = function()
self:drawTileLayer(layer)
end
elseif layer.type == "objectgroup" then elseif layer.type == "objectgroup" then
self:setObjectData(layer) self:setObjectData(layer)
self:setObjectCoordinates(layer) self:setObjectCoordinates(layer)
self:setObjectSpriteBatches(layer) self:setObjectSpriteBatches(layer)
layer.draw = function() self:drawObjectLayer(layer) end layer.draw = function()
self:drawObjectLayer(layer)
end
elseif layer.type == "imagelayer" then elseif layer.type == "imagelayer" then
layer.draw = function() self:drawImageLayer(layer) end layer.draw = function()
self:drawImageLayer(layer)
end
if layer.image ~= "" then if layer.image ~= "" then
local formatted_path = utils.format_path(path .. layer.image) local formatted_path = utils.format_path(path .. layer.image)
@ -551,7 +552,7 @@ function Map:addNewLayerTile(layer, chunk, tile, x, y)
x = tileX, x = tileX,
y = tileY, y = tileY,
r = tile.r, r = tile.r,
oy = 0 oy = 0,
} }
-- NOTE: STI can run headless so it is not guaranteed that a batch exists. -- NOTE: STI can run headless so it is not guaranteed that a batch exists.
@ -738,7 +739,7 @@ function Map:setObjectSpriteBatches(layer)
x = tileX, x = tileX,
y = tileY - oy, y = tileY - oy,
r = tileR, r = tileR,
oy = oy oy = oy,
} }
self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
@ -866,12 +867,15 @@ function Map:update(dt)
tile.time = tile.time - tonumber(tile.animation[tile.frame].duration) tile.time = tile.time - tonumber(tile.animation[tile.frame].duration)
tile.frame = tile.frame + 1 tile.frame = tile.frame + 1
if tile.frame > #tile.animation then tile.frame = 1 end if tile.frame > #tile.animation then
tile.frame = 1
end
end end
if update and self.tileInstances[tile.gid] then if update and self.tileInstances[tile.gid] then
for _, j in pairs(self.tileInstances[tile.gid]) do for _, j in pairs(self.tileInstances[tile.gid]) do
local t = self.tiles[tonumber(tile.animation[tile.frame].tileid) + self.tilesets[tile.tileset].firstgid] local t =
self.tiles[tonumber(tile.animation[tile.frame].tileid) + self.tilesets[tile.tileset].firstgid]
j.batch:set(j.id, t.quad, j.x, j.y, j.r, tile.sx, tile.sy, 0, j.oy) j.batch:set(j.id, t.quad, j.x, j.y, j.r, tile.sx, tile.sy, 0, j.oy)
end end
end end
@ -1284,22 +1288,9 @@ function Map:swapTile(instance, tile)
-- Update sprite batch -- Update sprite batch
if instance.batch then if instance.batch then
if tile then if tile then
instance.batch:set( instance.batch:set(instance.id, tile.quad, instance.x, instance.y, tile.r, tile.sx, tile.sy)
instance.id,
tile.quad,
instance.x,
instance.y,
tile.r,
tile.sx,
tile.sy
)
else else
instance.batch:set( instance.batch:set(instance.id, instance.x, instance.y, 0, 0)
instance.id,
instance.x,
instance.y,
0,
0)
self.freeBatchSprites[instance.batch] = self.freeBatchSprites[instance.batch] or {} self.freeBatchSprites[instance.batch] = self.freeBatchSprites[instance.batch] or {}
table.insert(self.freeBatchSprites[instance.batch], instance) table.insert(self.freeBatchSprites[instance.batch], instance)
@ -1348,31 +1339,22 @@ function Map:convertTileToPixel(x,y)
if self.orientation == "orthogonal" then if self.orientation == "orthogonal" then
local tileW = self.tilewidth local tileW = self.tilewidth
local tileH = self.tileheight local tileH = self.tileheight
return return x * tileW, y * tileH
x * tileW,
y * tileH
elseif self.orientation == "isometric" then elseif self.orientation == "isometric" then
local mapH = self.height local mapH = self.height
local tileW = self.tilewidth local tileW = self.tilewidth
local tileH = self.tileheight local tileH = self.tileheight
local offsetX = mapH * tileW / 2 local offsetX = mapH * tileW / 2
return return (x - y) * tileW / 2 + offsetX, (x + y) * tileH / 2
(x - y) * tileW / 2 + offsetX, elseif self.orientation == "staggered" or self.orientation == "hexagonal" then
(x + y) * tileH / 2
elseif self.orientation == "staggered" or
self.orientation == "hexagonal" then
local tileW = self.tilewidth local tileW = self.tilewidth
local tileH = self.tileheight local tileH = self.tileheight
local sideLen = self.hexsidelength or 0 local sideLen = self.hexsidelength or 0
if self.staggeraxis == "x" then if self.staggeraxis == "x" then
return return x * tileW, ceil(y) * (tileH + sideLen) + (ceil(y) % 2 == 0 and tileH or 0)
x * tileW,
ceil(y) * (tileH + sideLen) + (ceil(y) % 2 == 0 and tileH or 0)
else else
return return ceil(x) * (tileW + sideLen) + (ceil(x) % 2 == 0 and tileW or 0), y * tileH
ceil(x) * (tileW + sideLen) + (ceil(x) % 2 == 0 and tileW or 0),
y * tileH
end end
end end
end end
@ -1386,17 +1368,13 @@ function Map:convertPixelToTile(x, y)
if self.orientation == "orthogonal" then if self.orientation == "orthogonal" then
local tileW = self.tilewidth local tileW = self.tilewidth
local tileH = self.tileheight local tileH = self.tileheight
return return x / tileW, y / tileH
x / tileW,
y / tileH
elseif self.orientation == "isometric" then elseif self.orientation == "isometric" then
local mapH = self.height local mapH = self.height
local tileW = self.tilewidth local tileW = self.tilewidth
local tileH = self.tileheight local tileH = self.tileheight
local offsetX = mapH * tileW / 2 local offsetX = mapH * tileW / 2
return return y / tileH + (x - offsetX) / tileW, y / tileH - (x - offsetX) / tileW
y / tileH + (x - offsetX) / tileW,
y / tileH - (x - offsetX) / tileW
elseif self.orientation == "staggered" then elseif self.orientation == "staggered" then
local staggerX = self.staggeraxis == "x" local staggerX = self.staggeraxis == "x"
local even = self.staggerindex == "even" local even = self.staggerindex == "even"
@ -1481,13 +1459,13 @@ function Map:convertPixelToTile(x, y)
local relativeX = x - referenceX * tileW local relativeX = x - referenceX * tileW
local relativeY = y - referenceY * tileH local relativeY = y - referenceY * tileH
if (halfH - relativeX * ratio > relativeY) then if halfH - relativeX * ratio > relativeY then
return topLeft(referenceX, referenceY) return topLeft(referenceX, referenceY)
elseif (-halfH + relativeX * ratio > relativeY) then elseif -halfH + relativeX * ratio > relativeY then
return topRight(referenceX, referenceY) return topRight(referenceX, referenceY)
elseif (halfH + relativeX * ratio < relativeY) then elseif halfH + relativeX * ratio < relativeY then
return bottomLeft(referenceX, referenceY) return bottomLeft(referenceX, referenceY)
elseif (halfH * 3 - relativeX * ratio < relativeY) then elseif halfH * 3 - relativeX * ratio < relativeY then
return bottomRight(referenceX, referenceY) return bottomRight(referenceX, referenceY)
end end
@ -1550,7 +1528,7 @@ function Map:convertPixelToTile(x, y)
{ x = centerX, y = top }, { x = centerX, y = top },
{ x = centerX - colW, y = centerY }, { x = centerX - colW, y = centerY },
{ x = centerX + colW, y = centerY }, { x = centerX + colW, y = centerY },
{ x = centerX, y = centerY + rowH } { x = centerX, y = centerY + rowH },
} }
end end
@ -1586,9 +1564,7 @@ function Map:convertPixelToTile(x, y)
local offsets = staggerX and offsetsStaggerX or offsetsStaggerY local offsets = staggerX and offsetsStaggerX or offsetsStaggerY
return return referenceX + offsets[nearest].x, referenceY + offsets[nearest].y
referenceX + offsets[nearest].x,
referenceY + offsets[nearest].y
end end
end end

@ -5,8 +5,8 @@
-- @license MIT/X11 -- @license MIT/X11
local love = _G.love local love = _G.love
local utils = require((...):gsub('plugins.box2d', 'utils')) local utils = require((...):gsub("plugins.box2d", "utils"))
local lg = require((...):gsub('plugins.box2d', 'graphics')) local lg = require((...):gsub("plugins.box2d", "graphics"))
return { return {
box2d_LICENSE = "MIT/X11", box2d_LICENSE = "MIT/X11",
@ -40,13 +40,13 @@ return {
local currentBody = body local currentBody = body
--dynamic are objects/players etc. --dynamic are objects/players etc.
if userdata.properties.dynamic == true then if userdata.properties.dynamic == true then
currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'dynamic') currentBody = love.physics.newBody(world, map.offsetx, map.offsety, "dynamic")
-- static means it shouldn't move. Things like walls/ground. -- static means it shouldn't move. Things like walls/ground.
elseif userdata.properties.static == true then elseif userdata.properties.static == true then
currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'static') currentBody = love.physics.newBody(world, map.offsetx, map.offsety, "static")
-- kinematic means that the object is static in the game world but effects other bodies -- kinematic means that the object is static in the game world but effects other bodies
elseif userdata.properties.kinematic == true then elseif userdata.properties.kinematic == true then
currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'kinematic') currentBody = love.physics.newBody(world, map.offsetx, map.offsety, "kinematic")
end end
local fixture = love.physics.newFixture(currentBody, shape) local fixture = love.physics.newFixture(currentBody, shape)
@ -89,12 +89,12 @@ return {
y = (object.dy or object.y) + map.offsety, y = (object.dy or object.y) + map.offsety,
w = object.width, w = object.width,
h = object.height, h = object.height,
polygon = object.polygon or object.polyline or object.ellipse or object.rectangle polygon = object.polygon or object.polyline or object.ellipse or object.rectangle,
} }
local userdata = { local userdata = {
object = o, object = o,
properties = object.properties properties = object.properties,
} }
o.r = object.rotation or 0 o.r = object.rotation or 0
@ -136,7 +136,7 @@ return {
{ x = o.x + 0, y = o.y + 0 }, { x = o.x + 0, y = o.y + 0 },
{ x = o.x + o.w, y = o.y + 0 }, { x = o.x + o.w, y = o.y + 0 },
{ x = o.x + o.w, y = o.y + o.h }, { x = o.x + o.w, y = o.y + o.h },
{ x=o.x+0, y=o.y+o.h } { x = o.x + 0, y = o.y + o.h },
} }
for _, vertex in ipairs(o.polygon) do for _, vertex in ipairs(o.polygon) do
@ -202,7 +202,7 @@ return {
y = instance.y, y = instance.y,
width = map.tilewidth, width = map.tilewidth,
height = map.tileheight, height = map.tileheight,
properties = tile.properties properties = tile.properties,
} }
calculateObjectPosition(object, instance) calculateObjectPosition(object, instance)
@ -227,7 +227,7 @@ return {
y = instance.y, y = instance.y,
width = tileset.tilewidth, width = tileset.tilewidth,
height = tileset.tileheight, height = tileset.tileheight,
properties = tile.properties properties = tile.properties,
} }
calculateObjectPosition(object, instance) calculateObjectPosition(object, instance)
@ -245,7 +245,7 @@ return {
y = layer.y or 0, y = layer.y or 0,
width = layer.width, width = layer.width,
height = layer.height, height = layer.height,
properties = layer.properties properties = layer.properties,
} }
calculateObjectPosition(object) calculateObjectPosition(object)
@ -308,7 +308,7 @@ return {
end end
lg.pop() lg.pop()
end end,
} }
--- Custom Properties in Tiled are used to tell this plugin what to do. --- Custom Properties in Tiled are used to tell this plugin what to do.

@ -4,7 +4,7 @@
-- @copyright 2019 -- @copyright 2019
-- @license MIT/X11 -- @license MIT/X11
local lg = require((...):gsub('plugins.bump', 'graphics')) local lg = require((...):gsub("plugins.bump", "graphics"))
return { return {
bump_LICENSE = "MIT/X11", bump_LICENSE = "MIT/X11",
@ -36,8 +36,7 @@ return {
width = object.width, width = object.width,
height = object.height, height = object.height,
layer = instance.layer, layer = instance.layer,
properties = object.properties properties = object.properties,
} }
world:add(t, t.x, t.y, t.width, t.height) world:add(t, t.x, t.y, t.width, t.height)
@ -55,7 +54,7 @@ return {
height = map.tileheight, height = map.tileheight,
layer = instance.layer, layer = instance.layer,
type = tile.type, type = tile.type,
properties = tile.properties properties = tile.properties,
} }
world:add(t, t.x, t.y, t.width, t.height) world:add(t, t.x, t.y, t.width, t.height)
@ -72,7 +71,6 @@ return {
if layer.type == "tilelayer" then if layer.type == "tilelayer" then
for y, tiles in ipairs(layer.data) do for y, tiles in ipairs(layer.data) do
for x, tile in pairs(tiles) do for x, tile in pairs(tiles) do
if tile.objectGroup then if tile.objectGroup then
for _, object in ipairs(tile.objectGroup.objects) do for _, object in ipairs(tile.objectGroup.objects) do
if object.properties.collidable == true then if object.properties.collidable == true then
@ -84,7 +82,7 @@ return {
width = object.width, width = object.width,
height = object.height, height = object.height,
layer = layer, layer = layer,
properties = object.properties properties = object.properties,
} }
world:add(t, t.x, t.y, t.width, t.height) world:add(t, t.x, t.y, t.width, t.height)
@ -93,7 +91,6 @@ return {
end end
end end
local t = { local t = {
x = (x - 1) * map.tilewidth + tile.offset.x + map.offsetx, x = (x - 1) * map.tilewidth + tile.offset.x + map.offsetx,
y = (y - 1) * map.tileheight + tile.offset.y + map.offsety, y = (y - 1) * map.tileheight + tile.offset.y + map.offsety,
@ -101,7 +98,7 @@ return {
height = tile.height, height = tile.height,
layer = layer, layer = layer,
type = tile.type, type = tile.type,
properties = tile.properties properties = tile.properties,
} }
world:add(t, t.x, t.y, t.width, t.height) world:add(t, t.x, t.y, t.width, t.height)
@ -128,7 +125,7 @@ return {
width = obj.width, width = obj.width,
height = obj.height, height = obj.height,
layer = layer, layer = layer,
properties = obj.properties properties = obj.properties,
} }
if obj.gid then if obj.gid then
@ -157,11 +154,7 @@ return {
for i = #collidables, 1, -1 do for i = #collidables, 1, -1 do
local obj = collidables[i] local obj = collidables[i]
if obj.layer == layer if obj.layer == layer and (layer.properties.collidable == true or obj.properties.collidable == true) then
and (
layer.properties.collidable == true
or obj.properties.collidable == true
) then
map.bump_world:remove(obj) map.bump_world:remove(obj)
table.remove(collidables, i) table.remove(collidables, i)
end end
@ -185,7 +178,7 @@ return {
end end
lg.pop() lg.pop()
end end,
} }
--- Custom Properties in Tiled are used to tell this plugin what to do. --- Custom Properties in Tiled are used to tell this plugin what to do.

@ -3,19 +3,21 @@ local utils = {}
-- https://github.com/stevedonovan/Penlight/blob/master/lua/pl/path.lua#L286 -- https://github.com/stevedonovan/Penlight/blob/master/lua/pl/path.lua#L286
function utils.format_path(path) function utils.format_path(path)
local np_gen1,np_gen2 = '[^SEP]+SEP%.%.SEP?','SEP+%.?SEP' local np_gen1, np_gen2 = "[^SEP]+SEP%.%.SEP?", "SEP+%.?SEP"
local np_pat1, np_pat2 = np_gen1:gsub('SEP','/'), np_gen2:gsub('SEP','/') local np_pat1, np_pat2 = np_gen1:gsub("SEP", "/"), np_gen2:gsub("SEP", "/")
local k local k
repeat -- /./ -> / repeat -- /./ -> /
path,k = path:gsub(np_pat2,'/',1) path, k = path:gsub(np_pat2, "/", 1)
until k == 0 until k == 0
repeat -- A/../ -> (empty) repeat -- A/../ -> (empty)
path,k = path:gsub(np_pat1,'',1) path, k = path:gsub(np_pat1, "", 1)
until k == 0 until k == 0
if path == '' then path = '.' end if path == "" then
path = "."
end
return path return path
end end
@ -25,8 +27,12 @@ function utils.compensate(tile, tileX, tileY, tileW, tileH)
local compx = 0 local compx = 0
local compy = 0 local compy = 0
if tile.sx < 0 then compx = tileW end if tile.sx < 0 then
if tile.sy < 0 then compy = tileH end compx = tileW
end
if tile.sy < 0 then
compy = tileH
end
if tile.r > 0 then if tile.r > 0 then
tileX = tileX + tileH - compy tileX = tileX + tileH - compy
@ -56,8 +62,12 @@ function utils.get_tiles(imageW, tileW, margin, spacing)
while imageW >= tileW do while imageW >= tileW do
imageW = imageW - tileW imageW = imageW - tileW
if n ~= 0 then imageW = imageW - spacing end if n ~= 0 then
if imageW >= 0 then n = n + 1 end imageW = imageW - spacing
end
if imageW >= 0 then
n = n + 1
end
end end
return n return n
@ -65,7 +75,7 @@ end
-- Decompress tile layer data -- Decompress tile layer data
function utils.get_decompressed_data(data) function utils.get_decompressed_data(data)
local ffi = require "ffi" local ffi = require("ffi")
local d = {} local d = {}
local decoded = ffi.cast("uint32_t*", data) local decoded = ffi.cast("uint32_t*", data)
@ -148,9 +158,7 @@ function utils.rotate_vertex(map, vertex, x, y, cos, sin, oy)
vertex.x = vertex.x - x vertex.x = vertex.x - x
vertex.y = vertex.y - y vertex.y = vertex.y - y
return return x + cos * vertex.x - sin * vertex.y, y + sin * vertex.x + cos * vertex.y - (oy or 0)
x + cos * vertex.x - sin * vertex.y,
y + sin * vertex.x + cos * vertex.y - (oy or 0)
end end
--- Project isometric position to cartesian position --- Project isometric position to cartesian position
@ -162,9 +170,7 @@ function utils.convert_isometric_to_screen(map, x, y)
local tileY = y / tileH local tileY = y / tileH
local offsetX = mapW * tileW / 2 local offsetX = mapW * tileW / 2
return return (tileX - tileY) * tileW / 2 + offsetX, (tileX + tileY) * tileH / 2
(tileX - tileY) * tileW / 2 + offsetX,
(tileX + tileY) * tileH / 2
end end
function utils.hex_to_color(hex) function utils.hex_to_color(hex)
@ -175,16 +181,14 @@ function utils.hex_to_color(hex)
return { return {
r = tonumber(hex:sub(1, 2), 16) / 255, r = tonumber(hex:sub(1, 2), 16) / 255,
g = tonumber(hex:sub(3, 4), 16) / 255, g = tonumber(hex:sub(3, 4), 16) / 255,
b = tonumber(hex:sub(5, 6), 16) / 255 b = tonumber(hex:sub(5, 6), 16) / 255,
} }
end end
function utils.pixel_function(_, _, r, g, b, a) function utils.pixel_function(_, _, r, g, b, a)
local mask = utils._TC local mask = utils._TC
if r == mask.r and if r == mask.r and g == mask.g and b == mask.b then
g == mask.g and
b == mask.b then
return r, g, b, 0 return r, g, b, 0
end end

@ -20,11 +20,12 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]]-- ]]
--
local path = ... .. '.' local path = ... .. "."
local wf = {} local wf = {}
wf.Math = require(path .. 'mlib.mlib') wf.Math = require(path .. "mlib.mlib")
World = {} World = {}
World.__index = World World.__index = World
@ -32,14 +33,28 @@ World.__index = World
function wf.newWorld(xg, yg, sleep) function wf.newWorld(xg, yg, sleep)
local world = wf.World.new(wf, xg, yg, sleep) local world = wf.World.new(wf, xg, yg, sleep)
world.box2d_world:setCallbacks(world.collisionOnEnter, world.collisionOnExit, world.collisionPre, world.collisionPost) world.box2d_world:setCallbacks(
world.collisionOnEnter,
world.collisionOnExit,
world.collisionPre,
world.collisionPost
)
world:collisionClear() world:collisionClear()
world:addCollisionClass('Default') world:addCollisionClass("Default")
-- Points all box2d_world functions to this wf.World object -- Points all box2d_world functions to this wf.World object
-- This means that the user can call world:setGravity for instance without having to say world.box2d_world:setGravity -- This means that the user can call world:setGravity for instance without having to say world.box2d_world:setGravity
for k, v in pairs(world.box2d_world.__index) do for k, v in pairs(world.box2d_world.__index) do
if k ~= '__gc' and k ~= '__eq' and k ~= '__index' and k ~= '__tostring' and k ~= 'update' and k ~= 'destroy' and k ~= 'type' and k ~= 'typeOf' then if
k ~= "__gc"
and k ~= "__eq"
and k ~= "__index"
and k ~= "__tostring"
and k ~= "update"
and k ~= "destroy"
and k ~= "type"
and k ~= "typeOf"
then
world[k] = function(self, ...) world[k] = function(self, ...)
return v(self.box2d_world, ...) return v(self.box2d_world, ...)
end end
@ -84,18 +99,20 @@ function World:draw(alpha)
for _, body in ipairs(bodies) do for _, body in ipairs(bodies) do
local fixtures = body:getFixtures() local fixtures = body:getFixtures()
for _, fixture in ipairs(fixtures) do for _, fixture in ipairs(fixtures) do
if fixture:getShape():type() == 'PolygonShape' then if fixture:getShape():type() == "PolygonShape" then
love.graphics.polygon('line', body:getWorldPoints(fixture:getShape():getPoints())) love.graphics.polygon("line", body:getWorldPoints(fixture:getShape():getPoints()))
elseif fixture:getShape():type() == 'EdgeShape' or fixture:getShape():type() == 'ChainShape' then elseif fixture:getShape():type() == "EdgeShape" or fixture:getShape():type() == "ChainShape" then
local points = { body:getWorldPoints(fixture:getShape():getPoints()) } local points = { body:getWorldPoints(fixture:getShape():getPoints()) }
for i = 1, #points, 2 do for i = 1, #points, 2 do
if i < #points-2 then love.graphics.line(points[i], points[i+1], points[i+2], points[i+3]) end if i < #points - 2 then
love.graphics.line(points[i], points[i + 1], points[i + 2], points[i + 3])
end end
elseif fixture:getShape():type() == 'CircleShape' then end
elseif fixture:getShape():type() == "CircleShape" then
local body_x, body_y = body:getPosition() local body_x, body_y = body:getPosition()
local shape_x, shape_y = fixture:getShape():getPoint() local shape_x, shape_y = fixture:getShape():getPoint()
local r = fixture:getShape():getRadius() local r = fixture:getShape():getRadius()
love.graphics.circle('line', body_x + shape_x, body_y + shape_y, r, 360) love.graphics.circle("line", body_x + shape_x, body_y + shape_y, r, 360)
end end
end end
end end
@ -106,8 +123,12 @@ function World:draw(alpha)
local joints = self.box2d_world:getJoints() local joints = self.box2d_world:getJoints()
for _, joint in ipairs(joints) do for _, joint in ipairs(joints) do
local x1, y1, x2, y2 = joint:getAnchors() local x1, y1, x2, y2 = joint:getAnchors()
if x1 and y1 then love.graphics.circle('line', x1, y1, 4) end if x1 and y1 then
if x2 and y2 then love.graphics.circle('line', x2, y2, 4) end love.graphics.circle("line", x1, y1, 4)
end
if x2 and y2 then
love.graphics.circle("line", x2, y2, 4)
end
end end
love.graphics.setColor(255, 255, 255, alpha) love.graphics.setColor(255, 255, 255, alpha)
@ -115,15 +136,17 @@ function World:draw(alpha)
love.graphics.setColor(64, 64, 222, alpha) love.graphics.setColor(64, 64, 222, alpha)
for _, query_draw in ipairs(self.query_debug_draw) do for _, query_draw in ipairs(self.query_debug_draw) do
query_draw.frames = query_draw.frames - 1 query_draw.frames = query_draw.frames - 1
if query_draw.type == 'circle' then if query_draw.type == "circle" then
love.graphics.circle('line', query_draw.x, query_draw.y, query_draw.r) love.graphics.circle("line", query_draw.x, query_draw.y, query_draw.r)
elseif query_draw.type == 'rectangle' then elseif query_draw.type == "rectangle" then
love.graphics.rectangle('line', query_draw.x, query_draw.y, query_draw.w, query_draw.h) love.graphics.rectangle("line", query_draw.x, query_draw.y, query_draw.w, query_draw.h)
elseif query_draw.type == 'line' then elseif query_draw.type == "line" then
love.graphics.line(query_draw.x1, query_draw.y1, query_draw.x2, query_draw.y2) love.graphics.line(query_draw.x1, query_draw.y1, query_draw.x2, query_draw.y2)
elseif query_draw.type == 'polygon' then elseif query_draw.type == "polygon" then
local triangles = love.math.triangulate(query_draw.vertices) local triangles = love.math.triangulate(query_draw.vertices)
for _, triangle in ipairs(triangles) do love.graphics.polygon('line', triangle) end for _, triangle in ipairs(triangles) do
love.graphics.polygon("line", triangle)
end
end end
end end
for i = #self.query_debug_draw, 1, -1 do for i = #self.query_debug_draw, 1, -1 do
@ -143,7 +166,9 @@ function World:setExplicitCollisionEvents(value)
end end
function World:addCollisionClass(collision_class_name, collision_class) function World:addCollisionClass(collision_class_name, collision_class)
if self.collision_classes[collision_class_name] then error('Collision class ' .. collision_class_name .. ' already exists.') end if self.collision_classes[collision_class_name] then
error("Collision class " .. collision_class_name .. " already exists.")
end
if self.explicit_collision_events then if self.explicit_collision_events then
self.collision_classes[collision_class_name] = collision_class or {} self.collision_classes[collision_class_name] = collision_class or {}
@ -177,10 +202,18 @@ function World:collisionClassesSet()
local collision_table = self:getCollisionCallbacksTable() local collision_table = self:getCollisionCallbacksTable()
for collision_class_name, collision_list in pairs(collision_table) do for collision_class_name, collision_list in pairs(collision_table) do
for _, collision_info in ipairs(collision_list) do for _, collision_info in ipairs(collision_list) do
if collision_info.type == 'enter' then self:addCollisionEnter(collision_class_name, collision_info.other) end if collision_info.type == "enter" then
if collision_info.type == 'exit' then self:addCollisionExit(collision_class_name, collision_info.other) end self:addCollisionEnter(collision_class_name, collision_info.other)
if collision_info.type == 'pre' then self:addCollisionPre(collision_class_name, collision_info.other) end end
if collision_info.type == 'post' then self:addCollisionPost(collision_class_name, collision_info.other) end if collision_info.type == "exit" then
self:addCollisionExit(collision_class_name, collision_info.other)
end
if collision_info.type == "pre" then
self:addCollisionPre(collision_class_name, collision_info.other)
end
if collision_info.type == "post" then
self:addCollisionPost(collision_class_name, collision_info.other)
end
end end
end end
@ -214,25 +247,33 @@ end
function World:addCollisionEnter(type1, type2) function World:addCollisionEnter(type1, type2)
if not self:isCollisionBetweenSensors(type1, type2) then if not self:isCollisionBetweenSensors(type1, type2) then
table.insert(self.collisions.on_enter.non_sensor, { type1 = type1, type2 = type2 }) table.insert(self.collisions.on_enter.non_sensor, { type1 = type1, type2 = type2 })
else table.insert(self.collisions.on_enter.sensor, {type1 = type1, type2 = type2}) end else
table.insert(self.collisions.on_enter.sensor, { type1 = type1, type2 = type2 })
end
end end
function World:addCollisionExit(type1, type2) function World:addCollisionExit(type1, type2)
if not self:isCollisionBetweenSensors(type1, type2) then if not self:isCollisionBetweenSensors(type1, type2) then
table.insert(self.collisions.on_exit.non_sensor, { type1 = type1, type2 = type2 }) table.insert(self.collisions.on_exit.non_sensor, { type1 = type1, type2 = type2 })
else table.insert(self.collisions.on_exit.sensor, {type1 = type1, type2 = type2}) end else
table.insert(self.collisions.on_exit.sensor, { type1 = type1, type2 = type2 })
end
end end
function World:addCollisionPre(type1, type2) function World:addCollisionPre(type1, type2)
if not self:isCollisionBetweenSensors(type1, type2) then if not self:isCollisionBetweenSensors(type1, type2) then
table.insert(self.collisions.pre.non_sensor, { type1 = type1, type2 = type2 }) table.insert(self.collisions.pre.non_sensor, { type1 = type1, type2 = type2 })
else table.insert(self.collisions.pre.sensor, {type1 = type1, type2 = type2}) end else
table.insert(self.collisions.pre.sensor, { type1 = type1, type2 = type2 })
end
end end
function World:addCollisionPost(type1, type2) function World:addCollisionPost(type1, type2)
if not self:isCollisionBetweenSensors(type1, type2) then if not self:isCollisionBetweenSensors(type1, type2) then
table.insert(self.collisions.post.non_sensor, { type1 = type1, type2 = type2 }) table.insert(self.collisions.post.non_sensor, { type1 = type1, type2 = type2 })
else table.insert(self.collisions.post.sensor, {type1 = type1, type2 = type2}) end else
table.insert(self.collisions.post.sensor, { type1 = type1, type2 = type2 })
end
end end
function World:doesType1IgnoreType2(type1, type2) function World:doesType1IgnoreType2(type1, type2)
@ -246,31 +287,46 @@ function World:doesType1IgnoreType2(type1, type2)
end end
local ignored_types = {} local ignored_types = {}
for _, collision_class_type in ipairs(collision_ignores[type1]) do for _, collision_class_type in ipairs(collision_ignores[type1]) do
if collision_class_type == 'All' then if collision_class_type == "All" then
for _, collision_class_name in ipairs(all) do for _, collision_class_name in ipairs(all) do
table.insert(ignored_types, collision_class_name) table.insert(ignored_types, collision_class_name)
end end
else table.insert(ignored_types, collision_class_type) end else
table.insert(ignored_types, collision_class_type)
end
end end
for key, _ in pairs(collision_ignores[type1]) do for key, _ in pairs(collision_ignores[type1]) do
if key == 'except' then if key == "except" then
for _, except_type in ipairs(collision_ignores[type1].except) do for _, except_type in ipairs(collision_ignores[type1].except) do
for i = #ignored_types, 1, -1 do for i = #ignored_types, 1, -1 do
if ignored_types[i] == except_type then table.remove(ignored_types, i) end if ignored_types[i] == except_type then
table.remove(ignored_types, i)
end
end end
end end
end end
end end
for _, ignored_type in ipairs(ignored_types) do for _, ignored_type in ipairs(ignored_types) do
if ignored_type == type2 then return true end if ignored_type == type2 then
return true
end
end end
end end
function World:isCollisionBetweenSensors(type1, type2) function World:isCollisionBetweenSensors(type1, type2)
if not self.is_sensor_memo[type1] then self.is_sensor_memo[type1] = {} end if not self.is_sensor_memo[type1] then
if not self.is_sensor_memo[type1][type2] then self.is_sensor_memo[type1][type2] = (self:doesType1IgnoreType2(type1, type2) or self:doesType1IgnoreType2(type2, type1)) end self.is_sensor_memo[type1] = {}
if self.is_sensor_memo[type1][type2] then return true end
else return false end if not self.is_sensor_memo[type1][type2] then
self.is_sensor_memo[type1][type2] = (
self:doesType1IgnoreType2(type1, type2) or self:doesType1IgnoreType2(type2, type1)
)
end
if self.is_sensor_memo[type1][type2] then
return true
else
return false
end
end end
-- https://love2d.org/forums/viewtopic.php?f=4&t=75441 -- https://love2d.org/forums/viewtopic.php?f=4&t=75441
@ -289,18 +345,18 @@ function World:generateCategoriesMasks()
end end
for object_type, ignore_list in pairs(collision_ignores) do for object_type, ignore_list in pairs(collision_ignores) do
for key, ignored_type in pairs(ignore_list) do for key, ignored_type in pairs(ignore_list) do
if ignored_type == 'All' then if ignored_type == "All" then
for _, all_object_type in ipairs(all) do for _, all_object_type in ipairs(all) do
table.insert(incoming[all_object_type], object_type) table.insert(incoming[all_object_type], object_type)
table.insert(expanded[object_type], all_object_type) table.insert(expanded[object_type], all_object_type)
end end
elseif type(ignored_type) == 'string' then elseif type(ignored_type) == "string" then
if ignored_type ~= 'All' then if ignored_type ~= "All" then
table.insert(incoming[ignored_type], object_type) table.insert(incoming[ignored_type], object_type)
table.insert(expanded[object_type], ignored_type) table.insert(expanded[object_type], ignored_type)
end end
end end
if key == 'except' then if key == "except" then
for _, except_ignored_type in ipairs(ignored_type) do for _, except_ignored_type in ipairs(ignored_type) do
for i, v in ipairs(incoming[except_ignored_type]) do for i, v in ipairs(incoming[except_ignored_type]) do
if v == object_type then if v == object_type then
@ -322,7 +378,9 @@ function World:generateCategoriesMasks()
end end
local edge_groups = {} local edge_groups = {}
for k, v in pairs(incoming) do for k, v in pairs(incoming) do
table.sort(v, function(a, b) return string.lower(a) < string.lower(b) end) table.sort(v, function(a, b)
return string.lower(a) < string.lower(b)
end)
end end
local i = 0 local i = 0
for k, v in pairs(incoming) do for k, v in pairs(incoming) do
@ -330,7 +388,10 @@ function World:generateCategoriesMasks()
for _, c in ipairs(v) do for _, c in ipairs(v) do
str = str .. c str = str .. c
end end
if not edge_groups[str] then i = i + 1; edge_groups[str] = {n = i} end if not edge_groups[str] then
i = i + 1
edge_groups[str] = { n = i }
end
table.insert(edge_groups[str], k) table.insert(edge_groups[str], k)
end end
local categories = {} local categories = {}
@ -356,24 +417,39 @@ function World:getCollisionCallbacksTable()
local collision_table = {} local collision_table = {}
for collision_class_name, collision_class in pairs(self.collision_classes) do for collision_class_name, collision_class in pairs(self.collision_classes) do
collision_table[collision_class_name] = {} collision_table[collision_class_name] = {}
for _, v in ipairs(collision_class.enter or {}) do table.insert(collision_table[collision_class_name], {type = 'enter', other = v}) end for _, v in ipairs(collision_class.enter or {}) do
for _, v in ipairs(collision_class.exit or {}) do table.insert(collision_table[collision_class_name], {type = 'exit', other = v}) end table.insert(collision_table[collision_class_name], { type = "enter", other = v })
for _, v in ipairs(collision_class.pre or {}) do table.insert(collision_table[collision_class_name], {type = 'pre', other = v}) end end
for _, v in ipairs(collision_class.post or {}) do table.insert(collision_table[collision_class_name], {type = 'post', other = v}) end for _, v in ipairs(collision_class.exit or {}) do
table.insert(collision_table[collision_class_name], { type = "exit", other = v })
end
for _, v in ipairs(collision_class.pre or {}) do
table.insert(collision_table[collision_class_name], { type = "pre", other = v })
end
for _, v in ipairs(collision_class.post or {}) do
table.insert(collision_table[collision_class_name], { type = "post", other = v })
end
end end
return collision_table return collision_table
end end
local function collEnsure(collision_class_name1, a, collision_class_name2, b) local function collEnsure(collision_class_name1, a, collision_class_name2, b)
if a.collision_class == collision_class_name2 and b.collision_class == collision_class_name1 then return b, a if a.collision_class == collision_class_name2 and b.collision_class == collision_class_name1 then
else return a, b end return b, a
else
return a, b
end
end end
local function collIf(collision_class_name1, collision_class_name2, a, b) local function collIf(collision_class_name1, collision_class_name2, a, b)
if (a.collision_class == collision_class_name1 and b.collision_class == collision_class_name2) or if
(a.collision_class == collision_class_name2 and b.collision_class == collision_class_name1) then (a.collision_class == collision_class_name1 and b.collision_class == collision_class_name2)
or (a.collision_class == collision_class_name2 and b.collision_class == collision_class_name1)
then
return true return true
else return false end else
return false
end
end end
function World.collisionOnEnter(fixture_a, fixture_b, contact) function World.collisionOnEnter(fixture_a, fixture_b, contact)
@ -384,22 +460,33 @@ function World.collisionOnEnter(fixture_a, fixture_b, contact)
for _, collision in ipairs(a.world.collisions.on_enter.sensor) do for _, collision in ipairs(a.world.collisions.on_enter.sensor) do
if collIf(collision.type1, collision.type2, a, b) then if collIf(collision.type1, collision.type2, a, b) then
a, b = collEnsure(collision.type1, a, collision.type2, b) a, b = collEnsure(collision.type1, a, collision.type2, b)
table.insert(a.collision_events[collision.type2], {collision_type = 'enter', collider_1 = a, collider_2 = b, contact = contact}) table.insert(
a.collision_events[collision.type2],
{ collision_type = "enter", collider_1 = a, collider_2 = b, contact = contact }
)
if collision.type1 == collision.type2 then if collision.type1 == collision.type2 then
table.insert(b.collision_events[collision.type1], {collision_type = 'enter', collider_1 = b, collider_2 = a, contact = contact}) table.insert(
b.collision_events[collision.type1],
{ collision_type = "enter", collider_1 = b, collider_2 = a, contact = contact }
)
end end
end end
end end
end end
elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then
if a and b then if a and b then
for _, collision in ipairs(a.world.collisions.on_enter.non_sensor) do for _, collision in ipairs(a.world.collisions.on_enter.non_sensor) do
if collIf(collision.type1, collision.type2, a, b) then if collIf(collision.type1, collision.type2, a, b) then
a, b = collEnsure(collision.type1, a, collision.type2, b) a, b = collEnsure(collision.type1, a, collision.type2, b)
table.insert(a.collision_events[collision.type2], {collision_type = 'enter', collider_1 = a, collider_2 = b, contact = contact}) table.insert(
a.collision_events[collision.type2],
{ collision_type = "enter", collider_1 = a, collider_2 = b, contact = contact }
)
if collision.type1 == collision.type2 then if collision.type1 == collision.type2 then
table.insert(b.collision_events[collision.type1], {collision_type = 'enter', collider_1 = b, collider_2 = a, contact = contact}) table.insert(
b.collision_events[collision.type1],
{ collision_type = "enter", collider_1 = b, collider_2 = a, contact = contact }
)
end end
end end
end end
@ -415,22 +502,33 @@ function World.collisionOnExit(fixture_a, fixture_b, contact)
for _, collision in ipairs(a.world.collisions.on_exit.sensor) do for _, collision in ipairs(a.world.collisions.on_exit.sensor) do
if collIf(collision.type1, collision.type2, a, b) then if collIf(collision.type1, collision.type2, a, b) then
a, b = collEnsure(collision.type1, a, collision.type2, b) a, b = collEnsure(collision.type1, a, collision.type2, b)
table.insert(a.collision_events[collision.type2], {collision_type = 'exit', collider_1 = a, collider_2 = b, contact = contact}) table.insert(
a.collision_events[collision.type2],
{ collision_type = "exit", collider_1 = a, collider_2 = b, contact = contact }
)
if collision.type1 == collision.type2 then if collision.type1 == collision.type2 then
table.insert(b.collision_events[collision.type1], {collision_type = 'exit', collider_1 = b, collider_2 = a, contact = contact}) table.insert(
b.collision_events[collision.type1],
{ collision_type = "exit", collider_1 = b, collider_2 = a, contact = contact }
)
end end
end end
end end
end end
elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then
if a and b then if a and b then
for _, collision in ipairs(a.world.collisions.on_exit.non_sensor) do for _, collision in ipairs(a.world.collisions.on_exit.non_sensor) do
if collIf(collision.type1, collision.type2, a, b) then if collIf(collision.type1, collision.type2, a, b) then
a, b = collEnsure(collision.type1, a, collision.type2, b) a, b = collEnsure(collision.type1, a, collision.type2, b)
table.insert(a.collision_events[collision.type2], {collision_type = 'exit', collider_1 = a, collider_2 = b, contact = contact}) table.insert(
a.collision_events[collision.type2],
{ collision_type = "exit", collider_1 = a, collider_2 = b, contact = contact }
)
if collision.type1 == collision.type2 then if collision.type1 == collision.type2 then
table.insert(b.collision_events[collision.type1], {collision_type = 'exit', collider_1 = b, collider_2 = a, contact = contact}) table.insert(
b.collision_events[collision.type1],
{ collision_type = "exit", collider_1 = b, collider_2 = a, contact = contact }
)
end end
end end
end end
@ -453,7 +551,6 @@ function World.collisionPre(fixture_a, fixture_b, contact)
end end
end end
end end
elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then
if a and b then if a and b then
for _, collision in ipairs(a.world.collisions.pre.non_sensor) do for _, collision in ipairs(a.world.collisions.pre.non_sensor) do
@ -484,7 +581,6 @@ function World.collisionPost(fixture_a, fixture_b, contact, ni1, ti1, ni2, ti2)
end end
end end
end end
elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then
if a and b then if a and b then
for _, collision in ipairs(a.world.collisions.post.non_sensor) do for _, collision in ipairs(a.world.collisions.post.non_sensor) do
@ -501,34 +597,36 @@ function World.collisionPost(fixture_a, fixture_b, contact, ni1, ti1, ni2, ti2)
end end
function World:newCircleCollider(x, y, r, settings) function World:newCircleCollider(x, y, r, settings)
return self.wf.Collider.new(self, 'Circle', x, y, r, settings) return self.wf.Collider.new(self, "Circle", x, y, r, settings)
end end
function World:newRectangleCollider(x, y, w, h, settings) function World:newRectangleCollider(x, y, w, h, settings)
return self.wf.Collider.new(self, 'Rectangle', x, y, w, h, settings) return self.wf.Collider.new(self, "Rectangle", x, y, w, h, settings)
end end
function World:newBSGRectangleCollider(x, y, w, h, corner_cut_size, settings) function World:newBSGRectangleCollider(x, y, w, h, corner_cut_size, settings)
return self.wf.Collider.new(self, 'BSGRectangle', x, y, w, h, corner_cut_size, settings) return self.wf.Collider.new(self, "BSGRectangle", x, y, w, h, corner_cut_size, settings)
end end
function World:newPolygonCollider(vertices, settings) function World:newPolygonCollider(vertices, settings)
return self.wf.Collider.new(self, 'Polygon', vertices, settings) return self.wf.Collider.new(self, "Polygon", vertices, settings)
end end
function World:newLineCollider(x1, y1, x2, y2, settings) function World:newLineCollider(x1, y1, x2, y2, settings)
return self.wf.Collider.new(self, 'Line', x1, y1, x2, y2, settings) return self.wf.Collider.new(self, "Line", x1, y1, x2, y2, settings)
end end
function World:newChainCollider(vertices, loop, settings) function World:newChainCollider(vertices, loop, settings)
return self.wf.Collider.new(self, 'Chain', vertices, loop, settings) return self.wf.Collider.new(self, "Chain", vertices, loop, settings)
end end
-- Internal AABB box2d query used before going for more specific and precise computations. -- Internal AABB box2d query used before going for more specific and precise computations.
function World:_queryBoundingBox(x1, y1, x2, y2) function World:_queryBoundingBox(x1, y1, x2, y2)
local colliders = {} local colliders = {}
local callback = function(fixture) local callback = function(fixture)
if not fixture:isSensor() then table.insert(colliders, fixture:getUserData()) end if not fixture:isSensor() then
table.insert(colliders, fixture:getUserData())
end
return true return true
end end
self.box2d_world:queryBoundingBox(x1, y1, x2, y2, callback) self.box2d_world:queryBoundingBox(x1, y1, x2, y2, callback)
@ -536,7 +634,7 @@ function World:_queryBoundingBox(x1, y1, x2, y2)
end end
function World:collisionClassInCollisionClassesList(collision_class, collision_classes) function World:collisionClassInCollisionClassesList(collision_class, collision_classes)
if collision_classes[1] == 'All' then if collision_classes[1] == "All" then
local all_collision_classes = {} local all_collision_classes = {}
for class, _ in pairs(self.collision_classes) do for class, _ in pairs(self.collision_classes) do
table.insert(all_collision_classes, class) table.insert(all_collision_classes, class)
@ -552,25 +650,43 @@ function World:collisionClassInCollisionClassesList(collision_class, collision_c
end end
end end
for _, class in ipairs(all_collision_classes) do for _, class in ipairs(all_collision_classes) do
if class == collision_class then return true end if class == collision_class then
return true
end
end end
else else
for _, class in ipairs(collision_classes) do for _, class in ipairs(collision_classes) do
if class == collision_class then return true end if class == collision_class then
return true
end
end end
end end
end end
function World:queryCircleArea(x, y, radius, collision_class_names) function World:queryCircleArea(x, y, radius, collision_class_names)
if not collision_class_names then collision_class_names = {'All'} end if not collision_class_names then
if self.query_debug_drawing_enabled then table.insert(self.query_debug_draw, {type = 'circle', x = x, y = y, r = radius, frames = self.draw_query_for_n_frames}) end collision_class_names = { "All" }
end
if self.query_debug_drawing_enabled then
table.insert(
self.query_debug_draw,
{ type = "circle", x = x, y = y, r = radius, frames = self.draw_query_for_n_frames }
)
end
local colliders = self:_queryBoundingBox(x - radius, y - radius, x + radius, y + radius) local colliders = self:_queryBoundingBox(x - radius, y - radius, x + radius, y + radius)
local outs = {} local outs = {}
for _, collider in ipairs(colliders) do for _, collider in ipairs(colliders) do
if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then
for _, fixture in ipairs(collider.body:getFixtures()) do for _, fixture in ipairs(collider.body:getFixtures()) do
if self.wf.Math.polygon.getCircleIntersection(x, y, radius, {collider.body:getWorldPoints(fixture:getShape():getPoints())}) then if
self.wf.Math.polygon.getCircleIntersection(
x,
y,
radius,
{ collider.body:getWorldPoints(fixture:getShape():getPoints()) }
)
then
table.insert(outs, collider) table.insert(outs, collider)
break break
end end
@ -581,15 +697,27 @@ function World:queryCircleArea(x, y, radius, collision_class_names)
end end
function World:queryRectangleArea(x, y, w, h, collision_class_names) function World:queryRectangleArea(x, y, w, h, collision_class_names)
if not collision_class_names then collision_class_names = {'All'} end if not collision_class_names then
if self.query_debug_drawing_enabled then table.insert(self.query_debug_draw, {type = 'rectangle', x = x, y = y, w = w, h = h, frames = self.draw_query_for_n_frames}) end collision_class_names = { "All" }
end
if self.query_debug_drawing_enabled then
table.insert(
self.query_debug_draw,
{ type = "rectangle", x = x, y = y, w = w, h = h, frames = self.draw_query_for_n_frames }
)
end
local colliders = self:_queryBoundingBox(x, y, x + w, y + h) local colliders = self:_queryBoundingBox(x, y, x + w, y + h)
local outs = {} local outs = {}
for _, collider in ipairs(colliders) do for _, collider in ipairs(colliders) do
if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then
for _, fixture in ipairs(collider.body:getFixtures()) do for _, fixture in ipairs(collider.body:getFixtures()) do
if self.wf.Math.polygon.isPolygonInside({x, y, x+w, y, x+w, y+h, x, y+h}, {collider.body:getWorldPoints(fixture:getShape():getPoints())}) then if
self.wf.Math.polygon.isPolygonInside(
{ x, y, x + w, y, x + w, y + h, x, y + h },
{ collider.body:getWorldPoints(fixture:getShape():getPoints()) }
)
then
table.insert(outs, collider) table.insert(outs, collider)
break break
end end
@ -600,21 +728,35 @@ function World:queryRectangleArea(x, y, w, h, collision_class_names)
end end
function World:queryPolygonArea(vertices, collision_class_names) function World:queryPolygonArea(vertices, collision_class_names)
if not collision_class_names then collision_class_names = {'All'} end if not collision_class_names then
if self.query_debug_drawing_enabled then table.insert(self.query_debug_draw, {type = 'polygon', vertices = vertices, frames = self.draw_query_for_n_frames}) end collision_class_names = { "All" }
end
if self.query_debug_drawing_enabled then
table.insert(
self.query_debug_draw,
{ type = "polygon", vertices = vertices, frames = self.draw_query_for_n_frames }
)
end
local cx, cy = self.wf.Math.polygon.getCentroid(vertices) local cx, cy = self.wf.Math.polygon.getCentroid(vertices)
local d_max = 0 local d_max = 0
for i = 1, #vertices, 2 do for i = 1, #vertices, 2 do
local d = self.wf.Math.line.getLength(cx, cy, vertices[i], vertices[i + 1]) local d = self.wf.Math.line.getLength(cx, cy, vertices[i], vertices[i + 1])
if d > d_max then d_max = d end if d > d_max then
d_max = d
end
end end
local colliders = self:_queryBoundingBox(cx - d_max, cy - d_max, cx + d_max, cy + d_max) local colliders = self:_queryBoundingBox(cx - d_max, cy - d_max, cx + d_max, cy + d_max)
local outs = {} local outs = {}
for _, collider in ipairs(colliders) do for _, collider in ipairs(colliders) do
if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then
for _, fixture in ipairs(collider.body:getFixtures()) do for _, fixture in ipairs(collider.body:getFixtures()) do
if self.wf.Math.polygon.isPolygonInside(vertices, {collider.body:getWorldPoints(fixture:getShape():getPoints())}) then if
self.wf.Math.polygon.isPolygonInside(
vertices,
{ collider.body:getWorldPoints(fixture:getShape():getPoints()) }
)
then
table.insert(outs, collider) table.insert(outs, collider)
break break
end end
@ -625,14 +767,21 @@ function World:queryPolygonArea(vertices, collision_class_names)
end end
function World:queryLine(x1, y1, x2, y2, collision_class_names) function World:queryLine(x1, y1, x2, y2, collision_class_names)
if not collision_class_names then collision_class_names = {'All'} end if not collision_class_names then
collision_class_names = { "All" }
end
if self.query_debug_drawing_enabled then if self.query_debug_drawing_enabled then
table.insert(self.query_debug_draw, {type = 'line', x1 = x1, y1 = y1, x2 = x2, y2 = y2, frames = self.draw_query_for_n_frames}) table.insert(
self.query_debug_draw,
{ type = "line", x1 = x1, y1 = y1, x2 = x2, y2 = y2, frames = self.draw_query_for_n_frames }
)
end end
local colliders = {} local colliders = {}
local callback = function(fixture, ...) local callback = function(fixture, ...)
if not fixture:isSensor() then table.insert(colliders, fixture:getUserData()) end if not fixture:isSensor() then
table.insert(colliders, fixture:getUserData())
end
return 1 return 1
end end
self.box2d_world:rayCast(x1, y1, x2, y2, callback) self.box2d_world:rayCast(x1, y1, x2, y2, callback)
@ -648,9 +797,13 @@ end
function World:addJoint(joint_type, ...) function World:addJoint(joint_type, ...)
local args = { ... } local args = { ... }
if args[1].body then args[1] = args[1].body end if args[1].body then
if type(args[2]) == "table" and args[2].body then args[2] = args[2].body end args[1] = args[1].body
local joint = love.physics['new' .. joint_type](unpack(args)) end
if type(args[2]) == "table" and args[2].body then
args[2] = args[2].body
end
local joint = love.physics["new" .. joint_type](unpack(args))
return joint return joint
end end
@ -665,13 +818,13 @@ function World:destroy()
collider:destroy() collider:destroy()
end end
local joints = self.box2d_world:getJoints() local joints = self.box2d_world:getJoints()
for _, joint in ipairs(joints) do joint:destroy() end for _, joint in ipairs(joints) do
joint:destroy()
end
self.box2d_world:destroy() self.box2d_world:destroy()
self.box2d_world = nil self.box2d_world = nil
end end
local Collider = {} local Collider = {}
Collider.__index = Collider Collider.__index = Collider
@ -704,40 +857,58 @@ function Collider.new(world, collider_type, ...)
local args = { ... } local args = { ... }
local shape, fixture local shape, fixture
if self.type == 'Circle' then if self.type == "Circle" then
self.collision_class = (args[4] and args[4].collision_class) or 'Default' self.collision_class = (args[4] and args[4].collision_class) or "Default"
self.body = love.physics.newBody(self.world.box2d_world, args[1], args[2], (args[4] and args[4].body_type) or 'dynamic') self.body =
love.physics.newBody(self.world.box2d_world, args[1], args[2], (args[4] and args[4].body_type) or "dynamic")
shape = love.physics.newCircleShape(args[3]) shape = love.physics.newCircleShape(args[3])
elseif self.type == "Rectangle" then
elseif self.type == 'Rectangle' then self.collision_class = (args[5] and args[5].collision_class) or "Default"
self.collision_class = (args[5] and args[5].collision_class) or 'Default' self.body = love.physics.newBody(
self.body = love.physics.newBody(self.world.box2d_world, args[1] + args[3]/2, args[2] + args[4]/2, (args[5] and args[5].body_type) or 'dynamic') self.world.box2d_world,
args[1] + args[3] / 2,
args[2] + args[4] / 2,
(args[5] and args[5].body_type) or "dynamic"
)
shape = love.physics.newRectangleShape(args[3], args[4]) shape = love.physics.newRectangleShape(args[3], args[4])
elseif self.type == "BSGRectangle" then
elseif self.type == 'BSGRectangle' then self.collision_class = (args[6] and args[6].collision_class) or "Default"
self.collision_class = (args[6] and args[6].collision_class) or 'Default' self.body = love.physics.newBody(
self.body = love.physics.newBody(self.world.box2d_world, args[1] + args[3]/2, args[2] + args[4]/2, (args[6] and args[6].body_type) or 'dynamic') self.world.box2d_world,
args[1] + args[3] / 2,
args[2] + args[4] / 2,
(args[6] and args[6].body_type) or "dynamic"
)
local w, h, s = args[3], args[4], args[5] local w, h, s = args[3], args[4], args[5]
shape = love.physics.newPolygonShape({ shape = love.physics.newPolygonShape({
-w/2, -h/2 + s, -w/2 + s, -h/2, -w / 2,
w/2 - s, -h/2, w/2, -h/2 + s, -h / 2 + s,
w/2, h/2 - s, w/2 - s, h/2, -w / 2 + s,
-w/2 + s, h/2, -w/2, h/2 - s -h / 2,
w / 2 - s,
-h / 2,
w / 2,
-h / 2 + s,
w / 2,
h / 2 - s,
w / 2 - s,
h / 2,
-w / 2 + s,
h / 2,
-w / 2,
h / 2 - s,
}) })
elseif self.type == "Polygon" then
elseif self.type == 'Polygon' then self.collision_class = (args[2] and args[2].collision_class) or "Default"
self.collision_class = (args[2] and args[2].collision_class) or 'Default' self.body = love.physics.newBody(self.world.box2d_world, 0, 0, (args[2] and args[2].body_type) or "dynamic")
self.body = love.physics.newBody(self.world.box2d_world, 0, 0, (args[2] and args[2].body_type) or 'dynamic')
shape = love.physics.newPolygonShape(unpack(args[1])) shape = love.physics.newPolygonShape(unpack(args[1]))
elseif self.type == "Line" then
elseif self.type == 'Line' then self.collision_class = (args[5] and args[5].collision_class) or "Default"
self.collision_class = (args[5] and args[5].collision_class) or 'Default' self.body = love.physics.newBody(self.world.box2d_world, 0, 0, (args[5] and args[5].body_type) or "dynamic")
self.body = love.physics.newBody(self.world.box2d_world, 0, 0, (args[5] and args[5].body_type) or 'dynamic')
shape = love.physics.newEdgeShape(args[1], args[2], args[3], args[4]) shape = love.physics.newEdgeShape(args[1], args[2], args[3], args[4])
elseif self.type == "Chain" then
elseif self.type == 'Chain' then self.collision_class = (args[3] and args[3].collision_class) or "Default"
self.collision_class = (args[3] and args[3].collision_class) or 'Default' self.body = love.physics.newBody(self.world.box2d_world, 0, 0, (args[3] and args[3].body_type) or "dynamic")
self.body = love.physics.newBody(self.world.box2d_world, 0, 0, (args[3] and args[3].body_type) or 'dynamic')
shape = love.physics.newChainShape(args[1], unpack(args[2])) shape = love.physics.newChainShape(args[1], unpack(args[2]))
end end
@ -752,9 +923,9 @@ function Collider.new(world, collider_type, ...)
sensor:setSensor(true) sensor:setSensor(true)
sensor:setUserData(self) sensor:setUserData(self)
self.shapes['main'] = shape self.shapes["main"] = shape
self.fixtures['main'] = fixture self.fixtures["main"] = fixture
self.sensors['main'] = sensor self.sensors["main"] = sensor
self.shape = shape self.shape = shape
self.fixture = fixture self.fixture = fixture
@ -764,21 +935,45 @@ function Collider.new(world, collider_type, ...)
-- Points all body, fixture and shape functions to this wf.Collider object -- Points all body, fixture and shape functions to this wf.Collider object
-- This means that the user can call collider:setLinearVelocity for instance without having to say collider.body:setLinearVelocity -- This means that the user can call collider:setLinearVelocity for instance without having to say collider.body:setLinearVelocity
for k, v in pairs(self.body.__index) do for k, v in pairs(self.body.__index) do
if k ~= '__gc' and k ~= '__eq' and k ~= '__index' and k ~= '__tostring' and k ~= 'destroy' and k ~= 'type' and k ~= 'typeOf' then if
k ~= "__gc"
and k ~= "__eq"
and k ~= "__index"
and k ~= "__tostring"
and k ~= "destroy"
and k ~= "type"
and k ~= "typeOf"
then
self[k] = function(self, ...) self[k] = function(self, ...)
return v(self.body, ...) return v(self.body, ...)
end end
end end
end end
for k, v in pairs(self.fixture.__index) do for k, v in pairs(self.fixture.__index) do
if k ~= '__gc' and k ~= '__eq' and k ~= '__index' and k ~= '__tostring' and k ~= 'destroy' and k ~= 'type' and k ~= 'typeOf' then if
k ~= "__gc"
and k ~= "__eq"
and k ~= "__index"
and k ~= "__tostring"
and k ~= "destroy"
and k ~= "type"
and k ~= "typeOf"
then
self[k] = function(self, ...) self[k] = function(self, ...)
return v(self.fixture, ...) return v(self.fixture, ...)
end end
end end
end end
for k, v in pairs(self.shape.__index) do for k, v in pairs(self.shape.__index) do
if k ~= '__gc' and k ~= '__eq' and k ~= '__index' and k ~= '__tostring' and k ~= 'destroy' and k ~= 'type' and k ~= 'typeOf' then if
k ~= "__gc"
and k ~= "__eq"
and k ~= "__index"
and k ~= "__tostring"
and k ~= "destroy"
and k ~= "type"
and k ~= "typeOf"
then
self[k] = function(self, ...) self[k] = function(self, ...)
return v(self.shape, ...) return v(self.shape, ...)
end end
@ -796,7 +991,9 @@ function Collider:collisionEventsClear()
end end
function Collider:setCollisionClass(collision_class_name) function Collider:setCollisionClass(collision_class_name)
if not self.world.collision_classes[collision_class_name] then error("Collision class " .. collision_class_name .. " doesn't exist.") end if not self.world.collision_classes[collision_class_name] then
error("Collision class " .. collision_class_name .. " doesn't exist.")
end
self.collision_class = collision_class_name self.collision_class = collision_class_name
for _, fixture in pairs(self.fixtures) do for _, fixture in pairs(self.fixtures) do
if self.world.masks[collision_class_name] then if self.world.masks[collision_class_name] then
@ -810,9 +1007,14 @@ function Collider:enter(other_collision_class_name)
local events = self.collision_events[other_collision_class_name] local events = self.collision_events[other_collision_class_name]
if events and #events >= 1 then if events and #events >= 1 then
for _, e in ipairs(events) do for _, e in ipairs(events) do
if e.collision_type == 'enter' then if e.collision_type == "enter" then
if not self.collision_stay[other_collision_class_name] then self.collision_stay[other_collision_class_name] = {} end if not self.collision_stay[other_collision_class_name] then
table.insert(self.collision_stay[other_collision_class_name], {collider = e.collider_2, contact = e.contact}) self.collision_stay[other_collision_class_name] = {}
end
table.insert(
self.collision_stay[other_collision_class_name],
{ collider = e.collider_2, contact = e.contact }
)
self.enter_collision_data[other_collision_class_name] = { collider = e.collider_2, contact = e.contact } self.enter_collision_data[other_collision_class_name] = { collider = e.collider_2, contact = e.contact }
return true return true
end end
@ -828,11 +1030,13 @@ function Collider:exit(other_collision_class_name)
local events = self.collision_events[other_collision_class_name] local events = self.collision_events[other_collision_class_name]
if events and #events >= 1 then if events and #events >= 1 then
for _, e in ipairs(events) do for _, e in ipairs(events) do
if e.collision_type == 'exit' then if e.collision_type == "exit" then
if self.collision_stay[other_collision_class_name] then if self.collision_stay[other_collision_class_name] then
for i = #self.collision_stay[other_collision_class_name], 1, -1 do for i = #self.collision_stay[other_collision_class_name], 1, -1 do
local collision_stay = self.collision_stay[other_collision_class_name][i] local collision_stay = self.collision_stay[other_collision_class_name][i]
if collision_stay.collider.id == e.collider_2.id then table.remove(self.collision_stay[other_collision_class_name], i) end if collision_stay.collider.id == e.collider_2.id then
table.remove(self.collision_stay[other_collision_class_name], i)
end
end end
end end
self.exit_collision_data[other_collision_class_name] = { collider = e.collider_2, contact = e.contact } self.exit_collision_data[other_collision_class_name] = { collider = e.collider_2, contact = e.contact }
@ -875,9 +1079,11 @@ function Collider:getObject()
end end
function Collider:addShape(shape_name, shape_type, ...) function Collider:addShape(shape_name, shape_type, ...)
if self.shapes[shape_name] or self.fixtures[shape_name] then error("Shape/fixture " .. shape_name .. " already exists.") end if self.shapes[shape_name] or self.fixtures[shape_name] then
error("Shape/fixture " .. shape_name .. " already exists.")
end
local args = { ... } local args = { ... }
local shape = love.physics['new' .. shape_type](unpack(args)) local shape = love.physics["new" .. shape_type](unpack(args))
local fixture = love.physics.newFixture(self.body, shape) local fixture = love.physics.newFixture(self.body, shape)
if self.world.masks[self.collision_class] then if self.world.masks[self.collision_class] then
fixture:setCategory(unpack(self.world.masks[self.collision_class].categories)) fixture:setCategory(unpack(self.world.masks[self.collision_class].categories))
@ -894,7 +1100,9 @@ function Collider:addShape(shape_name, shape_type, ...)
end end
function Collider:removeShape(shape_name) function Collider:removeShape(shape_name)
if not self.shapes[shape_name] then return end if not self.shapes[shape_name] then
return
end
self.shapes[shape_name] = nil self.shapes[shape_name] = nil
self.fixtures[shape_name]:setUserData(nil) self.fixtures[shape_name]:setUserData(nil)
self.fixtures[shape_name]:destroy() self.fixtures[shape_name]:destroy()
@ -926,4 +1134,3 @@ wf.World = World
wf.Collider = Collider wf.Collider = Collider
return wf return wf

@ -21,13 +21,17 @@ local unpack = table.unpack or unpack
-- Used to handle variable-argument functions and whether they are passed as func{ table } or func( unpack( table ) ) -- Used to handle variable-argument functions and whether they are passed as func{ table } or func( unpack( table ) )
local function checkInput(...) local function checkInput(...)
local input = {} local input = {}
if type( ... ) ~= 'table' then input = { ... } else input = ... end if type(...) ~= "table" then
input = { ... }
else
input = ...
end
return input return input
end end
-- Deals with floats / verify false false values. This can happen because of significant figures. -- Deals with floats / verify false false values. This can happen because of significant figures.
local function checkFuzzy(number1, number2) local function checkFuzzy(number1, number2)
return ( number1 - .00001 <= number2 and number2 <= number1 + .00001 ) return (number1 - 0.00001 <= number2 and number2 <= number1 + 0.00001)
end end
-- Remove multiple occurrences from a table. -- Remove multiple occurrences from a table.
@ -37,7 +41,12 @@ local function removeDuplicatePairs( tab )
for index2 = #tab, 1, -1 do for index2 = #tab, 1, -1 do
local second = tab[index2] local second = tab[index2]
if index1 ~= index2 then if index1 ~= index2 then
if type( first[1] ) == 'number' and type( second[1] ) == 'number' and type( first[2] ) == 'number' and type( second[2] ) == 'number' then if
type(first[1]) == "number"
and type(second[1]) == "number"
and type(first[2]) == "number"
and type(second[2]) == "number"
then
if checkFuzzy(first[1], second[1]) and checkFuzzy(first[2], second[2]) then if checkFuzzy(first[1], second[1]) and checkFuzzy(first[2], second[2]) then
table.remove(tab, index1) table.remove(tab, index1)
end end
@ -50,19 +59,29 @@ local function removeDuplicatePairs( tab )
return tab return tab
end end
local function removeDuplicates4Points(tab) local function removeDuplicates4Points(tab)
for index1 = #tab, 1, -1 do for index1 = #tab, 1, -1 do
local first = tab[index1] local first = tab[index1]
for index2 = #tab, 1, -1 do for index2 = #tab, 1, -1 do
local second = tab[index2] local second = tab[index2]
if index1 ~= index2 then if index1 ~= index2 then
if type( first[1] ) ~= type( second[1] ) then return false end if type(first[1]) ~= type(second[1]) then
if type( first[2] ) == 'number' and type( second[2] ) == 'number' and type( first[3] ) == 'number' and type( second[3] ) == 'number' then return false
end
if
type(first[2]) == "number"
and type(second[2]) == "number"
and type(first[3]) == "number"
and type(second[3]) == "number"
then
if checkFuzzy(first[2], second[2]) and checkFuzzy(first[3], second[3]) then if checkFuzzy(first[2], second[2]) and checkFuzzy(first[3], second[3]) then
table.remove(tab, index1) table.remove(tab, index1)
end end
elseif checkFuzzy( first[1], second[1] ) and checkFuzzy( first[2], second[2] ) and checkFuzzy( first[3], second[3] ) then elseif
checkFuzzy(first[1], second[1])
and checkFuzzy(first[2], second[2])
and checkFuzzy(first[3], second[3])
then
table.remove(tab, index1) table.remove(tab, index1)
end end
end end
@ -71,7 +90,6 @@ local function removeDuplicates4Points( tab )
return tab return tab
end end
-- Add points to the table. -- Add points to the table.
local function addPoints(tab, x, y) local function addPoints(tab, x, y)
tab[#tab + 1] = x tab[#tab + 1] = x
@ -86,7 +104,8 @@ local function removeDuplicatePointsFlat( tab )
local x1, y1 = tab[i], tab[i + 1] local x1, y1 = tab[i], tab[i + 1]
local x2, y2 = tab[ii], tab[ii + 1] local x2, y2 = tab[ii], tab[ii + 1]
if checkFuzzy(x1, x2) and checkFuzzy(y1, y2) then if checkFuzzy(x1, x2) and checkFuzzy(y1, y2) then
table.remove( tab, ii ); table.remove( tab, ii + 1 ) table.remove(tab, ii)
table.remove(tab, ii + 1)
end end
end end
end end
@ -94,16 +113,22 @@ local function removeDuplicatePointsFlat( tab )
return tab return tab
end end
-- Check if input is actually a number -- Check if input is actually a number
local function validateNumber(n) local function validateNumber(n)
if type( n ) ~= 'number' then return false if type(n) ~= "number" then
elseif n ~= n then return false -- nan return false
elseif math.abs( n ) == math.huge then return false elseif n ~= n then
else return true end return false -- nan
elseif math.abs(n) == math.huge then
return false
else
return true
end
end end
local function cycle( tab, index ) return tab[( index - 1 ) % #tab + 1] end local function cycle(tab, index)
return tab[(index - 1) % #tab + 1]
end
local function getGreatestPoint(points, offset) local function getGreatestPoint(points, offset)
offset = offset or 1 offset = offset or 1
@ -134,7 +159,8 @@ end -- }}}
-- Points -------------------------------------- {{{ -- Points -------------------------------------- {{{
local function rotatePoint(x, y, rotation, ox, oy) local function rotatePoint(x, y, rotation, ox, oy)
ox, oy = ox or 0, oy or 0 ox, oy = ox or 0, oy or 0
return ( x - ox ) * math.cos( rotation ) + ox - ( y - oy ) * math.sin( rotation ), ( x - ox ) * math.sin( rotation ) + ( y - oy ) * math.cos( rotation ) + oy return (x - ox) * math.cos(rotation) + ox - (y - oy) * math.sin(rotation),
(x - ox) * math.sin(rotation) + (y - oy) * math.cos(rotation) + oy
end end
local function scalePoint(x, y, scale, ox, oy) local function scalePoint(x, y, scale, ox, oy)
@ -157,7 +183,9 @@ end
-- Gives the slope of a line. -- Gives the slope of a line.
local function getSlope(x1, y1, x2, y2) local function getSlope(x1, y1, x2, y2)
if checkFuzzy( x1, x2 ) then return false end -- Technically it's undefined, but this is easier to program. if checkFuzzy(x1, x2) then
return false
end -- Technically it's undefined, but this is easier to program.
return (y1 - y2) / (x1 - x2) return (y1 - y2) / (x1 - x2)
end end
@ -174,9 +202,13 @@ local function getPerpendicularSlope( ... )
slope = unpack(input) slope = unpack(input)
end end
if not slope then return 0 -- Vertical lines become horizontal. if not slope then
elseif checkFuzzy( slope, 0 ) then return false -- Horizontal lines become vertical. return 0 -- Vertical lines become horizontal.
else return -1 / slope end elseif checkFuzzy(slope, 0) then
return false -- Horizontal lines become vertical.
else
return -1 / slope
end
end end
-- Gives the y-intercept of a line. -- Gives the y-intercept of a line.
@ -192,7 +224,9 @@ local function getYIntercept( x, y, ... )
slope = getSlope(x, y, unpack(input)) slope = getSlope(x, y, unpack(input))
end end
if not slope then return x, true end -- This way we have some information on the line. if not slope then
return x, true
end -- This way we have some information on the line.
return y - slope * x, false return y - slope * x, false
end end
@ -343,7 +377,11 @@ local function getLineSegmentIntersection( x1, y1, x2, y2, ... )
end end
end end
if length1 <= distance and length2 <= distance then return x, y else return false end if length1 <= distance and length2 <= distance then
return x, y
else
return false
end
end end
-- Checks if a point is on a line. -- Checks if a point is on a line.
@ -370,7 +408,9 @@ end
local function checkSegmentPoint(px, py, x1, y1, x2, y2) local function checkSegmentPoint(px, py, x1, y1, x2, y2)
-- Explanation around 5:20: https://www.youtube.com/watch?v=A86COO8KC58 -- Explanation around 5:20: https://www.youtube.com/watch?v=A86COO8KC58
local x = checkLinePoint(px, py, x1, y1, x2, y2) local x = checkLinePoint(px, py, x1, y1, x2, y2)
if not x then return false end if not x then
return false
end
local lengthX = x2 - x1 local lengthX = x2 - x1
local lengthY = y2 - y1 local lengthY = y2 - y1
@ -378,22 +418,38 @@ local function checkSegmentPoint( px, py, x1, y1, x2, y2 )
if checkFuzzy(lengthX, 0) then -- Vertical line if checkFuzzy(lengthX, 0) then -- Vertical line
if checkFuzzy(px, x1) then if checkFuzzy(px, x1) then
local low, high local low, high
if y1 > y2 then low = y2; high = y1 if y1 > y2 then
else low = y1; high = y2 end low = y2
high = y1
else
low = y1
high = y2
end
if py >= low and py <= high then return true if py >= low and py <= high then
else return false end return true
else
return false
end
else else
return false return false
end end
elseif checkFuzzy(lengthY, 0) then -- Horizontal line elseif checkFuzzy(lengthY, 0) then -- Horizontal line
if checkFuzzy(py, y1) then if checkFuzzy(py, y1) then
local low, high local low, high
if x1 > x2 then low = x2; high = x1 if x1 > x2 then
else low = x1; high = x2 end low = x2
high = x1
else
low = x1
high = x2
end
if px >= low and px <= high then return true if px >= low and px <= high then
else return false end return true
else
return false
end
else else
return false return false
end end
@ -418,13 +474,23 @@ local function getSegmentSegmentIntersection( x1, y1, x2, y2, x3, y3, x4, y4 )
if ((slope1 and slope2) and checkFuzzy(slope1, slope2)) or (not slope1 and not slope2) then -- Parallel lines if ((slope1 and slope2) and checkFuzzy(slope1, slope2)) or (not slope1 and not slope2) then -- Parallel lines
if checkFuzzy(intercept1, intercept2) then -- The same lines, possibly in different points. if checkFuzzy(intercept1, intercept2) then -- The same lines, possibly in different points.
local points = {} local points = {}
if checkSegmentPoint( x1, y1, x3, y3, x4, y4 ) then addPoints( points, x1, y1 ) end if checkSegmentPoint(x1, y1, x3, y3, x4, y4) then
if checkSegmentPoint( x2, y2, x3, y3, x4, y4 ) then addPoints( points, x2, y2 ) end addPoints(points, x1, y1)
if checkSegmentPoint( x3, y3, x1, y1, x2, y2 ) then addPoints( points, x3, y3 ) end end
if checkSegmentPoint( x4, y4, x1, y1, x2, y2 ) then addPoints( points, x4, y4 ) end if checkSegmentPoint(x2, y2, x3, y3, x4, y4) then
addPoints(points, x2, y2)
end
if checkSegmentPoint(x3, y3, x1, y1, x2, y2) then
addPoints(points, x3, y3)
end
if checkSegmentPoint(x4, y4, x1, y1, x2, y2) then
addPoints(points, x4, y4)
end
points = removeDuplicatePointsFlat(points) points = removeDuplicatePointsFlat(points)
if #points == 0 then return false end if #points == 0 then
return false
end
return unpack(points) return unpack(points)
else else
return false return false
@ -446,7 +512,9 @@ end
-- Checks if a number is prime. -- Checks if a number is prime.
local function isPrime(number) local function isPrime(number)
if number < 2 then return false end if number < 2 then
return false
end
for i = 2, math.sqrt(number) do for i = 2, math.sqrt(number) do
if number % i == 0 then if number % i == 0 then
@ -459,7 +527,7 @@ end
-- Rounds a number to the xth decimal place (round( 3.14159265359, 4 ) --> 3.1416) -- Rounds a number to the xth decimal place (round( 3.14159265359, 4 ) --> 3.1416)
local function round(number, place) local function round(number, place)
local pow = 10 ^ (place or 0) local pow = 10 ^ (place or 0)
return math.floor( number * pow + .5 ) / pow return math.floor(number * pow + 0.5) / pow
end end
-- Gives the summation given a local function -- Gives the summation given a local function
@ -491,7 +559,9 @@ end
-- Returns the quadratic roots of an equation. -- Returns the quadratic roots of an equation.
local function getQuadraticRoots(a, b, c) local function getQuadraticRoots(a, b, c)
local discriminant = b ^ 2 - (4 * a * c) local discriminant = b ^ 2 - (4 * a * c)
if discriminant < 0 then return false end if discriminant < 0 then
return false
end
discriminant = math.sqrt(discriminant) discriminant = math.sqrt(discriminant)
local denominator = (2 * a) local denominator = (2 * a)
return (-b - discriminant) / denominator, (-b + discriminant) / denominator return (-b - discriminant) / denominator, (-b + discriminant) / denominator
@ -534,35 +604,39 @@ local function getCircleLineIntersection( circleX, circleY, radius, x1, y1, x2,
if slope then if slope then
local a = (1 + slope ^ 2) local a = (1 + slope ^ 2)
local b = ( -2 * ( circleX ) + ( 2 * slope * intercept ) - ( 2 * circleY * slope ) ) local b = (-2 * circleX + (2 * slope * intercept) - (2 * circleY * slope))
local c = ( circleX ^ 2 + intercept ^ 2 - 2 * ( circleY ) * ( intercept ) + circleY ^ 2 - radius ^ 2 ) local c = (circleX ^ 2 + intercept ^ 2 - 2 * circleY * intercept + circleY ^ 2 - radius ^ 2)
x1, x2 = getQuadraticRoots(a, b, c) x1, x2 = getQuadraticRoots(a, b, c)
if not x1 then return false end if not x1 then
return false
end
y1 = slope * x1 + intercept y1 = slope * x1 + intercept
y2 = slope * x2 + intercept y2 = slope * x2 + intercept
if checkFuzzy(x1, x2) and checkFuzzy(y1, y2) then if checkFuzzy(x1, x2) and checkFuzzy(y1, y2) then
return 'tangent', x1, y1 return "tangent", x1, y1
else else
return 'secant', x1, y1, x2, y2 return "secant", x1, y1, x2, y2
end end
else -- Vertical Lines else -- Vertical Lines
local lengthToPoint1 = circleX - x1 local lengthToPoint1 = circleX - x1
local remainingDistance = lengthToPoint1 - radius local remainingDistance = lengthToPoint1 - radius
local intercept = math.sqrt(-(lengthToPoint1 ^ 2 - radius ^ 2)) local intercept = math.sqrt(-(lengthToPoint1 ^ 2 - radius ^ 2))
if -( lengthToPoint1 ^ 2 - radius ^ 2 ) < 0 then return false end if -(lengthToPoint1 ^ 2 - radius ^ 2) < 0 then
return false
end
local bottomX, bottomY = x1, circleY - intercept local bottomX, bottomY = x1, circleY - intercept
local topX, topY = x1, circleY + intercept local topX, topY = x1, circleY + intercept
if topY ~= bottomY then if topY ~= bottomY then
return 'secant', topX, topY, bottomX, bottomY return "secant", topX, topY, bottomX, bottomY
else else
return 'tangent', topX, topY return "tangent", topX, topY
end end
end end
end end
@ -570,32 +644,36 @@ end
-- Gives the type of intersection of a line segment. -- Gives the type of intersection of a line segment.
local function getCircleSegmentIntersection(circleX, circleY, radius, x1, y1, x2, y2) local function getCircleSegmentIntersection(circleX, circleY, radius, x1, y1, x2, y2)
local Type, x3, y3, x4, y4 = getCircleLineIntersection(circleX, circleY, radius, x1, y1, x2, y2) local Type, x3, y3, x4, y4 = getCircleLineIntersection(circleX, circleY, radius, x1, y1, x2, y2)
if not Type then return false end if not Type then
return false
end
local slope, intercept = getSlope(x1, y1, x2, y2), getYIntercept(x1, y1, x2, y2) local slope, intercept = getSlope(x1, y1, x2, y2), getYIntercept(x1, y1, x2, y2)
if isPointOnCircle(x1, y1, circleX, circleY, radius) and isPointOnCircle(x2, y2, circleX, circleY, radius) then -- Both points are on line-segment. if isPointOnCircle(x1, y1, circleX, circleY, radius) and isPointOnCircle(x2, y2, circleX, circleY, radius) then -- Both points are on line-segment.
return 'chord', x1, y1, x2, y2 return "chord", x1, y1, x2, y2
end end
if slope then if slope then
if checkCirclePoint( x1, y1, circleX, circleY, radius ) and checkCirclePoint( x2, y2, circleX, circleY, radius ) then -- Line-segment is fully in circle. if
return 'enclosed', x1, y1, x2, y2 checkCirclePoint(x1, y1, circleX, circleY, radius) and checkCirclePoint(x2, y2, circleX, circleY, radius)
then -- Line-segment is fully in circle.
return "enclosed", x1, y1, x2, y2
elseif x3 and x4 then elseif x3 and x4 then
if checkSegmentPoint(x3, y3, x1, y1, x2, y2) and not checkSegmentPoint(x4, y4, x1, y1, x2, y2) then -- Only the first of the points is on the line-segment. if checkSegmentPoint(x3, y3, x1, y1, x2, y2) and not checkSegmentPoint(x4, y4, x1, y1, x2, y2) then -- Only the first of the points is on the line-segment.
return 'tangent', x3, y3 return "tangent", x3, y3
elseif checkSegmentPoint(x4, y4, x1, y1, x2, y2) and not checkSegmentPoint(x3, y3, x1, y1, x2, y2) then -- Only the second of the points is on the line-segment. elseif checkSegmentPoint(x4, y4, x1, y1, x2, y2) and not checkSegmentPoint(x3, y3, x1, y1, x2, y2) then -- Only the second of the points is on the line-segment.
return 'tangent', x4, y4 return "tangent", x4, y4
else -- Neither of the points are on the circle (means that the segment is not on the circle, but "encasing" the circle) else -- Neither of the points are on the circle (means that the segment is not on the circle, but "encasing" the circle)
if checkSegmentPoint(x3, y3, x1, y1, x2, y2) and checkSegmentPoint(x4, y4, x1, y1, x2, y2) then if checkSegmentPoint(x3, y3, x1, y1, x2, y2) and checkSegmentPoint(x4, y4, x1, y1, x2, y2) then
return 'secant', x3, y3, x4, y4 return "secant", x3, y3, x4, y4
else else
return false return false
end end
end end
elseif not x4 then -- Is a tangent. elseif not x4 then -- Is a tangent.
if checkSegmentPoint(x3, y3, x1, y1, x2, y2) then if checkSegmentPoint(x3, y3, x1, y1, x2, y2) then
return 'tangent', x3, y3 return "tangent", x3, y3
else -- Neither of the points are on the line-segment (means that the segment is not on the circle or "encasing" the circle). else -- Neither of the points are on the line-segment (means that the segment is not on the circle or "encasing" the circle).
local length = getLength(x1, y1, x2, y2) local length = getLength(x1, y1, x2, y2)
local distance1 = getLength(x1, y1, x3, y3) local distance1 = getLength(x1, y1, x3, y3)
@ -606,7 +684,7 @@ local function getCircleSegmentIntersection( circleX, circleY, radius, x1, y1, x
elseif length < distance1 and length < distance2 then elseif length < distance1 and length < distance2 then
return false return false
else else
return 'tangent', x3, y3 return "tangent", x3, y3
end end
end end
end end
@ -615,7 +693,9 @@ local function getCircleSegmentIntersection( circleX, circleY, radius, x1, y1, x
local remainingDistance = lengthToPoint1 - radius local remainingDistance = lengthToPoint1 - radius
local intercept = math.sqrt(-(lengthToPoint1 ^ 2 - radius ^ 2)) local intercept = math.sqrt(-(lengthToPoint1 ^ 2 - radius ^ 2))
if -( lengthToPoint1 ^ 2 - radius ^ 2 ) < 0 then return false end if -(lengthToPoint1 ^ 2 - radius ^ 2) < 0 then
return false
end
local topX, topY = x1, circleY - intercept local topX, topY = x1, circleY - intercept
local bottomX, bottomY = x1, circleY + intercept local bottomX, bottomY = x1, circleY + intercept
@ -625,18 +705,20 @@ local function getCircleSegmentIntersection( circleX, circleY, radius, x1, y1, x
local distance2 = getLength(x2, y2, topX, topY) local distance2 = getLength(x2, y2, topX, topY)
if bottomY ~= topY then -- Not a tangent if bottomY ~= topY then -- Not a tangent
if checkSegmentPoint( topX, topY, x1, y1, x2, y2 ) and checkSegmentPoint( bottomX, bottomY, x1, y1, x2, y2 ) then if
return 'chord', topX, topY, bottomX, bottomY checkSegmentPoint(topX, topY, x1, y1, x2, y2) and checkSegmentPoint(bottomX, bottomY, x1, y1, x2, y2)
then
return "chord", topX, topY, bottomX, bottomY
elseif checkSegmentPoint(topX, topY, x1, y1, x2, y2) then elseif checkSegmentPoint(topX, topY, x1, y1, x2, y2) then
return 'tangent', topX, topY return "tangent", topX, topY
elseif checkSegmentPoint(bottomX, bottomY, x1, y1, x2, y2) then elseif checkSegmentPoint(bottomX, bottomY, x1, y1, x2, y2) then
return 'tangent', bottomX, bottomY return "tangent", bottomX, bottomY
else else
return false return false
end end
else -- Tangent else -- Tangent
if checkSegmentPoint(topX, topY, x1, y1, x2, y2) then if checkSegmentPoint(topX, topY, x1, y1, x2, y2) then
return 'tangent', topX, topY return "tangent", topX, topY
else else
return false return false
end end
@ -647,9 +729,15 @@ end
-- Checks if one circle intersects another circle. -- Checks if one circle intersects another circle.
local function getCircleCircleIntersection(circle1x, circle1y, radius1, circle2x, circle2y, radius2) local function getCircleCircleIntersection(circle1x, circle1y, radius1, circle2x, circle2y, radius2)
local length = getLength(circle1x, circle1y, circle2x, circle2y) local length = getLength(circle1x, circle1y, circle2x, circle2y)
if length > radius1 + radius2 then return false end -- If the distance is greater than the two radii, they can't intersect. if length > radius1 + radius2 then
if checkFuzzy( length, 0 ) and checkFuzzy( radius1, radius2 ) then return 'equal' end return false
if checkFuzzy( circle1x, circle2x ) and checkFuzzy( circle1y, circle2y ) then return 'collinear' end end -- If the distance is greater than the two radii, they can't intersect.
if checkFuzzy(length, 0) and checkFuzzy(radius1, radius2) then
return "equal"
end
if checkFuzzy(circle1x, circle2x) and checkFuzzy(circle1y, circle2y) then
return "collinear"
end
local a = (radius1 * radius1 - radius2 * radius2 + length * length) / (2 * length) local a = (radius1 * radius1 - radius2 * radius2 + length * length) / (2 * length)
local h = math.sqrt(radius1 * radius1 - a * a) local h = math.sqrt(radius1 * radius1 - a * a)
@ -662,25 +750,31 @@ local function getCircleCircleIntersection( circle1x, circle1y, radius1, circle2
local p4y = p2y + h * (circle2x - circle1x) / length local p4y = p2y + h * (circle2x - circle1x) / length
if not validateNumber(p3x) or not validateNumber(p3y) or not validateNumber(p4x) or not validateNumber(p4y) then if not validateNumber(p3x) or not validateNumber(p3y) or not validateNumber(p4x) or not validateNumber(p4y) then
return 'inside' return "inside"
end end
if checkFuzzy( length, radius1 + radius2 ) or checkFuzzy( length, math.abs( radius1 - radius2 ) ) then return 'tangent', p3x, p3y end if checkFuzzy(length, radius1 + radius2) or checkFuzzy(length, math.abs(radius1 - radius2)) then
return 'intersection', p3x, p3y, p4x, p4y return "tangent", p3x, p3y
end
return "intersection", p3x, p3y, p4x, p4y
end end
-- Checks if circle1 is entirely inside of circle2. -- Checks if circle1 is entirely inside of circle2.
local function isCircleCompletelyInsideCircle(circle1x, circle1y, circle1radius, circle2x, circle2y, circle2radius) local function isCircleCompletelyInsideCircle(circle1x, circle1y, circle1radius, circle2x, circle2y, circle2radius)
if not checkCirclePoint( circle1x, circle1y, circle2x, circle2y, circle2radius ) then return false end if not checkCirclePoint(circle1x, circle1y, circle2x, circle2y, circle2radius) then
return false
end
local Type = getCircleCircleIntersection(circle2x, circle2y, circle2radius, circle1x, circle1y, circle1radius) local Type = getCircleCircleIntersection(circle2x, circle2y, circle2radius, circle1x, circle1y, circle1radius)
if ( Type ~= 'tangent' and Type ~= 'collinear' and Type ~= 'inside' ) then return false end if Type ~= "tangent" and Type ~= "collinear" and Type ~= "inside" then
return false
end
return true return true
end end
-- Checks if a line-segment is entirely within a circle. -- Checks if a line-segment is entirely within a circle.
local function isSegmentCompletelyInsideCircle(circleX, circleY, circleRadius, x1, y1, x2, y2) local function isSegmentCompletelyInsideCircle(circleX, circleY, circleRadius, x1, y1, x2, y2)
local Type = getCircleSegmentIntersection(circleX, circleY, circleRadius, x1, y1, x2, y2) local Type = getCircleSegmentIntersection(circleX, circleY, circleRadius, x1, y1, x2, y2)
return Type == 'enclosed' return Type == "enclosed"
end -- }}} end -- }}}
-- Polygon -------------------------------------- {{{ -- Polygon -------------------------------------- {{{
@ -693,12 +787,13 @@ local function getSignedPolygonArea( ... )
points[#points + 1] = points[1] points[#points + 1] = points[1]
points[#points + 1] = points[2] points[#points + 1] = points[2]
return ( .5 * getSummation( 1, #points / 2, return (
function( index ) 0.5
* getSummation(1, #points / 2, function(index)
index = index * 2 - 1 -- Convert it to work properly. index = index * 2 - 1 -- Convert it to work properly.
return ((points[index] * cycle(points, index + 3)) - (cycle(points, index + 2) * points[index + 1])) return ((points[index] * cycle(points, index + 3)) - (cycle(points, index + 2) * points[index + 1]))
end end)
) ) )
end end
-- Simply returns the area of the polygon. -- Simply returns the area of the polygon.
@ -713,8 +808,11 @@ local function getTriangleHeight( base, ... )
local input = checkInput(...) local input = checkInput(...)
local area local area
if #input == 1 then area = input[1] -- Given area. if #input == 1 then
else area = getPolygonArea( input ) end -- Given coordinates. area = input[1] -- Given area.
else
area = getPolygonArea(input)
end -- Given coordinates.
return (2 * area) / base, area return (2 * area) / base, area
end end
@ -729,19 +827,27 @@ local function getCentroid( ... )
local area = getSignedPolygonArea(points) -- Needs to be signed here in case points are counter-clockwise. local area = getSignedPolygonArea(points) -- Needs to be signed here in case points are counter-clockwise.
-- This formula: https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon -- This formula: https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
local centroidX = ( 1 / ( 6 * area ) ) * ( getSummation( 1, #points / 2, local centroidX = (1 / (6 * area))
function( index ) * (
getSummation(1, #points / 2, function(index)
index = index * 2 - 1 -- Convert it to work properly. index = index * 2 - 1 -- Convert it to work properly.
return ( ( points[index] + cycle( points, index + 2 ) ) * ( ( points[index] * cycle( points, index + 3 ) ) - ( cycle( points, index + 2 ) * points[index + 1] ) ) ) return (
end (points[index] + cycle(points, index + 2))
) ) * ((points[index] * cycle(points, index + 3)) - (cycle(points, index + 2) * points[index + 1]))
)
local centroidY = ( 1 / ( 6 * area ) ) * ( getSummation( 1, #points / 2, end)
function( index ) )
local centroidY = (1 / (6 * area))
* (
getSummation(1, #points / 2, function(index)
index = index * 2 - 1 -- Convert it to work properly. index = index * 2 - 1 -- Convert it to work properly.
return ( ( points[index + 1] + cycle( points, index + 3 ) ) * ( ( points[index] * cycle( points, index + 3 ) ) - ( cycle( points, index + 2 ) * points[index + 1] ) ) ) return (
end (points[index + 1] + cycle(points, index + 3))
) ) * ((points[index] * cycle(points, index + 3)) - (cycle(points, index + 2) * points[index + 1]))
)
end)
)
return centroidX, centroidY return centroidX, centroidY
end end
@ -765,9 +871,13 @@ local function getPolygonLineIntersection( x1, y1, x2, y2, ... )
end end
for i = 1, #input, 2 do for i = 1, #input, 2 do
local x1, y1, x2, y2 = getLineSegmentIntersection( input[i], input[i + 1], cycle( input, i + 2 ), cycle( input, i + 3 ), x3, y3, x4, y4 ) local x1, y1, x2, y2 =
if x1 and not x2 then choices[#choices + 1] = { x1, y1 } getLineSegmentIntersection(input[i], input[i + 1], cycle(input, i + 2), cycle(input, i + 3), x3, y3, x4, y4)
elseif x1 and x2 then choices[#choices + 1] = { x1, y1, x2, y2 } end if x1 and not x2 then
choices[#choices + 1] = { x1, y1 }
elseif x1 and x2 then
choices[#choices + 1] = { x1, y1, x2, y2 }
end
-- No need to check 2-point sets since they only intersect each poly line once. -- No need to check 2-point sets since they only intersect each poly line once.
end end
@ -782,9 +892,21 @@ local function getPolygonSegmentIntersection( x1, y1, x2, y2, ... )
local choices = {} local choices = {}
for i = 1, #input, 2 do for i = 1, #input, 2 do
local x1, y1, x2, y2 = getSegmentSegmentIntersection( input[i], input[i + 1], cycle( input, i + 2 ), cycle( input, i + 3 ), x1, y1, x2, y2 ) local x1, y1, x2, y2 = getSegmentSegmentIntersection(
if x1 and not x2 then choices[#choices + 1] = { x1, y1 } input[i],
elseif x2 then choices[#choices + 1] = { x1, y1, x2, y2 } end input[i + 1],
cycle(input, i + 2),
cycle(input, i + 3),
x1,
y1,
x2,
y2
)
if x1 and not x2 then
choices[#choices + 1] = { x1, y1 }
elseif x2 then
choices[#choices + 1] = { x1, y1, x2, y2 }
end
end end
local final = removeDuplicatePairs(choices) local final = removeDuplicatePairs(choices)
@ -796,18 +918,22 @@ local function checkPolygonPoint( px, py, ... )
local points = { unpack(checkInput(...)) } -- Make a new table, as to not edit values of previous. local points = { unpack(checkInput(...)) } -- Make a new table, as to not edit values of previous.
local greatest, least = getGreatestPoint(points, 0) local greatest, least = getGreatestPoint(points, 0)
if not isWithinBounds( least, py, greatest ) then return false end if not isWithinBounds(least, py, greatest) then
return false
end
greatest, least = getGreatestPoint(points) greatest, least = getGreatestPoint(points)
if not isWithinBounds( least, px, greatest ) then return false end if not isWithinBounds(least, px, greatest) then
return false
end
local count = 0 local count = 0
for i = 1, #points, 2 do for i = 1, #points, 2 do
if checkFuzzy(points[i + 1], py) then if checkFuzzy(points[i + 1], py) then
points[i + 1] = py + .001 -- Handles vertices that lie on the point. points[i + 1] = py + 0.001 -- Handles vertices that lie on the point.
-- Not exactly mathematically correct, but a lot easier. -- Not exactly mathematically correct, but a lot easier.
end end
if points[i + 3] and checkFuzzy(points[i + 3], py) then if points[i + 3] and checkFuzzy(points[i + 3], py) then
points[i + 3] = py + .001 -- Do not need to worry about alternate case, since points[2] has already been done. points[i + 3] = py + 0.001 -- Do not need to worry about alternate case, since points[2] has already been done.
end end
local x1, y1 = points[i], points[i + 1] local x1, y1 = points[i], points[i + 1]
local x2, y2 = points[i + 2] or points[1], points[i + 3] or points[2] local x2, y2 = points[i + 2] or points[1], points[i + 3] or points[2]
@ -826,9 +952,13 @@ local function isSegmentInsidePolygon( x1, y1, x2, y2, ... )
local input = checkInput(...) local input = checkInput(...)
local choices = getPolygonSegmentIntersection(x1, y1, x2, y2, input) -- If it's partially enclosed that's all we need. local choices = getPolygonSegmentIntersection(x1, y1, x2, y2, input) -- If it's partially enclosed that's all we need.
if choices then return true end if choices then
return true
end
if checkPolygonPoint( x1, y1, input ) or checkPolygonPoint( x2, y2, input ) then return true end if checkPolygonPoint(x1, y1, input) or checkPolygonPoint(x2, y2, input) then
return true
end
return false return false
end end
@ -837,7 +967,13 @@ local function getPolygonPolygonIntersection( polygon1, polygon2 )
local choices = {} local choices = {}
for index1 = 1, #polygon1, 2 do for index1 = 1, #polygon1, 2 do
local intersections = getPolygonSegmentIntersection( polygon1[index1], polygon1[index1 + 1], cycle( polygon1, index1 + 2 ), cycle( polygon1, index1 + 3 ), polygon2 ) local intersections = getPolygonSegmentIntersection(
polygon1[index1],
polygon1[index1 + 1],
cycle(polygon1, index1 + 2),
cycle(polygon1, index1 + 3),
polygon2
)
if intersections then if intersections then
for index2 = 1, #intersections do for index2 = 1, #intersections do
choices[#choices + 1] = intersections[index2] choices[#choices + 1] = intersections[index2]
@ -846,7 +982,13 @@ local function getPolygonPolygonIntersection( polygon1, polygon2 )
end end
for index1 = 1, #polygon2, 2 do for index1 = 1, #polygon2, 2 do
local intersections = getPolygonSegmentIntersection( polygon2[index1], polygon2[index1 + 1], cycle( polygon2, index1 + 2 ), cycle( polygon2, index1 + 3 ), polygon1 ) local intersections = getPolygonSegmentIntersection(
polygon2[index1],
polygon2[index1 + 1],
cycle(polygon2, index1 + 2),
cycle(polygon2, index1 + 3),
polygon1
)
if intersections then if intersections then
for index2 = 1, #intersections do for index2 = 1, #intersections do
choices[#choices + 1] = intersections[index2] choices[#choices + 1] = intersections[index2]
@ -856,7 +998,7 @@ local function getPolygonPolygonIntersection( polygon1, polygon2 )
choices = removeDuplicatePairs(choices) choices = removeDuplicatePairs(choices)
for i = #choices, 1, -1 do for i = #choices, 1, -1 do
if type( choices[i][1] ) == 'table' then -- Remove co-linear pairs. if type(choices[i][1]) == "table" then -- Remove co-linear pairs.
table.remove(choices, i) table.remove(choices, i)
end end
end end
@ -871,10 +1013,13 @@ local function getPolygonCircleIntersection( x, y, radius, ... )
local choices = {} local choices = {}
for i = 1, #input, 2 do for i = 1, #input, 2 do
local Type, x1, y1, x2, y2 = getCircleSegmentIntersection( x, y, radius, input[i], input[i + 1], cycle( input, i + 2 ), cycle( input, i + 3 ) ) local Type, x1, y1, x2, y2 =
getCircleSegmentIntersection(x, y, radius, input[i], input[i + 1], cycle(input, i + 2), cycle(input, i + 3))
if x2 then if x2 then
choices[#choices + 1] = { Type, x1, y1, x2, y2 } choices[#choices + 1] = { Type, x1, y1, x2, y2 }
elseif x1 then choices[#choices + 1] = { Type, x1, y1 } end elseif x1 then
choices[#choices + 1] = { Type, x1, y1 }
end
end end
local final = removeDuplicates4Points(choices) local final = removeDuplicates4Points(choices)
@ -894,8 +1039,17 @@ local function isPolygonInsidePolygon( polygon1, polygon2 )
local bool = false local bool = false
for i = 1, #polygon2, 2 do for i = 1, #polygon2, 2 do
local result = false local result = false
result = isSegmentInsidePolygon( polygon2[i], polygon2[i + 1], cycle( polygon2, i + 2 ), cycle( polygon2, i + 3 ), polygon1 ) result = isSegmentInsidePolygon(
if result then bool = true; break end polygon2[i],
polygon2[i + 1],
cycle(polygon2, i + 2),
cycle(polygon2, i + 3),
polygon1
)
if result then
bool = true
break
end
end end
return bool return bool
end end
@ -903,9 +1057,11 @@ end
-- Checks if a segment is completely inside a polygon -- Checks if a segment is completely inside a polygon
local function isSegmentCompletelyInsidePolygon(x1, y1, x2, y2, ...) local function isSegmentCompletelyInsidePolygon(x1, y1, x2, y2, ...)
local polygon = checkInput(...) local polygon = checkInput(...)
if not checkPolygonPoint( x1, y1, polygon ) if
not checkPolygonPoint(x1, y1, polygon)
or not checkPolygonPoint(x2, y2, polygon) or not checkPolygonPoint(x2, y2, polygon)
or getPolygonSegmentIntersection( x1, y1, x2, y2, polygon ) then or getPolygonSegmentIntersection(x1, y1, x2, y2, polygon)
then
return false return false
end end
return true return true
@ -934,7 +1090,9 @@ local function isPolygonCompletelyInsideCircle( circleX, circleY, circleRadius,
end end
for i = 1, #input, 2 do for i = 1, #input, 2 do
if not checkCirclePoint( input[i], input[i + 1], circleX, circleY, circleRadius ) then return false end if not checkCirclePoint(input[i], input[i + 1], circleX, circleY, circleRadius) then
return false
end
end end
return true return true
end end
@ -943,15 +1101,21 @@ end
-- circleX, circleY, circleRadius, polygonPoints -- circleX, circleY, circleRadius, polygonPoints
local function isCircleCompletelyInsidePolygon(circleX, circleY, circleRadius, ...) local function isCircleCompletelyInsidePolygon(circleX, circleY, circleRadius, ...)
local input = checkInput(...) local input = checkInput(...)
if not checkPolygonPoint( circleX, circleY, ... ) then return false end if not checkPolygonPoint(circleX, circleY, ...) then
return false
end
local rad2 = circleRadius * circleRadius local rad2 = circleRadius * circleRadius
for i = 1, #input, 2 do for i = 1, #input, 2 do
local x1, y1 = input[i], input[i + 1] local x1, y1 = input[i], input[i + 1]
local x2, y2 = input[i + 2] or input[1], input[i + 3] or input[2] local x2, y2 = input[i + 2] or input[1], input[i + 3] or input[2]
if distance2( x1, y1, circleX, circleY ) <= rad2 then return false end if distance2(x1, y1, circleX, circleY) <= rad2 then
if getCircleSegmentIntersection( circleX, circleY, circleRadius, x1, y1, x2, y2 ) then return false end return false
end
if getCircleSegmentIntersection(circleX, circleY, circleRadius, x1, y1, x2, y2) then
return false
end
end end
return true return true
end -- }}} end -- }}}
@ -962,11 +1126,9 @@ end -- }}}
local function getMean(...) local function getMean(...)
local input = checkInput(...) local input = checkInput(...)
mean = getSummation( 1, #input, mean = getSummation(1, #input, function(i, t)
function( i, t )
return input[i] return input[i]
end end) / #input
) / #input
return mean return mean
end end
@ -980,7 +1142,7 @@ local function getMedian( ... )
if #input % 2 == 0 then -- If you have an even number of terms, you need to get the average of the middle 2. if #input % 2 == 0 then -- If you have an even number of terms, you need to get the average of the middle 2.
median = getMean(input[#input / 2], input[#input / 2 + 1]) median = getMean(input[#input / 2], input[#input / 2 + 1])
else else
median = input[#input / 2 + .5] median = input[#input / 2 + 0.5]
end end
return median return median
@ -1007,8 +1169,11 @@ local function getMode( ... )
end end
end end
if #least >= 1 then return least, occurrences if #least >= 1 then
else return false end return least, occurrences
else
return false
end
end end
-- Gets the range of the numbers. -- Gets the range of the numbers.
@ -1054,9 +1219,9 @@ local function getDispersion( ... )
end -- }}} end -- }}}
return { return {
_VERSION = 'MLib 0.10.0', _VERSION = "MLib 0.10.0",
_DESCRIPTION = 'A math and shape-intersection detection library for Lua', _DESCRIPTION = "A math and shape-intersection detection library for Lua",
_URL = 'https://github.com/davisdude/mlib', _URL = "https://github.com/davisdude/mlib",
point = { point = {
rotate = rotatePoint, rotate = rotatePoint,
scale = scalePoint, scale = scalePoint,

@ -1,10 +1,14 @@
require 'constants' require("constants")
function love.run() function love.run()
if love.load then love.load(love.arg.parseGameArguments(arg), arg) end if love.load then
love.load(love.arg.parseGameArguments(arg), arg)
end
-- We don't want the first frame's dt to include time taken by love.load. -- We don't want the first frame's dt to include time taken by love.load.
if love.timer then love.timer.step() end if love.timer then
love.timer.step()
end
local dt = 0 local dt = 0
@ -24,21 +28,29 @@ function love.run()
end end
-- Update dt, as we'll be passing it to update -- Update dt, as we'll be passing it to update
if love.timer then dt = love.timer.step() end if love.timer then
dt = love.timer.step()
end
-- Call update and draw -- Call update and draw
if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled if love.update then
love.update(dt)
end -- will pass 0 if love.timer is disabled
if love.graphics and love.graphics.isActive() then if love.graphics and love.graphics.isActive() then
love.graphics.origin() love.graphics.origin()
love.graphics.clear(love.graphics.getBackgroundColor()) love.graphics.clear(love.graphics.getBackgroundColor())
if love.draw then love.draw() end if love.draw then
love.draw()
end
love.graphics.present() love.graphics.present()
end end
if love.timer then love.timer.sleep(0.001) end if love.timer then
love.timer.sleep(0.001)
end
end end
end end
@ -112,12 +124,11 @@ function love.load()
musicStory = love.audio.newSource("music/story.mp3", "stream") musicStory = love.audio.newSource("music/story.mp3", "stream")
musicPause = musicBattle:clone() musicPause = musicBattle:clone()
musicPause:setFilter{ musicPause:setFilter({
type = 'lowpass', type = "lowpass",
volume = 0.3, volume = 0.7,
highgain = 0.4, highgain = 0.4,
} })
end end
function love.keypressed(key) function love.keypressed(key)
@ -130,7 +141,6 @@ function love.keypressed(key)
if _G.GAMESTATE == "GAME" then if _G.GAMESTATE == "GAME" then
GameKeyPressed(key) GameKeyPressed(key)
end end
end end
function love.update(dt) function love.update(dt)
@ -162,6 +172,11 @@ function love.update(dt)
love.audio.play(musicPause) love.audio.play(musicPause)
end end
end end
if _G.GAMESTATE == "WIN" then
print(P1_WIN)
print(P2_WIN)
love.event.quit()
end
end end
function love.draw() function love.draw()
@ -179,5 +194,4 @@ function love.draw()
love.graphics.print("" .. GAMESTATE, 200, 200) love.graphics.print("" .. GAMESTATE, 200, 200)
end end
end end
end end

@ -47,38 +47,38 @@ function Player:shoot(bulletSpeed)
return newBullet return newBullet
end end
function handleKeys(dt) function Player:handleKeys(up, down, left, right, dt)
end self.vx, self.vy = 0, 0 --reset every frame
if love.keyboard.isDown(up) then
-- Update method for the Player class
function Player:update(dt)
if _G.GAMESTATE == "GAME" then
self.vx = 0
self.vy = 0
local bulletSpeed = 20000
if self.p == 1 then
-- Handle player 1 controls
if love.keyboard.isDown("w") then
self.vx = cos(self.rotation) * (self.speed * dt) self.vx = cos(self.rotation) * (self.speed * dt)
self.vy = sin(self.rotation) * (self.speed * dt) self.vy = sin(self.rotation) * (self.speed * dt)
elseif love.keyboard.isDown("s") then elseif love.keyboard.isDown(down) then
self.vx = cos(self.rotation) * (self.speed * dt) * -1 self.vx = cos(self.rotation) * (self.speed * dt) * -1
self.vy = sin(self.rotation) * (self.speed * dt) * -1 self.vy = sin(self.rotation) * (self.speed * dt) * -1
elseif love.keyboard.isDown("a") then elseif love.keyboard.isDown(left) then
self.rotation = self.rotation - (self.rotSpeed * dt) self.rotation = self.rotation - (self.rotSpeed * dt)
elseif love.keyboard.isDown("d") then elseif love.keyboard.isDown(right) then
self.rotation = self.rotation + (self.rotSpeed * dt) self.rotation = self.rotation + (self.rotSpeed * dt)
end end
self.collider:setLinearVelocity(self.vx, self.vy) self.collider:setLinearVelocity(self.vx, self.vy)
end
-- Check for collision with walls function Player:updateCol()
if self.collider:enter("Wall") then if self.p == 1 then
print("Player 1 collided with wall") self.x = self.collider:getX()
self.y = self.collider:getY()
elseif self.p == 2 then
self.x = self.collider:getX()
self.y = self.collider:getY()
end
end end
if EnableKeyPress1 == true and GAMESTATE == "GAME" then -- Update method for the Player class
function Player:update(dt)
local bulletSpeed = 20000
if EnableKeyPress1 == true and self.p == 1 then
if love.keyboard.isDown("space") then if love.keyboard.isDown("space") then
local newBullet = self:shoot(bulletSpeed) local newBullet = self:shoot(bulletSpeed)
table.insert(Bullets1, newBullet) table.insert(Bullets1, newBullet)
@ -86,30 +86,8 @@ function Player:update(dt)
EnableKeyPress1 = false EnableKeyPress1 = false
end end
end end
self.x = self.collider:getX()
self.y = self.collider:getY()
elseif self.p == 2 then if EnableKeyPress2 == true and self.p == 2 then
-- Handle player 2 controls
if love.keyboard.isDown("up") then
self.vx = cos(self.rotation) * (self.speed * dt)
self.vy = sin(self.rotation) * (self.speed * dt)
elseif love.keyboard.isDown("down") then
self.vx = cos(self.rotation) * (self.speed * dt) * -1
self.vy = sin(self.rotation) * (self.speed * dt) * -1
elseif love.keyboard.isDown("left") then
self.rotation = self.rotation - (self.rotSpeed * dt)
elseif love.keyboard.isDown("right") then
self.rotation = self.rotation + (self.rotSpeed * dt)
end
self.collider:setLinearVelocity(self.vx, self.vy)
-- Check for collision with walls
if self.collider:enter("Wall") then
print("Player 2 collided with wall")
end
if EnableKeyPress2 == true then
if love.keyboard.isDown("return") then if love.keyboard.isDown("return") then
local newBullet = self:shoot(bulletSpeed) local newBullet = self:shoot(bulletSpeed)
table.insert(Bullets2, newBullet) table.insert(Bullets2, newBullet)
@ -118,10 +96,6 @@ function Player:update(dt)
end end
end end
end end
self.x = self.collider:getX()
self.y = self.collider:getY()
end
end
function Player:draw() function Player:draw()
love.graphics.draw(self.image, self.x, self.y, self.rotation, self.scaleX, self.scaleY, self.originX, self.originY) love.graphics.draw(self.image, self.x, self.y, self.rotation, self.scaleX, self.scaleY, self.originX, self.originY)

Loading…
Cancel
Save