Here’s some code that calculates the visible field of view on a grid. I love this sort of stuff and figured someone else might like it, too.
--# Main
map = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
}
function isBlockedAt(pos)
if pos.x < 1 or pos.x > 17 or pos.y < 1 or pos.y > 17 then return true end
return map[18-pos.y][pos.x] == 1
end
function setup()
parameter.integer("Radius", 1, 25, 5)
parameter.boolean("DrawWalls")
end
-- This function gets called once every frame
function draw()
background(40, 40, 50)
stroke(128, 128, 128, 255)
strokeWidth(2)
local size = 38
for y=1,17 do
for x=1,17 do
if map[y][x] == 1 then
fill(225, 172, 54, 255)
else
noFill()
end
rect(x*size, (18-y)*size, size, size)
end
end
local x = math.floor(CurrentTouch.x / size)
local y = math.floor(CurrentTouch.y / size)
noFill()
stroke(255, 255, 255, 255)
ellipse(x*size+size/2, y*size+size/2, size)
floors, walls, shadows = visualField(vec2(x, y), Radius, isBlockedAt)
fill(255, 255, 255, 69)
noStroke()
for _,pos in pairs(floors) do
rect(pos.x*size,pos.y*size, size, size)
end
if DrawWalls then
noFill()
stroke(255, 0, 0, 255)
strokeWidth(3)
for _,pos in pairs(walls) do
rect(pos.x*size,pos.y*size, size, size)
end
end
end
--# FOV
-- code adapted from http://journal.stuffwithstuff.com/2015/09/07/what-the-hero-sees/
local function isInShadow(shadows, other)
for _, shadow in pairs(shadows) do
if shadow.start <= other.start and shadow.stop >= other.stop then
return true
end
end
end
local function addShadow(shadows, shadow)
local index = 1
local overlappingPreviousIndex
local overlappingNextIndex
while index <= #shadows do
if shadows[index].start >= shadow.start then
break
else
index = index + 1
end
end
if index > 1 and shadows[index - 1].stop > shadow.start then
overlappingPreviousIndex = index - 1
end
if index <= #shadows and shadows[index].start < shadow.stop then
overlappingNextIndex = index
end
if overlappingNextIndex then
if overlappingPreviousIndex then
shadows[overlappingPreviousIndex].stop = shadows[overlappingNextIndex].stop
table.remove(shadows, index)
else
shadows[overlappingNextIndex].start = shadow.start
end
else
if overlappingPreviousIndex then
shadows[overlappingPreviousIndex].stop = shadow.stop
else
table.insert(shadows, index, shadow)
end
end
end
local function isFullShadow(shadows)
return #shadows == 1 and shadows[1].start == 0 and shadows[1].stop == 1
end
function visualField(pos, radius, blocked)
local SHADOW = 0
local LIGHT = 1
local WALL = 2
local width = (radius * 2) + 1
local vis = {}
for octant=1,8 do
local shadowLine = {}
local lineIsFullShadow = false
local delta = ({
function(x, y) return vec2(x, -y) end,
function(x, y) return vec2(y, -x) end,
function(x, y) return vec2(y, x) end,
function(x, y) return vec2(x, y) end,
function(x, y) return vec2(-x, y) end,
function(x, y) return vec2(-y, x) end,
function(x, y) return vec2(-y, -x) end,
function(x, y) return vec2(-x, -y) end,
})[octant]
for row=0,radius do if lineIsFullShadow then break end
for col=0,row do if lineIsFullShadow then break end
local offset = delta(col,row)
local at = pos + offset
local visible = SHADOW
if not lineIsFullShadow and at:dist(pos) <= radius then
local projection = {start = col / (row + 2), stop = (col + 1) / (row + 1)}
if not isInShadow(shadowLine, projection) then
visible = LIGHT
if blocked(at) then
addShadow(shadowLine, projection)
lineIsFullShadow = isFullShadow(shadowLine)
visible = WALL
end
end
end
vis[(offset.y+radius) * width + (offset.x+radius)] = visible
end
end
end
local visible = {}
local walls = {}
for i,visibility in pairs(vis) do
local x = math.floor(i % width)-radius
local y = math.floor(i / width)-radius
local at = vec2(x, y) + pos
if visibility == LIGHT then
table.insert(visible, at)
elseif visibility == WALL then
table.insert(walls, at)
end
end
return visible, walls
end