handa-opengl-0.1.13.0: src/Graphics/Rendering/Handa/Viewer.hs
{-|
Module : Graphics.Rendering.Handa.Viewer
Copyright : (c) 2015 Brian W Bush
License : MIT
Maintainer : Brian W Bush <consult@brianwbush.info>
Stability : Stable
Portability : Portable
Functions for managing perspectives and frusta.
-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE RecordWildCards #-}
module Graphics.Rendering.Handa.Viewer (
-- * Viewer Geometry
ViewerParameters(..)
, viewerGeometry
, displayAspectRatio
, displayThrowRatio
, fieldOfView
-- * Typical Devices
, phoneViewer
, laptopViewer
, desktopViewer
, projectorViewer
, glassesViewer
-- * Callbacks and Rendering
, reshape
, loadViewer
, dlpViewerDisplay
, dlpViewerDisplay'
) where
import Data.AdditiveGroup (AdditiveGroup)
import Data.AffineSpace ((.+^))
import Data.Aeson (FromJSON)
import Data.Binary (Binary)
import Data.Data (Data)
import Data.VectorSpace ((*^))
import Data.Default (Default, def)
import Data.IORef (IORef)
import Foreign.Storable (Storable)
import GHC.Generics (Generic)
import Graphics.Rendering.DLP (DlpEncoding, DlpEye(..))
import Graphics.Rendering.DLP.Callbacks (DlpDisplay(..))
import Graphics.Rendering.Handa.Projection (OffAxisProjection(VTKOffAxis), Screen(..), aspectRatio, projection, throwRatio)
import Graphics.Rendering.Handa.Util (degree)
import Graphics.Rendering.OpenGL (GLdouble, MatrixComponent, MatrixMode(..), Position(..), Vector3(..), Vertex3(..), ($=!), get, loadIdentity, lookAt, matrixMode, scale, viewport)
import Graphics.Rendering.OpenGL.GL.Tensor.Instances ()
import Graphics.UI.GLUT (DisplayCallback, ReshapeCallback)
-- | Paramaters specifying a viewer, including the frustum of the view.
data ViewerParameters a =
ViewerParameters
{
screen :: Screen a -- ^ The screen location.
, nearPlane :: a -- ^ The distance to the near plane of the frustum.
, farPlane :: a -- ^ The distance to the far plane of the frustum.
, eyePosition :: Vertex3 a -- ^ The position of the eyes.
, eyeSeparation :: Vector3 a -- ^ The separation between the eyes.
, eyeUpward :: Vector3 a -- ^ The upward direction.
, sceneCenter :: Vertex3 a -- ^ The center of the scene.
, sceneScale :: Vector3 a -- ^ The factor by which to scale the scene.
}
deriving (Binary, Data, Eq, FromJSON, Generic, Read, Show)
instance Functor ViewerParameters where
fmap f ViewerParameters{..} =
ViewerParameters
{
screen = fmap f screen
, nearPlane = f nearPlane
, farPlane = f farPlane
, eyePosition = fmap f eyePosition
, eyeSeparation = fmap f eyeSeparation
, eyeUpward = fmap f eyeUpward
, sceneCenter = fmap f sceneCenter
, sceneScale = fmap f sceneScale
}
instance (Fractional a, Storable a) => Default (ViewerParameters a) where
def = laptopViewer
-- | Construct viewer geometry from physical geometry.
viewerGeometry :: (Fractional a, Storable a)
=> a -- ^ The aspect ratio (width over height) of the screen or display.
-> a -- ^ The throw ratio (distance over width) of the screen or display.
-> a -- ^ The distance from the eyes to the screen or display, in inches.
-> ViewerParameters a -- ^ The corresponding viewer parameters.
viewerGeometry aspect throw distance =
ViewerParameters
{
screen =
Screen
{
lowerLeft = Vertex3 (- width / 2) (- height / 2) 0
, lowerRight = Vertex3 ( width / 2) (- height / 2) 0
, upperLeft = Vertex3 (- width / 2) ( height / 2) 0
}
, nearPlane = 1
, farPlane = 5 * distance
, eyePosition = Vertex3 0 0 distance
, eyeSeparation = Vector3 (typical * (1 - comfort)) 0 0
, eyeUpward = Vector3 0 1 0
, sceneCenter = Vertex3 0 0 1
, sceneScale = size *^ Vector3 aspect 1 1
}
where
width = distance / throw
height = width / aspect
margin = 0.10 -- 10% margin
size = (1 - margin) / (1 / height + 1 / distance)
-- See <https://www.nvidia.com/content/GTC-2010/pdfs/2010_GTC2010.pdf>:
typical = 2.5 -- inches for typical maximum eye separation
comfort = 0.50 -- 25% reduction in eye separation for comforat at mid-range focus
-- | Viewer parameters for a typical smartphone screen.
phoneViewer :: (Fractional a, Storable a) => ViewerParameters a
phoneViewer = viewerGeometry (1280 / 768) 0.5 18
-- | Viewer parameters for a typical laptop screen.
laptopViewer :: (Fractional a, Storable a) => ViewerParameters a
laptopViewer = viewerGeometry (1920 / 1080) 1.8 24
-- | Viewer parameters for a typical desktop monitor.
desktopViewer :: (Fractional a, Storable a) => ViewerParameters a
desktopViewer = viewerGeometry (1920 / 1080) 1.6 32
-- | Viewer parameters for a typical projector.
projectorViewer :: (Fractional a, Storable a) => ViewerParameters a
projectorViewer = viewerGeometry 1.6 1.5 36
-- | Viewer parameters for typical VR glasses.
glassesViewer :: (Fractional a, Storable a) => ViewerParameters a
glassesViewer =
viewerGeometry (realToFrac $ pixelWidth / pixelHeight) (distance / width) $ 0.2 * distance
where
distance = 4 * 39.37
pixelWidth = 852
pixelHeight = 480
pixelDiagonal = sqrt (pixelWidth * pixelWidth + pixelHeight * pixelHeight)
width = 100 * realToFrac (pixelWidth / pixelDiagonal :: Double)
-- | The aspect ratio of the viewer.
displayAspectRatio :: (AdditiveGroup a, RealFloat a, Storable a)
=> ViewerParameters a -- ^ The viewer parameters.
-> a -- ^ The aspect ratio, namely the screen width divided by its height.
displayAspectRatio ViewerParameters{..} = aspectRatio screen
-- | The throw ratio of the viewer.
displayThrowRatio :: (AdditiveGroup a, RealFloat a, Storable a)
=> ViewerParameters a -- ^ The viewer parameters.
-> a -- ^ The throw ratio, namely the distance to the screen divided by its height.
displayThrowRatio ViewerParameters{..} = throwRatio screen eyePosition
-- | Compute the field of view for viewer parameters.
fieldOfView :: (AdditiveGroup a, RealFloat a, Storable a)
=> ViewerParameters a -- ^ The viewer parameters
-> a -- ^ The field of view, in degrees.
fieldOfView ViewerParameters{..} = 2 * atan2 0.5 (throwRatio screen eyePosition) * 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 :: (AdditiveGroup a, MatrixComponent a, RealFloat a, Storable a)
=> ViewerParameters a -- ^ The viewer parameters.
-> ReshapeCallback -- ^ The reshape callback.
reshape ViewerParameters{..} wh =
do
viewport $=! (Position 0 0, wh)
matrixMode $=! Projection
loadIdentity
projection VTKOffAxis screen eyePosition nearPlane farPlane
matrixMode $=! Modelview 0
-- | Create an action look at the scene according to the viewer parameters.
loadViewer :: (AdditiveGroup a, MatrixComponent a, RealFloat a, Storable a)
=> Bool -- ^ Whether to use an on-axis projection.
-> ViewerParameters a -- ^ 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 onAxis ViewerParameters{..} eye =
let
offset =
case eye of
LeftDlp -> -0.5
RightDlp -> 0.5
eyePosition' = eyePosition .+^ offset *^ eyeSeparation
Vector3 sx sy sz = realToFrac <$> sceneScale :: Vector3 GLdouble
in
if onAxis
then do
loadIdentity
lookAt
(realToFrac <$> eyePosition')
(realToFrac <$> sceneCenter)
(realToFrac <$> eyeUpward)
scale sx sy sz
else do
matrixMode $=! Projection
loadIdentity
projection VTKOffAxis screen eyePosition' nearPlane farPlane
matrixMode $=! Modelview 0
loadIdentity
scale sx sy sz
-- | Construct a DLP display from a display callback.
dlpViewerDisplay :: (AdditiveGroup a, MatrixComponent a, RealFloat a, Storable a)
=> Bool -- ^ Whether to use on-axis projection.
-> DlpEncoding -- ^ The DLP encoding.
-> ViewerParameters a -- ^ The viewer parameters.
-> DisplayCallback -- ^ The display callback.
-> DlpDisplay -- ^ The DLP display data for using the specified encoding, viewer parameters, and display callback.
dlpViewerDisplay onAxis encoding viewerParameters display =
def
{
dlpEncoding = encoding
, doDisplay = \eye -> loadViewer onAxis viewerParameters eye >> display
}
-- | Construct a DLP display from a display callback.
dlpViewerDisplay' :: (AdditiveGroup a, MatrixComponent a, RealFloat a, Storable a)
=> Bool -- ^ Whether to use on-axis projection.
-> DlpEncoding -- ^ The DLP encoding.
-> IORef (ViewerParameters a) -- ^ A reference to the viewer parameters.
-> DisplayCallback -- ^ The display callback.
-> DlpDisplay -- ^ The DLP display data for using the specified encoding, viewer parameters, and display callback.
dlpViewerDisplay' onAxis encoding viewerParameters display =
def
{
dlpEncoding = encoding
, doDisplay = \eye -> do
viewerParameters' <- get viewerParameters
loadViewer onAxis viewerParameters' eye
display
}