Rope rewrite, part one

This commit is contained in:
Pyrex 2022-12-19 23:15:55 -08:00
parent 27692ba208
commit f3df6c674a

View File

@ -415,35 +415,27 @@ function add_adjacent_anchors(tbl,mx,my)
end
end
function level:reanchor(remove)
if remove or not self._anch then
function level:reanchor()
self._anch={}
end
for ax0=0,31 do
local ax1 = ax0-1+2*(ax0%2)
local mx0,mx1 = ax0\2,ax1\2
for ay0=0,31 do
local ay1=ay0-1+2*(ay0%2)
local my0,my1=ay0\2,ay1\2
for dxy in all{{-1,-1},{1,-1},{-1,1},{1,1}} do
local dx,dy=unpack(dxy)
assert(dx!=0 and dy!=0)
for mx0=0,15 do
for my0=0,15 do
local mx1,my1=mx0+dx,my0+dy
if (
not self:mcoll(mx0,my0) and
self:mcoll(mx0,my0) and
not self:mcoll(mx0,my1) and
not self:mcoll(mx1,my0) and
self:mcoll(mx1,my1)
not self:mcoll(mx1,my1)
) then
local px0,py0=level:a2p(ax0,ay0)
self._anch[_amix(ax0,ay0)]={ax=ax0,ay=ay0,x=px0,y=py0}
add(self._anch, {
ax=max(mx0,mx1),ay=max(my0,my1),adx=-dx,ady=-dy
})
end
end
end
for _,cr in pairs(self._crates) do
add_adjacent_anchors(self._anch,cr.mx,cr.my)
end
if (player.rope!=nil) player.rope:make_dirty()
end
function level:win_at(mx,my)
@ -453,20 +445,6 @@ function level:anchor_points()
return pairs(self._anch)
end
function level:anchors_in(px0,py0,px1,py1)
local anch,ax,xlim,sy,ylim=self._anch,px0\4,(px1+3)\4,py0\4,(py1+3)\4
local ay=sy-1
return function()
while true do
ay+=1
if(ay>ylim)ay,ax=sy,ax+1
if(ax>xlim)return nil
local anc=anch[_amix(ax,ay)]
if (anc) return anc
end
end
end
function level:get_open_pit(mx,my)
local pit=self._pits[_mix(mx,my)]
if (pit and pit.contents==nil) return pit
@ -574,8 +552,8 @@ function level:get_latch(dx,dy,px,py)
return {
el="crate",
dx=dx1,dy=dy1,
px_offset=px-crate.px+dx1,
py_offset=py-crate.py+dy1,
ax_offset=dx1*0.5,
ay_offset=dy1*0.5,
rec=crate
}
end
@ -758,7 +736,6 @@ end
function player:any_busy()
if (#self.todo>0) return true
if (level:busy()) return true
if (self.rope!=nil and self.rope:busy()) return true
return false
end
@ -826,10 +803,17 @@ function player:update()
self.todo=f4({{orienty=1,py=2},{py=7},{y=self.y+1}})
else wrongbleep:bleep() end
elseif self.rope==nil and kbd:btnr(4) then
local rx,ry,rx2,ry2=self:_rope_pos()
local dx,dy=12*self.orientx,12*self.orienty
local dx,dy=self.orientx,self.orienty
if (dy!=0) dx=0
self.rope=rope:new(rx,ry,rx2,ry2,dx,dy)
local x,y=self.x,self.y
while not level:mcoll(x,y) do x+=dx y+=dy end
self.rope=rope:new(
x+0.5-dx*0.5,y+0.5-dy*0.5,
self.x+0.5,self.y+0.5,
level:get_latch(dx,dy,x*8,y*8)
)
self.todo={{
update=function()
@ -848,8 +832,7 @@ function player:update()
if self.rope then
self.rope:update()
local rx,ry=self:_rope_pos()
self.rope:drag_dst(rx,ry)
self.rope:drag_dst(self.x+0.5,self.y+0.5)
local tdx,tdy=self.rope:tug_orientxy()
if (tdx!=0) self.orientx=tdx
@ -881,14 +864,6 @@ function player:_fall()
if (self.fall_frame<10) self.fall_frame+=1
end
function player:_rope_pos()
local px=self.x*8+self.px+4
local px2=px
local py=self.y*8+self.py+3
local py2=py
return px,py,px2,py2
end
function player:draw()
local px=self.x*8+self.px
local py=self.y*8+self.py
@ -956,18 +931,21 @@ rope={}
rope.__index=rope
function rope:new(
x,y,src_x,src_y,dx,dy
src_ax,src_ay,dst_ax,dst_ay,latch
)
local r={
id=0,
src={x=src_x,y=src_y,todo={}},
ancs={},
dst={x=x,y=y,todo={}},
state={name="cast",dx=dx,dy=dy},
dirty=true,
latch=nil,
latch_frame=0,
anchors={
{ax=src_ax,ay=src_ay,prev=nil,next=nil},
{ax=dst_ax,ay=dst_ay,prev=nil,next=nil}
},
state={name="cast",frame=0},
latch=latch,
}
r.src=r.anchors[1]
r.dst=r.anchors[2]
r.src.next=r.dst
r.dst.prev=r.src
setmetatable(r,rope)
return r
end
@ -980,30 +958,12 @@ function rope:done()
return self.state.name=="done"
end
function rope:busy()
for i=0,#self.ancs+1 do
if (#(self:_anc(i).todo)>0) return true
end
return false
end
function rope:update()
local was_busy=self:busy()
for i=0,#self.ancs+1 do
local anc=self:_anc(i)
_apply(anc,anc.todo,i)
end
local is_busy=self:busy()
if (was_busy and not is_busy) self.dirty=true
if self.state.name=="cast" then
self:continue_cast()
elseif self.state.name=="latched" then
self.latch_frame+=1
if self.latch_frame>=10 then
self.latch_frame=10
end
self.state.frame+=1
if (self.state.frame>=3) self.state={name="latched"}
elseif self.state.name=="latched" then
if (self.latch==nil) wrongbleep:bleep(3) self:destroy() return
if
@ -1011,8 +971,8 @@ function rope:update()
self.latch.rec!=nil
then
self:drag_src(
self.latch.rec.px+self.latch.px_offset,
self.latch.rec.py+self.latch.py_offset
self.latch.rec.mx+0.5+self.latch.ax_offset,
self.latch.rec.my+0.5+self.latch.ay_offset
)
if #self.latch.rec.todo==0 then
@ -1022,7 +982,6 @@ function rope:update()
end
end
if (not is_busy) self:_tidy_up_gen()
elseif self.state.name=="destroy" then -- destroy
self.state.frame+=1
if (self.state.frame>=5) self.state={name="done"}
@ -1036,41 +995,11 @@ function rope:destroy()
self.state={name="destroy",frame=0}
end
function rope:continue_cast()
local dx,dy=self.state.dx,self.state.dy
local x0=self.src.x
local y0=self.src.y
local x1=x0+dx
local y1=y0+dy
for x,y in _rast(
x0,y0,x1,y1
) do
local latch=
level:get_latch(dx,dy,x,y)
if latch!=nil or level:pcoll(x,y) then
self.latch=latch
self.state={name="latched"}
break
end
self.src={x=x,y=y,todo={},dirty=true}
end
end
function rope:_reindex()
self.src.ix=0
self.dst.ix=#self.ancs
for i,anc in ipairs(self.ancs) do
anc.ix=i
end
end
function rope:draw(artificial_dx,artificial_dy)
local points,highlight=self:_tug(true)
if (self:busy()) highlight=nil
if (self.state.name=="done") return
local perc_to_show=1.0
if (self.state.name=="cast") perc_to_show=self.state.frame/2
if (self.state.name=="destroy") perc_to_show=(1.0-self.state.frame/5)^2
points[#points]={x=points[#points].x+artificial_dx,y=points[#points].y+artificial_dy}
@ -1123,7 +1052,13 @@ function rope:draw(artificial_dx,artificial_dy)
rectfill(x,y-1,x-1,y-3,color)
end
end
for _,p in pairs(level._anch) do
pset(p.ax*8,p.ay*8,11)
pset(p.ax*8+p.adx,p.ay*8,11)
pset(p.ax*8,p.ay*8+p.ady,11)
end
--[[
for i=0,#self.ancs+1 do
p=self:_anc(i)
local c=12
@ -1131,10 +1066,6 @@ function rope:draw(artificial_dx,artificial_dy)
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,12)
end
--[[
for _,p in pairs(level._anch) do
pset(p.x,p.y,11)
end
print("dirty:"..tostr(self.dirty),32,0,9)
print("busy:"..tostr(self:busy()),32,7,9)
print("state:"..tostr(self.state.name),32,14,9)
@ -1147,247 +1078,72 @@ function rope:draw(artificial_dx,artificial_dy)
]]--
end
function rope:_anc(i)
if (i==0) return self.src
if (i==#self.ancs+1) return self.dst
return self.ancs[i]
end
function rope:drag_dst(x,y)
self:drag(function() return #self.ancs+1 end,x,y)
self:drag(self.dst,x,y)
end
function rope:drag_src(x,y)
self:drag(function() return 0 end,x,y)
self:drag(self.src,x,y)
end
function rope:drag(
i,x,y
n1,ax_new,ay_new
)
local anc=self:_anc(i())
local busy=self:busy()
for x,y in _rast(
anc.x,anc.y,x,y
) do
local a=self:_anc(i())
if not (_point_eq(a,{x=x,y=y})) then
a.x=x
a.y=y
a.dirty=true
self.dirty=true
if (not busy) self:_tidy_up_gen()
end
end
-- TODO: stepwise?
rope:_drag1(n1,ax_new,ay_new)
end
function rope:make_dirty(only_if_invalid)
local invalid=false
for a=0,#self.ancs do
local a0=self:_anc(a)
local a1=self:_anc(a+1)
if not level:can_stretch(a0,a1) then
a0.dirty=true
a1.dirty=true
invalid=true
end
end
if (invalid or not only_if_invalid) self.dirty=true
end
-- TODO: Upon adding a point, start from there to see if we need another
-- rather than adding at most one
function rope:_drag1(n1,ax1_new,ay1_new)
local ax1_old,ay1_old=n1.ax,n1.ay
local n0=n1.prev
function rope:_tidy_up_gen()
self:make_dirty(true)
local busy=self:busy()
if (not self:latched()) return
if (not self.dirty) return
local settled=true
local loop=function(f)
local a=0
while a<=#self.ancs+1 do
local anc=self:_anc(a)
if anc.dirty then
anc.seen=true
if self[f](self,a,busy) then
settled=false anc.changed=true
a=0
end
end
a+=1
end
end
local mark_unseen=function()
touched={}
for a=0,#self.ancs+1,1 do
local anc=self:_anc(a)
anc.seen=false
anc.changed=false
end
end
local propagate_dirty=function(f)
for a=0,#self.ancs+1,1 do
local a1=self:_anc(a)
if a1.dirty then
local a0=self:_anc(a-1)
if (a0!=nil) a0.dirty=true
local a2=self:_anc(a+1)
if (a2!=nil) a2.dirty=true
end
end
end
while true do
settled=true
mark_unseen()
propagate_dirty()
loop("_find_needed_anchors")
loop("_find_touched_anchors")
loop("_elide_point")
for a=0,#self.ancs+1,1 do
local anc=self:_anc(a)
if (anc.seen) anc.dirty=anc.changed
if (anc.dirty) settled=false
end
if (settled) break
end
if (self:busy()) return
for i=0,#self.ancs do
local a0=self:_anc(i)
local a1=self:_anc(i+1)
if not level:can_stretch(a0,a1) then
self:destroy()
end
end
self.dirty=false
end
function rope:_find_needed_anchors(i,busy)
if (i<=0) return false
if (#self.ancs+1<i) return false
local a0=self:_anc(i-1)
local a2=self:_anc(i)
if (level:pcoll(a2.x,a2.y)) return false
if (level:pcoll(a0.x,a0.y)) return false
if (level:can_stretch(a0,a2)) return false
local anchors_bydist={}
local x0,x2=_mnmx(a0.x,a2.x)
local y0,y2=_mnmx(a0.y,a2.y)
for a1 in level:anchors_in(x0-1,y0-1,x2+1,y2+1) do
add(anchors_bydist,{el=a1,key=_linedist(a0,a1,a2)})
end
shellsort(anchors_bydist)
for a1 in all(anchors_bydist) do
a1=a1.el
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)
self.id+=1
return true
end
end
end
local ELIDE_POINT=0.01
function rope:_find_touched_anchors(i)
if (i<=0) return false
if (#self.ancs<i) return false
local a0=self:_anc(i-1)
local a2=self:_anc(i)
if (level:pcoll(a0.x,a0.y)) return false
if (level:pcoll(a2.x,a2.y)) return false
for bx,by in _rast(a0.x,a0.y,a2.x,a2.y) do
local a1=level:point_anchor(bx,by)
if n0!=nil then
local ax0,ay0=n0.ax,n0.ay
for _,anchor in level:anchor_points() do
if
a1!=nil and not _point_eq(a0,a1) and not _point_eq(a1,a2)
and _linedist(a0,a1,a2)<ELIDE_POINT
(_in_box(anchor.ax,anchor.ay,ax0,ay0,ax1_old,ay1_old) or
_in_box(anchor.ax,anchor.ay,ax0,ay0,ax1_new,ay1_new)) and
_which_side(anchor.ax,anchor.ay,ax0,ay0,ax1_old,ay1_old) !=
_which_side(anchor.ax,anchor.ay,ax0,ay0,ax1_new,ay1_new)
then
local id=self.id
add(self.ancs,{id=id,x=a1.x,y=a1.y,dirty=true,todo={}},i)
self.id+=1
return true
local n05={ax=anchor.ax,ay=anchor.ay,prev=n0,next=n1}
n0.next = n05
n1.prev = n05
end
end
return false
end
function rope:_elide_point(i,busy)
if (i<=0) return false
if (#self.ancs<i) return false
local a0=self:_anc(i-1)
local a1=self:_anc(i)
local a2=self:_anc(i+1)
local level_anc=level:point_anchor(a1.x,a1.y)
if _point_eq(a0,a1) or _point_eq(a1,a2) or (not busy and level_anc==nil) then
-- do it unconditionally
else
if _linedist(a0,a1,a2) < ELIDE_POINT then
return false
end
if not level:can_stretch(a0,a2) then
return false
end
local midpoint={
x=(a0.x+a2.x)\2,
y=(a0.y+a2.y)\2
}
if not self:_can_move_midpoint(a0,a1,midpoint,a2) then
return false
end
end
deli(self.ancs,i)
a0.dirty=true a0.changed=true
a2.dirty=true a2.changed=true
return true
local n2=n1.next
if n2!=nil then
local ax2,ay2=n2.ax,n2.ay
for _,anchor in level:anchor_points() do
if
(_in_box(anchor.ax,anchor.ay,ax1_old,ay1_old,ax2,ay2) or
_in_box(anchor.ax,anchor.ay,ax1_new,ay1_new,ax2,ay2)) and
_which_side(anchor.ax,anchor.ay,ax1_old,ay1_old,ax2,ay2) !=
_which_side(anchor.ax,anchor.ay,ax1_new,ay1_new,ax2,ay2)
then
local n15={ax=anchor.ax,ay=anchor.ay,prev=n1,next=n2}
n1.next = n15
n2.prev = n15
end
end
end
function rope:_can_move_midpoint(a0,a1_0,a1_1,a2)
if not level:can_stretch(a1_0, a1_1) then
return false
n1.ax=ax1_new
n1.ay=ay1_new
end
if not level:can_stretch(a0,a1_1) then
return false
function _in_box(x,y,x0,y0,x1,y1)
x0,x1=_mnmx(x0,x1)
y0,y1=_mnmx(y0,y1)
return x0<=x and y0<=y and x<=x1 and y<=y1
end
if not level:can_stretch(a1_1,a2) then
return false
end
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 level:can_stretch(a0,tm) then
return false
end
if not level:can_stretch(tm,a2) then
return false
end
end
return true
function _which_side(x,y,x0,y0,x1,y1)
return sgn0((x1-x0)*(y-y0) - (y1-y0)*(x-x0))
end
function _linedist(x0,v,x1)
@ -1409,16 +1165,18 @@ function distance(p1,p2)
end
function rope:collide_rect(x1,y1,x2,y2,exclude_src,exclude_dst)
local last=#self.ancs-exclude_dst
for i=exclude_src,last,1 do
local a0=self:_anc(i)
local a1=self:_anc(i+1)
local a0=self.src
while true do
local a1=a0.next
if (a1==nil) return false
--[[
if (_line_line(a0.x,a0.y,a1.x,a1.y,x1,y1,x2,y1)) return true
if (_line_line(a0.x,a0.y,a1.x,a1.y,x1,y1,x1,y2)) return true
if (_line_line(a0.x,a0.y,a1.x,a1.y,x1,y2,x2,y2)) return true
if (_line_line(a0.x,a0.y,a1.x,a1.y,x2,y1,x2,y2)) return true
]]--
a0=a0.next
end
return false
end
function _line_line(x1,y1,x2,y2,x3,y3,x4,y4)
@ -1451,28 +1209,25 @@ end
-- moved here because it's complicated
function rope:tug_orientxy()
local a1=self:_anc(#self.ancs+1)
local a0=self:_anc(#self.ancs)
local dx=a0.x-a1.x
local a1=self.dst
local a0=self.dst.prev
local dx=a0.ax-a1.ax
local tdx=0
if (dx>3) tdx=1
if (dx<-3) tdx=-1
if (dx>3/8) tdx=1
if (dx<-3/8) tdx=-1
local dy=a0.y-a1.y
local dy=a0.ay-a1.ay
local tdy=0
if abs(dy)>abs(dx)/2 then
if (dy>3) tdy=1
if (dy<-3) tdy=-1
if (dy>3/8) tdy=1
if (dy<-3/8) tdy=-1
end
return tdx,tdy
end
function rope:tug()
self:_tidy_up_gen()
if (not self:latched()) return
local rc=self:_tug()
self:_tidy_up_gen()
return rc
return self:_tug()
end
function rope:_tug(hypothetically)
@ -1568,12 +1323,7 @@ function rope:_tug(hypothetically)
then
if (hypothetically) return ancs,0
level:tug_crate(
mx0,my0,
dmx,dmy
)
-- be busy for 4 ticks while the crate moves
self:_anc(0).todo={{},{},{},{},{}}
level:tug_crate(mx0,my0,dmx,dmy)
return true
end
end
@ -1666,26 +1416,26 @@ end
function rope:_anchors_simplified()
-- todo: cache this
self:_reindex()
local points={}
local _slope = function(p0,p1)
return atan2(p1.y-p0.y,p1.x-p0.x)
end
for i=0,#self.ancs+1,1 do
local anc=self:_anc(i)
a=self.src
while a!=nil do
local point={x=a.ax*8,y=a.ay*8,ax=a.ax,ay=a.ay}
if #points<=1 then
add(points,anc)
add(points,point)
elseif abs(
_slope(points[#points-1],points[#points])-
_slope(points[#points],anc)
_slope(points[#points],point)
)==0 then -- epsilon?
points[#points]=anc
points[#points]=point
else
add(points,anc)
add(points,point)
end
a=a.next
assert(#points<100)
end
return points
end