{-| Module : Graphics.Rendering.Handa.Viewer Copyright : (c) 2015 Brian W Bush License : MIT Maintainer : Brian W Bush Stability : Stable Portability : Portable Functions for managing perspectives and frusta. -} {-# LANGUAGE RecordWildCards #-} module Graphics.Rendering.Handa.Viewer ( -- * Viewer Geometry ViewerParameters(..) , viewerGeometry , fieldOfView -- * Typical Devices , phoneViewer , laptopViewer , desktopViewer , projectorViewer -- * Callbacks and Rendering , reshape , loadViewer , dlpViewerDisplay ) where import Data.Default (Default, def) import Graphics.Rendering.DLP (DlpEncoding, DlpEye(..)) import Graphics.Rendering.DLP.Callbacks (DlpDisplay(..)) import Graphics.Rendering.Handa.Util (degree) import Graphics.Rendering.OpenGL (GLdouble, MatrixMode(..), Position(..), Size(..), Vector3(..), Vertex3(..), ($=!), loadIdentity, lookAt, matrixMode, perspective, viewport) import Graphics.UI.GLUT (DisplayCallback, ReshapeCallback) -- | Paramaters specifying a viewer, including the frustum of the view. data ViewerParameters = ViewerParameters { displayAspectRatio :: GLdouble -- ^ The aspect ratio of the screen or display (i.e., the width divided by the height). , displayThrowRatio :: GLdouble -- ^ The throw ratio of the screen or display (i.e., the width divided by the distance). , distanceNearPlane :: GLdouble -- ^ The distance to the near plane of the frustum. , distanceFarPlane :: GLdouble -- ^ The distance to the far plane of the frustum. , eyePosition :: Vertex3 GLdouble -- ^ The position of the eyes. , eyeSeparation :: Vector3 GLdouble -- ^ The separation between the eyes. , eyeUpward :: Vector3 GLdouble -- ^ The upward direction. , sceneCenter :: Vertex3 GLdouble -- ^ The center of the scene. } deriving (Eq, Read, Show) instance Default ViewerParameters where def = ViewerParameters { displayAspectRatio = 1 , displayThrowRatio = 1 , distanceNearPlane = 0.5 , distanceFarPlane = 4.5 , eyePosition = Vertex3 0 0 2 , eyeSeparation = Vector3 0.2 0 0 , eyeUpward = Vector3 0 1 0 , sceneCenter = Vertex3 0 0 0 } -- | Construct viewer geometry from physical geometry. viewerGeometry :: GLdouble -- ^ The width of the screen or display. -> GLdouble -- ^ The height of the screen or display. -> GLdouble -- ^ The distance from the eyes to the screen or display. -> ViewerParameters -- ^ The corresponding viewer parameters. viewerGeometry width height throw = def { displayAspectRatio = width / height , displayThrowRatio = throw / width } -- | Viewer parameters for a typical smartphone screen. phoneViewer :: ViewerParameters phoneViewer = viewerGeometry 5.27 2.80 12 -- | Viewer parameters for a typical laptop screen. laptopViewer :: ViewerParameters laptopViewer = viewerGeometry 13.625 7.875 24 -- | Viewer parameters for a typical desktop monitor. desktopViewer :: ViewerParameters desktopViewer = viewerGeometry 20.75 11.625 32 -- | Viewer parameters for a typical projector. projectorViewer :: ViewerParameters projectorViewer = def { displayAspectRatio = 1.6 / 1.0 , displayThrowRatio = 1.5 / 1.0 } -- | Compute the field of view for viewer parameters. fieldOfView :: ViewerParameters -- ^ The viewer parameters -> GLdouble -- ^ The field of view, in degrees. fieldOfView ViewerParameters{..} = 2 * atan2 0.5 displayThrowRatio * degree -- | Construct a reshape callback from viewer parameters. This simply sets the frustum based on the viewer parameters and the size of the viewport. reshape :: ViewerParameters -- ^ The viewer parameters. -> ReshapeCallback -- ^ The reshape callback. reshape vp@ViewerParameters{..} wh@(Size w h) = do viewport $=! (Position 0 0, wh) matrixMode $=! Projection loadIdentity perspective (fieldOfView vp) (fromIntegral w / fromIntegral h) distanceNearPlane distanceFarPlane matrixMode $=! Modelview 0 -- | Create an action look at the scene according to the viewer parameters. loadViewer :: ViewerParameters -- ^ The viewer parameters. -> DlpEye -- ^ The eye from which to view. -> IO () -- ^ An action for looking at the scene using the specified eye and viewer parameters. loadViewer ViewerParameters{..} eye = do loadIdentity let offset = case eye of LeftDlp -> -1/2 RightDlp -> 1/2 Vertex3 xEye yEye zEye = eyePosition Vector3 dxEye dyEye dzEye = eyeSeparation lookAt (Vertex3 (xEye + offset * dxEye) (yEye + offset * dyEye) (zEye + offset * dzEye)) sceneCenter eyeUpward -- | Construct a DLP display from a display callback. dlpViewerDisplay :: DlpEncoding -- ^ The DLP encoding. -> ViewerParameters -- ^ The viewer parameters. -> DisplayCallback -- ^ The display callback. -> DlpDisplay -- ^ The DLP display data for using the specified encoding, viewer parameters, and display callback. dlpViewerDisplay encoding viewerParameters display = def { dlpEncoding = encoding , doDisplay = \eye -> loadViewer viewerParameters eye >> display }