aboutsummaryrefslogtreecommitdiff
path: root/libs/sti/plugins/box2d.lua
diff options
context:
space:
mode:
Diffstat (limited to 'libs/sti/plugins/box2d.lua')
-rw-r--r--libs/sti/plugins/box2d.lua323
1 files changed, 323 insertions, 0 deletions
diff --git a/libs/sti/plugins/box2d.lua b/libs/sti/plugins/box2d.lua
new file mode 100644
index 0000000..c6d4148
--- /dev/null
+++ b/libs/sti/plugins/box2d.lua
@@ -0,0 +1,323 @@
+--- Box2D plugin for STI
+-- @module box2d
+-- @author Landon Manning
+-- @copyright 2019
+-- @license MIT/X11
+
+local love = _G.love
+local utils = require((...):gsub('plugins.box2d', 'utils'))
+local lg = require((...):gsub('plugins.box2d', 'graphics'))
+
+return {
+ box2d_LICENSE = "MIT/X11",
+ box2d_URL = "https://github.com/karai17/Simple-Tiled-Implementation",
+ box2d_VERSION = "2.3.2.7",
+ box2d_DESCRIPTION = "Box2D hooks for STI.",
+
+ --- Initialize Box2D physics world.
+ -- @param world The Box2D world to add objects to.
+ box2d_init = function(map, world)
+ assert(love.physics, "To use the Box2D plugin, please enable the love.physics module.")
+
+ local body = love.physics.newBody(world, map.offsetx, map.offsety)
+ local collision = {
+ body = body,
+ }
+
+ local function addObjectToWorld(objshape, vertices, userdata, object)
+ local shape
+
+ if objshape == "polyline" then
+ if #vertices == 4 then
+ shape = love.physics.newEdgeShape(unpack(vertices))
+ else
+ shape = love.physics.newChainShape(false, unpack(vertices))
+ end
+ else
+ shape = love.physics.newPolygonShape(unpack(vertices))
+ end
+
+ local currentBody = body
+ --dynamic are objects/players etc.
+ if userdata.properties.dynamic == true then
+ currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'dynamic')
+ -- static means it shouldn't move. Things like walls/ground.
+ elseif userdata.properties.static == true then
+ 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
+ elseif userdata.properties.kinematic == true then
+ currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'kinematic')
+ end
+
+ local fixture = love.physics.newFixture(currentBody, shape)
+ fixture:setUserData(userdata)
+
+ -- Set some custom properties from userdata (or use default set by box2d)
+ fixture:setFriction(userdata.properties.friction or 0.2)
+ fixture:setRestitution(userdata.properties.restitution or 0.0)
+ fixture:setSensor(userdata.properties.sensor or false)
+ fixture:setFilterData(
+ userdata.properties.categories or 1,
+ userdata.properties.mask or 65535,
+ userdata.properties.group or 0
+ )
+
+ local obj = {
+ object = object,
+ body = currentBody,
+ shape = shape,
+ fixture = fixture,
+ }
+
+ table.insert(collision, obj)
+ end
+
+ local function getPolygonVertices(object)
+ local vertices = {}
+ for _, vertex in ipairs(object.polygon) do
+ table.insert(vertices, vertex.x)
+ table.insert(vertices, vertex.y)
+ end
+
+ return vertices
+ end
+
+ local function calculateObjectPosition(object, tile)
+ local o = {
+ shape = object.shape,
+ x = (object.dx or object.x) + map.offsetx,
+ y = (object.dy or object.y) + map.offsety,
+ w = object.width,
+ h = object.height,
+ polygon = object.polygon or object.polyline or object.ellipse or object.rectangle
+ }
+
+ local userdata = {
+ object = o,
+ properties = object.properties
+ }
+
+ o.r = object.rotation or 0
+ if o.shape == "rectangle" then
+ local cos = math.cos(math.rad(o.r))
+ local sin = math.sin(math.rad(o.r))
+ local oy = 0
+
+ if object.gid then
+ local tileset = map.tilesets[map.tiles[object.gid].tileset]
+ local lid = object.gid - tileset.firstgid
+ local t = {}
+
+ -- This fixes a height issue
+ o.y = o.y + map.tiles[object.gid].offset.y
+ oy = o.h
+
+ for _, tt in ipairs(tileset.tiles) do
+ if tt.id == lid then
+ t = tt
+ break
+ end
+ end
+
+ if t.objectGroup then
+ for _, obj in ipairs(t.objectGroup.objects) do
+ -- Every object in the tile
+ calculateObjectPosition(obj, object)
+ end
+
+ return
+ else
+ o.w = map.tiles[object.gid].width
+ o.h = map.tiles[object.gid].height
+ end
+ end
+
+ o.polygon = {
+ { 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+o.h },
+ { x=o.x+0, y=o.y+o.h }
+ }
+
+ for _, vertex in ipairs(o.polygon) do
+ vertex.x, vertex.y = utils.rotate_vertex(map, vertex, o.x, o.y, cos, sin, oy)
+ end
+
+ local vertices = getPolygonVertices(o)
+ addObjectToWorld(o.shape, vertices, userdata, tile or object)
+ elseif o.shape == "ellipse" then
+ if not o.polygon then
+ o.polygon = utils.convert_ellipse_to_polygon(o.x, o.y, o.w, o.h)
+ end
+ local vertices = getPolygonVertices(o)
+ local triangles = love.math.triangulate(vertices)
+
+ for _, triangle in ipairs(triangles) do
+ addObjectToWorld(o.shape, triangle, userdata, tile or object)
+ end
+ elseif o.shape == "polygon" then
+ -- Recalculate collision polygons inside tiles
+ if tile then
+ local cos = math.cos(math.rad(o.r))
+ local sin = math.sin(math.rad(o.r))
+ for _, vertex in ipairs(o.polygon) do
+ vertex.x = vertex.x + o.x
+ vertex.y = vertex.y + o.y
+ vertex.x, vertex.y = utils.rotate_vertex(map, vertex, o.x, o.y, cos, sin)
+ end
+ end
+
+ local vertices = getPolygonVertices(o)
+ local triangles = love.math.triangulate(vertices)
+
+ for _, triangle in ipairs(triangles) do
+ addObjectToWorld(o.shape, triangle, userdata, tile or object)
+ end
+ elseif o.shape == "polyline" then
+ local vertices = getPolygonVertices(o)
+ addObjectToWorld(o.shape, vertices, userdata, tile or object)
+ end
+ end
+
+ for _, tile in pairs(map.tiles) do
+ if map.tileInstances[tile.gid] then
+ for _, instance in ipairs(map.tileInstances[tile.gid]) do
+ -- Every object in every instance of a tile
+ if tile.objectGroup then
+ for _, object in ipairs(tile.objectGroup.objects) do
+ if object.properties.collidable == true then
+ object = utils.deepCopy(object)
+ object.dx = instance.x + object.x
+ object.dy = instance.y + object.y
+ calculateObjectPosition(object, instance)
+ end
+ end
+ end
+
+ -- Every instance of a tile
+ if tile.properties.collidable == true then
+ local object = {
+ shape = "rectangle",
+ x = instance.x,
+ y = instance.y,
+ width = map.tilewidth,
+ height = map.tileheight,
+ properties = tile.properties
+ }
+
+ calculateObjectPosition(object, instance)
+ end
+ end
+ end
+ end
+
+ for _, layer in ipairs(map.layers) do
+ -- Entire layer
+ if layer.properties.collidable == true then
+ if layer.type == "tilelayer" then
+ for gid, tiles in pairs(map.tileInstances) do
+ local tile = map.tiles[gid]
+ local tileset = map.tilesets[tile.tileset]
+
+ for _, instance in ipairs(tiles) do
+ if instance.layer == layer then
+ local object = {
+ shape = "rectangle",
+ x = instance.x,
+ y = instance.y,
+ width = tileset.tilewidth,
+ height = tileset.tileheight,
+ properties = tile.properties
+ }
+
+ calculateObjectPosition(object, instance)
+ end
+ end
+ end
+ elseif layer.type == "objectgroup" then
+ for _, object in ipairs(layer.objects) do
+ calculateObjectPosition(object)
+ end
+ elseif layer.type == "imagelayer" then
+ local object = {
+ shape = "rectangle",
+ x = layer.x or 0,
+ y = layer.y or 0,
+ width = layer.width,
+ height = layer.height,
+ properties = layer.properties
+ }
+
+ calculateObjectPosition(object)
+ end
+ end
+
+ -- Individual objects
+ if layer.type == "objectgroup" then
+ for _, object in ipairs(layer.objects) do
+ if object.properties.collidable == true then
+ calculateObjectPosition(object)
+ end
+ end
+ end
+ end
+
+ map.box2d_collision = collision
+ end,
+
+ --- Remove Box2D fixtures and shapes from world.
+ -- @param index The index or name of the layer being removed
+ box2d_removeLayer = function(map, index)
+ local layer = assert(map.layers[index], "Layer not found: " .. index)
+ local collision = map.box2d_collision
+
+ -- Remove collision objects
+ for i = #collision, 1, -1 do
+ local obj = collision[i]
+
+ if obj.object.layer == layer then
+ obj.fixture:destroy()
+ table.remove(collision, i)
+ end
+ end
+ end,
+
+ --- Draw Box2D physics world.
+ -- @param tx Translate on X
+ -- @param ty Translate on Y
+ -- @param sx Scale on X
+ -- @param sy Scale on Y
+ box2d_draw = function(map, tx, ty, sx, sy)
+ local collision = map.box2d_collision
+
+ lg.push()
+ lg.scale(sx or 1, sy or sx or 1)
+ lg.translate(math.floor(tx or 0), math.floor(ty or 0))
+
+ for _, obj in ipairs(collision) do
+ local points = {obj.body:getWorldPoints(obj.shape:getPoints())}
+ local shape_type = obj.shape:getType()
+
+ if shape_type == "edge" or shape_type == "chain" then
+ love.graphics.line(points)
+ elseif shape_type == "polygon" then
+ love.graphics.polygon("line", points)
+ else
+ error("sti box2d plugin does not support "..shape_type.." shapes")
+ end
+ end
+
+ lg.pop()
+ end
+}
+
+--- Custom Properties in Tiled are used to tell this plugin what to do.
+-- @table Properties
+-- @field collidable set to true, can be used on any Layer, Tile, or Object
+-- @field sensor set to true, can be used on any Tile or Object that is also collidable
+-- @field dynamic set to true, can be used on any Tile or Object
+-- @field friction can be used to define the friction of any Object
+-- @field restitution can be used to define the restitution of any Object
+-- @field categories can be used to set the filter Category of any Object
+-- @field mask can be used to set the filter Mask of any Object
+-- @field group can be used to set the filter Group of any Object