{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ConstraintKinds #-}
{-# OPTIONS_HADDOCK hide #-}
module Reanimate.Math.Polygon
( APolygon(..)
, Polygon
, FPolygon
, P
, mkPolygon
, mkPolygonFromRing
, castPolygon
, pParent
, pSetOffset
, pAdjustOffset
, pSize
, pNull
, pNext
, pPrev
, pIsSimple
, pIsConvex
, pIsCCW
, pScale
, pAtCentroid
, pAtCenter
, pTranslate
, pCenter
, pBoundingBox
, pIsInside
, pAccess
, pMkWinding
, pDeoverlap
, pCycles
, pCycle
, pCentroid
, pMapEdges
, pArea
, pCircumference
, pCircumference'
, pAddPoints
, pAddPointsRestricted
, pAddPointsBetween
, pRayIntersect
, pOverlap
, pCuts
, pCutEqual
, isValidTriangulation
, triangulationsToPolygons
, ssspVisibility
, ssspWindows
, triangle
, triangle'
, shape1
, shape2
, shape3
, shape4
, shape5
, shape6
, shape7
, shape8
, shape9
, shape10
, shape11
, shape12
, shape13
, shape14
, shape15
, shape16
, shape17
, shape18
, shape19
, shape20
, shape21
, shape22
, shape23
, concave
, pRing
, pUnsafeMap
, pCopy
, pGenerate
, pUnGenerate
, Epsilon
) where
import Data.Hashable
import Data.List (intersect, maximumBy, sort, sortOn,
tails)
import Data.Maybe
import Data.Ratio
import Data.Serialize
import Data.Vector (Vector)
import qualified Data.Vector as V
import Linear.V2
import Linear.Vector
import Reanimate.Math.Common
import Reanimate.Math.SSSP
import Reanimate.Math.Triangulate
type FPolygon = APolygon Double
data APolygon a = Polygon
{ polygonPoints :: Vector (V2 a)
, polygonOffset :: Int
, polygonTriangulation :: Triangulation
, polygonSSSP :: Vector SSSP
}
type Polygon = APolygon Rational
type P = V2 Double
instance Show a => Show (APolygon a) where
show = show . V.toList . polygonPoints
instance Hashable a => Hashable (APolygon a) where
hashWithSalt s p = V.foldl' hashWithSalt s (polygonPoints p)
instance (PolyCtx a, Serialize a) => Serialize (APolygon a) where
put = put . V.toList . polygonPoints
get = mkPolygon . V.fromList <$> get
pRing :: APolygon a -> Ring a
pRing = ringPack . polygonPoints
type PolyCtx a = (Real a, Fractional a, Epsilon a)
mkPolygon :: PolyCtx a => V.Vector (V2 a) -> APolygon a
mkPolygon points = Polygon
{ polygonPoints = points
, polygonOffset = 0
, polygonTriangulation = trig
, polygonSSSP = V.generate n $ \i -> sssp ring (dual i trig)
}
where
n = length points
ring = ringPack points
trig = triangulate ring
castPolygon :: (PolyCtx a, PolyCtx b) => APolygon a -> APolygon b
castPolygon = mkPolygon . V.map (fmap realToFrac) . polygonPoints
mkPolygonFromRing :: PolyCtx a => Ring a -> APolygon a
mkPolygonFromRing = mkPolygon . ringUnpack
pUnsafeMap :: (Ring a -> Ring a) -> APolygon a -> APolygon a
pUnsafeMap fn p = p{ polygonPoints = ringUnpack (fn (pRing p)) }
pParent :: APolygon a -> Int -> Int -> Int
pParent p i j =
(sTree V.! mod (j + polygonOffset p) n - polygonOffset p) `mod` n
where
sTree = polygonSSSP p V.! mod (i + polygonOffset p) n
n = pSize p
pCopy :: Polygon -> Polygon
pCopy p = mkPolygon $ V.generate (pSize p) $ pAccess p
pSetOffset :: APolygon a -> Int -> APolygon a
pSetOffset p offset =
p { polygonOffset = offset `mod` pSize p }
pAdjustOffset :: APolygon a -> Int -> APolygon a
pAdjustOffset p offset =
p { polygonOffset = (polygonOffset p + offset) `mod` pSize p }
{-# INLINE pSize #-}
pSize :: APolygon a -> Int
pSize = length . polygonPoints
pNull :: APolygon a -> Bool
pNull = V.null . polygonPoints
pNext :: APolygon a -> Int -> Int
pNext p i = (i+1) `mod` pSize p
pPrev :: APolygon a -> Int -> Int
pPrev p i = (i-1) `mod` pSize p
pIsSimple :: Polygon -> Bool
pIsSimple p | pSize p < 3 = False
pIsSimple p = pIsCCW p && noDups && checkEdge 0 2
where
noDups = checkForDups (sort (V.toList (polygonPoints p)))
checkForDups (x:y:xs)
= x /= y && checkForDups (y:xs)
checkForDups _ = True
len = pSize p
checkEdge i j
| j >= len = (i > len-3) || checkEdge (i+1) (i+3)
| otherwise =
case lineIntersect (pAccess p i, pAccess p $ i+1)
(pAccess p j, pAccess p $ j+1) of
Just u | u /= pAccess p i -> False
_nothing -> checkEdge i (j+1)
pScale :: Rational -> Polygon -> Polygon
pScale s = pUnsafeMap (ringMap (^* s))
pAtCentroid :: Polygon -> Polygon
pAtCentroid p = pTranslate (negate c) p
where c = pCentroid p ^/ 2
pAtCenter :: Polygon -> Polygon
pAtCenter p = pTranslate (negate $ pCenter p) p
pTranslate :: V2 Rational -> Polygon -> Polygon
pTranslate v = pUnsafeMap (ringMap (+v))
pCenter :: Polygon -> V2 Rational
pCenter p = V2 (x+w/2) (y+h/2)
where
(x,y,w,h) = pBoundingBox p
pBoundingBox :: Polygon -> (Rational, Rational, Rational, Rational)
pBoundingBox = \p ->
let V2 x y = pAccess p 0 in
case V.foldl' worker (x, y, 0, 0) (polygonPoints p) of
(xMin, yMin, xMax, yMax) ->
(xMin, yMin, xMax-xMin, yMax-yMin)
where
worker (xMin,yMin,xMax,yMax) (V2 thisX thisY) =
(min xMin thisX, min yMin thisY
,max xMax thisX, max yMax thisY)
pGenerate :: [(Double, Double)] -> Polygon
pGenerate points
| len < 4 = error "pGenerate: require at least four points"
| otherwise = mkPolygon $ V.fromList
[ V2 (realToFrac $ cos ang * rMod)
(realToFrac $ sin ang * rMod)
| (i,(angMod,rMod)) <- zip [0..] points
, let minAngle = tau / len * i - pi
maxAngle = tau / len * (i+1) - pi
ang = minAngle + (maxAngle-minAngle)*angMod
]
where
tau = 2*pi
len = fromIntegral (length points)
pUnGenerate :: Polygon -> [(Double, Double)]
pUnGenerate p =
[ worker i (fmap realToFrac e)
| (i,e) <- zip [0..] (V.toList $ polygonPoints p) ]
where
len = fromIntegral (pSize p)
worker i (V2 x y) =
let ang = atan2 y x
minAngle = tau / len * i - pi
maxAngle = tau / len * (i+1) - pi
in ((ang-minAngle)/(maxAngle-minAngle), sqrt (x*x+y*y))
tau = 2*pi
isValidTriangulation :: Polygon -> Triangulation -> Bool
isValidTriangulation p t = isComplete && intersectionFree
where
o = polygonOffset p
isComplete = all isProper [0 .. pSize p-1]
isProper i =
let j = pNext p i in
length ((pPrev p i : (t V.! i)) `intersect` (pNext p j : t V.! j)) == 1
intersectionFree = and
[ case lineIntersect (pAccess p (a-o), pAccess p (b-o)) (pAccess p (c-o), pAccess p (d-o)) of
Nothing -> True
Just u -> u == pAccess p (a-o) || u == pAccess p (b-o) ||
u == pAccess p (c-o) || u == pAccess p (d-o)
| ((a,b),(c,d)) <- edgePairs ]
edgePairs = [ (e1, e2) | (e1, rest) <- zip edges (drop 1 $ tails edges), e2 <- rest]
edges =
[ (n, i)
| (n, lst) <- zip [0..] (V.toList t)
, i <- lst
, n < i
]
triangulationsToPolygons :: Polygon -> Triangulation -> [Polygon]
triangulationsToPolygons p t =
[ mkPolygon $ V.fromList
[ pAccess p g, pAccess p i, pAccess p j ]
| i <- [0 .. pSize p-1]
, let js = filter (i<) $ t V.! i
, (g, j) <- zip (i-1:js) js
]
pIsInside :: Polygon -> V2 Rational -> Bool
pIsInside p point = or
[ isInside (rawAccess g) (rawAccess i) (rawAccess j) point
| i <- [0 .. pSize p-1]
, let js = filter (i<) $ polygonTriangulation p V.! i
, (g, j) <- zip (i-1:js) js
]
where
rawAccess x = polygonPoints p V.! x
{-# INLINE pAccess #-}
pAccess :: APolygon a -> Int -> V2 a
pAccess p i =
polygonPoints p `V.unsafeIndex` ((polygonOffset p + i) `mod` pSize p)
triangle :: Polygon
triangle = mkPolygon $ V.fromList [V2 1 1, V2 0 0, V2 2 0]
triangle' :: [P]
triangle' = reverse [V2 1 1, V2 0 0, V2 2 0]
shape1 :: Polygon
shape1 = mkPolygon $ V.fromList
[ V2 0 0, V2 2 0
, V2 2 1, V2 2 2, V2 2 3, V2 2 4, V2 2 5, V2 2 6
, V2 1 1, V2 0 1 ]
shape2 :: Polygon
shape2 = mkPolygon $ V.fromList
[ V2 0 0, V2 1 0, V2 1 1, V2 2 1, V2 2 (-1), V2 0 (-1), V2 0 (-2)
, V2 3 (-2), V2 3 2, V2 0 2]
shape3 :: Polygon
shape3 = mkPolygon $ V.fromList
[ V2 0 0, V2 1 0, V2 1 1, V2 2 1, V2 2 2, V2 0 2]
shape4 :: Polygon
shape4 = mkPolygon $ V.fromList
[ V2 0 0, V2 1 0, V2 1 1, V2 2 1, V2 2 (-1), V2 3 (-1),V2 3 2, V2 0 2]
shape5 :: Polygon
shape5 = pCycles shape4 !! 2
shape6 :: Polygon
shape6 = mkPolygon $ V.fromList [ V2 0 0, V2 1 0, V2 1 1, V2 0 1 ]
shape7 :: Polygon
shape7 = pScale 6 $ mkPolygon $ V.fromList
[V2 ((-1567171105775771) % 144115188075855872) ((-7758063241391039) % 1152921504606846976)
,V2 ((-2711114907999263) % 18014398509481984) ((-3561889280168807) % 18014398509481984)
,V2 ((-6897139157863177) % 72057594037927936) ((-1632144794297397) % 4503599627370496)
,V2 (5592137945106423 % 36028797018963968) ((-71351641856107) % 281474976710656)
,V2 (2568147525079071 % 4503599627370496) ((-4312925637247687) % 18014398509481984)
,V2 (1291079014395023 % 2251799813685248) (321513444515769 % 2251799813685248)
,V2 (2071709221627247 % 4503599627370496) (4019115966736491 % 9007199254740992)
,V2 ((-1589087869859839) % 144115188075855872) (4904023654354179 % 9007199254740992)
,V2 ((-2328090886101149) % 36028797018963968) (2587887893460759 % 36028797018963968)
,V2 ((-7990199074159871) % 18014398509481984) (1301850651537745 % 4503599627370496)]
shape8 :: Polygon
shape8 = pScale 10 $ pGenerate
[(0.36,0.4),(0.7,1.8e-2),(0.7,0.2),(0.1,0.4),(0.2,0.2),(0.7,0.1),(0.4,8.0e-2)]
shape9 :: Polygon
shape9 = pScale 5 $ pGenerate
[(0.5,0.2),(0.7,0.6),(0.4,0.3),(0.1,0.7),(0.3,1.0e-2),(0.5,0.3),(0.2,0.8),(0.1,0.8),(0.7,6.0e-2),(0.1,0.6)]
shape10 :: Polygon
shape10 = pGenerate
[(0.4,0.7),(0.2,0.2),(0.3,0.9),(5.0e-2,0.1),(0.7,1.0e-2),(0.7,0.9),(0.2,0.1),(0.5,6.0e-2),(0.6,9.0e-2)]
shape11 :: Polygon
shape11 = pGenerate
[(0.1,0.8),(0.7,0.6),(0.7,0.4),(0.3,0.5),(0.8,0.9),(0.8,6.0e-2),(1.0e-2,4.0e-2),(0.8,0.1)]
shape12 :: Polygon
shape12 = mkPolygon $ V.fromList
[ V2 0 0, V2 0.5 1.5, V2 2 2, V2 (-2) 2, V2 (-0.5) 1.5 ]
shape13 :: Polygon
shape13 = pCycles (mkPolygon $ V.reverse (V.fromList
[ V2 0 0, V2 0 2
, V2 1 2, V2 1 1.7, V2 0.3 1.7, V2 0.3 1
, V2 1 1, V2 1 0.7
, V2 0.3 0.7, V2 0.3 0 ])) !! 7
shape14 :: Polygon
shape14 = pCycles (mkPolygon $ V.reverse $ V.fromList
[ V2 0 0, V2 0 2
, V2 1 2, V2 1 1.7, V2 0.3 1.7, V2 0.3 1
, V2 1 1, V2 1 0.7, V2 0.3 0.7, V2 0.3 0.3
, V2 1 0.3, V2 1 0
]) !! 9
shape15 :: Polygon
shape15 = mkPolygon $ V.fromList
[ V2 0 0, V2 2 0
, V2 2 2, V2 1 2
, V2 1 1, V2 0 1]
shape16 :: Polygon
shape16 = mkPolygon $ V.fromList
[ V2 0 0, V2 2 0
, V2 2 1, V2 1 1
, V2 1 2, V2 0 2]
shape17 :: Polygon
shape17 = mkPolygon $ V.fromList
[ V2 2 0, V2 2 1
, V2 1 1, V2 1 2
, V2 0 2, V2 0 1, V2 0 0 ]
shape18 :: Polygon
shape18 = mkPolygon $ V.fromList
[ V2 2 0, V2 2 1, V2 2 2
, V2 1 2, V2 1 1
, V2 0 1, V2 0 0 ]
shape19 :: Polygon
shape19 = mkPolygon $ V.fromList
[ V2 (-3) (-3), V2 0 (-1)
, V2 3 (-3), V2 1 0
, V2 3 3, V2 0 1
, V2 (-3) 3, V2 (-1) 0 ]
shape20 :: Polygon
shape20 = mkPolygon $ V.fromList
[ V2 (-3) (-3)
, V2 0 (-1)
, V2 3 (-3)
, V2 5 0
, V2 2.5 (-2)
, V2 1 0
, V2 3 3
, V2 0 1
, V2 (-3) 3
, V2 (-1) 0 ]
shape21 :: Polygon
shape21 = mkPolygon $ V.fromList
[V2 0.0 0.0,V2 1.0 0.0,V2 1.0 1.0,V2 2.0 1.0,V2 2.0 (-1.0),V2 3.0 (-1.0)
,V2 3.0 2.0,V2 0.0 2.0]
shape22 :: Polygon
shape22 = pScale 2 $ mkPolygon $ V.fromList
[V2 (-0.17) (-0.08)
,V2 (-0.34) (-0.21)
,V2 0.0 0.0
,V2 (-0.10) 0.60
,V2 (-0.14) 0.19
,V2 (-0.05) 0.03
]
shape23 :: Polygon
shape23 = mkPolygon $ V.fromList
[ V2 0 0, V2 4 0
, V2 4 3, V2 2 3
, V2 2 2, V2 3 2
, V2 3 1, V2 1 1
, V2 1 2, V2 2 2
, V2 2 3, V2 0 3 ]
concave :: Polygon
concave = mkPolygon $
V.fromList [V2 0 0, V2 2 0, V2 2 2, V2 1 1, V2 0 2]
pMkWinding :: Int -> Polygon
pMkWinding n | n < 1 = error "Polygon must have at least one winding."
pMkWinding n = mkPolygon $
V.fromList $ p0 : p1 : walkTo p1 1 n (V2 1 0) ++ reverse (walkTo p0 1 (n+2) (V2 (-1) 0))
where
p0 = V2 0 0
p1 = V2 0 1
walkTo at a b dir
| a == b = []
| otherwise =
let newAt = at + (dir ^* toRational a)
in newAt : walkTo newAt (a+1) b (rot dir)
rot (V2 x y) =
V2 y (-x)
pDeoverlap :: Polygon -> Polygon
pDeoverlap p = mkPolygon arr
where
arr = V.generate (pSize p) worker
worker 0 = pAccess p 0
worker n =
if length (V.elemIndices (pAccess p n) (polygonPoints p)) /= 1
then
let prev = arr V.! (n-1)
this = pAccess p n
in lerp 0.99999 this prev
else pAccess p n
pCycles :: APolygon a -> [APolygon a]
pCycles p = map (pAdjustOffset p) [0 .. pSize p-1]
pCycle :: PolyCtx a => APolygon a -> Double -> APolygon a
pCycle p 0 = p
pCycle p t = mkPolygon $ worker 0 0
where
worker acc i
| segment + acc > limit =
V.singleton (lerp (realToFrac $ (segment + acc - limit)/segment) x y) <>
V.fromList (map (pAccess p) [i+1..pSize p-1]) <>
V.fromList (map (pAccess p) [0 .. i])
| i == pSize p-1 = V.fromList (map (pAccess p) [0 .. pSize p-1])
| otherwise = worker (acc+segment) (i+1)
where
x = pAccess p i
y = pAccess p $ i+1
segment = distance' x y
len = pCircumference' p
limit = t * len
pCentroid :: Fractional a => APolygon a -> V2 a
pCentroid p = V2 cx cy
where
a = pArea p
cx = recip (6*a) * V.sum (pMapEdges fnX p)
cy = recip (6*a) * V.sum (pMapEdges fnY p)
fnX (V2 x y) (V2 x' y') = (x+x')*(x*y' - x'*y)
fnY (V2 x y) (V2 x' y') = (y+y')*(x*y' - x'*y)
{-# INLINE pMapEdges #-}
pMapEdges :: (V2 a -> V2 a -> b) -> APolygon a -> V.Vector b
pMapEdges fn p = V.generate n $ \i ->
if i == n-1
then fn (arr `V.unsafeIndex` i) (arr `V.unsafeIndex` 0)
else fn (arr `V.unsafeIndex` i) (arr `V.unsafeIndex` (i+1))
where
n = pSize p
arr = polygonPoints p
{-# SPECIALIZE pArea :: APolygon Double -> Double #-}
{-# SPECIALIZE pArea :: APolygon Rational -> Rational #-}
pArea :: (Fractional a) => APolygon a -> a
pArea p =
0.5 * worker 0 0
where
fn (V2 x y) (V2 x' y') = x*y' - x'*y
arr = polygonPoints p
worker !acc i
| i == pSize p - 1 = acc + fn (arr `V.unsafeIndex` i) (arr `V.unsafeIndex` 0)
| otherwise =
worker (acc + fn (arr `V.unsafeIndex` i) (arr `V.unsafeIndex` (i+1))) (i+1)
pCircumference :: (Real a, Fractional a) => APolygon a -> a
pCircumference p = sum
[ approxDist (pAccess p i) (pAccess p $ i+1)
| i <- [0 .. pSize p-1]]
pCircumference' :: (Real a, Fractional a) => APolygon a -> Double
pCircumference' p = sum
[ distance' (pAccess p i) (pAccess p $ i+1)
| i <- [0 .. pSize p-1]]
pAddPoints :: PolyCtx a => Int -> APolygon a -> APolygon a
pAddPoints = pAddPointsRestricted []
pAddPointsRestricted :: PolyCtx a => [(V2 a, V2 a)] -> Int -> APolygon a -> APolygon a
pAddPointsRestricted _immutableEdges n p | n <= 0 = p
pAddPointsRestricted immutableEdges n p = pAddPointsRestricted immutableEdges (n-1) $
mkPolygon $ V.fromList $ concatMap worker [0 .. pSize p-1]
where
isImmutable idx =
(pAccess p idx, pAccess p $ idx+1) `elem` immutableEdges ||
(pAccess p $ idx+1, pAccess p idx) `elem` immutableEdges
worker idx
| idx == longestEdge && not (isImmutable idx) =
[pAccess p idx, pMiddlePoint p idx]
| otherwise = [pAccess p idx]
longestEdge = maximumBy cmpLength [0 .. pSize p-1]
cmpLength a _ | isImmutable a = LT
cmpLength _ b | isImmutable b = GT
cmpLength a b =
distSquared (pAccess p a) (pAccess p $ a+1) `compare`
distSquared (pAccess p b) (pAccess p $ b+1)
pMiddlePoint :: PolyCtx a => APolygon a -> Int -> V2 a
pMiddlePoint p idx
= lerp 0.5 (pAccess p $ idx+1) (pAccess p idx)
pAddPointsBetween :: PolyCtx a => (Int, Int) -> Int -> APolygon a -> APolygon a
pAddPointsBetween _ n p | n <= 0 = p
pAddPointsBetween (i,l) n p = pAddPointsBetween (i,l+1) (n-1) $
mkPolygon $ V.fromList $ concatMap worker [0 .. pSize p-1]
where
worker idx
| idx == longestEdge =
[pAccess p idx, pMiddlePoint p idx]
| otherwise = [pAccess p idx]
longestEdge = maximumBy cmpLength [i .. i+l-1]
cmpLength a b =
distSquared (pAccess p a) (pAccess p $ a+1) `compare`
distSquared (pAccess p b) (pAccess p $ b+1)
pIsConvex :: Polygon -> Bool
pIsConvex p = and
[ area2X (pAccess p i) (pAccess p j) (pAccess p k) > 0
| i <- [0..n-1]
, j <- [i+1..n-1]
, k <- [j+1..n-1]
]
where n = pSize p
pIsCCW :: Polygon -> Bool
pIsCCW p | pNull p = False
pIsCCW p = V.sum (pMapEdges fn p) < 0
where
fn (V2 x1 y1) (V2 x2 y2) = (x2-x1)*(y2+y1)
{-# INLINE pRayIntersect #-}
pRayIntersect :: PolyCtx a => APolygon a -> (Int, Int) -> (Int,Int) -> Maybe (V2 a)
pRayIntersect p (a,b) (c,d) =
rayIntersect (pAccess p a, pAccess p b) (pAccess p c, pAccess p d)
pCuts :: (Real a, Fractional a, Epsilon a) => APolygon a -> [(APolygon a,APolygon a)]
pCuts p =
[ pCutAt (pAdjustOffset p i) (j-i)
| i <- [0 .. pSize p-1 ]
, j <- [i+2 .. pSize p-1 ]
, (j+1) `mod` pSize p /= i
, pParent p i j == i ]
pCutEqual :: PolyCtx a => APolygon a -> (APolygon a, APolygon a)
pCutEqual p =
fromMaybe (p,p) $ listToMaybe $ sortOn f $ pCuts p
where
f (a,b) = abs (pArea a - pArea b)
pCutAt :: PolyCtx a => APolygon a -> Int -> (APolygon a, APolygon a)
pCutAt p i = (mkPolygon $ V.fromList left, mkPolygon $ V.fromList right)
where
n = pSize p
left = map (pAccess p) [0 .. i]
right = map (pAccess p) (0:[i..n-1])
pOverlap :: PolyCtx a => APolygon a -> APolygon a -> APolygon a
pOverlap a b = mkPolygon $ V.fromList $ clearDups $ concatMap edgeIntersect [0 .. pSize a-1]
where
clearDups (x:y:xs)
| x == y = clearDups (y:xs)
| otherwise = x : clearDups (y:xs)
clearDups xs = xs
edgeIntersect edge =
sortOn (distSquared (pAccess a edge)) $ catMaybes
[ lineIntersect (aP, aP') (bP, bP')
| i <- [0 .. pSize b-1]
, let aP = pAccess a edge
aP' = pAccess a (edge+1)
bP = pAccess b i
bP' = pAccess b (i+1)
]
ssspVisibility :: PolyCtx a => APolygon a -> APolygon a
ssspVisibility p = mkPolygon $
V.fromList $ clearDups $ go [0 .. pSize p-1]
where
clearDups (x:y:xs)
| x == y = clearDups (y:xs)
| otherwise = x : clearDups (y:xs)
clearDups xs = xs
obstructedBy n =
case pParent p 0 n of
0 -> n
i -> obstructedBy i
go [] = []
go [x] = [pAccess p x]
go (x:y:xs) =
let xO = obstructedBy x
yO = obstructedBy y
in case () of
()
| xO == x && yO == y -> pAccess p x : go (y:xs)
| xO == x ->
pAccess p x : fromMaybe (pAccess p y) (pRayIntersect p (0,yO) (x,y)) : go (y:xs)
| yO == y -> fromMaybe (pAccess p x) (pRayIntersect p (0,xO) (x,y)) : pAccess p y : go (y:xs)
| xO == yO -> go (y:xs)
| otherwise ->
let a = fromMaybe (error "a") (pRayIntersect p (0,xO) (x,y))
b = fromMaybe (error "b") (pRayIntersect p (0,yO) (x,y))
in if a /= b
then a : b : go (y:xs)
else go (y:xs)
ssspWindows :: Polygon -> [(V2 Rational, V2 Rational)]
ssspWindows p = clearDups $ go (pAccess p 0) [0..pSize p-1]
where
clearDups (x:y:xs)
| x == y = clearDups (y:xs)
| otherwise = x : clearDups (y:xs)
clearDups xs = xs
obstructedBy n =
case pParent p 0 n of
0 -> n
i -> obstructedBy i
go _ [] = []
go _ [_] = []
go l (x:y:xs) =
let xO = obstructedBy x
yO = obstructedBy y
in case () of
()
| xO == x && yO == y -> go (pAccess p x) (y:xs)
| xO == x ->
go (fromMaybe (pAccess p y) (pRayIntersect p (0,yO) (x,y))) (y:xs)
| yO == y ->
let newL = fromMaybe (pAccess p x) (pRayIntersect p (0,xO) (x,y)) in
(l, newL) :
go newL (y:xs)
| xO == yO -> go l (y:xs)
| otherwise ->
let a = fromMaybe (error "a") (pRayIntersect p (0,xO) (x,y))
b = fromMaybe (error "b") (pRayIntersect p (0,yO) (x,y))
in if a /= b
then (l, a) : (b, pAccess p yO) : go (pAccess p yO) (y:xs)
else go l (y:xs)