diff --git a/src/abstract_grid_world.jl b/src/abstract_grid_world.jl index e022817..fe4d365 100644 --- a/src/abstract_grid_world.jl +++ b/src/abstract_grid_world.jl @@ -32,5 +32,3 @@ get_actions(w::AbstractGridWorld) = (MOVE_FORWARD, TURN_LEFT, TURN_RIGHT) get_agent_view_inds(w::AbstractGridWorld, s=(7,7)) = get_agent_view_inds(get_agent_pos(w).I, s, get_agent_dir(w)) -get_agent_view!(v::BitArray{3}, w::AbstractGridWorld) = get_agent_view!(v, convert(GridWorldBase, w), get_agent_pos(w), get_agent_dir(w)) - diff --git a/src/grid_world_base.jl b/src/grid_world_base.jl index cf3fb14..0bab9a2 100644 --- a/src/grid_world_base.jl +++ b/src/grid_world_base.jl @@ -60,6 +60,54 @@ function Random.rand(f::Function, w::GridWorldBase; max_try=typemax(Int), rng=Ra return nothing end +##### +# Occlusion +##### + +radius(x, y) = √(x^2 + y^2) +radius(p::Tuple) = radius(p[1], p[2]) +theta(x, y) = x == 0 ? sign(y)*π/2 : atan(y, x) +theta(p::Tuple) = theta(p[1], p[2]) + +struct PolarCoord + θ::AbstractFloat + r::AbstractFloat +end +PolarCoord(θ::Real, r::Real) = PolarCoord((θ + (r<0 && π)) % 2π, abs(r)) +PolarCoord(p::Tuple) = PolarCoord(theta(p), radius(p)) +PolarCoord(p::CartesianIndex) = PolarCoord(Tuple(p)) + +struct Shadow + minθ::AbstractFloat + maxθ::AbstractFloat + r::AbstractFloat +end +function Shadow(p::CartesianIndex) + r = radius(Tuple(p)) + corners = [(p[1]+x, p[2]+y) for x in -.5:.5, y in -.5:.5] + corners = theta.(corners) + Shadow(minimum(corners), maximum(corners), r) +end + +""" +returns a 2D array of boolean values, where `true` represents an index which is +occluded by the shadow `s` evaluating `v` +""" +function (s::Shadow)(v::CartesianIndices) + polar = PolarCoord.(v)`` + f(x) = x.r > s.r && s.minθ <= x.θ <= s.maxθ + f.(polar) +end + +function shadowmap(m::Matrix{Bool}) + indices = CartesianIndices(((-1*size(m)[1]÷2):(size(m)[1]÷2), 0:(size(m)[2]-1))) + shadows = Shadow.(indices[m]) + output = fill(false, size(a)) + for s in shadows + output |= s(indices) + end + output +end ##### # get_agent_view ##### @@ -74,14 +122,46 @@ ind_map((i,j), (m, n), ::Right) = (j, n-i+1) ind_map((i,j), (m, n), ::Up) = (m-i+1, n-j+1) ind_map((i,j), (m, n), ::Down) = (i,j) -function get_agent_view!(v::AbstractArray{Bool,3}, a::AbstractArray{Bool,3}, p::CartesianIndex, dir::LRUD) +""" +Updates the agent view + +Args: + v::AbstractArray{Bool,3}: the current agent view as a 3D array with indices [type, x, y] + a::AbstractArray{Bool,3}: the current environment as a 3D array with indices [type, x, y] + p::CartesianIndex: location of the agent + dir::LRUD: direction the agent is looking +""" +function get_agent_view!(v::AbstractArray{Bool,3}, w::AbstractGridWorld; perspective::Bool=true) + a = convert(GridWorldBase, w) + p = get_agent_pos(w) + dir = get_agent_dir(w) + view_size = (size(v, 2), size(v, 3)) grid_size = (size(a,2),size(a,3)) - inds = get_agent_view_inds(p.I, view_size, dir) - valid_inds = CartesianIndices(grid_size) - for ind in CartesianIndices(inds) - if inds[ind] ∈ valid_inds - v[:, ind_map(ind.I, view_size, dir)...] .= a[:, inds[ind]] + inds = get_agent_view_inds(p.I, view_size, dir) # indices of the visible points + valid_inds = CartesianIndices(grid_size) # CartesianIndices representing the whole environment + + if perspective + m = convert(Matrix{Bool}, copy(v)) + shadows = [] + for ind in CartesianIndices(inds) # for every index in view... + if inds[ind] ∈ valid_inds # if it's within the environment... + o = findfirst(w[:, inds[ind]]) # first object at the index + m[ind_map(ind.I, view_size, dir)...] .= isnothing(o) ? false : isopaque(w.objects[o]) <: Opaque + end + end + shadows = .!(shadowmap(m)) + for ind in CartesianIndices(inds) + if inds[ind] ∈ valid_inds && shadows[ind_map(ind.I, view_size, dir)...] + v[:, ind_map(ind.I, view_size, dir)...] .= a[:, inds[ind]] + end + end + else + for ind in CartesianIndices(inds) # for every index in view... + if inds[ind] ∈ valid_inds # if it's within the environment... + # set its corresponding value in the view to the value in the environment + v[:, ind_map(ind.I, view_size, dir)...] .= a[:, inds[ind]] + end end end v diff --git a/src/objects.jl b/src/objects.jl index 1fb947e..5d21b2d 100644 --- a/src/objects.jl +++ b/src/objects.jl @@ -30,8 +30,10 @@ const GOAL = Goal() Base.convert(::Type{Char}, ::Goal) = '♥' get_color(::Goal) = :red -struct Door{C} <: AbstractObject end -Door(c) = Door{c}() +struct Door{C} <: AbstractObject + open::Bool +end +Door(c) = Door{c}(false) Base.convert(::Type{Char}, ::Door) = '⩎' get_color(::Door{C}) where C = C @@ -84,6 +86,16 @@ istransportable(::Type{<:Key}) = TRANSPORTABLE istransportable(::Type{Gem}) = TRANSPORTABLE istransportable(x::AbstractObject) = istransportable(typeof(x)) +struct Opaque end +struct Transparent end +const OPAQUE = Opaque() +const TRANSPARENT = Transparent() +isopaque(::Type{<:AbstractObject}) = OPAQUE +isopaque(::Type{<:Key}) = TRANSPARENT +isopaque(::Type{Gem}) = TRANSPARENT +isopaque(x::AbstractObject) = isopaque(typeof(x)) +isopaque(x<:Door) = x.open ? TRANSPARENT : OPAQUE + (x::Pickup)(a::Agent, o) = x(istransportable(o), a, o) function (::Pickup)(::Transportable, a::Agent, o::AbstractObject) diff --git a/test/runtests.jl b/test/runtests.jl index 8a28222..64dbf1f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -28,4 +28,21 @@ ACTIONS = [TURN_LEFT, TURN_RIGHT, MOVE_FORWARD] end end end + @testset "grid_world_base.jl" begin + grid = CartesianIndices((-3:3, 0:6)) + soln = + [[1,1,1,1,1,1,1] + [1,1,1,1,1,1,1] + [1,1,1,1,1,1,1] + [1,1,1,1,1,1,1] + [1,1,1,1,0,0,1] + [0,0,1,1,0,0,1] + [0,1,1,1,0,0,0]] + + s = GridWorlds.Shadow(CartesianIndex((1,3))) + println("minθ:$(s.minθ)|maxθ:$(s.maxθ)|r:$(s.r)") + println(s(grid)) + println("($(GridWorlds.theta(3,4)), $(GridWorlds.radius(3,4)))") + println(s(CartesianIndices((3:3, 5:5)))) + end end