packages feed

3d-graphics-examples-0.0.0.0: src/l-systems/LSystems.hs

module Main (main) where

--------------------------------------------------------------------------------
--
-- Graphische Darstellung von Lindenmayer-Systemen
--
-- Tastenbelegungen im Hauptfenster:
-- Links, Rechts              -> Ansicht um Up-Achse drehen
-- Oben, Unten                -> Ansicht um Head-Achse drehen
-- Strg + Pfeiltaste          -> Ansicht verschieben
-- '+', '-'                   -> Ableitungsschritt ändern
-- 'I', 'O'                   -> Hinein- bzw. Hinauszoomen
--
--------------------------------------------------------------------------------

import Graphics.UI.GLUT hiding (initState)
import Graphics.Rendering.OpenGL

import LSystem
import Turtle

import Utilities

import Data.Char (toUpper)
import Data.IORef

import KochLSystem     -- Koch'sche Schneeflocke, Präfix: "koch"
import IslandLSystem   -- Aufgabe 1.a, Präfix: "island"
import TreeLSystem     -- Aufgabe 1.b, Präfix: "tree"
import ConiferLSystem  -- Aufgabe 1.c, Präfix: "conifer"

-- Präfix für andere Beispiele ändern
global_Interpretation = kochInterpretation
global_LSystem = kochLSystem


-- Aktueller Zustand des Systems
data State = State {
  derivationIndex :: Int,

  viewPhi :: Double,
  viewTheta :: Double,
  zoom :: Double,
  pan :: (Double, Double),
  viewRatio :: Double,
  renderingRequired :: Bool,

  currentLineWidth :: Double,
  lineWidthStack :: [Double]
  }


initState = State {
  derivationIndex = 0,

  viewPhi = 0,
  viewTheta = 0,
  zoom = 1,
  pan = (0,0),
  viewRatio =
    (fromIntegral global_windowSizeX) / (fromIntegral global_windowSizeY),
  renderingRequired = True,

  currentLineWidth = 1,
  lineWidthStack = []
  }


-- globale Werte
global_windowTitle = "Lindenmayer-Systeme"
global_windowSizeX = 400
global_windowSizeY = 400
global_pixelWidth = 2 / (fromIntegral global_windowSizeX)



-------------------------------------------------------------------------------
-- MAIN
-------------------------------------------------------------------------------


main = do
  getArgsAndInitialize
  initialDisplayMode $= [WithDepthBuffer, DoubleBuffered]

  state <- newIORef initState

  depthFunc $= Just Less
  createWindow global_windowTitle
  windowSize $= Size global_windowSizeX global_windowSizeY

  lighting $= Enabled
  normalize $= Enabled
  depthFunc $= Just Less

  displayCallback $= display state
  keyboardMouseCallback $= Just (keyboard state)
  reshapeCallback $= Just (reshape state)

  mainLoop



-- Hauptzeichenfunktion --------------------------------------------------------


display state = do
  curState <- get state
  let
    z = realToReal $ zoom curState
    (panX, panY) = pan curState
    rat = viewRatio curState

  projection (-z) (z) (-z / realToReal rat) (z / realToReal rat) (-1000) (1000)

  clearColor $= Color4 0 0 0 0
  clear [ColorBuffer, DepthBuffer]

  loadIdentity

  position (Light 0) $= Vertex4 (-1) (1) 10 1
  ambient (Light 0) $= Color4 1 1 1 1
  diffuse (Light 0) $= Color4 1 1 1 1
  specular (Light 0) $= Color4 1 1 1 1
  light (Light 0) $= Enabled

  cCyanMaterial

  translate $ Vector3 (doubleToGLfloat panX) (doubleToGLfloat panY) 0

  rotate (doubleToGLfloat $ negate $ viewTheta curState) $ Vector3 1 0 0
  rotate (doubleToGLfloat $ viewPhi curState) $ Vector3 0 0 1

  let i = derivationIndex curState

  if (renderingRequired curState)
    then defineList (DisplayList 1) CompileAndExecute $ do
      sequence_ $ (render state $
        interpretations global_Interpretation $
        derivations global_LSystem) !! i

    else do
      callList (DisplayList 1)

  windowTitle $= global_windowTitle ++ " (n=" ++ (show i) ++ ")"
  state $= curState { renderingRequired = False }
  swapBuffers



-- Fensterskalierung -----------------------------------------------------------


reshape state s = do
  curState <- get state
  let (Size x y) = s
  state $= curState { viewRatio = (fromIntegral x) / (fromIntegral y) }
  viewport $= (Position 0 0, s)



-- Tastatur-Ereignisverarbeitung -----------------------------------------------


keyboard state (Char key) Down _ _ = do
  curState <- get state
  let
    i = derivationIndex curState
    z = zoom curState

  case toUpper key of
    'I' -> do
      state $= curState { zoom = z / 1.05 }
      postRedisplay Nothing
    'O' -> do
      state $= curState { zoom = z * 1.05 }
      postRedisplay Nothing
    '+' -> do
      state $= curState { derivationIndex = i+1, renderingRequired = True }
      postRedisplay Nothing
    '-' -> do
      state $= curState { derivationIndex = if (i==0) then 0 else i-1,
        renderingRequired = True }
      postRedisplay Nothing
    _ -> return ()

