packages feed

hgeometry-0.4.0.0: src/Data/Geometry/HalfLine.hs

{-# LANGUAGE TemplateHaskell  #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DeriveFunctor  #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Geometry.HalfLine where

import           Control.Applicative
import           Control.Lens
import           Data.Ext
import           Data.Geometry.Interval
import           Data.Geometry.Point
import           Data.Geometry.Properties
import           Data.Geometry.Transformation
import           Data.Geometry.Vector
import           Data.Geometry.Line
import           Data.Geometry.LineSegment
import           Linear.Vector((*^))
import           Linear.Affine(Affine(..),distanceA)

--------------------------------------------------------------------------------
-- * d-dimensional Half-Lines

-- | d-dimensional Half-Lines
data HalfLine d r = HalfLine { _startPoint        :: Point  d r
                             , _halfLineDirection :: Vector d r
                             }
makeLenses ''HalfLine

deriving instance (Show r, Arity d) => Show    (HalfLine d r)
deriving instance (Eq r, Arity d)   => Eq      (HalfLine d r)
deriving instance Arity d           => Functor (HalfLine d)

type instance Dimension (HalfLine d r) = d
type instance NumType   (HalfLine d r) = r

instance HasStart (HalfLine d r) where
  type StartCore  (HalfLine d r) = Point d r
  type StartExtra (HalfLine d r) = ()

  start = lens ((:+ ()) . _startPoint) (\(HalfLine _ v) p -> HalfLine (p^.core) v)

instance HasSupportingLine (HalfLine d r) where
  supportingLine ~(HalfLine p v) = Line p v

-- Half-Lines are transformable
instance (Num r, AlwaysTruePFT d) => IsTransformable (HalfLine d r) where
  transformBy t = toHalfLine . transformPointFunctor t . toLineSegment'
    where
      toLineSegment' :: (Num r, Arity d) => HalfLine d r -> LineSegment d () r
      toLineSegment' (HalfLine p v) = LineSegment (p :+ ()) ((p .+^ v) :+ ())

instance (Ord r, Fractional r) => (HalfLine 2 r) `IsIntersectableWith` (Line 2 r) where
  data Intersection (HalfLine 2 r) (Line 2 r) = NoHalfLineLineIntersection
                                              | HalfLineLineIntersection !(Point 2 r)
                                              | HalfLineLineOverlap      !(HalfLine 2 r)
                                              deriving (Show,Eq)

  nonEmptyIntersection NoHalfLineLineIntersection = False
  nonEmptyIntersection _                          = True

  hl `intersect` l = case supportingLine hl `intersect` l of
    SameLine _             -> HalfLineLineOverlap hl
    LineLineIntersection p -> if p `onHalfLine` hl then HalfLineLineIntersection p
                                                   else NoHalfLineLineIntersection
    ParallelLines          -> NoHalfLineLineIntersection


instance (Ord r, Fractional r) => (HalfLine 2 r) `IsIntersectableWith` (HalfLine 2 r) where
  data Intersection (HalfLine 2 r) (HalfLine 2 r) = NoHalfLineHalfLineIntersection
                                                  | HLHLIntersectInPoint    !(Point 2 r)
                                                  | HLHLIntersectInSegment  !(LineSegment 2 () r)
                                                  | HLHLIntersectInHalfLine !(HalfLine 2 r)
                                                  deriving (Show,Eq)

  nonEmptyIntersection NoHalfLineHalfLineIntersection = False
  nonEmptyIntersection _                              = True

  hl' `intersect` hl = case supportingLine hl' `intersect` supportingLine hl of
    ParallelLines          -> NoHalfLineHalfLineIntersection
    LineLineIntersection p -> if p `onHalfLine` hl' && p `onHalfLine` hl then HLHLIntersectInPoint p
                                                                         else NoHalfLineHalfLineIntersection
    SameLine _             -> let p   = _startPoint hl'
                                  q   = _startPoint hl
                                  seg = LineSegment (p :+ ()) (q :+ ())
                              in case (p `onHalfLine` hl, q `onHalfLine` hl') of
                                   (False,False) -> NoHalfLineHalfLineIntersection
                                   (False,True)  -> HLHLIntersectInHalfLine hl
                                   (True, False) -> HLHLIntersectInHalfLine hl'
                                   (True, True)  -> if hl == hl' then HLHLIntersectInHalfLine hl
                                                                 else HLHLIntersectInSegment seg



instance (Ord r, Fractional r) => (LineSegment 2 p r) `IsIntersectableWith` (HalfLine 2 r) where
  data Intersection (LineSegment 2 p r) (HalfLine 2 r) = NoSegmentHalfLineIntersection
                                                       | SegmentHalfLineIntersection !(Point 2 r)
                                                       | SegmentOnHalfLine           !(LineSegment 2 () r)

  nonEmptyIntersection NoSegmentHalfLineIntersection = False
  nonEmptyIntersection _                             = True

  s `intersect` hl = case supportingLine s `intersect` supportingLine hl of
    ParallelLines          -> NoSegmentHalfLineIntersection
    LineLineIntersection p -> if p `onSegment` s && p `onHalfLine` hl then SegmentHalfLineIntersection p
                                                                      else NoSegmentHalfLineIntersection
    SameLine _             -> let p = s  ^.start.core
                                  q = s  ^.end.core
                                  r = hl ^.start.core
                                  seg a b = LineSegment (a :+ ()) (b :+ ())
                              in case (p `onHalfLine` hl, q `onHalfLine` hl) of
                                   (False, False)   -> NoSegmentHalfLineIntersection
                                   (False, True)    -> SegmentOnHalfLine $ seg r q
                                   (True,  False)   -> SegmentOnHalfLine $ seg p r
                                   (True,  True)    -> SegmentOnHalfLine $ seg p q




-- | Test if a point lies on a half-line
onHalfLine :: (Ord r, Fractional r, Arity d) => Point d r -> HalfLine d r -> Bool
p `onHalfLine` (HalfLine q v) = maybe False (>= 0) $ scalarMultiple (p .-. q) v





-- | Transform a LineSegment into a half-line, by forgetting the second endpoint.
toHalfLine                     :: (Num r, Arity d) => LineSegment d p r -> HalfLine d r
toHalfLine (LineSegment p' q') = let p = p' ^.core
                                     q = q' ^.core
                                 in HalfLine p (q .-. p)