From f96a1e19ec144088fca8acd67c7aed0cd57f1819 Mon Sep 17 00:00:00 2001 From: Vodkannelle Date: Sat, 8 May 2021 11:42:13 +0200 Subject: [PATCH] added breezefield as a dep, needed to edit some stuff to make it work but it works --- deps/breezefield/collider.lua | 68 ++++++++++ deps/breezefield/init.lua | 24 ++++ deps/breezefield/utils.lua | 32 +++++ deps/breezefield/world.lua | 238 ++++++++++++++++++++++++++++++++++ 4 files changed, 362 insertions(+) create mode 100644 deps/breezefield/collider.lua create mode 100644 deps/breezefield/init.lua create mode 100644 deps/breezefield/utils.lua create mode 100644 deps/breezefield/world.lua diff --git a/deps/breezefield/collider.lua b/deps/breezefield/collider.lua new file mode 100644 index 0000000..06f8608 --- /dev/null +++ b/deps/breezefield/collider.lua @@ -0,0 +1,68 @@ +-- a Collider object, wrapping shape, body, and fixtue +local set_funcs, lp, lg, COLLIDER_TYPES = unpack(require("deps.breezefield.utils")) -- it me who put deps.breezefield + +local Collider = {} +Collider.__index = Collider + + + +function Collider.new(world, collider_type, ...) + -- deprecated + return world:newCollider(collider_type, {...}) +end + +function Collider:draw_type() + if self.collider_type == 'Edge' or self.collider_type == 'Chain' then + return 'line' + end + return self.collider_type:lower() +end + +function Collider:__draw__() + self._draw_type = self._draw_type or self:draw_type() + local args + if self._draw_type == 'line' then + args = {self:getSpatialIdentity()} + else + args = {'line', self:getSpatialIdentity()} + end + love.graphics[self:draw_type()](unpack(args)) +end + +function Collider:draw() + self:__draw__() +end + + +function Collider:destroy() + self._world.colliders[self] = nil + self.fixture:setUserData(nil) + self.fixture:destroy() + self.body:destroy() +end + +function Collider:getSpatialIdentity() + if self.collider_type == 'Circle' then + return self:getX(), self:getY(), self:getRadius() + else + return self:getWorldPoints(self:getPoints()) + end +end + +function Collider:collider_contacts() + local contacts = self:getContacts() + local colliders = {} + for i, contact in ipairs(contacts) do + if contact:isTouching() then + local f1, f2 = contact:getFixtures() + if f1 == self.fixture then + colliders[#colliders+1] = f2:getUserData() + else + colliders[#colliders+1] = f1:getUserData() + end + end + end + return colliders +end + +return Collider diff --git a/deps/breezefield/init.lua b/deps/breezefield/init.lua new file mode 100644 index 0000000..db149c8 --- /dev/null +++ b/deps/breezefield/init.lua @@ -0,0 +1,24 @@ +-- breezefield: init.lua +--[[ + implements Collider and World objects + Collider wraps the basic functionality of shape, fixture, and body + World wraps world, and provides automatic drawing simplified collisions +]]-- + + + +local bf = {} + +local BASE = (...) .. "." +local Collider = require(BASE .. 'collider') +local World = require(BASE .. 'world') + + +function bf.newWorld(...) + return bf.World:new(...) +end + +bf.Collider = Collider +bf.World = World + +return bf diff --git a/deps/breezefield/utils.lua b/deps/breezefield/utils.lua new file mode 100644 index 0000000..910d06e --- /dev/null +++ b/deps/breezefield/utils.lua @@ -0,0 +1,32 @@ +-- function used for both +local function set_funcs(mainobject, subobject) + -- this function assigns functions of a subobject to a primary object + --[[ + mainobject: the table to which to assign the functions + subobject: the table whose functions to assign + no output + --]] + for k, v in pairs(subobject.__index) do + if k ~= '__gc' and k ~= '__eq' and k ~= '__index' + and k ~= '__tostring' and k ~= 'destroy' and k ~= 'type' + and k ~= 'typeOf'and k ~= 'getUserData' and k ~= 'setUserData' then + mainobject[k] = function(mainobject, ...) + return v(subobject, ...) + end + end + end +end + +local COLLIDER_TYPES = { + CIRCLE = "Circle", + CIRC = "Circle", + RECTANGLE = "Rectangle", + RECT = "Rectangle", + POLYGON = "Polygon", + POLY = "Polygon", + EDGE = 'Edge', + CHAIN = 'Chain' +} + + +return {set_funcs, love.physics, love.graphics, COLLIDER_TYPES} diff --git a/deps/breezefield/world.lua b/deps/breezefield/world.lua new file mode 100644 index 0000000..c53fa49 --- /dev/null +++ b/deps/breezefield/world.lua @@ -0,0 +1,238 @@ +-- breezefield: World.lua +--[[ + World: has access to all the functions of love.physics.world + additionally stores all Collider objects assigned to it in + self.colliders (as key-value pairs) + can draw all its Colliders + by default, calls :collide on any colliders in it for postSolve + or for beginContact if the colliders are sensors +--]] +-- TODO make updating work from here too +-- TODO: update test and tutorial +local Collider = require('deps.breezefield.collider') -- it me who put deps.breezefield +local set_funcs, lp, lg, COLLIDER_TYPES = unpack(require("deps.breezefield.utils")) -- it me who put deps.breezefield + + +local World = {} +World.__index = World +function World:new(...) + -- create a new physics world + --[[ + inputs: (same as love.physics.newWorld) + xg: float, gravity in x direction + yg: float, gravity in y direction + sleep: boolean, whether bodies can sleep + outputs: + w: bf.World, the created world + ]]-- + + local w = {} + setmetatable(w, self) + w._world = lp.newWorld(...) + set_funcs(w, w._world) + w.update = nil -- to use our custom update + w.colliders = {} + + -- some functions defined here to use w without being passed it + + function w.collide(obja, objb, coll_type, ...) + -- collision event for two Colliders + local function run_coll(obj1, obj2, ...) + if obj1[coll_type] ~= nil then + local e = obj1[coll_type](obj1, obj2, ...) + if type(e) == 'function' then + w.collide_events[#w.collide_events+1] = e + end + end + end + + if obja ~= nil and objb ~= nil then + run_coll(obja, objb, ...) + run_coll(objb, obja, ...) + end + end + + function w.enter(a, b, ...) + return w.collision(a, b, 'enter', ...) + end + function w.exit(a, b, ...) + return w.collision(a, b, 'exit', ...) + end + function w.preSolve(a, b, ...) + return w.collision(a, b, 'preSolve', ...) + end + function w.postSolve(a, b, ...) + return w.collision(a, b, 'postSolve', ...) + end + + function w.collision(a, b, ...) + -- objects that hit one another can have collide methods + -- by default used as postSolve callback + local obja = a:getUserData(a) + local objb = b:getUserData(b) + w.collide(obja, objb, ...) + end + + w:setCallbacks(w.enter, w.exit, w.preSolve, w.postSolve) + w.collide_events = {} + return w +end + + +function World:draw(alpha, draw_over) + -- draw the world + --[[ + alpha: sets the alpha of the drawing, defaults to 1 + draw_over: draws the collision objects shapes even if their + .draw method is overwritten + --]] + local color = {love.graphics.getColor()} + for _, c in pairs(self.colliders) do + love.graphics.setColor(1, 1, 1, alpha or 1) + c:draw(alpha) + if draw_over then + love.graphics.setColor(1, 1, 1, alpha or 1) + c:__draw__() + end + end + love.graphics.setColor(color) +end + +function World:queryRectangleArea(x1, y1, x2, y2) + -- query a bounding-box aligned area for colliders + --[[ + inputs: + x1, y1, x2, y2: floats, the x and y coordinates of two points + outputs: + colls: table, all colliders in bounding box + --]] + + local colls = {} + local callback = function(fixture) + table.insert(colls, fixture:getUserData()) + return true + end + self:queryBoundingBox(x1, y1, x2, y2, callback) + return colls +end + +local function check_vertices(vertices) + if #vertices % 2 ~= 0 then + error('vertices must be a multiple of 2') + elseif #vertices < 4 then + error('must have at least 2 vertices with x and y each') + end +end + +local function query_region(world, coll_type, args) + local collider = world:newCollider(coll_type, args) + collider:setSensor(true) + world:update(0) + local colls = collider:collider_contacts(collider) + collider:destroy() + return colls +end + +function World:queryPolygonArea(...) + -- query an area enclosed by the lines connecting a series of points + --[[ + inputs: + x1, y1, x2, y2, ... floats, the x and y positions defining polygon + outputs: + colls: table, all Colliders intersecting the area + --]] + local vertices = {...} + if type(vertices[1]) == 'table' then + vertices = vertices[1] + end + check_vertices(vertices) + return query_region(self, 'Polygon', vertices) +end + +function World:queryCircleArea(x, y, r) + -- get all colliders in a circle are + --[[ + inputs: + x, y, r: floats, x, y and radius of circle + outputs: + colls: table: colliders in area + ]]-- + return query_region(self, 'Circle', {x, y, r}) +end + +function World:queryEdgeArea(...) + -- get all colliders along a (series of) line(s) + --[[ + inputs: + x1, y1, x2, y2, ... floats, the x and y positions defining lines + outpts: + colls: table: colliders intersecting these lines + --]] + local vertices = {...} + if type(vertices[1]) == 'table' then + vertices = vertices[1] + end + check_vertices(vertices) + return query_region(self, 'Edge', vertices) +end + + +function World:update(dt) + -- update physics world + self._world:update(dt) + for i, v in pairs(self.collide_events) do + v() + self.collide_events[i] = nil + end +end + +--[[ +create a new collider in this world + +args: + collider_type (string): the type of the collider (not case seinsitive). any of: + circle, rectangle, polygon, edge, chain. + shape_arguments (table): arguments required to instantiate shape. + circle: {x, y, radius} + rectangle: {x, y, width height} + polygon/edge/chain: {x1, y1, x2, y2, ...} + table_to_use (optional, table): table to generate as the collider +]]-- +function World:newCollider(collider_type, shape_arguments, table_to_use) + + local o = table_to_use or {} + setmetatable(o, Collider) + -- note that you will need to set static vs dynamic later + local _collider_type = COLLIDER_TYPES[collider_type:upper()] + assert(_collider_type ~= nil, "unknown collider type: "..collider_type) + collider_type = _collider_type + if collider_type == 'Circle' then + local x, y, r = unpack(shape_arguments) + o.body = lp.newBody(self._world, x, y, "dynamic") + o.shape = lp.newCircleShape(r) + elseif collider_type == "Rectangle" then + local x, y, w, h = unpack(shape_arguments) + o.body = lp.newBody(self._world, x, y, "dynamic") + o.shape = lp.newRectangleShape(w, h) + collider_type = "Polygon" + else + o.body = lp.newBody(self._world, 0, 0, "dynamic") + o.shape = lp['new'..collider_type..'Shape'](unpack(shape_arguments)) + end + + o.collider_type = collider_type + + o.fixture = lp.newFixture(o.body, o.shape, 1) + o.fixture:setUserData(o) + + set_funcs(o, o.body) + set_funcs(o, o.shape) + set_funcs(o, o.fixture) + + -- index by self for now + o._world = self + self.colliders[o] = o + return o +end + +return World