diff --git a/chameleonic.p8 b/chameleonic.p8 index 9be5963..fb75cbe 100644 --- a/chameleonic.p8 +++ b/chameleonic.p8 @@ -112,6 +112,73 @@ 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 +) + local dx=abs(x1-x0) + local dy=abs(y1-y0) + local x=x0 + local y=y0 + + local sx=-1 + local sy=-1 + if (x0dy then + err=dx/2.0 + return function() + 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+dy return oldx,y + x+=sx + return oldx,oldy + end + else + err=dy/2.0 + return function() + 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+dx return x,oldy + 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={} @@ -222,6 +289,7 @@ function level:reinit(n) self.todo={} self.bigx=(n%8) self.bigy=(n\8) + self.cache_can_stretch=dcache:new() self:load_dynobjs() self:recollide() @@ -335,6 +403,7 @@ function level:recollide() self._crates[mxy]!=nil end end + self.cache_can_stretch:clear() end function add_adjacent_anchors(tbl,mx,my) @@ -588,6 +657,75 @@ function level:tug_crate(mx0,my0,dmx,dmy) 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 --player handling player={} @@ -905,7 +1043,7 @@ function rope:continue_cast() local x1=x0+dx local y1=y0+dy - for x,y in self:_rast( + for x,y in _rast( x0,y0,x1,y1 ) do local latch= @@ -986,14 +1124,14 @@ function rope:draw(artificial_dx,artificial_dy) 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,9) + 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 @@ -1029,7 +1167,7 @@ function rope:drag( local anc=self:_anc(i()) local busy=self:busy() - for x,y in self:_rast( + for x,y in _rast( anc.x,anc.y,x,y ) do local a=self:_anc(i()) @@ -1038,8 +1176,6 @@ function rope:drag( a.y=y a.dirty=true self.dirty=true - --self:_find_needed_anchors(i()) - --self:_find_needed_anchors(i()+1) if (not busy) self:_tidy_up_gen() end end @@ -1050,7 +1186,7 @@ function rope:make_dirty(only_if_invalid) for a=0,#self.ancs do local a0=self:_anc(a) local a1=self:_anc(a+1) - if not self:_can_stretch(a0,a1) then + if not level:can_stretch(a0,a1) then a0.dirty=true a1.dirty=true invalid=true @@ -1075,6 +1211,7 @@ function rope:_tidy_up_gen() anc.seen=true if self[f](self,a,busy) then settled=false anc.changed=true + a=0 end end a+=1 @@ -1127,7 +1264,7 @@ function rope:_tidy_up_gen() for i=0,#self.ancs do local a0=self:_anc(i) local a1=self:_anc(i+1) - if not self:_can_stretch(a0,a1) then + if not level:can_stretch(a0,a1) then self:destroy() end end @@ -1144,7 +1281,7 @@ function rope:_find_needed_anchors(i,busy) if (level:pcoll(a2.x,a2.y)) return false if (level:pcoll(a0.x,a0.y)) return false - if (self:_can_stretch(a0,a2)) return false + if (level:can_stretch(a0,a2)) return false local anchors_bydist={} local x0,x2=_mnmx(a0.x,a2.x) @@ -1156,8 +1293,8 @@ function rope:_find_needed_anchors(i,busy) for a1 in all(anchors_bydist) do a1=a1.el - if self:_can_stretch(a0,a1) and - self:_can_stretch(a1,a2) + 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) @@ -1179,12 +1316,11 @@ function rope:_find_touched_anchors(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 + for bx,by in _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)dy then - err=dx/2.0 - return function() - 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+dy return oldx,y - x+=sx - return oldx,oldy - end - else - err=dy/2.0 - return function() - 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+dx return x,oldy - y+=sy - return oldx,oldy - end - end -end - -function _point_eq(p1,p2) - return p1.x==p2.x and p1.y==p2.y -end - function neighbors(p) local r={} for dx=-1,1,1 do