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