packages feed

accelerate-examples-0.12.0.0: examples/fluid/src/World.hs

{-# LANGUAGE BangPatterns  #-}
{-# LANGUAGE PatternGuards #-}
--
-- Displaying the world state
--

module World (World(..), Source(..), initialise, render) where

import Type
import Config

import Codec.BMP
import Data.Bits
import Data.Int
import Data.Word
import Data.Label
import Control.Monad
import Graphics.Gloss.Interface.IO.Game
import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.Storable
import Foreign.Marshal.Alloc
import Data.Array.Accelerate                            ( Z(..), (:.)(..), Exp )
import Data.Array.Accelerate.Array.Sugar                ( Array(..) )

import qualified Data.Array.Accelerate.Array.Data       as A
import qualified Data.Array.Accelerate.IO               as A
import qualified Data.Array.Accelerate                  as A


data World = World
  {
    -- current state of the simulation
    densityField        :: !DensityField
  , velocityField       :: !VelocityField

    -- user input
  , densitySource       :: [(Index, Density)]
  , velocitySource      :: [(Index, Velocity)]
  , currentSource       :: Source
  , displayDensity      :: Bool
  , displayVelocity     :: Bool
  }
  deriving Show

data Source = Density  (Int, Int)
            | Velocity (Int, Int)
            | None
  deriving (Eq, Show)


-- Initialisation --------------------------------------------------------------
-- --------------                                                             --

initialise :: Options -> IO World
initialise opt =
  let width     = get simulationWidth  opt
      height    = get simulationHeight opt
  in do
  density       <- initialDensity  opt width height
  velocity      <- initialVelocity opt width height

  return $ World
    { densityField      = density
    , velocityField     = velocity
    , densitySource     = []
    , velocitySource    = []
    , currentSource     = None
    , displayDensity    = True
    , displayVelocity   = False
    }


initialDensity :: Options -> Int -> Int -> IO DensityField
initialDensity opt width height
  -- Load the file, and use the luminance value as scalar density
  --
  | Just file   <- get densityBMP opt
  = do
      arr               <- readImageFromBMP file
      let (Z:.h:.w)     =  A.arrayShape arr

      when (w /= width || h /= height)
        $ error "fluid: density-bmp does not match width x height"

      return . run opt $ A.map densityOfRGBA (A.use arr)

  -- No density file given, just set the field to zero
  --
  | otherwise
  = return $ A.fromList (Z :. height :. width) (repeat 0)


initialVelocity :: Options -> Int -> Int -> IO VelocityField
initialVelocity opt width height
  -- Load the file, and use the red and green channels for x- and y- velocity
  -- values respectively
  --
  | Just file   <- get velocityBMP opt
  = do
      arr               <- readImageFromBMP file
      let (Z:.h:.w)     =  A.arrayShape arr

      when (w /= width || h /= height)
        $ error "fluid: velocity-bmp does not match width x height"

      return . run opt $ A.map velocityOfRGBA (A.use arr)

  -- No density file given, just set to zero
  --
  | otherwise
  = return $ A.fromList (Z :. height :. width) (repeat (0,0))


readImageFromBMP :: FilePath -> IO (Image RGBA)
readImageFromBMP file = do
  bmp           <- either (error . show) id `fmap` readBMP file
  let (w,h)     =  bmpDimensions bmp
  --
  A.fromByteString (Z :. h :. w) ((), unpackBMPToRGBA32 bmp)


densityOfRGBA :: Exp RGBA -> Exp Density
densityOfRGBA rgba =
  let b = (0.11 / 255) * A.fromIntegral ((rgba `div` 0x100)     .&. 0xFF)
      g = (0.59 / 255) * A.fromIntegral ((rgba `div` 0x10000)   .&. 0xFF)
      r = (0.3  / 255) * A.fromIntegral ((rgba `div` 0x1000000) .&. 0xFF)
  in
  r + g + b

velocityOfRGBA :: Exp RGBA -> Exp Velocity
velocityOfRGBA rgba =
  let g = A.fromIntegral (-128 + A.fromIntegral ((rgba `div` 0x10000)   .&. 0xFF) :: Exp Int32)
      r = A.fromIntegral (-128 + A.fromIntegral ((rgba `div` 0x1000000) .&. 0xFF) :: Exp Int32)
  in
  A.lift (r * 0.001, g * 0.001)


-- Rendering -------------------------------------------------------------------
-- ---------                                                                  --

render :: Options -> World -> IO Picture
render opt world = do
  den   <- if displayDensity world
              then renderDensity   $ densityField  world
              else return blank

  vel   <- if displayVelocity world
              then renderVelocity  $ velocityField world
              else return blank
  --
  return $ Scale zoom zoom $ Pictures [ den, vel ]
  where
    zoom        = fromIntegral $ get displayScale opt


renderDensity :: DensityField -> IO Picture
renderDensity df@(Array _ ad) = do
  dst   <- mallocBytes (n*4)
  fill 0 src dst
  fptr  <- newForeignPtr finalizerFree dst
  return $ bitmapOfForeignPtr w h fptr False
  where
    ((),src)    = A.ptrsOfArrayData ad
    Z:.h:.w     = A.arrayShape df
    n           = h*w
    colour !f   = let c = 0 `max` f `min` 1
                  in  floatToWord8 (255*c)

    fill !i !s !d | i >= n    = return ()
                  | otherwise = do c <- colour `fmap` peek s
                                   poke        d   0xFF         -- A
                                   pokeByteOff d 1 c            -- B
                                   pokeByteOff d 2 c            -- G
                                   pokeByteOff d 3 c            -- R
                                   fill (i+1) (plusPtr s 4) (plusPtr d 4)


renderVelocity :: VelocityField -> IO Picture
renderVelocity vf
  = return
  $ Translate (fromIntegral $ -w `div` 2) (fromIntegral $ -h `div` 2)
  $ Pictures [ field (x,y) | y <- [2,7..h], x <- [2,7..w] ]
  where
    Z:.h:.w       = A.arrayShape vf
    field (x0,y0) =
      let x     = fromIntegral x0
          y     = fromIntegral y0
          (u,v) = A.indexArray vf (Z:.y0:.x0)
      in  Color red $ Line [ (x,y), (x+u, y+v) ]


-- Float to Word8 conversion because the one in the GHC libraries doesn't have
-- enough specialisations and goes via Integer.
{-# INLINE floatToWord8 #-}
floatToWord8 :: Float -> Word8
floatToWord8 f = fromIntegral (truncate f :: Int)