packages feed

brillo-examples-2.0.0: picture/Occlusion/World.hs

{-# LANGUAGE ScopedTypeVariables #-}

module World where

import Brillo.Algorithms.RayCast
import Brillo.Data.Extent
import Brillo.Data.QuadTree
import Brillo.Interface.Pure.Game
import Cell


-- | The game world.
data World
  = World
  { worldWidth :: Int
  , worldHeight :: Int
  , worldTree :: QuadTree Cell
  , worldCellSize :: Int
  , worldCellSpace :: Int
  }
  deriving (Show)


-- | Get the extent covering the entire world.
worldExtent :: World -> Extent
worldExtent world =
  makeExtent (worldWidth world) 0 (worldHeight world) 0


-- | Load a world from a file.
loadWorld :: FilePath -> IO World
loadWorld fileName =
  do
    str <- readFile fileName
    return $ readWorld str


-- | Read a world from a string.
readWorld :: String -> World
readWorld str =
  case lines str of
    ("WORLD" : strWidthHeight : _ : cellLines) ->
      case map read $ words strWidthHeight of
        [width, height] ->
          let rows = take height cellLines
              cells =
                concat $
                  map (readLine width) $
                    reverse rows
              extent = makeExtent height 0 width 0
          in  World
                { worldWidth = width
                , worldHeight = height
                , worldTree = makeWorldTree extent cells
                , worldCellSize = 20
                , worldCellSpace = 0
                }
        _ -> error "readWorld: invalid width/height format"
    _ -> error "readWorld: invalid world format"


readLine :: Int -> String -> [Cell]
readLine width (_ : str) =
  map readCell $
    take width str
readLine _ [] = []


-- | Get the size of the window needed to display a world.
windowSizeOfWorld :: World -> (Int, Int)
windowSizeOfWorld world =
  let cellSize = worldCellSize world
      cellSpace = worldCellSpace world
      cellPad = cellSize + cellSpace
      height = cellPad * (worldHeight world) + cellSpace
      width = cellPad * (worldWidth world) + cellSpace
  in  (width, height)


-- | Create the tree representing the world from a list of all its cells.
makeWorldTree :: Extent -> [Cell] -> QuadTree Cell
makeWorldTree extent cells =
  foldr insert' TNil nonEmptyPosCells
  where
    insert' (pos, cell) tree =
      case insertByCoord extent pos cell tree of
        Nothing -> tree
        Just tree' -> tree'

    (width, height) =
      sizeOfExtent extent

    posCells =
      zip
        [ (x, y)
        | y <- [0 .. height - 1]
        , x <- [0 .. width - 1]
        ]
        cells

    nonEmptyPosCells =
      filter (\x -> snd x /= CellEmpty) posCells


-- | Get the world position coresponding to a point in the window.
worldPosOfWindowPos :: World -> Point -> Point
worldPosOfWindowPos world (x, y) =
  let (windowSizeX, windowSizeY) =
        windowSizeOfWorld world

      offsetX = fromIntegral $ windowSizeX `div` 2
      offsetY = fromIntegral $ windowSizeY `div` 2

      scaleFactor = fromIntegral $ worldCellSize world

      x' = (x + offsetX) / scaleFactor
      y' = (y + offsetY) / scaleFactor
  in  (x', y')


-- | Check if a the cell at a given coordinate is visible from a point.
cellAtCoordIsVisibleFromCoord :: World -> Coord -> Coord -> Bool
cellAtCoordIsVisibleFromCoord world cFrom cTo =
  let (cx, cy) = cFrom
      pFrom = (fromIntegral cx + 0.5, fromIntegral cy + 0.5)
  in  cellAtCoordIsVisibleFromPoint world pFrom cTo


{-| Check if a cell at a given coordinate is visible from a point.
     We say it's visible if the center of any of its faces is visible.
-}
cellAtCoordIsVisibleFromPoint :: World -> Point -> Coord -> Bool
cellAtCoordIsVisibleFromPoint world pFrom (x', y') =
  or $ map (cellAtPointIsVisibleFromPoint world pFrom) [pa, pb, pc, pd]
  where
    x :: Float = fromIntegral x' + 0.5
    y :: Float = fromIntegral y' + 0.5
    pa = (x - 0.4999, y)
    pb = (x + 0.4999, y)
    pc = (x, y - 0.4999)
    pd = (x, y + 0.4999)


-- | Check if a point on some cell (P2) is visible from some other point (P1).
cellAtPointIsVisibleFromPoint :: World -> Point -> Point -> Bool
cellAtPointIsVisibleFromPoint world p1 p2 =
  let mOccluder = castSegIntoWorld world p1 p2
  in  case mOccluder of
        Nothing -> False
        Just (_, extent, _) -> pointInExtent extent p2


-- | Given a line segment (P1-P2) get the cell closest to P1 that intersects the segment.
castSegIntoWorld :: World -> Point -> Point -> Maybe (Point, Extent, Cell)
castSegIntoWorld world p1 p2 =
  castSegIntoCellularQuadTree p1 p2 (worldExtent world) (worldTree world)


-- | Given a line segment (P1-P2) get the cell closest to P1 that intersects the segment.
traceSegIntoWorld :: World -> Point -> Point -> [(Point, Extent, Cell)]
traceSegIntoWorld world p1 p2 =
  traceSegIntoCellularQuadTree p1 p2 (worldExtent world) (worldTree world)