{-# LANGUAGE RecordWildCards #-} -------------------------------------------------------------------------------- -- | -- Module : Algorithms.Geometry.SSSP -- Copyright : (C) David Himmelstrup -- License : see the LICENSE file -- Maintainer : David Himmelstrup -------------------------------------------------------------------------------- module Algorithms.Geometry.SSSP ( SSSP , triangulate , sssp , visibilityDual , visibilityFinger , visibilitySensitive ) where import Algorithms.Geometry.PolygonTriangulation.Triangulate (triangulate') import Algorithms.Geometry.PolygonTriangulation.Types (PolygonEdgeType) import Algorithms.Graph.DFS (adjacencyLists, dfs', dfsSensitive) import Control.Lens ((^.)) import Data.Bitraversable import Data.Either import Data.Ext (ext, extra, type (:+) (..)) import qualified Data.FingerTree as F import Data.Geometry.Line (lineThrough) import Data.Geometry.LineSegment (LineSegment (ClosedLineSegment, LineSegment)) import Data.Geometry.PlanarSubdivision (PolygonFaceData (..)) import Data.Geometry.Point (Point, ccw, pattern CCW, pattern CW) import Data.Geometry.Polygon import Data.Intersection import Data.List (sortOn, (\\)) import Data.Maybe (fromMaybe) import Data.PlanarGraph (PlanarGraph) import qualified Data.PlanarGraph as Graph import Data.PlaneGraph (FaceId (..), PlaneGraph, VertexData (..), VertexId, VertexId', dual, graph, incidentEdges, leftFace, vertices) import qualified Data.PlaneGraph as PlaneGraph import Data.Tree (Tree (Node)) import qualified Data.Vector as V import qualified Data.Vector.Circular as CV import qualified Data.Vector.Circular.Util as CV import Data.Vector.Unboxed (Vector) import qualified Data.Vector.Unboxed as VU import Data.Vinyl import Data.Vinyl.CoRec {- type AbsOffset = Int data TriangulatedPolygon t p r = TriangulatedPolygon { triangulatedMap :: Map AbsOffset (VertexId () Primal) , triangulatedGraph :: PlaneGraph () AbsOffset PolygonEdgeType PolygonFaceData r , triangulatedPolygon :: Polygon t p r } -} -- | Single-source shortest paths tree. Both keys and values are vertex offset ints. -- -- @parentOf(i) = sssp[i]@ type SSSP = Vector Int -- FIXME: The code for generating the dual cannot deal with offsets so -- we're running 'unsafeFromPoints . toPoints' to reset the polygon. -- Super silly. Please fix. -- | \( O(n \log n) \) triangulate :: forall s p r. (Ord r, Fractional r) => SimplePolygon p r -> PlaneGraph s Int PolygonEdgeType PolygonFaceData r triangulate p = let poly' = snd $ bimapAccumL (\a _ -> (a+1,a)) (,) 0 $ unsafeFromPoints $ toPoints p in triangulate' @s poly' -- | \( O(n) \) Single-Source shortest path. sssp :: (Ord r, Fractional r) => PlaneGraph s Int PolygonEdgeType PolygonFaceData r -> SSSP sssp trig = ssspFinger d where Just v0 = fst <$> V.find (\(_vid, VertexData _ idx) -> idx == 0) (vertices trig) v0i = incidentEdges v0 trig Just (FaceId firstFace) = V.find (/= FaceId outer) $ V.map (`leftFace` trig) v0i FaceId outer = PlaneGraph.outerFaceId trig dualGraph = trig^.graph.dual dualTree' = dfs' (V.map (filter (/= outer)) $ adjacencyLists dualGraph) firstFace dualVS = fmap (\v -> toCCW $ PlaneGraph.boundaryVertices (FaceId v) trig) dualTree' trigTree = toTrigTree trig dualVS d = mkDual trigTree toCCW v = let cv = CV.reverse $ CV.unsafeFromVector v in CV.toVector $ fromMaybe cv $ CV.findRotateTo (== v0) cv {- 1. Find the starting face. -} visibilitySensitive :: forall s r. (Ord r, Fractional r, Show r) => PlaneGraph s Int PolygonEdgeType PolygonFaceData r -> SimplePolygon () r visibilitySensitive = fromPoints . map ext . rights . visibilityFinger . visibilityDual visibilityDual :: forall s r. (Ord r, Fractional r) => PlaneGraph s Int PolygonEdgeType PolygonFaceData r -> Dual r visibilityDual trig = d where Just v0 = fst <$> V.find (\(_vid, VertexData _ idx) -> idx == 0) (vertices trig) v0i = incidentEdges v0 trig outer :: VertexId s Graph.Dual FaceId outer = PlaneGraph.outerFaceId trig firstFace :: VertexId s Graph.Dual Just (FaceId firstFace) = V.find (/= FaceId outer) $ V.map (`leftFace` trig) v0i dualGraph :: PlanarGraph s Graph.Dual PolygonFaceData PolygonEdgeType (VertexData r Int) dualGraph = trig^.graph.dual dualTree' :: Tree (VertexId s Graph.Dual) dualTree' = dfsSensitive neigh firstFace neigh :: VertexId s Graph.Dual -> [VertexId s Graph.Dual] neigh v = V.toList $ V.filter (/=outer) $ Graph.neighboursOf v dualGraph dualVS :: Tree (V.Vector (VertexId' s)) dualVS = fmap (\v -> toCCW $ PlaneGraph.boundaryVertices (FaceId v) trig) dualTree' trigTree :: Tree (Index r, Index r, Index r) trigTree = toTrigTree trig dualVS d :: Dual r d = mkDual trigTree toCCW v = let cv = CV.reverse $ CV.unsafeFromVector v in CV.toVector $ fromMaybe cv $ CV.findRotateTo (== v0) cv visibilityFinger :: forall r. (Fractional r, Ord r, Show r) => Dual r -> [Either (Int, Int, Int) (Point 2 r)] visibilityFinger d = case d of Dual (a,b,c) ab bc ca -> Left (indexExtra a, indexExtra b, indexExtra c) : worker (Funnel (F.singleton b) a F.empty) ab ++ worker (Funnel (F.singleton c) a (F.singleton b)) bc ++ worker (Funnel F.empty a (F.singleton c)) ca where -- Final edge is the leftmost of each funnel. -- The most visible are the rightmost of each funnel. -- Cut line segment. worker f EmptyDual = let edgeA = ringAccess $ funnelRightTop f edgeB = ringAccess $ funnelLeftTop f edge = ClosedLineSegment (ext edgeA) (ext edgeB) coneA = ringAccess $ funnelRightBottom f coneB = ringAccess $ funnelLeftBottom f lineA = lineThrough (ringAccess $ funnelCusp f) coneA lineB = lineThrough (ringAccess $ funnelCusp f) coneB -- findIntersection :: Line 2 r -> Point 2 r findIntersection line = match (edge `intersect` line) $ H (\NoIntersection -> error "no intersection") :& H (\pt -> Right pt) :& H (\LineSegment{} -> error "line intersection") :& RNil in [if edgeA == coneA then Right coneA else findIntersection lineA] ++ if edgeB == coneB then [] else [findIntersection lineB] worker f (NodeDual x l r) = Left (indexExtra $ fromMaybe (funnelCusp f) $ chainTop (funnelRight f) ,indexExtra x ,indexExtra $ fromMaybe (funnelCusp f) $ chainTop (funnelLeft f)) : case splitFunnel x f of (_v, fL, fR, dir) -> case dir of -- 'x' is to the left of the visibility cone. Everything further to the left cannot -- be visible to just go right. SplitLeft -> worker fR r -- assert cusp of fR == cusp of f -- 'x' is visible from our cusp. Add it to the output and go both to the left and right. NoSplit -> worker fR r ++ [Right (ringAccess x)] ++ worker fL l -- 'x' is to the right of the visibility cone. Everything further to the right cannot -- be visible to just go left. SplitRight -> worker fL l -- assert cusp of fL == cusp of f -------------------------------------------------------------------------------- -- SSSP (with fingertree) implementation data MinMax r = MinMax (Index r) (Index r) | MinMaxEmpty deriving (Show) instance Semigroup (MinMax r) where MinMaxEmpty <> b = b a <> MinMaxEmpty = a MinMax a _b <> MinMax _c d = MinMax a d instance Monoid (MinMax r) where mempty = MinMaxEmpty -- Including the 'Point 2 r' here means we don't have to look it up. -- This mattered since lookups used to be O(log n) rather than O(1). newtype Index r = Index (Point 2 r :+ Int) -- deriving (Show) instance Show (Index r) where show = show . indexExtra indexExtra :: Index r -> Int indexExtra (Index p) = p^.extra instance Eq (Index r) where Index (_ :+ a) == Index (_ :+ b) = a == b type Chain r = F.FingerTree (MinMax r) (Index r) data Funnel r = Funnel { funnelLeft :: Chain r -- Left-most element is furthest away from cusp. , funnelCusp :: Index r , funnelRight :: Chain r -- Left-most element is furthest away from cusp. } deriving (Show) -- Left side of the funnel, furthest away from the cusp. funnelLeftTop :: Funnel r -> Index r funnelLeftTop f = fromMaybe (funnelCusp f) $ chainTop (funnelLeft f) -- Left side of the funnel, closest to the cusp. funnelLeftBottom :: Funnel r -> Index r funnelLeftBottom f = fromMaybe (funnelCusp f) $ chainBottom (funnelLeft f) -- Right side of the funnel, furthest away from the cusp. funnelRightTop :: Funnel r -> Index r funnelRightTop f = fromMaybe (funnelCusp f) $ chainTop (funnelRight f) -- Right side of the funnel, closest to the cusp. funnelRightBottom :: Funnel r -> Index r funnelRightBottom f = fromMaybe (funnelCusp f) $ chainBottom (funnelRight f) -- Element closest to the cusp. chainBottom :: Chain r -> Maybe (Index r) chainBottom chain = case F.viewl chain of F.EmptyL -> Nothing elt F.:< _ -> Just elt -- Element furthest away from the cusp. chainTop :: Chain r -> Maybe (Index r) chainTop chain = case F.viewr chain of F.EmptyR -> Nothing _ F.:> elt -> Just elt instance F.Measured (MinMax r) (Index r) where measure i = MinMax i i data SplitDirection = SplitLeft | NoSplit | SplitRight deriving (Show) -- Split a funnel w.r.t. a point 'x'. There are three cases: -- 1. 'x' is visible from the cusp. -- 2. the path to 'x' hits the left side of the funnel. -- 3. the path to 'x' hits the right side of the funnel. -- -- ******************************************************** -- Drawing guide: -- \ / -- left side of funnel -> \ / <- right side of funnel -- \ / -- * <- cusp -- ******************************************************** -- -- Case 1: -- x -- \ / -- \ / -- \ / -- * -- -- Case 2: -- -- x -- \ / -- \ / -- \ / -- * -- -- Case 3: -- -- x -- \ / -- \ / -- \ / -- * -- -- If 'x' is visible from the cusp, then the shortest path is a straight line and we're done. -- If 'x' is not visible from the cusp, then we find the first point up the funnel where -- 'x' becomes visible. We'll use a fingertree to find the point in O(log(min(n,m))). Because -- of math, this adds up to O(n) for the entire SSSP tree. -- -- Once we've found the first point that can see 'x', we split the funnel in two: One funnel -- that will be used for points to the left of 'x' and one funnel for points to the right of -- 'x'. Oh, "left" and "right" here are used to indicate branches in the dual tree. splitFunnel :: (Fractional r, Ord r) => Index r -> Funnel r -> (Index r, Funnel r, Funnel r, SplitDirection) splitFunnel x Funnel{..} | isOnLeftChain = case doSearch isRightTurn funnelLeft of (lower, t, upper) -> ( t , Funnel upper t (F.singleton x) , Funnel (lower F.|> t F.|> x) funnelCusp funnelRight , SplitLeft) | isOnRightChain = case doSearch isLeftTurn funnelRight of (lower, t, upper) -> ( t , Funnel funnelLeft funnelCusp (lower F.|> t F.|> x) , Funnel (F.singleton x) t upper , SplitRight) | otherwise = ( funnelCusp , Funnel funnelLeft funnelCusp (F.singleton x) , Funnel (F.singleton x) funnelCusp funnelRight , NoSplit) where isOnLeftChain = fromMaybe False $ isLeftTurnOrLinear cuspElt <$> leftElt <*> pure targetElt isOnRightChain = fromMaybe False $ isRightTurnOrLinear cuspElt <$> rightElt <*> pure targetElt doSearch fn chain = case F.search (searchChain fn) chain of F.Position lower t upper -> (lower, t, upper) F.OnLeft -> error "cannot happen" F.OnRight -> error "cannot happen" F.Nowhere -> error "cannot happen" searchChain _ MinMaxEmpty _ = False searchChain _ _ MinMaxEmpty = True searchChain check (MinMax _ l) (MinMax r _) = check (ringAccess l) (ringAccess r) targetElt cuspElt = ringAccess funnelCusp targetElt = ringAccess x leftElt = ringAccess <$> chainBottom funnelLeft rightElt = ringAccess <$> chainBottom funnelRight -- FIXME: Turning a list of pairs into a vector is incredibly inefficient. -- Would be much faster to write directly into a mutable vector and -- then freeze it at the end. -- \( O(n) \) ssspFinger :: (Fractional r, Ord r) => Dual r -> SSSP ssspFinger d = toSSSP $ case d of Dual (a,b,c) ab bc ca -> (a, a) : (b, a) : (c, a) : loopLeft a c ca ++ worker (Funnel (F.singleton c) a (F.singleton b)) bc ++ loopRight a b ab where toSSSP :: [(Index r,Index r)] -> SSSP toSSSP lst = VU.fromList . map snd . sortOn fst $ [ (a,b) | (Index (_ :+ a), Index (_ :+ b)) <- lst ] loopLeft a outer l = case l of EmptyDual -> [] NodeDual x l' r' -> (x,a) : worker (Funnel (F.singleton x) a (F.singleton outer)) r' ++ loopLeft a x l' loopRight a outer r = case r of EmptyDual -> [] NodeDual x l' r' -> (x, a) : worker (Funnel (F.singleton outer) a (F.singleton x)) l' ++ loopRight a x r' worker _ EmptyDual = [] worker f (NodeDual x l r) = case splitFunnel x f of (v, fL, fR, _) -> (x, v) : worker fL l ++ worker fR r -------------------------------------------------------------------------------- -- Duals data Dual r = Dual (Index r, Index r, Index r) -- (a,b,c) (DualTree r) -- borders ab (DualTree r) -- borders bc (DualTree r) -- borders ca deriving (Show) data DualTree r = EmptyDual | NodeDual (Index r) -- axb triangle, a and b are from parent. (DualTree r) -- borders xb (DualTree r) -- borders ax deriving (Show) toTrigTree :: PlaneGraph s Int PolygonEdgeType PolygonFaceData r -> Tree (V.Vector (VertexId' s)) -> Tree (Index r,Index r,Index r) toTrigTree trig = fmap toTrig . fmap (fmap toDat) where toTrig v = case V.toList v of [a,b,c] -> (a,b,c) _ -> error "Algorithms.Geometry.SSSP: Invalid triangulation." toDat v = Index $ PlaneGraph.vtxDataToExt (trig ^. PlaneGraph.vertexDataOf v) -- pp :: Show a => Tree a -> IO () -- pp = putStrLn . drawTree . fmap show mkDual :: Tree (Index r,Index r,Index r) -> Dual r mkDual (Node (a,b,c) forest) = Dual (a, b, c) (dualTree a b forest) (dualTree b c forest) (dualTree c a forest) dualTree :: Index r -> Index r -> [Tree (Index r,Index r,Index r)] -> DualTree r dualTree p1 p2 (Node (a,b,c) sub:xs) = case [a,b,c] \\ [p1,p2] of [x] -> NodeDual x (dualTree x p2 sub) (dualTree p1 x sub) _ -> dualTree p1 p2 xs dualTree _p1 _p2 [] = EmptyDual -------------------------------------------------------------------------------- -- Helpers ringAccess :: Index r -> Point 2 r ringAccess (Index (pt :+ _idx)) = pt isRightTurnOrLinear :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool isRightTurnOrLinear p1 p2 p3 = not $ isLeftTurn p1 p2 p3 isLeftTurnOrLinear :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool isLeftTurnOrLinear p1 p2 p3 = not $ isRightTurn p1 p2 p3 isLeftTurn :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool isLeftTurn p1 p2 p3 = ccw p1 p2 p3 == CCW isRightTurn :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool isRightTurn p1 p2 p3 = ccw p1 p2 p3 == CW