{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Geometry.Triangle where
import Control.Lens
import Data.Bifunctor
import Data.Either (partitionEithers)
import Data.Ext
import Data.Geometry.Ball (Disk, disk)
import Data.Geometry.Boundary
import Data.Geometry.HyperPlane
import Data.Geometry.Line
import Data.Geometry.LineSegment
import Data.Geometry.Point
import Data.Geometry.Properties
import Data.Geometry.Transformation
import Data.Geometry.Vector
import qualified Data.Geometry.Vector as V
import qualified Data.List as List
import Data.Maybe (mapMaybe)
import Data.Vinyl
import Data.Vinyl.CoRec
import GHC.TypeLits
data Triangle d p r = Triangle (Point d r :+ p)
(Point d r :+ p)
(Point d r :+ p)
deriving instance (Arity d, Show r, Show p) => Show (Triangle d p r)
deriving instance (Arity d, Read r, Read p) => Read (Triangle d p r)
deriving instance (Arity d, Eq r, Eq p) => Eq (Triangle d p r)
instance Arity d => Functor (Triangle d p) where
fmap f (Triangle p q r) = let f' = first (fmap f) in Triangle (f' p) (f' q) (f' r)
type instance NumType (Triangle d p r) = r
type instance Dimension (Triangle d p r) = d
instance PointFunctor (Triangle d p) where
pmap f (Triangle p q r) = Triangle (p&core %~ f) (q&core %~ f) (r&core %~ f)
instance (Fractional r, Arity d, Arity (d + 1)) => IsTransformable (Triangle d p r) where
transformBy = transformPointFunctor
triangle' :: Point d r -> Point d r -> Point d r -> Triangle d () r
triangle' p q r = Triangle (ext p) (ext q) (ext r)
sideSegments :: Triangle d p r -> [LineSegment d p r]
sideSegments (Triangle p q r) =
[ClosedLineSegment p q, ClosedLineSegment q r, ClosedLineSegment r p]
area :: Fractional r => Triangle 2 p r -> r
area t = doubleArea t / 2
doubleArea :: Num r => Triangle 2 p r -> r
doubleArea (Triangle a b c) = abs $ ax*by - ax*cy
+ bx*cy - bx*ay
+ cx*ay - cx*by
where
Point2 ax ay = a^.core
Point2 bx by = b^.core
Point2 cx cy = c^.core
isDegenerateTriangle :: (Num r, Eq r) => Triangle 2 p r -> Bool
isDegenerateTriangle = (== 0) . doubleArea
inscribedDisk :: (Eq r, Fractional r)
=> Triangle 2 p r -> Maybe (Disk () r)
inscribedDisk (Triangle p q r) = disk (p^.core) (q^.core) (r^.core)
instance Num r => HasSupportingPlane (Triangle 3 p r) where
supportingPlane (Triangle p q r) = from3Points (p^.core) (q^.core) (r^.core)
toBarricentric :: Fractional r
=> Point 2 r -> Triangle 2 p r
-> Vector 3 r
toBarricentric (Point2 qx qy) (Triangle a b c) = Vector3 alpha beta gamma
where
Point2 ax ay = a^.core
Point2 bx by = b^.core
Point2 cx cy = c^.core
dett = (by - cy)*(ax - cx) + (cx - bx)*(ay - cy)
alpha = ((by - cy)*(qx - cx) + (cx - bx)*(qy - cy)) / dett
beta = ((cy - ay)*(qx - cx) + (ax - cx)*(qy - cy)) / dett
gamma = 1 - alpha - beta
fromBarricentric :: (Arity d, Num r)
=> Vector 3 r -> Triangle d p r
-> Point d r
fromBarricentric (Vector3 a b c) (Triangle p q r) = let f = view (core.vector) in
Point $ a *^ f p ^+^ b *^ f q ^+^ c *^ f r
inTriangle :: (Ord r, Fractional r)
=> Point 2 r -> Triangle 2 p r -> PointLocationResult
inTriangle q t
| all (`inRange` (OpenRange 0 1)) [a,b,c] = Inside
| all (`inRange` (ClosedRange 0 1)) [a,b,c] = OnBoundary
| otherwise = Outside
where
Vector3 a b c = toBarricentric q t
onTriangle :: (Ord r, Fractional r)
=> Point 2 r -> Triangle 2 p r -> Bool
q `onTriangle` t = let Vector3 a b c = toBarricentric q t
in all (`inRange` (ClosedRange 0 1)) [a,b,c]
type instance IntersectionOf (Line 2 r) (Triangle 2 p r) =
[ NoIntersection, Point 2 r, LineSegment 2 () r ]
instance (Fractional r, Ord r) => (Line 2 r) `IsIntersectableWith` (Triangle 2 p r) where
nonEmptyIntersection = defaultNonEmptyIntersection
l `intersect` (Triangle p q r) =
case first List.nub . partitionEithers . mapMaybe collect $ sides of
([],[]) -> coRec NoIntersection
(_, [s]) -> coRec $ first (const ()) s
([a],_) -> coRec a
([a,b],_) -> coRec $ ClosedLineSegment (ext a) (ext b)
(_,_) -> error "intersecting a line with a triangle. Triangle is degenerate"
where
sides = [ClosedLineSegment p q, ClosedLineSegment q r, ClosedLineSegment r p]
collect :: LineSegment 2 p r -> Maybe (Either (Point 2 r) (LineSegment 2 p r))
collect s = match (s `intersect` l) $
(H $ \NoIntersection -> Nothing)
:& (H $ \(a :: Point 2 r) -> Just $ Left a)
:& (H $ \(e :: LineSegment 2 p r) -> Just $ Right e)
:& RNil
type instance IntersectionOf (Line 3 r) (Triangle 3 p r) =
[ NoIntersection, Point 3 r, LineSegment 3 () r ]
instance (Fractional r, Ord r) => (Line 3 r) `IsIntersectableWith` (Triangle 3 p r) where
nonEmptyIntersection = defaultNonEmptyIntersection
l@(Line a v) `intersect` t@(Triangle (p :+ _) (q :+ _) (r :+ _)) =
match (l `intersect` h) $
(H $ \NoIntersection -> coRec NoIntersection)
:& (H $ \i@(Point3 _ _ _) -> if onTriangle' i then coRec i else coRec NoIntersection)
:& (H $ \_ -> intersect2d)
:& RNil
where
h@(Plane _ n) = supportingPlane t
t' = Triangle (ext $ project p) (ext origin) (ext $ project r)
l' = Line (project a) (project' v)
onTriangle' :: Point 3 r -> Bool
onTriangle' i = (project i) `onTriangle` t'
transf :: Transformation 3 r
transf = let u = p .-. q
in rotateTo (Vector3 u (n `cross` u) n) |.| translation ((-1) *^ (toVec q))
invTrans :: Transformation 3 r
invTrans = inverseOf transf
project :: Point 3 r -> Point 2 r
project = projectPoint . transformBy transf
project' :: Vector 3 r -> Vector 2 r
project' = toVec . project . Point
lift :: Point 2 r -> Point 3 r
lift = Point . transformBy invTrans . flip V.snoc 0 . toVec
intersect2d :: Intersection (Line 3 r) (Triangle 3 p r)
intersect2d = match (l' `intersect` t') $
(H $ \NoIntersection -> coRec NoIntersection)
:& (H $ \i@(Point2 _ _) -> coRec $ lift i)
:& (H $ \(LineSegment s e) -> coRec $ LineSegment (s&unEndPoint.core %~ lift)
(e&unEndPoint.core %~ lift))
:& RNil