{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Geometry.Line.Internal where
import Control.DeepSeq
import Control.Lens
import qualified Data.Foldable as F
import Data.Geometry.Point
import Data.Geometry.Properties
import Data.Geometry.Vector
import Data.Ord (comparing)
import qualified Data.Traversable as T
import Data.Vinyl
import Data.Vinyl.CoRec
import GHC.Generics (Generic)
import Test.QuickCheck
data Line d r = Line { _anchorPoint :: !(Point d r)
, _direction :: !(Vector d r)
} deriving Generic
makeLenses ''Line
instance (Show r, Arity d) => Show (Line d r) where
show (Line p v) = concat [ "Line (", show p, ") (", show v, ")" ]
deriving instance (NFData r, Arity d) => NFData (Line d r)
deriving instance Arity d => Functor (Line d)
deriving instance Arity d => F.Foldable (Line d)
deriving instance Arity d => T.Traversable (Line d)
instance (Arity d, Eq r, Fractional r) => Eq (Line d r) where
l@(Line p _) == m = l `isParallelTo` m && p `onLine` m
instance (Arbitrary r, Arity d, Num r, Eq r) => Arbitrary (Line d r) where
arbitrary = do p <- arbitrary
q <- suchThat arbitrary (/= p)
return $ lineThrough p q
type instance Dimension (Line d r) = d
type instance NumType (Line d r) = r
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) (Vector2 0 1)
horizontalLine :: Num r => r -> Line 2 r
horizontalLine y = Line (Point2 0 y) (Vector2 1 0)
perpendicularTo :: Num r => Line 2 r -> Line 2 r
perpendicularTo (Line p ~(Vector2 vx vy)) = Line p (Vector2 (-vy) vx)
isPerpendicularTo :: (Num r, Eq r) => Vector 2 r -> Line 2 r -> Bool
v `isPerpendicularTo` (Line _ u) = v `dot` u == 0
isIdenticalTo :: (Eq r, Arity d) => Line d r -> Line d r -> Bool
(Line p u) `isIdenticalTo` (Line q v) = (p,u) == (q,v)
isParallelTo :: (Eq r, Fractional r, Arity d)
=> Line d r -> Line d r -> Bool
(Line _ u) `isParallelTo` (Line _ v) = u `isScalarMultipleOf` v
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
onLine2 :: (Ord r, Num r) => Point 2 r -> Line 2 r -> Bool
p `onLine2` (Line q v) = ccw p q (q .+^ v) == CoLinear
pointAt :: (Num r, Arity d) => r -> Line d r -> Point d r
pointAt a (Line p v) = p .+^ (a *^ v)
toOffset :: (Eq r, Fractional r, Arity d) => Point d r -> Line d r -> Maybe r
toOffset p (Line q v) = scalarMultiple (p .-. q) v
toOffset' :: (Eq r, Fractional r, Arity d) => Point d r -> Line d r -> r
toOffset' p = fromJust' . toOffset p
where
fromJust' (Just x) = x
fromJust' _ = error "toOffset: Nothing"
type instance IntersectionOf (Line 2 r) (Line 2 r) = [ NoIntersection
, Point 2 r
, Line 2 r
]
instance (Eq r, Fractional r) => (Line 2 r) `IsIntersectableWith` (Line 2 r) where
nonEmptyIntersection = defaultNonEmptyIntersection
l@(Line p ~(Vector2 ux uy)) `intersect` (Line q ~v@(Vector2 vx vy))
| areParallel = if q `onLine` l then coRec l
else coRec NoIntersection
| otherwise = coRec r
where
r = q .+^ alpha *^ v
denom = vy * ux - vx * uy
areParallel = denom == 0
alpha = (ux * (py - qy) + uy * (qx - px)) / denom
Point2 px py = p
Point2 qx qy = q
sqDistanceTo :: (Fractional r, Arity d) => Point d r -> Line d r -> r
sqDistanceTo p = fst . sqDistanceToArg p
sqDistanceToArg :: (Fractional r, Arity d)
=> Point d r -> Line d r -> (r, Point d r)
sqDistanceToArg p (Line q v) = let u = q .-. p
t = (-1 * (u `dot` v)) / (v `dot` v)
m = q .+^ (v ^* t)
in (qdA m p, m)
class HasSupportingLine t where
supportingLine :: t -> Line (Dimension t) (NumType t)
instance HasSupportingLine (Line d r) where
supportingLine = id
fromLinearFunction :: Num r => r -> r -> Line 2 r
fromLinearFunction a b = Line (Point2 0 b) (Vector2 1 a)
toLinearFunction :: forall r. (Fractional r, Eq r)
=> Line 2 r -> Maybe (r,r)
toLinearFunction l@(Line _ ~(Vector2 vx vy)) = match (l `intersect` verticalLine (0 :: r)) $
(H $ \NoIntersection -> Nothing)
:& (H $ \(Point2 _ b) -> Just (vy / vx,b))
:& (H $ \_ -> Nothing)
:& RNil
data SideTestUpDown = Below | On | Above deriving (Show,Read,Eq,Ord)
class OnSideUpDownTest t where
onSideUpDown :: (d ~ Dimension t, r ~ NumType t, Ord r, Num r)
=> Point d r -> t -> SideTestUpDown
instance OnSideUpDownTest (Line 2 r) where
q `onSideUpDown` (Line p v) = let r = p .+^ v
f z = (z^.xCoord, -z^.yCoord)
minBy g a b = F.minimumBy (comparing g) [a,b]
maxBy g a b = F.maximumBy (comparing g) [a,b]
in case ccw (minBy f p r) (maxBy f p r) q of
CCW -> Above
CW -> Below
CoLinear -> On
data SideTest = LeftSide | OnLine | RightSide deriving (Show,Read,Eq,Ord)
onSide :: (Ord r, Num r) => Point 2 r -> Line 2 r -> SideTest
q `onSide` (Line p v) = let r = p .+^ v
in case ccw p r q of
CCW -> LeftSide
CW -> RightSide
CoLinear -> OnLine
liesAbove :: (Ord r, Num r) => Point 2 r -> Line 2 r -> Bool
q `liesAbove` l = q `onSideUpDown` l == Above
bisector :: Fractional r => Point 2 r -> Point 2 r -> Line 2 r
bisector p q = let v = q .-. p
h = p .+^ (v ^/ 2)
in perpendicularTo (Line h v)
cmpSlope :: (Num r, Ord r) => Line 2 r -> Line 2 r -> Ordering
(Line _ u) `cmpSlope` (Line _ v) = case ccw origin (f u) (f v) of
CCW -> LT
CW -> GT
CoLinear -> EQ
where
f w@(Vector2 x y) = Point $ case (x `compare` 0, y >= 0) of
(GT,_) -> w
(EQ,True) -> w
_ -> (-1) *^ w