{- 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 , farWallPoints , farWallDistDirection ) where import Dodge.Data import Dodge.Base import Dodge.Zone import Dodge.Equipment import Dodge.Creature.Test import Geometry --import Geometry.ConvexPoly import Dodge.GameRoom import LensHelp --import qualified Data.List.NonEmpty as NEL --import Control.Applicative import Data.Maybe import Control.Monad import qualified Data.Set as S import qualified Data.IntMap.Strict as IM import qualified SDL --import Data.Monoid --import Data.Semigroup --import qualified Control.Foldl as L {- 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 = setViewDistance cfig . moveZoomCamera cfig . updateScopeZoom . rotateCamera cfig moveZoomCamera :: Configuration -> World -> World moveZoomCamera cfig w = w & cameraCenter .~ newcen & cameraViewFrom .~ newvf & cameraZoom .~ newzoom & defaultZoom .~ newDefaultZoom & itemZoom .~ newItemZoom where cpos =_crPos (you w) newvf = cpos +.+ fromMaybe (V2 0 0) vfoffset vfoffset = do iscam <- yourItem w ^? _Just . itScope . scopeIsCamera guard iscam guard (SDL.ButtonRight `S.member` _mouseButtons w) yourItem w ^? _Just . itScope . scopePos mscopeoffset = do guard (SDL.ButtonRight `S.member` _mouseButtons w) yourItem w ^? _Just . itScope . scopePos newcen = cpos +.+ fromMaybe (V2 0 0) mscopeoffset +.+ offset offset = rotateV (_cameraRot w) $ ((newzoom - newDefaultZoom)/(newDefaultZoom*newzoom)) *.* _mousePos w --newzoom = changeZoom (_cameraZoom w) idealZoom' newzoom = case yourItem w ^? _Just . itScope of Just zs@ZoomScope {} -> _scopeZoom zs _ -> newDefaultZoom * newItemZoom idealDefaultZoom = clipZoom wallZoom newDefaultZoom = case yourItem w ^? _Just . itScope of Just zs@ZoomScope {} -> _scopeZoom zs _ -> changeZoom (_defaultZoom w) idealDefaultZoom idealItemZoom = fromMaybe 1 $ do guard $ crIsAiming (you w) zoomFromItem' <$> (yourItem w ^? _Just . itUse . useAim . aimZoom) newItemZoom = changeZoom (_itemZoom w) 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 = farWallDist newvf cfig w -- = maybe clipZoom zoomFromItem (yourItem w ^? itZoom) wallZoom -- these speeds are inverted, larger means slower zoomInSpeed = 25 zoomOutSpeed = 15 updateScopeZoom :: World -> World updateScopeZoom w | SDL.ButtonRight `S.member` _mouseButtons w = case w ^? creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0)) . itScope . scopeZoomChange of Just x | x > 10 -> zoomInLongGun $ zoomInLongGun $ zoomInLongGun w | x > 5 -> zoomInLongGun $ zoomInLongGun w | x > 0 -> zoomInLongGun w | x < -10 -> zoomOutLongGun $ zoomOutLongGun $ zoomOutLongGun w | x < -5 -> zoomOutLongGun $ zoomOutLongGun w | x < 0 -> zoomOutLongGun w | otherwise -> w _ -> w | otherwise = w & creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0)) . itScope %~ updateScope where updateScope (ZoomScope _ _ _ defz bl) = ZoomScope (V2 0 0) 0 defz defz bl updateScope otherAtt = otherAtt zoomSpeed :: Float zoomSpeed = 39/40 zoomInLongGun :: World -> World zoomInLongGun w | currentZoom < 8 = w & wpPointer . itScope . scopePos .+.+~ (1-zoomSpeed) / newzoom *.* mousep & wpPointer . itScope . scopeZoom %~ (/ zoomSpeed) & decreaseScopeZoomChange | otherwise = w & wpPointer . itScope . scopeZoomChange .~ 0 where decreaseScopeZoomChange = wpPointer . itScope . scopeZoomChange -~ 1 wpPointer = creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0)) wp = _crInv (_creatures w IM.! 0) IM.! _crInvSel (_creatures w IM.! 0) Just currentZoom = wp ^? itScope . scopeZoom newzoom = (wp ^?! itScope . scopeZoom) / zoomSpeed mousep = rotateV (_cameraRot w) $ _mousePos w zoomOutLongGun :: World -> World zoomOutLongGun w | currentZoom > 0.5 = w -- & wpPointer . itScope . scopePos .+.+~ ((1*zoomSpeed-1)) / currentZoom *.* mousep & wpPointer . itScope . scopeZoom *~ zoomSpeed & increaseScopeZoomChange -- | otherwise = increaseScopeZoomChange w | otherwise = w & wpPointer . itScope . scopeZoomChange .~ 0 where --mousep = rotateV (_cameraRot w) $ _mousePos w increaseScopeZoomChange = wpPointer . itScope . scopeZoomChange +~ 1 wpPointer = creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0)) wp = _crInv (_creatures w IM.! 0) IM.! _crInvSel (_creatures w IM.! 0) Just currentZoom = wp ^? itScope . scopeZoom ifConfigWallRotate :: Configuration -> World -> World ifConfigWallRotate cfig w | _gameplay_rotate_to_wall cfig && not (SDL.ButtonRight `S.member` _mouseButtons w) = rotateToOverlappingWall w | otherwise = w rotateToOverlappingWall :: World -> World rotateToOverlappingWall w = maybe w dowallrotate $ overlapCircWallsReturnWall p (_crRad cr + 5) (IM.filter _wlRotateTo $ wallsNearPoint p w) where dowallrotate wl' = rotateUsing $ (argV . uncurry (-.-) $ _wlLine wl') - _cameraRot w rotateUsing a | b - b' > 0.01 = w & cameraRot +~ 0.01 | b - b' < negate 0.01 = w & cameraRot -~ 0.01 | otherwise = w where --b = a * (2 / pi) b = a * (4 / pi) b' = fromIntegral (round b :: Int) cr = you w p = _crPos cr rotateCameraBy :: Float -> World -> World rotateCameraBy x w = w & cameraRot +~ x & creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0)) . itScope . scopePos %~ rotateV x rotateCamera :: Configuration -> World -> World rotateCamera cfig w | keyl && keyr = w | keyl = rotateCameraBy 0.025 w | keyr = rotateCameraBy (-0.025) w | otherwise = ifConfigWallRotate cfig w where keyl = SDL.ScancodeQ `S.member` _keys w && notAtTerminal w keyr = SDL.ScancodeE `S.member` _keys 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 {_itZoomMax = zMax, _itZoomMin = zMin, _itZoomFac = zFac} = min zMax $ max zMin zFac clipZoom :: Float -- ^ Furthest viewable distance -> Float clipZoom = min 20 . max 0.2 setViewDistance :: Configuration -> World -> World setViewDistance cfig w = w & viewDistance .~ sqrt (halfWidth cfig ** 2 + halfHeight cfig ** 2) / _cameraZoom w -- .~ max (halfWidth cfig) (halfHeight cfig) / _cameraZoom w -- TODO consider adding visible/nearby creatures as view points farWallDist :: Point2 -> Configuration -> World -> Float --farWallDist p cfig w = (winFac /) . min maxViewDistance $ ssfold (> maxViewDistance) findMax 1 vps farWallDist p cfig w = (winFac /) . min maxViewDistance $ ssfold (> maxViewDistance) findMax distFromEqmnt vps where findMax curMax pout = max curMax $ dist p $ collidePointUpToIndirectMinDist p pout curMax wos hw = halfWidth cfig hh = halfHeight cfig winFac = min hw hh vps = concatMap _grViewpoints grs ++ extendedViewPoints p grs grs = filter (pointInOrOnPolygon p . _grBound) (_gameRooms w) wos = wallsOnScreen cfig w distFromEqmnt = foldr max 1 $ IM.mapMaybe (_eqViewDist . _eqEq. _itUse) $ getCrEquipment $ you w farWallDistDirection :: Point2 -> Configuration -> World -> (Float,Float,Float,Float) --farWallDist p cfig w = (winFac /) . min maxViewDistance $ ssfold (> maxViewDistance) findMax 1 vps farWallDistDirection p cfig w = foldr m (0,0,0,0) $ map f vps where f q = g $ rotateV (negate $ _cameraRot w) $ collidePointWallsFilter wlIsOpaque p q wos -.- p g (V2 x y) = (y, negate y, x, negate x) m (a,b,c,d) (x,y,z,w') = (max a x, max b y, max c z, max d w') vps = concatMap _grViewpoints grs ++ extendedViewPoints p grs grs = filter (pointInOrOnPolygon p . _grBound) (_gameRooms w) wos = wallsOnScreen cfig w extendedViewPoints :: Point2 -> [GameRoom] -> [Point2] extendedViewPoints p grs = map extend (concatMap _grViewpointsEx grs ++ map addDir (concatMap _grLinkDirs grs) ) where extend outp = p +.+ maxViewDistance *.* squashNormalizeV (outp -.- p) addDir a = p +.+ unitVectorAtAngle a farWallPoints :: Point2 -> World -> [Point2] farWallPoints p w = concatMap _grViewpoints grs ++ extendedViewPoints p grs where grs = filter (pointInOrOnPolygon p . _grBound) (_gameRooms w) maxViewDistance :: Float maxViewDistance = 800