From f3df6c674a6210672b72038e92a2aa4f1e87b4bb Mon Sep 17 00:00:00 2001 From: Nyeogmi Date: Mon, 19 Dec 2022 23:15:55 -0800 Subject: [PATCH] Rope rewrite, part one --- chameleonic.p8 | 498 ++++++++++++------------------------------------- 1 file changed, 124 insertions(+), 374 deletions(-) diff --git a/chameleonic.p8 b/chameleonic.p8 index fb75cbe..aede61f 100644 --- a/chameleonic.p8 +++ b/chameleonic.p8 @@ -415,35 +415,27 @@ function add_adjacent_anchors(tbl,mx,my) end end -function level:reanchor(remove) - if remove or not self._anch then - 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 - - if ( - not self:mcoll(mx0,my0) and - not self:mcoll(mx0,my1) and - not self:mcoll(mx1,my0) and - 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} +function level:reanchor() + self._anch={} + 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 ( + self:mcoll(mx0,my0) and + not self:mcoll(mx0,my1) and + not self:mcoll(mx1,my0) and + not self:mcoll(mx1,my1) + ) then + add(self._anch, { + ax=max(mx0,mx1),ay=max(my0,my1),adx=-dx,ady=-dy + }) + end 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 + if n0!=nil then + local ax0,ay0=n0.ax,n0.ay + for _,anchor in level:anchor_points() do + if + (_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 n05={ax=anchor.ax,ay=anchor.ay,prev=n0,next=n1} + n0.next = n05 + n1.prev = n05 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() + 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 - self.dirty=false + n1.ax=ax1_new + n1.ay=ay1_new end -function rope:_find_needed_anchors(i,busy) - if (i<=0) return false - if (#self.ancs+13) 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