Cache for collision checks

This commit is contained in:
Pyrex 2022-12-19 21:36:11 -08:00
parent f67b617a78
commit 27692ba208

View File

@ -112,6 +112,73 @@ function _mnmx(x,y)
return x,y
end
function _rastn(
x0,y0,x1,y1,dx,dy
)
-- todo: more optimized implementation?
local iter=_rast(x0,y0,x1,y1)
local prevx,prevy=nil,nil
local done=false
return function()
while not done do
local x,y=iter()
if (x==nil) done=true return x1, y1
local x8 = x\dx
local y8 = y\dy
if not (x8==prevx and y8==prevy) then
prevx,prevy=x8,y8
return x,y
end
end
end
end
function _rast(
x0,y0,x1,y1
)
local dx=abs(x1-x0)
local dy=abs(y1-y0)
local x=x0
local y=y0
local sx=-1
local sy=-1
if (x0<x1) sx=1
if (y0<y1) sy=1
local done=false,err
if dx>dy then
err=dx/2.0
return function()
if (done) return
if (x==x1) done=true return x1,y1
local oldx,oldy=x,y
err-=dy
if (err<0) y+=sy err+=dx+dy return oldx,y
x+=sx
return oldx,oldy
end
else
err=dy/2.0
return function()
if (done) return
if (y==y1) done=true return x1,y1
local oldx,oldy=x,y
err-=dx
if (err<0) x+=sx err+=dy+dx return x,oldy
y+=sy
return oldx,oldy
end
end
end
function _point_eq(p1,p2)
return p1.x==p2.x and p1.y==p2.y
end
-->8
-- input
kbd={}
@ -222,6 +289,7 @@ function level:reinit(n)
self.todo={}
self.bigx=(n%8)
self.bigy=(n\8)
self.cache_can_stretch=dcache:new()
self:load_dynobjs()
self:recollide()
@ -335,6 +403,7 @@ function level:recollide()
self._crates[mxy]!=nil
end
end
self.cache_can_stretch:clear()
end
function add_adjacent_anchors(tbl,mx,my)
@ -588,6 +657,75 @@ function level:tug_crate(mx0,my0,dmx,dmy)
self:recollide()
self:reanchor(false)
end
-->8
-- collision checks
function level:can_stretch(
p1,p2
)
local key=p1.x..","..p1.y..","..p2.x..","..p2.y
return self.cache_can_stretch:wrap(key,function()
return self:_can_stretch(p1,p2)
end)
end
function level:_can_stretch(
p1,p2
)
-- faster implementation for straight lines
if p1.y\8==p2.y\8 then
local my=p2.y\8
for mx=p1.x\8,p2.x\8 do
if (level:mcoll(mx,my)) return false
end
end
if p1.x\8==p2.x\8 then
local mx=p2.x\8
for my=p1.y\8,p2.y\8 do
if (level:mcoll(mx,my)) return false
end
end
if (level:pcoll(p1.x,p1.y)) return false
if (level:pcoll(p2.x,p2.y)) return false
local res=true
for x,y in _rastn(p1.x,p1.y,p2.x,p2.y,8,8) do
if level:pcoll(x,y) then
res=false
break
end
end
return res
end
-->8
--cache impl for collision checks
dcache={}
dcache.__index=dcache
function dcache:new()
local d={}
setmetatable(d,dcache)
d:clear()
return d
end
function dcache:clear()
self.old,self.new,self.new_n={},{},0
end
function dcache:wrap(key,f)
local el=self.new[key]
if (el!=nil) return el
local el=self.old[key]
if (el==nil) el=f()
self.new[key]=el
self.new_n+=1
if (self.new_n>1000) self.old,self.new,self.new_n=self.new,{},0
return el
end
-->8
--player handling
player={}
@ -905,7 +1043,7 @@ function rope:continue_cast()
local x1=x0+dx
local y1=y0+dy
for x,y in self:_rast(
for x,y in _rast(
x0,y0,x1,y1
) do
local latch=
@ -986,14 +1124,14 @@ function rope:draw(artificial_dx,artificial_dy)
end
end
--[[
for i=0,#self.ancs+1 do
p=self:_anc(i)
local c=12
if (p.dirty) c=13
rectfill(p.x-1,p.y-1,p.x+1,p.y+1,c)
print(tostr(p.id)..":"..p.x..","..p.y..","..#p.todo,0,i*8,9)
print(tostr(p.id)..":"..p.x..","..p.y..","..#p.todo,0,i*8,12)
end
--[[
for _,p in pairs(level._anch) do
pset(p.x,p.y,11)
end
@ -1029,7 +1167,7 @@ function rope:drag(
local anc=self:_anc(i())
local busy=self:busy()
for x,y in self:_rast(
for x,y in _rast(
anc.x,anc.y,x,y
) do
local a=self:_anc(i())
@ -1038,8 +1176,6 @@ function rope:drag(
a.y=y
a.dirty=true
self.dirty=true
--self:_find_needed_anchors(i())
--self:_find_needed_anchors(i()+1)
if (not busy) self:_tidy_up_gen()
end
end
@ -1050,7 +1186,7 @@ function rope:make_dirty(only_if_invalid)
for a=0,#self.ancs do
local a0=self:_anc(a)
local a1=self:_anc(a+1)
if not self:_can_stretch(a0,a1) then
if not level:can_stretch(a0,a1) then
a0.dirty=true
a1.dirty=true
invalid=true
@ -1075,6 +1211,7 @@ function rope:_tidy_up_gen()
anc.seen=true
if self[f](self,a,busy) then
settled=false anc.changed=true
a=0
end
end
a+=1
@ -1127,7 +1264,7 @@ function rope:_tidy_up_gen()
for i=0,#self.ancs do
local a0=self:_anc(i)
local a1=self:_anc(i+1)
if not self:_can_stretch(a0,a1) then
if not level:can_stretch(a0,a1) then
self:destroy()
end
end
@ -1144,7 +1281,7 @@ function rope:_find_needed_anchors(i,busy)
if (level:pcoll(a2.x,a2.y)) return false
if (level:pcoll(a0.x,a0.y)) return false
if (self:_can_stretch(a0,a2)) return false
if (level:can_stretch(a0,a2)) return false
local anchors_bydist={}
local x0,x2=_mnmx(a0.x,a2.x)
@ -1156,8 +1293,8 @@ function rope:_find_needed_anchors(i,busy)
for a1 in all(anchors_bydist) do
a1=a1.el
if self:_can_stretch(a0,a1) and
self:_can_stretch(a1,a2)
if level:can_stretch(a0,a1) and
level:can_stretch(a1,a2)
then
local id=self.id
add(self.ancs,{id=id,x=a1.x,y=a1.y,dirty=true,todo={}},i)
@ -1179,12 +1316,11 @@ function rope:_find_touched_anchors(i)
if (level:pcoll(a0.x,a0.y)) return false
if (level:pcoll(a2.x,a2.y)) return false
for bx,by in self:_rast(a0.x,a0.y,a2.x,a2.y) do
for bx,by in _rast(a0.x,a0.y,a2.x,a2.y) do
local a1=level:point_anchor(bx,by)
if
a1!=nil and not _point_eq(a0,a1) and not _point_eq(a1,a2)
and _linedist(a0,a1,a2)<ELIDE_POINT
-- and self:_can_stretch(p,a2)
then
local id=self.id
add(self.ancs,{id=id,x=a1.x,y=a1.y,dirty=true,todo={}},i)
@ -1212,7 +1348,7 @@ function rope:_elide_point(i,busy)
return false
end
if not self:_can_stretch(a0,a2) then
if not level:can_stretch(a0,a2) then
return false
end
@ -1233,26 +1369,21 @@ function rope:_elide_point(i,busy)
end
function rope:_can_move_midpoint(a0,a1_0,a1_1,a2)
if (level:pcoll(a0.x,a0.y)) return false
if (level:pcoll(a2.x,a2.y)) return false
if (level:pcoll(a1_0.x,a1_0.y)) return false
if (level:pcoll(a1_1.x,a1_1.y)) return false
if not self:_can_stretch(a1_0, a1_1) then
if not level:can_stretch(a1_0, a1_1) then
return false
end
if not self:_can_stretch(a0,a1_1) then
if not level:can_stretch(a0,a1_1) then
return false
end
if not self:_can_stretch(a1_1,a2) then
if not level:can_stretch(a1_1,a2) then
return false
end
for x,y in self:_rastn(a1_0.x,a1_0.y,a1_1.x,a1_1.y,8,8) do
for x,y in _rastn(a1_0.x,a1_0.y,a1_1.x,a1_1.y,8,8) do
local tm={x=x,y=y}
if not self:_can_stretch(a0,tm) then
if not level:can_stretch(a0,tm) then
return false
end
if not self:_can_stretch(tm,a2) then
if not level:can_stretch(tm,a2) then
return false
end
end
@ -1304,105 +1435,6 @@ function _line_line(x1,y1,x2,y2,x3,y3,x4,y4)
return true
end
function rope:_can_stretch(
p1,p2
)
-- faster implementation for straight lines
if p1.y\8==p2.y\8 then
local my=p2.y\8
for mx=p1.x\8,p2.x\8 do
if (level:mcoll(mx,my)) return false
end
end
if p1.x\8==p2.x\8 then
local mx=p2.x\8
for my=p1.y\8,p2.y\8 do
if (level:mcoll(mx,my)) return false
end
end
if (level:pcoll(p1.x,p1.y)) return false
if (level:pcoll(p2.x,p2.y)) return false
local res=true
for x,y in self:_rastn(p1.x,p1.y,p2.x,p2.y,8,8) do
if level:pcoll(x,y) then
res=false
break
end
end
return res
end
function rope:_rastn(
x0,y0,x1,y1,dx,dy
)
-- todo: more optimized implementation?
local iter=self:_rast(x0,y0,x1,y1)
local prevx,prevy=nil,nil
local done=false
return function()
while not done do
local x,y=iter()
if (x==nil) done=true return x1, y1
local x8 = x\dx
local y8 = y\dy
if not (x8==prevx and y8==prevy) then
prevx,prevy=x8,y8
return x,y
end
end
end
end
function rope:_rast(
x0,y0,x1,y1
)
local dx=abs(x1-x0)
local dy=abs(y1-y0)
local x=x0
local y=y0
local sx=-1
local sy=-1
if (x0<x1) sx=1
if (y0<y1) sy=1
local done=false,err
if dx>dy then
err=dx/2.0
return function()
if (done) return
if (x==x1) done=true return x1,y1
local oldx,oldy=x,y
err-=dy
if (err<0) y+=sy err+=dx+dy return oldx,y
x+=sx
return oldx,oldy
end
else
err=dy/2.0
return function()
if (done) return
if (y==y1) done=true return x1,y1
local oldx,oldy=x,y
err-=dx
if (err<0) x+=sx err+=dy+dx return x,oldy
y+=sy
return oldx,oldy
end
end
end
function _point_eq(p1,p2)
return p1.x==p2.x and p1.y==p2.y
end
function neighbors(p)
local r={}
for dx=-1,1,1 do