aboutsummaryrefslogtreecommitdiff
path: root/src/phys.lua
diff options
context:
space:
mode:
authoriamcheeseman <[email protected]>2026-03-11 13:19:11 -0400
committeriamcheeseman <[email protected]>2026-03-11 13:19:11 -0400
commit8121de73db8acfe5264fd6e0218dc5413ffac95d (patch)
tree2308f65d361c4ffcc365d7a16ff458ed602efc22 /src/phys.lua
parent9e7d27e8d43617955c8a1213b392d98e3932f7a3 (diff)
basic AABB collision resolution
Diffstat (limited to 'src/phys.lua')
-rw-r--r--src/phys.lua163
1 files changed, 163 insertions, 0 deletions
diff --git a/src/phys.lua b/src/phys.lua
new file mode 100644
index 0000000..a57bccd
--- /dev/null
+++ b/src/phys.lua
@@ -0,0 +1,163 @@
+phys = {}
+
+local world = {}
+phys.world = world
+
+world.boxes = {}
+world.layers = {}
+
+-- There are a few primary physics layers:
+--
+-- * Hard: for hard collisions that do not allow things in whatsoever
+-- * Soft: for collisions that push other things away over time
+-- * Player_Hurtbox: collision box that hurts the player
+-- * Player_Hitbox: collision box from the player that damages things
+-- * Enemy_Hurtbox: collision box that hurts enemies
+-- * Enemy_Hitbox: collision box from an enemy that damages things
+--
+-- You can use whatever physics layer you want, but these are the standard ones
+
+local function aabb_x(ax, aw, bx, bw)
+ return ax < bx + bw and bx < ax + aw
+end
+
+local function aabb_y(ay, ah, by, bh)
+ return ay < by + bh and by < ay + ah
+end
+
+local function aabb(ax, ay, aw, ah, bx, by, bw, bh)
+ return
+ ax < bx + bw and bx < ax + aw and
+ ay < by + bh and by < ay + ah
+end
+
+local function add_box_to_layers(box)
+ for _, layer in ipairs(box.layers) do
+ if not world.layers[layer] then
+ world.layers[layer] = {}
+ end
+ table.insert(world.layers[layer], box)
+ end
+end
+
+phys.Box = {}
+phys.Box.__index = phys.Box
+
+function phys.Box.new(
+ x, y,
+ width, height,
+ opts
+)
+ opts = opts or {}
+
+ local self = setmetatable({}, phys.Box)
+
+ self.x = x
+ self.y = y
+
+ self.offsetx = opts.offsetx or 0
+ self.offsety = opts.offsety or 0
+
+ self.width = width
+ self.height = height
+
+ self.layers = opts.layers or {"hard"}
+ self.mask = opts.mask or {}
+
+ self.touching_down = false
+ self.touching_up = false
+ self.touching_vertically = false
+ self.touching_left = false
+ self.touching_right = false
+ self.touching_horizontally = false
+
+ add_box_to_layers(self)
+
+ return self
+end
+
+function phys.Box:get_rect()
+ return
+ self.x + self.offsetx,
+ self.y + self.offsety,
+ self.width,
+ self.height
+end
+
+function phys.Box:update(velx, vely, dt)
+ local resx = self.x + velx * dt
+ local resy = self.y + vely * dt
+
+ local small_side = math.min(self.width, self.height)
+ local steps = math.ceil(vec_len(velx * dt, vely * dt) / small_side)
+
+ self.touching_down = false
+ self.touching_up = false
+ self.touching_vertically = false
+ self.touching_left = false
+ self.touching_right = false
+ self.touching_horizontally = false
+
+ -- this block is fat. much like a certain croatian...
+ for i=1, steps do
+ local p = i / steps
+
+ local boxx = resx + self.offsetx + velx * dt * p
+ local boxy = resy + self.offsety + vely * dt * p
+
+ for _, layer in ipairs(self.mask) do
+ for _, other in ipairs(world.layers[layer] or {}) do
+ if other ~= self then
+ local ox = other.x + other.offsetx
+ local oy = other.y + other.offsety
+
+ if aabb(
+ boxx, boxy, self.width, self.height,
+ ox, oy, other.width, other.height
+ ) then
+ if aabb_x(self.x + self.offsetx, self.width, ox, other.width) then
+ if vely > 0 then
+ resy = oy - self.height - self.offsety
+ self.touching_down = true
+ else
+ resy = oy + other.height - self.offsety
+ self.touching_up = true
+ end
+ self.touching_vertically = true
+ elseif aabb_y(self.y + self.offsety, self.height, oy, other.height) then
+ if velx > 0 then
+ resx = ox - self.width - self.offsetx
+ self.touching_left = true
+ else
+ resx = ox + other.width - self.offsetx
+ self.touching_right = true
+ end
+ self.touching_horizontally = true
+ end
+ end
+ end
+ end
+ end
+
+ if self.touching_horizontally then
+ velx = 0
+ end
+ if self.touching_vertically then
+ vely = 0
+ end
+
+ if self.touching_horizontally or self.touching_vertically then
+ break
+ end
+ end
+
+ self.x = resx
+ self.y = resy
+
+ return velx, vely
+end
+
+function phys.Box:draw()
+ local x, y, w, h = self:get_rect()
+ love.graphics.rectangle("line", x, y, w, h)
+end