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)