import Data.Ratio type Point = (Rational, Rational) type Polygon = [Point] data Line = Sloped {lineSlope, lineYIntercept :: Rational} | Vert {lineXIntercept :: Rational} polygonSides :: Polygon -> [(Point, Point)] polygonSides poly@(p1 : ps) = zip poly $ ps ++ [p1] intersects :: Point -> Line -> Bool {- @intersects (px, py) l@ is true if the ray {(x, py) | x ≥ px} intersects l. -} intersects (px, _) (Vert xint) = px <= xint intersects (px, py) (Sloped m b) | m < 0 = py <= m * px + b | otherwise = py >= m * px + b onLine :: Point -> Line -> Bool {- Is the point on the line? -} onLine (px, _) (Vert xint) = px == xint onLine (px, py) (Sloped m b) = py == m * px + b carrier :: (Point, Point) -> Line {- Finds the line containing the given line segment. -} carrier ((ax, ay), (bx, by)) | ax == bx = Vert ax | otherwise = Sloped slope yint where slope = (ay - by) / (ax - bx) yint = ay - slope * ax between :: Ord a => a -> a -> a -> Bool between x a b | a > b = b <= x && x <= a | otherwise = a <= x && x <= b inPolygon :: Point -> Polygon -> Bool inPolygon p@(px, py) = f 0 . polygonSides where f n [] = odd n f n (side : sides) | far = f n sides | onSegment = True | rayIntersects = f (n + 1) sides | otherwise = f n sides where far = not $ between py ay by onSegment | ay == by = between px ax bx | otherwise = p `onLine` line rayIntersects = intersects p line && (py /= ay || by < py) && (py /= by || ay < py) ((ax, ay), (bx, by)) = side line = carrier side