```{-# Language ConstraintKinds #-}
--------------------------------------------------------------------------------
-- |
-- Module     : Geometry.SetOperations
-- Copyright  : (C) 2017 Maksymilian Owsianny
-- 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

```