packages feed

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

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DeriveFunctor  #-}
module Data.Geometry.LineSegment where

import           Control.Applicative
import           Control.Lens
import           Data.Ext
import           Data.Geometry.Box
import           Data.Geometry.Interval
import           Data.Geometry.Point
import           Data.Geometry.Line.Internal
import           Data.Geometry.Properties
import           Data.Geometry.Transformation
import           Data.Geometry.Vector
import           Linear.Vector((*^))
import           Linear.Affine(Affine(..),distanceA)
import qualified Data.List as L
import           Data.Maybe(maybe)
import           Data.Ord(comparing)
import           Data.Semigroup

--------------------------------------------------------------------------------
-- * d-dimensional LineSegments

-- | Line segments. LineSegments have a start and end point, both of which may
-- contain additional data of type p.
newtype LineSegment d p r = LineSeg { _unLineSeg :: Interval p (Point d r) }
pattern LineSegment s t = LineSeg (Interval s t)

instance HasStart (LineSegment d p r) where
  type StartCore  (LineSegment d p r) = Point d r
  type StartExtra (LineSegment d p r) = p
  start = lens (_start . _unLineSeg) (\(LineSegment _ t) s -> LineSegment s t)

instance HasEnd (LineSegment d p r) where
  type EndCore  (LineSegment d p r) = Point d r
  type EndExtra (LineSegment d p r) = p
  end = lens (_end . _unLineSeg) (\(LineSegment s _) t -> LineSegment s t)

instance (Num r, Arity d) => HasSupportingLine (LineSegment d p r) where
  supportingLine (LineSegment (p :+ _) (q :+ _)) = lineThrough p q

deriving instance (Show r, Show p, Arity d) => Show (LineSegment d p r)
deriving instance (Eq r, Eq p, Arity d)     => Eq (LineSegment d p r)
deriving instance (Ord r, Ord p, Arity d)   => Ord (LineSegment d p r)
deriving instance Arity d                   => Functor (LineSegment d p)
type instance Dimension (LineSegment d p r) = d
type instance NumType   (LineSegment d p r) = r

instance PointFunctor (LineSegment d p) where
  pmap f (LineSegment s e) = LineSegment (f <$> s) (f <$> e)

instance Arity d => IsBoxable (LineSegment d p r) where
  boundingBox l = boundingBoxList [l^.start.core, l^.end.core]

instance (Num r, AlwaysTruePFT d) => IsTransformable (LineSegment d p r) where
  transformBy = transformPointFunctor








-- ** Converting between Lines and LineSegments

toLineSegment            :: (Monoid p, Num r, Arity d) => Line d r -> LineSegment d p r
toLineSegment (Line p v) = LineSegment (p       :+ mempty)
                                       (p .+^ v :+ mempty)

-- *** Intersecting LineSegments

instance (Ord r, Fractional r) =>
         (LineSegment 2 p r) `IsIntersectableWith` (LineSegment 2 p r) where

  data Intersection (LineSegment 2 p r) (LineSegment 2 p r) =
        OverlappingSegment         !(LineSegment 2 p r)
      | LineSegLineSegIntersection !(Point 2 r)
      | NoIntersection
      deriving (Show,Eq)

  nonEmptyIntersection NoIntersection = False
  nonEmptyIntersection _              = True

  a@(LineSegment p q) `intersect` b@(LineSegment s t) = case la `intersect` lb of
      SameLine _                                ->
          maybe NoIntersection OverlappingSegment $ overlap a b
      LineLineIntersection r | onBothSegments r -> LineSegLineSegIntersection r
      _                                         -> NoIntersection
    where
      la = supportingLine a
      lb = supportingLine b
      onBothSegments r = onSegment r a && onSegment r b

instance (Ord r, Fractional r) =>
         (LineSegment 2 p r) `IsIntersectableWith` (Line 2 r) where
  data Intersection (LineSegment 2 p r) (Line 2 r) =
           LineContainsSegment !(LineSegment 2 p r)
         | LineLineSegmentIntersection !(Point 2 r)
         | NoLineLineSegmentIntersection
         deriving (Show,Eq)

  nonEmptyIntersection NoLineLineSegmentIntersection = False
  nonEmptyIntersection _                             = True

  s `intersect` l = case (supportingLine s) `intersect` l of
    SameLine _                               -> LineContainsSegment s
    LineLineIntersection p | p `onSegment` s -> LineLineSegmentIntersection p
    _                                        -> NoLineLineSegmentIntersection


