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))