{-# LANGUAGE ScopedTypeVariables #-}
module Algorithms.Geometry.DelaunayTriangulation.DivideAndConquer where
import Algorithms.Geometry.ConvexHull.GrahamScan as GS
import Algorithms.Geometry.DelaunayTriangulation.Types
import Control.Lens
import Control.Monad.Reader
import Control.Monad.State
import Data.BinaryTree
import qualified Data.CircularList as CL
import qualified Data.CircularList.Util as CU
import qualified Data.CircularSeq as CS
import Data.Ext
import qualified Data.Foldable as F
import Data.Function (on)
import Data.Geometry hiding (rotateTo)
import Data.Geometry.Ball (disk, insideBall)
import Data.Geometry.Polygon
import Data.Geometry.Polygon.Convex (ConvexPolygon(..), simplePolygon)
import qualified Data.Geometry.Polygon.Convex as Convex
import qualified Data.IntMap.Strict as IM
import qualified Data.List as L
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map as M
import Data.Maybe (fromJust, fromMaybe)
import Data.Measured.Size
import qualified Data.Vector as V
delaunayTriangulation :: (Ord r, Fractional r)
=> NonEmpty.NonEmpty (Point 2 r :+ p) -> Triangulation p r
delaunayTriangulation pts' = Triangulation vtxMap ptsV adjV
where
pts = nub' . NonEmpty.sortBy (compare `on` (^.core)) $ pts'
ptsV = V.fromList . F.toList $ pts
vtxMap = M.fromList $ zip (map (^.core) . V.toList $ ptsV) [0..]
tr = _unElem <$> asBalancedBinLeafTree pts
(adj,_) = delaunayTriangulation' tr (vtxMap,ptsV)
adjV = V.fromList . IM.elems $ adj
delaunayTriangulation' :: (Ord r, Fractional r)
=> BinLeafTree Size (Point 2 r :+ p)
-> Mapping p r
-> (Adj, ConvexPolygon (p :+ VertexID) r)
delaunayTriangulation' pts mapping'@(vtxMap,_)
| size' pts == 1 = let (Leaf p) = pts
i = lookup' vtxMap (p^.core)
in (IM.singleton i CL.empty, ConvexPolygon $ fromPoints [withID p i])
| size' pts <= 3 = let pts' = NonEmpty.fromList
. map (\p -> withID p (lookup' vtxMap (p^.core)))
. F.toList $ pts
ch = GS.convexHull pts'
in (fromHull mapping' ch, ch)
| otherwise = let (Node lt _ rt) = pts
(ld,lch) = delaunayTriangulation' lt mapping'
(rd,rch) = delaunayTriangulation' rt mapping'
(ch, bt, ut) = Convex.merge lch rch
in (merge ld rd bt ut mapping' (firsts ch), ch)
firsts :: ConvexPolygon (p :+ VertexID) r -> IM.IntMap VertexID
firsts = IM.fromList . map (\s -> (s^.end.extra.extra, s^.start.extra.extra))
. F.toList . outerBoundaryEdges . _simplePolygon
fromHull :: Ord r => Mapping p r -> ConvexPolygon (p :+ q) r -> Adj
fromHull (vtxMap,_) p = let vs@(u:v:vs') = map (lookup' vtxMap . (^.core))
. F.toList . CS.rightElements
$ p^.simplePolygon.outerBoundary
es = zipWith3 f vs (tail vs ++ [u]) (vs' ++ [u,v])
f prv c nxt = (c,CL.fromList . L.nub $ [prv, nxt])
in IM.fromList es
merge :: (Ord r, Fractional r)
=> Adj
-> Adj
-> LineSegment 2 (p :+ VertexID) r
-> LineSegment 2 (p :+ VertexID) r
-> Mapping p r
-> Firsts
-> Adj
merge ld rd bt ut mapping'@(vtxMap,_) fsts =
flip runReader (mapping', fsts) . flip execStateT adj $ moveUp (tl,tr) l r
where
l = lookup' vtxMap (bt^.start.core)
r = lookup' vtxMap (bt^.end.core)
tl = lookup' vtxMap (ut^.start.core)
tr = lookup' vtxMap (ut^.end.core)
adj = ld `IM.union` rd
type Merge p r = StateT Adj (Reader (Mapping p r, Firsts))
type Firsts = IM.IntMap VertexID
moveUp :: (Ord r, Fractional r)
=> (VertexID,VertexID) -> VertexID -> VertexID -> Merge p r ()
moveUp ut l r
| (l,r) == ut = insert l r
| otherwise = do
insert l r
r1 <- pred' . rotateTo l . lookup'' r <$> get
l1 <- succ' . rotateTo r . lookup'' l <$> get
(r1',a) <- rotateR l r r1
(l1',b) <- rotateL l r l1
c <- qTest l r r1' l1'
let (l',r') = case (a,b,c) of
(True,_,_) -> (focus' l1', r)
(False,True,_) -> (l, focus' r1')
(False,False,True) -> (l, focus' r1')
(False,False,False) -> (focus' l1', r)
moveUp ut l' r'
rotateR :: (Ord r, Fractional r)
=> VertexID -> VertexID -> Vertex -> Merge p r (Vertex, Bool)
rotateR l r r1 = focus' r1 `isLeftOf` (l, r) >>= \case
True -> (,False) <$> rotateR' l r r1 (pred' r1)
False -> pure (r1,True)
rotateR' :: (Ord r, Fractional r)
=> VertexID -> VertexID -> Vertex -> Vertex -> Merge p r Vertex
rotateR' l r = go
where
go r1 r2 = qTest l r r1 r2 >>= \case
True -> pure r1
False -> do modify $ delete r (focus' r1)
go r2 (pred' r2)
rotateL :: (Ord r, Fractional r)
=> VertexID -> VertexID -> Vertex -> Merge p r (Vertex, Bool)
rotateL l r l1 = focus' l1 `isRightOf` (r, l) >>= \case
True -> (,False) <$> rotateL' l r l1 (succ' l1)
False -> pure (l1,True)
rotateL' :: (Ord r, Fractional r)
=> VertexID -> VertexID -> Vertex -> Vertex -> Merge p r Vertex
rotateL' l r = go
where
go l1 l2 = qTest l r l1 l2 >>= \case
True -> pure l1
False -> do modify $ delete l (focus' l1)
go l2 (succ' l2)
qTest :: (Ord r, Fractional r)
=> VertexID -> VertexID -> Vertex -> Vertex -> Merge p r Bool
qTest h i j k = withPtMap . snd . fst <$> ask
where
withPtMap ptMap = let h' = ptMap V.! h
i' = ptMap V.! i
j' = ptMap V.! (focus' j)
k' = ptMap V.! (focus' k)
in not . maybe True ((k'^.core) `insideBall`) $ disk' h' i' j'
disk' p q r = disk (p^.core) (q^.core) (r^.core)
insert :: (Num r, Ord r) => VertexID -> VertexID -> Merge p r ()
insert u v = do
(mapping',fsts) <- ask
modify $ insert' u v mapping'
rotateToFirst u fsts
rotateToFirst v fsts
rotateToFirst :: VertexID -> Firsts -> Merge p r ()
rotateToFirst v fsts = modify $ IM.adjust f v
where
mfst = IM.lookup v fsts
f cl = fromMaybe cl $ mfst >>= flip CL.rotateTo cl
insert' :: (Num r, Ord r)
=> VertexID -> VertexID -> Mapping p r -> Adj -> Adj
insert' u v (_,ptMap) = IM.adjustWithKey (insert'' v) u
. IM.adjustWithKey (insert'' u) v
where
insert'' bi ai = CU.insertOrdBy (cwCmpAround' (ptMap V.! ai) `on` (ptMap V.!)) bi
cwCmpAround' c p q = cwCmpAround c p q <> cmpByDistanceTo c p q
delete :: VertexID -> VertexID -> Adj -> Adj
delete u v = IM.adjust (delete' v) u . IM.adjust (delete' u) v
where
delete' x = CL.filterL (/= x)
isLeftOf :: (Ord r, Num r)
=> VertexID -> (VertexID, VertexID) -> Merge p r Bool
p `isLeftOf` (l,r) = withPtMap . snd . fst <$> ask
where
withPtMap ptMap = (ptMap V.! p) `isLeftOf'` (ptMap V.! l, ptMap V.! r)
a `isLeftOf'` (b,c) = ccw' b c a == CCW
isRightOf :: (Ord r, Num r)
=> VertexID -> (VertexID, VertexID) -> Merge p r Bool
p `isRightOf` (l,r) = withPtMap . snd . fst <$> ask
where
withPtMap ptMap = (ptMap V.! p) `isRightOf'` (ptMap V.! l, ptMap V.! r)
a `isRightOf'` (b,c) = ccw' b c a == CW
lookup' :: Ord k => M.Map k a -> k -> a
lookup' m x = fromJust $ M.lookup x m
size' :: BinLeafTree Size a -> Size
size' (Leaf _) = 1
size' (Node _ s _) = s
rotateTo :: Eq a => a -> CL.CList a -> CL.CList a
rotateTo x = fromJust . CL.rotateTo x
pred' :: CL.CList a -> CL.CList a
pred' = CL.rotR
succ' :: CL.CList a -> CL.CList a
succ' = CL.rotL
focus' :: CL.CList a -> a
focus' = fromJust . CL.focus
nub' :: Eq a => NonEmpty.NonEmpty (a :+ b) -> NonEmpty.NonEmpty (a :+ b)
nub' = fmap NonEmpty.head . NonEmpty.groupBy1 ((==) `on` (^.core))
withID :: c :+ e -> e' -> c :+ (e :+ e')
withID p i = p&extra %~ (:+i)
lookup'' :: Int -> IM.IntMap a -> a
lookup'' k m = fromJust . IM.lookup k $ m