2015-01-07 17:55:16 +00:00
|
|
|
{-# OPTIONS_HADDOCK ignore-exports #-}
|
|
|
|
|
|
|
|
module Algorithms.PolygonTriangulation where
|
|
|
|
|
2015-01-08 00:44:47 +00:00
|
|
|
import Algebra.Polygon
|
2015-01-07 17:55:16 +00:00
|
|
|
import Algebra.Vector
|
2015-01-09 02:24:09 +00:00
|
|
|
import qualified Control.Arrow as A
|
2015-01-08 00:44:47 +00:00
|
|
|
import Data.List
|
|
|
|
import Data.Maybe
|
2015-01-09 02:24:09 +00:00
|
|
|
import Safe
|
2015-01-07 17:55:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
data VCategory = VStart
|
|
|
|
| VEnd
|
|
|
|
| VRegular
|
|
|
|
| VSplit
|
|
|
|
| VMerge
|
2015-01-07 18:16:12 +00:00
|
|
|
deriving (Show, Eq)
|
2015-01-07 17:55:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- |Classify all vertices on a polygon into five categories (see VCategory).
|
|
|
|
classifyList :: [PT] -> [(PT, VCategory)]
|
|
|
|
classifyList p@(x:y:_:_) =
|
|
|
|
-- need to handle the first and last element separately
|
|
|
|
[classify (last p) x y] ++ go p ++ [classify (last . init $ p) (last p) x]
|
|
|
|
where
|
|
|
|
go :: [PT] -> [(PT, VCategory)]
|
|
|
|
go (x':y':z':xs) = classify x' y' z' : go (y':z':xs)
|
|
|
|
go _ = []
|
|
|
|
classifyList _ = []
|
|
|
|
|
|
|
|
|
|
|
|
-- |Classify a vertex on a polygon given it's next and previous vertex
|
|
|
|
-- into five categories (see VCategory).
|
|
|
|
classify :: PT -- ^ prev vertex
|
|
|
|
-> PT -- ^ classify this one
|
|
|
|
-> PT -- ^ next vertex
|
|
|
|
-> (PT, VCategory)
|
|
|
|
classify prev v next
|
|
|
|
| isVStart prev v next = (v, VStart)
|
|
|
|
| isVSplit prev v next = (v, VSplit)
|
|
|
|
| isVEnd prev v next = (v, VEnd)
|
|
|
|
| isVMerge prev v next = (v, VMerge)
|
|
|
|
| otherwise = (v, VRegular)
|
|
|
|
|
|
|
|
|
2015-01-07 18:15:51 +00:00
|
|
|
-- |Whether the vertex, given it's next and previous vertex,
|
2015-01-07 17:55:16 +00:00
|
|
|
-- is a start vertex.
|
2015-01-07 18:15:51 +00:00
|
|
|
isVStart :: PT -- ^ previous vertex
|
2015-01-07 17:55:16 +00:00
|
|
|
-> PT -- ^ vertice to check
|
2015-01-07 18:15:51 +00:00
|
|
|
-> PT -- ^ next vertex
|
2015-01-07 17:55:16 +00:00
|
|
|
-> Bool
|
|
|
|
isVStart prev v next =
|
2015-01-09 02:24:44 +00:00
|
|
|
ptCmpY next v == LT && ptCmpY prev v == LT && cw next v prev
|
2015-01-07 17:55:16 +00:00
|
|
|
|
|
|
|
|
2015-01-07 18:15:51 +00:00
|
|
|
-- |Whether the vertex, given it's next and previous vertex,
|
2015-01-07 17:55:16 +00:00
|
|
|
-- is a split vertex.
|
2015-01-07 18:15:51 +00:00
|
|
|
isVSplit :: PT -- ^ previous vertex
|
2015-01-07 17:55:16 +00:00
|
|
|
-> PT -- ^ vertice to check
|
2015-01-07 18:15:51 +00:00
|
|
|
-> PT -- ^ next vertex
|
2015-01-07 17:55:16 +00:00
|
|
|
-> Bool
|
|
|
|
isVSplit prev v next =
|
2015-01-09 02:24:44 +00:00
|
|
|
ptCmpY prev v == LT && ptCmpY next v == LT && cw prev v next
|
2015-01-07 17:55:16 +00:00
|
|
|
|
|
|
|
|
2015-01-07 18:15:51 +00:00
|
|
|
-- |Whether the vertex, given it's next and previous vertex,
|
2015-01-07 17:55:16 +00:00
|
|
|
-- is an end vertex.
|
2015-01-07 18:15:51 +00:00
|
|
|
isVEnd :: PT -- ^ previous vertex
|
2015-01-07 17:55:16 +00:00
|
|
|
-> PT -- ^ vertice to check
|
2015-01-07 18:15:51 +00:00
|
|
|
-> PT -- ^ next vertex
|
2015-01-07 17:55:16 +00:00
|
|
|
-> Bool
|
|
|
|
isVEnd prev v next =
|
2015-01-09 02:24:44 +00:00
|
|
|
ptCmpY prev v == GT && ptCmpY next v == GT && cw next v prev
|
2015-01-07 17:55:16 +00:00
|
|
|
|
|
|
|
|
2015-01-07 18:15:51 +00:00
|
|
|
-- |Whether the vertex, given it's next and previous vertex,
|
2015-01-07 17:55:16 +00:00
|
|
|
-- is a merge vertex.
|
2015-01-07 18:15:51 +00:00
|
|
|
isVMerge :: PT -- ^ previous vertex
|
2015-01-07 17:55:16 +00:00
|
|
|
-> PT -- ^ vertice to check
|
2015-01-07 18:15:51 +00:00
|
|
|
-> PT -- ^ next vertex
|
2015-01-07 17:55:16 +00:00
|
|
|
-> Bool
|
|
|
|
isVMerge prev v next =
|
2015-01-09 02:24:44 +00:00
|
|
|
ptCmpY next v == GT && ptCmpY prev v == GT && cw prev v next
|
2015-01-07 17:55:16 +00:00
|
|
|
|
|
|
|
|
2015-01-07 18:15:51 +00:00
|
|
|
-- |Whether the vertex, given it's next and previous vertex,
|
2015-01-07 17:55:16 +00:00
|
|
|
-- is a regular vertex.
|
2015-01-07 18:15:51 +00:00
|
|
|
isVRegular :: PT -- ^ previous vertex
|
2015-01-07 17:55:16 +00:00
|
|
|
-> PT -- ^ vertice to check
|
2015-01-07 18:15:51 +00:00
|
|
|
-> PT -- ^ next vertex
|
2015-01-07 17:55:16 +00:00
|
|
|
-> Bool
|
|
|
|
isVRegular prev v next =
|
|
|
|
(not . isVStart prev v $ next)
|
|
|
|
&& (not . isVSplit prev v $ next)
|
|
|
|
&& (not . isVEnd prev v $ next)
|
|
|
|
&& (not . isVMerge prev v $ next)
|
|
|
|
|
|
|
|
|
2015-01-07 18:16:12 +00:00
|
|
|
|
2015-01-09 02:58:24 +00:00
|
|
|
-- |A polygon P is y-monotone, if it has no split and merge vertices.
|
2015-01-07 18:16:12 +00:00
|
|
|
isYmonotone :: [PT] -> Bool
|
|
|
|
isYmonotone poly =
|
|
|
|
not
|
|
|
|
. any (\x -> x == VSplit || x == VMerge)
|
|
|
|
. fmap snd
|
|
|
|
$ classifyList poly
|
2015-01-08 00:44:47 +00:00
|
|
|
|
|
|
|
|
2015-01-09 02:58:24 +00:00
|
|
|
-- |Partition P in y-monotone pieces.
|
2015-01-09 02:58:05 +00:00
|
|
|
monotonePartitioning :: [PT] -> [[PT]]
|
|
|
|
monotonePartitioning pts
|
|
|
|
| isYmonotone pts = [pts]
|
|
|
|
| and . fmap isYmonotone $ maybeMonotone = maybeMonotone
|
|
|
|
| otherwise = (\(x, y) -> x ++ (concat . fmap monotonePartitioning $ y))
|
2015-01-08 00:44:47 +00:00
|
|
|
(partition isYmonotone maybeMonotone)
|
|
|
|
where
|
2015-01-09 02:58:05 +00:00
|
|
|
maybeMonotone = foldr (\x y -> splitPoly pts x ++ y)
|
|
|
|
[]
|
|
|
|
(monotoneDiagonals pts)
|
2015-01-08 00:44:47 +00:00
|
|
|
|
|
|
|
|
2015-01-09 02:58:24 +00:00
|
|
|
-- |Try to eliminate the merge and split vertices by computing the
|
|
|
|
-- diagonals we have to use for splitting the polygon. This doesn't
|
|
|
|
-- necessarily make our polygon y-monotone yet.
|
2015-01-08 00:44:47 +00:00
|
|
|
monotoneDiagonals :: [PT] -> [(PT, PT)]
|
|
|
|
monotoneDiagonals pts = catMaybes . go $ classifyList pts
|
|
|
|
where
|
|
|
|
go (x:xs) = case snd x of
|
|
|
|
VMerge -> getSeg (belowS (fst x) pts) (fst x) pts : go xs
|
|
|
|
VSplit -> getSeg (aboveS (fst x) pts) (fst x) pts : go xs
|
|
|
|
_ -> [] ++ go xs
|
|
|
|
go [] = []
|
|
|
|
getSeg [] _ _ = Nothing
|
|
|
|
getSeg (z:zs) pt pts'
|
2015-01-09 02:20:13 +00:00
|
|
|
| isInsidePoly pts (z, pt) = Just (z, pt)
|
2015-01-08 00:44:47 +00:00
|
|
|
| otherwise = getSeg zs pt pts'
|
|
|
|
aboveS pt pts' = tail . dropWhile (/= pt) $ sortedYX pts'
|
|
|
|
belowS pt pts' = reverse . takeWhile (/= pt) $ sortedYX pts'
|
|
|
|
|
2015-01-09 02:24:09 +00:00
|
|
|
|
2015-01-09 02:58:24 +00:00
|
|
|
-- |A simple polygon with n vertices can be partitioned into y-monotone pieces
|
|
|
|
-- in O(n log n).
|
2015-01-09 02:58:05 +00:00
|
|
|
triangulate :: [PT] -> [[PT]]
|
|
|
|
triangulate pts =
|
2015-01-09 02:24:09 +00:00
|
|
|
go pts . A.first reverse . splitAt 3 . reverse . sortedYX $ pts
|
|
|
|
where
|
|
|
|
go xs (p@[_, _], r:rs) = go xs (r:p, rs)
|
|
|
|
go xs (p@(u:vi:vi1:ys), rs)
|
|
|
|
-- case 1 and 3
|
|
|
|
| adjacent u (last p) xs =
|
|
|
|
splitPoly xs (u, (last . init) p)
|
|
|
|
++ go (fromMaybe []
|
|
|
|
. headMay
|
|
|
|
. nonTriangleOnly
|
|
|
|
. splitPoly xs
|
|
|
|
$ (u, (last . init) p))
|
|
|
|
(init p, rs)
|
|
|
|
-- case 2
|
|
|
|
| adjacent u vi xs && (not . null) rs =
|
|
|
|
if getAngle (vp2 vi u) (vp2 vi vi1) < pi / 2
|
|
|
|
then splitPoly xs (u, vi1)
|
|
|
|
++ go (fromMaybe []
|
|
|
|
. headMay
|
|
|
|
. nonTriangleOnly
|
|
|
|
. splitPoly xs
|
|
|
|
$ (u, vi1))
|
|
|
|
(u:vi1:ys, rs)
|
|
|
|
else go xs (head rs:p, tail rs)
|
|
|
|
| otherwise = [[]]
|
|
|
|
go _ _ = [[]]
|