diff options
author | chai <chaifix@163.com> | 2018-12-26 00:32:10 +0800 |
---|---|---|
committer | chai <chaifix@163.com> | 2018-12-26 00:32:10 +0800 |
commit | 74e76362dadd38c5a5231a4630c093efdd0febfd (patch) | |
tree | f305f37cf7c19a1f0c95a9f848132f654e0beffc /src/libjin-lua/scripts/physics/physics.lua | |
parent | b15b96151e8b25dd2a36a6b469b992a8eded73b2 (diff) |
*格式化代码
Diffstat (limited to 'src/libjin-lua/scripts/physics/physics.lua')
-rw-r--r-- | src/libjin-lua/scripts/physics/physics.lua | 929 |
1 files changed, 465 insertions, 464 deletions
diff --git a/src/libjin-lua/scripts/physics/physics.lua b/src/libjin-lua/scripts/physics/physics.lua index d312762..691c879 100644 --- a/src/libjin-lua/scripts/physics/physics.lua +++ b/src/libjin-lua/scripts/physics/physics.lua @@ -12,36 +12,36 @@ local DELTA = 1e-10 -- floating-point margin of error local abs, floor, ceil, min, max = math.abs, math.floor, math.ceil, math.min, math.max local function sign(x) - if x > 0 then return 1 end - if x == 0 then return 0 end - return -1 + if x > 0 then return 1 end + if x == 0 then return 0 end + return -1 end local function nearest(x, a, b) - if abs(a - x) < abs(b - x) then return a else return b end + if abs(a - x) < abs(b - x) then return a else return b end end local function assertType(desiredType, value, name) - if type(value) ~= desiredType then - error(name .. ' must be a ' .. desiredType .. ', but was ' .. tostring(value) .. '(a ' .. type(value) .. ')') - end + if type(value) ~= desiredType then + error(name .. ' must be a ' .. desiredType .. ', but was ' .. tostring(value) .. '(a ' .. type(value) .. ')') + end end local function assertIsPositiveNumber(value, name) - if type(value) ~= 'number' or value <= 0 then - error(name .. ' must be a positive integer, but was ' .. tostring(value) .. '(' .. type(value) .. ')') - end + if type(value) ~= 'number' or value <= 0 then + error(name .. ' must be a positive integer, but was ' .. tostring(value) .. '(' .. type(value) .. ')') + end end local function assertIsRect(x,y,w,h) - assertType('number', x, 'x') - assertType('number', y, 'y') - assertIsPositiveNumber(w, 'w') - assertIsPositiveNumber(h, 'h') + assertType('number', x, 'x') + assertType('number', y, 'y') + assertIsPositiveNumber(w, 'w') + assertIsPositiveNumber(h, 'h') end local defaultFilter = function() - return 'slide' + return 'slide' end ------------------------------------------ @@ -49,7 +49,7 @@ end ------------------------------------------ local function rect_getNearestCorner(x,y,w,h, px, py) - return nearest(px, x, x+w), nearest(py, y, y+h) + return nearest(px, x, x+w), nearest(py, y, y+h) end -- This is a generalized implementation of the liang-barsky algorithm, which also returns @@ -57,122 +57,122 @@ end -- Returns nil if the segment never touches the rect -- Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge local function rect_getSegmentIntersectionIndices(x,y,w,h, x1,y1,x2,y2, ti1,ti2) - ti1, ti2 = ti1 or 0, ti2 or 1 - local dx, dy = x2-x1, y2-y1 - local nx, ny - local nx1, ny1, nx2, ny2 = 0,0,0,0 - local p, q, r - - for side = 1,4 do - if side == 1 then nx,ny,p,q = -1, 0, -dx, x1 - x -- left - elseif side == 2 then nx,ny,p,q = 1, 0, dx, x + w - x1 -- right - elseif side == 3 then nx,ny,p,q = 0, -1, -dy, y1 - y -- top - else nx,ny,p,q = 0, 1, dy, y + h - y1 -- bottom - end + ti1, ti2 = ti1 or 0, ti2 or 1 + local dx, dy = x2-x1, y2-y1 + local nx, ny + local nx1, ny1, nx2, ny2 = 0,0,0,0 + local p, q, r + + for side = 1,4 do + if side == 1 then nx,ny,p,q = -1, 0, -dx, x1 - x -- left + elseif side == 2 then nx,ny,p,q = 1, 0, dx, x + w - x1 -- right + elseif side == 3 then nx,ny,p,q = 0, -1, -dy, y1 - y -- top + else nx,ny,p,q = 0, 1, dy, y + h - y1 -- bottom + end - if p == 0 then - if q <= 0 then return nil end - else - r = q / p - if p < 0 then - if r > ti2 then return nil - elseif r > ti1 then ti1,nx1,ny1 = r,nx,ny - end - else -- p > 0 - if r < ti1 then return nil - elseif r < ti2 then ti2,nx2,ny2 = r,nx,ny - end - end + if p == 0 then + if q <= 0 then return nil end + else + r = q / p + if p < 0 then + if r > ti2 then return nil + elseif r > ti1 then ti1,nx1,ny1 = r,nx,ny + end + else -- p > 0 + if r < ti1 then return nil + elseif r < ti2 then ti2,nx2,ny2 = r,nx,ny end + end end + end - return ti1,ti2, nx1,ny1, nx2,ny2 + return ti1,ti2, nx1,ny1, nx2,ny2 end -- Calculates the minkowsky difference between 2 rects, which is another rect local function rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2) - return x2 - x1 - w1, - y2 - y1 - h1, - w1 + w2, - h1 + h2 + return x2 - x1 - w1, + y2 - y1 - h1, + w1 + w2, + h1 + h2 end local function rect_containsPoint(x,y,w,h, px,py) - return px - x > DELTA and py - y > DELTA and - x + w - px > DELTA and y + h - py > DELTA + return px - x > DELTA and py - y > DELTA and + x + w - px > DELTA and y + h - py > DELTA end local function rect_isIntersecting(x1,y1,w1,h1, x2,y2,w2,h2) - return x1 < x2+w2 and x2 < x1+w1 and - y1 < y2+h2 and y2 < y1+h1 + return x1 < x2+w2 and x2 < x1+w1 and + y1 < y2+h2 and y2 < y1+h1 end local function rect_getSquareDistance(x1,y1,w1,h1, x2,y2,w2,h2) - local dx = x1 - x2 + (w1 - w2)/2 - local dy = y1 - y2 + (h1 - h2)/2 - return dx*dx + dy*dy + local dx = x1 - x2 + (w1 - w2)/2 + local dy = y1 - y2 + (h1 - h2)/2 + return dx*dx + dy*dy end local function rect_detectCollision(x1,y1,w1,h1, x2,y2,w2,h2, goalX, goalY) - goalX = goalX or x1 - goalY = goalY or y1 + goalX = goalX or x1 + goalY = goalY or y1 + + local dx, dy = goalX - x1, goalY - y1 + local x,y,w,h = rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2) + + local overlaps, ti, nx, ny + + if rect_containsPoint(x,y,w,h, 0,0) then -- item was intersecting other + local px, py = rect_getNearestCorner(x,y,w,h, 0, 0) + local wi, hi = min(w1, abs(px)), min(h1, abs(py)) -- area of intersection + ti = -wi * hi -- ti is the negative area of intersection + overlaps = true + else + local ti1,ti2,nx1,ny1 = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, math.huge) + + -- item tunnels into other + if ti1 + and ti1 < 1 + and (abs(ti1 - ti2) >= DELTA) -- special case for rect going through another rect's corner + and (0 < ti1 + DELTA + or 0 == ti1 and ti2 > 0) + then + ti, nx, ny = ti1, nx1, ny1 + overlaps = false + end + end - local dx, dy = goalX - x1, goalY - y1 - local x,y,w,h = rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2) + if not ti then return end - local overlaps, ti, nx, ny + local tx, ty - if rect_containsPoint(x,y,w,h, 0,0) then -- item was intersecting other - local px, py = rect_getNearestCorner(x,y,w,h, 0, 0) - local wi, hi = min(w1, abs(px)), min(h1, abs(py)) -- area of intersection - ti = -wi * hi -- ti is the negative area of intersection - overlaps = true + if overlaps then + if dx == 0 and dy == 0 then + -- intersecting and not moving - use minimum displacement vector + local px, py = rect_getNearestCorner(x,y,w,h, 0,0) + if abs(px) < abs(py) then py = 0 else px = 0 end + nx, ny = sign(px), sign(py) + tx, ty = x1 + px, y1 + py else - local ti1,ti2,nx1,ny1 = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, math.huge) - - -- item tunnels into other - if ti1 - and ti1 < 1 - and (abs(ti1 - ti2) >= DELTA) -- special case for rect going through another rect's corner - and (0 < ti1 + DELTA - or 0 == ti1 and ti2 > 0) - then - ti, nx, ny = ti1, nx1, ny1 - overlaps = false - end + -- intersecting and moving - move in the opposite direction + local ti1, _ + ti1,_,nx,ny = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, 1) + if not ti1 then return end + tx, ty = x1 + dx * ti1, y1 + dy * ti1 end + else -- tunnel + tx, ty = x1 + dx * ti, y1 + dy * ti + end - if not ti then return end - - local tx, ty - - if overlaps then - if dx == 0 and dy == 0 then - -- intersecting and not moving - use minimum displacement vector - local px, py = rect_getNearestCorner(x,y,w,h, 0,0) - if abs(px) < abs(py) then py = 0 else px = 0 end - nx, ny = sign(px), sign(py) - tx, ty = x1 + px, y1 + py - else - -- intersecting and moving - move in the opposite direction - local ti1, _ - ti1,_,nx,ny = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, 1) - if not ti1 then return end - tx, ty = x1 + dx * ti1, y1 + dy * ti1 - end - else -- tunnel - tx, ty = x1 + dx * ti, y1 + dy * ti - end - - return { - overlaps = overlaps, - ti = ti, - move = {x = dx, y = dy}, - normal = {x = nx, y = ny}, - touch = {x = tx, y = ty}, - itemRect = {x = x1, y = y1, w = w1, h = h1}, - otherRect = {x = x2, y = y2, w = w2, h = h2} - } + return { + overlaps = overlaps, + ti = ti, + move = {x = dx, y = dy}, + normal = {x = nx, y = ny}, + touch = {x = tx, y = ty}, + itemRect = {x = x1, y = y1, w = w1, h = h1}, + otherRect = {x = x2, y = y2, w = w2, h = h2} + } end ------------------------------------------ @@ -180,11 +180,11 @@ end ------------------------------------------ local function grid_toWorld(cellSize, cx, cy) - return (cx - 1)*cellSize, (cy-1)*cellSize + return (cx - 1)*cellSize, (cy-1)*cellSize end local function grid_toCell(cellSize, x, y) - return floor(x / cellSize) + 1, floor(y / cellSize) + 1 + return floor(x / cellSize) + 1, floor(y / cellSize) + 1 end -- grid_traverse* functions are based on "A Fast Voxel Traversal Algorithm for Ray Tracing", @@ -193,49 +193,49 @@ end -- and with a different exit condition local function grid_traverse_initStep(cellSize, ct, t1, t2) - local v = t2 - t1 - if v > 0 then - return 1, cellSize / v, ((ct + v) * cellSize - t1) / v - elseif v < 0 then - return -1, -cellSize / v, ((ct + v - 1) * cellSize - t1) / v - else - return 0, math.huge, math.huge - end + local v = t2 - t1 + if v > 0 then + return 1, cellSize / v, ((ct + v) * cellSize - t1) / v + elseif v < 0 then + return -1, -cellSize / v, ((ct + v - 1) * cellSize - t1) / v + else + return 0, math.huge, math.huge + end end local function grid_traverse(cellSize, x1,y1,x2,y2, f) - local cx1,cy1 = grid_toCell(cellSize, x1,y1) - local cx2,cy2 = grid_toCell(cellSize, x2,y2) - local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2) - local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2) - local cx,cy = cx1,cy1 - - f(cx, cy) - - -- The default implementation had an infinite loop problem when - -- approaching the last cell in some occassions. We finish iterating - -- when we are *next* to the last cell - while abs(cx - cx2) + abs(cy - cy2) > 1 do - if tx < ty then - tx, cx = tx + dx, cx + stepX - f(cx, cy) - else - -- Addition: include both cells when going through corners - if tx == ty then f(cx + stepX, cy) end - ty, cy = ty + dy, cy + stepY - f(cx, cy) - end + local cx1,cy1 = grid_toCell(cellSize, x1,y1) + local cx2,cy2 = grid_toCell(cellSize, x2,y2) + local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2) + local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2) + local cx,cy = cx1,cy1 + + f(cx, cy) + + -- The default implementation had an infinite loop problem when + -- approaching the last cell in some occassions. We finish iterating + -- when we are *next* to the last cell + while abs(cx - cx2) + abs(cy - cy2) > 1 do + if tx < ty then + tx, cx = tx + dx, cx + stepX + f(cx, cy) + else + -- Addition: include both cells when going through corners + if tx == ty then f(cx + stepX, cy) end + ty, cy = ty + dy, cy + stepY + f(cx, cy) end + end - -- If we have not arrived to the last cell, use it - if cx ~= cx2 or cy ~= cy2 then f(cx2, cy2) end + -- If we have not arrived to the last cell, use it + if cx ~= cx2 or cy ~= cy2 then f(cx2, cy2) end end local function grid_toCellRect(cellSize, x,y,w,h) - local cx,cy = grid_toCell(cellSize, x, y) - local cr,cb = ceil((x+w) / cellSize), ceil((y+h) / cellSize) - return cx, cy, cr - cx + 1, cb - cy + 1 + local cx,cy = grid_toCell(cellSize, x, y) + local cr,cb = ceil((x+w) / cellSize), ceil((y+h) / cellSize) + return cx, cy, cr - cx + 1, cb - cy + 1 end ------------------------------------------ @@ -243,55 +243,55 @@ end ------------------------------------------ local touch = function(world, col, x,y,w,h, goalX, goalY, filter) - return col.touch.x, col.touch.y, {}, 0 + return col.touch.x, col.touch.y, {}, 0 end local cross = function(world, col, x,y,w,h, goalX, goalY, filter) - local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter) - return goalX, goalY, cols, len + local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter) + return goalX, goalY, cols, len end local slide = function(world, col, x,y,w,h, goalX, goalY, filter) - goalX = goalX or x - goalY = goalY or y - - local tch, move = col.touch, col.move - if move.x ~= 0 or move.y ~= 0 then - if col.normal.x ~= 0 then - goalX = tch.x - else - goalY = tch.y - end + goalX = goalX or x + goalY = goalY or y + + local tch, move = col.touch, col.move + if move.x ~= 0 or move.y ~= 0 then + if col.normal.x ~= 0 then + goalX = tch.x + else + goalY = tch.y end + end - col.slide = {x = goalX, y = goalY} + col.slide = {x = goalX, y = goalY} - x,y = tch.x, tch.y - local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter) - return goalX, goalY, cols, len + x,y = tch.x, tch.y + local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter) + return goalX, goalY, cols, len end local bounce = function(world, col, x,y,w,h, goalX, goalY, filter) - goalX = goalX or x - goalY = goalY or y + goalX = goalX or x + goalY = goalY or y - local tch, move = col.touch, col.move - local tx, ty = tch.x, tch.y + local tch, move = col.touch, col.move + local tx, ty = tch.x, tch.y - local bx, by = tx, ty + local bx, by = tx, ty - if move.x ~= 0 or move.y ~= 0 then - local bnx, bny = goalX - tx, goalY - ty - if col.normal.x == 0 then bny = -bny else bnx = -bnx end - bx, by = tx + bnx, ty + bny - end + if move.x ~= 0 or move.y ~= 0 then + local bnx, bny = goalX - tx, goalY - ty + if col.normal.x == 0 then bny = -bny else bnx = -bnx end + bx, by = tx + bnx, ty + bny + end - col.bounce = {x = bx, y = by} - x,y = tch.x, tch.y - goalX, goalY = bx, by + col.bounce = {x = bx, y = by} + x,y = tch.x, tch.y + goalX, goalY = bx, by - local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter) - return goalX, goalY, cols, len + local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter) + return goalX, goalY, cols, len end ------------------------------------------ @@ -306,211 +306,211 @@ local World_mt = {__index = World} local function sortByWeight(a,b) return a.weight < b.weight end local function sortByTiAndDistance(a,b) - if a.ti == b.ti then - local ir, ar, br = a.itemRect, a.otherRect, b.otherRect - local ad = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, ar.x,ar.y,ar.w,ar.h) - local bd = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, br.x,br.y,br.w,br.h) - return ad < bd - end - return a.ti < b.ti + if a.ti == b.ti then + local ir, ar, br = a.itemRect, a.otherRect, b.otherRect + local ad = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, ar.x,ar.y,ar.w,ar.h) + local bd = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, br.x,br.y,br.w,br.h) + return ad < bd + end + return a.ti < b.ti end local function addItemToCell(self, item, cx, cy) - self.rows[cy] = self.rows[cy] or setmetatable({}, {__mode = 'v'}) - local row = self.rows[cy] - row[cx] = row[cx] or {itemCount = 0, x = cx, y = cy, items = setmetatable({}, {__mode = 'k'})} - local cell = row[cx] - self.nonEmptyCells[cell] = true - if not cell.items[item] then - cell.items[item] = true - cell.itemCount = cell.itemCount + 1 - end + self.rows[cy] = self.rows[cy] or setmetatable({}, {__mode = 'v'}) + local row = self.rows[cy] + row[cx] = row[cx] or {itemCount = 0, x = cx, y = cy, items = setmetatable({}, {__mode = 'k'})} + local cell = row[cx] + self.nonEmptyCells[cell] = true + if not cell.items[item] then + cell.items[item] = true + cell.itemCount = cell.itemCount + 1 + end end local function removeItemFromCell(self, item, cx, cy) - local row = self.rows[cy] - if not row or not row[cx] or not row[cx].items[item] then return false end + local row = self.rows[cy] + if not row or not row[cx] or not row[cx].items[item] then return false end - local cell = row[cx] - cell.items[item] = nil - cell.itemCount = cell.itemCount - 1 - if cell.itemCount == 0 then - self.nonEmptyCells[cell] = nil - end - return true + local cell = row[cx] + cell.items[item] = nil + cell.itemCount = cell.itemCount - 1 + if cell.itemCount == 0 then + self.nonEmptyCells[cell] = nil + end + return true end local function getDictItemsInCellRect(self, cl,ct,cw,ch) - local items_dict = {} - for cy=ct,ct+ch-1 do - local row = self.rows[cy] - if row then - for cx=cl,cl+cw-1 do - local cell = row[cx] - if cell and cell.itemCount > 0 then -- no cell.itemCount > 1 because tunneling - for item,_ in pairs(cell.items) do - items_dict[item] = true - end - end - end + local items_dict = {} + for cy=ct,ct+ch-1 do + local row = self.rows[cy] + if row then + for cx=cl,cl+cw-1 do + local cell = row[cx] + if cell and cell.itemCount > 0 then -- no cell.itemCount > 1 because tunneling + for item,_ in pairs(cell.items) do + items_dict[item] = true + end end + end end + end - return items_dict + return items_dict end local function getCellsTouchedBySegment(self, x1,y1,x2,y2) - local cells, cellsLen, visited = {}, 0, {} + local cells, cellsLen, visited = {}, 0, {} - grid_traverse(self.cellSize, x1,y1,x2,y2, function(cx, cy) - local row = self.rows[cy] - if not row then return end - local cell = row[cx] - if not cell or visited[cell] then return end + grid_traverse(self.cellSize, x1,y1,x2,y2, function(cx, cy) + local row = self.rows[cy] + if not row then return end + local cell = row[cx] + if not cell or visited[cell] then return end - visited[cell] = true - cellsLen = cellsLen + 1 - cells[cellsLen] = cell - end) + visited[cell] = true + cellsLen = cellsLen + 1 + cells[cellsLen] = cell + end) - return cells, cellsLen + return cells, cellsLen end local function getInfoAboutItemsTouchedBySegment(self, x1,y1, x2,y2, filter) - local cells, len = getCellsTouchedBySegment(self, x1,y1,x2,y2) - local cell, rect, l,t,w,h, ti1,ti2, tii0,tii1 - local visited, itemInfo, itemInfoLen = {},{},0 - for i=1,len do - cell = cells[i] - for item in pairs(cell.items) do - if not visited[item] then - visited[item] = true - if (not filter or filter(item)) then - rect = self.rects[item] - l,t,w,h = rect.x,rect.y,rect.w,rect.h - - ti1,ti2 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, 0, 1) - if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then - -- the sorting is according to the t of an infinite line, not the segment - tii0,tii1 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, -math.huge, math.huge) - itemInfoLen = itemInfoLen + 1 - itemInfo[itemInfoLen] = {item = item, ti1 = ti1, ti2 = ti2, weight = min(tii0,tii1)} - end - end - end + local cells, len = getCellsTouchedBySegment(self, x1,y1,x2,y2) + local cell, rect, l,t,w,h, ti1,ti2, tii0,tii1 + local visited, itemInfo, itemInfoLen = {},{},0 + for i=1,len do + cell = cells[i] + for item in pairs(cell.items) do + if not visited[item] then + visited[item] = true + if (not filter or filter(item)) then + rect = self.rects[item] + l,t,w,h = rect.x,rect.y,rect.w,rect.h + + ti1,ti2 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, 0, 1) + if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then + -- the sorting is according to the t of an infinite line, not the segment + tii0,tii1 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, -math.huge, math.huge) + itemInfoLen = itemInfoLen + 1 + itemInfo[itemInfoLen] = {item = item, ti1 = ti1, ti2 = ti2, weight = min(tii0,tii1)} + end end + end end - table.sort(itemInfo, sortByWeight) - return itemInfo, itemInfoLen + end + table.sort(itemInfo, sortByWeight) + return itemInfo, itemInfoLen end local function getResponseByName(self, name) - local response = self.responses[name] - if not response then - error(('Unknown collision type: %s (%s)'):format(name, type(name))) - end - return response + local response = self.responses[name] + if not response then + error(('Unknown collision type: %s (%s)'):format(name, type(name))) + end + return response end -- Misc Public Methods function World:addResponse(name, response) - self.responses[name] = response + self.responses[name] = response end function World:project(item, x,y,w,h, goalX, goalY, filter) - assertIsRect(x,y,w,h) + assertIsRect(x,y,w,h) - goalX = goalX or x - goalY = goalY or y - filter = filter or defaultFilter + goalX = goalX or x + goalY = goalY or y + filter = filter or defaultFilter - local collisions, len = {}, 0 + local collisions, len = {}, 0 - local visited = {} - if item ~= nil then visited[item] = true end + local visited = {} + if item ~= nil then visited[item] = true end - -- This could probably be done with less cells using a polygon raster over the cells instead of a - -- bounding rect of the whole movement. Conditional to building a queryPolygon method - local tl, tt = min(goalX, x), min(goalY, y) - local tr, tb = max(goalX + w, x+w), max(goalY + h, y+h) - local tw, th = tr-tl, tb-tt + -- This could probably be done with less cells using a polygon raster over the cells instead of a + -- bounding rect of the whole movement. Conditional to building a queryPolygon method + local tl, tt = min(goalX, x), min(goalY, y) + local tr, tb = max(goalX + w, x+w), max(goalY + h, y+h) + local tw, th = tr-tl, tb-tt - local cl,ct,cw,ch = grid_toCellRect(self.cellSize, tl,tt,tw,th) + local cl,ct,cw,ch = grid_toCellRect(self.cellSize, tl,tt,tw,th) - local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch) + local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch) - for other,_ in pairs(dictItemsInCellRect) do - if not visited[other] then - visited[other] = true + for other,_ in pairs(dictItemsInCellRect) do + if not visited[other] then + visited[other] = true - local responseName = filter(item, other) - if responseName then - local ox,oy,ow,oh = self:getRect(other) - local col = rect_detectCollision(x,y,w,h, ox,oy,ow,oh, goalX, goalY) + local responseName = filter(item, other) + if responseName then + local ox,oy,ow,oh = self:getRect(other) + local col = rect_detectCollision(x,y,w,h, ox,oy,ow,oh, goalX, goalY) - if col then - col.other = other - col.item = item - col.type = responseName + if col then + col.other = other + col.item = item + col.type = responseName - len = len + 1 - collisions[len] = col - end - end + len = len + 1 + collisions[len] = col end + end end + end - table.sort(collisions, sortByTiAndDistance) + table.sort(collisions, sortByTiAndDistance) - return collisions, len + return collisions, len end function World:countCells() - local count = 0 - for _,row in pairs(self.rows) do - for _,_ in pairs(row) do - count = count + 1 - end + local count = 0 + for _,row in pairs(self.rows) do + for _,_ in pairs(row) do + count = count + 1 end - return count + end + return count end function World:hasItem(item) - return not not self.rects[item] + return not not self.rects[item] end function World:getItems() - local items, len = {}, 0 - for item,_ in pairs(self.rects) do - len = len + 1 - items[len] = item - end - return items, len + local items, len = {}, 0 + for item,_ in pairs(self.rects) do + len = len + 1 + items[len] = item + end + return items, len end function World:countItems() - local len = 0 - for _ in pairs(self.rects) do len = len + 1 end - return len + local len = 0 + for _ in pairs(self.rects) do len = len + 1 end + return len end function World:getRect(item) - local rect = self.rects[item] - if not rect then - error('Item ' .. tostring(item) .. ' must be added to the world before getting its rect. Use world:add(item, x,y,w,h) to add it first.') - end - return rect.x, rect.y, rect.w, rect.h + local rect = self.rects[item] + if not rect then + error('Item ' .. tostring(item) .. ' must be added to the world before getting its rect. Use world:add(item, x,y,w,h) to add it first.') + end + return rect.x, rect.y, rect.w, rect.h end function World:toWorld(cx, cy) - return grid_toWorld(self.cellSize, cx, cy) + return grid_toWorld(self.cellSize, cx, cy) end function World:toCell(x,y) - return grid_toCell(self.cellSize, x, y) + return grid_toCell(self.cellSize, x, y) end @@ -518,231 +518,232 @@ end function World:queryRect(x,y,w,h, filter) - assertIsRect(x,y,w,h) + assertIsRect(x,y,w,h) - local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h) - local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch) + local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h) + local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch) - local items, len = {}, 0 + local items, len = {}, 0 - local rect - for item,_ in pairs(dictItemsInCellRect) do - rect = self.rects[item] - if (not filter or filter(item)) - and rect_isIntersecting(x,y,w,h, rect.x, rect.y, rect.w, rect.h) - then - len = len + 1 - items[len] = item - end + local rect + for item,_ in pairs(dictItemsInCellRect) do + rect = self.rects[item] + if (not filter or filter(item)) + and rect_isIntersecting(x,y,w,h, rect.x, rect.y, rect.w, rect.h) + then + len = len + 1 + items[len] = item end + end - return items, len + return items, len end function World:queryPoint(x,y, filter) - local cx,cy = self:toCell(x,y) - local dictItemsInCellRect = getDictItemsInCellRect(self, cx,cy,1,1) - - local items, len = {}, 0 - - local rect - for item,_ in pairs(dictItemsInCellRect) do - rect = self.rects[item] - if (not filter or filter(item)) - and rect_containsPoint(rect.x, rect.y, rect.w, rect.h, x, y) - then - len = len + 1 - items[len] = item - end + local cx,cy = self:toCell(x,y) + local dictItemsInCellRect = getDictItemsInCellRect(self, cx,cy,1,1) + + local items, len = {}, 0 + + local rect + for item,_ in pairs(dictItemsInCellRect) do + rect = self.rects[item] + if (not filter or filter(item)) + and rect_containsPoint(rect.x, rect.y, rect.w, rect.h, x, y) + then + len = len + 1 + items[len] = item end + end - return items, len + return items, len end function World:querySegment(x1, y1, x2, y2, filter) - local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter) - local items = {} - for i=1, len do - items[i] = itemInfo[i].item - end - return items, len + local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter) + local items = {} + for i=1, len do + items[i] = itemInfo[i].item + end + return items, len end function World:querySegmentWithCoords(x1, y1, x2, y2, filter) - local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter) - local dx, dy = x2-x1, y2-y1 - local info, ti1, ti2 - for i=1, len do - info = itemInfo[i] - ti1 = info.ti1 - ti2 = info.ti2 - - info.weight = nil - info.x1 = x1 + dx * ti1 - info.y1 = y1 + dy * ti1 - info.x2 = x1 + dx * ti2 - info.y2 = y1 + dy * ti2 - end - return itemInfo, len + local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter) + local dx, dy = x2-x1, y2-y1 + local info, ti1, ti2 + for i=1, len do + info = itemInfo[i] + ti1 = info.ti1 + ti2 = info.ti2 + + info.weight = nil + info.x1 = x1 + dx * ti1 + info.y1 = y1 + dy * ti1 + info.x2 = x1 + dx * ti2 + info.y2 = y1 + dy * ti2 + end + return itemInfo, len end + --- Main methods function World:add(item, x,y,w,h) - local rect = self.rects[item] - if rect then - error('Item ' .. tostring(item) .. ' added to the world twice.') - end - assertIsRect(x,y,w,h) - - self.rects[item] = {x=x,y=y,w=w,h=h} - - local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h) - for cy = ct, ct+ch-1 do - for cx = cl, cl+cw-1 do - addItemToCell(self, item, cx, cy) - end + local rect = self.rects[item] + if rect then + error('Item ' .. tostring(item) .. ' added to the world twice.') + end + assertIsRect(x,y,w,h) + + self.rects[item] = {x=x,y=y,w=w,h=h} + + local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h) + for cy = ct, ct+ch-1 do + for cx = cl, cl+cw-1 do + addItemToCell(self, item, cx, cy) end + end - return item + return item end function World:remove(item) - local x,y,w,h = self:getRect(item) + local x,y,w,h = self:getRect(item) - self.rects[item] = nil - local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h) - for cy = ct, ct+ch-1 do - for cx = cl, cl+cw-1 do - removeItemFromCell(self, item, cx, cy) - end + self.rects[item] = nil + local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h) + for cy = ct, ct+ch-1 do + for cx = cl, cl+cw-1 do + removeItemFromCell(self, item, cx, cy) end + end end function World:update(item, x2,y2,w2,h2) - local x1,y1,w1,h1 = self:getRect(item) - w2,h2 = w2 or w1, h2 or h1 - assertIsRect(x2,y2,w2,h2) - - if x1 ~= x2 or y1 ~= y2 or w1 ~= w2 or h1 ~= h2 then - - local cellSize = self.cellSize - local cl1,ct1,cw1,ch1 = grid_toCellRect(cellSize, x1,y1,w1,h1) - local cl2,ct2,cw2,ch2 = grid_toCellRect(cellSize, x2,y2,w2,h2) - - if cl1 ~= cl2 or ct1 ~= ct2 or cw1 ~= cw2 or ch1 ~= ch2 then - - local cr1, cb1 = cl1+cw1-1, ct1+ch1-1 - local cr2, cb2 = cl2+cw2-1, ct2+ch2-1 - local cyOut - - for cy = ct1, cb1 do - cyOut = cy < ct2 or cy > cb2 - for cx = cl1, cr1 do - if cyOut or cx < cl2 or cx > cr2 then - removeItemFromCell(self, item, cx, cy) - end - end - end - - for cy = ct2, cb2 do - cyOut = cy < ct1 or cy > cb1 - for cx = cl2, cr2 do - if cyOut or cx < cl1 or cx > cr1 then - addItemToCell(self, item, cx, cy) - end - end - end + local x1,y1,w1,h1 = self:getRect(item) + w2,h2 = w2 or w1, h2 or h1 + assertIsRect(x2,y2,w2,h2) + + if x1 ~= x2 or y1 ~= y2 or w1 ~= w2 or h1 ~= h2 then + local cellSize = self.cellSize + local cl1,ct1,cw1,ch1 = grid_toCellRect(cellSize, x1,y1,w1,h1) + local cl2,ct2,cw2,ch2 = grid_toCellRect(cellSize, x2,y2,w2,h2) + + if cl1 ~= cl2 or ct1 ~= ct2 or cw1 ~= cw2 or ch1 ~= ch2 then + + local cr1, cb1 = cl1+cw1-1, ct1+ch1-1 + local cr2, cb2 = cl2+cw2-1, ct2+ch2-1 + local cyOut + + for cy = ct1, cb1 do + cyOut = cy < ct2 or cy > cb2 + for cx = cl1, cr1 do + if cyOut or cx < cl2 or cx > cr2 then + removeItemFromCell(self, item, cx, cy) + end end + end - local rect = self.rects[item] - rect.x, rect.y, rect.w, rect.h = x2,y2,w2,h2 + for cy = ct2, cb2 do + cyOut = cy < ct1 or cy > cb1 + for cx = cl2, cr2 do + if cyOut or cx < cl1 or cx > cr1 then + addItemToCell(self, item, cx, cy) + end + end + end end + + local rect = self.rects[item] + rect.x, rect.y, rect.w, rect.h = x2,y2,w2,h2 + + end end function World:move(item, goalX, goalY, filter) - local actualX, actualY, cols, len = self:check(item, goalX, goalY, filter) + local actualX, actualY, cols, len = self:check(item, goalX, goalY, filter) - self:update(item, actualX, actualY) + self:update(item, actualX, actualY) - return actualX, actualY, cols, len + return actualX, actualY, cols, len end function World:check(item, goalX, goalY, filter) - filter = filter or defaultFilter + filter = filter or defaultFilter - local visited = {[item] = true} - local visitedFilter = function(itm, other) - if visited[other] then return false end - return filter(itm, other) - end + local visited = {[item] = true} + local visitedFilter = function(itm, other) + if visited[other] then return false end + return filter(itm, other) + end - local cols, len = {}, 0 + local cols, len = {}, 0 - local x,y,w,h = self:getRect(item) + local x,y,w,h = self:getRect(item) - local projected_cols, projected_len = self:project(item, x,y,w,h, goalX,goalY, visitedFilter) + local projected_cols, projected_len = self:project(item, x,y,w,h, goalX,goalY, visitedFilter) - while projected_len > 0 do - local col = projected_cols[1] - len = len + 1 - cols[len] = col + while projected_len > 0 do + local col = projected_cols[1] + len = len + 1 + cols[len] = col - visited[col.other] = true + visited[col.other] = true - local response = getResponseByName(self, col.type) + local response = getResponseByName(self, col.type) - goalX, goalY, projected_cols, projected_len = response( - self, - col, - x, y, w, h, - goalX, goalY, - visitedFilter - ) - end + goalX, goalY, projected_cols, projected_len = response( + self, + col, + x, y, w, h, + goalX, goalY, + visitedFilter + ) + end - return goalX, goalY, cols, len + return goalX, goalY, cols, len end -- Public library functions bump.newWorld = function(cellSize) - cellSize = cellSize or 64 - assertIsPositiveNumber(cellSize, 'cellSize') - local world = setmetatable({ - cellSize = cellSize, - rects = {}, - rows = {}, - nonEmptyCells = {}, - responses = {} - }, World_mt) + cellSize = cellSize or 64 + assertIsPositiveNumber(cellSize, 'cellSize') + local world = setmetatable({ + cellSize = cellSize, + rects = {}, + rows = {}, + nonEmptyCells = {}, + responses = {} + }, World_mt) - world:addResponse('touch', touch) - world:addResponse('cross', cross) - world:addResponse('slide', slide) - world:addResponse('bounce', bounce) + world:addResponse('touch', touch) + world:addResponse('cross', cross) + world:addResponse('slide', slide) + world:addResponse('bounce', bounce) - return world + return world end bump.rect = { - getNearestCorner = rect_getNearestCorner, - getSegmentIntersectionIndices = rect_getSegmentIntersectionIndices, - getDiff = rect_getDiff, - containsPoint = rect_containsPoint, - isIntersecting = rect_isIntersecting, - getSquareDistance = rect_getSquareDistance, - detectCollision = rect_detectCollision + getNearestCorner = rect_getNearestCorner, + getSegmentIntersectionIndices = rect_getSegmentIntersectionIndices, + getDiff = rect_getDiff, + containsPoint = rect_containsPoint, + isIntersecting = rect_isIntersecting, + getSquareDistance = rect_getSquareDistance, + detectCollision = rect_detectCollision } bump.responses = { - touch = touch, - cross = cross, - slide = slide, - bounce = bounce + touch = touch, + cross = cross, + slide = slide, + bounce = bounce } -- Export to Jin. |