{-# Language MultiParamTypeClasses #-} {-# Language FlexibleInstances #-} -------------------------------------------------------------------------------- -- | -- Module : Geometry.Plane.General -- Copyright : (C) 2017 Maksymilian Owsianny -- License : BSD-style (see LICENSE) -- Maintainer : Maksymilian.Owsianny@gmail.com -- -- General representation of a plane. Plane in the General Form is Hession -- Normal Form scaled by an arbitrary non-zero scalar. -- -------------------------------------------------------------------------------- module Geometry.Plane.General ( Plane (..) , Plane2, Plane3 , Plane2D, Plane3D , MakePlane (..) , unsafeMakePlane , flipPlane , collinear -- , coincidence, coorientation , PlanesRelation (..), Incidence (..), Orientation (..) , planesRelation , isParallel ) where import Protolude hiding (zipWith, zero) import Data.Maybe (fromJust) import qualified Data.List as List import Linear -- import Linear.Solve import Linear.Affine (Point, (.-.)) import qualified Linear.Affine as Point import Data.EqZero -- | Internally Plane is represented as a pair (sN, sO) where N is a normal -- vector of a plane O is the distance of that plane from the origin and s is an -- arbitrary non-zero scalar. data Plane v n = Plane { planeVector :: !(v n) , planeLast :: !n } deriving (Eq, Ord, Show) type Plane2 = Plane V2 type Plane3 = Plane V3 type Plane2D = Plane V2 Double type Plane3D = Plane V3 Double instance (NFData (v n), NFData n) => NFData (Plane v n) where rnf (Plane vs l) = rnf vs `seq` rnf l -- | Flip plane orientation. flipPlane :: (Functor v, Num n) => Plane v n -> Plane v n flipPlane (Plane v n) = Plane (fmap negate v) (negate n) class MakePlane v n where -- | Make plane from vector of points. Returns Nothing if vectors between -- points are linearly dependent makePlane :: v (Point v n) -> Maybe (Plane v n) instance (Num n, Eq n) => MakePlane V3 n where makePlane (V3 p1 p2 p3) | n == zero = Nothing | otherwise = Just $ Plane n d where n = cross (p2 .-. p1) (p3 .-. p1) d = negate $ dot n $ unPoint p1 -- | Assumes that points form a valid plane (i.e. vectors between all points are -- linearly independent). unsafeMakePlane :: MakePlane v n => v (Point v n) -> Plane v n unsafeMakePlane = fromJust . makePlane {- makePlane :: (Applicative v, Solve v n, Num n) => v (Point v n) -> Maybe (Plane v n) -- makePlane ps = Plane <$> solve ups (pure 1) <*> pure 1 makePlane ps = uncurry Plane <$> solve ups (pure 1) where ups = fmap unPoint ps -- | Assumes that points form a valid plane (i.e. vectors between all points are -- linearly independent). unsafeMakePlane :: (Applicative v, Solve v n, Num n) => v (Point v n) -> Plane v n -- unsafeMakePlane ps = Plane (fromJust $ solve ups (pure 1)) 1 -- unsafeMakePlane ps = Plane v d unsafeMakePlane ps = case solve ups (pure 1) of Just (v, d) -> Plane v d Nothing -> error "Bla" -- . toS $ List.unlines $ map show ps where -- Just (v, d) = solve ups (pure 1) ups = fmap unPoint ps -} -- | Convert point to a vector. unPoint :: Point v n -> v n unPoint (Point.P x) = x -------------------------------------------------------------------------------- -- | Test whether two vectors are collinear. collinear :: (Foldable v, Num n, EqZero n) => v n -> v n -> Bool collinear v w = all f $ combinations 2 $ zipWith (,) v w where f [(a, b), (c, d)] = eqZero $ a*d - b*c f _ = False -- To silence exhaustiveness checker -- | All n-combinations of a given list. combinations :: Int -> [a] -> [[a]] combinations k is | k <= 0 = [ [] ] | otherwise = [ x:r | x:xs <- tails is, r <- combinations (k-1) xs ] -- | Zip two `Foldable` structures to a list with a given function. zipWith :: Foldable f => (a -> b -> c) -> f a -> f b -> [c] zipWith f a b = List.zipWith f (toList a) (toList b) -- | Test co-incidence of two planes assuming collinearity. coincidence :: (Foldable v, Num n, EqZero n) => Plane v n -> Plane v n -> Bool coincidence (Plane v1 d1) (Plane v2 d2) = all f $ zipWith (,) v1 v2 where f (x1, x2) = eqZero $ x1*d2 - x2*d1 -- | Test co-orientation of two assuming collinearity. coorientation :: (Foldable v, Num n, Ord n, EqZero n) => Plane v n -> Plane v n -> Bool coorientation (Plane v1 d1) (Plane v2 d2) = all geqZero $ d1*d2 : zipWith (*) v1 v2 -------------------------------------------------------------------------------- data PlanesRelation = Parallel Incidence Orientation | Crossing deriving Show data Incidence = CoIncident | NonIncident deriving Show data Orientation = CoOriented | AntiOriented deriving Show -- | Relate two planes on Parallelism, Incidence and Orientation. planesRelation :: (Foldable v, Num n, Ord n, EqZero n) => Plane v n -> Plane v n -> PlanesRelation planesRelation p1@(Plane v1 _) p2@(Plane v2 _) | collinear v1 v2 = Parallel incidence orientation | otherwise = Crossing where incidence = bool NonIncident CoIncident $ coincidence p1 p2 orientation = bool AntiOriented CoOriented $ coorientation p1 p2 isParallel :: (Foldable v, Num n, Ord n, EqZero n) => Plane v n -> Plane v n -> Bool isParallel a b = case planesRelation a b of Parallel _ _ -> True Crossing -> False