136 lines
4.5 KiB
Haskell
136 lines
4.5 KiB
Haskell
--{-# LANGUAGE TupleSections #-}
|
|
module Dodge.Path (
|
|
pointTowardsImpulse,
|
|
makePathBetween,
|
|
makePathBetweenPs,
|
|
obstructPathsCrossing,
|
|
getNodePos,
|
|
walkableNodeNear,
|
|
snapToGrid,
|
|
pairsToIncGraph,
|
|
-- updateEdge,
|
|
getEdgesCrossing,
|
|
) where
|
|
|
|
import Dodge.WorldEvent.ThingsHit
|
|
import qualified Data.Set as S
|
|
import qualified Algorithm.Search as AS
|
|
import Control.Lens
|
|
--import Data.Bifunctor
|
|
import Data.Foldable
|
|
--import Data.Graph.Inductive hiding ((&))
|
|
import Data.List (sortOn)
|
|
--import Data.Map.Strict (Map)
|
|
--import qualified Data.Map.Strict as M
|
|
import Data.Maybe
|
|
import Data.Set (Set)
|
|
import qualified Data.Set as Set
|
|
import qualified Data.Vector as V
|
|
import qualified Data.Vector.Unboxed as UV
|
|
import Dodge.Data.World
|
|
import Dodge.Zoning.Base
|
|
import Dodge.Zoning.Common
|
|
import Dodge.Zoning.Pathing
|
|
import Geometry
|
|
import qualified IntMapHelp as IM
|
|
import Linear
|
|
|
|
getNodePos :: Int -> World -> Point2
|
|
getNodePos i w = w ^?! cWorld . incNode . ix i
|
|
|
|
getEdgesCrossing :: Point2 -> Point2 -> World -> [(Int,Int)]
|
|
getEdgesCrossing s e w = filter inedgecrosses $ nearSeg peZoneSize _incEdgeZoning s e w
|
|
where
|
|
inedgecrosses (i, j) = isJust $ intersectSegSeg s e (f i) (f j)
|
|
f i = w ^?! cWorld . incNode . ix i
|
|
|
|
updateEdge ::
|
|
(Set.Set EdgeObstacle -> Set.Set EdgeObstacle) ->
|
|
(Int, Int) ->
|
|
V.Vector (IM.IntMap SimpleEdge) ->
|
|
V.Vector (IM.IntMap SimpleEdge)
|
|
{-# INLINE updateEdge #-}
|
|
updateEdge f (i, j) = ix i . ix j . seObstacles %~ f
|
|
|
|
makePathUsing :: (Set.Set EdgeObstacle -> Bool) -> Point2 -> Point2 -> World -> Maybe [Int]
|
|
makePathUsing t s e w = do
|
|
na <- walkableNodeNear w s
|
|
nb <- walkableNodeNear w e
|
|
let h i = distance (getn nb) (getn i)
|
|
(na :) . snd <$> AS.aStarAssoc getes h (== nb) na
|
|
where
|
|
getes i = IM.toList
|
|
. IM.map (^. seDist)
|
|
. IM.filter (^. seObstacles . to t) $ w ^?! cWorld . incGraph . ix i
|
|
getn i = w ^?! cWorld . incNode . ix i
|
|
|
|
makePathBetween :: Point2 -> Point2 -> World -> Maybe [Int]
|
|
makePathBetween = makePathUsing $ not . pathEdgeObstructed
|
|
|
|
pathEdgeObstructed :: Set.Set EdgeObstacle -> Bool
|
|
pathEdgeObstructed pe = any (`Set.member` pe) [WallObstacle WallNotAutoOpen, ChasmObstacle]
|
|
|
|
walkableNodeNear :: World -> Point2 -> Maybe Int
|
|
{-# INLINE walkableNodeNear #-}
|
|
walkableNodeNear w p = fmap fst . find (flip (isWalkable p) w . snd) $ nodesNear
|
|
where
|
|
nodesNear = zonesExtract (w ^. incNodeZoning) . snailAround $ zoneOfPoint pnZoneSize p
|
|
|
|
snailAround :: Int2 -> [Int2]
|
|
snailAround x = (x +) <$> smallSnailInt2
|
|
|
|
smallSnailInt2 :: [Int2]
|
|
smallSnailInt2 =
|
|
sortOn
|
|
(distance (V2 0 (0 :: Float)) . fmap fromIntegral)
|
|
[V2 x y | x <- [-2 .. 2], y <- [-2 .. 2]]
|
|
|
|
makePathBetweenPs :: Point2 -> Point2 -> World -> Maybe [Point2]
|
|
makePathBetweenPs a b w = fmap (`getNodePos` w) <$> makePathBetween a b w
|
|
|
|
-- assumes that pathfinding is symmetric
|
|
pointTowardsImpulse :: Point2 -> Point2 -> World -> Maybe Point2
|
|
pointTowardsImpulse a b w = find (flip (isWalkable a) w) =<< makePathBetweenPs b a w
|
|
|
|
pairsToIncGraph ::
|
|
Set.Set (Point2, Point2) ->
|
|
( UV.Vector Point2
|
|
--, V.Vector [(Int, SimpleEdge)]
|
|
, V.Vector (IM.IntMap SimpleEdge)
|
|
, [(Int, Int)]
|
|
)
|
|
pairsToIncGraph pairs =
|
|
( inodes
|
|
, incgraph
|
|
, Set.toList pairs & each . each %~ (\i -> pToNode ^?! ix i)
|
|
)
|
|
where
|
|
incgraph :: V.Vector (IM.IntMap SimpleEdge)
|
|
incgraph = V.generate (length im) (im IM.!)
|
|
-- I'm not fully happy with using the monoidal instance of IntMap here
|
|
-- especially seeing as there are two levels of IntMaps.
|
|
im = IM.fromListWith (<>) . fmap toedge $ Set.toList pairs
|
|
toedge (x, y) =
|
|
( pToNode ^?! ix x
|
|
, IM.fromList [(pToNode ^?! ix y, SimpleEdge (distance x y) mempty)]
|
|
)
|
|
pToNode = IM.invertIntMapUnique . IM.fromDistinctAscList $ zip [0 ..] ps
|
|
inodes = UV.generate (length ps) (ps !!)
|
|
ps = Set.toList $ Set.map fst pairs <> Set.map snd pairs
|
|
|
|
-- consider updating all path edges at the end of world generation?
|
|
obstructPathsCrossing :: S.Set EdgeObstacle -> Point2 -> Point2 -> World -> World
|
|
obstructPathsCrossing obs s e w = w & cWorld . incGraph %~ updateincedges
|
|
where
|
|
updateincedge = flip $ updateEdge (S.union obs)
|
|
updateincedges gr = foldl' updateincedge gr inces
|
|
inces = filter inedgecrosses $ nearSeg peZoneSize _incEdgeZoning s e w
|
|
inedgecrosses (i, j) = isJust $ intersectSegSeg s e (f i) (f j)
|
|
f i = w ^?! cWorld . incNode . ix i
|
|
|
|
snapToGrid :: Set (Point2, Point2) -> Set (Point2, Point2)
|
|
snapToGrid = Set.map (over each (fmap (fromIntegral . f)))
|
|
where
|
|
f :: Float -> Int
|
|
f = round
|