packages feed

keid-core-0.1.6.0: src/Engine/Camera/Controls.hs

module Engine.Camera.Controls
  ( Camera.ProjectionProcess
  , Camera.ViewProcess
  , spawnViewOrbital

  , Controls(..)
  , ControlsProcess
  , spawnControls

  , panInstant
  ) where

import RIO

import Engine.Camera qualified as Camera
import Engine.Worker qualified as Worker
import Geomancy (Vec3, vec3)
import Geomancy.Quaternion qualified as Quaternion

spawnViewOrbital :: Camera.ViewOrbitalInput -> RIO env Camera.ViewProcess
spawnViewOrbital = Worker.spawnCell Camera.mkViewOrbital_

data Controls a = Controls
  { panHorizontal   :: a
  , panVertical     :: a
  , turnAzimuth     :: a
  , turnInclination :: a
  }
  deriving (Functor, Foldable, Traversable)

type ControlsProcess = Controls (Worker.Timed Float ())

spawnControls :: Camera.ViewProcess -> RIO env ControlsProcess
spawnControls vp =
  traverse mkUpdater Controls
    { panHorizontal   = panTargetHorizontal
    , panVertical     = panTargetVertical
    , turnAzimuth     = orbitAzimuthTurn
    , turnInclination = orbitAscentTurn
    }
  where
    vpInput = Worker.getInput vp

    dtI = 1e3
    dt = fromIntegral dtI / 1e6

    mkUpdater updater =
      Worker.spawnTimed
        True
        (Left dtI)
        (\_delta -> pure ((), 0.0))
        (\acceleration delta -> do
            when (delta /= 0) $
              Worker.pushInput vpInput $
                updater (1 + acceleration) (delta * dt)
            pure
              ( Nothing
              , if delta == 0 then
                  if acceleration < 1/128 then
                    acceleration
                  else
                    acceleration * 0.97
                else
                  acceleration + 0.01
              )
        )
        0

    panTargetHorizontal acceleration delta voi = voi
      { Camera.orbitTarget = Camera.orbitTarget voi + pan
      }
      where
        pan = Quaternion.rotate
          (Quaternion.axisAngle up azimuth)
          (vec3 (delta * acceleration) 0 0)
        up = Camera.orbitUp voi
        azimuth = Camera.orbitAzimuth voi

    panTargetVertical acceleration delta voi = voi
      { Camera.orbitTarget = Camera.orbitTarget voi + pan
      }
      where
        pan =
          Quaternion.rotate
            (Quaternion.axisAngle up azimuth)
            (vec3 0 0 (delta * acceleration))

        up = Camera.orbitUp voi
        azimuth = Camera.orbitAzimuth voi

    orbitAzimuthTurn _acceleration delta voi = voi
      { Camera.orbitAzimuth = azimuth
      }
      where
        azimuth
          | azimuth' < (-τ) = azimuth' + (2 * τ)
          | azimuth' > τ = azimuth' - (2 * τ)
          | otherwise = azimuth'

        azimuth' = Camera.orbitAzimuth voi + delta

    orbitAscentTurn _acceleration delta voi = voi
      { Camera.orbitAscent = ascent
      }
      where
        ascent =
          max (-limit) . min limit $
            Camera.orbitAscent voi + delta

        limit = τ/4 - 1/512

panInstant :: MonadIO m => Camera.ViewProcess -> Vec3 -> m ()
panInstant vp delta3 = do
  Worker.pushInput vp \voi@Camera.ViewOrbitalInput{..} ->
    let
      pan azimuth = Quaternion.rotate
        (Quaternion.axisAngle orbitUp azimuth)
        delta3 * 0.01
    in
      voi
        { Camera.orbitTarget =
            Camera.orbitTarget voi + pan orbitAzimuth
        }

τ :: Float
τ = 2 * pi