-- * Functions on LineSegments

-- | Test if a point lies on a line segment.
--
-- >>> (point2 1 0) `onSegment` (LineSegment (origin :+ ()) (point2 2 0 :+ ()))
-- True
-- >>> (point2 1 1) `onSegment` (LineSegment (origin :+ ()) (point2 2 0 :+ ()))
-- False
-- >>> (point2 5 0) `onSegment` (LineSegment (origin :+ ()) (point2 2 0 :+ ()))
-- False
-- >>> (point2 (-1) 0) `onSegment` (LineSegment (origin :+ ()) (point2 2 0 :+ ()))
-- False
-- >>> (point2 1 1) `onSegment` (LineSegment (origin :+ ()) (point2 3 3 :+ ()))
-- True
--
-- Note that the segments are assumed to be closed. So the end points lie on the segment.
--
-- >>> (point2 2 0) `onSegment` (LineSegment (origin :+ ()) (point2 2 0 :+ ()))
-- True
-- >>> origin `onSegment` (LineSegment (origin :+ ()) (point2 2 0 :+ ()))
-- True
--
--
-- This function works for arbitrary dimensons.
--
-- >>> (point3 1 1 1) `onSegment` (LineSegment (origin :+ ()) (point3 3 3 3 :+ ()))
-- True
-- >>> (point3 1 2 1) `onSegment` (LineSegment (origin :+ ()) (point3 3 3 3 :+ ()))
-- False
onSegment       :: (Ord r, Fractional r, Arity d)
                => Point d r -> LineSegment d p r -> Bool
p `onSegment` l = let s         = l^.start.core
                      t         = l^.end.core
                      inRange x = 0 <= x && x <= 1
                  in maybe False inRange $ scalarMultiple (p .-. s) (t .-. s)


-- | Compute the overlap between the two segments (if they overlap/intersect)
overlap     :: (Ord r, Fractional r, Arity d)
            => LineSegment d p r -> LineSegment d p r -> Maybe (LineSegment d p r)
overlap l m = mim >>= \im -> case il `intersect` im of
    IntervalIntersection (Interval s e) -> Just $ LineSegment (s^.extra) (e^.extra)
    NoOverlap                           -> Nothing
  where
    p = l^.start
    q = l^.end
    r = m^.start
    s = m^.end

    u = q^.core .-. p^.core

    -- lineseg l corresp to an interval from 0 1
    il = Interval (0 :+ p) (1 :+ q)


    -- let lambda x denote the scalar s.t. x = p + (lambda x) *^ u
    --
    -- lambda' computes lambda' and pairs it with the associated point.
    -- lambda  :: (Point d r :+ extra) -> Maybe (r :+ (Point d r :+ extra)
    lambda' x = (:+ x) <$> scalarMultiple u (x^.core .-. p^.core)

    -- lineseg m corresponds to an interval
    -- [min (lambda r, lambda s), max (lambda r, lambda s)]
    --
    -- mim denotes this interval, assuming it exists (i.e. that is,
    -- assuming r and s are indeed colinear with pq.
    mim = mapM lambda' [r, s] >>= (f . L.sortBy (comparing (^.core)))

    -- Make sure we have two elems, s.t. both r and s are colinear with pq
    f [a,b] = Just $ Interval a b
    f _     = Nothing


-- | The left and right end point (or left below right if they have equal x-coords)
orderedEndPoints   :: Ord r => LineSegment 2 p r -> (Point 2 r :+ p, Point 2 r :+ p)
orderedEndPoints s = if pc <= qc then (p, q) else (q,p)
  where
    p@(pc :+ _) = s^.start
    q@(qc :+ _) = s^.end


-- | Length of the line segment
segmentLength                   :: (Arity d, Floating r) => LineSegment d p r -> r
segmentLength (LineSegment p q) = distanceA (p^.core) (q^.core)