packages feed

accelerate-examples-1.3.0.0: examples/julia/World.hs

{-# LANGUAGE BangPatterns     #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PatternGuards    #-}
{-# LANGUAGE RecordWildCards  #-}
{-# LANGUAGE TypeOperators    #-}

module World where

import Config
import Julia
import Palette

import Graphics.Gloss.Accelerate.Data.Picture                       as G
import Graphics.Gloss.Interface.Pure.Game                           hiding ( Vector, translate, scale )

import Data.Array.Accelerate                                        ( Array, Scalar, Vector, Exp, DIM2, Elt, Z(..), (:.)(..) )
import Data.Array.Accelerate.Data.Complex
import Data.Array.Accelerate.Examples.Internal                      as A
import qualified Data.Array.Accelerate                              as A

import Data.Char
import Data.Int
import Data.Label
import Data.Word
import System.Exit
import Text.Printf
import Prelude                                                      as P


-- World state
-- -----------

type R = Double

data World = World
  { worldPicture      :: !Picture
  , worldAnimating    :: !Bool
  , worldDirty        :: !Bool
  , worldRender       :: !(Scalar Float -> Scalar R -> Scalar R -> Scalar R -> Scalar Int32 -> Scalar R -> Array DIM2 Word32)
  , worldPalette      :: !(Vector Word32)
  , worldSizeX        :: !Int
  , worldSizeY        :: !Int
  , worldTime         :: !(Scalar Float)
  , worldSpeed        :: !(Scalar Float)
  , worldPosX         :: !(Scalar R)
  , worldPosY         :: !(Scalar R)
  , worldWidth        :: !(Scalar R)
  , worldRadius       :: !(Scalar R)
  , worldIters        :: !(Scalar Int32)
  , worldPanning      :: !(Maybe (Float, Float))
  , worldZooming      :: !(Maybe Double)
  }

initialWorld :: Config -> Options -> World
initialWorld conf opts
  = loadPreset opts 0
  $ World
      { worldDirty        = True
      , worldAnimating    = True
      , worldPicture      = blank
      , worldRender       = \_ _ _ _ _ _ -> A.fromList (Z:.0:.0) []
      , worldPalette      = run (get optBackend opts) (ultraPalette 2048)
      , worldSizeX        = get configWidth conf
      , worldSizeY        = get configHeight conf
      , worldPanning      = Nothing
      , worldZooming      = Nothing
      , worldTime         = unit' 0
      , worldSpeed        = unit' 1
      , worldPosX         = unit' 0
      , worldPosY         = unit' 0
      , worldWidth        = unit' 0
      , worldRadius       = unit' 0
      , worldIters        = unit' 0
      }


-- Draw the world
--
draw :: World -> IO Picture
draw = return . worldPicture

-- React to events
--
react :: Config -> Options -> Event -> World -> IO World
react conf opts event world@World{..} =
  case event of
    -- zooming
    EventKey (Char 'w') s _ _               -> toggle zooming 0.975 s world
    EventKey (Char 's') s _ _               -> toggle zooming 1.025 s world

    -- panning
    EventKey (MouseButton LeftButton) s _ p -> toggle panning p s world
    EventMotion (x,y)
      | Just (x0,y0) <- worldPanning
      -> let dx = (x0-x) * P.realToFrac (the' worldWidth) / P.fromIntegral worldSizeX
             dy = (y0-y) * P.realToFrac (the' worldWidth) / P.fromIntegral worldSizeX
         in
         return . dirty
                $ world { worldPosX    = unit' (the' worldPosX + P.realToFrac dx)
                        , worldPosY    = unit' (the' worldPosY - P.realToFrac dy)
                        , worldPanning = Just (x,y)
                        }

    -- algorithm
    EventKey (Char 'a') Down _ _ -> return . dirty
      $ world { worldIters = unit' (P.truncate (P.fromIntegral (the' worldIters) * 0.8 :: Double)) }
    EventKey (Char 'd') Down _ _ -> return . dirty
      $ world { worldIters = unit' (P.truncate (P.fromIntegral (the' worldIters) * 1.2 :: Double)) }

    EventKey (Char 'z') Down _ _ -> return . dirty
      $ world { worldRadius = unit' (the' worldRadius * 0.5) }
    EventKey (Char 'c') Down _ _ -> return . dirty
      $ world { worldRadius = unit' (the' worldRadius * 2.0) }

    EventKey (Char 'q') Down _ _ -> return . dirty
      $ world { worldSpeed = unit' (the' worldSpeed * 0.75) }
    EventKey (Char 'e') Down _ _ -> return . dirty
      $ world { worldSpeed = unit' (the' worldSpeed * 1.25) }

    -- animation
    EventKey (SpecialKey KeyLeft) Down _ _  -> return . dirty
      $ world { worldTime = unit' (the' worldTime - 1/60 * the' worldSpeed) }
    EventKey (SpecialKey KeyRight) Down _ _ -> return . dirty
      $ world { worldTime = unit' (the' worldTime + 1/60 * the' worldSpeed) }

    -- presets
    EventKey (Char d) Down _ _ | isDigit d  -> return . dirty $ loadPreset opts (read [d]) world

    -- misc
    EventKey (Char '.') Down _ _            -> putStrLn (showWorld world) >> return world
    EventKey (Char 'r') Down _ _            -> return $ (initialWorld conf opts) { worldAnimating = worldAnimating }
    EventKey (SpecialKey KeySpace) Down _ _ -> return $ world { worldAnimating = not worldAnimating }
    EventKey (SpecialKey KeyEsc) Down _ _   -> exitSuccess

    _                                       -> return world
  where
    toggle f x Down = return . dirty . set f (Just x)
    toggle f _ Up   = return         . set f Nothing

    dirty w = w { worldDirty = True }


-- Move and zoom the display based on key state
--
advance :: Float -> World -> IO World
advance dt = return . updateWorld . animate . zoom
  where
    animate world@World{..}
      | worldAnimating          = world { worldDirty = True, worldTime = unit' (dt * the' worldSpeed + the' worldTime) }
      | otherwise               = world
    --
    zoom world@World{..}
      | Just f <- worldZooming  = world { worldDirty = True, worldWidth = unit' (P.realToFrac f * the' worldWidth)  }
      | otherwise               = world

-- Update the picture
--
updateWorld :: World -> World
updateWorld world@World{..} =
  if worldDirty
    then world { worldDirty   = False
               , worldPicture = bitmapOfArray (renderWorld world) (not worldAnimating)
               }
    else world

renderWorld :: World -> Array DIM2 Word32
renderWorld World{..} =
  let !r = worldRender worldTime worldPosX worldPosY worldWidth worldIters worldRadius
   in r


-- Miscellaneous
-- -------------

zooming :: World :-> Maybe Double
zooming = lens worldZooming (\f World{..} -> World { worldZooming = f worldZooming, .. })

panning :: World :-> Maybe (Float,Float)
panning = lens worldPanning (\f World{..} -> World { worldPanning = f worldPanning, .. })

unit' :: Elt a => a -> Scalar a
unit' a = A.fromList Z [a]

the' :: Elt a => Scalar a -> a
the' a = a `A.indexArray` Z


-- Presets
-- -------

showWorld :: World -> String
showWorld World{..} =
  printf "centre: (%f, %f), width: %f, iterations: %d, radius: %f"
    (the' worldPosX)
    (the' worldPosY)
    (the' worldWidth)
    (the' worldIters)
    (the' worldRadius)

loadPreset :: Options -> Int -> World -> World
loadPreset opts n world@World{..} =
  let
      (f, posX, posY, width, iters, radius)
        = presets !! n

      render t x y w l r
        = A.map (escapeToRGBA l (A.use worldPalette))
        $ julia worldSizeX worldSizeY (\a b -> f (A.toFloating a) b) t x y w l r
  in
  world
    { worldRender = runN (get optBackend opts) render
    , worldPosX   = unit' posX
    , worldPosY   = unit' posY
    , worldWidth  = unit' width
    , worldIters  = unit' iters
    , worldRadius = unit' radius
    }

golden :: Floating a => a
golden = (1 + sqrt 5) / 2

presets
    :: (Fractional a, A.RealFloat a)
    => [ (Exp a -> Exp (Complex a) -> Exp (Complex a), a, a, a, Int32, a) ]
presets =
  [ (\t z -> z ^ (2::Int) + mkPolar 0.7885 t,                                   0, 0, 4, 255, 16.0)
  , (\t z -> z ^ (2::Int) + A.lift ((1 - golden) * cos t :+ A.constant 0),      0, 0, 4, 255, 16.0)
  , (\t z -> z ^ (2::Int) + A.lift ((golden-2) * cos t :+ (golden-1) * sin t),  0, 0, 4, 255, 16.0)
  , (\t z -> z ^ (2::Int) + A.lift (0.285 * cos t :+ 0.1 * sin t),              0, 0, 4, 255, 16.0)
  , (\t z -> z ^ (2::Int) + A.lift (0.45 * cos t :+ 0.1428 * sin t),            0, 0, 4, 255, 16.0)
  , (\t z -> z ^ (2::Int) + A.lift ((-0.70176) * cos t :+ (-0.3842) * sin t),   0, 0, 4, 255, 16.0)
  , (\t z -> z ^ (2::Int) + A.lift ((-0.835) * cos t :+ (-0.2321) * sin t),     0, 0, 4, 255, 16.0)
  , (\t z -> z ^ (2::Int) + A.lift ((-0.8) * cos t :+ 0.156 * sin t),           0, 0, 4, 255, 16.0)
  , (\t z -> z ^ (2::Int) + A.lift ((-0.7269) * cos t :+ 0.1889 * sin t),       0, 0, 4, 255, 16.0)
  , (\t z -> z ^ (2::Int) + A.lift (0 :+ (-0.8) * sin t),                       0, 0, 4, 255, 16.0)
  ]