LambdaHack-0.1.20110918: src/FOV/Shadow.hs
module FOV.Shadow where
import Data.Ratio
import Data.Set as S
import FOV.Common
import Geometry
import Level
-- Recursive Shadow Casting.
-- | A restrictive variant of Recursive Shadow Casting FOV with infinite range.
-- It's not designed for dungeons with diagonal walls, so they block visibility,
-- though they don't block movement. Such cases appear in the game only
-- when two corridors touch diagonally by accident and on the random pillars
-- levels.
-- | The current state of a scan is kept in a variable of Maybe Rational.
-- If Just something, we're in a visible interval. If Nothing, we're in
-- a shadowed interval.
scan :: ((Distance, Progress) -> Loc) -> LMap -> Distance -> Interval -> Set Loc
scan tr l d (s,e) =
let ps = downBias (s * fromIntegral d) -- minimal progress to check
pe = upBias (e * fromIntegral d) -- maximal progress to check
st = if open (l `at` tr (d,ps)) then Just s -- start in light
else Nothing -- start in shadow
in
-- trace (show (d,s,e,ps,pe)) $
S.union (S.fromList [tr (d,p) | p <- [ps..pe]]) (scan' st ps pe)
where
scan' :: Maybe Rational -> Progress -> Progress -> Set Loc
-- scan' st ps pe
-- | trace (show (st,ps,pe)) False = undefined
scan' (Just s) ps pe
| s >= e = S.empty -- empty interval
| ps > pe = scan tr l (d+1) (s,e) -- reached end, scan next
| closed (l `at` tr (d,ps)) =
let ne = (fromIntegral ps - (1%2)) / (fromIntegral d + (1%2))
in scan tr l (d+1) (s,ne) `S.union` scan' Nothing (ps+1) pe
-- entering shadow
| otherwise = scan' (Just s) (ps+1) pe
-- continue in light
scan' Nothing ps pe
| ps > pe = S.empty -- reached end while in shadow
| open (l `at` tr (d,ps)) =
let ns = (fromIntegral ps - (1%2)) / (fromIntegral d - (1%2))
in scan' (Just ns) (ps+1) pe
-- moving out of shadow
| otherwise = scan' Nothing (ps+1) pe
-- continue in shadow
downBias, upBias :: (Integral a, Integral b) => Ratio a -> b
downBias x = round (x - 1 % (denominator x * 3))
upBias x = round (x + 1 % (denominator x * 3))