Files
loop/src/Dodge/Update/Camera.hs
T

247 lines
8.8 KiB
Haskell

{- Functions controlling the movement of the screen camera:
'_cameraCenter', '_cameraZoom', _cameraRot';
and the position that the character sees from: '_cameraViewFrom'. -}
module Dodge.Update.Camera (
updateCamera,
) where
import Bound
import Dodge.Viewpoints
import Control.Monad
import Data.Foldable
import qualified Data.Map.Strict as M
import Data.Maybe
import Dodge.Base
import Dodge.Creature.Test
import Dodge.Data.Config
import Dodge.Data.World
import Dodge.Equipment
import Dodge.Zoning.Wall
import Geometry
import qualified IntMapHelp as IM
import LensHelp
import qualified SDL
{- Update the screen camera rotation and position, including any in rold scope/remote camera modifiers;
update where your avatar's view is from. -}
updateCamera :: Configuration -> World -> World
updateCamera cfig w =
w
& updateBounds cfig
& over (cWorld . camPos) (setViewDistance cfig)
& cWorld . camPos %~ moveZoomCamera cfig (w ^. input) (you w)
& updateScopeZoom
& rotateCamera cfig
moveZoomCamera :: Configuration -> Input -> Creature -> CamPos -> CamPos
moveZoomCamera cfig theinput cr campos =
campos
& camCenter .~ newcen
& camViewFrom .~ newvf
& camZoom .~ newzoom
& camDefaultZoom .~ newDefaultZoom
& camItemZoom .~ newItemZoom
where
cpos = _crPos cr
mitm = do
i <- cr ^? crInvSel . isel . ispItem
cr ^? crInv . ix i
newvf = cpos +.+ fromMaybe (V2 0 0) vfoffset
vfoffset = do
iscam <- mitm ^? _Just . itScope . scopeIsCamera
guard iscam
guard (SDL.ButtonRight `M.member` _mouseButtons theinput)
mitm ^? _Just . itScope . scopePos
mscopeoffset = do
guard (SDL.ButtonRight `M.member` _mouseButtons theinput)
mitm ^? _Just . itScope . scopePos
newcen = cpos +.+ fromMaybe (V2 0 0) mscopeoffset +.+ offset
offset = rotateV (campos ^. camRot)
$ ((newzoom - newDefaultZoom) / (newDefaultZoom * newzoom)) *.* _mousePos theinput
newzoom = case mitm ^? _Just . itScope of
Just zs@ZoomScope{} -> _scopeZoom zs
_ -> newDefaultZoom * newItemZoom
idealDefaultZoom = clipZoom wallZoom
newDefaultZoom = case mitm ^? _Just . itScope of
Just zs@ZoomScope{} -> _scopeZoom zs
_ -> changeZoom (campos ^. camDefaultZoom) idealDefaultZoom
idealItemZoom = fromMaybe 1 $ do
guard $ crIsAiming cr
zoomFromItem <$> (mitm ^? _Just . itUse . heldAim . aimZoom)
newItemZoom = changeZoom (campos ^. camItemZoom) idealItemZoom
changeZoom curZoom idealZoom
| curZoom > idealZoom + 0.01 = ((zoomOutSpeed -1) * curZoom + idealZoom) / zoomOutSpeed
| curZoom < idealZoom - 0.01 = ((zoomInSpeed -1) * curZoom + idealZoom) / zoomInSpeed
| otherwise = idealZoom
wallZoom = min4 (campos ^. camBoundDist)
min4 (a, b, c, d) = minimum [hh / maxd a, hh / maxd (- b), hw / maxd c, hw / maxd (- d)]
maxd = max distFromEqmnt
distFromEqmnt = foldl' max 1 $ IM.mapMaybe (_eeViewDist . _equipEffect . _itUse) $ getCrEquipment cr
hw = halfWidth cfig
hh = halfHeight cfig
-- = maybe clipZoom zoomFromItem (yourItem w ^? itZoom) wallZoom
-- these speeds are inverted, larger means slower
zoomInSpeed = 25
zoomOutSpeed = 15
updateScopeZoom :: World -> World
updateScopeZoom w = fromMaybe w $ do
i <- w ^? cWorld . lWorld . creatures . ix 0 . crInvSel . isel . ispItem
return $ updateScopeZoom' i w
updateScopeZoom' :: Int -> World -> World
updateScopeZoom' i w
| SDL.ButtonRight `M.member` _mouseButtons (_input w) = w & wppointer %~ doScopeZoom mp
| otherwise = w & wppointer %~ resetscope
where
wppointer = cWorld . lWorld . creatures . ix 0 . crInv . ix i . itScope
resetscope (ZoomScope _ _ _ defz bl) = ZoomScope (V2 0 0) 0 defz defz bl
resetscope otherAtt = otherAtt
mp = rotateV (w ^. cWorld . camPos . camRot) $ _mousePos (_input w)
doScopeZoom :: Point2 -> Scope -> Scope
doScopeZoom mp sc = case sc ^? scopeZoomChange of
Just x
| x > 10 -> (zoomInLongGun mp . zoomInLongGun mp . zoomInLongGun mp) $ sc
& scopeZoomChange -~ 2
| x > 5 -> (zoomInLongGun mp . zoomInLongGun mp) $ sc
& scopeZoomChange -~ 1
| x > 0 -> zoomInLongGun mp $ sc
& scopeZoomChange -~ 1
| x < -10 -> (zoomOutLongGun . zoomOutLongGun . zoomOutLongGun) $ sc
& scopeZoomChange +~ 2
| x < -5 -> (zoomOutLongGun . zoomOutLongGun) $ sc
& scopeZoomChange +~ 1
| x < 0 -> zoomOutLongGun $ sc
& scopeZoomChange +~ 1
| otherwise -> sc
_ -> sc
zoomSpeed :: Float
zoomSpeed = 39 / 40
zoomInLongGun :: Point2 -> Scope -> Scope
zoomInLongGun mousep sc
| currentZoom < 8 =
sc
& scopePos .+.+~ (1 - zoomSpeed) / newzoom *.* mousep
& scopeZoom %~ (/ zoomSpeed)
| otherwise = sc & scopeZoomChange .~ 0
where
Just currentZoom = sc ^? scopeZoom
newzoom = (sc ^?! scopeZoom) / zoomSpeed
zoomOutLongGun :: Scope -> Scope
zoomOutLongGun sc
| currentzoom > 0.5 =
sc
& scopePos %~ (\p -> p +.+ (zoomSpeed-1) / currentzoom *.* p)
-- & scopePos .*.*~ 1 - ((newzoom * 2) ** zoomSpeed)
& scopeZoom *~ zoomSpeed
| otherwise = sc & scopeZoomChange .~ 0
& scopePos .~ V2 0 0
where
Just currentzoom = sc ^? scopeZoom
--newzoom = (sc ^?! scopeZoom) / zoomSpeed
ifConfigWallRotate :: Configuration -> World -> World
ifConfigWallRotate cfig w
| _gameplay_rotate_to_wall cfig && not (SDL.ButtonRight `M.member` _mouseButtons (_input w)) =
rotateToOverlappingWall w
| otherwise = w
rotateToOverlappingWall :: World -> World
rotateToOverlappingWall w =
maybe
id
(doWallRotate . snd)
(overlapCircWallsClosest p (_crRad cr + 5) (filter _wlRotateTo $ wlsNearPoint p w))
w
where
cr = you w
p = _crPos (you w)
doWallRotate :: Wall -> World -> World
doWallRotate wl w = rotateUsing $ (argV . uncurry (-.-) $ _wlLine wl) - (w ^. cWorld . camPos . camRot)
where
rotateUsing a
| b - b' > 0.01 = w & cWorld . camPos . camRot +~ 0.01
| b - b' < negate 0.01 = w & cWorld . camPos . camRot -~ 0.01
| otherwise = w
where
--b = a * (2 / pi)
b = a * (4 / pi)
b' = fromIntegral (round b :: Int)
rotateCameraBy :: Float -> CWorld -> CWorld
rotateCameraBy x w =
w
& camPos . camRot +~ x
& fromMaybe id (do
i <- w ^? lWorld . creatures . ix 0 . crInvSel . isel . ispItem
return $ lWorld . creatures . ix 0 . crInv . ix i
. itScope
. scopePos
%~ rotateV x
)
rotateCamera :: Configuration -> World -> World
rotateCamera cfig w
| keyl && keyr = w
| keyl = over cWorld (rotateCameraBy 0.025) w
| keyr = over cWorld (rotateCameraBy (-0.025)) w
| otherwise = ifConfigWallRotate cfig w
where
keyl = SDL.ScancodeQ `M.member` _pressedKeys (_input w) && notAtTerminal w
keyr = SDL.ScancodeE `M.member` _pressedKeys (_input w) && notAtTerminal w
-- TODO check where/how this is used
notAtTerminal :: World -> Bool
notAtTerminal w = isNothing $ w ^? hud . hudElement . subInventory . termID
--zoomCamBy :: Float -> World -> World
--zoomCamBy x w = w {_cameraZoom = max (_cameraZoom w + x) 0.01}
zoomFromItem ::
ItZoom ->
Float
zoomFromItem ItZoom{_izMax = zMax, _izMin = zMin, _izFac = zFac} =
min zMax $ max zMin zFac
clipZoom ::
-- | Furthest viewable distance
Float ->
Float
clipZoom = min 20 . max 0.2
setViewDistance :: Configuration -> CamPos -> CamPos
setViewDistance cfig w =
w & camViewDistance
.~ sqrt (halfWidth cfig ** 2 + halfHeight cfig ** 2) / (w ^. camZoom)
farWallDistDirection :: Point2 -> World -> Maybe (Float, Float, Float, Float)
farWallDistDirection p w =
boundPoints $
map f $ getViewpoints p (_cWorld w)
where
f q = (rotateV (negate (w ^. cWorld . camPos . camRot)) . (-.- p)) (foldl' findPoint q (wls q))
wls q = filter wlIsOpaque $ wlsNearSeg p q w
findPoint q = fromMaybe q . uncurry (intersectSegSeg p q) . _wlLine
findBoundDists :: Configuration -> World -> (Float, Float, Float, Float)
findBoundDists cfig w
| debugOn Bound_box_screen cfig = (hh, - hh, hw, - hw)
-- | otherwise = fromMaybe (0, 0, 0, 0) $ farWallDistDirection (w ^. cWorld . camPos . camCenter) w
| otherwise = fromMaybe (0, 0, 0, 0) $ farWallDistDirection (w ^. cWorld . camPos . camViewFrom) w
where
hw = halfWidth cfig
hh = halfHeight cfig
updateBounds :: Configuration -> World -> World
updateBounds cfig w =
w
& cWorld . camPos . camBoundDist .~ bdists
& cWorld . camPos . camBoundBox
.~ map ((+.+ w ^. cWorld . camPos . camCenter) . rotateV (w ^. cWorld . camPos . camRot)) (rectNSWE n s w' e)
where
bdists@(n, s, e, w') = findBoundDists cfig w