-- | -- Module : Cartesian.Plane -- Description : -- Copyright : (c) Jonatan H Sundqvist, year -- License : MIT -- Maintainer : Jonatan H Sundqvist -- Stability : experimental|stable -- Portability : POSIX (not sure) -- -- Created date year -- TODO | - Which constraints are appropriate (Num is probably too generic, should be Real, maybe RealFrac) -- - Strictness, performance -- SPEC | - -- - -------------------------------------------------------------------------------------------------------------------------------------------- -- API -------------------------------------------------------------------------------------------------------------------------------------------- module Cartesian.Plane where -------------------------------------------------------------------------------------------------------------------------------------------- -- We'll need these -------------------------------------------------------------------------------------------------------------------------------------------- import Data.List (sort, minimumBy) import Data.Ord (comparing) import Data.Complex hiding (magnitude) import qualified Control.Lens as L -- import Southpaw.Utilities.Utilities (pairwise) -------------------------------------------------------------------------------------------------------------------------------------------- -- Types -------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------- -- | -- TODO: Rename (?) data Vector num = Vector num num deriving (Eq, Show) -- TODO: Constraints on argument types (cf. GADT) (?) -- | data Line num = Line (Vector num) (Vector num) -- | -- TODO: Rename (eg. 'Shape') (?) type Polygon num = [Vector num] -- | data Linear num = Linear { intercept :: num, slope :: num } -------------------------------------------------------------------------------------------------------------------------------------------- -- | -- type Domain -- | Determines if a point lies within a polygon using the odd-even method. -- -- TODO: Use epsilon (?) -- TODO: How to treat points that lie on an edge inside :: Num n => Polygon n -> Vector n -> Bool inside (p:olygon) (Vector x y) = undefined where lines = (p:olygon)++[p] -- Close the loop -- between (Line (Vector ax ay) (Vector bx by)) = _ -------------------------------------------------------------------------------------------------------------------------------------------- -- Instances -------------------------------------------------------------------------------------------------------------------------------------------- -- | abs v * signum v == v instance (Floating a, Eq a) => Num (Vector a) where -- TODO: Helper method to reduce boilerplate for component-wise operations (+) = dotwise (+) (-) = dotwise (-) (*) = dotwise (*) -- TODO: Is this really correct? fromInteger n = Vector (fromInteger n) 0 signum (Vector 0 0) = Vector 0 0 signum v@(Vector x y) = Vector (x/mag v) (y/mag v) abs a = Vector (euclidean a a) 0 -------------------------------------------------------------------------------------------------------------------------------------------- -- Functions -------------------------------------------------------------------------------------------------------------------------------------------- -- Vector math ------------------------------------------------------------------------------------ -- | Performs component-wise operations dotwise :: (a -> b -> c) -> Vector a -> Vector b -> Vector c dotwise f (Vector x y) (Vector x' y') = Vector (f x x') (f y y') -- | Dot product of two vectors dot :: Floating a => Vector a -> Vector a -> a dot (Vector x y) (Vector x' y') = (x * x') + (y * y') -- TODO: Refactor with Num instance (?) -- | Euclidean distance between two points euclidean :: Floating a => Vector a -> Vector a -> a euclidean a b = sqrt $ dot a b -- | magnitude :: (Floating a, Eq a) => Vector a -> a magnitude v = euclidean v v mag :: (Floating a, Eq a) => Vector a -> a mag = magnitude -- | Angle (in radians) between the positive X-axis and the vector argument :: (Floating a, Eq a) => Vector a -> a argument (Vector 0 0) = 0 argument (Vector x y) = atan $ y/x arg :: (Floating a, Eq a) => Vector a -> a arg = argument -- | Vector -> (magnitude, argument) polar :: (Floating a, Eq a) => Vector a -> (a, a) polar v@(Vector x y) = (magnitude v, argument v) -- Geometry --------------------------------------------------------------------------------------- -- | Yields the point at which two finite lines intersect. The lines are defined inclusively by -- their endpoints. The result is wrapped in a Maybe value to account for non-intersecting -- lines. -- -- TODO: Move equation solving to separate function (two linear functions) -- TODO: Simplify logic by considering f(x) = y for vertical lines (?) -- TODO: Return Either instead of Maybe (eg. Left "parallel") (?) -- -- TODO: Math notes, MathJax or LaTex -- TODO: Intersect for curves (functions) and single points (?) -- TODO: Polymorphic, typeclass (lines, shapes, ranges, etc.) (?) -- intersect :: RealFrac n => Line n -> Line n -> Maybe (Vector n) intersect a b | (fst $ deltas a) == 0 = Just $ error "Not implemented" | (fst $ deltas b) == 0 = Just $ error "Not implemented" | slope a == slope b = Nothing | otherwise = Nothing where deltas (Line (Vector ax ay) (Vector bx by)) = (bx - ax, by - ay) -- TODO: Rename (eg. deltas) (?) vertical (Line (Vector ax _) (Vector bx _)) = ax == bx slope line = let (dx, dy) = deltas line in dy/dx intercept line@(Line (Vector x y) _) | vertical line = Nothing | otherwise = Just $ y - slope line * x -- Geometry --------------------------------------------------------------------------------------- -- | -- inside :: (Num n, Ord n) => Triangle n -> Point n -> Bool -- inside _ _ = False -- | intersects :: RealFrac r => Line r -> Line r -> Bool intersects a b = case intersect a b of Just _ -> True Nothing -> False -- | Yields the overlap of two closed intervals (n ∈ R) -- TODO: Normalise intervals (eg. (12, 5) -> (5, 12)) overlap :: Real a => (a, a) -> (a, a) -> Maybe (a, a) overlap a b | leftmost /= (α, β) = Just (β, γ) -- | otherwise = Nothing -- where [α, β, γ, _] = sort [fst a, snd a, fst b, snd b] -- That's right. leftmost = minimumBy (comparing fst) [a, b] -- -- | -- TODO: Intersect Rectangles -- | Coefficients for the linear function of a Line (slope, intercept). -- Fails for vertical and horizontal lines. -- -- TODO: Use Maybe (?) -- TODO: Rename (eg. toLinear, function) (?) -- coefficients :: (Fractional a, Eq a) => Line a -> Maybe (a, a) coefficients (Line (Vector ax ay) (Vector bx by)) | ax == bx = Nothing | ay == ay = Nothing | otherwise = let slope' = (by - ay)/(bx - ax) in Just (slope', ay - slope'*ax) -- Linear functions ------------------------------------------------------------------------------- -- | Solves a linear equation for x (f(x) = g(x)) -- TODO: Use Epsilon (?) solve :: (Fractional n, Eq n) => Linear n -> Linear n -> Maybe n solve f g | slope f == slope g = Nothing | otherwise = Just $ (intercept f - intercept g)/(slope f - slope g)