Prefiro lançar raios de sombra em vez de raios de linha de visão.
Digamos que esta seja sua área de visualização (a área potencialmente visível)
######################
#####.............####
###................###
##..................##
#....................#
#....................#
#..........@.........#
#....................#
#....................#
##..................##
###................###
#####.............####
######################
Os # blocos não são visíveis enquanto o. são visíveis
Vamos colocar um obstáculo X:
######################
#####.............####
###................###
##.....X.....XXX....##
#......X.......X.....#
#...X.XX.............#
#...X......@.........#
#...X..........X.....#
#...XXXXXX...........#
##..................##
###....X...........###
#####.............####
######################
Você tem uma lista dos X que estão na área de visualização e marca como ocultos todos os blocos que estão atrás de cada um desses obstáculos: quando um obstáculo é marcado como oculto, você o remove da lista.
######################
#####.............####
###................###
##.....X.....XXX....##
#......X.......X.....#
#...X.XX.............#
#...X......@.........#
#...X..........X.....#
#...XXXXX*...........#
##......##..........##
###....*#..........###
#####.###.........####
######################
No exemplo acima, você pode ver a sombra projetada pela extremidade direita da parede inferior e como essa sombra exclui o obstáculo oculto da lista do obstáculo que você deve verificar (X precisa verificar; * verificado).
Se você classificar a lista usando alguma partição binária, para que o X mais confortável seja verificado primeiro, você pode acelerar um pouco sua verificação.
Você pode usar uma espécie de algoritmo "Batalhas Naval" para verificar o bloco de Xs de uma só vez (basicamente procurando por um X adiacent que esteja em uma direção que possa aumentar o cone da sombra)
[EDITAR]
São necessários dois raios para projetar corretamente uma sombra e, como um bloco é retangular, muitas suposições podem ser feitas usando as simetrias disponíveis.
As coordenadas dos raios podem ser calculadas usando um particionamento simples de espaço ao redor do bloco de obstáculos:
Cada área retangular constitui uma escolha sobre qual canto do ladrilho deve ser considerado como borda do cone da sombra.
Esse raciocínio pode ser aprimorado para conectar vários ladrilhos adjacentes e permitir que eles projetem um único cone mais largo, conforme a seguir.
O primeiro passo é garantir que não haja obstáculos na direção do observador; nesse caso, o obstáculo mais próximo é considerado:
Se o ladrilho amarelo é um obstáculo, esse ladrilho se torna o novo ladrilho vermelho.
Agora vamos considerar a borda superior do cone:
Os azulejos azuis são todos possíveis candidatos a deixar o cone da sombra mais largo: se pelo menos um deles for um obstáculo, o raio pode ser movido usando o espaço particionado em torno desse azulejo, como visto anteriormente.
O ladrilho verde é candidato apenas se o observador estiver acima da linha laranja a seguir:
O mesmo vale para o outro raio e para as outras posições do observador em relação ao obstáculo vermelho.
A idéia subjacente é cobrir o máximo de área possível para cada vazamento de cone e reduzir o mais rápido possível a lista de obstáculos a serem verificados.