{-# LANGUAGE TemplateHaskell #-}
module Data.Geometry.Arrangement.Internal where
import Algorithms.BinarySearch
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 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 :: forall r l. (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 @(Point 2 r, Point 2 r)
$ (m^.core) `intersect` (Boundary b)
intersectionPoint :: forall r l. (Ord r, Fractional r)
=> Line 2 r :+ l -> Line 2 r :+ l -> Maybe (Point 2 r)
intersectionPoint (l :+ _) (m :+ _) = asA @(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
Sides t r b l = fmap sideIntersections' $ sides rect
Corners tl tr br bl = fmap ((,Nothing) . (^.core)) $ corners rect
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 :: forall s l v e f r. (Ord r, Fractional r)
=> Line 2 r -> Arrangement s l v (Maybe e) f r -> Maybe (Dart s)
findStart l arr = do
(p,_) <- asA @(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
Sides t r b l = sides'' $ arr^.boundedArea
sides'' = fmap (\(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