Field of View

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

@BigZaphod Interesting. I like seeing programs that demonstrate something or give a visual representation of what’s happening.

oh, very nice. thank you for sharing this.

Very nice, like the “fog of war” effect in games like LaserSquad or Xcom.

@BigZaphod This is really interesting. I haven’t tested peoples code in a while, but I took the time to test this one just because it looked so awesome! Great Job!

Can I get permission to use this as an effect for my game?

Go for it - it’s not like I invented it. :stuck_out_tongue:

Well done, mate!
That’s a very cool feature for a small project :smiley:
Good luck to you in your endeavors!

just found this. will save me writing it. yay! thanks!