aboutsummaryrefslogtreecommitdiff
path: root/libs/windfield/init.lua
diff options
context:
space:
mode:
Diffstat (limited to 'libs/windfield/init.lua')
-rw-r--r--libs/windfield/init.lua929
1 files changed, 929 insertions, 0 deletions
diff --git a/libs/windfield/init.lua b/libs/windfield/init.lua
new file mode 100644
index 0000000..2c7192a
--- /dev/null
+++ b/libs/windfield/init.lua
@@ -0,0 +1,929 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2018 SSYGEN
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+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
+SOFTWARE.
+]]--
+
+local path = ... .. '.'
+local wf = {}
+wf.Math = require(path .. 'mlib.mlib')
+
+World = {}
+World.__index = World
+
+function wf.newWorld(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:collisionClear()
+ world:addCollisionClass('Default')
+
+ -- 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
+ 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
+ world[k] = function(self, ...)
+ return v(self.box2d_world, ...)
+ end
+ end
+ end
+
+ return world
+end
+
+function World.new(wf, xg, yg, sleep)
+ local self = {}
+ local settings = settings or {}
+ self.wf = wf
+
+ self.draw_query_for_n_frames = 10
+ self.query_debug_drawing_enabled = false
+ self.explicit_collision_events = false
+ self.collision_classes = {}
+ self.masks = {}
+ self.is_sensor_memo = {}
+ self.query_debug_draw = {}
+
+ love.physics.setMeter(32)
+ self.box2d_world = love.physics.newWorld(xg, yg, sleep)
+
+ return setmetatable(self, World)
+end
+
+function World:update(dt)
+ self:collisionEventsClear()
+ self.box2d_world:update(dt)
+end
+
+function World:draw(alpha)
+ -- get the current color values to reapply
+ local r, g, b, a = love.graphics.getColor()
+ -- alpha value is optional
+ alpha = alpha or 255
+ -- Colliders debug
+ love.graphics.setColor(222, 222, 222, alpha)
+ local bodies = self.box2d_world:getBodies()
+ for _, body in ipairs(bodies) do
+ local fixtures = body:getFixtures()
+ for _, fixture in ipairs(fixtures) do
+ if fixture:getShape():type() == 'PolygonShape' then
+ love.graphics.polygon('line', body:getWorldPoints(fixture:getShape():getPoints()))
+ elseif fixture:getShape():type() == 'EdgeShape' or fixture:getShape():type() == 'ChainShape' then
+ local points = {body:getWorldPoints(fixture:getShape():getPoints())}
+ 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
+ end
+ elseif fixture:getShape():type() == 'CircleShape' then
+ local body_x, body_y = body:getPosition()
+ local shape_x, shape_y = fixture:getShape():getPoint()
+ local r = fixture:getShape():getRadius()
+ love.graphics.circle('line', body_x + shape_x, body_y + shape_y, r, 360)
+ end
+ end
+ end
+ love.graphics.setColor(255, 255, 255, alpha)
+
+ -- Joint debug
+ love.graphics.setColor(222, 128, 64, alpha)
+ local joints = self.box2d_world:getJoints()
+ for _, joint in ipairs(joints) do
+ local x1, y1, x2, y2 = joint:getAnchors()
+ if x1 and y1 then love.graphics.circle('line', x1, y1, 4) end
+ if x2 and y2 then love.graphics.circle('line', x2, y2, 4) end
+ end
+ love.graphics.setColor(255, 255, 255, alpha)
+
+ -- Query debug
+ love.graphics.setColor(64, 64, 222, alpha)
+ for _, query_draw in ipairs(self.query_debug_draw) do
+ query_draw.frames = query_draw.frames - 1
+ if query_draw.type == 'circle' then
+ love.graphics.circle('line', query_draw.x, query_draw.y, query_draw.r)
+ elseif query_draw.type == 'rectangle' then
+ love.graphics.rectangle('line', query_draw.x, query_draw.y, query_draw.w, query_draw.h)
+ elseif query_draw.type == 'line' then
+ love.graphics.line(query_draw.x1, query_draw.y1, query_draw.x2, query_draw.y2)
+ elseif query_draw.type == 'polygon' then
+ local triangles = love.math.triangulate(query_draw.vertices)
+ for _, triangle in ipairs(triangles) do love.graphics.polygon('line', triangle) end
+ end
+ end
+ for i = #self.query_debug_draw, 1, -1 do
+ if self.query_debug_draw[i].frames <= 0 then
+ table.remove(self.query_debug_draw, i)
+ end
+ end
+ love.graphics.setColor(r, g, b, a)
+end
+
+function World:setQueryDebugDrawing(value)
+ self.query_debug_drawing_enabled = value
+end
+
+function World:setExplicitCollisionEvents(value)
+ self.explicit_collision_events = value
+end
+
+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.explicit_collision_events then
+ self.collision_classes[collision_class_name] = collision_class or {}
+ else
+ self.collision_classes[collision_class_name] = collision_class or {}
+ self.collision_classes[collision_class_name].enter = {}
+ self.collision_classes[collision_class_name].exit = {}
+ self.collision_classes[collision_class_name].pre = {}
+ self.collision_classes[collision_class_name].post = {}
+ for c_class_name, _ in pairs(self.collision_classes) do
+ table.insert(self.collision_classes[collision_class_name].enter, c_class_name)
+ table.insert(self.collision_classes[collision_class_name].exit, c_class_name)
+ table.insert(self.collision_classes[collision_class_name].pre, c_class_name)
+ table.insert(self.collision_classes[collision_class_name].post, c_class_name)
+ end
+ for c_class_name, _ in pairs(self.collision_classes) do
+ table.insert(self.collision_classes[c_class_name].enter, collision_class_name)
+ table.insert(self.collision_classes[c_class_name].exit, collision_class_name)
+ table.insert(self.collision_classes[c_class_name].pre, collision_class_name)
+ table.insert(self.collision_classes[c_class_name].post, collision_class_name)
+ end
+ end
+
+ self:collisionClassesSet()
+end
+
+function World:collisionClassesSet()
+ self:generateCategoriesMasks()
+
+ self:collisionClear()
+ local collision_table = self:getCollisionCallbacksTable()
+ for collision_class_name, collision_list in pairs(collision_table) 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 == '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
+
+ self:collisionEventsClear()
+end
+
+function World:collisionClear()
+ self.collisions = {}
+ self.collisions.on_enter = {}
+ self.collisions.on_enter.sensor = {}
+ self.collisions.on_enter.non_sensor = {}
+ self.collisions.on_exit = {}
+ self.collisions.on_exit.sensor = {}
+ self.collisions.on_exit.non_sensor = {}
+ self.collisions.pre = {}
+ self.collisions.pre.sensor = {}
+ self.collisions.pre.non_sensor = {}
+ self.collisions.post = {}
+ self.collisions.post.sensor = {}
+ self.collisions.post.non_sensor = {}
+end
+
+function World:collisionEventsClear()
+ local bodies = self.box2d_world:getBodies()
+ for _, body in ipairs(bodies) do
+ local collider = body:getFixtures()[1]:getUserData()
+ collider:collisionEventsClear()
+ end
+end
+
+function World:addCollisionEnter(type1, type2)
+ if not self:isCollisionBetweenSensors(type1, type2) then
+ 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
+end
+
+function World:addCollisionExit(type1, type2)
+ if not self:isCollisionBetweenSensors(type1, type2) then
+ 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
+end
+
+function World:addCollisionPre(type1, type2)
+ if not self:isCollisionBetweenSensors(type1, type2) then
+ table.insert(self.collisions.pre.non_sensor, {type1 = type1, type2 = type2})
+ else table.insert(self.collisions.pre.sensor, {type1 = type1, type2 = type2}) end
+end
+
+function World:addCollisionPost(type1, type2)
+ if not self:isCollisionBetweenSensors(type1, type2) then
+ table.insert(self.collisions.post.non_sensor, {type1 = type1, type2 = type2})
+ else table.insert(self.collisions.post.sensor, {type1 = type1, type2 = type2}) end
+end
+
+function World:doesType1IgnoreType2(type1, type2)
+ local collision_ignores = {}
+ for collision_class_name, collision_class in pairs(self.collision_classes) do
+ collision_ignores[collision_class_name] = collision_class.ignores or {}
+ end
+ local all = {}
+ for collision_class_name, _ in pairs(collision_ignores) do
+ table.insert(all, collision_class_name)
+ end
+ local ignored_types = {}
+ for _, collision_class_type in ipairs(collision_ignores[type1]) do
+ if collision_class_type == 'All' then
+ for _, collision_class_name in ipairs(all) do
+ table.insert(ignored_types, collision_class_name)
+ end
+ else table.insert(ignored_types, collision_class_type) end
+ end
+ for key, _ in pairs(collision_ignores[type1]) do
+ if key == 'except' then
+ for _, except_type in ipairs(collision_ignores[type1].except) do
+ for i = #ignored_types, 1, -1 do
+ if ignored_types[i] == except_type then table.remove(ignored_types, i) end
+ end
+ end
+ end
+ end
+ for _, ignored_type in ipairs(ignored_types) do
+ if ignored_type == type2 then return true end
+ end
+end
+
+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][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
+
+-- https://love2d.org/forums/viewtopic.php?f=4&t=75441
+function World:generateCategoriesMasks()
+ local collision_ignores = {}
+ for collision_class_name, collision_class in pairs(self.collision_classes) do
+ collision_ignores[collision_class_name] = collision_class.ignores or {}
+ end
+ local incoming = {}
+ local expanded = {}
+ local all = {}
+ for object_type, _ in pairs(collision_ignores) do
+ incoming[object_type] = {}
+ expanded[object_type] = {}
+ table.insert(all, object_type)
+ end
+ for object_type, ignore_list in pairs(collision_ignores) do
+ for key, ignored_type in pairs(ignore_list) do
+ if ignored_type == 'All' then
+ for _, all_object_type in ipairs(all) do
+ table.insert(incoming[all_object_type], object_type)
+ table.insert(expanded[object_type], all_object_type)
+ end
+ elseif type(ignored_type) == 'string' then
+ if ignored_type ~= 'All' then
+ table.insert(incoming[ignored_type], object_type)
+ table.insert(expanded[object_type], ignored_type)
+ end
+ end
+ if key == 'except' then
+ for _, except_ignored_type in ipairs(ignored_type) do
+ for i, v in ipairs(incoming[except_ignored_type]) do
+ if v == object_type then
+ table.remove(incoming[except_ignored_type], i)
+ break
+ end
+ end
+ end
+ for _, except_ignored_type in ipairs(ignored_type) do
+ for i, v in ipairs(expanded[object_type]) do
+ if v == except_ignored_type then
+ table.remove(expanded[object_type], i)
+ break
+ end
+ end
+ end
+ end
+ end
+ end
+ local edge_groups = {}
+ for k, v in pairs(incoming) do
+ table.sort(v, function(a, b) return string.lower(a) < string.lower(b) end)
+ end
+ local i = 0
+ for k, v in pairs(incoming) do
+ local str = ""
+ for _, c in ipairs(v) do
+ str = str .. c
+ end
+ if not edge_groups[str] then i = i + 1; edge_groups[str] = {n = i} end
+ table.insert(edge_groups[str], k)
+ end
+ local categories = {}
+ for k, _ in pairs(collision_ignores) do
+ categories[k] = {}
+ end
+ for k, v in pairs(edge_groups) do
+ for i, c in ipairs(v) do
+ categories[c] = v.n
+ end
+ end
+ for k, v in pairs(expanded) do
+ local category = {categories[k]}
+ local current_masks = {}
+ for _, c in ipairs(v) do
+ table.insert(current_masks, categories[c])
+ end
+ self.masks[k] = {categories = category, masks = current_masks}
+ end
+end
+
+function World:getCollisionCallbacksTable()
+ local collision_table = {}
+ for collision_class_name, collision_class in pairs(self.collision_classes) do
+ 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.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
+ return collision_table
+end
+
+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
+ else return a, b end
+end
+
+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
+ (a.collision_class == collision_class_name2 and b.collision_class == collision_class_name1) then
+ return true
+ else return false end
+end
+
+function World.collisionOnEnter(fixture_a, fixture_b, contact)
+ local a, b = fixture_a:getUserData(), fixture_b:getUserData()
+
+ if fixture_a:isSensor() and fixture_b:isSensor() then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.on_enter.sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ 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})
+ if collision.type1 == collision.type2 then
+ table.insert(b.collision_events[collision.type1], {collision_type = 'enter', collider_1 = b, collider_2 = a, contact = contact})
+ end
+ end
+ end
+ end
+
+ elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.on_enter.non_sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ 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})
+ if collision.type1 == collision.type2 then
+ table.insert(b.collision_events[collision.type1], {collision_type = 'enter', collider_1 = b, collider_2 = a, contact = contact})
+ end
+ end
+ end
+ end
+ end
+end
+
+function World.collisionOnExit(fixture_a, fixture_b, contact)
+ local a, b = fixture_a:getUserData(), fixture_b:getUserData()
+
+ if fixture_a:isSensor() and fixture_b:isSensor() then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.on_exit.sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ 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})
+ if collision.type1 == collision.type2 then
+ table.insert(b.collision_events[collision.type1], {collision_type = 'exit', collider_1 = b, collider_2 = a, contact = contact})
+ end
+ end
+ end
+ end
+
+ elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.on_exit.non_sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ 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})
+ if collision.type1 == collision.type2 then
+ table.insert(b.collision_events[collision.type1], {collision_type = 'exit', collider_1 = b, collider_2 = a, contact = contact})
+ end
+ end
+ end
+ end
+ end
+end
+
+function World.collisionPre(fixture_a, fixture_b, contact)
+ local a, b = fixture_a:getUserData(), fixture_b:getUserData()
+
+ if fixture_a:isSensor() and fixture_b:isSensor() then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.pre.sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ a, b = collEnsure(collision.type1, a, collision.type2, b)
+ a:preSolve(b, contact)
+ if collision.type1 == collision.type2 then
+ b:preSolve(a, contact)
+ end
+ end
+ end
+ end
+
+ elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.pre.non_sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ a, b = collEnsure(collision.type1, a, collision.type2, b)
+ a:preSolve(b, contact)
+ if collision.type1 == collision.type2 then
+ b:preSolve(a, contact)
+ end
+ end
+ end
+ end
+ end
+end
+
+function World.collisionPost(fixture_a, fixture_b, contact, ni1, ti1, ni2, ti2)
+ local a, b = fixture_a:getUserData(), fixture_b:getUserData()
+
+ if fixture_a:isSensor() and fixture_b:isSensor() then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.post.sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ a, b = collEnsure(collision.type1, a, collision.type2, b)
+ a:postSolve(b, contact, ni1, ti1, ni2, ti2)
+ if collision.type1 == collision.type2 then
+ b:postSolve(a, contact, ni1, ti1, ni2, ti2)
+ end
+ end
+ end
+ end
+
+ elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.post.non_sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ a, b = collEnsure(collision.type1, a, collision.type2, b)
+ a:postSolve(b, contact, ni1, ti1, ni2, ti2)
+ if collision.type1 == collision.type2 then
+ b:postSolve(a, contact, ni1, ti1, ni2, ti2)
+ end
+ end
+ end
+ end
+ end
+end
+
+function World:newCircleCollider(x, y, r, settings)
+ return self.wf.Collider.new(self, 'Circle', x, y, r, settings)
+end
+
+function World:newRectangleCollider(x, y, w, h, settings)
+ return self.wf.Collider.new(self, 'Rectangle', x, y, w, h, settings)
+end
+
+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)
+end
+
+function World:newPolygonCollider(vertices, settings)
+ return self.wf.Collider.new(self, 'Polygon', vertices, settings)
+end
+
+function World:newLineCollider(x1, y1, x2, y2, settings)
+ return self.wf.Collider.new(self, 'Line', x1, y1, x2, y2, settings)
+end
+
+function World:newChainCollider(vertices, loop, settings)
+ return self.wf.Collider.new(self, 'Chain', vertices, loop, settings)
+end
+
+-- Internal AABB box2d query used before going for more specific and precise computations.
+function World:_queryBoundingBox(x1, y1, x2, y2)
+ local colliders = {}
+ local callback = function(fixture)
+ if not fixture:isSensor() then table.insert(colliders, fixture:getUserData()) end
+ return true
+ end
+ self.box2d_world:queryBoundingBox(x1, y1, x2, y2, callback)
+ return colliders
+end
+
+function World:collisionClassInCollisionClassesList(collision_class, collision_classes)
+ if collision_classes[1] == 'All' then
+ local all_collision_classes = {}
+ for class, _ in pairs(self.collision_classes) do
+ table.insert(all_collision_classes, class)
+ end
+ if collision_classes.except then
+ for _, except in ipairs(collision_classes.except) do
+ for i, class in ipairs(all_collision_classes) do
+ if class == except then
+ table.remove(all_collision_classes, i)
+ break
+ end
+ end
+ end
+ end
+ for _, class in ipairs(all_collision_classes) do
+ if class == collision_class then return true end
+ end
+ else
+ for _, class in ipairs(collision_classes) do
+ if class == collision_class then return true end
+ end
+ end
+end
+
+function World:queryCircleArea(x, y, radius, collision_class_names)
+ if not collision_class_names then 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 outs = {}
+ for _, collider in ipairs(colliders) do
+ if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then
+ for _, fixture in ipairs(collider.body:getFixtures()) do
+ if self.wf.Math.polygon.getCircleIntersection(x, y, radius, {collider.body:getWorldPoints(fixture:getShape():getPoints())}) then
+ table.insert(outs, collider)
+ break
+ end
+ end
+ end
+ end
+ return outs
+end
+
+function World:queryRectangleArea(x, y, w, h, collision_class_names)
+ if not collision_class_names then 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 outs = {}
+ for _, collider in ipairs(colliders) do
+ if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then
+ 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
+ table.insert(outs, collider)
+ break
+ end
+ end
+ end
+ end
+ return outs
+end
+
+function World:queryPolygonArea(vertices, collision_class_names)
+ if not collision_class_names then 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 d_max = 0
+ for i = 1, #vertices, 2 do
+ local d = self.wf.Math.line.getLength(cx, cy, vertices[i], vertices[i+1])
+ if d > d_max then d_max = d end
+ end
+ local colliders = self:_queryBoundingBox(cx-d_max, cy-d_max, cx+d_max, cy+d_max)
+ local outs = {}
+ for _, collider in ipairs(colliders) do
+ if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then
+ for _, fixture in ipairs(collider.body:getFixtures()) do
+ if self.wf.Math.polygon.isPolygonInside(vertices, {collider.body:getWorldPoints(fixture:getShape():getPoints())}) then
+ table.insert(outs, collider)
+ break
+ end
+ end
+ end
+ end
+ return outs
+end
+
+function World:queryLine(x1, y1, x2, y2, collision_class_names)
+ if not collision_class_names then collision_class_names = {'All'} end
+ 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})
+ end
+
+ local colliders = {}
+ local callback = function(fixture, ...)
+ if not fixture:isSensor() then table.insert(colliders, fixture:getUserData()) end
+ return 1
+ end
+ self.box2d_world:rayCast(x1, y1, x2, y2, callback)
+
+ local outs = {}
+ for _, collider in ipairs(colliders) do
+ if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then
+ table.insert(outs, collider)
+ end
+ end
+ return outs
+end
+
+function World:addJoint(joint_type, ...)
+ local args = {...}
+ if args[1].body then args[1] = args[1].body 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
+end
+
+function World:removeJoint(joint)
+ joint:destroy()
+end
+
+function World:destroy()
+ local bodies = self.box2d_world:getBodies()
+ for _, body in ipairs(bodies) do
+ local collider = body:getFixtures()[1]:getUserData()
+ collider:destroy()
+ end
+ local joints = self.box2d_world:getJoints()
+ for _, joint in ipairs(joints) do joint:destroy() end
+ self.box2d_world:destroy()
+ self.box2d_world = nil
+end
+
+
+
+local Collider = {}
+Collider.__index = Collider
+
+local generator = love.math.newRandomGenerator(os.time())
+local function UUID()
+ local fn = function(x)
+ local r = generator:random(16) - 1
+ r = (x == "x") and (r + 1) or (r % 4) + 9
+ return ("0123456789abcdef"):sub(r, r)
+ end
+ return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn))
+end
+
+function Collider.new(world, collider_type, ...)
+ local self = {}
+ self.id = UUID()
+ self.world = world
+ self.type = collider_type
+ self.object = nil
+
+ self.shapes = {}
+ self.fixtures = {}
+ self.sensors = {}
+
+ self.collision_events = {}
+ self.collision_stay = {}
+ self.enter_collision_data = {}
+ self.exit_collision_data = {}
+ self.stay_collision_data = {}
+
+ local args = {...}
+ local shape, fixture
+ if self.type == 'Circle' then
+ 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')
+ shape = love.physics.newCircleShape(args[3])
+
+ elseif self.type == 'Rectangle' then
+ self.collision_class = (args[5] and args[5].collision_class) or 'Default'
+ 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')
+ shape = love.physics.newRectangleShape(args[3], args[4])
+
+ elseif self.type == 'BSGRectangle' then
+ self.collision_class = (args[6] and args[6].collision_class) or 'Default'
+ 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')
+ local w, h, s = args[3], args[4], args[5]
+ shape = love.physics.newPolygonShape({
+ -w/2, -h/2 + s, -w/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
+ 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')
+ shape = love.physics.newPolygonShape(unpack(args[1]))
+
+ elseif self.type == 'Line' then
+ 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')
+ shape = love.physics.newEdgeShape(args[1], args[2], args[3], args[4])
+
+ elseif self.type == 'Chain' then
+ 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')
+ shape = love.physics.newChainShape(args[1], unpack(args[2]))
+ end
+
+ -- Define collision classes and attach them to fixture and sensor
+ fixture = love.physics.newFixture(self.body, shape)
+ if self.world.masks[self.collision_class] then
+ fixture:setCategory(unpack(self.world.masks[self.collision_class].categories))
+ fixture:setMask(unpack(self.world.masks[self.collision_class].masks))
+ end
+ fixture:setUserData(self)
+ local sensor = love.physics.newFixture(self.body, shape)
+ sensor:setSensor(true)
+ sensor:setUserData(self)
+
+ self.shapes['main'] = shape
+ self.fixtures['main'] = fixture
+ self.sensors['main'] = sensor
+ self.shape = shape
+ self.fixture = fixture
+
+ self.preSolve = function() end
+ self.postSolve = function() end
+
+ -- 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
+ 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
+ self[k] = function(self, ...)
+ return v(self.body, ...)
+ end
+ end
+ end
+ 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
+ self[k] = function(self, ...)
+ return v(self.fixture, ...)
+ end
+ end
+ end
+ 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
+ self[k] = function(self, ...)
+ return v(self.shape, ...)
+ end
+ end
+ end
+
+ return setmetatable(self, Collider)
+end
+
+function Collider:collisionEventsClear()
+ self.collision_events = {}
+ for other, _ in pairs(self.world.collision_classes) do
+ self.collision_events[other] = {}
+ end
+end
+
+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
+ self.collision_class = collision_class_name
+ for _, fixture in pairs(self.fixtures) do
+ if self.world.masks[collision_class_name] then
+ fixture:setCategory(unpack(self.world.masks[collision_class_name].categories))
+ fixture:setMask(unpack(self.world.masks[collision_class_name].masks))
+ end
+ end
+end
+
+function Collider:enter(other_collision_class_name)
+ local events = self.collision_events[other_collision_class_name]
+ if events and #events >= 1 then
+ for _, e in ipairs(events) do
+ if e.collision_type == 'enter' then
+ if not self.collision_stay[other_collision_class_name] then 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}
+ return true
+ end
+ end
+ end
+end
+
+function Collider:getEnterCollisionData(other_collision_class_name)
+ return self.enter_collision_data[other_collision_class_name]
+end
+
+function Collider:exit(other_collision_class_name)
+ local events = self.collision_events[other_collision_class_name]
+ if events and #events >= 1 then
+ for _, e in ipairs(events) do
+ if e.collision_type == 'exit' then
+ if self.collision_stay[other_collision_class_name] then
+ for i = #self.collision_stay[other_collision_class_name], 1, -1 do
+ 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
+ end
+ end
+ self.exit_collision_data[other_collision_class_name] = {collider = e.collider_2, contact = e.contact}
+ return true
+ end
+ end
+ end
+end
+
+function Collider:getExitCollisionData(other_collision_class_name)
+ return self.exit_collision_data[other_collision_class_name]
+end
+
+function Collider:stay(other_collision_class_name)
+ if self.collision_stay[other_collision_class_name] then
+ if #self.collision_stay[other_collision_class_name] >= 1 then
+ return true
+ end
+ end
+end
+
+function Collider:getStayCollisionData(other_collision_class_name)
+ return self.collision_stay[other_collision_class_name]
+end
+
+function Collider:setPreSolve(callback)
+ self.preSolve = callback
+end
+
+function Collider:setPostSolve(callback)
+ self.postSolve = callback
+end
+
+function Collider:setObject(object)
+ self.object = object
+end
+
+function Collider:getObject()
+ return self.object
+end
+
+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
+ local args = {...}
+ local shape = love.physics['new' .. shape_type](unpack(args))
+ local fixture = love.physics.newFixture(self.body, shape)
+ if self.world.masks[self.collision_class] then
+ fixture:setCategory(unpack(self.world.masks[self.collision_class].categories))
+ fixture:setMask(unpack(self.world.masks[self.collision_class].masks))
+ end
+ fixture:setUserData(self)
+ local sensor = love.physics.newFixture(self.body, shape)
+ sensor:setSensor(true)
+ sensor:setUserData(self)
+
+ self.shapes[shape_name] = shape
+ self.fixtures[shape_name] = fixture
+ self.sensors[shape_name] = sensor
+end
+
+function Collider:removeShape(shape_name)
+ if not self.shapes[shape_name] then return end
+ self.shapes[shape_name] = nil
+ self.fixtures[shape_name]:setUserData(nil)
+ self.fixtures[shape_name]:destroy()
+ self.fixtures[shape_name] = nil
+ self.sensors[shape_name]:setUserData(nil)
+ self.sensors[shape_name]:destroy()
+ self.sensors[shape_name] = nil
+end
+
+function Collider:destroy()
+ self.collision_stay = nil
+ self.enter_collision_data = nil
+ self.exit_collision_data = nil
+ self:collisionEventsClear()
+
+ self:setObject(nil)
+ for name, _ in pairs(self.fixtures) do
+ self.shapes[name] = nil
+ self.fixtures[name]:setUserData(nil)
+ self.fixtures[name] = nil
+ self.sensors[name]:setUserData(nil)
+ self.sensors[name] = nil
+ end
+ self.body:destroy()
+ self.body = nil
+end
+
+wf.World = World
+wf.Collider = Collider
+
+return wf
+