177 lines
4.5 KiB
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
|