about summary refs log tree commit diff
path: root/src/Xanthous/Util/Graphics.hs
blob: bd6a0906a6d5e355041cdf1ab536a175f666b403 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
-- | Graphics algorithms and utils for rendering things in 2D space
--------------------------------------------------------------------------------
module Xanthous.Util.Graphics
  ( circle
  , filledCircle
  , line
  , straightLine
  , delaunay
  ) where
--------------------------------------------------------------------------------
import           Xanthous.Prelude
--------------------------------------------------------------------------------
import qualified Algorithms.Geometry.DelaunayTriangulation.DivideAndConquer
              as Geometry
import qualified Algorithms.Geometry.DelaunayTriangulation.Types as Geometry
import           Codec.Picture (imagePixels)
import qualified Data.Geometry.Point as Geometry
import           Data.Ext ((:+)(..))
import           Data.List (unfoldr)
import           Data.List.NonEmpty (NonEmpty)
import           Data.Ix (range, Ix)
import           Data.Word (Word8)
import qualified Graphics.Rasterific as Raster
import           Graphics.Rasterific hiding (circle, line, V2(..))
import           Graphics.Rasterific.Texture (uniformTexture)
import           Linear.V2
--------------------------------------------------------------------------------


circle :: (Num i, Integral i, Ix i)
       => (i, i) -- ^ center
       -> i      -- ^ radius
       -> [(i, i)]
circle (ox, oy) radius
  = pointsFromRaster (ox + radius) (oy + radius)
  $ stroke 1 JoinRound (CapRound, CapRound)
  $ Raster.circle (Raster.V2 (fromIntegral ox) (fromIntegral oy))
  $ fromIntegral radius

filledCircle :: (Num i, Integral i, Ix i)
             => (i, i) -- ^ center
             -> i      -- ^ radius
             -> [(i, i)]
filledCircle (ox, oy) radius
  = pointsFromRaster (ox + radius) (oy + radius)
  $ fill
  $ Raster.circle (Raster.V2 (fromIntegral ox) (fromIntegral oy))
  $ fromIntegral radius

-- showCells . fromPoints . NE.fromList $ filledCircle (15, 15) 7
-- pointsFromRaster :: (Num i, Integral i, Ix i)
--                  => i      -- ^ width
--                  -> i      -- ^ height
--                  -> _
--                  -> [(i, i)]
pointsFromRaster
  :: (Integral a, Integral b, Ix a, Ix b)
  => a
  -> b
  -> Drawing Word8 ()
  -> [(a, b)]
pointsFromRaster w h raster
  = map snd
  $ filter ((== 1) . fst)
  $ zip pixels
  $ range ((1, 1), (w, h))
  where
    pixels = toListOf imagePixels
           $ renderDrawing @Word8 (fromIntegral w) (fromIntegral h) 0
           $ withTexture (uniformTexture 1) raster



-- | Draw a line between two points using Bresenham's line drawing algorithm
--
-- Code taken from <https://wiki.haskell.org/Bresenham%27s_line_drawing_algorithm>
line :: (Num i, Ord i) => (i, i) -> (i, i) -> [(i, i)]
line pa@(xa, ya) pb@(xb, yb)
  = (if maySwitch pa < maySwitch pb then id else reverse) points
  where
    points               = map maySwitch . unfoldr go $ (x₁, y₁, 0)
    steep                = abs (yb - ya) > abs (xb - xa)
    maySwitch            = if steep then swap else id
    [(x₁, y₁), (x₂, y₂)] = sort [maySwitch pa, maySwitch pb]
    δx                   = x₂ - x₁
    δy                   = abs (y₂ - y₁)
    ystep                = if y₁ < y₂ then 1 else -1
    go (xTemp, yTemp, err)
      | xTemp > x₂ = Nothing
      | otherwise  = Just ((xTemp, yTemp), (xTemp + 1, newY, newError))
      where
        tempError        = err + δy
        (newY, newError) = if (2 * tempError) >= δx
                           then (yTemp + ystep, tempError - δx)
                           else (yTemp, tempError)

straightLine :: (Num i, Ord i) => (i, i) -> (i, i) -> [(i, i)]
straightLine pa@(xa, _) pb@(_, yb) = line pa midpoint ++ line midpoint pb
  where midpoint = (xa, yb)


delaunay
  :: (Ord n, Fractional n)
  => NonEmpty (V2 n, p)
  -> [((V2 n, p), (V2 n, p))]
delaunay
  = map (over both fromPoint)
  . Geometry.triangulationEdges
  . Geometry.delaunayTriangulation
  . map toPoint
  where
    toPoint (V2 px py, pid) = Geometry.Point2 px py :+ pid
    fromPoint (Geometry.Point2 px py :+ pid) = (V2 px py, pid)