{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE CPP #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | This module will try to reconstruct closed shapes and -- lines from -- the set of anchors and segments. -- -- The output of this module may be duplicated, needing -- deduplication as post processing. -- -- This is mostly a depth first search in the set of anchors -- and segments. module Text.AsciiDiagram.Reconstructor( reconstruct ) where #if !MIN_VERSION_base(4,8,0) import Data.Monoid( mempty ) import Control.Applicative( (<$>) ) #endif import Control.Monad( when ) import Control.Monad.State.Strict( execState ) import Control.Monad.State.Class( get ) import Data.Function( on ) import Data.List( sortBy ) import Data.Maybe( catMaybes ) import qualified Data.Foldable as F import qualified Data.Set as S import qualified Data.Map as M import qualified Data.Vector as V import Linear( V2( .. ), (^+^), (^-^) ) import Text.AsciiDiagram.Geometry import Text.AsciiDiagram.Graph import Control.Lens {-import Debug.Trace-} {-import Text.Printf-} {-import Text.Groom-} data Direction = LeftToRight | RightToLeft | TopToBottom | BottomToTop | NoDirection deriving (Eq, Show) directionOfVector :: Vector -> Direction directionOfVector (V2 0 n) | n > 0 = TopToBottom | otherwise = BottomToTop directionOfVector (V2 n 0) | n > 0 = LeftToRight | otherwise = RightToLeft directionOfVector _ = NoDirection -- -- ****|**** -- *** | *** -- ** 1 ** -- * | * -- -----0----+---2------ -- * ^ * -- ** : ** -- *** : *** -- ****:**** -- -- -- ****|**** -- *** | *** -- ** 0 ** -- * | * -- =========>+---1------ -- * | * -- ** 2 ** -- *** | *** -- ****|**** -- -- -- ****|**** -- *** | *** -- ** 2 ** -- * | * -- -----1----+<========= -- * | * -- ** 0 ** -- *** | *** -- ****|**** -- -- -- ****:**** -- *** : *** -- ** : ** -- * V * -- -----2----+---0------ -- * | * -- ** 1 ** -- *** | *** -- ****|**** -- vectorsForAnchor :: Anchor -> Direction -> [Vector] vectorsForAnchor anchor dir = case (anchor, dir) of (AnchorArrowUp, _) -> [down] (AnchorArrowDown, _) -> [up] (AnchorArrowLeft, _) -> [right] (AnchorArrowRight, _) -> [left] (_, LeftToRight) -> [up, right, down, left] (_, TopToBottom) -> [right, down, left, up] (_, NoDirection) -> [right, down, left, up] (_, RightToLeft) -> [down, left, up, right] (_, BottomToTop) -> [left, up, right, down] where left = V2 (-1) 0 up = V2 0 (-1) right = V2 1 0 down = V2 0 1 directionVectorOf :: Point -> Point -> Vector directionVectorOf a b = signum <$> a ^-^ b nextDirectionAfterAnchor :: Anchor -> Point -> Point -> [Point] nextDirectionAfterAnchor anchor previousPoint anchorPosition = [delta | delta <- deltas , let nextPoint = anchorPosition ^+^ delta , nextPoint /= previousPoint] where directionVector = directionVectorOf anchorPosition previousPoint direction = directionOfVector directionVector deltas = vectorsForAnchor anchor direction nextPointAfterAnchor :: Anchor -> Point -> Point -> [Point] nextPointAfterAnchor anchor prev p = (^+^ p) <$> nextDirectionAfterAnchor anchor prev p segmentManathanLength :: Segment -> Int segmentManathanLength seg = x + y where V2 x y = abs <$> _segEnd seg ^-^ _segStart seg segmentDirectionMap :: S.Set Segment -> M.Map Point SegmentKind segmentDirectionMap = S.fold go mempty where go seg = M.insert (_segEnd seg) k . M.insert (_segStart seg) k where k = _segKind seg toGraph :: M.Map Point Anchor -> S.Set Segment -> Graph Point ShapeElement Segment toGraph anchors segs = execState graphCreator baseGraph where baseGraph = graphOfVertices $ M.mapWithKey ShapeAnchor anchors segDirs = segmentDirectionMap segs graphCreator = do F.traverse_ linkSegments segs F.traverse_ linkAnchors $ M.assocs anchors linkOf p1 p2 | p1 < p2 = (p1, p2) | otherwise = (p2, p1) linkAnchors (p, a) = F.traverse_ createLinks nextPoints where nextPoints = nextPointAfterAnchor a (V2 (-1) (-1)) p createLinks nextPoint = do nextExists <- has (vertices . ix nextPoint) <$> get let dirNext = nextPoint ^-^ p nextP = M.lookup nextPoint anchors nextS = M.lookup nextPoint segDirs nextIsOk = case (nextP, nextS) of (Just AnchorArrowUp, _) -> V2 0 (-1) == dirNext (Just AnchorArrowDown, _) -> V2 0 1 == dirNext (Just AnchorArrowLeft, _) -> V2 (-1) 0 == dirNext (Just AnchorArrowRight, _) -> V2 1 0 == dirNext (Just _, _) -> True (Nothing, Nothing) -> True (Nothing, Just SegmentHorizontal) -> (abs <$> dirNext) == V2 1 0 (Nothing, Just SegmentVertical) -> (abs <$> dirNext) == V2 0 1 alreadyLinked <- has (edges . ix (linkOf p nextPoint)) <$> get when (nextExists && nextIsOk && not alreadyLinked) $ edges . at (linkOf p nextPoint) ?= mempty linkSegments seg | segmentManathanLength seg == 0 = do vertices . at (_segStart seg ) ?= ShapeSegment seg linkSegments seg@(Segment { _segStart = p1, _segEnd = p2 }) = do vertices . at p1 ?= ShapeSegment seg vertices . at p2 ?= ShapeSegment seg edges . at (linkOf p1 p2) ?= seg findClockwisePossible :: S.Set Point -> Maybe Point -> Point -> [Point] findClockwisePossible adjacents Nothing p = findClockwisePossible adjacents (Just p) p findClockwisePossible adjacents (Just prev) p = fmap snd $ sortBy (compare `on` fst) indexedAdjacents where -- don't care about specific direction, restrictions should have -- been made during the construction of the graph. dirArray = V.fromList $ nextDirectionAfterAnchor AnchorMulti prev p zipIndex k = (V.elemIndex dir dirArray, k) where dir = directionVectorOf k p indexedAdjacents = [(idx, nextPoint) | (Just idx, nextPoint) <- zipIndex <$> S.elems adjacents , nextPoint /= prev] safeHead :: [a] -> Maybe a safeHead [] = Nothing safeHead (x:_) = Just x instance PlanarVertice (V2 Int) where getClockwiseMost adj prev = safeHead . findClockwisePossible adj prev getCounterClockwiseMost adj prev = safeHead . reverse . findClockwisePossible adj prev dedupEqual :: Eq a => [a] -> [a] dedupEqual [] = [] dedupEqual (x:rest@(y:_)) | x == y = dedupEqual rest dedupEqual (x:xs) = x : dedupEqual xs -- | Break filaments at multi anchor to ensure proper dashing -- of the segments. breakFilaments :: Filament ShapeElement -> [Filament ShapeElement] breakFilaments = go where go lst = f : fs where (f, fs) = breaker lst breaker [] = ([], []) breaker [a@(ShapeAnchor _ AnchorMulti)] = ([a], []) breaker (a@(ShapeAnchor _ AnchorMulti):xs) = ([a], (a:filamentRest):others) where (filamentRest, others) = breaker xs breaker (x:xs) = (x:filamentRest, others) where (filamentRest, others) = breaker xs -- | Main call of the reconstruction function reconstruct :: M.Map Point Anchor -> S.Set Segment -> S.Set Shape reconstruct anchors segments = S.fromList $ fmap toShapes cycles ++ concatMap toFilaments filaments where graph = toGraph anchors segments (cycles, filaments) = extractAllPrimitives graph toElems = dedupEqual . filter (/= ShapeSegment mempty) . catMaybes . fmap (`M.lookup` _vertices graph) toFilaments shapes = [Shape piece False mempty mempty | piece <- breakFilaments $ toElems shapes] toShapes shapes = Shape (toElems shapes) True mempty mempty