gruff-0.1: CacheView.hs
module CacheView (cInitialize) where
import Control.Concurrent (forkIO)
import Control.Monad (forM_, liftM2, when)
import Data.IORef (IORef, newIORef, readIORef, atomicModifyIORef)
import qualified Data.Map as M
import Data.Map (Map)
import Data.Maybe (catMaybes, mapMaybe)
import System.FilePath ((</>), dropExtension)
import Foreign (alloca, peek, nullPtr)
import Graphics.Rendering.OpenGL hiding (Position, Size)
import qualified Graphics.Rendering.OpenGL as GL
import Graphics.Rendering.OpenGL.Raw
( glGenFramebuffers, glBindFramebuffer, glFramebufferTexture2D
, gl_FRAMEBUFFER, gl_COLOR_ATTACHMENT0, gl_TEXTURE_2D
, glTexImage2D, gl_R32F, gl_LUMINANCE, gl_FLOAT
, gl_FALSE, glClampColor, gl_CLAMP_VERTEX_COLOR, gl_CLAMP_READ_COLOR
, gl_CLAMP_FRAGMENT_COLOR )
import Graphics.UI.Gtk hiding (Region, Size)
import Numeric.QD (QuadDouble)
import GLUTGtk
import Shader (shader)
import QuadTree (Child(..), unsafeName, Quad, Square(..), square, child, root, outside, Region(..))
--import Tile (rootSquare)
import Utils (getFilesRecursive)
import Paths_gruff (getDataFileName)
rootSquare :: Square
rootSquare = Square{ squareSize = 8, squareWest = -4, squareNorth = -4 }
names :: Map Char [Child]
names = M.fromList names1 `M.union` M.fromList names2
where
names1 = [ (unsafeName [i], [i]) | i <- [minBound..maxBound] ]
names2 = [ (unsafeName [i,j], [i,j]) | i <- [minBound..maxBound], j <- [minBound..maxBound] ]
fromPath :: ([FilePath], FilePath) -> Maybe [Child]
fromPath (ps, f) =
let s = concat ps ++ dropExtension f
p = concat $ mapMaybe (`M.lookup` names) s
in if all (`M.member` names) s then Just p else Nothing
cDisplay :: IORef GruffCache -> IO ()
cDisplay cR = do
c <- readIORef cR
let Size w h = cSize c
TextureObject tex = cTex c
when (cRecalc c) $ do
viewport $= (GL.Position 0 0, GL.Size (fromIntegral w) (fromIntegral h))
loadIdentity
ortho2D 0 (fromIntegral w) (fromIntegral h) 0
withFBO (cFBO c) tex $ do
clearColor $= Color4 0 0 0 1
clear [ColorBuffer]
let toPixel' = toPixel c
qs = cQuads c
zm = fromIntegral . snd . head $ qs
withBlend (One, One) $ renderPrimitive Quads $ forM_ qs $ \(q, z) -> do
let sq = square rootSquare q
s = squareSize sq `max` cSquareSize c
(x0, y0) = toPixel' (squareWest sq) (squareNorth sq)
(x1, y1) = toPixel' (squareWest sq + s) (squareNorth sq + s)
v x y = vertex $ Vertex2 (fromRational x :: GLdouble) (fromRational y :: GLdouble)
k = fromIntegral z / zm :: GLdouble
color $ Color3 k k k
v x0 y1 >> v x0 y0 >> v x1 y0 >> v x1 y1
atomicModifyIORef cR $ \c' -> (c'{ cRecalc = False }, ())
-- draw texture
loadIdentity
ortho2D 0 1 1 0
textureBinding Texture2D $= Just (cTex c)
currentProgram $= Just (cProg c)
lt <- GL.get $ uniformLocation (cProg c) "tex"
uniform lt $= TexCoord1 (0 :: GLint)
renderPrimitive Quads $ do
let Size tw th = cTexSize c
let tx = fromIntegral w / fromIntegral tw
ty = fromIntegral h / fromIntegral th
v :: GLdouble -> GLdouble -> IO ()
v x y = do
texCoord $ TexCoord2 (tx * x) (ty * (1-y))
vertex $ Vertex2 x y
v 0 1 >> v 0 0 >> v 1 0 >> v 1 1
currentProgram $= Nothing
textureBinding Texture2D $= Nothing
withFBO :: GLuint -> GLuint -> IO () -> IO ()
withFBO fbo tex act = do
glBindFramebuffer gl_FRAMEBUFFER fbo
glFramebufferTexture2D gl_FRAMEBUFFER gl_COLOR_ATTACHMENT0 gl_TEXTURE_2D tex 0
act
glFramebufferTexture2D gl_FRAMEBUFFER gl_COLOR_ATTACHMENT0 gl_TEXTURE_2D 0 0
glBindFramebuffer gl_FRAMEBUFFER 0
withBlend :: (BlendingFactor, BlendingFactor) -> IO a -> IO a
withBlend bf act = do
ob <- GL.get blend
obf <- GL.get blendFunc
blend $= Enabled
blendFunc $= bf
r <- act
blendFunc $= obf
blend $= ob
return r
data GruffCache = GruffCache
{ cSize :: Size
, cCenter :: (Rational, Rational)
, cLevel :: Int
, cCacheDir :: FilePath
, cGL :: GLUTGtk
, cProg :: Program
, cTex :: TextureObject
, cTexSize :: Size
, cFBO :: GLuint
, cRecalc :: Bool
, cQTree :: QTree Integer
}
fromPixel :: GruffCache -> Int -> Int -> (Rational, Rational)
fromPixel c x y = (x', y')
where
Size w h = cSize c
a = fromIntegral w / fromIntegral h
(cx, cy) = cCenter c
r = 8 / 2 ^ cLevel c * fromIntegral h / (2 * fromIntegral tileSize)
x' = cx + r * (fromIntegral x / fromIntegral w - 0.5) * a
y' = cy + r * (fromIntegral y / fromIntegral h - 0.5)
toPixel :: GruffCache -> Rational -> Rational -> (Rational, Rational)
toPixel c = f
where
f x y = {-# SCC "toPixel'" #-} (x', y')
where
x' = ((x - cx) / r' + 0.5) * w'
y' = ((y - cy) / r + 0.5) * h'
Size w h = cSize c
w' = fromIntegral w
h' = fromIntegral h
a = w' / h'
(cx, cy) = cCenter c
r = 8 / 2 ^ cLevel c * h' / (2 * fromIntegral tileSize)
r' = a * r
tileSize :: Int
tileSize = 256
cRealize :: IORef GruffCache -> IO ()
cRealize cR = do
f <- getDataFileName "cache.frag"
prog <- shader Nothing (Just f)
drawBuffer $= BackBuffers
glClampColor gl_CLAMP_VERTEX_COLOR gl_FALSE
glClampColor gl_CLAMP_READ_COLOR gl_FALSE
glClampColor gl_CLAMP_FRAGMENT_COLOR gl_FALSE
[tex] <- genObjectNames 1
texture Texture2D $= Enabled
textureBinding Texture2D $= Just tex
textureFilter Texture2D $= ((Nearest, Nothing), Nearest)
textureWrapMode Texture2D S $= (Repeated, ClampToEdge)
textureWrapMode Texture2D T $= (Repeated, ClampToEdge)
textureBinding Texture2D $= Nothing
texture Texture2D $= Disabled
fbo <- alloca $ \p -> glGenFramebuffers 1 p >> peek p
atomicModifyIORef cR $ \c -> (c{ cProg = prog, cFBO = fbo, cTex = tex }, ())
c <- readIORef cR
cReshape cR (cSize c)
cSquareSize :: GruffCache -> Rational
cSquareSize c = squareSize rootSquare / 2 ^ cLevel c / (2 * fromIntegral tileSize)
cInitialize :: GLUTGtk -> Pixbuf -> FilePath
-> IO ( Window
, Maybe QuadDouble -> Maybe QuadDouble -> Maybe QuadDouble -> IO ()
, IO () -> IO ()
)
cInitialize gl' icon cacheDir' = do
-- image window
cw <- windowNew
let Size defW defH = defSize
windowSetDefaultSize cw defW defH
set cw
[ containerBorderWidth := 0
, containerChild := widget gl'
, windowIcon := Just icon
, windowTitle := "gruff cache"
]
cR <- newIORef GruffCache
{ cCenter = (0, 0)
, cLevel = 0
, cSize = defSize
, cQTree = QTree 0 Nothing Nothing Nothing Nothing
, cCacheDir = cacheDir'
, cGL = gl'
, cProg = undefined
, cFBO = 0
, cTex = TextureObject 0
, cTexSize = roundUpSize defSize
, cRecalc = False
}
realizeCallback gl' $= cRealize cR
reshapeCallback gl' $= cReshape cR
displayCallback gl' $= cDisplay cR
keyboardMouseCallback gl' $= cMouse cR
let cUpdateCoords mre mim mz = do
case liftM2 (,) mre mim of
Just (r, i) -> atomicModifyIORef cR $ \s ->
( s { cCenter = (toRational r, negate $ toRational i) }, () )
Nothing -> return ()
case mz of
Just z | z > 0 -> atomicModifyIORef cR $ \s ->
( s { cLevel = max 0 . min maxLevel . floor . negate . logBase 2 $ z }, () )
_ -> return ()
cUpdate cR True
cInitializeLate aExit = do
_ <- cw `onDestroy` aExit
_ <- forkIO $ cRescan cR
return ()
return (cw, cUpdateCoords, cInitializeLate)
cRescan :: IORef GruffCache -> IO ()
cRescan cR = do
c' <- readIORef cR
fs <- getFilesRecursive (cCacheDir c' </> ".")
let q = depthQ . treeQ . mapMaybe fromPath $ fs
atomicModifyIORef cR $ \c -> (c{ cQTree = q }, ())
postGUISync (cUpdate cR True)
maxLevel :: Int
maxLevel = 200
defSize :: Size
defSize = Size 320 200
cUpdate :: IORef GruffCache -> Bool -> IO ()
cUpdate cR rec = do
c' <- readIORef cR
atomicModifyIORef cR $ \c -> (c{ cRecalc = cRecalc c || rec }, ())
postRedisplay (cGL c')
cReshape :: IORef GruffCache -> Size -> IO ()
cReshape iR size' = do
c <- readIORef iR
let tsize@(Size tw th) = roundUpSize size'
textureBinding Texture2D $= Just (cTex c)
glTexImage2D gl_TEXTURE_2D 0 (fromIntegral gl_R32F)
(fromIntegral tw) (fromIntegral th) 0 gl_LUMINANCE gl_FLOAT nullPtr
textureBinding Texture2D $= Nothing
atomicModifyIORef iR $ \s -> (s{ cSize = size', cTexSize = tsize }, ())
cUpdate iR True
cMouse :: IORef GruffCache -> Key -> KeyState -> [Modifier] -> Position -> IO ()
cMouse sR (MouseButton LeftButton ) Down _ (Position x y) = do
atomicModifyIORef sR $ \s -> let level' = (cLevel s + 1) `min` maxLevel in
( s{ cCenter = fromPixel s (round x) (round y), cLevel = level' }
, ())
cUpdate sR True
cMouse sR (MouseButton MiddleButton) Down _ (Position x y) = do
atomicModifyIORef sR $ \s ->
( s{ cCenter = fromPixel s (round x) (round y) }
, ())
cUpdate sR True
cMouse sR (MouseButton RightButton ) Down _ (Position x y) = do
atomicModifyIORef sR $ \s -> let level' = (cLevel s - 1) `max` 0 in
( s{ cCenter = fromPixel s (round x) (round y), cLevel = level' }
, ())
cUpdate sR True
cMouse _ _ _ _ _ = return ()
roundUpSize :: Size -> Size
roundUpSize (Size w h) = Size (roundUp w) (roundUp h)
roundUp :: Int -> Int
roundUp x = head . filter (>= x) . iterate (2*) $ 1
data QTree a = QTree a (Maybe (QTree a)) (Maybe (QTree a)) (Maybe (QTree a)) (Maybe (QTree a))
payloadQ :: QTree a -> a
payloadQ (QTree w _ _ _ _) = w
childQ :: Child -> QTree a -> Maybe (QTree a)
childQ NorthWest (QTree _ a _ _ _) = a
childQ NorthEast (QTree _ _ b _ _) = b
childQ SouthWest (QTree _ _ _ c _) = c
childQ SouthEast (QTree _ _ _ _ d) = d
emptyQ :: QTree Bool
emptyQ = QTree False Nothing Nothing Nothing Nothing
insertQ :: [Child] -> QTree Bool -> QTree Bool
insertQ [] (QTree _ a b c d) = QTree True a b c d
insertQ (x:xs) (QTree w a b c d) = case x of
NorthWest -> case a of
Nothing -> QTree w (Just $ insertQ xs emptyQ) b c d
Just a' -> QTree w (Just $ insertQ xs a' ) b c d
NorthEast -> case b of
Nothing -> QTree w a (Just $ insertQ xs emptyQ) c d
Just b' -> QTree w a (Just $ insertQ xs b' ) c d
SouthWest -> case c of
Nothing -> QTree w a b (Just $ insertQ xs emptyQ) d
Just c' -> QTree w a b (Just $ insertQ xs c' ) d
SouthEast -> case d of
Nothing -> QTree w a b c (Just $ insertQ xs emptyQ)
Just d' -> QTree w a b c (Just $ insertQ xs d' )
treeQ :: [[Child]] -> QTree Bool
treeQ = foldr insertQ emptyQ
{-
sizeQ :: QTree Bool -> QTree Integer
sizeQ (QTree w a b c d) = QTree n a' b' c' d'
where
s@[a', b', c', d'] = fmap sizeQ `map` [a, b, c, d]
n = (if w then 1 else 0) + (sum . map payloadQ . catMaybes) s
-}
depthQ :: QTree a -> QTree Integer
depthQ = depthQ' 0
where
depthQ' n (QTree _ Nothing Nothing Nothing Nothing) = QTree n Nothing Nothing Nothing Nothing
depthQ' n (QTree _ a b c d) = QTree m a' b' c' d'
where
s@[a', b', c', d'] = fmap (depthQ' (n + 1)) `map` [a, b, c, d]
m = maximum . map payloadQ . catMaybes $ s
pruneQ :: Int -> QTree a -> QTree a
pruneQ 0 (QTree w _ _ _ _) = QTree w Nothing Nothing Nothing Nothing
pruneQ n (QTree w a b c d) = QTree w a' b' c' d'
where
[a', b', c', d'] = fmap (pruneQ (n - 1)) `map` [a, b, c, d]
cQuads :: GruffCache -> [(Quad, Integer)]
cQuads c = map (fmap payloadQ) . concat . quadsQ view . pruneQ (cLevel c + 12) . cQTree $ c
where
Size w h = cSize c
((bw,bn),(be,bs)) =
( fromPixel c 0 0
, fromPixel c (fromIntegral w) (fromIntegral h) )
view = Region
{ regionWest = bw, regionNorth = bn
, regionEast = be, regionSouth = bs }
quadsQ :: Region -> QTree a -> [[(Quad, QTree a)]]
quadsQ view q = takeWhile (not . null) . iterate (filter keep . children') $ [(root, q)]
where
keep = not . outside view . square rootSquare . fst
children' = catMaybes . liftM2 child' [minBound .. maxBound]
child' c (quad, qtree) = (,) (child c quad) `fmap` childQ c qtree