2012-05-18 15:33:45 +00:00
|
|
|
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
|
2012-05-15 15:25:50 +00:00
|
|
|
module YGL (
|
|
|
|
Point
|
2012-05-18 15:33:45 +00:00
|
|
|
, DisplayableWorld
|
|
|
|
, Camera
|
|
|
|
, YObject
|
2012-05-15 15:25:50 +00:00
|
|
|
, Scalar
|
|
|
|
, Point3D
|
|
|
|
, Function3D
|
|
|
|
, yMainLoop
|
|
|
|
, InputMap
|
|
|
|
, inputMapFromList) where
|
2012-05-15 13:21:18 +00:00
|
|
|
|
|
|
|
import Graphics.Rendering.OpenGL
|
|
|
|
import Graphics.UI.GLUT
|
|
|
|
import Data.IORef
|
|
|
|
import Data.Map ((!))
|
|
|
|
import qualified Data.Map as Map
|
|
|
|
|
2012-05-18 15:33:45 +00:00
|
|
|
{-- Things start to be complex here.
|
|
|
|
- Just take the time to follow me.
|
|
|
|
--}
|
|
|
|
|
|
|
|
{-- A lot of declaration that I find helpful,
|
|
|
|
- I don't like default naming convention --}
|
|
|
|
|
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-18 13:26:36 +00:00
|
|
|
-- | A 3D Point mainly '(x,y,z)'
|
2012-05-15 15:25:50 +00:00
|
|
|
data Point3D = Point3D {
|
|
|
|
xpoint :: Point
|
|
|
|
, ypoint :: Point
|
|
|
|
, zpoint :: Point }
|
2012-05-18 13:26:36 +00:00
|
|
|
|
2012-05-18 15:33:45 +00:00
|
|
|
makePoint3D :: (Scalar,Scalar,Scalar) -> Point3D
|
|
|
|
makePoint3D (x,y,z) = Point3D {xpoint=x, ypoint=y, zpoint=z}
|
|
|
|
|
|
|
|
toGLPoint :: Point3D -> Vector3 GLfloat
|
|
|
|
toGLPoint p = Vector3 (xpoint p) (ypoint p) (zpoint p)
|
|
|
|
|
|
|
|
-- | 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
|
|
|
|
, maxPoint :: Point3D }
|
|
|
|
|
|
|
|
makeBox mini maxi = Box3D {
|
|
|
|
minPoint = makePoint3D mini
|
|
|
|
, maxPoint = makePoint3D maxi }
|
|
|
|
|
|
|
|
-- | We want to be able to create object with
|
|
|
|
-- | many different ways.
|
|
|
|
-- | We then made a type class.
|
|
|
|
-- | A type is in the YObject class if we declare
|
|
|
|
-- | a function triangles which take this type
|
|
|
|
-- | and a bounded box, and return a list of triangles.
|
|
|
|
class YObject objectType where
|
|
|
|
triangles :: objectType -> Box3D -> [Point3D]
|
|
|
|
|
|
|
|
-- | We declare Function3D as f(x,y) -> z
|
2012-05-15 15:25:50 +00:00
|
|
|
type Function3D = Point -> Point -> Maybe Point
|
2012-05-18 15:33:45 +00:00
|
|
|
instance YObject Function3D where
|
|
|
|
-- | The details of the code somewhere else
|
|
|
|
triangles = getObject3DFromShapeFunction
|
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)
|
|
|
|
data UserInput = Press Char | Ctrl Char | Alt Char | CtrlAlt Char
|
|
|
|
|
|
|
|
-- | A displayable world
|
|
|
|
class DisplayableWorld a where
|
2012-05-18 15:33:45 +00:00
|
|
|
camera :: a -> Camera
|
|
|
|
camera _ = defaultCamera
|
|
|
|
lights :: a -> [Light]
|
|
|
|
lights _ = []
|
|
|
|
objects :: (YObject o) => a -> [o]
|
|
|
|
objects _ = []
|
|
|
|
|
|
|
|
-- | 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-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-15 15:25:50 +00:00
|
|
|
x <- [xmin..xmax]
|
|
|
|
y <- [ymin..ymax]
|
|
|
|
let
|
|
|
|
neighbors = [(x,y),(x+1,y),(x+1,y+1),(x,y+1)]
|
|
|
|
-- zs are 3D points with found depth
|
|
|
|
zs = map (\(u,v) -> (u,v,shape u v)) neighbors
|
|
|
|
-- ps are 3D opengl points + color value
|
|
|
|
ps = map (\(u,v,w,c') ->
|
|
|
|
(u/width,v/height,w/depth)) zs
|
|
|
|
-- If the point diverged too fast, don't display it
|
2012-05-18 15:33:45 +00:00
|
|
|
if (and $ map (\(_,_,z) -> z==Nothing) zs)
|
2012-05-15 15:25:50 +00:00
|
|
|
then []
|
|
|
|
-- Draw two triangles
|
|
|
|
-- 3 - 2
|
|
|
|
-- | / |
|
|
|
|
-- 0 - 1
|
|
|
|
else [ps!!0,ps!!1,ps!!2,ps!!0,ps!!2,ps!!3]
|
|
|
|
where
|
|
|
|
-- some naming to make it
|
|
|
|
-- easier to read
|
|
|
|
xmin = xpoint minPoint box
|
|
|
|
xmax = xpoint maxPoint box
|
|
|
|
width = xmax - xmin
|
|
|
|
ymin = ypoint minPoint box
|
2012-05-18 15:33:45 +00:00
|
|
|
ymax = ypoint maxPoint box
|
2012-05-15 15:25:50 +00:00
|
|
|
height = ymax - ymin
|
|
|
|
zmin = zpoint minPoint box
|
2012-05-18 15:33:45 +00:00
|
|
|
zmax = zpoint maxPoint box
|
2012-05-15 15:25:50 +00:00
|
|
|
depth = zmax - zmin
|
|
|
|
|
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) =>
|
|
|
|
String -- window name
|
|
|
|
-> InputMap worldType -- the mapping user input / world
|
2012-05-15 15:25:50 +00:00
|
|
|
-> worldType -- the world state
|
|
|
|
-> IO () -- into IO () for obvious reason
|
|
|
|
yMainLoop windowTitle
|
|
|
|
inputActionMap
|
|
|
|
camera
|
2012-05-18 15:33:45 +00:00
|
|
|
world = do
|
2012-05-15 13:21:18 +00:00
|
|
|
-- The boilerplate
|
|
|
|
(progname,_) <- getArgsAndInitialize
|
|
|
|
initialDisplayMode $=
|
|
|
|
[WithDepthBuffer,DoubleBuffered,RGBMode]
|
|
|
|
createWindow windowTitle
|
|
|
|
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
|
|
|
|
idleCallback $= Just idle
|
|
|
|
-- 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-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-15 13:21:18 +00:00
|
|
|
idle = postRedisplay Nothing
|
|
|
|
|
|
|
|
-- Get User Input
|
2012-05-15 15:25:50 +00:00
|
|
|
-- both cleaner, terser and more expendable than the preceeding code
|
2012-05-15 13:21:18 +00:00
|
|
|
keyboardMouse input world state modifiers position =
|
|
|
|
if modifiers == Down
|
|
|
|
then
|
|
|
|
let transformator = input ! (Press state)
|
|
|
|
in do
|
|
|
|
w <- get world
|
|
|
|
world $= (transformator w)
|
|
|
|
else return ()
|
|
|
|
|
|
|
|
|
|
|
|
-- The function that will display datas
|
2012-05-15 15:25:50 +00:00
|
|
|
display worldRef getViewFromWorld = 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 15:33:45 +00:00
|
|
|
translate $ toGLPoint (position 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)
|
|
|
|
t <- get elapsedTime
|
2012-05-18 15:33:45 +00:00
|
|
|
preservingMatrix $ drawObject (objects w)
|
2012-05-15 15:25:50 +00:00
|
|
|
swapBuffers -- refresh screen
|
2012-05-15 13:21:18 +00:00
|
|
|
|
|
|
|
drawObject shape = do
|
|
|
|
-- We will print Points (not triangles for example)
|
|
|
|
renderPrimitive Triangles $ do
|
2012-05-18 13:26:36 +00:00
|
|
|
mapM_ drawPoint (triangles unityBox shape)
|
2012-05-15 13:21:18 +00:00
|
|
|
where
|
2012-05-18 15:33:45 +00:00
|
|
|
drawPoint (x,y,z) = vertex $ Vertex3 x y z
|
|
|
|
unityBox = makeBox (0,0,0) (1,1,1)
|