{-# Language ConstraintKinds #-}
{-# OPTIONS_HADDOCK prune #-}
--------------------------------------------------------------------------------
-- |
-- Module     : Geometry.SetOperations
-- Copyright  : (C) 2017 Maksymilian Owsianny
-- License    : BSD-style (see LICENSE)
-- Maintainer : Maksymilian.Owsianny@gmail.com
--
-- Set Operations of Polytopes. You can read about implementation details of
-- this algorithm in a dedicated <MaxOw.github.io/posts/computational-geometry-set-operations-on-polytopes.html Blog Post>.
--
-- Small example:
--
-- > test :: SetOperation -> Double -> PolyT3D
-- > test op t = fromVolume $ merge op boxA boxB
-- >     where
-- >     boxA = cube
-- >     boxB = toVolume $ Poly3 (papply tr <$> ps) is
-- >     Poly3 ps is = cubePoly3
-- >     tr = translation (V3 (sin (t*0.3) * 0.3) 0.2 0.3)
-- >        <> aboutX (t*20 @@ deg)
-- >        <> aboutY (t*3  @@ deg)
--
-- Rendered:
--
-- <<images/setops3d.gif>>
--
--------------------------------------------------------------------------------
module Geometry.SetOperations (

    -- * Base Functionality
      Volume, emptyVolume
    , toVolume, fromVolume

    , SetOperation (..)
    , merge, merges

    -- * Selected Merge Operations
    , union, unions
    , intersection, intersections
    , difference, differences

    -- * Conversion from/to BReps
    , FromPolytopeRep
    , ToPolytopeRep

    , Poly3 (..)
    , PolyT3 (..)

    -- * Primitives
    , cubePoly3, cube

    -- * Specializations/Synonyms
    , toVolume3D
    , fromVolume3D
    , Volume2D, Volume3D

    , Poly3D
    , PolyT3D
    , Merge

    ) where

import Protolude

import Linear
import Linear.Affine (Point)
import qualified Linear.Affine as Point

import qualified Data.Vector as T

import Geometry.SetOperations.Types
import Geometry.SetOperations.Volume
import Geometry.SetOperations.Clip
import Geometry.SetOperations.BRep

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

-- | Convert an arbitrary polytope boundary representation into a Volume.
toVolume :: (FromPolytopeRep p b v n, Clip b v n, Functor v, Num n)
    => p v n -> Volume b v n
toVolume = makeVolume . fromPolytopeRep

-- | Recover a boundary representation of a Volume.
fromVolume :: ToPolytopeRep p b v n => Volume b v n -> p v n
fromVolume = toPolytopeRep . volumeFacets

-- | Convert a simple 3-BRep polyhedron to a Volume.
toVolume3D :: Poly3D -> Volume3D
toVolume3D = toVolume

-- | Reconstruct a triangulated 3-BRep from a Volume.
fromVolume3D :: Volume3D -> PolyT3D
fromVolume3D = fromVolume

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

type Merge b v n = (Clip b v n, Functor v, Num n)

-- | Merge two Volumes under a specified Set Operation.
merge :: Merge b v n
      => SetOperation -> Volume b v n -> Volume b v n -> Volume b v n
merge = mergeVolumes

-- | Merges list of Volumes under a specified Set Operation. Empty list equals
-- empty set.
merges :: Merge b v n => SetOperation -> [Volume b v n] -> Volume b v n
merges _  []     = emptyVolume
merges op (v:vs) = foldl' (merge op) v vs
-- As to not leak memory on folding just a strict left fold is not enough. The
-- merge operation also needs to be strict since it operates on record with lazy
-- fields of spine lazy structures. Should I change representation to strict or
-- just deepseq it here? TODO: Fix this.

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

-- | Union of two volumes. Convenience synonym for `merge Union`
union :: Merge b v n => Volume b v n -> Volume b v n -> Volume b v n
union = merge Union

-- | Union of list of volumes.
unions :: Merge b v n => [Volume b v n] -> Volume b v n
unions = merges Union

-- | Intersection of two volumes.
intersection :: Merge b v n => Volume b v n -> Volume b v n -> Volume b v n
intersection = merge Intersection

-- | Intersection of list of volumes.
intersections :: Merge b v n => [Volume b v n] -> Volume b v n
intersections = merges Intersection

-- | Difference between two volumes.
difference :: Merge b v n => Volume b v n -> Volume b v n -> Volume b v n
difference = merge Difference

-- | Subtract list of volumes from a given volume.
differences :: Merge b v n => Volume b v n -> [Volume b v n] -> Volume b v n
differences = foldl' (merge Difference)

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

p3 :: a -> a -> a -> Point V3 a
p3 x y z = Point.P $ V3 x y z

-- | Cube represented as a denormalized list of polygons.
cubePoly3 :: Poly3D
cubePoly3 = Poly3 (T.fromList ps) is
    where
    ps = map (subtract 0.5) $
       [ p3 0 0 0, p3 1 0 0, p3 1 0 1, p3 0 0 1
       , p3 0 1 0, p3 1 1 0, p3 1 1 1, p3 0 1 1 ]

    is = map reverse
         [ [ 0, 1, 2, 3 ]
         , [ 1, 5, 6, 2 ]
         , [ 3, 2, 6, 7 ]
         , [ 0, 3, 7, 4 ]
         , [ 7, 6, 5, 4 ]
         , [ 0, 4, 5, 1 ]
         ]

-- | Cube volume.
cube :: Volume3D
cube = toVolume cubePoly3