diff --git a/chameleonic.p8 b/chameleonic.p8 index c03b108..d99a15d 100644 --- a/chameleonic.p8 +++ b/chameleonic.p8 @@ -214,6 +214,17 @@ function level:anchor_points() return pairs(self._anch) end +function level:anchors_in(px0,py0,px1,py1) + ancs={} + for ax=px0\4,(px1+3)\4 do + for ay=py0\4,(py1+3)\4 do + local anc=self._anch[_amix(ax,ay)] + if (anc!=nil) add(ancs, anc) + end + end + return ancs +end + function level:point_anchor(px,py) local ax,ay=self:p2a(px,py) local anc=self._anch[_amix(ax,ay)] @@ -575,7 +586,6 @@ function rope:_make_consistent() ) if #self.latch.rec.todo==0 then - self:_tidy_up_gen() for i=0,#self.ancs do local a0=self:_anc(i) local a1=self:_anc(i+1) @@ -665,49 +675,83 @@ function rope:drag( i,x,y ) local anc=self:_anc(i()) + local busy=self:busy() + for x,y in self:_rast( anc.x,anc.y,x,y ) do - self:_drag1(i(),x,y) - self:_tidy_up_gen() - end -end - -function rope:_tidy_up_gen() - if (self:busy()) return - - for i=0,#self.ancs+1 do - local a=self:_anc(i) - a.dirty=true - end - - local a=0 - while a<=#self.ancs+1 do - local anc=self:_anc(a) - if anc.dirty and #anc.todo==0 then - while not self.under_destruction and ( - self:_find_needed_anchors(a) or - self:_find_touched_anchors(a) or - self:_elide_point(a) - ) do end - - anc.dirty=false - a=0 - else - a+=1 + 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 -function rope:_drag1( - i,x,y -) - local a_old=self:_anc(i) - local a_new={x=x,y=y} - if (_point_eq(a_old, a_new)) return - - a_old.x=x - a_old.y=y +function rope:_tidy_up_gen() + if (self.under_destruction) return + if (not self.dirty) return + + local settled=true + local touched={} + 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) then + settled=false anc.changed=true + 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 + end + + if (settled) break + end + + self.dirty=false end function rope:_find_needed_anchors(i) @@ -724,14 +768,8 @@ function rope:_find_needed_anchors(i) local anchors_bydist={} local x0,x2=_mnmx(a0.x,a2.x) local y0,y2=_mnmx(a0.y,a2.y) - for _,a1 in level:anchor_points() do - -- the new anchor point must be in the bounding box - -- of the old line - -- note: this fudge never turned out necessary - -- i just would be ok with it - if x0-1<=a1.x and a1.x<=x2+1 and y0-1<=a1.y and a1.y<=y2+1 then - add(anchors_bydist,{el=a1,key=_linedist(a0,a1,a2)}) - end + for a1 in all(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) @@ -741,11 +779,9 @@ function rope:_find_needed_anchors(i) self:_can_stretch(a1,a2) then local id=self.id - add(self.ancs,{id=id,x=a1.x,y=a1.y,todo={}},i) + add(self.ancs,{id=id,x=a1.x,y=a1.y,dirty=true,todo={}},i) self.id+=1 - self:_anc(i-1).dirty=true - self:_anc(i+1).dirty=true return true end end @@ -758,19 +794,20 @@ function rope:_find_touched_anchors(i) 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 self:_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) < 0.01 + if + a1!=nil and not _point_eq(a0,a1) and not _point_eq(a1,a2) + and _linedist(a0,a1,a2) == 0.0 -- and self:_can_stretch(p,a2) then local id=self.id - add(self.ancs,{id=id,x=a1.x,y=a1.y,todo={}},i) + add(self.ancs,{id=id,x=a1.x,y=a1.y,dirty=true,todo={}},i) self.id+=1 - self:_anc(i-1).dirty=true - self:_anc(i+1).dirty=true - return true end end @@ -808,12 +845,15 @@ function rope:_elide_point(i) end deli(self.ancs,i) - self:_anc(i-1).dirty=true - self:_anc(i).dirty=true return true 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 return false end @@ -823,7 +863,7 @@ function rope:_can_move_midpoint(a0,a1_0,a1_1,a2) if not self:_can_stretch(a1_1,a2) then return false end - for x,y in self:_rastm(a1_0.x,a1_0.y,a1_1.x,a1_1.y) do + for x,y in self:_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 return false @@ -879,11 +919,26 @@ 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:_rastm(p1.x,p1.y,p2.x,p2.y) do + 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 @@ -893,8 +948,8 @@ function rope:_can_stretch( return res end -function rope:_rastm( - x0,y0,x1,y1 +function rope:_rastn( + x0,y0,x1,y1,dx,dy ) -- todo: more optimized implementation? local iter=self:_rast(x0,y0,x1,y1) @@ -907,8 +962,8 @@ function rope:_rastm( if (x==nil) done=true return x1, y1 - local x8 = x\8 - local y8 = y\8 + local x8 = x\dx + local y8 = y\dy if not (x8==prevx and y8==prevy) then prevx,prevy=x8,y8 return x,y @@ -931,34 +986,27 @@ function rope:_rast( if (y0dy then err=dx/2.0 return function() - if (queue==nil) return - if (x==x1) queue=nil return x1,y1 - if #queue==0 then - add(queue,{x,y}) - err-=dy - if (err<0) y+=sy add(queue,{x,y}) err+=dx - x+=sx - end - return unpack(deli(queue,1)) + 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 + x+=sx + return oldx,oldy end else err=dy/2.0 return function() - if (queue==nil) return - if (y==y1) queue=nil return x1,y1 - if #queue==0 then - add(queue,{x,y}) - - local oldx,oldy=x,y - err-=dx - if (err<0) x+=sx add(queue,{x,y}) err+=dy - y+=sy - end - return unpack(deli(queue,1)) + 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 + y+=sy + return oldx,oldy end end end @@ -1034,7 +1082,7 @@ function rope:_tug() if force or not level:pcoll(x,y) then s.x=x s.y=y - s.dirty=true + self.dirty=true end return true end} @@ -1048,7 +1096,6 @@ function rope:_tug() end for node=ancs[i-1].ix-1,ancs[i].ix+1 do local anc=self:_anc(node) - if (anc!=nil) anc.dirty=true end return end @@ -1088,6 +1135,8 @@ function rope:_tug() mx0,my0, dmx,dmy ) + -- be busy for 4 ticks while the crate moves + self:_anc(0).todo={{},{},{},{}} end end end