aboutsummaryrefslogtreecommitdiff
path: root/src/Rahm/Desktop/Geometry.hs
blob: 5048205b8bcc269717776b090bc023fbaa6aecf4 (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
114
115
116
117
118
119
120
module Rahm.Desktop.Geometry where

import Data.List (minimumBy, intercalate)
import Data.Maybe (listToMaybe)
import Data.Ord (comparing)
import Debug.Trace (trace, traceM)
import Text.Printf (printf)

data GeomRectangle = GeomRectangle
  { rectX, rectY, rectWidth, rectHeight :: Double
  }
  deriving (Show, Eq)

data Direction = N | S | E | W
  deriving (Show, Eq, Enum, Bounded)

center :: GeomRectangle -> (Double, Double)
center r = (rectX r + rectWidth r / 2, rectY r + rectHeight r / 2)

-- Distance from reference center to the closest point on candidate in the given direction
-- Assumes candidate is wholly in the given direction (verified elsewhere)
distanceToEdgeInDirection :: Direction -> GeomRectangle -> GeomRectangle -> Double
distanceToEdgeInDirection dir ref cand =
  let (rx, ry) = center ref
      left   = rectX cand
      right  = rectX cand + rectWidth cand
      top    = rectY cand
      bottom = rectY cand + rectHeight cand

      dxToSpan
        | rx < left = left - rx
        | rx > right = rx - right
        | otherwise = 0

      dyToSpan
        | ry < top = top - ry
        | ry > bottom = ry - bottom
        | otherwise = 0

  in case dir of
       N -> -- candidate above: nearest point is on its bottom edge
            let dx = dxToSpan
                dy = ry - bottom
            in sqrt (dx*dx + dy*dy)

       S -> -- candidate below: nearest point is on its top edge
            let dx = dxToSpan
                dy = top - ry
            in sqrt (dx*dx + dy*dy)

       W -> -- candidate left: nearest point is on its right edge
            let dy = dyToSpan
                dx = rx - right
            in sqrt (dx*dx + dy*dy)

       E -> -- candidate right: nearest point is on its left edge
            let dy = dyToSpan
                dx = left - rx
            in sqrt (dx*dx + dy*dy)


boundingBox :: [GeomRectangle] -> (Double, Double, Double, Double)
boundingBox rs =
  let xs = map rectX rs
      ys = map rectY rs
      xMaxes = zipWith (+) (map rectX rs) (map rectWidth rs)
      yMaxes = zipWith (+) (map rectY rs) (map rectHeight rs)
   in (minimum xs, minimum ys, maximum xMaxes - minimum xs, maximum yMaxes - minimum ys)

translate :: GeomRectangle -> Double -> Double -> GeomRectangle
translate r dx dy =
  GeomRectangle (rectX r + dx) (rectY r + dy) (rectWidth r) (rectHeight r)

-- "Wholly in direction": candidate is completely in direction, with no overlap
whollyInDirection :: Direction -> GeomRectangle -> GeomRectangle -> Bool
whollyInDirection N r1 r2 = bottomEdge r1 <= topEdge r2
whollyInDirection S r1 r2 = topEdge r1 >= bottomEdge r2
whollyInDirection E r1 r2 = leftEdge r1 >= rightEdge r2
whollyInDirection W r1 r2 = rightEdge r1 <= leftEdge r2

topEdge, bottomEdge, leftEdge, rightEdge :: GeomRectangle -> Double
topEdge r = rectY r
bottomEdge r = rectY r + rectHeight r
leftEdge r = rectX r
rightEdge r = rectX r + rectWidth r

-- Generate up to 2 tile copies: original + one tile in direction
tileCandidates :: Direction -> Double -> Double -> GeomRectangle -> [GeomRectangle]
tileCandidates dir sw sh r =
  case dir of
    N -> [r, translate r 0 (-sh)]
    S -> [r, translate r 0 sh]
    W -> [r, translate r (-sw) 0]
    E -> [r, translate r sw 0]

closestInDirection ::
  (Eq a, Show a) =>
  [(GeomRectangle, a)] ->
  a ->
  Direction ->
  Maybe (GeomRectangle, a)
closestInDirection rects tag dir = do
  ref <- listToMaybe [r | (r, t) <- rects, t == tag]
  let allRects = map fst rects
      (_, _, sw, sh) = boundingBox allRects
      sw' = if sw == 0 then 1 else sw
      sh' = if sh == 0 then 1 else sh

  let candidates =
        [ (r, t, distanceToEdgeInDirection dir ref virt)
          | (r, t) <- rects,
            t /= tag,
            virt <- tileCandidates dir sw' sh' r,
            whollyInDirection dir virt ref
        ]
  case candidates of
    [] -> Nothing
    _ -> Just (r, t)
      where
        (r, t, _) = minimumBy (comparing (\(_, _, d) -> d)) candidates