{-# LANGUAGE UnicodeSyntax, ForeignFunctionInterface #-}
{-# CFILES Numeric/Geometric/Predicates/Interval/IntervalFilterPrimitives.c #-}

{- | These predicates use hardware (SSE) based interval arithmetic based on the algorithms presented in (1). 
     They are intended to be used as a filter before resorting to slower exact computation.

     * These routines return Nothing if the result could not be determined
       exactly from the calculated interval. 

     * Each call toggles the SSE rounding mode to -infinity and back.

     * All computations are done in Double precision.

     * Rewrite specializations are in place for Float and Double that greatly reduce allocations compared to Real.
       Using anything but Float or Double is probably absurdly slow thanks to realToFrac.
       
     * For performance reasons we assume CDouble == Double.

    (1) BRANIMIR LAMBOV. \"INTERVAL ARITHMETIC USING SSE-2\" 
-}

module Numeric.Geometric.Predicates.Interval (cinttSSE, incircleSSE, ccwSSE) where
import Numeric.Geometric.Primitives
import System.IO.Unsafe
import Foreign.Ptr
import Foreign.Marshal
import Foreign.C.Types
import Control.Exception (assert)
import GHC.Float

foreign import ccall unsafe ccw_d       Double  Double  Double  Double  Double  Double  Ptr Double  IO () 
foreign import ccall unsafe incircle_d  Double  Double  Double  Double  Double  Double  Double  Double  Ptr Double  IO () 
foreign import ccall unsafe cintt_d     Double  Double  Double  Ptr Double  IO CInt 


{-# RULES "incircleSSE/Double" incircleSSE = incircleSSE_D #-}
{-# RULES "cinttSSE/Double"    cinttSSE    = cinttSSE_D #-}
{-# RULES "ccwSSE/Double"      ccwSSE      = ccwSSE_D #-}

{-# RULES "incircleSSE/Float" incircleSSE = incircleSSE_F #-}
{-# RULES "cinttSSE/Float"    cinttSSE    = cinttSSE_F #-}
{-# RULES "ccwSSE/Float"      ccwSSE      = ccwSSE_F #-}


-- | Test if p3 is within the closed interval specified by [p1,p2]

cinttSSE  Real a => a  a  a  Maybe Bool 
cinttSSE a b c = cinttSSE_D (realToFrac a) (realToFrac b) (realToFrac c)

-- | Counter-clockwise orientation test. Classifies p3 in relation to the line formed by p1 and p2. 
--   Result: LT=Right, GT=Left, EQ=Coincident 

ccwSSE  Real a => Vector2 a  Vector2 a  Vector2 a  Maybe Ordering
ccwSSE (xa,ya) (xb,yb) (xc,yc) = ccwSSE_D (realToFrac xa,realToFrac ya) 
                                          (realToFrac xb,realToFrac yb) 
                                          (realToFrac xc,realToFrac yc)

-- | Test the relation of a point to the circle formed by (p1..p3). (p1..p3) must be in counterclockwise order. 
--   Result: GT=inside, EQ=border, LT=outside

incircleSSE  Real a => (Vector2 a, Vector2 a, Vector2 a)  Vector2 a  Maybe Ordering
incircleSSE ((x1,y1), (x2,y2), (x3,y3)) (x4,y4) = incircleSSE_D ((realToFrac x1, realToFrac y1), 
                                                                 (realToFrac x2, realToFrac y2), 
                                                                 (realToFrac x3, realToFrac y3)) 
                                                                 (realToFrac x4, realToFrac y4)
---------------------------------------------------

cinttSSE_F  Float  Float  Float  Maybe Bool 
cinttSSE_F a b c = cinttSSE_D (float2Double a) (float2Double b) (float2Double c)

ccwSSE_F  Vector2 Float  Vector2 Float  Vector2 Float  Maybe Ordering
ccwSSE_F (xa,ya) (xb,yb) (xc,yc) = ccwSSE_D (float2Double xa,float2Double ya) 
                                            (float2Double xb,float2Double yb) 
                                            (float2Double xc,float2Double yc)

incircleSSE_F  (Vector2 Float, Vector2 Float, Vector2 Float)  Vector2 Float  Maybe Ordering
incircleSSE_F ((x1,y1), (x2,y2), (x3,y3)) (x4,y4) = incircleSSE_D ((float2Double x1, float2Double y1), 
                                                                   (float2Double x2, float2Double y2), 
                                                                   (float2Double x3, float2Double y3)) 
                                                                   (float2Double x4, float2Double y4)

---------------------------------------------------

cinttSSE_D  Double  Double  Double  Maybe Bool 
cinttSSE_D l h p 
    | l == h  = Just (p == l)
    | otherwise = unsafePerformIO $ allocaArray 2 $ \out  do

                         x  cintt_d l h p out

                         if x == 0 
                           then return Nothing
                           else do 

                             [hi,lo]  peekArray 2 out
                             return . assert (lo <= hi) $ check lo hi
    where
      check lo hi 
          | hi < 0             = Just False
          | lo > 1             = Just False
          | lo >= 0 && hi <= 1 = Just True
          | otherwise          = Nothing


incircleSSE_D  (Vector2 Double, Vector2 Double, Vector2 Double)  Vector2 Double  Maybe Ordering
incircleSSE_D ((x1,y1), (x2,y2), (x3,y3)) (x4,y4) = unsafePerformIO $ allocaArray 2 $ \out  do

           incircle_d x1 y1 
                      x2 y2 
                      x3 y3 
                      x4 y4 out

           [hi,lo]  peekArray 2 out
           return . assert (lo <= hi) $ check lo hi
    where
      check lo hi 
          | lo > 0             = Just GT
          | hi < 0             = Just LT 
          | lo == 0 && hi == 0 = Just EQ
          | otherwise          = Nothing


ccwSSE_D  Vector2 Double  Vector2 Double  Vector2 Double  Maybe Ordering 
ccwSSE_D (x1,y1) (x2,y2) (x3,y3) = unsafePerformIO $ allocaArray 2 $ \out  do

           ccw_d x1 y1 
                 x2 y2 
                 x3 y3 out

           [hi,lo]  peekArray 2 out
           return . assert (lo <= hi) $ check lo hi
    where
      check lo hi 
          | lo > 0             = Just GT
          | hi < 0             = Just LT 
          | lo == 0 && hi == 0 = Just EQ
          | otherwise          = Nothing