packages feed

pure-cdt-0.2.0.0: src/Triangulation/Generator.hs

{-# LANGUAGE DerivingStrategies #-}

-- | Deterministic pseudo-random point sets.
module Triangulation.Generator (
  Rectangle (..),
  generatePoints,
  generatePointsWithDistance,
) where

import System.Random (mkStdGen, randoms)
import Triangulation.Geometry.Point (Point (..), manhattanDistance)

-- | An axis-aligned rectangle given by its two extreme corners.
data Rectangle = Rectangle
  { minCorner :: !Point
  -- ^ smallest x and y (lower-left with the y axis up)
  , maxCorner :: !Point
  -- ^ largest x and y (upper-right with the y axis up)
  }
  deriving stock (Eq, Show)

-- | @n@ pseudo-random points in the rectangle, determined by the seed.
generatePoints :: Int -> Int -> Rectangle -> [Point]
generatePoints seed n r = take n $ randomPointsInRectangle seed r

-- | @n@ random points in the rectangle, added to the existing points so that
-- all points are at least @distance@ apart (in 'manhattanDistance').
generatePointsWithDistance :: Int -> Int -> Double -> Rectangle -> [Point] -> [Point]
generatePointsWithDistance seed n distance r =
  addPointsWithDistance n distance (randomPointsInRectangle seed r)

-- | One stream of numbers, taken two at a time: @split@ is deprecated in
-- @random-1.3@ and its replacement does not exist in @random-1.2@.
randomPointsInRectangle :: Int -> Rectangle -> [Point]
randomPointsInRectangle seed r = pairUp (randoms (mkStdGen seed))
  where
    pairUp (x : y : rest) = Point (scaleX r x) (scaleY r y) : pairUp rest
    pairUp _ = []

addPointsWithDistance :: Int -> Double -> [Point] -> [Point] -> [Point]
addPointsWithDistance n distance = go n
  where
    go 0 _ out = out
    go _ [] out = out
    go k (c : cs) out
      | all ((>= distance) . manhattanDistance c) out = go (k - 1) cs (c : out)
      | otherwise = go k cs out

scaleX :: Rectangle -> Double -> Double
scaleX Rectangle {minCorner = Point x1 _, maxCorner = Point x2 _} x = x1 + x * (x2 - x1)

scaleY :: Rectangle -> Double -> Double
scaleY Rectangle {minCorner = Point _ y1, maxCorner = Point _ y2} y = y1 + y * (y2 - y1)