2012-05-22 15:04:08 +00:00
|
|
|
-- The languages include needed because I wanted to use
|
|
|
|
-- (Point,Point,Point) instead of
|
|
|
|
-- data Point3D = Point3D (Point,Point,Point) deriving ...
|
|
|
|
{-
|
|
|
|
The module YGL will contains most boilerplate
|
|
|
|
And display details.
|
|
|
|
|
|
|
|
To make things even nicer, we should separate
|
|
|
|
this file in many different parts.
|
|
|
|
Typically separate the display function.
|
|
|
|
|
|
|
|
-}
|
2012-05-15 15:25:50 +00:00
|
|
|
module YGL (
|
2012-05-21 12:41:55 +00:00
|
|
|
-- Datas
|
|
|
|
Point
|
2012-05-23 15:01:43 +00:00
|
|
|
, Time
|
2012-05-15 15:25:50 +00:00
|
|
|
, Scalar
|
2012-05-22 15:04:08 +00:00
|
|
|
, Point3D
|
2012-05-21 12:41:55 +00:00
|
|
|
, makePoint3D -- helper (x,y,z) -> Point3D
|
|
|
|
, (-*<) -- scalar product on Point3D
|
2012-05-15 15:25:50 +00:00
|
|
|
, Function3D
|
2012-05-21 12:41:55 +00:00
|
|
|
-- Your world state must be an instance
|
|
|
|
-- of the DisplayableWorld type class
|
2012-05-22 20:40:44 +00:00
|
|
|
, DisplayableWorld (..)
|
2012-05-21 12:41:55 +00:00
|
|
|
-- Datas related to DisplayableWorld
|
2012-05-22 20:40:44 +00:00
|
|
|
, Camera (..)
|
|
|
|
, YObject (..)
|
|
|
|
, Box3D (..)
|
2012-05-23 15:01:43 +00:00
|
|
|
, makeBox
|
2012-05-21 12:41:55 +00:00
|
|
|
-- Datas related to user Input
|
2012-05-15 15:25:50 +00:00
|
|
|
, InputMap
|
2012-05-21 12:41:55 +00:00
|
|
|
, UserInput (Press,Ctrl,Alt,CtrlAlt)
|
|
|
|
, inputMapFromList
|
|
|
|
-- The main loop function to call
|
|
|
|
, yMainLoop) where
|
2012-05-15 13:21:18 +00:00
|
|
|
|
2012-05-22 13:53:11 +00:00
|
|
|
import Numeric (readHex)
|
2012-05-15 13:21:18 +00:00
|
|
|
import Graphics.Rendering.OpenGL
|
|
|
|
import Graphics.UI.GLUT
|
|
|
|
import Data.IORef
|
|
|
|
import qualified Data.Map as Map
|
2012-05-21 12:41:55 +00:00
|
|
|
import Control.Monad (when)
|
|
|
|
import Data.Maybe (isNothing)
|
2012-05-15 13:21:18 +00:00
|
|
|
|
2012-05-18 15:33:45 +00:00
|
|
|
{-- Things start to be complex here.
|
|
|
|
- Just take the time to follow me.
|
|
|
|
--}
|
|
|
|
|
2012-05-18 13:26:36 +00:00
|
|
|
-- | A 1D point
|
2012-05-15 15:25:50 +00:00
|
|
|
type Point = GLfloat
|
2012-05-18 13:26:36 +00:00
|
|
|
-- | A Scalar value
|
2012-05-15 15:25:50 +00:00
|
|
|
type Scalar = GLfloat
|
2012-05-23 15:01:43 +00:00
|
|
|
-- | The time type (currently its Int
|
|
|
|
type Time = Int
|
2012-05-18 13:26:36 +00:00
|
|
|
-- | A 3D Point mainly '(x,y,z)'
|
2012-05-22 15:04:08 +00:00
|
|
|
data Point3D = P (Point,Point,Point) deriving (Eq,Show,Read)
|
|
|
|
|
|
|
|
xpoint :: Point3D -> Point
|
|
|
|
xpoint (P (x,_,_)) = x
|
|
|
|
ypoint :: Point3D -> Point
|
|
|
|
ypoint (P (_,y,_)) = y
|
|
|
|
zpoint :: Point3D -> Point
|
|
|
|
zpoint (P (_,_,z)) = z
|
2012-05-18 13:26:36 +00:00
|
|
|
|
2012-05-21 12:41:55 +00:00
|
|
|
makePoint3D :: (Point,Point,Point) -> Point3D
|
2012-05-22 15:04:08 +00:00
|
|
|
makePoint3D p = P p
|
2012-05-18 15:33:45 +00:00
|
|
|
|
2012-05-21 12:41:55 +00:00
|
|
|
|
2012-05-22 15:04:08 +00:00
|
|
|
instance Num Point3D where
|
|
|
|
(+) (P (ax,ay,az)) (P (bx,by,bz)) = P (ax+bx,ay+by,az+bz)
|
|
|
|
(-) (P (ax,ay,az)) (P (bx,by,bz)) = P (ax-bx,ay-by,az-bz)
|
|
|
|
(*) (P (ax,ay,az)) (P (bx,by,bz)) = P ( ay*bz - az*by
|
|
|
|
, az*bx - ax*bz
|
|
|
|
, ax*by - ay*bx )
|
|
|
|
abs (P (x,y,z)) = P (abs x,abs y, abs z)
|
|
|
|
signum (P (x,y,z)) = P (signum x, signum y, signum z)
|
|
|
|
fromInteger i = P (fromInteger i, 0, 0)
|
|
|
|
|
2012-05-21 12:41:55 +00:00
|
|
|
infixr 5 -*<
|
|
|
|
(-*<) :: Scalar -> Point3D -> Point3D
|
2012-05-22 15:04:08 +00:00
|
|
|
(-*<) s p = P (s*xpoint p, s*ypoint p, s*zpoint p)
|
|
|
|
|
2012-05-21 12:41:55 +00:00
|
|
|
|
2012-05-18 17:22:28 +00:00
|
|
|
toGLVector3 :: Point3D -> Vector3 GLfloat
|
2012-05-22 15:04:08 +00:00
|
|
|
toGLVector3 (P(x,y,z)) = Vector3 x y z
|
2012-05-18 17:22:28 +00:00
|
|
|
|
|
|
|
toGLVertex3 :: Point3D -> Vertex3 GLfloat
|
2012-05-22 15:04:08 +00:00
|
|
|
toGLVertex3 (P(x,y,z)) = Vertex3 x y z
|
2012-05-18 15:33:45 +00:00
|
|
|
|
2012-05-22 13:53:11 +00:00
|
|
|
toGLNormal3 :: Point3D -> Normal3 GLfloat
|
2012-05-22 15:04:08 +00:00
|
|
|
toGLNormal3 (P(x,y,z)) = Normal3 x y z
|
2012-05-22 13:53:11 +00:00
|
|
|
|
2012-05-18 15:33:45 +00:00
|
|
|
-- | The Box3D type represent a 3D bounding box
|
|
|
|
-- | Note if minPoint = (x,y,z) and maxPoint = (x',y',z')
|
|
|
|
-- | Then to have a non empty box you must have
|
|
|
|
-- | x<x' & y<y' & z<z'
|
|
|
|
data Box3D = Box3D {
|
|
|
|
minPoint :: Point3D
|
2012-05-18 17:22:28 +00:00
|
|
|
, maxPoint :: Point3D
|
|
|
|
, resolution :: Scalar }
|
2012-05-18 15:33:45 +00:00
|
|
|
|
2012-05-21 12:41:55 +00:00
|
|
|
makeBox :: (Point,Point,Point) -> (Point,Point,Point) -> Scalar -> Box3D
|
|
|
|
makeBox mini maxi res = Box3D {
|
2012-05-18 15:33:45 +00:00
|
|
|
minPoint = makePoint3D mini
|
2012-05-18 17:22:28 +00:00
|
|
|
, maxPoint = makePoint3D maxi
|
2012-05-21 12:41:55 +00:00
|
|
|
, resolution = res }
|
|
|
|
|
2012-05-15 15:25:50 +00:00
|
|
|
type Function3D = Point -> Point -> Maybe Point
|
2012-05-22 20:04:22 +00:00
|
|
|
data YObject = XYFunc Function3D Box3D
|
|
|
|
| XYSymFunc Function3D Box3D
|
2012-05-21 12:41:55 +00:00
|
|
|
| Tri [Point3D]
|
|
|
|
|
2012-05-22 20:04:22 +00:00
|
|
|
triangles :: YObject -> [Point3D]
|
|
|
|
triangles (XYFunc f b) = getObject3DFromShapeFunction f b
|
|
|
|
triangles (XYSymFunc f b) = tris ++
|
|
|
|
( reverse $ map (\(P(x,y,z)) -> P (x,y,-z)) tris )
|
|
|
|
where tris = getObject3DFromShapeFunction f b
|
|
|
|
triangles (Tri tri) = tri
|
2012-05-18 13:26:36 +00:00
|
|
|
|
|
|
|
-- | We decalre the input map type we need here
|
|
|
|
-- | It is our API
|
|
|
|
type InputMap worldType = Map.Map UserInput (worldType -> worldType)
|
2012-05-18 17:22:28 +00:00
|
|
|
data UserInput = Press Char | Ctrl Char | Alt Char | CtrlAlt Char
|
|
|
|
deriving (Eq,Ord,Show,Read)
|
2012-05-18 13:26:36 +00:00
|
|
|
|
|
|
|
-- | A displayable world
|
2012-05-18 18:01:24 +00:00
|
|
|
class DisplayableWorld world where
|
|
|
|
camera :: world -> Camera
|
2012-05-18 15:33:45 +00:00
|
|
|
camera _ = defaultCamera
|
2012-05-18 18:01:24 +00:00
|
|
|
lights :: world -> [Light]
|
2012-05-18 15:33:45 +00:00
|
|
|
lights _ = []
|
2012-05-21 12:41:55 +00:00
|
|
|
objects :: world -> [YObject]
|
2012-05-18 15:33:45 +00:00
|
|
|
objects _ = []
|
2012-05-23 15:01:43 +00:00
|
|
|
winTitle :: world -> String
|
|
|
|
winTitle _ = "YGL"
|
2012-05-18 15:33:45 +00:00
|
|
|
|
|
|
|
-- | the Camera type to know how to
|
|
|
|
-- | Transform the scene to see the right view.
|
2012-05-18 13:26:36 +00:00
|
|
|
data Camera = Camera {
|
2012-05-18 15:33:45 +00:00
|
|
|
camPos :: Point3D
|
|
|
|
, camDir :: Point3D
|
|
|
|
, camZoom :: Scalar }
|
2012-05-18 13:26:36 +00:00
|
|
|
|
2012-05-21 12:41:55 +00:00
|
|
|
defaultCamera :: Camera
|
2012-05-18 15:33:45 +00:00
|
|
|
defaultCamera = Camera {
|
|
|
|
camPos = makePoint3D (0,0,0)
|
|
|
|
, camDir = makePoint3D (0,0,0)
|
|
|
|
, camZoom = 1 }
|
2012-05-15 15:25:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- Given a shape function and a delimited Box3D
|
|
|
|
-- return a list of Triangles to be displayed
|
|
|
|
getObject3DFromShapeFunction :: Function3D -> Box3D -> [Point3D]
|
2012-05-18 15:33:45 +00:00
|
|
|
getObject3DFromShapeFunction shape box = do
|
2012-05-18 17:22:28 +00:00
|
|
|
x <- [xmin,xmin+res..xmax]
|
|
|
|
y <- [ymin,ymin+res..ymax]
|
2012-05-15 15:25:50 +00:00
|
|
|
let
|
2012-05-22 13:53:11 +00:00
|
|
|
neighbors = [(x,y),(x+res,y),(x+res,y+res),(x,y+res)]
|
2012-05-15 15:25:50 +00:00
|
|
|
-- zs are 3D points with found depth
|
|
|
|
zs = map (\(u,v) -> (u,v,shape u v)) neighbors
|
|
|
|
-- ps are 3D opengl points + color value
|
2012-05-18 17:22:28 +00:00
|
|
|
removeMaybe (u,v,Just w) = (u,v,w)
|
2012-05-21 12:41:55 +00:00
|
|
|
removeMaybe (_,_,Nothing) = (0,0,0)
|
2012-05-18 17:22:28 +00:00
|
|
|
ps = map removeMaybe zs
|
2012-05-15 15:25:50 +00:00
|
|
|
-- If the point diverged too fast, don't display it
|
2012-05-21 12:41:55 +00:00
|
|
|
if any (\(_,_,z) -> isNothing z) zs
|
2012-05-15 15:25:50 +00:00
|
|
|
then []
|
|
|
|
-- Draw two triangles
|
|
|
|
-- 3 - 2
|
|
|
|
-- | / |
|
|
|
|
-- 0 - 1
|
2012-05-22 13:53:11 +00:00
|
|
|
-- The order is important
|
|
|
|
else map makePoint3D [ps!!0,ps!!2,ps!!1,ps!!0,ps!!3,ps!!2]
|
2012-05-15 15:25:50 +00:00
|
|
|
where
|
|
|
|
-- some naming to make it
|
|
|
|
-- easier to read
|
2012-05-18 17:22:28 +00:00
|
|
|
xmin = xpoint $ minPoint box
|
|
|
|
xmax = xpoint $ maxPoint box
|
|
|
|
ymin = ypoint $ minPoint box
|
|
|
|
ymax = ypoint $ maxPoint box
|
|
|
|
res = resolution box
|
2012-05-15 15:25:50 +00:00
|
|
|
|
2012-05-21 12:41:55 +00:00
|
|
|
inputMapFromList :: (DisplayableWorld world) =>
|
|
|
|
[(UserInput,world -> world)] -> InputMap world
|
2012-05-15 13:21:18 +00:00
|
|
|
inputMapFromList = Map.fromList
|
|
|
|
|
2012-05-18 15:33:45 +00:00
|
|
|
{--
|
|
|
|
- We set our mainLoop function
|
|
|
|
- As you can see the code is _not_ pure
|
|
|
|
- and not even functionnal friendly!
|
|
|
|
- But when called,
|
|
|
|
- it will look like a standard function.
|
|
|
|
--}
|
|
|
|
yMainLoop :: (DisplayableWorld worldType) =>
|
2012-05-23 15:01:43 +00:00
|
|
|
InputMap worldType -- the mapping user input / world
|
|
|
|
-> (Time -> worldType -> worldType)
|
2012-05-15 15:25:50 +00:00
|
|
|
-> worldType -- the world state
|
|
|
|
-> IO () -- into IO () for obvious reason
|
2012-05-23 15:01:43 +00:00
|
|
|
yMainLoop inputActionMap
|
|
|
|
worldTranformer
|
2012-05-18 15:33:45 +00:00
|
|
|
world = do
|
2012-05-15 13:21:18 +00:00
|
|
|
-- The boilerplate
|
2012-05-21 12:41:55 +00:00
|
|
|
_ <- getArgsAndInitialize
|
2012-05-15 13:21:18 +00:00
|
|
|
initialDisplayMode $=
|
|
|
|
[WithDepthBuffer,DoubleBuffered,RGBMode]
|
2012-05-23 15:01:43 +00:00
|
|
|
_ <- createWindow $ winTitle world
|
2012-05-15 13:21:18 +00:00
|
|
|
depthFunc $= Just Less
|
|
|
|
windowSize $= Size 500 500
|
|
|
|
-- The state variables for the world (I know it feels BAD)
|
|
|
|
worldRef <- newIORef world
|
|
|
|
-- Action to call when waiting
|
2012-05-23 15:01:43 +00:00
|
|
|
idleCallback $= Just (idle worldTranformer worldRef)
|
2012-05-15 13:21:18 +00:00
|
|
|
-- the keyboard will update the world
|
|
|
|
keyboardMouseCallback $=
|
|
|
|
Just (keyboardMouse inputActionMap worldRef)
|
|
|
|
-- We generate one frame using the callback
|
2012-05-18 15:33:45 +00:00
|
|
|
displayCallback $= display worldRef
|
2012-05-22 13:53:11 +00:00
|
|
|
-- Lights
|
|
|
|
lighting $= Enabled
|
2012-05-23 12:17:24 +00:00
|
|
|
ambient (Light 1) $= Color4 0.99 0.98 0.62 1
|
|
|
|
diffuse (Light 1) $= Color4 0.99 0.98 0.62 1
|
|
|
|
position (Light 1) $= Vertex4 0 0 1 0.1
|
2012-05-22 13:53:11 +00:00
|
|
|
light (Light 1) $= Enabled
|
2012-05-15 13:21:18 +00:00
|
|
|
-- We enter the main loop
|
|
|
|
mainLoop
|
|
|
|
|
2012-05-15 15:25:50 +00:00
|
|
|
-- When no user input entered do nothing
|
2012-05-23 15:01:43 +00:00
|
|
|
idle :: (Time -> worldType -> worldType) -> IORef worldType -> IO ()
|
|
|
|
idle worldTranformer world = do
|
|
|
|
w <- get world
|
|
|
|
t <- get elapsedTime
|
|
|
|
world $= worldTranformer t w
|
|
|
|
postRedisplay Nothing
|
2012-05-15 13:21:18 +00:00
|
|
|
|
|
|
|
-- Get User Input
|
2012-05-15 15:25:50 +00:00
|
|
|
-- both cleaner, terser and more expendable than the preceeding code
|
2012-05-21 12:41:55 +00:00
|
|
|
keyboardMouse :: InputMap a -> IORef a
|
2012-05-18 17:22:28 +00:00
|
|
|
-> Key -> KeyState -> Modifiers -> Position -> IO()
|
2012-05-21 12:41:55 +00:00
|
|
|
keyboardMouse input world key state _ _ =
|
|
|
|
when (state == Down) $
|
2012-05-18 17:22:28 +00:00
|
|
|
let
|
|
|
|
charFromKey (Char c) = c
|
2012-05-21 12:41:55 +00:00
|
|
|
-- To replace
|
2012-05-21 15:03:17 +00:00
|
|
|
charFromKey _ = '#'
|
2012-05-21 12:41:55 +00:00
|
|
|
|
2012-05-21 15:03:17 +00:00
|
|
|
transformator = Map.lookup (Press (charFromKey key)) input
|
|
|
|
in
|
|
|
|
mayTransform transformator
|
|
|
|
where
|
|
|
|
mayTransform Nothing = return ()
|
|
|
|
mayTransform (Just transform) = do
|
2012-05-15 13:21:18 +00:00
|
|
|
w <- get world
|
2012-05-21 15:03:17 +00:00
|
|
|
world $= transform w
|
2012-05-15 13:21:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- The function that will display datas
|
2012-05-21 12:41:55 +00:00
|
|
|
display :: (HasGetter g, DisplayableWorld world) =>
|
|
|
|
g world -> IO ()
|
2012-05-18 17:22:28 +00:00
|
|
|
display worldRef = do
|
2012-05-18 15:33:45 +00:00
|
|
|
-- BEWARE UGLINESS!!!!
|
2012-05-15 15:25:50 +00:00
|
|
|
-- SHOULD NEVER MODIFY worldRef HERE!!!!
|
2012-05-18 15:33:45 +00:00
|
|
|
--
|
|
|
|
-- I SAID NEVER.
|
2012-05-15 15:25:50 +00:00
|
|
|
w <- get worldRef
|
2012-05-18 15:33:45 +00:00
|
|
|
-- NO REALLY, NEVER!!!!
|
|
|
|
-- If someone write a line starting by
|
|
|
|
-- w $= ... Shoot him immediately in the head
|
|
|
|
-- and refere to competent authorities
|
|
|
|
let cam = camera w
|
2012-05-15 15:25:50 +00:00
|
|
|
-- set the background color (dark solarized theme)
|
|
|
|
clearColor $= Color4 0 0.1686 0.2117 1
|
|
|
|
clear [ColorBuffer,DepthBuffer]
|
|
|
|
-- Transformation to change the view
|
|
|
|
loadIdentity -- reset any transformation
|
|
|
|
-- tranlate
|
2012-05-18 17:22:28 +00:00
|
|
|
translate $ toGLVector3 (camPos cam)
|
2012-05-15 15:25:50 +00:00
|
|
|
-- zoom
|
2012-05-18 15:33:45 +00:00
|
|
|
scale (camZoom cam) (camZoom cam) (camZoom cam)
|
2012-05-15 15:25:50 +00:00
|
|
|
-- rotate
|
2012-05-18 15:33:45 +00:00
|
|
|
rotate (xpoint (camDir cam)) $ Vector3 1.0 0.0 (0.0::GLfloat)
|
|
|
|
rotate (ypoint (camDir cam)) $ Vector3 0.0 1.0 (0.0::GLfloat)
|
|
|
|
rotate (zpoint (camDir cam)) $ Vector3 0.0 0.0 (1.0::GLfloat)
|
2012-05-15 15:25:50 +00:00
|
|
|
-- Now that all transformation were made
|
|
|
|
-- We create the object(s)
|
2012-05-21 15:03:17 +00:00
|
|
|
_ <- preservingMatrix $ mapM drawObject (objects w)
|
2012-05-15 15:25:50 +00:00
|
|
|
swapBuffers -- refresh screen
|
2012-05-15 13:21:18 +00:00
|
|
|
|
2012-05-22 13:53:11 +00:00
|
|
|
-- Hexa style colors
|
|
|
|
scalarFromHex :: String -> Scalar
|
|
|
|
scalarFromHex = (/256) . fst . head . readHex
|
|
|
|
|
|
|
|
hexColor :: [Char] -> Color3 Scalar
|
|
|
|
hexColor ('#':rd:ru:gd:gu:bd:bu:[]) = Color3 (scalarFromHex (rd:ru:[]))
|
|
|
|
(scalarFromHex (gd:gu:[]))
|
|
|
|
(scalarFromHex (bd:bu:[]))
|
|
|
|
hexColor ('#':r:g:b:[]) = hexColor ('#':r:r:g:g:b:b:[])
|
|
|
|
hexColor _ = error "Bad color!!!!"
|
|
|
|
---
|
|
|
|
|
2012-05-18 18:01:24 +00:00
|
|
|
-- drawObject :: (YObject obj) => obj -> IO()
|
2012-05-21 12:41:55 +00:00
|
|
|
drawObject :: YObject -> IO()
|
2012-05-21 15:03:17 +00:00
|
|
|
drawObject shape = do
|
2012-05-15 13:21:18 +00:00
|
|
|
-- We will print Points (not triangles for example)
|
2012-05-21 15:03:17 +00:00
|
|
|
renderPrimitive Triangles $ do
|
2012-05-22 13:53:11 +00:00
|
|
|
-- solarized base3 color
|
|
|
|
-- color $ Color3 (0.988::Point) (0.96::Point) (0.886::Point)
|
|
|
|
color $ hexColor "#fdf6e3"
|
2012-05-22 20:04:22 +00:00
|
|
|
drawTriangles (triangles shape)
|
2012-05-15 13:21:18 +00:00
|
|
|
where
|
2012-05-22 13:53:11 +00:00
|
|
|
drawTriangles tri@(p0:p1:p2:points) = do
|
|
|
|
normal $ toGLNormal3 trinorm
|
|
|
|
vertex $ toGLVertex3 p0
|
|
|
|
vertex $ toGLVertex3 p1
|
|
|
|
vertex $ toGLVertex3 p2
|
|
|
|
drawTriangles points
|
|
|
|
where
|
|
|
|
trinorm = (getNormal tri)
|
|
|
|
drawTriangles _ = return ()
|
|
|
|
|
2012-05-22 15:04:08 +00:00
|
|
|
getNormal :: [Point3D] -> Point3D
|
|
|
|
getNormal (p0:p1:p2:_) = (p1 - p0) * (p2 - p0)
|
2012-05-22 13:53:11 +00:00
|
|
|
getNormal _ = makePoint3D (0,0,1)
|