{-# LANGUAGE BangPatterns #-}

-- Author: Gergely Patai
-- from sloth2d: https://github.com/cobbpg/sloth2d/blob/master/Physics/Sloth2D/Geometry2D.hs
-- based on Garey, Johnson, Preparata, runtime O(n log n)

module Graphics.Triangulation.GJPTriangulation where

import Data.List
import Data.Ord
import Data.Vector (Vector, (!))
import qualified Data.Vector as V
import qualified Data.Vector.Algorithms.Intro as V
import Graphics.Formats.Collada.Vector2D3D

data VertexType = TopCap | BottomCap | TopCup | BottomCup | Side
    deriving Show

data Vertex = Vtx
    { idx :: Int
    , prev :: Int
    , next :: Int
    , vtype :: VertexType
    , px :: Float
    , py :: Float
    } deriving Show

type MonotoneSegment = ([Int],[Int])

-- | Descriptor for a pair of features. The ordering stands for the
-- following configurations: @LT@ - V to E, @EQ@ - E to E, @GT - E to
-- V, where E stands for edge and V stands for vertex (in other words,
-- you can think of edges being greater than vertices). The integers
-- are the indices of the features: the vertex itself or the first
-- vertex (in ccw order) of the edge. For instance, @(LT,2,4)@ means
-- the pair formed by vertex 2 of the first body and the edge between
-- vertices 4 and 5 of the second body.
type Separation = (Ordering, Int, Int)

-- | Checking whether an angle is within a given interval.
between :: Angle -> (Angle,Angle) -> Bool
a `between` (a1,a2)
    | a1 <= a2 = a >= a1 && a <= a2
    | otherwise = a >= a1 || a <= a2

infixl 6 +<

-- | The sum of two angles.
(+<) :: Angle -> Angle -> Angle
a1 +< a2 = if a < -pi then a+2*pi
           else if a > pi then a-2*pi
                else a
  where
    a = a1+a2

-- | Linear interpolation between two angles along the smaller arc.
alerp :: Angle -> Angle -> Float -> Angle
alerp a1 a2 t = a1+<(a2+<(-a1))*t

-- | Applying a binary function to consecutive pairs in a vector with
-- wrap-around.
pairsWith :: (a -> a -> b) -> Vector a -> Vector b
pairsWith f vs
    | V.null vs = V.empty
    | otherwise = V.zipWith f vs (V.snoc (V.tail vs) (V.head vs))

-- | The edge vectors of a polygon given as a list of vertices.
edges :: Vector V2 -> Vector V2
edges vs = if V.length vs < 2 then V.empty else pairsWith (flip (-)) vs

-- | The absolute angles (with respect to the x axis) of the edges of
-- a polygon given as a list of vertices.
angles :: Vector V2 -> Vector Angle
angles = V.map dir . edges

-- | The signed area of a simple polygon (positive if vertices are in
-- counter-clockwise order).
area :: Vector V2 -> Float
area vs = 0.5 * V.sum (pairsWith cross vs)

-- | The centroid of a simple polygon.
centroid :: Vector V2 -> V2
centroid vs
    | V.null vs = V 0 0
    | otherwise = divsum (V.foldl1' accum (pairsWith gen vs))
  where
    gen v1 v2 = let c = v1 `cross` v2 in (c,(v1+v2)*.c)
    accum (!c1,!v1) (c2,v2) = (c1+c2,v1+v2)
    divsum (c,v)
        | c /= 0 = v*.(recip (3*c))
        | otherwise = (V.minimum vs+V.maximum vs)*.0.5

-- | The moment of inertia of a simple polygon with respect to the origin.
moment :: Vector V2 -> Float
moment vs
    | V.length vs < 3 = 0
    | otherwise = divsum (V.foldl1' accum (pairsWith gen vs))
  where
    gen v1 v2 = let c = v2 `cross` v1 in (c,(v1 `dot` (v1+v2) + square v2)*c)
    accum (!s1,!s2) (p1,p2) = (s1+p1,s2+p2)
    divsum (s1,s2)
        | s1 /= 0 = s2/(6*s1)
        | otherwise = 0

-- | The convex hull of a collection of vertices in counter-clockwise
-- order. (Andrew's Monotone Chain Algorithm)
convexHull :: Vector V2 -> Vector V2
convexHull vs = case compare (V.length vs) 2 of
    LT -> vs
    EQ -> V.fromList . nub . V.toList $ vs
    GT -> V.fromList (avs' ++ bvs')
  where
    svs = V.modify V.sort vs
    vmin = V.head svs
    vmax = V.last svs
    vd = vmax-vmin

    (avs,bvs) = V.partition (\v -> vd `turnNR` v-vmax) . V.init . V.tail $ svs
    avs' = if V.null avs then [vmin]
           else tail . V.foldl' (flip addVertex) [V.head avs,vmin] $ V.snoc (V.tail avs) vmax
    bvs' = if V.null bvs then [vmax]
           else tail . V.foldr' addVertex [V.last bvs,vmax] $ V.cons vmin (V.init bvs)

    addVertex v (v1:vs@(v2:_)) | v1-v2 `turnNR` v-v1 = addVertex v vs
    addVertex v vs = v:vs

-- | Monotone decomposition of a simple polygon.
monotoneDecomposition :: Vector V2 -> [MonotoneSegment]
monotoneDecomposition vs = (map getIndices . snd) (V.foldl' addVertex ([], []) scvs)
  where
    cw = area vs < 0
    ovs = if cw then vs else V.reverse vs
    getIndices (l,r) = if cw then (map idx l, map idx r)
                       else (map idx' l, map idx' r)
      where
        idx' v = V.length vs - 1 - idx v
    addVertex (mss, out) v = case vtype v of
      -- open new monotone segment with this sole vertex
      TopCap -> (([v], [v]) : mss, out)
      -- split monotone segment: all vertices are added to left side,
      -- only last two to right; this is the only case where we need
      -- to check geometry to find the matching segment
      BottomCap -> let (mss',(msl,msr):mss'') = break isContained mss
                       ms' = (msl, v : msr)
                       ms'' = ([v, head msr], [head msr])
                   in (mss' ++ ms':ms'':mss'', out)
      -- close the segment on the right side using the join vertex and
      -- the next vertex on its other side
      TopCup -> let ([(msl1,msr1),(msl2,msr2)], mssr) = partition isConnected mss
                    (msl1',msr1',msl2',msr2') =
                      if idx v == prev (head msr1)
                      then let i = prev (head msr2)
                               v' = cvs ! i
                           in (msl1, v { prev = i } : msr1, v':v:msl2, v':msr2)
                      else let i = prev (head msr1)
                               v' = cvs ! i
                           in (msl2, v { prev = i } : msr2, v':v:msl1, v':msr1)
                in ((msl1',msr1'):mssr,(msl2',msr2'):out)
      -- close monotone segment (stage for emission, remove from
      -- active collection)
      BottomCup -> let (mss',(msl,msr):mss'') = break isConnected mss
                   in (mss' ++ mss'', (v:msl,v:msr):out)
      -- add to the segment the upper neighbour belongs to
      Side -> let (mss',(msl,msr):mss'') = break isConnected mss
                  ms' = if idx v == next (head msl) then (v:msl, msr) else (msl, v:msr)
              in (mss' ++ ms':mss'', out)
      where
        isConnected ((vl:_), (vr:_)) = idx v == next vl || idx v == prev vr
        isConnected _ = error "isConnected"

        isContained ((vl:_), (vr:_)) = px v > xl && px v <= xr
          where
            vl' = cvs ! (next vl)
            vr' = cvs ! (prev vr)
            xl = px vl + (px vl' - px vl) * (py v - py vl) / (py vl' - py vl)
            xr = px vr + (px vr' - px vr) * (py v - py vr) / (py vr' - py vr)
        isContained _ = error "isContained"

    scvs = V.modify (V.sortBy (comparing py)) cvs
    cvs = V.imap classify ovs
    classify i1 v1@(V x1 y1) = Vtx i1 i0 i2 vty x1 y1
      where
        vty = case (compare y1 y0, compare y1 y2, v2-v1 `turn` v1-v0) of
          (LT, LT, LT) -> BottomCap
          (EQ, LT, LT) -> BottomCap
          (LT, LT, GT) -> TopCap
          (LT, EQ, GT) -> TopCap
          (GT, GT, GT) -> BottomCup
          (EQ, GT, GT) -> BottomCup
          (GT, GT, LT) -> TopCup
          (GT, EQ, LT) -> TopCup
          _ -> Side
        i0 = if i1 == 0 then V.length ovs - 1 else i1-1
        i2 = if i1 == V.length ovs - 1 then 0 else i1+1
        v0@(V _ y0) = ovs ! i0
        v2@(V _ y2) = ovs ! i2

-- | Triangulation of a monotone polygon.
monotoneTriangulation :: Vector V2 -> MonotoneSegment -> [(Int,Int,Int)]
monotoneTriangulation vs (msl,msr) = snd (foldl' addVertex ([si2,si1],[]) sis)
  where
    addVertex (si@(s,i):sis,ts) si'@(s',i')
      | s /= s' = ([si',si], zipWith (if s' then tl else tr) (si:sis) sis ++ ts)
      | concave = (si':si:sis,ts)
      | otherwise = (si':si'':map snd si2s'', zipWith (if s' then tr else tl) sis' sis'' ++ ts)
      where
        concave = isConcave (snd (head sis)) i
        (si2s',si2s'') = break visible (zip (si:sis) sis)
          where
            visible ((_,i1),(_,i2)) = isConcave i2 i1
        (sis',sis'') = unzip si2s'
        si'' = last sis''

        tl (_,i1) (_,i2) = (i',i2,i1)
        tr (_,i1) (_,i2) = (i',i1,i2)

        isConcave i0 i1 = s' == v1-v0 `turnL` v2-v1
          where
            v0 = vs ! i0
            v1 = vs ! i1
            v2 = vs ! i'

    addVertex _ _ = error "addVertex"

    si1:si2:sis = merge msl (init (tail msr))
    merge [] irs = map ((,) True) irs
    merge ils [] = map ((,) False) ils
    merge ils@(il:ils') irs@(ir:irs')
      | y1 < y2 = (True,ir) : merge ils irs'
      | otherwise = (False,il) : merge ils' irs
      where
        V _ y1 = vs ! il
        V _ y2 = vs ! ir

-- | Triangulation of a simple polygon.
triangulation :: Vector V2 -> [(Int, Int, Int)]
triangulation vs = [tri | ms <- monotoneDecomposition vs, tri <- monotoneTriangulation vs ms]

-- | A 5-tuple @(d2,ds,sep,v1,v2)@ that provides distance information
-- on two convex polygons, where @d2@ is the square of the distance,
-- @ds@ is its sign (negative in case of penetration), @sep@ describes
-- the opposing features, while @v1@ and @v2@ are the absolute
-- coordinates of the deepest points within the opposite polygon. If
-- the third parameter is @True@, only negative distances are
-- reported, and the function yields @Nothing@ for non-overlapping
-- polygons. This is more efficient if we are only interested in
-- collisions, since the computation can be cancelled upon finding the
-- first separating axis. If the third parameter is @False@, the
-- result cannot be @Nothing@.
convexSeparation
    :: Vector V2 -- ^ The vertices of the first polygon (vs1)
    -> Vector V2 -- ^ The vertices of the second polygon (vs2)
    -> Bool -- ^ Whether we are only interested in overlapping
    -> Maybe (Float, Float, Separation, V2, V2)
convexSeparation vs1 vs2 onlyCollision
    | onlyCollision = closestPenetratingPair firstValidPair
    | otherwise = Just (closestPair firstValidPair)
  where
    l1 = V.length vs1
    l2 = V.length vs2
    succ1 n = let n' = succ n in if n' >= l1 then 0 else n'
    succ2 n = let n' = succ n in if n' >= l2 then 0 else n'
    pred1 n = if n == 0 then l1-1 else pred n
    pred2 n = if n == 0 then l2-1 else pred n

    firstValidPair = until validSeparation stepBackwards (GT,0,0)

    -- Exhaustive search for the closest feature pair
    closestPair s = go (l1+l2-1) (stepBackwards s) (s,v12) dst
      where
        (dst,v12) = separation s
        go 0 _ (s,(v1,v2)) (sd,sgd) = (sd,-sgd,s,v1,v2)
        go n s sep dst
            | dst < dst' = go n' (stepBackwards s) sep dst
            | otherwise = go n' (stepBackwards s) (s,v12) dst'
          where
            (dst',v12) = separation s
            n' = n-1

    -- Exhaustive search for the closest penetrating feature pair
    closestPenetratingPair s = go (l1+l2-1) (stepBackwards s) (s,v12) dst
      where
        (dst,v12) = separation s
        go 0 _ (s,(v1,v2)) (sd,sgd) = Just (sd,-sgd,s,v1,v2)
        go n s sep dst@(_,sg)
            | sg < 0 = Nothing
            | dst < dst' = go n' (stepBackwards s) sep dst
            | otherwise = go n' (stepBackwards s) (s,v12) dst'
          where
            (dst',v12) = separation s
            n' = n-1
{-
-- Step towards the next feature pair counter-clockwise
stepForward (rel,i1,i2) = case rel of
LT -> (turn e1 e2',i1 ,i2')
EQ -> (turn e1' e2',i1',i2')
GT -> (turn e1' e2 ,i1',i2 )
where
i1' = succ1 i1
i2' = succ2 i2
e1 = vs1 ! i1' - vs1 ! i1
e2 = vs2 ! i2 - vs2 ! i2'
e1' = vs1 ! succ1 i1' - vs1 ! i1'
e2' = vs2 ! i2' - vs2 ! succ2 i2'
-}
    -- Step towards the next feature pair clockwise
    stepBackwards (_,i1,i2) = case turn e2 e1 of
        LT -> (LT,i1 ,i2')
        EQ -> (EQ,i1',i2')
        GT -> (GT,i1',i2 )
      where
        i1' = pred1 i1
        i2' = pred2 i2
        e1 = vs1 ! i1 - vs1 ! i1'
        e2 = vs2 ! i2' - vs2 ! i2

    -- Check if the feature pair is valid (i.e. the edge lies within
    -- the interval defined by the vertex, or the edges are parallel)
    validSeparation (rel,i1,i2) = case rel of
        LT -> turnNR e11 e22 && turnNR e22 e12
        EQ -> parv e12 e22
        GT -> turnNR e21 e12 && turnNR e12 e22
      where
        v1 = vs1 ! i1
        v2 = vs2 ! i2
        e11 = v1 - vs1 ! pred1 i1
        e12 = vs1 ! succ1 i1 - v1
        e21 = vs2 ! pred2 i2 - v2
        e22 = v2 - vs2 ! succ2 i2

    -- Distance information for a given feature pair
    separation (rel,i1,i2) = case rel of
        LT -> swap (s v2 v2' e2 sd2 v1)
        GT -> s v1 v1' e1 sd1 v2
        EQ | sd1 > sd2 -> min (s v1 v1' e1 sd1 v2) (s v1 v1' e1 sd1 v2')
           | otherwise -> swap (min (s v2 v2' e2 sd2 v1) (s v2 v2' e2 sd2 v1'))
      where
        swap (d,(v1,v2)) = (d,(v2,v1))

        v1 = vs1 ! i1
        v2 = vs2 ! i2
        v1' = vs1 ! succ1 i1
        v2' = vs2 ! succ2 i2
        e1 = v1'-v1
        e2 = v2'-v2
        sd1 = square e1
        sd2 = square e2

        -- The squared distance of the v1 to v2 segment and the v3 vertex
        s v1 v2 e12 sd12 v3 = ((sd,signum cp),(v,v3))
          where
            e13 = v3-v1
            e23 = v3-v2
            sd12' = recip sd12
            dp = e12 `dot` e13
            -- negative: separation, positive: penetration
            cp = e12 `cross` e13
            (v,sd) | dp <= 0 = (v1,square e13)
                   | dp >= sd12 = (v2,square e23)
                   | otherwise = (v1+e12*.(dp*sd12'),cp*cp*sd12')