{-# LANGUAGE CPP #-}
-- Quasicrystals demo.
--
-- Based on code from:
-- http://hackage.haskell.org/package/repa-examples
-- http://mainisusuallyafunction.blogspot.com/2011/10/quasicrystals-as-sums-of-waves-in-plane.html
--
module Main where
import Config
import ParseArgs
import Data.Word
import Data.Label
import Foreign.Ptr
import Control.Monad
import Control.Exception
import Criterion.Main ( defaultMainWith, bench, whnf )
import Foreign.ForeignPtr
import System.Environment
import System.IO.Unsafe
import Data.Array.Accelerate ( Array, Scalar, Exp, Acc, DIM2, Z(..), (:.)(..) )
import qualified Data.Array.Accelerate as A
import qualified Graphics.Gloss as G
import Data.Array.Accelerate.Array.Data ( ptrsOfArrayData )
import Data.Array.Accelerate.Array.Sugar ( Array(..) )
-- Types ----------------------------------------------------------------------
-- | Real value
type R = Float
-- | Point on the 2D plane.
type R2 = (R, R)
-- | Angle in radians.
type Angle = R
-- | Angle offset used for animation.
type Phi = Float
-- | Number of waves to sum for each pixel.
type Degree = Int
-- | Feature size of visualisation.
type Scale = Float
-- | Size of image to render.
type Size = Int
-- | How many times to duplicate each pixel / image zoom.
type Zoom = Int
-- | Type of the generated image data
type RGBA = Word32
type Bitmap = Array DIM2 RGBA
-- | Action to render a frame
type Render = Scalar Phi -> Bitmap
-- Point ----------------------------------------------------------------------
-- | Compute a single point of the visualisation.
quasicrystal :: Size -> Scale -> Degree -> Acc (Scalar Phi) -> Exp DIM2 -> Exp R
quasicrystal size scale degree phi p
= waves degree phi $ point size scale p
-- | Sum up all the waves at a particular point.
waves :: Degree -> Acc (Scalar Phi) -> Exp R2 -> Exp R
waves degree phi x = wrap $ waver degree 0
where
waver :: Int -> Exp Float -> Exp Float
waver n acc
| n == 0 = acc
| otherwise = waver (n - 1) (acc + wave (A.constant (fromIntegral n) * phi') x)
phi'
= A.the phi
wrap n
= let n_ = A.truncate n :: Exp Int
n' = n - A.fromIntegral n_
in
(n_ `rem` 2 A./=* 0) A.? (1-n', n')
-- | Generate the value for a single wave.
wave :: Exp Angle -> Exp R2 -> Exp R
wave th pt = (cos (cth*x + sth*y) + 1) / 2
where
(x,y) = A.unlift pt
cth = cos th
sth = sin th
-- | Convert an image point to a point on our wave plane.
point :: Size -> Scale -> Exp DIM2 -> Exp R2
point size scale ix = A.lift (adj x, adj y)
where
(Z:.x:.y) = A.unlift ix
denom = A.constant (fromIntegral size - 1)
adj n = A.constant scale * ((2 * A.fromIntegral n / denom) - 1)
-- Computation ----------------------------------------------------------------
-- | Compute a single frame
makeImage :: Size -> Scale -> Degree -> Acc (Scalar Phi) -> Acc Bitmap
makeImage size scale degree phi = arrPixels
where
-- Compute values for the wave density at each point in the range [0..1]
arrVals :: Acc (Array DIM2 Float)
arrVals = A.generate
(A.constant $ Z :. size :. size)
(quasicrystal size scale degree phi)
-- Convert the [0..1] values of wave density to an RGBA flat image
arrPixels :: Acc Bitmap
arrPixels = A.map rampColour arrVals
-- | Colour ramp from red to white, convert into RGBA
rampColour :: Exp Float -> Exp RGBA
rampColour v = ra + g + b
where
u = 0 `A.max` v `A.min` 1
ra = 0xFF0000FF
g = A.truncate ((0.4 + (u * 0.6)) * 0xFF) * 0x10000
b = A.truncate (u * 0xFF) * 0x100
-- Rendering ------------------------------------------------------------------
-- | Compute a single frame of the animation as a Gloss picture.
--
frame :: Render -> Size -> Zoom -> Float -> G.Picture
frame render size zoom time = G.scale zoom' zoom' pic
where
-- Scale the time to be the phi value of the animation. The action seems to
-- slow down at increasing phi values, so we increase phi faster as time
-- moves on.
x = 1 + (time ** 1.5) * 0.005
-- lift to a singleton array, else we would generate new code with the
-- constant embedded at every frame
phi = A.fromList Z [pi / x]
-- Compute the image
arrPixels = render phi
-- Wrap the array data in a Foreign pointer and turn into a Gloss picture
{-# NOINLINE rawData #-}
rawData = let (Array _ adata) = arrPixels
((),ptr) = ptrsOfArrayData adata
in
unsafePerformIO $ newForeignPtr_ (castPtr ptr)
pic = G.bitmapOfForeignPtr
size size -- raw image size
rawData -- the image data
False -- don't cache this in texture memory
-- Zoom the image so we get a bigger window.
zoom' = fromIntegral zoom
-- Main -----------------------------------------------------------------------
main :: IO ()
main
= do argv <- getArgs
(config, crit, nops) <- parseArgs optHelp optBackend options defaults header footer argv
let size = get optSize config
zoom = get optZoom config
scale = get optScale config
degree = get optDegree config
backend = get optBackend config
render = run1 backend $ makeImage size scale degree
force arr = A.indexArray arr (Z:.0:.0) `seq` arr
void . evaluate $ render (A.fromList Z [0])
if get optBench config
then withArgs nops $ defaultMainWith crit (return ())
[ bench "crystal" $ whnf (force . render) (A.fromList Z [1.0]) ]
#ifndef ACCELERATE_ENABLE_GUI
else return ()
#else
else G.animate
(G.InWindow "Quasicrystals" (size * zoom, size * zoom) (10, 10))
(G.black)
(frame render size zoom)
#endif