module Algorithms.Geometry.ConvexHull.JarvisMarch(
convexHull
, upperHull, upperHull'
, lowerHull, lowerHull'
, steepestCcwFrom, steepestCwFrom
) where
import Control.Lens ((^.))
import Data.Bifunctor
import Data.Either (either)
import Data.Ext
import Data.Foldable
import Data.Geometry.Point
import Data.Geometry.Polygon
import Data.Geometry.Polygon.Convex (ConvexPolygon(..))
import Data.Geometry.Vector
import qualified Data.List as List
import Data.List.NonEmpty (NonEmpty(..), (<|))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Ord (comparing, Down(..))
import Data.Semigroup.Foldable
convexHull :: (Ord r, Num r)
=> NonEmpty (Point 2 r :+ p) -> ConvexPolygon p r
convexHull (p :| []) = ConvexPolygon . fromPoints $ [p]
convexHull pts = ConvexPolygon . fromPoints $ uh <> reverse lh
where
lh = case NonEmpty.nonEmpty (NonEmpty.init $ lowerHull pts) of
Nothing -> []
Just (_:|lh') -> lh'
uh = toList $ upperHull pts
upperHull :: (Num r, Ord r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)
upperHull pts = repeatedly cmp steepestCwFrom s rest
where
(s:_ :+ rest) = extractMinimaBy cmp (NonEmpty.toList pts)
cmp = comparing (\(Point2 x y :+ _) -> (x, Down y))
upperHull' :: (Num r, Ord r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)
upperHull' pts = pruneVertical $ repeatedly cmp steepestCwFrom s rest
where
(s:_ :+ rest) = extractMinimaBy cmp0 (NonEmpty.toList pts)
cmp0 = comparing (\(Point2 x y :+ _) -> (x, Down y))
cmp = comparing (^.core)
lowerHull :: (Num r, Ord r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)
lowerHull pts = pruneVertical $ repeatedly cmp steepestCcwFrom s rest
where
(s:_ :+ rest) = extractMinimaBy cmp0 (NonEmpty.toList pts)
cmp0 = comparing (\(Point2 x y :+ _) -> (x, Down y))
cmp = comparing (^.core)
lowerHull' :: (Num r, Ord r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)
lowerHull' pts = pruneVertical $ repeatedly cmp steepestCcwFrom s rest
where
(s:_ :+ rest) = extractMinimaBy cmp (NonEmpty.toList pts)
cmp = comparing (^.core)
steepestCcwFrom :: (Ord r, Num r)
=> (Point 2 r :+ a) -> NonEmpty (Point 2 r :+ b) -> Point 2 r :+ b
steepestCcwFrom p = List.minimumBy (ccwCmpAroundWith (Vector2 0 (-1)) p)
steepestCwFrom :: (Ord r, Num r)
=> (Point 2 r :+ a) -> NonEmpty (Point 2 r :+ b) -> Point 2 r :+ b
steepestCwFrom p = List.minimumBy (cwCmpAroundWith (Vector2 0 1) p)
repeatedly :: (a -> a -> Ordering) -> (a -> NonEmpty a -> a) -> a -> [a] -> NonEmpty a
repeatedly cmp f = go
where
go m xs' = case NonEmpty.nonEmpty xs' of
Nothing -> m :| []
Just xs -> let p = f m xs
in m <| go p (NonEmpty.filter (\x -> p `cmp` x == LT) xs)
pruneVertical :: Eq r => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)
pruneVertical = either id id . foldr1With f (\q -> Left $ q:|[])
where
f p = \case
Left (q:|qs) | p^.core.xCoord == q^.core.xCoord -> Left (p :| qs)
| otherwise -> Right (p :| q:qs)
Right pts -> Right (p <| pts)
foldr1With :: Foldable1 f => (a -> b -> b) -> (a -> b) -> f a -> b
foldr1With f b = go . toNonEmpty
where
go (x :| xs) = case NonEmpty.nonEmpty xs of
Nothing -> b x
Just xs' -> x `f` (go xs')
extractMinimaBy :: (a -> a -> Ordering) -> [a] -> [a] :+ [a]
extractMinimaBy cmp = \case
[] -> [] :+ []
(x:xs) -> first NonEmpty.toList $ foldr (\y (mins@(m:|_) :+ rest) ->
case m `cmp` y of
LT -> mins :+ y:rest
EQ -> (y NonEmpty.<| mins) :+ rest
GT -> (y:|[]) :+ NonEmpty.toList mins <> rest
) ((x:|[]) :+ []) xs