{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE DeriveFunctor #-} module Data.Geometry.Line.Internal where import Control.Applicative import Control.Lens import Data.Ext import qualified Data.Foldable as F import Data.Geometry.Box import Data.Geometry.Interval import Data.Geometry.Point import Data.Geometry.Properties import Data.Geometry.Vector import Linear.Affine(Affine(..)) import Linear.Vector((*^)) -------------------------------------------------------------------------------- -- * d-dimensional Lines -- | A line is given by an anchor point and a vector indicating the -- direction. data Line d r = Line { _anchorPoint :: Point d r , _direction :: Vector d r } makeLenses ''Line deriving instance (Show r, Arity d) => Show (Line d r) deriving instance Arity d => Functor (Line d) type instance Dimension (Line d r) = d type instance NumType (Line d r) = r -- ** Functions on lines -- | A line may be constructed from two points. lineThrough :: (Num r, Arity d) => Point d r -> Point d r -> Line d r lineThrough p q = Line p (q .-. p) verticalLine :: Num r => r -> Line 2 r verticalLine x = Line (point2 x 0) (v2 0 1) horizontalLine :: Num r => r -> Line 2 r horizontalLine y = Line (point2 0 y) (v2 1 0) perpendicularTo :: Num r => Line 2 r -> Line 2 r perpendicularTo (Line p (Vector2 vx vy)) = Line p (v2 (-vy) vx) -- | Test if two lines are identical, meaning; if they have exactly the same -- anchor point and directional vector. isIdenticalTo :: (Eq r, Arity d) => Line d r -> Line d r -> Bool (Line p u) `isIdenticalTo` (Line q v) = (p,u) == (q,v) -- | Test if the two lines are parallel. -- -- >>> lineThrough origin (point2 1 0) `isParallelTo` lineThrough (point2 1 1) (point2 2 1) -- True -- >>> lineThrough origin (point2 1 0) `isParallelTo` lineThrough (point2 1 1) (point2 2 2) -- False isParallelTo :: (Eq r, Fractional r, Arity d) => Line d r -> Line d r -> Bool (Line _ u) `isParallelTo` (Line _ v) = u `isScalarMultipleOf` v -- TODO: Maybe use a specialize pragma for 2D (see intersect instance for two lines.) -- | Test if point p lies on line l -- -- >>> origin `onLine` lineThrough origin (point2 1 0) -- True -- >>> point2 10 10 `onLine` lineThrough origin (point2 2 2) -- True -- >>> point2 10 5 `onLine` lineThrough origin (point2 2 2) -- False onLine :: (Eq r, Fractional r, Arity d) => Point d r -> Line d r -> Bool p `onLine` (Line q v) = p == q || (p .-. q) `isScalarMultipleOf` v -- TODO: Maybe use a specialize pragma for 2D with an implementation using ccw instance (Eq r, Fractional r) => (Line 2 r) `IsIntersectableWith` (Line 2 r) where data Intersection (Line 2 r) (Line 2 r) = SameLine !(Line 2 r) | LineLineIntersection !(Point 2 r) | ParallelLines -- ^ No intersection deriving (Show) nonEmptyIntersection ParallelLines = False nonEmptyIntersection _ = True l@(Line p (Vector2 ux uy)) `intersect` m@(Line q v@(Vector2 vx vy)) | areParallel = if q `onLine` l then SameLine l else ParallelLines | otherwise = LineLineIntersection r where r = q .+^ alpha *^ v denom = vy * ux - vx * uy areParallel = denom == 0 -- Instead of using areParallel, we can also use the generic 'isParallelTo' function -- for lines of arbitrary dimension, but this is a bit more efficient. alpha = (ux * (py - qy) + uy * (qx - px)) / denom Point2 px py = p Point2 qx qy = q instance (Eq r, Fractional r) => Eq (Intersection (Line 2 r) (Line 2 r)) where (SameLine l) == (SameLine m) = case (l `intersect` m) of SameLine _ -> True _ -> False (LineLineIntersection p) == (LineLineIntersection q) = p == q ParallelLines == ParallelLines = True _ == _ = False -------------------------------------------------------------------------------- -- * Supporting Lines -- | Types for which we can compute a supporting line, i.e. a line that contains the thing of type t. class HasSupportingLine t where supportingLine :: t -> Line (Dimension t) (NumType t) instance HasSupportingLine (Line d r) where supportingLine = id