hgeometry-0.12.0.0: src/Data/Geometry/Polygon/Inflate.hs
--------------------------------------------------------------------------------
-- |
-- Module : Data.Geometry.Polygon.Inflate
-- Copyright : (C) David Himmelstrup
-- License : see the LICENSE file
-- Maintainer : David Himmelstrup
--------------------------------------------------------------------------------
module Data.Geometry.Polygon.Inflate
( Arc(..)
, inflate
) where
import Algorithms.Geometry.SSSP (SSSP, sssp, triangulate)
import Control.Lens
import Data.Ext
import Data.Geometry.Line (lineThrough)
import Data.Geometry.LineSegment (LineSegment (LineSegment, OpenLineSegment),
interpolate, sqSegmentLength)
import Data.Geometry.Point
import Data.Geometry.Polygon.Core
import Data.Intersection (IsIntersectableWith (intersect),
NoIntersection (NoIntersection))
import Data.Maybe (catMaybes)
import qualified Data.Vector as V
import qualified Data.Vector.Circular as CV
import qualified Data.Vector.Unboxed as VU
import Data.Vinyl (Rec (RNil, (:&)))
import Data.Vinyl.CoRec (Handler (H), match)
----------------------------------------------------
-- Implementation
-- | Points annotated with an 'Arc' indicate that the edge from this point to
-- the next should not be a straight line but instead an arc with a given center
-- and a given border edge.
data Arc r = Arc
{ arcCenter :: Point 2 r
, arcEdge :: (Point 2 r, Point 2 r)
} deriving (Show)
type Parent = Int
markParents :: SSSP -> SimplePolygon p r -> SimplePolygon Parent r
markParents t p = unsafeFromCircularVector $
CV.imap (\i (pt :+ _) -> pt :+ t VU.! i) (p^.outerBoundaryVector)
addSteinerPoints :: (Ord r, Fractional r) => SimplePolygon Parent r -> SimplePolygon Parent r
addSteinerPoints p = fromPoints $ concatMap worker [0 .. size p - 1]
where
worker nth = do
pointA : catMaybes [ (:+ parent nth) <$> getIntersection edge lineA
, (:+ parent (nth+1)) <$> getIntersection edge lineB ]
where
fetch idx = p ^. outerVertex idx
pointA = fetch nth
pointB = fetch (nth+1)
parent idx = p^.outerVertex idx.extra
lineA = lineThrough
(fetch (parent nth) ^. core)
(fetch (parent (parent nth)) ^. core)
lineB = lineThrough
(fetch (parent (nth+1)) ^. core)
(fetch (parent (parent (nth+1))) ^. core)
edge = OpenLineSegment pointA pointB
getIntersection segment line =
match (segment `intersect` line) (
H (\NoIntersection -> Nothing)
:& H (\pt -> Just pt)
:& H (\LineSegment{} -> Nothing)
:& RNil
)
annotate :: (Real r, Fractional r) =>
Double -> SimplePolygon Parent r -> SimplePolygon Parent r -> SimplePolygon (Arc r) r
annotate t original p = unsafeFromCircularVector $
CV.imap ann (p^.outerBoundaryVector)
-- CV.generate (size p) ann -- Use this when circular-vector-0.1.2 is out.
where
nO = size original
visibleDist = V.maximum distanceTreeSum * t
parent idx = p^.outerVertex idx.extra
parentO idx = original^.outerVertex idx.extra
getLineO idx = OpenLineSegment (original ^. outerVertex (parentO idx)) (original ^. outerVertex idx)
getLineP idx = OpenLineSegment (original ^. outerVertex (parent idx)) (p ^. outerVertex idx)
ann i _ =
ptLocation i :+ arc
where
start = p ^. outerVertex i . core
end = p ^. outerVertex (i+1) . core
arc = Arc
{ arcCenter =
original ^. outerVertex (commonParent original (parent i) (parent (i+1))) . core
, arcEdge = (start, end) }
-- Array of locations for points in the original polygon.
ptLocationsO = V.generate nO ptLocationO
ptLocationO 0 = (original ^. outerVertex 0 . core)
ptLocationO i
| frac <= 0 = ptLocationsO V.! (parentO i)
| frac >= 1 = (original ^. outerVertex i . core)
| otherwise = (interpolate frac (getLineO i))
where
dParent = distanceTreeSum V.! parentO i
dSelf = oDistance VU.! i
frac = realToFrac ((visibleDist - dParent) / dSelf)
-- Locations for original points and steiner points.
ptLocation 0 = (p ^. outerVertex 0 . core)
ptLocation i
| frac <= 0 = ptLocationsO V.! (parent i)
| frac >= 1 = (p ^. outerVertex i . core)
| otherwise = (interpolate frac (getLineP i))
where
dParent = distanceTreeSum V.! parent i
dSelf = sqrt $ realToFrac $ sqSegmentLength $ getLineP i
frac = realToFrac ((visibleDist - dParent) / dSelf)
oDistance = VU.generate nO $ \i ->
case i of
0 -> 0
_ -> sqrt $ realToFrac $ sqSegmentLength $ getLineO i
distanceTreeSum = V.generate nO $ \i ->
case i of
0 -> 0
_ -> distanceTreeSum V.! parentO i + oDistance VU.! i
commonParent :: SimplePolygon Parent r -> Int -> Int -> Int
commonParent p a b = worker 0 (parents a) (parents b)
where
worker _shared (x:xs) (y:ys)
| x == y = worker x xs ys
worker shared _ _ = shared
parents 0 = [0]
parents i = parents (p ^. outerVertex i . extra) ++ [i]
-- | \( O(n \log n) \)
inflate :: (Real r, Fractional r) => Double -> SimplePolygon () r -> SimplePolygon (Arc r) r
inflate t p = annotate t marked steiner
where
marked = markParents (sssp (triangulate p)) p
steiner = addSteinerPoints marked