File: Line.hs

package info (click to toggle)
haskell-gloss 1.13.2.2-4
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 344 kB
  • sloc: haskell: 2,903; makefile: 2
file content (325 lines) | stat: -rw-r--r-- 9,011 bytes parent folder | download | duplicates (5)
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
{-# LANGUAGE PatternGuards #-}

-- | Geometric functions concerning lines and segments.
--
--   A @Line@ is taken to be infinite in length, while a @Seg@ is finite length
--   line segment represented by its two endpoints.
module Graphics.Gloss.Geometry.Line
        ( segClearsBox

        -- * Closest points
        , closestPointOnLine
        , closestPointOnLineParam

        -- * Line-Line intersection
        , intersectLineLine

        -- * Seg-Line intersection
        , intersectSegLine
        , intersectSegHorzLine
        , intersectSegVertLine

        -- * Seg-Seg intersection
        , intersectSegSeg
        , intersectSegHorzSeg
        , intersectSegVertSeg)

where
import Graphics.Gloss.Data.Point
import Graphics.Gloss.Data.Vector
import qualified Graphics.Gloss.Data.Point.Arithmetic as Pt

-- | Check if line segment (P1-P2) clears a box (P3-P4) by being well outside it.
segClearsBox
        :: Point        -- ^ P1 First point of segment.
        -> Point        -- ^ P2 Second point of segment.
        -> Point        -- ^ P3 Lower left point of box.
        -> Point        -- ^ P4 Upper right point of box.
        -> Bool

segClearsBox (x1, y1) (x2, y2) (xa, ya) (xb, yb)
        | x1 < xa, x2 < xa      = True
        | x1 > xb, x2 > xb      = True
        | y1 < ya, y2 < ya      = True
        | y1 > yb, y2 > yb      = True
        | otherwise             = False


-- | Given an infinite line which intersects `P1` and `P1`,
--      return the point on that line that is closest to `P3`
closestPointOnLine
        :: Point        -- ^ `P1`
        -> Point        -- ^ `P2`
        -> Point        -- ^ `P3`
        -> Point        -- ^ the point on the line P1-P2 that is closest to `P3`

{-# INLINE closestPointOnLine #-}

closestPointOnLine p1 p2 p3
        = p1 Pt.+ (u `mulSV` (p2 Pt.- p1))
        where   u       = closestPointOnLineParam p1 p2 p3


-- | Given an infinite line which intersects P1 and P2,
--      let P4 be the point on the line that is closest to P3.
--
--      Return an indication of where on the line P4 is relative to P1 and P2.
--
-- @
--      if P4 == P1 then 0
--      if P4 == P2 then 1
--      if P4 is halfway between P1 and P2 then 0.5
-- @
--
-- @
--        |
--       P1
--        |
--     P4 +---- P3
--        |
--       P2
--        |
-- @
--
{-# INLINE closestPointOnLineParam #-}
closestPointOnLineParam
        :: Point        -- ^ `P1`
        -> Point        -- ^ `P2`
        -> Point        -- ^ `P3`
        -> Float

closestPointOnLineParam p1 p2 p3
        = (p3 Pt.- p1) `dotV` (p2 Pt.- p1)
        / (p2 Pt.- p1) `dotV` (p2 Pt.- p1)



-- Line-Line intersection -----------------------------------------------------

-- | Given four points specifying two lines, get the point where the two lines
--   cross, if any. Note that the lines extend off to infinity, so the
--   intersection point might not line between either of the two pairs of points.
--
-- @
--     \\      /
--      P1  P4
--       \\ /
--        +
--       / \\
--      P3  P2
--     /     \\
-- @
--
intersectLineLine
        :: Point        -- ^ `P1`
        -> Point        -- ^ `P2`
        -> Point        -- ^ `P3`
        -> Point        -- ^ `P4`
        -> Maybe Point

intersectLineLine (x1, y1) (x2, y2) (x3, y3) (x4, y4)
 = let  dx12    = x1 - x2
        dx34    = x3 - x4

        dy12    = y1 - y2
        dy34    = y3 - y4

        den     = dx12 * dy34  - dy12 * dx34

   in if den == 0
        then Nothing
        else let
                det12   = x1*y2 - y1*x2
                det34   = x3*y4 - y3*x4

                numx    = det12 * dx34 - dx12 * det34
                numy    = det12 * dy34 - dy12 * det34
             in Just (numx / den, numy / den)


-- Segment-Line intersection --------------------------------------------------
-- | Get the point where a segment @P1-P2@ crosses an infinite line @P3-P4@,
--   if any.
--
intersectSegLine
        :: Point        -- ^ `P1`
        -> Point        -- ^ `P2`
        -> Point        -- ^ `P3`
        -> Point        -- ^ `P4`
        -> Maybe Point

intersectSegLine p1 p2 p3 p4
        -- TODO: merge closest point check with intersection, reuse subterms.
        | Just p0       <- intersectLineLine p1 p2 p3 p4
        , t12           <- closestPointOnLineParam p1 p2 p0
        , t12 >= 0 && t12 <= 1
        = Just p0

        | otherwise
        = Nothing


-- | Get the point where a segment crosses a horizontal line, if any.
--
-- @
--                + P1
--               /
--       -------+---------
--             /        y0
--         P2 +
-- @
--
intersectSegHorzLine
        :: Point        -- ^ P1 First point of segment.
        -> Point        -- ^ P2 Second point of segment.
        -> Float        -- ^ y value of line.
        -> Maybe Point
intersectSegHorzLine (x1, y1) (x2, y2) y0

        -- seg is on line
        | y1 == y0, y2 == y0    = Nothing

        -- seg is above line
        | y1 > y0,  y2 > y0     = Nothing

        -- seg is below line
        | y1 < y0,  y2 < y0     = Nothing

        -- seg is a single point on the line.
        -- this should be caught by the first case,
        -- but we'll test for it anyway.
        | y2 - y1 == 0
        = Just (x1, y1)

        | otherwise
        = Just ( (y0 - y1) * (x2 - x1) / (y2 - y1) + x1
               , y0)



-- | Get the point where a segment crosses a vertical line, if any.
--
-- @
--              |
--              |   + P1
--              | /
--              +
--            / |
--       P2 +   |
--              | x0
-- @
--
intersectSegVertLine
        :: Point        -- ^ P1 First point of segment.
        -> Point        -- ^ P2 Second point of segment.
        -> Float        -- ^ x value of line.
        -> Maybe Point

intersectSegVertLine (x1, y1) (x2, y2) x0

        -- seg is on line
        | x1 == x0, x2 == x0    = Nothing

        -- seg is to right of line
        | x1 > x0,  x2 > x0     = Nothing

        -- seg is to left of line
        | x1 < x0,  x2 < x0     = Nothing

        -- seg is a single point on the line.
        -- this should be caught by the first case,
        -- but we'll test for it anyway.
        | x2 - x1 == 0
        = Just (x1, y1)

        | otherwise
        = Just (  x0
               , (x0 - x1) * (y2 - y1) / (x2 - x1) + y1)


-- Segment-Segment intersection -----------------------------------------------

-- | Get the point where a segment @P1-P2@ crosses another segement @P3-P4@,
--   if any.
intersectSegSeg
        :: Point        -- ^ `P1`
        -> Point        -- ^ `P2`
        -> Point        -- ^ `P3`
        -> Point        -- ^ `P4`
        -> Maybe Point

intersectSegSeg p1 p2 p3 p4
        -- TODO: merge closest point checks with intersection, reuse subterms.
        | Just p0       <- intersectLineLine p1 p2 p3 p4
        , t12           <- closestPointOnLineParam p1 p2 p0
        , t23           <- closestPointOnLineParam p3 p4 p0
        , t12 >= 0 && t12 <= 1
        , t23 >= 0 && t23 <= 1
        = Just p0

        | otherwise
        = Nothing


-- | Check if an arbitrary segment intersects a horizontal segment.
--
-- @
--                 + P2
--                /
-- (xa, y3)  +---+----+ (xb, y3)
--              /
--          P1 +
-- @

intersectSegHorzSeg
        :: Point        -- ^ P1 First point of segment.
        -> Point        -- ^ P2 Second point of segment.
        -> Float        -- ^ (y3) y value of horizontal segment.
        -> Float        -- ^ (xa) Leftmost x value of horizontal segment.
        -> Float        -- ^ (xb) Rightmost x value of horizontal segment.
        -> Maybe Point  -- ^ (x3, y3) Intersection point, if any.

intersectSegHorzSeg p1@(x1, y1) p2@(x2, y2) y0 xa xb
        | segClearsBox p1 p2 (xa, y0) (xb, y0)
        = Nothing

        | x0 < xa       = Nothing
        | x0 > xb       = Nothing
        | otherwise     = Just (x0, y0)

        where x0 | (y2 - y1) == 0 = x1
                 | otherwise      = (y0 - y1) * (x2 - x1) / (y2 - y1) + x1


-- | Check if an arbitrary segment intersects a vertical segment.
--
-- @
--      (x3, yb) +
--               |   + P1
--               | /
--               +
--             / |
--        P2 +   |
--               + (x3, ya)
-- @

intersectSegVertSeg
        :: Point        -- ^ P1 First point of segment.
        -> Point        -- ^ P2 Second point of segment.
        -> Float        -- ^ (x3) x value of vertical segment
        -> Float        -- ^ (ya) Lowest y value of vertical segment.
        -> Float        -- ^ (yb) Highest y value of vertical segment.
        -> Maybe Point  -- ^ (x3, y3) Intersection point, if any.

intersectSegVertSeg p1@(x1, y1) p2@(x2, y2) x0 ya yb
        | segClearsBox p1 p2 (x0, ya) (x0, yb)
        = Nothing

        | y0 < ya       = Nothing
        | y0 > yb       = Nothing
        | otherwise     = Just (x0, y0)

        where y0 | (x2 - x1) == 0 = y1
                 | otherwise      = (x0 - x1) * (y2 - y1) / (x2 - x1) + y1