1
0
dodge-bolt/src/systems/physics.lua

177 lines
4.5 KiB
Lua

local Physics = {}
local bump = require("lib.bump")
local pprint = require("lib.pprint")
local Vec = require("lib.brinevector")
-- TODO: Tweak bump world `cellSize` at runtime, when the map switches
Physics.required_groups = {"physical", "collider"}
function Physics:init()
self.bump = bump.newWorld()
self.boundCollisionFilter = function(...)
return self:collisionFilter(...)
end
end
function Physics:onMapSwitch(map)
map:bump_init(self.bump)
end
local function getColliderBounds(e)
local x = e.collider[1]
local y = e.collider[2]
local w = math.abs(e.collider[3] - e.collider[1])
local h = math.abs(e.collider[4] - e.collider[2])
if e.pos then
x = x + e.pos.x
y = y + e.pos.y
end
return x, y, w, h
end
function Physics:addToGroup(group, e)
if group == "collider" then
self.bump:add(e, getColliderBounds(e))
end
end
function Physics:removeFromGroup(group, e)
if group == "collider" then
self.bump:remove(e)
end
end
function Physics:collisionFilter(entity, other)
if entity.hidden then return end
if other.hidden then return end
local bolt = entity.bolt or other.bolt
local vel = entity.vel or other.vel
local player = entity.bolts or other.bolts
if bolt and player then
return "cross"
elseif bolt then
return "bounce"
elseif (vel or vel) and not (entity.bolts and other.bolts) then
return "slide"
end
end
function Physics:resolveCollisions(e, dt)
local targetPos = e.pos + e.vel * dt
local ox = e.collider[1]
local oy = e.collider[2]
self.bump:update(e, e.pos.x+ox, e.pos.y+oy)
local x, y, cols = self.bump:move(e, targetPos.x+ox, targetPos.y+oy, self.boundCollisionFilter)
local skip_moving = false
if #cols > 0 then
e.pos.x = x - ox
e.pos.y = y - oy
skip_moving = true
end
for _, col in ipairs(cols) do
if col.type == "cross" then
local player = col.other.bolts and col.other or e
local bolt = col.other.bolt and col.other or e
if player and bolt and bolt.owner ~= player then
self.pool:emit("hitPlayer", player, bolt)
end
elseif col.type == "bounce" then
-- sx and sy and just number for flipped the direction when something
-- bounces.
-- When `normal.x` is zero, sx = 1, otherwise sx = -1. Same with sy
local sx = 1 - 2*math.abs(col.normal.x)
local sy = 1 - 2*math.abs(col.normal.y)
e.vel.x = e.vel.x * sx
e.vel.y = e.vel.y * sy
if e.acc then
e.acc.x = e.acc.x * sx
e.acc.y = e.acc.y * sy
end
skip_moving = true
end
end
return skip_moving
end
function Physics:update(dt)
for _, e in ipairs(self.pool.groups.physical.entities) do
if e.acc then
e.vel = e.vel + e.acc * dt
end
if e.friction then
e.vel = e.vel * (1 - math.min(e.friction, 1)) ^ dt
end
local skip_moving = false
if self.pool.groups.collider.hasEntity[e] then
skip_moving = self:resolveCollisions(e, dt)
end
if not skip_moving then
if e.max_speed then
e.pos = e.pos + e.vel:trim(e.max_speed) * dt
else
e.pos = e.pos + e.vel * dt
end
end
end
end
function Physics:castRay(from, to)
local items = self.bump:querySegmentWithCoords(from.x, from.y, to.x, to.y, function(item)
return not item.vel
end)
for _, item in ipairs(items) do
if item.nx ~= 0 or item.ny ~= 0 then
local pos = Vec(item.x1, item.y1)
local normal = Vec(item.nx, item.ny)
return pos, normal
end
end
end
function Physics:project(from, direction, distance)
local key_points = {}
table.insert(key_points, from)
local distance_traveled = 0
local position = from
-- While the ray hasen't traveled the required amount
while math.abs(distance_traveled - distance) > 0.01 do
-- Check if it collides with anything
local destination = position + direction * (distance-distance_traveled)
local intersect, normal = self:castRay(position, destination)
local new_position = intersect or destination
-- Update how much the ray has moved in total
distance_traveled = distance_traveled + (position - new_position).length
position = new_position
-- If it collided change its travel direction
if normal then
-- sx and sy and just number for flipped the direction when something
-- bounces.
-- When `normal.x` is zero, sx = 1, otherwise sx = -1. Same with sy
direction.x = direction.x * (1 - 2*math.abs(normal.x))
direction.y = direction.y * (1 - 2*math.abs(normal.y))
end
-- Save this point where it stopped/collided
table.insert(key_points, new_position)
end
return key_points
end
function Physics:queryRect(pos, size, filter)
return self.bump:queryRect(pos.x, pos.y, size.x, size.y, filter)
end
return Physics