packages feed

qhull-0.1.0.1: src/Voronoi2D.hs

module Voronoi2D
  (Edge2(..)
 , Cell2
 , Voronoi2
 , prettyShowVoronoi2
 , voronoiCell2
 , voronoi2
 , clipVoronoi2
 , boundedCell2
 , restrictVoronoi2
 , cell2Vertices
 , cell2Vertices'
 , module Voronoi.Shared)
  where
import           Control.Arrow    (second)
import           Data.Graph       (flattenSCCs, stronglyConnComp)
import           Data.List
import           Data.List.Index  (imap)
import           Data.Tuple.Extra (both)
import           Delaunay.Types
import           Qhull.Types
import           Text.Show.Pretty (ppShow)
import           Voronoi.Shared
import           Voronoi.Voronoi

type Point2 = (Double, Double)
type Vector2 = (Double, Double)
data Edge2 = Edge2 (Point2, Point2) | IEdge2 (Point2, Vector2)
             | TIEdge2 (Point2, Point2)
              deriving (Show, Eq)
type Cell2 = [Edge2]
type Voronoi2 = [([Double], Cell2)]
type Box2 = ((Double, Double), (Double, Double))

-- | pretty print a Voronoi 2D diagram
prettyShowVoronoi2 :: Voronoi2 -> Maybe Int -> IO ()
prettyShowVoronoi2 v m = do
  let string = intercalate "\n---\n" (map (prettyShowCell2 m) v)
  putStrLn string
  where
    approx :: RealFrac a => Int -> a -> a
    approx n x = fromInteger (round $ x * (10^n)) / (10.0^^n)
    roundPairPoint2 :: (Point2, Point2) -> Int -> (Point2, Point2)
    roundPairPoint2 ((x1,x2), (y1,y2)) n =
      (asPair $ map (approx n) [x1,x2], asPair $ map (approx n) [y1,y2])
    prettyShowEdge2 :: Maybe Int -> Edge2 -> String
    prettyShowEdge2 n edge = case edge of
      Edge2 x   -> " Edge " ++ string x
      IEdge2 x  -> " IEdge " ++ string x
      TIEdge2 x -> " TIEdge " ++ string x
      where
        string x = ppShow $ maybe x (roundPairPoint2 x) n
    prettyShowEdges2 :: Maybe Int -> [Edge2] -> String
    prettyShowEdges2 n edges = intercalate "\n" (map (prettyShowEdge2 n) edges)
    prettyShowCell2 :: Maybe Int -> ([Double], Cell2) -> String
    prettyShowCell2 n (site, edges) =
      "Site " ++ ppShow site ++ " :\n" ++ prettyShowEdges2 n edges

asPair :: [Double] -> (Double, Double)
asPair [a,b] = (a,b)
asPair _     = (undefined, undefined)

edgeToEdge2 :: Edge -> Edge2
edgeToEdge2 (Edge (x, y))  = Edge2 (both asPair (x, y))
edgeToEdge2 (IEdge (x, v)) = IEdge2 (both asPair (x, v))

-- | Voronoi cell of a vertex given by its index
voronoiCell2 :: Tesselation -> Index -> Cell2
voronoiCell2 = voronoiCell id edgeToEdge2

-- | 2D Voronoi Diagram
voronoi2 :: Tesselation -> Voronoi2
voronoi2 = voronoi voronoiCell2

-- | whether a 2D Voronoi cell is bounded
boundedCell2 :: Cell2 -> Bool
boundedCell2 = all isFiniteEdge
  where
    isFiniteEdge (Edge2 _) = True
    isFiniteEdge _         = False

-- | restrict a 2D Voronoi diagram to its bounded cells
restrictVoronoi2 :: Voronoi2 -> Voronoi2
restrictVoronoi2 = filterVoronoi boundedCell2

-- | vertices of a bounded 2D cell
cell2Vertices :: Cell2 -> [[Double]]
cell2Vertices cell = nub $ concatMap extractVertices cell
  where
    extractVertices :: Edge2 -> [[Double]]
    extractVertices (Edge2 ((x1,x2),(y1,y2))) = [[x1,x2],[y1,y2]]
    extractVertices _                         = []

-- | ordered vertices of a bounded 2D cell
cell2Vertices' :: Cell2 -> [[Double]]
cell2Vertices' cell = flattenSCCs (stronglyConnComp x)
  where
    vs = cell2Vertices cell
    x = imap (\i v -> (v, i, findIndices (connectedVertices v) vs)) vs
    connectedVertices :: [Double] -> [Double] -> Bool
    connectedVertices [x1,x2] [y1,y2] =
      (Edge2 ((x1,x2),(y1,y2)) `elem` cell) ||
      (Edge2 ((y1,y2),(x1,x2)) `elem` cell)
    connectedVertices _ _ = False

truncEdge2 :: Box2 -> Edge2 -> Edge2
truncEdge2 ((xmin, xmax), (ymin, ymax)) edge =
  if isIEdge edge
    then TIEdge2 (p, (p1 + factor v1 v2 * v1, p2 + factor v1 v2 * v2))
    else edge
  where
    isIEdge (IEdge2 _) = True
    isIEdge _          = False
    IEdge2 (p@(p1,p2), (v1,v2)) = edge
    factor w1 w2 | w1==0 = if w2>0 then (ymax-p2)/w2 else (ymin-p2)/w2
                 | w2==0 = if w1>0 then (xmax-p1)/w1 else (xmin-p1)/w1
                 | otherwise = min (factor w1 0) (factor 0 w2)
    -- factor = factor2 box p v

-- | clip a 2D Voronoi diagram in a bounding box
clipVoronoi2 :: Box2 -> Voronoi2 -> Voronoi2
clipVoronoi2 box = map (second (map (truncEdge2 box)))