{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Data.Geometry.Arrangement.Internal where
import Control.Lens
import qualified Data.CircularSeq as CSeq
import Data.Ext
import qualified Data.Foldable as F
import Data.Geometry.Boundary
import Data.Geometry.Box
import Data.Geometry.Line
import Data.Geometry.LineSegment
import Data.Geometry.PlanarSubdivision
import Data.Geometry.Point
import Data.Geometry.Properties
import qualified Data.List as List
import Data.Maybe
import Data.Ord (Down(..))
import Data.Proxy
import Data.Sequence.Util
import qualified Data.Vector as V
import Data.Vinyl.CoRec
type ArrangementBoundary s e r = V.Vector (Point 2 r, VertexId' s, Maybe (Line 2 r :+ e))
data Arrangement s l v e f r = Arrangement {
_inputLines :: V.Vector (Line 2 r :+ l)
, _subdivision :: PlanarSubdivision s v e f r
, _boundedArea :: Rectangle () r
, _unboundedIntersections :: ArrangementBoundary s l r
} deriving (Show,Eq)
makeLenses ''Arrangement
type instance NumType (Arrangement s l v e f r) = r
type instance Dimension (Arrangement s l v e f r) = 2
constructArrangement :: (Ord r, Fractional r)
=> proxy s
-> [Line 2 r :+ l]
-> Arrangement s l () (Maybe l) () r
constructArrangement px ls = let b = makeBoundingBox ls
in constructArrangementInBox' px b ls
constructArrangementInBox :: (Ord r, Fractional r)
=> proxy s
-> Rectangle () r
-> [Line 2 r :+ l]
-> Arrangement s l () (Maybe l) () r
constructArrangementInBox px rect ls = let b = makeBoundingBox ls
in constructArrangementInBox' px (b <> rect) ls
constructArrangementInBox' :: (Ord r, Fractional r)
=> proxy s
-> Rectangle () r
-> [Line 2 r :+ l]
-> Arrangement s l () (Maybe l) () r
constructArrangementInBox' px rect ls =
Arrangement (V.fromList ls) subdiv rect (link parts' subdiv)
where
subdiv = fromConnectedSegments px segs
& rawVertexData.traverse.dataVal .~ ()
(segs,parts') = computeSegsAndParts rect ls
computeSegsAndParts :: (Ord r, Fractional r)
=> Rectangle () r
-> [Line 2 r :+ l]
-> ( [LineSegment 2 () r :+ Maybe l]
, [(Point 2 r, Maybe (Line 2 r :+ l))]
)
computeSegsAndParts rect ls = ( segs <> boundarySegs, parts')
where
segs = map (&extra %~ Just)
. concatMap (\(l,ls') -> perLine rect l ls') $ makePairs ls
boundarySegs = map (:+ Nothing) . toSegments . dupFirst $ map fst parts'
dupFirst = \case [] -> []
xs@(x:_) -> xs ++ [x]
parts' = unBoundedParts rect ls
perLine :: forall r l. (Ord r, Fractional r)
=> Rectangle () r -> Line 2 r :+ l -> [Line 2 r :+ l]
-> [LineSegment 2 () r :+ l]
perLine b m ls = map (:+ m^.extra) . toSegments . rmDuplicates . List.sort $ vs <> vs'
where
rmDuplicates = map head . List.group
vs = mapMaybe (m `intersectionPoint`) ls
vs' = maybe [] (\(p,q) -> [p,q]) . asA (Proxy :: Proxy (Point 2 r, Point 2 r))
$ (m^.core) `intersect` (Boundary b)
intersectionPoint :: (Ord r, Fractional r)
=> Line 2 r :+ l -> Line 2 r :+ l -> Maybe (Point 2 r)
intersectionPoint (l :+ _) (m :+ _) = asA (Proxy :: Proxy (Point 2 r)) $ l `intersect` m
toSegments :: Ord r => [Point 2 r] -> [LineSegment 2 () r]
toSegments ps = let pts = map ext $ ps in
zipWith ClosedLineSegment pts (tail pts)
makeBoundingBox :: (Ord r, Fractional r) => [Line 2 r :+ l] -> Rectangle () r
makeBoundingBox = grow 1 . boundingBoxList' . intersections
intersections :: (Ord r, Fractional r) => [Line 2 r :+ l] -> [Point 2 r]
intersections = mapMaybe (uncurry intersectionPoint) . allPairs
sideIntersections :: (Ord r, Fractional r)
=> [Line 2 r :+ l] -> LineSegment 2 q r
-> [(Point 2 r, Line 2 r :+ l)]
sideIntersections ls s = let l = supportingLine s :+ undefined
in List.sortOn fst . filter (flip onSegment s . fst)
. mapMaybe (\m -> (,m) <$> l `intersectionPoint` m) $ ls
unBoundedParts :: (Ord r, Fractional r)
=> Rectangle () r
-> [Line 2 r :+ l]
-> [(Point 2 r, Maybe (Line 2 r :+ l))]
unBoundedParts rect ls = [tl] <> t <> [tr] <> reverse r <> [br] <> reverse b <> [bl] <> l
where
sideIntersections' = over (traverse._2) Just . sideIntersections ls
(t,r,b,l) = map4 sideIntersections' $ sides rect
(tl,tr,br,bl) = map4 ((,Nothing) . (^.core)) $ corners rect
map4 :: (a -> b) -> (a,a,a,a) -> (b,b,b,b)
map4 f (a,b',c,d) = (f a, f b', f c, f d)
link :: Eq r => [(Point 2 r, a)] -> PlanarSubdivision s v (Maybe e) f r
-> V.Vector (Point 2 r, VertexId' s, a)
link vs ps = V.fromList . map (\((p,x),(_,y)) -> (p,y,x)) . F.toList
. fromJust' $ alignWith (\(p,_) (q,_) -> p == q) (CSeq.fromList vs) vs'
where
vs' = CSeq.fromList . map (\v -> (ps^.locationOf v,v) ) . V.toList
$ boundaryVertices (outerFaceId ps) ps
fromJust' = fromMaybe (error "Data.Geometry.Arrangement.link: fromJust")
makePairs :: [a] -> [(a,[a])]
makePairs = go
where
go [] = []
go (x:xs) = (x,xs) : map (\(y,ys) -> (y,x:ys)) (go xs)
allPairs :: [a] -> [(a,a)]
allPairs ys = go ys
where
go [] = []
go (x:xs) = map (x,) xs ++ go xs
alignWith :: (a -> b -> Bool) -> CSeq.CSeq a -> CSeq.CSeq b
-> Maybe (CSeq.CSeq (a,b))
alignWith p xs ys = CSeq.zipL xs <$> CSeq.findRotateTo (p (CSeq.focus xs)) ys
traverseLine :: (Ord r, Fractional r)
=> Line 2 r -> Arrangement s l v (Maybe e) f r -> [Dart s]
traverseLine l arr = let md = findStart l arr
dup x = (x,x)
in maybe [] (List.unfoldr (fmap dup . follow arr)) md
findStart :: (Ord r, Fractional r)
=> Line 2 r -> Arrangement s l v (Maybe e) f r -> Maybe (Dart s)
findStart l arr = do
(p,_) <- asA (Proxy :: Proxy (Point 2 r, Point 2 r)) $
l `intersect` (Boundary $ arr^.boundedArea)
(_,v,_) <- findStartVertex p arr
findStartDart (arr^.subdivision) v
findStartVertex :: (Ord r, Fractional r)
=> Point 2 r
-> Arrangement s l v e f r
-> Maybe (Point 2 r, VertexId' s, Maybe (Line 2 r :+ l))
findStartVertex p arr = do
ss <- findSide p
i <- binarySearchVec (pred' ss) (arr^.unboundedIntersections)
pure $ arr^.unboundedIntersections.singular (ix i)
where
(t,r,b,l) = sides'' $ arr^.boundedArea
sides'' = map4 (\(ClosedLineSegment a c) -> LineSegment (Closed a) (Open c)) . sides
findSide q = fmap fst . List.find (onSegment q . snd) $ zip [1..] [t,r,b,l]
pred' ss (q,_,_) = let Just j = findSide q
x = before (ss,p) (j,q)
in x == LT || x == EQ
before (i,p') (j,q') = case i `compare` j of
LT -> LT
GT -> GT
EQ | i == 2 || i == 3 -> Down p' `compare` Down q'
| otherwise -> p' `compare` q'
findStartDart :: PlanarSubdivision s v (Maybe e) f r -> VertexId' s -> Maybe (Dart s)
findStartDart ps v = V.find (\d -> isJust $ ps^.dataOf d) $ incidentEdges v ps
follow :: (Ord r, Num r) => Arrangement s l v e f r -> Dart s -> Maybe (Dart s)
follow arr d = V.find extends $ incidentEdges v ps
where
ps = arr^.subdivision
v = headOf d ps
(up,vp) = over both (^.location) $ endPointData d ps
extends d' = let wp = ps^.locationOf (headOf d' ps)
in d' /= twin d && ccw up vp wp == CoLinear