-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Copyright (C) 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE

module Graphics.Implicit.Export.Render.GetSegs (getSegs) where

import Prelude(Bool(True, False), sqrt, (+), (*), (/=), map, (.), filter, ($), (<=))

import Graphics.Implicit.Definitions (, ℝ2, Obj2, Polyline(Polyline))
import Graphics.Implicit.Export.Render.RefineSegs (refine)
import Graphics.Implicit.Export.Util (centroid)

import Data.VectorSpace ((^-^))

{- The goal of getSegs is to create polylines to separate
   the interior and exterior vertices of a square intersecting
   an object described by an implicit function.

      O.....O        O.....O
      :     :        :     :
      :     *        :  ,--*
      *     :   =>   *--   :
      :     :        :     :
      #.....#        #.....#

  An interior point is one at which obj is negative.

  What are all the variables?
  ===========================

  To allow data sharing, lots of values we
  could calculate are instead arguments.


       positions               obj values
       ---------               ----------

  (x1,y2) .. (x2,y2)    obj   x1y2 .. x2y2
     :          :       =>     :       :
  (x1,y1) .. (x2,y1)          x1y1 .. x2y2


               mid points
               ----------

               (midy2V, y2)
                 = midy2

               ......*.....
               :          :
 (x1, midx1V)  *          *  (x2, midx2V)
   = midx1     :          :     = midx2
               :....*.....:

               (midy1V, y1)
                 = midy1

-}
getSegs :: ℝ2 -> ℝ2 -> Obj2 -> (,,,) -> (,,,) -> [Polyline]
getSegs p1@(x,y) p2 obj (x1y1, x2y1, x1y2, x2y2) (midx1V,midx2V,midy1V,midy2V) =
    let
        -- Let's evaluate obj at a few points...
        c = obj (centroid [p1,p2])

        (dx,dy) = p2 ^-^ p1
        res = sqrt (dx*dy)

        midx1 = (x,      midx1V)
        midx2 = (x + dx, midx2V)
        midy1 = (midy1V, y     )
        midy2 = (midy2V, y + dy)

        notPointLine :: Polyline -> Bool
        notPointLine (Polyline [np1,np2]) = np1 /= np2
        notPointLine _ = False

        -- takes straight lines between mid points and subdivides them to
        -- account for sharp corners, etc.

    in map (refine res obj) . filter notPointLine $ case (x1y2 <= 0, x2y2 <= 0,
                                                          x1y1 <= 0, x2y1 <= 0) of

        -- An important point here is orientation. If you imagine going along a
        -- generated segment, the interior should be on the left-hand side.

        -- Empty Cases

        (True,   True,  True,  True) -> []
        (False, False, False, False) -> []

        -- Horizontal Cases
        ( True,  True, False, False) -> [Polyline [midx1, midx2]]
        (False, False,  True,  True) -> [Polyline [midx2, midx1]]

        -- Vertical Cases
        (False,  True, False,  True) -> [Polyline [midy2, midy1]]
        ( True, False,  True, False) -> [Polyline [midy1, midy2]]

        -- Corner Cases
        ( True, False, False, False) -> [Polyline [midx1, midy2]]
        (False,  True,  True,  True) -> [Polyline [midy2, midx1]]
        ( True,  True, False,  True) -> [Polyline [midx1, midy1]]
        (False, False,  True, False) -> [Polyline [midy1, midx1]]
        ( True,  True,  True, False) -> [Polyline [midy1, midx2]]
        (False, False, False,  True) -> [Polyline [midx2, midy1]]
        ( True, False,  True,  True) -> [Polyline [midx2, midy2]]
        (False,  True, False, False) -> [Polyline [midy2, midx2]]

        -- Dual Corner Cases
        (True,  False, False, True)  -> if c <= 0
            then [Polyline [midx1, midy1], Polyline [midx2, midy2]]
            else [Polyline [midx1, midy2], Polyline [midx2, midy1]]

        (False, True, True,  False) -> if c <= 0
            then [Polyline [midy2, midx1], Polyline [midy1, midx2]]
            else [Polyline [midy1, midx1], Polyline [midy2, midx2]]