2014-10-08 14:39:46 +00:00
|
|
|
{-# OPTIONS_HADDOCK ignore-exports #-}
|
|
|
|
|
2014-10-10 15:40:08 +00:00
|
|
|
module Algorithms.ConvexHull.GrahamScan where
|
2014-10-08 14:39:46 +00:00
|
|
|
|
2014-10-10 15:40:08 +00:00
|
|
|
import Algebra.Vector
|
|
|
|
import Algebra.VectorTypes
|
2014-10-08 14:39:46 +00:00
|
|
|
import Diagrams.TwoD.Types
|
2014-10-10 15:40:08 +00:00
|
|
|
import MyPrelude
|
2014-10-08 14:39:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- |Get all points on a convex hull by using the graham scan
|
|
|
|
-- algorithm.
|
|
|
|
grahamGetCH :: [PT] -> [PT]
|
2014-10-09 22:19:05 +00:00
|
|
|
grahamGetCH vs =
|
2014-10-11 23:54:44 +00:00
|
|
|
-- merge upper hull with lower hull while discarding
|
|
|
|
-- the duplicated points from the lower hull
|
2014-10-12 00:11:47 +00:00
|
|
|
scan uH uHRest ++ tailInit (scan lH lHRest)
|
2014-10-08 14:39:46 +00:00
|
|
|
where
|
2014-10-11 23:54:44 +00:00
|
|
|
-- sort lexicographically by x values (ties are resolved by y values)
|
2014-10-12 00:11:47 +00:00
|
|
|
sortedXY = fmap p2 . sortLex . fmap unp2 $ vs
|
2014-10-11 23:54:44 +00:00
|
|
|
-- lists for lower hull
|
2014-10-12 00:11:47 +00:00
|
|
|
(lH, lHRest) = first reverse . splitAt 2 $ sortedXY
|
2014-10-11 23:54:44 +00:00
|
|
|
-- lists for upper hull
|
2014-10-12 00:11:47 +00:00
|
|
|
(uH, uHRest) = first reverse . splitAt 2 . reverse $ sortedXY
|
2014-10-11 23:54:44 +00:00
|
|
|
-- This is the actual algorithm.
|
|
|
|
-- If we have a list say:
|
|
|
|
-- [(100, 100), (200, 450), (250, 250), (300, 400), (400, 200)]
|
|
|
|
--
|
|
|
|
-- then this will start with:
|
|
|
|
-- [(200, 450), (100, 100)] and [(250, 250), (300, 400), (400, 200)]
|
|
|
|
--
|
|
|
|
-- The first list is reversed since we only care about the last
|
|
|
|
-- 3 elements and want to stay efficient.
|
2014-10-12 00:11:47 +00:00
|
|
|
scan :: [PT] -- ^ the starting convex hull points
|
|
|
|
-> [PT] -- ^ the rest of the points
|
|
|
|
-> [PT] -- ^ all convex hull points
|
|
|
|
scan (y:z:xs) (x:ys)
|
2014-10-11 23:54:44 +00:00
|
|
|
-- last 3 elements are ccw, but there are elements left to check
|
2014-10-12 00:11:47 +00:00
|
|
|
| ccw z y x = scan (x:y:z:xs) ys
|
2014-10-11 23:54:44 +00:00
|
|
|
-- not ccw, pop one out
|
2014-10-12 00:11:47 +00:00
|
|
|
| otherwise = scan (x:z:xs) ys
|
|
|
|
scan (x:y:z:xs) []
|
2014-10-11 23:54:44 +00:00
|
|
|
-- nothing left and last 3 elements are ccw, so return
|
|
|
|
| ccw z y x = x:y:z:xs
|
|
|
|
-- not ccw, pop one out
|
2014-10-12 00:11:47 +00:00
|
|
|
| otherwise = scan (x:z:xs) []
|
|
|
|
scan xs _ = xs
|
2014-10-09 01:10:21 +00:00
|
|
|
|
|
|
|
|
2014-10-09 01:27:02 +00:00
|
|
|
-- |Compute all steps of the graham scan algorithm to allow
|
2014-10-09 01:10:21 +00:00
|
|
|
-- visualizing it.
|
|
|
|
grahamGetCHSteps :: [PT] -> [[PT]]
|
2014-10-09 22:19:05 +00:00
|
|
|
grahamGetCHSteps vs =
|
2014-10-12 01:00:13 +00:00
|
|
|
(++) (rmdups . reverse . g (length vs) lH $ lHRest)
|
|
|
|
(rmdups . init . reverse . g (length vs) uH $ uHRest)
|
2014-10-09 01:10:21 +00:00
|
|
|
where
|
2014-10-12 00:11:47 +00:00
|
|
|
sortedXY = fmap p2 . sortLex . fmap unp2 $ vs
|
|
|
|
(lH, lHRest) = first reverse . splitAt 2 $ sortedXY
|
|
|
|
(uH, uHRest) = first reverse . splitAt 2 . reverse $ sortedXY
|
2014-10-11 23:54:44 +00:00
|
|
|
g c xs' ys'
|
2014-10-12 00:11:47 +00:00
|
|
|
| c >= 0 = scan 0 xs' ys' : g (c - 1) xs' ys'
|
2014-10-09 01:10:21 +00:00
|
|
|
| otherwise = []
|
|
|
|
where
|
2014-10-12 00:11:47 +00:00
|
|
|
scan c' (y:z:xs) (x:ys)
|
2014-10-12 01:00:13 +00:00
|
|
|
| c' >= c = y:z:xs
|
2014-10-12 00:11:47 +00:00
|
|
|
| ccw z y x = scan (c' + 1) (x:y:z:xs) ys
|
|
|
|
| otherwise = scan (c' + 1) (x:z:xs) ys
|
|
|
|
scan _ [x,y] [] = [y,x]
|
|
|
|
scan c' (x:y:z:xs) []
|
2014-10-12 01:00:13 +00:00
|
|
|
| c' >= c = x:y:z:xs
|
2014-10-11 23:54:44 +00:00
|
|
|
| ccw z y x = x:y:z:xs
|
2014-10-12 00:11:47 +00:00
|
|
|
| otherwise = scan (c' + 1) (x:z:xs) []
|
2014-10-12 01:00:13 +00:00
|
|
|
scan _ xs _ = xs
|