From d2ee3d2078b1a9f25da1361746285b94bd2a63dd Mon Sep 17 00:00:00 2001 From: Pyrex Date: Wed, 21 Dec 2022 05:44:15 +0000 Subject: [PATCH] Rewrite rope (#11) Rope rewrite, part one We should only care about anchors on the old path Add comment expressing uncertainty Add further algo notes One final note Well, this is closer to right!! Elide points as needed Save current changes First version I couldn't immediately break Everything _seems_ to work Clean up some residual messes Tidy up more loose ends Co-authored-by: Nyeogmi Reviewed-on: https://git.chromaticdragon.app/pyrex/chameleonic/pulls/11 --- chameleonic.p8 | 1069 ++++++++++++++++++++++-------------------------- 1 file changed, 486 insertions(+), 583 deletions(-) diff --git a/chameleonic.p8 b/chameleonic.p8 index fb75cbe..68be32d 100644 --- a/chameleonic.p8 +++ b/chameleonic.p8 @@ -93,13 +93,13 @@ function _apply(x,ts,a) local t=deli(ts,1) for k,v in pairs(t) do if k=="update" then - if not v(x,a) then - add(ts,t,1) - end + -- else x[k]=v end end + + if (t and t.update and not t.update(x,a)) add(ts,t,1) end function sgn0(x) @@ -112,29 +112,6 @@ 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 @@ -157,7 +134,7 @@ function _rast( 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 + if (err<0) y+=sy err+=dx x+=sx return oldx,oldy end @@ -168,17 +145,13 @@ function _rast( 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 + if (err<0) x+=sx err+=dy 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={} @@ -228,6 +201,33 @@ function kbd:release(i) self.down&=~(1<"..tostring(v).." " + end + return str.."}" + end + if type(any)=="number" then + return ""..any + end + return "unknown" -- should never show +end + -->8 -- title screen title={} @@ -289,7 +289,7 @@ function level:reinit(n) self.todo={} self.bigx=(n%8) self.bigy=(n\8) - self.cache_can_stretch=dcache:new() + self.next_crate_id=1 self:load_dynobjs() self:recollide() @@ -322,16 +322,16 @@ function level:draw() spr(pit.contents,pit.px,pit.py) pal() pal(1,0) + end end for _,crate in pairs(self._crates) do spr(crate.s,crate.px,crate.py) - end end pal() end function level:busy() - for _,crate in pairs(self.crates) do + for _,crate in pairs(self._crates) do if (#crate.todo>0) return true end return false @@ -375,10 +375,12 @@ function level:load_dynobjs() if def then self._crates[mxy]={ s=s,def=def, + id=self.next_crate_id, mx=mx,my=my, px=px,py=py, todo={} } + self.next_crate_id+=1 end if s==28 then -- pit @@ -403,67 +405,74 @@ function level:recollide() self._crates[mxy]!=nil end end - self.cache_can_stretch:clear() end -function add_adjacent_anchors(tbl,mx,my) - for ax in all{mx*2-1,mx*2+2} do - for ay in all{my*2-1,my*2+2} do - local px,py=level:a2p(ax,ay) - tbl[_amix(ax,ay)]={ax=ax0,ay=ay0,x=px,y=py} - end - end -end +function level:reanchor() + local anch_new={} + 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 -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} + if ( + self:mcoll(mx0,my0) and not self:get_crate(mx0,my0) and + not self:mcoll(mx0,my1) and + not self:mcoll(mx1,my0) and + not self:mcoll(mx1,my1) + ) then + local key="GEOM"..mx0..","..my0..","..dx..","..dy + anch_new[key]= { + ax=max(mx0,mx1),ay=max(my0,my1),adx=-dx,ady=-dy + } + end end end + + for _,cr in pairs(self._crates) do + local key="CRATE"..cr.id..","..dx..","..dy + local mx0,my0=cr.mx,cr.my + local mx1,my1=mx0+dx,my0+dy + anch_new[key]={ + ax=max(mx0,mx1),ay=max(my0,my1),adx=-dx,ady=-dy + } + end end - for _,cr in pairs(self._crates) do - add_adjacent_anchors(self._anch,cr.mx,cr.my) + local anch_old=self._anch + if (anch_old==nil) anch_old={} + for _,old in pairs(anch_old) do + old.dropped=true end - if (player.rope!=nil) player.rope:make_dirty() + for k,new in pairs(anch_new) do + local old=anch_old[k] + if old then + anch_new[k]=old + old.ax,old.ay,old.adx,old.ady=new.ax,new.ay,new.adx,new.ady + old.dropped=nil + end + end + self._anch=anch_new + self._anch_keys={} + for k,_ in pairs(self._anch) do + add(self._anch_keys,{key=k}) + end + + if (player.rope!=nil) player.rope:relax() end function level:win_at(mx,my) return self._wins[_mix(mx,my)] end -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 +function level:anchor_points() + keys=all(self._anch_keys) + return function() + local k=keys() + if (k==nil) return nil + return self._anch[k.key] end end @@ -472,12 +481,6 @@ function level:get_open_pit(mx,my) if (pit and pit.contents==nil) return pit end -function level:point_anchor(px,py) - local ax,ay=self:p2a(px,py) - local anc=self._anch[_amix(ax,ay)] - return anc -end - function level:spawn_exit() self._wins={} local spawned=false @@ -491,9 +494,11 @@ function level:spawn_exit() end local win_at=function(x,y) if (self:_mget(x,y)!=18) return - for n in all(neighbors{x=x,y=y}) do - if n.x<0 or n.y<0 or n.x>15 or n.y>15 then - self._wins[_mix(n.x,n.y)]=true + for nx=x-1,x+1 do + for ny=y-1,y+1 do + if nx<0 or ny<0 or nx>15 or ny>15 then + self._wins[_mix(nx,ny)]=true + end end end end @@ -504,16 +509,6 @@ function level:spawn_exit() assert(spawned) end -function level:p2a(px,py) - return px\4,py\4 -end - -function level:a2p(ax,ay) - local px=ax*4+3*(ax%2) - local py=ay*4+3*(ay%2) - return px,py -end - function level:mcoll(mx,my) return self._coll[_mix(mx,my)]!=false end @@ -574,8 +569,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 @@ -592,7 +587,9 @@ function level:get_latch(dx,dy,px,py) return { el="eyehook", dx=dx1,dy=dy1, - mx=mx,my=my + ax_offset=dx1*0.5, + ay_offset=dy1*0.5, + rec={mx=mx,my=my,px=mx*8,py=my*8}, } end end @@ -615,16 +612,15 @@ function level:can_move( -- todo: check tongue collision if player.rope then - local px,py=mx0*8,my0*8 local chk=false if dmx==0 and dmy==-1 then - chk=player.rope:collide_rect(px+3,py-5,px+4,py+5,exclude_src,exclude_dst) + chk=player.rope:collide_mrect(mx0,my0-1,1,2,exclude_src,exclude_dst) elseif dmx==0 and dmy==1 then - chk=player.rope:collide_rect(px+3,py+3,px+4,py+13,exclude_src,exclude_dst) + chk=player.rope:collide_mrect(mx0,my0,1,2,exclude_src,exclude_dst) elseif dmx==-1 and dmy==0 then - chk=player.rope:collide_rect(px-5,py+3,px+5,py+4,exclude_src,exclude_dst) + chk=player.rope:collide_mrect(mx0-1,my0,2,1,exclude_src,exclude_dst) elseif dmx==1 and dmy==0 then - chk=player.rope:collide_rect(px+3,py+3,px+13,py+4,exclude_src,exclude_dst) + chk=player.rope:collide_mrect(mx0,my0,2,1,exclude_src,exclude_dst) end if (chk) return false @@ -642,88 +638,16 @@ function level:tug_crate(mx0,my0,dmx,dmy) local mx1,my1=mx0+dmx,my0+dmy local mxy1=_mix(mx1,my1) - existing.mx=mx1 - existing.my=my1 existing.todo={ - {px=mx0*8+dmx*2,py=my0*8+dmy*2}, - {px=mx0*8+dmx*7,py=my0*8+dmy*7}, - {px=mx1*8,py=my1*8,update=function() - self:reanchor(true) + {px=mx1*8+dmx,py=my1*8+dmy,mx=mx1,my=my1,update=function() + self:recollide() + self:reanchor() return true - end} + end}, + {px=mx1*8,py=my1*8} } self._crates[mxy1]=existing - 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 @@ -758,7 +682,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 +749,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.1, + level:get_latch(dx,dy,x*8,y*8) + ) self.todo={{ update=function() @@ -848,8 +778,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+self.px/8+0.5,self.y+self.py/8+0.1) local tdx,tdy=self.rope:tug_orientxy() if (tdx!=0) self.orientx=tdx @@ -866,6 +795,7 @@ function player:_vanish_if_requested() self.vanish_frame+=1 if (self.fall_frame>0 or self.vanish_frame>20) then + self.rope=nil level:restart() kbd:release(5) self.vanish_frame=20 @@ -881,14 +811,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 +878,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 +905,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,18 +918,17 @@ 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 - if self.latch.rec.dead==true then - self:destroy() - end + if self.latch.rec.dead==true then + self:destroy() end end - if (not is_busy) self:_tidy_up_gen() + if (not self:_check_sane()) self:destroy() + elseif self.state.name=="destroy" then -- destroy self.state.frame+=1 if (self.state.frame>=5) self.state={name="done"} @@ -1036,41 +942,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} @@ -1108,294 +984,347 @@ function rope:draw(artificial_dx,artificial_dy) end -- draw latch - if self.latch!=nil and perc_to_show>=1.0 then - local x,y=points[1].x,points[1].y + if self.latch!=nil and self.latch.rec and perc_to_show>=1.0 then + local x,y=self.latch.rec.px,self.latch.rec.py local ldx,ldy=self.latch.dx,self.latch.dy local color=8 if (highlight==0) color=12 if self.latch.dx==-1 and self.latch.dy==0 then - rectfill(x+1,y,x+3,y+1,color) + rectfill(x,y+3,x+2,y+4,color) elseif self.latch.dx==1 and self.latch.dy==0 then - rectfill(x-1,y,x-3,y+1,color) + rectfill(x+5,y+3,x+7,y+4,color) elseif self.latch.dx==0 and self.latch.dy==-1 then - rectfill(x,y+1,x-1,y+3,color) + rectfill(x+3,y,x+4,y+2,color) elseif self.latch.dx==0 and self.latch.dy==1 then - rectfill(x,y-1,x-1,y-3,color) + rectfill(x+3,y+5,x+4,y+7,color) 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,12) - end + -- debug --[[ - 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) - if self.all_ops!=nil then - for i,o in ipairs(self.all_ops) do - rect(o.mx*8,o.my*8,o.mx*8+7,o.my*8+7,4) - --print(o.mx..","..o.my,0,i*8,3) - end - end - ]]-- -end + local n1=self.src + local sy=0 + while true do + if (n1==nil) break + local x=n1.ax*8 + local y=n1.ay*8 + if n1.associated_with then + if (n1.associated_with.adx>0) x-=1 + if (n1.associated_with.ady>0) y-=1 + end + rectfill(x-1,y-1,x+1,y+1,12) + print("ax="..n1.ax..",ay="..n1.ay,0,sy) + sy+=7 -function rope:_anc(i) - if (i==0) return self.src - if (i==#self.ancs+1) return self.dst - return self.ancs[i] + local n0=n1.prev + local n2=n1.next + if n0!=nil and n2!=nil then + if n1.associated_with then + local _,_,_,adx,ady=would_stick(n0.ax,n0.ay,n1.associated_with,n2.ax,n2.ay) + assert(adx==-1 or adx==0 or adx==1) + assert(ady==-1 or ady==0 or ady==1) + --assert(not (adx==0 and ady==0)) + + local c=3 + if (n1.associated_with.dropped) c=8 + rectfill(x+2,y+2,x+4,y+4,c) + pset(x+adx*2,y,9) + pset(x,y+ady*2,9) + else + rectfill(x+2,y+2,x+4,y+4,2) + end + else + rectfill(x+2,y+2,x+4,y+4,4) + end + + n1=n1.next + end + + for _,p in pairs(level._anch) do + local x,y=p.ax*8,p.ay*8 + if (p.adx>0) x-=1 + if (p.ady>0) y-=1 + pset(x,y,11) + pset(x+p.adx,y,11) + pset(x,y+p.ady,11) + end + ]] 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 -) - 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 +function rope:drag(n1,ax_new,ay_new) + self:relax() + self:_drag(n1,ax_new,n1.ay) + self:_drag(n1,ax_new,ay_new) + self:relax() 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 - -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 +function rope:relax() + local n=self.src while true do - settled=true + if (n==nil) break - 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 + if (n.associated_with) then + self:_drag(n,n.associated_with.ax,n.associated_with.ay) end - if (settled) break + n=n.next end - if (self:busy()) return + local n0=self.src + while true do + if (n0==nil) return + local n1=n0.next + if (n1==nil) return + local n2=n1.next + if (n2==nil) 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 + if n1.associated_with!=nil then - self.dirty=false -end + local x0,y0=n0.ax,n0.ay + local x1,y1=n1.ax,n1.ay + local x2,y2=n2.ax,n2.ay -function rope:_find_needed_anchors(i,busy) - if (i<=0) return false - if (#self.ancs+1"..tostring(n1.associated_with).."->"..tostring(n2.associated_with)) + self:_drag(n1,x1_new,y1_new) + n0=n1.prev + n2=n1.next + n0.next=n2 + n2.prev=n0 + n1.next=nil + n1.prev=nil + else n0=n0.next end + else n0=n0.next end end end -local ELIDE_POINT=0.01 -function rope:_find_touched_anchors(i) - if (i<=0) return false - if (#self.ancsx2) x0,y0,x2,y2=x2,y2,x0,y0 - local a0=self:_anc(i-1) - local a1=self:_anc(i) - local a2=self:_anc(i+1) + local dx=x2-x0 + local dy=y2-y0 - 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 + local adx,ady + local x1_new,y1_new + if abs(dx)>abs(dy) then + local dprop=(x1-x0)/dx + x1_new,y1_new=x1,y0+dprop*(y2-y0) + ady=sgn0(y1_new-y1) + adx=0 + if (y0>y2) adx=ady + if (y0y2) ady=adx + if (y0"..tostring(n05.associated_with).."->"..tostring(n1.associated_with)) + n0.next=n05 + n1.prev=n05 + n0=n05 + end + + local n2=n1.next + while true do + if (n2==nil) break + local anch=_sweep_radar(n2.ax,n2.ay,ax1_old,ay1_old,ax1_new,ay1_new) + if (anch==nil) break + local n15={ax=anch.ax,ay=anch.ay,associated_with=anch,prev=n1,next=n2} + --printh("creating post: "..tostring(n1.associated_with).."->"..tostring(n15.associated_with).."->"..tostring(n2.associated_with)) + n1.next=n15 + n2.prev=n15 + n2=n15 + end end -function _linedist(x0,v,x1) - return 100 * (sum_distance(x0,v,x1)-distance(x0,x1))/distance(x0,x1) +function _stepfrom(x0,x1) + local done=false + if x0==x1 then + return function() + if (done) return nil + done=true return x0 + end + end + + local mul=0.5 + x0*=2 + x1*=2 + if (x0>x1) x0,x1,mul=-x0,-x1,-mul + local i=flr(x0) + local top=flr(x1) + return function() + if (done) return nil + i+=1 + if i>top then + done = true + if (x1!=flr(x1)) return mul*x1 + return nil + end + return mul*i + end end -function sum_distance(x,y,z) - return distance(x,y) + distance(y,z) +function _which_side(x,y,x0,y0,x1,y1) + return sgn0((x1-x0)*(y-y0) - (y1-y0)*(x-x0)) end function distance_dxy(dx,dy) @@ -1408,17 +1337,34 @@ function distance(p1,p2) return sqrt(dx*dx+dy*dy) 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) - 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 +function rope:collide_mrect(mx0,my0,mw,mh,exclude_src,exclude_dst) + local mx1,my1=mx0+mw,my0+mh + local n0=self.src + + mx0+=0.4 + my0+=0.4 + mx1-=0.4 + my1-=0.4 + + while true do + local n1=n0.next + if (n1==nil) return false + + local nd=n0 + for i=1,exclude_dst do + nd=nd.next + if (nd==nil) return false + end + + if exclude_src<=0 then + if (_line_line(n0.ax,n0.ay,n1.ax,n1.ay,mx0,my0,mx1,my0)) return true + if (_line_line(n0.ax,n0.ay,n1.ax,n1.ay,mx0,my0,mx0,my1)) return true + if (_line_line(n0.ax,n0.ay,n1.ax,n1.ay,mx0,my1,mx1,my1)) return true + if (_line_line(n0.ax,n0.ay,n1.ax,n1.ay,mx1,my0,mx1,my1)) return true + end + exclude_src-=1 + n0=n1 end - return false end function _line_line(x1,y1,x2,y2,x3,y3,x4,y4) @@ -1435,44 +1381,29 @@ function _line_line(x1,y1,x2,y2,x3,y3,x4,y4) return true end -function neighbors(p) - local r={} - for dx=-1,1,1 do - for dy=-1,1,1 do - if dx!=0 or dy!=0 then - add(r,{x=p.x+dx,y=p.y+dy}) - end - end - end - return r -end - -->8 -- 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) @@ -1480,12 +1411,12 @@ function rope:_tug(hypothetically) local touched={} for i=#ancs-1,2,-1 do - local ops_before_trash,hit_end1=self:_calc_push(ancs[i+1],ancs[i],ancs[i-1],ancs[i-2]) + local ops_before_trash=self:_calc_push(ancs[i+1],ancs[i],ancs[i-1],ancs[i-2]) local ops_to_do,corners={} if #ops_before_trash>0 then ops_to_do=ops_before_trash else - local ops_after_trash,hit_end2=self:_calc_push(ancs[i-2],ancs[i-1],ancs[i],ancs[i+1]) + local ops_after_trash=self:_calc_push(ancs[i-2],ancs[i-1],ancs[i],ancs[i+1]) ops_to_do=ops_after_trash end @@ -1495,39 +1426,9 @@ function rope:_tug(hypothetically) if (hypothetically) return ancs,i-1 local dmx,dmy=ops[1].dmx,ops[1].dmy - local adjacent_ancs={} for o in all(ops) do - add_adjacent_anchors(adjacent_ancs,o.mx,o.my) level:tug_crate(o.mx,o.my,o.dmx,o.dmy) end - for node=ancs[i-1].ix,ancs[i].ix do - local anc=self:_anc(node) - local x0,y0=anc.x,anc.y - - local upd=function(x,y,force) - return {update=function(s,i) - if force or not level:pcoll(x,y) then - s.x=x - s.y=y - end - s.dirty=true - self.dirty=true - return true - end} - end - local dmxh,dmyh=dmx,dmy - local ax,ay=level:p2a(x0,y0) - if (adjacent_ancs[_amix(ax,ay)]==nil) dmxh,dmyh=0,0 - anc.todo={ - {}, - upd(x0+dmxh*2,y0+dmyh*2), - upd(x0+dmxh*7,y0+dmyh*7), - upd(x0+dmxh*8,y0+dmyh*8), - } - end - for node=ancs[i-1].ix-1,ancs[i].ix+1 do - local anc=self:_anc(node) - end return true end end @@ -1568,12 +1469,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 @@ -1642,7 +1538,6 @@ function rope:_calc_push( end end - local hit_end=true local ops2={} for o in all(ops) do if not level:mcoll(o.mx,o.my) then @@ -1650,42 +1545,50 @@ function rope:_calc_push( else local crate=level:get_crate(o.mx,o.my) if crate==nil then - hit_end=false break else if not level:can_move(false,o.mx,o.my,o.dmx,o.dmy,0,0) then - hit_end=false break end end add(ops2,o) end end - return ops2,hit_end + return ops2 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=flr(a.ax*8+0.5),y=flr(a.ay*8+0.5), + ax=a.ax,ay=a.ay + } + if a.associated_with then + if (a.associated_with.adx==1) point.x-=1 + if (a.associated_with.ady==1) point.y-=1 + elseif a.prev==nil and self.latch then + if (self.latch.ax_offset<0) point.x-=1 + if (self.latch.ay_offset<0) point.y-=1 + end + 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 end - return points end