keyboard state (SpecialKey specialKey) Down mod _ = do
  curState <- get state
  let
    phi = viewPhi curState
    theta = viewTheta curState
    ctrlPressed = ctrl mod == Down
    z = zoom curState
    (x,y) = pan curState

  case specialKey of
    KeyLeft -> do
      if ctrlPressed
        then do
          state $= curState { pan = (x-0.05*z, y) }
        else do
          state $= curState { viewPhi = if phi < 0 then phi+355 else phi-5 }
      postRedisplay Nothing
    KeyRight -> do
      if ctrlPressed
        then do
          state $= curState { pan = (x+0.05*z, y) }
        else do
          state $= curState { viewPhi = if phi >= 360 then phi-355 else phi+5 }
      postRedisplay Nothing

    KeyDown -> do
      if ctrlPressed
        then do
          state $= curState { pan = (x, y-0.05*z) }
        else do
          state $= curState
            { viewTheta = if theta < 0 then theta+355 else theta-5 }
      postRedisplay Nothing
    KeyUp -> do
      if ctrlPressed
        then do
          state $= curState { pan = (x, y+0.05*z) }
        else do
          state $= curState
            { viewTheta = if theta >= 360 then theta-355 else theta+5 }
      postRedisplay Nothing
    _ -> return ()

keyboard _ _ _ _ _ = return ()



-- Grafik-Berechnung -----------------------------------------------------------


-- Drehung um alpha Grad um die Up-Achse
rotateU :: GLfloat -> IO ()
rotateU alpha = rotate alpha $ Vector3 0 0 (1::GLfloat)

turnLeft = rotateU
turnRight = rotateU . negate
turnAround = rotateU 180


-- Drehung um alpha Grad um die Left-Achse
rotateL :: GLfloat -> IO ()
rotateL alpha = rotate alpha $ Vector3 0 (1) (0::GLfloat)

pitchDown = rotateL
pitchUp = rotateL . negate


-- Drehung um alpha Grad um die Heading-Achse
rotateH :: GLfloat -> IO ()
rotateH alpha = rotate alpha $ Vector3 (-1) 0 (0::GLfloat)

rollLeft = rotateH
rollRight = rotateH . negate


-- forward state length draw
--
-- Turtle length Pixel vorwärts bewegen. Ist draw = True wird ein Zylinder
-- der Länge length im Raum gezeichnet, sonst nur die Position verändert.
--
forward :: IORef State -> GLfloat -> Bool -> IO ()
forward state length draw = do
  curState <- get state

  let
    len = global_pixelWidth * length
    wid = global_pixelWidth * realToReal (currentLineWidth curState)
    r = wid/2
    d = global_pixelWidth

  if draw
    then do
      if len/=0 then drawCylinder r r len 32 else return ()
      translate $ Vector3 len 0 0
      renderObject Solid $
        Sphere' (realToReal $ r + (global_pixelWidth/10)) 8 8
    else do
      translate $ Vector3 len 0 0


-- Liste von TurtleModul-Sequenzen in Zeichenfunktionen übersetzen
render :: IORef State -> [ [LPrim TurtleModule] ] -> [ [IO ()] ]
render state = map render'
  where
    render' = map r
    r (LStack s) = do
      curState <- get state
      let
        oldStack = lineWidthStack curState
        wid = currentLineWidth curState
      state $= curState { lineWidthStack = wid:oldStack }
      preservingMatrix $ sequence_ $ render' s
      state $= curState { lineWidthStack = oldStack }

    r (LPrim p) = case p of
      TDraw len         -> forward state (realToReal len) True
      TMove len         -> forward state (realToReal len) False

      TTurnLeft alpha   -> turnLeft $ realToReal alpha
      TTurnRight alpha  -> turnRight $ realToReal alpha
      TTurnAround       -> turnAround

      TPitchDown alpha  -> pitchDown $ realToReal alpha
      TPitchUp alpha    -> pitchUp $ realToReal alpha

      TRollLeft alpha   -> rollLeft $ realToReal alpha
      TRollRight alpha  -> rollRight $ realToReal alpha

      TSetLineWidth wid -> do
        curState <- get state
        state $= curState { currentLineWidth = realToReal wid }


-- Zeichnet Zylinder in der y-z-Ebene (= Turtle-Rücken-Ebene)
drawCylinder ry rz len n = do
  renderPrimitive QuadStrip (vertexesN $ interleave fullE fullE')
  where
    fullE' = map (\(x,y,z) -> (x+len,y,z)) fullE
    fullE = [(0,y,z::GLfloat) | i<-[0..n],
      let a = 2 * pi * i/n, let y = -ry * cos a, let z = rz * sin a]


-- Gibt eine Liste von Punkten in Form von vertex-Befehlen wieder. Die Liste
-- wird als QuadStrip interpretiert und für jedes Rechteck der entsprechende
-- Normalenvektor eingefügt
vertexesN :: (VertexComponent a, NormalComponent a, Num a) => [(a,a,a)] -> IO ()
vertexesN (a:b:c:d:rs) = do
  normal $ norm a b c
  vertex $ vert a
  vertex $ vert b
  vertexesN (c:d:rs)
  where
    vert = \(x,y,z) -> Vertex3 x y z
    norm = \v1 v2 v3 -> let (x, y, z) = (v2-v1) * (v3-v1) in Normal3 x y z
-- Restliche Punkte einfach in Vertexes umwandeln
vertexesN rs = sequence_ $ map (\(x,y,z) -> vertex (Vertex3 x y z)) rs