packages feed

numhask-space-0.8.1.0: src/NumHask/Space/Point.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wall #-}

-- | A 2-dimensional point.
module NumHask.Space.Point
  ( Point (..),
    rotateP,
    gridP,
    dotP,
    (<.>),
    crossP,
    flipY,
    Line (..),
    lineSolve,
    lineDistance,
    closestPoint,
    lineIntersect,
    translate,
    scaleT,
    skew,
  )
where

import Data.Distributive
import Data.Functor.Classes
import Data.Functor.Rep
import NumHask.Prelude hiding (Distributive)
import qualified NumHask.Prelude as P
import NumHask.Space.Range
import NumHask.Space.Types
import System.Random
import System.Random.Stateful

-- $setup
-- >>> import NumHask.Prelude
-- >>> import NumHask.Space

-- | A 2-dimensional Point of a's
--
-- In contrast with a tuple, a Point is functorial over both arguments.
--
-- >>> let p = Point 1 1
-- >>> p + p
-- Point 2 2
-- >>> (2*) <$> p
-- Point 2 2
--
-- A major reason for this bespoke treatment (compared to just using linear, say) is that Points do not have maximums and minimums but they do form a lattice, and this is useful for folding sets of points to find out the (rectangular) Space they occupy.
--
-- >>> Point 0 1 /\ Point 1 0
-- Point 0 0
-- >>> Point 0 1 \/ Point 1 0
-- Point 1 1
--
-- This is used extensively in [chart-svg](https://hackage.haskell.org/package/chart-svg) to ergonomically obtain chart areas.
--
-- > space1 [Point 1 0, Point 0 1] :: Rect Double
-- Rect 0.0 1.0 0.0 1.0
data Point a = Point
  { _x :: a,
    _y :: a
  }
  deriving (Eq, Generic)

instance (Show a) => Show (Point a) where
  show (Point a b) = "Point " <> show a <> " " <> show b

instance Functor Point where
  fmap f (Point a b) = Point (f a) (f b)

instance Eq1 Point where
  liftEq f (Point a b) (Point c d) = f a c && f b d

instance Show1 Point where
  liftShowsPrec sp _ d (Point a b) = showsBinaryWith sp sp "Point" d a b

instance Applicative Point where
  pure a = Point a a

  (Point fa fb) <*> Point a b = Point (fa a) (fb b)

instance Monad Point where
  Point a b >>= f = Point a' b'
    where
      Point a' _ = f a
      Point _ b' = f b

instance Foldable Point where
  foldMap f (Point a b) = f a `mappend` f b

instance Traversable Point where
  traverse f (Point a b) = Point <$> f a <*> f b

instance (Semigroup a) => Semigroup (Point a) where
  (Point a0 b0) <> (Point a1 b1) = Point (a0 <> a1) (b0 <> b1)

instance (Semigroup a, Monoid a) => Monoid (Point a) where
  mempty = Point mempty mempty

  mappend = (<>)

instance (Bounded a) => Bounded (Point a) where
  minBound = Point minBound minBound

  maxBound = Point maxBound maxBound

instance (Additive a) => Additive (Point a) where
  (Point a0 b0) + (Point a1 b1) = Point (a0 + a1) (b0 + b1)
  zero = Point zero zero

instance (Subtractive a) => Subtractive (Point a) where
  negate = fmap negate

instance (Multiplicative a) => Multiplicative (Point a) where
  (Point a0 b0) * (Point a1 b1) = Point (a0 * a1) (b0 * b1)
  one = Point one one

instance (P.Distributive a) => P.Distributive (Point a)

instance (Field a) => Field (Point a)

instance (Signed a) => Signed (Point a) where
  sign = fmap sign
  abs = fmap abs

instance (Divisive a) => Divisive (Point a) where
  recip = fmap recip

instance Distributive Point where
  collect f x = Point (getL . f <$> x) (getR . f <$> x)
    where
      getL (Point l _) = l
      getR (Point _ r) = r

instance (Additive a) => AdditiveAction (Point a) a where
  (.+) a (Point x y) = Point (a + x) (a + y)
  (+.) (Point x y) a = Point (a + x) (a + y)

instance (Subtractive a) => SubtractiveAction (Point a) a where
  (.-) a (Point x y) = Point (a - x) (a - y)
  (-.) (Point x y) a = Point (x - a) (y - a)

instance (Multiplicative a) => MultiplicativeAction (Point a) a where
  (.*) a (Point x y) = Point (a * x) (a * y)
  (*.) (Point x y) a = Point (a * x) (a * y)

instance (Divisive a) => DivisiveAction (Point a) a where
  (./) a (Point x y) = Point (a / x) (a / y)
  (/.) (Point x y) a = Point (x / a) (y / a)

instance Representable Point where
  type Rep Point = Bool

  tabulate f = Point (f False) (f True)

  index (Point l _) False = l
  index (Point _ r) True = r

instance (Ord a) => JoinSemiLattice (Point a) where
  (\/) (Point x y) (Point x' y') = Point (max x x') (max y y')

instance (Ord a) => MeetSemiLattice (Point a) where
  (/\) (Point x y) (Point x' y') = Point (min x x') (min y y')

instance
  (ExpField a, Eq a) =>
  Norm (Point a) a
  where
  norm (Point x y) = sqrt (x * x + y * y)
  basis p = let m = norm p in bool (p /. m) zero (m == zero)

-- | angle formed by a vector from the origin to a Point and the x-axis (Point 1 0). Note that an angle between two points p1 & p2 is thus angle p2 - angle p1
instance (TrigField a) => Direction (Point a) a where
  angle (Point x y) = atan2 y x
  ray x = Point (cos x) (sin x)

instance (UniformRange a) => UniformRange (Point a) where
  uniformRM (Point x y, Point x' y') g =
    Point <$> uniformRM (x, x') g <*> uniformRM (y, y') g

instance (Multiplicative a, Additive a) => Affinity (Point a) a where
  transform (Transform a b c d e f) (Point x y) =
    Point (a * x + b * y + c) (d * x + e * y + f)

-- | move an 'Affinity' by a 'Point'
translate :: (TrigField a) => Point a -> Transform a
translate (Point x y) = Transform one zero x zero one y

-- | scale an 'Affinity' by a 'Point'
scaleT :: (TrigField a) => Point a -> Transform a
scaleT (Point x y) = Transform x zero zero y zero zero

-- | Skew transform
--
-- x-axis skew
--
-- > skew (Point x 0)
skew :: (TrigField a) => Point a -> Transform a
skew (Point x y) = Transform one (tan x) zero (tan y) one zero

-- | rotate a point by x relative to the origin
--
-- >>> rotateP (pi/2) (Point 1 0)
-- Point 6.123233995736766e-17 1.0
rotateP :: (TrigField a) => a -> Point a -> Point a
rotateP d p = rotate d |. p

-- | Create Points for a formulae y = f(x) across an x range
--
-- >>> gridP (^2) (Range 0 4) 4
-- [Point 0.0 0.0,Point 1.0 1.0,Point 2.0 4.0,Point 3.0 9.0,Point 4.0 16.0]
gridP :: (FieldSpace (Range a)) => (a -> a) -> Range a -> Grid (Range a) -> [Point a]
gridP f r g = (\x -> Point x (f x)) <$> grid OuterPos r g

-- | dot product
dotP :: (Multiplicative a, Additive a) => Point a -> Point a -> a
dotP (Point x y) (Point x' y') = x * x' + y * y'

infix 4 <.>

-- | dot product operator
(<.>) :: (Multiplicative a, Additive a) => Point a -> Point a -> a
(<.>) = dotP

-- | cross product
crossP :: (Multiplicative a, Subtractive a) => Point a -> Point a -> a
crossP (Point x y) (Point x' y') = x * y' - y * x'

-- | reflect on x-axis
flipY :: (Subtractive a) => Point a -> Point a
flipY (Point x y) = Point x (-y)

-- | A line is a composed of 2 'Point's
data Line a = Line
  { lineStart :: Point a,
    lineEnd :: Point a
  }
  deriving (Show, Eq, Functor, Foldable, Traversable)

instance (Multiplicative a, Additive a) => Affinity (Line a) a where
  transform t (Line s e) = Line (transform t s) (transform t e)

-- | Return the parameters (a, b, c) for the line equation @a*x + b*y + c = 0@.
lineSolve :: (ExpField a, Eq a) => Line a -> (a, a, a)
lineSolve (Line p1 p2) = (-my, mx, c)
  where
    m@(Point mx my) = basis (p2 - p1)
    c = crossP p1 m

-- | Return the signed distance from a point to the line.  If the
-- distance is negative, the point lies to the right of the line
lineDistance :: (ExpField a) => Line a -> Point a -> a
lineDistance (Line (Point x1 y1) (Point x2 y2)) =
  let dy = y1 - y2
      dx = x2 - x1
      d = sqrt (dx * dx + dy * dy)
   in dy `seq` dx `seq` d
        `seq` \(Point x y) -> (x - x1) * dy / d + (y - y1) * dx / d

-- | Return the point on the line closest to the given point.
closestPoint :: (Field a) => Line a -> Point a -> Point a
closestPoint (Line p1 p2) p3 = Point px py
  where
    d@(Point dx dy) = p2 - p1
    u = dy * _y p3 + dx * _x p3
    v = _x p1 * _y p2 - _x p2 * _y p1
    m = d <.> d
    px = (dx * u + dy * v) / m
    py = (dy * u - dx * v) / m

-- | Calculate the intersection of two lines.  If the determinant is
-- less than tolerance (parallel or coincident lines), return Nothing.
lineIntersect :: (Ord a, Epsilon a, Signed a, Field a) => Line a -> Line a -> Maybe (Point a)
lineIntersect (Line p1 p2) (Line p3 p4)
  | abs det <= epsilon = Nothing
  | otherwise = Just $ (a .* d2 - b .* d1) /. det
  where
    d1 = p1 - p2
    d2 = p3 - p4
    det = crossP d1 d2
    a = crossP p1 p2
    b = crossP p3 p4