You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1136 lines
34 KiB
1136 lines
34 KiB
--[[
|
|
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
|
|
|