{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TemplateHaskell #-}
module Algorithms.Geometry.SmallestEnclosingBall.RIC(
smallestEnclosingDisk'
, smallestEnclosingDisk
, smallestEnclosingDiskWithPoint
, smallestEnclosingDiskWithPoints
) where
import Algorithms.Geometry.SmallestEnclosingBall.Types
import Control.Lens
import Control.Monad.Random.Class
import Data.Ext
import qualified Data.Foldable as F
import Data.Geometry
import Data.Geometry.Ball
import qualified Data.List as List
import Data.List.NonEmpty(NonEmpty(..))
import Data.Maybe (fromMaybe, mapMaybe, catMaybes)
import Data.Ord (comparing)
import System.Random.Shuffle (shuffle)
import Data.RealNumber.Rational
import Debug.Trace
smallestEnclosingDisk :: (Ord r, Fractional r, MonadRandom m
)
=> [Point 2 r :+ p]
-> m (DiskResult p r)
smallestEnclosingDisk pts@(_:_:_) = ((\(p:q:pts') -> smallestEnclosingDisk' p q pts')
. F.toList) <$> shuffle pts
smallestEnclosingDisk _ = error "smallestEnclosingDisk: Too few points"
smallestEnclosingDisk' :: (Ord r, Fractional r
)
=> Point 2 r :+ p -> Point 2 r :+ p -> [Point 2 r :+ p]
-> DiskResult p r
smallestEnclosingDisk' a b = foldr addPoint (initial a b) . List.tails
where
addPoint [] br = br
addPoint (p:pts) br@(DiskResult d _)
| (p^.core) `inClosedBall` d = br
| otherwise = fromJust' $ smallestEnclosingDiskWithPoint p (a :| (b : pts))
fromJust' = fromMaybe (error "smallestEncosingDisk' : fromJust, absurd")
smallestEnclosingDiskWithPoint :: (Ord r, Fractional r
)
=> Point 2 r :+ p -> NonEmpty (Point 2 r :+ p)
-> Maybe (DiskResult p r)
smallestEnclosingDiskWithPoint p (a :| pts) = foldr addPoint (Just $ initial p a) $ List.tails pts
where
addPoint [] br = br
addPoint (q:pts') br@(Just (DiskResult d _))
| (q^.core) `inClosedBall` d = br
| otherwise = smallestEnclosingDiskWithPoints p q (a:pts')
addPoint _ br = br
smallestEnclosingDiskWithPoints :: (Ord r, Fractional r
)
=> Point 2 r :+ p -> Point 2 r :+ p -> [Point 2 r :+ p]
-> Maybe (DiskResult p r)
smallestEnclosingDiskWithPoints p q ps = minimumOn (^.enclosingDisk.squaredRadius)
$ catMaybes [mkEnclosingDisk dl, mkEnclosingDisk dr, mdc]
where
centers = mapMaybe disk' ps
disk' r = (r:+) <$> disk (p^.core) (q^.core) (r^.core)
(leftCenters,rightCenters) = List.partition (\(r :+ _) -> ccw' p q r == CCW) centers
leftDist z = let c = z^.extra.center
s = if ccw' p q c == CCW then 1 else -1
in s * squaredEuclideanDist (p^.core) (c^.core)
dl = maximumOn leftDist leftCenters
dr = minimumOn leftDist rightCenters
dd = fromDiameter (p^.core) (q^.core)
mdc | isEnclosingDisk dd ps = Just $ DiskResult dd (Two p q)
| otherwise = Nothing
mkEnclosingDisk md = md >>= mkEnclosingDisk'
mkEnclosingDisk' (r :+ d) | isEnclosingDisk d ps = Just (DiskResult d (Three p q r))
| otherwise = Nothing
isEnclosingDisk :: (Foldable t, Ord r, Num r)
=> Disk p r -> t (Point 2 r :+ extra) -> Bool
isEnclosingDisk d = all (\s -> (s^.core) `inClosedBall` d)
initial :: Fractional r => Point 2 r :+ p -> Point 2 r :+ p -> DiskResult p r
initial p q = DiskResult (fromDiameter (p^.core) (q^.core)) (Two p q)
maximumOn :: Ord b => (a -> b) -> [a] -> Maybe a
maximumOn f = \case
[] -> Nothing
xs -> Just $ List.maximumBy (comparing f) xs
minimumOn :: Ord b => (a -> b) -> [a] -> Maybe a
minimumOn f = \case
[] -> Nothing
xs -> Just $ List.minimumBy (comparing f) xs