Haskell+gtk+gtkglext
gearsを移植。frame rateの表示とか、物体の回転とかは省いている。物体の回転は、quaternionで実現する予定。
{-# LANGUAGE TypeFamilies #-} import System import List import Control.Monad import Control.Monad.Trans import Data.IORef import Data.Tensor import qualified Graphics.UI.Gtk as Gtk import Graphics.UI.Gtk (AttrOp((:=)), on) import qualified Graphics.UI.Gtk.Gdk.EventM as Gdk.EventM import qualified Graphics.UI.Gtk.Gdk.Events as Gdk.Events import qualified Graphics.UI.Gtk.OpenGL as GtkGL import qualified Graphics.Rendering.OpenGL as GL import Graphics.Rendering.OpenGL (($=)) import Graphics.Rendering.OpenGL.GL.VertexSpec import Graphics.Rendering.OpenGL.Raw.Core31 (GLfloat,GLdouble,GLint) --アプリケーションの状態を表す変数(共通参照) data AppCtx = AppCtx { isSync :: Bool, angle :: GLfloat, gear1 :: GL.DisplayList, gear2 :: GL.DisplayList, gear3 :: GL.DisplayList, view :: Vector3 GLfloat, idleHandlerId :: Gtk.HandlerId } priorityRedraw :: Int priorityRedraw = Gtk.priorityHighIdle + 20 createGear:: GLfloat -> GLfloat -> GLfloat -> GLint -> GLfloat -> IO () createGear innterRadius outerRadius width teeth toothDepth = do let r0 = innterRadius r1 = outerRadius - (toothDepth / 2.0) r2 = outerRadius + (toothDepth / 2.0) da = 2.0 * pi / (fromIntegral teeth) /4.0 GL.shadeModel $= GL.Flat GL.normal (Normal3 0.0 0.0 1.0 :: Normal3 GLfloat) GL.renderPrimitive GL.QuadStrip $ drawFrontFace teeth (r0, r1, r2, da) GL.renderPrimitive GL.Quads $ drawFrontSidesTeeth teeth (r0, r1, r2, da) GL.normal (Normal3 0.0 0.0 (-1.0) :: Normal3 GLfloat) GL.renderPrimitive GL.QuadStrip $ drawBackFace teeth (r0, r1, r2, da) GL.renderPrimitive GL.Quads $ drawBackSidesTeeth teeth (r0, r1, r2, da) GL.renderPrimitive GL.QuadStrip $ drawOutwardFaceOfTeeth teeth (r0, r1, r2, da) GL.shadeModel $= GL.Smooth GL.renderPrimitive GL.QuadStrip $ drawInsideRadiusCylinder teeth r0 where drawFrontFace :: GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> IO () drawFrontFace teeth (r0, r1, r2, da) = drawFrontFaceRepeat 0 teeth (r0, r1, r2, da) where drawSurface :: GLint -> GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> IO () drawSurface counter teeth (r0, r1, r2, da) = do let angle = ((fromIntegral counter) * 2.0 * pi)/(fromIntegral teeth) GL.vertex (Vertex3 (r0*(cos angle)) (r0*(sin angle)) (width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r1*(cos angle)) (r1*(sin angle)) (width*0.5) :: Vertex3 GLfloat) if (counter < teeth) then (do GL.vertex (Vertex3 (r0*(cos angle)) (r0*(sin angle)) (width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r1*(cos $ angle + 3*da)) (r1*(sin $ angle + 3*da)) (width*0.5) :: Vertex3 GLfloat) drawFrontFaceRepeat (counter+1) teeth (r0, r1, r2, da) return ()) else drawFrontFaceRepeat (counter+1) teeth (r0, r1, r2, da) drawFrontFaceRepeat :: GLint -> GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> IO () drawFrontFaceRepeat counter teeth (r0, r1, r2, da) = if (counter <= teeth) then drawSurface counter teeth (r0, r1, r2, da) else return () drawFrontSidesTeeth :: GLint-> (GLfloat, GLfloat, GLfloat, GLfloat) -> IO () drawFrontSidesTeeth teeth (r0, r1, r2, da) = drawFrontSidesTeethRepeat 0 teeth (r0, r1, r2, da) where drawFrontSidesTeethRepeat :: GLint -> GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> IO () drawFrontSidesTeethRepeat counter teeth (r0, r1, r2, da) = do let angle = ((fromIntegral counter) * 2.0 * pi)/(fromIntegral teeth) if (counter < teeth) then (do GL.vertex (Vertex3 (r1*(cos angle)) (r1*(sin angle)) (width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r2*(cos $ angle + da)) (r2*(sin $ angle + da)) (width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r2*(cos $ angle + 2*da)) (r2*(sin $ angle + 2*da)) (width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r1*(cos $ angle + 3*da)) (r1*(sin $ angle + 3*da)) (width*0.5) :: Vertex3 GLfloat) drawFrontSidesTeethRepeat (counter+1) teeth (r0, r1, r2, da)) else return () drawBackFace :: GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> IO () drawBackFace teeth (r0, r1, r2, da) = drawBackFaceRepeat 0 teeth (r0, r1, r2, da) where drawSurface :: GLint -> GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> IO () drawSurface counter teeth (r0, r1, r2, da) = do let angle = ((fromIntegral counter)*2.0*pi)/(fromIntegral teeth) GL.vertex (Vertex3 (r1*(cos angle)) (r1*(sin angle)) (-width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r0*(cos angle)) (r0*(sin angle)) (-width*0.5) :: Vertex3 GLfloat) if (counter < teeth) then (do GL.vertex (Vertex3 (r1*(cos $ angle + 3*da)) (r1*(sin $ angle + 3*da)) (-width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r0*(cos angle)) (r0*(sin angle)) (-width*0.5) :: Vertex3 GLfloat) drawBackFaceRepeat (counter+1) teeth (r0, r1, r2, da)) else drawBackFaceRepeat (counter+1) teeth (r0, r1, r2, da) drawBackFaceRepeat :: GLint -> GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> IO () drawBackFaceRepeat counter teeth (r0, r1, r2, da) = if (counter <= teeth) then drawSurface counter teeth (r0, r1, r2, da) else return () drawBackSidesTeeth :: GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> IO () drawBackSidesTeeth teeth (r0, r1, r2, da) = drawBackSidesTeethRepeat 0 teeth (r0, r1, r2, da) where drawBackSidesTeethRepeat :: GLint -> GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> IO () drawBackSidesTeethRepeat counter teeth (r0, r1, r2, da) = do let angle = ((fromIntegral counter)*2.0*pi)/(fromIntegral teeth) if (counter < teeth) then (do GL.vertex (Vertex3 (r1*(cos $ angle + 3*da)) (r1*(sin $ angle + 3*da)) (-width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r2*(cos $ angle + 2*da)) (r2*(sin $ angle + 2*da)) (-width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r2*(cos $ angle + da)) (r2*(sin $ angle + da)) (-width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r1*(cos angle)) (r1*(sin angle)) (-width*0.5) :: Vertex3 GLfloat) drawBackSidesTeethRepeat (counter+1) teeth (r0, r1, r2, da)) else return () drawOutwardFaceOfTeeth :: GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> IO () drawOutwardFaceOfTeeth teeth (r0, r1, r2, da) = do drawOutwardFaceOfTeethRepeat 0 teeth (r0, r1, r2, da) GL.vertex (Vertex3 (r1*(cos 0)) (r2*(sin 0)) (width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r1*(cos 0)) (r2*(sin 0)) (-width*0.5) :: Vertex3 GLfloat) where drawOutwardFaceOfTeethRepeat :: GLint -> GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> IO () drawOutwardFaceOfTeethRepeat counter teeth (r0, r1, r2, da) = do if (counter < teeth) then (do let angle = ((fromIntegral counter)*2.0*pi)/(fromIntegral teeth) drawFrontFaceOfTeeth counter teeth (r0, r1, r2, da) angle drawBackFaceOfTeeth counter teeth (r0, r1, r2, da) angle drawOutwardFaceOfTeethRepeat (counter+1) teeth (r0, r1, r2, da)) else return () where drawFrontFaceOfTeeth :: GLint -> GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> GLfloat -> IO () drawFrontFaceOfTeeth counter teeth (r0, r1, r2, da) angle = do let u' = r2*(cos $ angle+da) - r1*(cos $ angle) v' = r2*(sin $ angle+da) - r1*(sin $ angle) len = sqrt (u'*u' + v'+v') u = u'/len v = v'/len GL.vertex (Vertex3 (r1*(cos angle)) (r1*(sin angle)) (width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r1*(cos angle)) (r1*(sin angle)) (-width*0.5) :: Vertex3 GLfloat) GL.normal (Normal3 v (-u) 0.0 :: Normal3 GLfloat) GL.vertex (Vertex3 (r2*(cos $ angle+da)) (r2*(sin $ angle+da)) (width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r2*(cos $ angle+da)) (r2*(sin $ angle+da)) (-width*0.5) :: Vertex3 GLfloat) GL.normal (Normal3 (cos angle) (sin angle) 0.0) drawBackFaceOfTeeth :: GLint -> GLint -> (GLfloat, GLfloat, GLfloat, GLfloat) -> GLfloat -> IO () drawBackFaceOfTeeth counter teeth (r0, r1, r2, da) angle = do let u' = r1*(cos $ angle + 3*da) - r2*(cos $ angle + 2*da) v' = r1*(sin $ angle + 3*da) - r2*(sin $ angle + 2*da) len = sqrt (u'*u' + v'+v') u = u'/len v = v'/len GL.vertex (Vertex3 (r2*(cos $ angle + 2*da)) (r2*(sin $ angle + 2*da)) (width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r2*(cos $ angle + 2*da)) (r2*(sin $ angle + 2*da)) (-width*0.5) :: Vertex3 GLfloat) GL.normal (Normal3 v (-u) 0.0 :: Normal3 GLfloat) GL.vertex (Vertex3 (r1*(cos $ angle + 3*da)) (r1*(sin $ angle + 3*da)) (width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r1*(cos $ angle + 3*da)) (r1*(sin $ angle + 3*da)) (-width*0.5) :: Vertex3 GLfloat) GL.normal (Normal3 (cos angle) (sin angle) 0.0) drawInsideRadiusCylinder :: GLint -> GLfloat -> IO () drawInsideRadiusCylinder teeth r0 = drawInsideRadiusCylinderRepeat 0 teeth r0 where drawInsideRadiusCylinderRepeat :: GLint -> GLint -> GLfloat -> IO () drawInsideRadiusCylinderRepeat counter teeth r0 = if (counter <= teeth) then (do let angle = ((fromIntegral counter)*2.0*pi)/(fromIntegral teeth) GL.normal (Normal3 (-(cos angle)) (-(sin angle)) 0.0 :: Normal3 GLfloat) GL.vertex (Vertex3 (r0*(cos angle)) (r0*(sin angle)) (-width*0.5) :: Vertex3 GLfloat) GL.vertex (Vertex3 (r0*(cos angle)) (r0*(sin angle)) (width*0.5) :: Vertex3 GLfloat) drawInsideRadiusCylinderRepeat (counter+1) teeth r0) else return () createGearDisplayList :: Color4 GLfloat -> GLfloat -> GLfloat -> GLfloat -> GLint -> GLfloat -> IO GL.DisplayList createGearDisplayList color innterRadius outerRadius width teeth toothDepth = do GL.defineNewList GL.Compile $ do GL.materialAmbientAndDiffuse GL.Front $= color createGear innterRadius outerRadius width teeth toothDepth return () createDisplayList :: IORef AppCtx -> IO () createDisplayList appCtx = do -- gearのモデルを作る newGear1 <- createGearDisplayList red 1.0 4.0 1.0 20 0.7 newGear2 <- createGearDisplayList green 0.5 2.0 2.0 10 0.7 newGear3 <- createGearDisplayList blue 1.3 2.0 0.5 10 0.7 (AppCtx isSync angle _ _ _ view idleHandlerId) <- readIORef appCtx writeIORef appCtx AppCtx { isSync = isSync, angle = angle, gear1 = newGear1, gear2 = newGear2, gear3 = newGear3, view = view, idleHandlerId = idleHandlerId } return () where red = Color4 0.8 0.1 0.0 1.0 :: Color4 GLfloat green = Color4 0.0 0.8 0.2 1.0 :: Color4 GLfloat blue = Color4 0.2 0.2 1.0 1.0 :: Color4 GLfloat setupGLenv :: GtkGL.GLWindow -> IO () setupGLenv glWindow = do GL.position (GL.Light 0) $= lightPosition GL.frontFace $= GL.CW GL.cullFace $= Just GL.Front -- glCullFace(GL_FRONT); glEnable (GL_CULL_FACE); GL.normalize $= GL.Enabled GL.lighting $= GL.Enabled GL.light (GL.Light 0) $= GL.Enabled GL.depthFunc $= Just GL.Less -- glEnable(GL_DEPTH_TEST) --透視変換を設定 (w,h) <- GtkGL.glDrawableGetSize glWindow let ratio = (fromIntegral h)/(fromIntegral w) GL.viewport $= (GL.Position 0 0, (GL.Size (fromIntegral w) (fromIntegral h))) GL.matrixMode $= GL.Projection GL.loadIdentity GL.frustum (-1.0) 1.0 (-ratio) ratio 5.0 60.0 GL.matrixMode $= GL.Modelview 0 GL.loadIdentity GL.translate (Vector3 0.0 0.0 (-40.0)::Vector3 GLfloat) return () where lightPosition = Vertex4 5.0 5.0 10.0 0.0 :: Vertex4 GLfloat initGLDrawEnv :: IORef AppCtx -> GtkGL.GLWindow -> IO () initGLDrawEnv appCtx glWindow = do createDisplayList appCtx setupGLenv glWindow draw :: IORef AppCtx -> GtkGL.GLWindow -> IO Bool draw appCtx glWindow = do (AppCtx _ angle gear1 gear2 gear3 view@(Vector3 view_rotx view_roty view_rotz) _) <- readIORef appCtx GL.clear [GL.ColorBuffer, GL.DepthBuffer] GL.preservingMatrix $ do GL.rotate view_rotx (Vector3 1.0 0.0 0.0 :: Vector3 GLfloat) GL.rotate view_roty (Vector3 0.0 1.0 0.0 :: Vector3 GLfloat) GL.rotate view_rotz (Vector3 0.0 0.0 1.0 :: Vector3 GLfloat) GL.preservingMatrix $ do GL.translate (Vector3 (-3.0) (-2.0) 0.0 :: Vector3 GLfloat) GL.rotate angle (Vector3 0.0 0.0 1.0 :: Vector3 GLfloat) GL.callList gear1 GL.preservingMatrix $ do GL.translate (Vector3 3.1 (-2.0) 0.0 :: Vector3 GLfloat) GL.rotate (-2.0*angle-9.0) (Vector3 0.0 0.0 1.0 :: Vector3 GLfloat) GL.callList gear2 GL.preservingMatrix $ do GL.translate (Vector3 (-3.1) (4.0) 0.0 :: Vector3 GLfloat) GL.rotate (-2.0*angle-25.0) (Vector3 0.0 0.0 1.0 :: Vector3 GLfloat) GL.callList gear3 GtkGL.glDrawableSwapBuffers glWindow return True main :: IO () main = do Gtk.initGUI GtkGL.initGL args <- getArgs -- 共通参照を作成 appCtx <- newIORef AppCtx { isSync = not $ any (\s -> (s == "--async")) args, angle = 0.0, gear1 = GL.DisplayList 0, gear2 = GL.DisplayList 0, gear3 = GL.DisplayList 0, view = Vector3 20.0 30.0 0.0, idleHandlerId = 0 } -- OpenGL frame bufferの設定 glConfig <- GtkGL.glConfigNew[GtkGL.GLModeRGBA, GtkGL.GLModeDepth, GtkGL.GLModeDouble] window <- createWindow appCtx glConfig Gtk.widgetShowAll window Gtk.mainGUI where idleCallback :: IORef AppCtx -> GtkGL.GLDrawingArea -> IO Bool idleCallback appCtx drawingArea = do (AppCtx isSync angle gear1 gear2 gear3 view idleHandlerId) <- readIORef appCtx writeIORef appCtx AppCtx { isSync = isSync, angle = if (angle+2.0 >= 360.0) then 0.0 else angle+2.0, gear1 = gear1, gear2 = gear2, gear3 = gear3, view = view, idleHandlerId = idleHandlerId } drawWindow <- Gtk.widgetGetDrawWindow drawingArea allocation <- Gtk.widgetGetAllocation drawingArea Gtk.drawWindowInvalidateRect drawWindow allocation False -- onExpose Eventを発生させる。 if isSync then do Gtk.drawWindowProcessUpdates drawWindow False return True else return True idleAdd :: IORef AppCtx -> GtkGL.GLDrawingArea-> IO () idleAdd appCtx drawingArea = do (AppCtx isSync angle gear1 gear2 gear3 view idleHandlerId) <- readIORef appCtx if (idleHandlerId == 0) then do newHandlerId <- Gtk.idleAdd (idleCallback appCtx drawingArea) priorityRedraw writeIORef appCtx AppCtx { isSync = isSync, angle = angle, gear1 = gear1, gear2 = gear2, gear3 = gear3, view = view, idleHandlerId = newHandlerId } else return () idleRemove :: IORef AppCtx -> GtkGL.GLDrawingArea-> IO () idleRemove appCtx drawingArea = do (AppCtx isSync angle gear1 gear2 gear3 view idleHandlerId) <- readIORef appCtx if (idleHandlerId /= 0) then do Gtk.idleRemove idleHandlerId writeIORef appCtx AppCtx { isSync = isSync, angle = angle, gear1 = gear1, gear2 = gear2, gear3 = gear3, view = view, idleHandlerId = 0 } else return () afterRealizeCallback :: IORef AppCtx -> GtkGL.GLDrawingArea -> IO () afterRealizeCallback appCtx drawingArea = GtkGL.withGLDrawingArea drawingArea $ initGLDrawEnv appCtx onConfigureEventCallback :: IORef AppCtx -> GtkGL.GLDrawingArea -> Gdk.Events.Event -> IO Bool onConfigureEventCallback appCtx drawingArea _ = GtkGL.withGLDrawingArea drawingArea (\glWindow -> do (w,h) <- GtkGL.glDrawableGetSize glWindow let ratio = (fromIntegral h)/(fromIntegral w) GL.viewport $= (GL.Position 0 0, (GL.Size (fromIntegral w) (fromIntegral h))) GL.matrixMode $= GL.Projection GL.loadIdentity GL.frustum (-1.0) 1.0 (-ratio) ratio 5.0 60.0 GL.matrixMode $= GL.Modelview 0 GL.loadIdentity GL.translate (Vector3 0.0 0.0 (-40.0)::Vector3 GLfloat) return True) onExposeCallback :: IORef AppCtx -> GtkGL.GLDrawingArea -> Gdk.Events.Event -> IO Bool onExposeCallback appCtx drawingArea _ = GtkGL.withGLDrawingArea drawingArea $ draw appCtx mapEventCallback :: IORef AppCtx -> GtkGL.GLDrawingArea -> Gdk.EventM.EventM Gdk.EventM.EAny () mapEventCallback appCtx drawingArea = liftIO $ idleAdd appCtx drawingArea unmapEventCallback :: IORef AppCtx -> GtkGL.GLDrawingArea -> Gdk.EventM.EventM Gdk.EventM.EAny () unmapEventCallback appCtx drawingArea = liftIO $ idleRemove appCtx drawingArea visibilityNotifyEventCallback :: IORef AppCtx -> GtkGL.GLDrawingArea -> Gdk.EventM.EventM Gdk.EventM.EVisibility () visibilityNotifyEventCallback appCtx drawingArea = do visibility <- Gdk.EventM.eventVisibilityState liftIO $ if (visibility == Gtk.VisibilityFullyObscured) then idleRemove appCtx drawingArea else idleAdd appCtx drawingArea return () createWindow :: IORef AppCtx -> GtkGL.GLConfig -> IO Gtk.Window createWindow appCtx glConfig = do -- 描画Widget作成 drawingArea <- GtkGL.glDrawingAreaNew glConfig Gtk.widgetSetSizeRequest drawingArea 500 500 -- どのEventを取るかのmask Gtk.widgetAddEvents drawingArea [Gtk.ScrollMask, Gtk.Button1MotionMask] -- drawingAreaにrealize after eventを登録する Gtk.afterRealize drawingArea $ afterRealizeCallback appCtx drawingArea -- drawingAreaにconfigure eventを登録する Gtk.onConfigure drawingArea $ onConfigureEventCallback appCtx drawingArea -- drawingAreaにexpose eventを登録する Gtk.onExpose drawingArea $ onExposeCallback appCtx drawingArea -- drawingAreaに map eventを登録する drawingArea `on` Gtk.mapEvent $ Gdk.EventM.tryEvent $ mapEventCallback appCtx drawingArea -- drawingAreaに unmap eventを登録する drawingArea `on` Gtk.unmapEvent $ Gdk.EventM.tryEvent $ unmapEventCallback appCtx drawingArea -- drawingAreaに visibility notify eventを登録する drawingArea `on` Gtk.visibilityNotifyEvent $ Gdk.EventM.tryEvent $ visibilityNotifyEventCallback appCtx drawingArea -- Quit Button quitButton <- Gtk.buttonNewWithLabel "Quit" Gtk.onClicked quitButton Gtk.mainQuit -- vbox作成 vbox <- Gtk.vBoxNew False 0 -- vboxにdrawingAreaを付ける。 Gtk.set vbox [ Gtk.containerChild := drawingArea, Gtk.containerChild := quitButton ] -- ベースのwindow widget window <- Gtk.windowNew Gtk.set window [ Gtk.windowTitle := "Gears", Gtk.containerChild := vbox ] Gtk.widgetSetRedrawOnAllocate window True -- Destroy Eventに終了関数をつなげる Gtk.onDestroy window Gtk.mainQuit return window