keid-core-0.1.8.0: src/Engine/Camera.hs
module Engine.Camera
( ProjectionKind(..)
, Projection(..)
, ProjectionParams
, ProjectionInput(..)
, ProjectionProcess
, spawnPerspective
, mkTransformPerspective
, spawnOrthoPixelsCentered
, mkTransformOrthoPixelsCentered
, spawnProjectionWith
, spawnProjection
, pattern PROJECTION_NEAR
, pattern PROJECTION_FAR
, View(..)
, ViewProcess
, ViewOrbitalInput(..)
, initialOrbitalInput
, mkViewOrbital
, mkViewOrbital_
) where
import RIO
import Geomancy (Transform(..), Vec3, vec3)
import Geomancy.Transform qualified as Transform
import Geomancy.Quaternion qualified as Quaternion
import Geomancy.Vec3 qualified as Vec3
import Geomancy.Vulkan.Projection qualified as Projection
import Geomancy.Vulkan.View qualified as View
import RIO.App (App)
import UnliftIO.Resource (MonadResource)
import Vulkan.Core10 qualified as Vk
import Vulkan.NamedType ((:::))
import Engine.Types qualified as Engine
import Engine.Worker qualified as Worker
-- * Projection
data ProjectionKind
= Perspective
| Orthographic
deriving (Eq, Ord, Show, Enum, Bounded, Generic)
data Projection (pk :: ProjectionKind) = Projection
{ projectionTransform :: Transform
, projectionInverse :: ~Transform
}
deriving (Show, Generic)
type ProjectionProcess pk = Worker.Cell (ProjectionInput pk) (Projection pk)
data ProjectionInput (pk :: ProjectionKind) = ProjectionInput
{ projectionParams :: ProjectionParams pk
, projectionNear :: Float
, projectionFar :: Float
}
-- XXX: undecidable
-- deriving instance (Show (ProjectionParams pk)) => Show (ProjectionInput pk)
type family ProjectionParams (pk :: ProjectionKind) where
ProjectionParams 'Perspective = "fov-v" ::: Float
ProjectionParams 'Orthographic = ()
spawnProjection
:: ( MonadReader (App Engine.GlobalHandles st) m
, MonadResource m
, MonadUnliftIO m
)
=> (Vk.Extent2D -> ProjectionInput pk -> Transform)
-> ProjectionParams pk
-> m (ProjectionProcess pk)
spawnProjection mkTransform params =
spawnProjectionWith mkTransform ProjectionInput
{ projectionNear = PROJECTION_NEAR
, projectionFar = PROJECTION_FAR
, projectionParams = params
}
pattern PROJECTION_NEAR :: (Eq a, Num a, Fractional a) => a
pattern PROJECTION_NEAR = 0x0.02 -- i.e. 1/2048
pattern PROJECTION_FAR :: (Eq a, Num a) => a
pattern PROJECTION_FAR = 16384
spawnProjectionWith
:: ( MonadReader (App Engine.GlobalHandles st) m
, MonadResource m
, MonadUnliftIO m
)
=> (Vk.Extent2D -> ProjectionInput pk -> Transform)
-> ProjectionInput pk
-> m (ProjectionProcess pk)
spawnProjectionWith mkTransform projectionInput = do
screen <- Engine.askScreenVar
input <- Worker.newVar projectionInput
fmap (input,) $
Worker.spawnMerge2
(\s i ->
let
transform = mkTransform s i
in
Projection
{ projectionTransform = transform
, projectionInverse = Transform.inverse transform -- XXX: better provide an inverse directly
}
)
screen
input
spawnPerspective
:: ( MonadReader (App Engine.GlobalHandles st) m
, MonadResource m
, MonadUnliftIO m
)
=> m (ProjectionProcess 'Perspective)
spawnPerspective = spawnProjection mkTransformPerspective (τ / 4)
mkTransformPerspective :: Vk.Extent2D -> ProjectionInput 'Perspective -> Transform
mkTransformPerspective Vk.Extent2D{width, height} ProjectionInput{..} =
Projection.perspective
projectionParams
projectionNear
projectionFar
width
height
spawnOrthoPixelsCentered
:: ( MonadReader (App Engine.GlobalHandles st) m
, MonadResource m
, MonadUnliftIO m
)
=> m (ProjectionProcess 'Orthographic)
spawnOrthoPixelsCentered = spawnProjectionWith mkTransformOrthoPixelsCentered ProjectionInput
{ projectionNear = 0
, projectionFar = 1
, projectionParams = ()
}
mkTransformOrthoPixelsCentered :: Vk.Extent2D -> ProjectionInput 'Orthographic -> Transform
mkTransformOrthoPixelsCentered Vk.Extent2D{width, height} ProjectionInput{..} =
Projection.orthoOffCenter projectionNear projectionFar width height
-- * View
data View = View
{ viewTransform :: Transform
, viewTransformInv :: Transform
, viewPosition :: Vec3
, viewDirection :: Vec3
}
deriving (Show)
type ViewProcess = Worker.Cell ViewOrbitalInput View
-- | Camera orbiting its target
data ViewOrbitalInput = ViewOrbitalInput
{ orbitAzimuth :: Float
, orbitAscent :: Float
, orbitDistance :: Float
, orbitScale :: Float
, orbitTarget :: Vec3
, orbitUp :: Vec3
, orbitRight :: Vec3
}
deriving (Show)
initialOrbitalInput :: ViewOrbitalInput
initialOrbitalInput = ViewOrbitalInput
{ orbitAzimuth = 0 -- τ/8
, orbitAscent = τ/7
, orbitDistance = 8.0
, orbitScale = 1
, orbitTarget = 0
, orbitUp = vec3 0 (-1) 0
, orbitRight = vec3 1 0 0
}
mkViewOrbital :: Vec3 -> ViewOrbitalInput -> View
mkViewOrbital cameraTarget ViewOrbitalInput{..} = View{..}
where
viewTransform = View.lookAt viewPosition cameraTarget orbitUp
viewTransformInv = Transform.inverse viewTransform
viewPosition =
orbitTarget +
Quaternion.rotate
( Quaternion.axisAngle orbitUp orbitAzimuth *
Quaternion.axisAngle orbitRight orbitAscent
)
(vec3 0 0 $ orbitDistance * orbitScale)
viewDirection = Vec3.normalize $ cameraTarget - viewPosition
{-# INLINE mkViewOrbital_ #-}
mkViewOrbital_ :: ViewOrbitalInput -> View
mkViewOrbital_ voi = mkViewOrbital (orbitTarget voi) voi
τ :: Float
τ = 2 * pi