2013-12-29 14:39:01 +01:00
|
|
|
{-# LANGUAGE BangPatterns #-}
|
2013-12-29 01:02:52 +01:00
|
|
|
module Main where
|
2013-12-22 23:29:11 +01:00
|
|
|
|
2013-12-23 00:00:51 +01:00
|
|
|
import qualified Graphics.UI.Gtk as Gtk
|
|
|
|
import Graphics.UI.Gtk (AttrOp((:=)))
|
|
|
|
import qualified Graphics.UI.Gtk.OpenGL as GtkGL
|
|
|
|
|
|
|
|
import Graphics.Rendering.OpenGL as GL
|
2013-12-29 18:18:18 +01:00
|
|
|
import qualified Graphics.UI.Gtk.Gdk.EventM as Event
|
2013-12-29 06:03:32 +01:00
|
|
|
import qualified Data.Array.IArray as A
|
2013-12-29 01:05:01 +01:00
|
|
|
|
|
|
|
import Map.Coordinates
|
2013-12-29 06:03:32 +01:00
|
|
|
import Map.Map
|
2013-12-29 01:05:01 +01:00
|
|
|
|
|
|
|
import Data.Maybe (fromMaybe)
|
2013-12-29 06:03:32 +01:00
|
|
|
import Debug.Trace
|
2013-12-29 14:39:01 +01:00
|
|
|
import Data.IntSet as IS
|
|
|
|
import Data.IORef
|
|
|
|
|
|
|
|
import Prelude as P
|
|
|
|
import Control.Monad
|
|
|
|
import Control.Concurrent
|
|
|
|
import Control.Concurrent.STM
|
2013-12-29 18:18:18 +01:00
|
|
|
import System.IO.Unsafe (unsafePerformIO)
|
|
|
|
import GHC.Conc.Sync (unsafeIOToSTM)
|
|
|
|
import Control.Monad.IO.Class (liftIO)
|
2013-12-29 14:39:01 +01:00
|
|
|
|
|
|
|
data ProgramState = PS { keysPressed :: IntSet
|
|
|
|
, px :: GLfloat
|
|
|
|
, py :: GLfloat
|
|
|
|
, pz :: GLfloat
|
|
|
|
, heading :: GLfloat
|
|
|
|
, pitch :: GLfloat
|
|
|
|
, dx :: GLfloat
|
2013-12-29 18:18:18 +01:00
|
|
|
, dy :: GLfloat
|
2013-12-29 14:39:01 +01:00
|
|
|
, dz :: GLfloat
|
|
|
|
, dheading :: GLfloat
|
|
|
|
, dpitch :: GLfloat }
|
2013-12-29 18:18:18 +01:00
|
|
|
deriving (Show)
|
2013-12-23 00:00:51 +01:00
|
|
|
|
2013-12-29 06:03:32 +01:00
|
|
|
animationWaitTime = 3 :: Int
|
2013-12-29 18:18:18 +01:00
|
|
|
canvasWidth = 1024 :: Int
|
|
|
|
canvasHeight = 768 :: Int
|
|
|
|
deltaV = 0.10
|
|
|
|
deltaH = 0.5
|
|
|
|
deltaP = 0.15
|
2013-12-29 06:03:32 +01:00
|
|
|
|
2013-12-29 14:39:01 +01:00
|
|
|
-- TODO: Put render-stuff in render-module
|
|
|
|
|
2013-12-29 06:03:32 +01:00
|
|
|
glTexCoord2f (x,y) = texCoord (TexCoord2 x y :: TexCoord2 GLfloat)
|
|
|
|
glVertex3f (x,y,z) = vertex (Vertex3 x y z :: Vertex3 GLfloat)
|
|
|
|
glNormal3f (x,y,z) = normal (Normal3 x y z :: Normal3 GLfloat)
|
|
|
|
|
|
|
|
|
|
|
|
prepareRenderTile :: PlayMap -> ((Int,Int),MapEntry) -> (Vector3 GLfloat, Color3 GLfloat, [Vertex3 GLfloat])
|
|
|
|
prepareRenderTile m (c@(cx,cz),(_,t)) =
|
|
|
|
(
|
2013-12-29 14:39:01 +01:00
|
|
|
if even cx then
|
|
|
|
Vector3 (1.5*(fromIntegral cx)) 0.0 (2*(fromIntegral cz))
|
2013-12-29 06:03:32 +01:00
|
|
|
else
|
2013-12-29 14:39:01 +01:00
|
|
|
Vector3 (1.5*(fromIntegral cx)) 0.0 (2*(fromIntegral cz)-1)
|
2013-12-29 06:03:32 +01:00
|
|
|
,
|
|
|
|
case t of
|
|
|
|
Water -> Color3 0.5 0.5 1 :: Color3 GLfloat
|
|
|
|
Grass -> Color3 0.3 0.9 0.1 :: Color3 GLfloat
|
|
|
|
Sand -> Color3 0.9 0.85 0.7 :: Color3 GLfloat
|
|
|
|
Mountain -> Color3 0.5 0.5 0.5 :: Color3 GLfloat
|
|
|
|
,getTileVertices m c)
|
|
|
|
|
|
|
|
renderTile :: (Vector3 GLfloat, Color3 GLfloat, [Vertex3 GLfloat]) -> IO ()
|
|
|
|
renderTile (coord,c,ts) =
|
|
|
|
preservingMatrix $ do
|
|
|
|
color c
|
|
|
|
translate coord
|
|
|
|
_ <- renderPrimitive Polygon $ do
|
|
|
|
glNormal3f(0.0,0.0,1.0)
|
|
|
|
mapM vertex ts
|
|
|
|
return ()
|
|
|
|
|
|
|
|
drawSphere = do
|
|
|
|
renderQuadric (QuadricStyle
|
|
|
|
(Just Smooth)
|
|
|
|
GenerateTextureCoordinates
|
|
|
|
Outside
|
|
|
|
FillStyle)
|
|
|
|
(Sphere 1.0 48 48)
|
|
|
|
|
2013-12-23 00:00:51 +01:00
|
|
|
-- OpenGL polygon-function for drawing stuff.
|
2013-12-29 18:18:18 +01:00
|
|
|
display :: MVar ProgramState -> PlayMap -> IO ()
|
2013-12-29 14:39:01 +01:00
|
|
|
display state t =
|
2013-12-29 06:03:32 +01:00
|
|
|
let
|
2013-12-29 14:39:01 +01:00
|
|
|
tiles = P.map (prepareRenderTile t) (A.assocs t)
|
2013-12-29 06:03:32 +01:00
|
|
|
in
|
|
|
|
do
|
2013-12-29 14:39:01 +01:00
|
|
|
ps@PS {
|
|
|
|
px = px
|
|
|
|
, py = py
|
|
|
|
, pz = pz
|
|
|
|
, pitch = pitch
|
|
|
|
, heading = heading }
|
2013-12-29 18:18:18 +01:00
|
|
|
<- readMVar state
|
2013-12-29 14:39:01 +01:00
|
|
|
loadIdentity
|
|
|
|
GL.rotate pitch (Vector3 1.0 0.0 0.0 :: Vector3 GLfloat)
|
|
|
|
GL.rotate heading (Vector3 0.0 1.0 0.0 :: Vector3 GLfloat)
|
|
|
|
translate (Vector3 (-px) (-py) (-pz)::Vector3 GLfloat)
|
|
|
|
|
|
|
|
position (Light 0) $= Vertex4 0.0 0.0 (20.0) 1.0
|
|
|
|
|
|
|
|
-- Instead of glBegin ... glEnd there is renderPrimitive.
|
|
|
|
--trace (show tiles) $
|
|
|
|
mapM_ renderTile tiles
|
|
|
|
return ()
|
2013-12-22 23:29:11 +01:00
|
|
|
|
2013-12-29 18:18:18 +01:00
|
|
|
updateCamera :: MVar ProgramState -> IO ()
|
|
|
|
updateCamera state = do
|
|
|
|
ps@PS { dx = dx
|
|
|
|
, dy = dy
|
|
|
|
, dz = dz
|
|
|
|
, px = px
|
|
|
|
, py = py
|
|
|
|
, pz = pz
|
|
|
|
, pitch = pitch
|
|
|
|
, heading = heading
|
|
|
|
, dpitch = dpitch
|
|
|
|
, dheading = dheading }
|
|
|
|
<- takeMVar state
|
|
|
|
|
|
|
|
d@((dx,dy,dz),(heading',pitch')) <-
|
|
|
|
if any (/= 0.0) [dx,dy,dz,dpitch,dheading] then
|
|
|
|
preservingMatrix $ do
|
|
|
|
putStrLn $ unwords $ P.map show [dx,dy,dz,dpitch,dheading]
|
|
|
|
loadIdentity
|
|
|
|
|
|
|
|
-- in direction of current heading and pitch
|
|
|
|
rotate (-heading) (Vector3 0.0 1.0 0.0 :: Vector3 GLfloat)
|
|
|
|
rotate (-pitch) (Vector3 1.0 0.0 0.0 :: Vector3 GLfloat)
|
|
|
|
|
|
|
|
-- perform motion
|
|
|
|
translate (Vector3 (-dx) (-dy) (-dz))
|
|
|
|
|
|
|
|
|
|
|
|
-- get changes in location components
|
|
|
|
mat <- get (matrix Nothing) :: IO (GLmatrix GLfloat)
|
|
|
|
comps <- getMatrixComponents ColumnMajor mat
|
|
|
|
putStrLn $ show $ comps
|
|
|
|
let [dx', dy', dz', _] = drop 12 comps
|
|
|
|
(heading', pitch') = (heading + dheading, pitch + dpitch)
|
|
|
|
return ((dx',dy',dz'),(heading',pitch'))
|
|
|
|
else
|
|
|
|
return ((0,0,0),(heading, pitch))
|
|
|
|
putMVar state ps { px = px + dx
|
|
|
|
, py = py + dy
|
|
|
|
, pz = pz + dz
|
|
|
|
, pitch = pitch'
|
|
|
|
, heading = heading'
|
|
|
|
}
|
|
|
|
|
2013-12-29 01:05:01 +01:00
|
|
|
--Adjust size to given dimensions
|
|
|
|
reconfigure :: Int -> Int -> IO (Int, Int)
|
|
|
|
reconfigure w h = do
|
|
|
|
-- maintain aspect ratio
|
|
|
|
let aspectRatio = (fromIntegral canvasWidth) / (fromIntegral canvasHeight)
|
|
|
|
(w1, h1) = (fromIntegral w, (fromIntegral w) / aspectRatio)
|
|
|
|
(w2, h2) = ((fromIntegral h) * aspectRatio, fromIntegral h)
|
|
|
|
(w', h') = if h1 <= fromIntegral h
|
|
|
|
then (floor w1, floor h1)
|
|
|
|
else (floor w2, floor h2)
|
|
|
|
reshape $ Just (w', h')
|
|
|
|
return (w', h')
|
|
|
|
|
|
|
|
-- Called by reconfigure to fix the OpenGL viewport according to the
|
|
|
|
-- dimensions of the widget, appropriately.
|
|
|
|
reshape :: Maybe (Int, Int) -> IO ()
|
|
|
|
reshape dims = do
|
|
|
|
let (width, height) = fromMaybe (canvasWidth, canvasHeight) dims
|
|
|
|
viewport $= (Position 0 0, Size (fromIntegral width) (fromIntegral height))
|
|
|
|
matrixMode $= Projection
|
|
|
|
loadIdentity
|
|
|
|
let (w, h) = if width <= height
|
|
|
|
then (fromIntegral height, fromIntegral width )
|
|
|
|
else (fromIntegral width, fromIntegral height)
|
2013-12-29 14:39:01 +01:00
|
|
|
-- open, aspect-ratio, near-plane, far-plane
|
|
|
|
perspective 60.0 (fromIntegral canvasWidth / fromIntegral canvasHeight) 1.0 100.0
|
2013-12-29 01:05:01 +01:00
|
|
|
matrixMode $= Modelview 0
|
|
|
|
loadIdentity
|
2013-12-23 00:56:34 +01:00
|
|
|
|
2013-12-29 18:18:18 +01:00
|
|
|
keyEvent state press = do
|
|
|
|
code <- Event.eventHardwareKeycode
|
|
|
|
val <- Event.eventKeyVal
|
|
|
|
mods <- Event.eventModifier
|
|
|
|
name <- Event.eventKeyName
|
|
|
|
liftIO $ do
|
|
|
|
ps@PS { keysPressed = kp
|
|
|
|
, dx = dx
|
|
|
|
, dy = dy
|
|
|
|
, dz = dz
|
|
|
|
, px = px
|
|
|
|
, py = py
|
|
|
|
, pz = pz
|
|
|
|
, pitch = pitch
|
|
|
|
, heading = heading
|
|
|
|
, dpitch = dpitch
|
|
|
|
, dheading = dheading }
|
|
|
|
<- takeMVar state
|
|
|
|
-- Only process the key event if it is not a repeat
|
|
|
|
(ps',ret) <- if (fromIntegral code `member` kp && not press) ||
|
|
|
|
(fromIntegral code `notMember` kp && press)
|
|
|
|
then let
|
|
|
|
accept a = return (a, True)
|
|
|
|
deny a = return (a, False)
|
|
|
|
in do
|
|
|
|
-- keep list of pressed keys up2date
|
|
|
|
ps <- if not press
|
|
|
|
then return ps { keysPressed = fromIntegral code `IS.delete` kp }
|
|
|
|
else return ps { keysPressed = fromIntegral code `IS.insert` kp }
|
|
|
|
putStrLn $ unwords [name , show val, show code, show ps] -- trace (unwords [name , show val]) -- debugging
|
|
|
|
-- process keys
|
|
|
|
case press of
|
|
|
|
-- on PRESS only
|
|
|
|
True
|
|
|
|
| code == 9 -> Gtk.mainQuit >> deny ps
|
|
|
|
| code == 26 -> accept $ ps { dz = dz + deltaV }
|
|
|
|
| code == 40 -> accept $ ps { dz = dz - deltaV }
|
|
|
|
| code == 39 -> accept $ ps { dx = dx + deltaV }
|
|
|
|
| code == 41 -> accept $ ps { dx = dx - deltaV }
|
|
|
|
| code == 65 -> accept $ ps { dy = dy - deltaV }
|
|
|
|
| code == 66 -> accept $ ps { dy = dy + deltaV }
|
|
|
|
| code == 25 -> accept $ ps { dheading = dheading - deltaH }
|
|
|
|
| code == 27 -> accept $ ps { dheading = dheading + deltaH }
|
|
|
|
| otherwise -> deny ps
|
|
|
|
-- on RELEASE only
|
|
|
|
False
|
|
|
|
| code == 26 -> accept $ ps { dz = dz - deltaV }
|
|
|
|
| code == 40 -> accept $ ps { dz = dz + deltaV }
|
|
|
|
| code == 39 -> accept $ ps { dx = dx - deltaV }
|
|
|
|
| code == 41 -> accept $ ps { dx = dx + deltaV }
|
|
|
|
| code == 65 -> accept $ ps { dy = dy + deltaV }
|
|
|
|
| code == 66 -> accept $ ps { dy = dy - deltaV }
|
|
|
|
| code == 25 -> accept $ ps { dheading = dheading + deltaH }
|
|
|
|
| code == 27 -> accept $ ps { dheading = dheading - deltaH }
|
|
|
|
| otherwise -> deny ps
|
|
|
|
else return (ps, False)
|
|
|
|
putMVar state ps'
|
|
|
|
return ret
|
|
|
|
|
2013-12-22 23:29:11 +01:00
|
|
|
main :: IO ()
|
|
|
|
main = do
|
2013-12-29 14:39:01 +01:00
|
|
|
! terrain <- testmap
|
|
|
|
-- create TVar using unsafePerformIO -> currently no other thread -> OK
|
2013-12-29 18:18:18 +01:00
|
|
|
state <- newMVar $ PS { keysPressed = IS.empty
|
2013-12-29 14:39:01 +01:00
|
|
|
, px = 7.5
|
|
|
|
, py = 20
|
|
|
|
, pz = 15
|
|
|
|
, heading = 0
|
|
|
|
, pitch = 60
|
|
|
|
, dx = 0
|
2013-12-29 18:18:18 +01:00
|
|
|
, dy = 0
|
2013-12-29 14:39:01 +01:00
|
|
|
, dz = 0
|
|
|
|
, dheading = 0
|
|
|
|
, dpitch = 0}
|
|
|
|
trace (show terrain) Gtk.initGUI
|
2013-12-23 00:00:51 +01:00
|
|
|
-- Initialise the Gtk+ OpenGL extension
|
|
|
|
-- (including reading various command line parameters)
|
|
|
|
GtkGL.initGL
|
|
|
|
|
|
|
|
-- We need a OpenGL frame buffer configuration to be able to create other
|
|
|
|
-- OpenGL objects.
|
|
|
|
glconfig <- GtkGL.glConfigNew [GtkGL.GLModeRGBA,
|
|
|
|
GtkGL.GLModeDepth,
|
|
|
|
GtkGL.GLModeDouble]
|
|
|
|
|
|
|
|
-- Create an OpenGL drawing area widget
|
|
|
|
canvas <- GtkGL.glDrawingAreaNew glconfig
|
|
|
|
|
2013-12-29 01:05:01 +01:00
|
|
|
Gtk.widgetSetSizeRequest canvas canvasWidth canvasHeight
|
2013-12-23 00:00:51 +01:00
|
|
|
|
|
|
|
-- Initialise some GL setting just before the canvas first gets shown
|
|
|
|
-- (We can't initialise these things earlier since the GL resources that
|
|
|
|
-- we are using wouldn't heve been setup yet)
|
|
|
|
Gtk.onRealize canvas $ GtkGL.withGLDrawingArea canvas $ \_ -> do
|
2013-12-29 06:03:32 +01:00
|
|
|
reconfigure canvasWidth canvasHeight
|
2013-12-29 06:12:25 +01:00
|
|
|
materialAmbient Front $= Color4 0.4 0.4 0.4 1.0
|
|
|
|
materialDiffuse Front $= Color4 0.4 0.4 0.4 1.0
|
|
|
|
materialSpecular Front $= Color4 0.8 0.8 0.8 1.0
|
|
|
|
materialShininess Front $= 25.0
|
|
|
|
|
|
|
|
ambient (Light 0) $= Color4 0.3 0.3 0.3 1.0
|
|
|
|
diffuse (Light 0) $= Color4 1.0 1.0 1.0 1.0
|
|
|
|
specular (Light 0) $= Color4 0.8 0.8 0.8 1.0
|
|
|
|
lightModelAmbient $= Color4 0.2 0.2 0.2 1.0
|
|
|
|
|
|
|
|
lighting $= Enabled
|
|
|
|
light (Light 0) $= Enabled
|
|
|
|
depthFunc $= Just Less
|
|
|
|
|
|
|
|
clearColor $= Color4 0.0 0.0 0.0 0.0
|
|
|
|
drawBuffer $= BackBuffers
|
|
|
|
colorMaterial $= Just (Front, Diffuse)
|
|
|
|
|
|
|
|
texture Texture2D $= Enabled
|
|
|
|
|
|
|
|
shadeModel $= Smooth
|
2013-12-29 06:03:32 +01:00
|
|
|
return ()
|
|
|
|
{-clearColor $= (Color4 0.0 0.0 0.0 0.0)
|
2013-12-23 00:00:51 +01:00
|
|
|
matrixMode $= Projection
|
|
|
|
loadIdentity
|
|
|
|
ortho 0.0 1.0 0.0 1.0 (-1.0) 1.0
|
|
|
|
depthFunc $= Just Less
|
2013-12-29 06:03:32 +01:00
|
|
|
drawBuffer $= BackBuffers-}
|
2013-12-23 00:00:51 +01:00
|
|
|
|
|
|
|
-- Set the repaint handler
|
|
|
|
Gtk.onExpose canvas $ \_ -> do
|
|
|
|
GtkGL.withGLDrawingArea canvas $ \glwindow -> do
|
|
|
|
GL.clear [GL.DepthBuffer, GL.ColorBuffer]
|
2013-12-29 14:39:01 +01:00
|
|
|
display state terrain
|
2013-12-23 00:00:51 +01:00
|
|
|
GtkGL.glDrawableSwapBuffers glwindow
|
|
|
|
return True
|
|
|
|
|
|
|
|
-- Setup the animation
|
|
|
|
Gtk.timeoutAddFull (do
|
2013-12-29 18:18:18 +01:00
|
|
|
updateCamera state
|
2013-12-23 00:00:51 +01:00
|
|
|
Gtk.widgetQueueDraw canvas
|
|
|
|
return True)
|
|
|
|
Gtk.priorityDefaultIdle animationWaitTime
|
|
|
|
|
|
|
|
--------------------------------
|
|
|
|
-- Setup the rest of the GUI:
|
|
|
|
--
|
|
|
|
-- Objects
|
|
|
|
window <- Gtk.windowNew
|
|
|
|
button <- Gtk.buttonNew
|
|
|
|
exitButton <- Gtk.buttonNew
|
2013-12-29 01:05:01 +01:00
|
|
|
label <- Gtk.labelNew (Just "Gtk2Hs using OpenGL via HOpenGL!")
|
2013-12-23 00:00:51 +01:00
|
|
|
vbox <- Gtk.vBoxNew False 4
|
|
|
|
|
|
|
|
--Wrench them together
|
|
|
|
|
|
|
|
Gtk.set window [ Gtk.containerBorderWidth := 10,
|
|
|
|
Gtk.containerChild := canvas,
|
2013-12-29 18:18:18 +01:00
|
|
|
Gtk.windowTitle := "Pioneer" ]
|
2013-12-23 00:00:51 +01:00
|
|
|
|
2013-12-29 18:18:18 +01:00
|
|
|
------
|
|
|
|
-- Events
|
|
|
|
--
|
2013-12-23 00:56:34 +01:00
|
|
|
Gtk.afterClicked button (putStrLn "Hello World")
|
|
|
|
Gtk.afterClicked exitButton Gtk.mainQuit
|
2013-12-23 00:00:51 +01:00
|
|
|
Gtk.onDestroy window Gtk.mainQuit
|
2013-12-23 00:56:34 +01:00
|
|
|
|
2013-12-29 18:18:18 +01:00
|
|
|
Gtk.on window Gtk.keyPressEvent $ keyEvent state True
|
|
|
|
|
|
|
|
Gtk.on window Gtk.keyReleaseEvent $ keyEvent state False
|
|
|
|
|
2013-12-29 01:05:01 +01:00
|
|
|
-- "reshape" event handler
|
2013-12-29 18:18:18 +01:00
|
|
|
Gtk.on canvas Gtk.configureEvent $ Event.tryEvent $ do
|
|
|
|
(w, h) <- Event.eventSize
|
|
|
|
(w', h') <- liftIO $ reconfigure w h
|
|
|
|
liftIO $ Gtk.labelSetText label $ unwords ["Width:",show w',"Height:",show h']
|
2013-12-29 01:05:01 +01:00
|
|
|
|
|
|
|
|
2013-12-23 00:00:51 +01:00
|
|
|
Gtk.widgetShowAll window
|
|
|
|
Gtk.mainGUI
|
2013-12-23 00:56:34 +01:00
|
|
|
|