packages feed

brush-stroking-0.1.0.0: src/BrushStroking/Brush.hs

{-# LANGUAGE AllowAmbiguousTypes   #-}
{-# LANGUAGE PolyKinds             #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE UndecidableInstances  #-}

module BrushStroking.Brush
  ( WithParams(..)
  , BrushName(..), NamedBrush(..)
  , BrushFunction , PointFields
  , provePointFields, duplicates
  )
  where

-- base
import Data.Kind
  ( Type, Constraint )
import Data.List
  ( nub )
import Data.Typeable
  ( Typeable )
import GHC.Exts
  ( Proxy#, proxy# )
import GHC.TypeLits
  ( Symbol, someSymbolVal
  , SomeSymbol(..)
  )
import GHC.TypeNats
  ( Nat, type (<=) )

-- deepseq
import Control.DeepSeq
  ( NFData(..) )

-- hashable
import Data.Hashable
  ( Hashable(..) )

-- text
import Data.Text
  ( Text )
import qualified Data.Text as Text
  ( unpack )

-- brush-strokes
import Calligraphy.Brushes
  ( Brush(..) )
import Math.Differentiable
  ( DiffInterp, IVness(..) )
import Math.Linear

-- MetaBrush
import BrushStroking.Records
  ( KnownSymbols, Length, Record )
import BrushStroking.Serialisable
  ( Serialisable )

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

-- | A brush, with default parameter values.
type WithParams :: Nat -> Type
data WithParams nbParams =
  WithParams
    { defaultParams :: ℝ nbParams
    , withParams :: Brush nbParams
    }

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

-- | A brush function: a function from a record of parameters to a closed spline.
type BrushFunction :: [ Symbol ] -> Type
type BrushFunction brushFields =
  WithParams ( Length brushFields )

type BrushName :: [ Symbol ] -> Type
newtype BrushName brushFields = BrushName Text
  deriving newtype ( Eq, Ord, NFData, Hashable )
instance Show ( BrushName brushFields ) where
  show ( BrushName nm ) = Text.unpack nm
type role BrushName representational

type NamedBrush :: [ Symbol ] -> Type
data NamedBrush brushFields where
  NamedBrush
    :: forall brushFields nbBrushFields
    .  ( nbBrushFields ~ Length brushFields
       , 1 <= nbBrushFields
       , KnownSymbols brushFields
       , PointFields brushFields

       , Show ( ℝ nbBrushFields ), NFData ( ℝ nbBrushFields )
       )
    => { brushName     :: !( BrushName brushFields )
       , brushFunction :: !( BrushFunction brushFields )
       }
    -> NamedBrush brushFields

instance Show ( NamedBrush brushFields ) where
  show ( NamedBrush { brushName } ) = show brushName
instance NFData ( NamedBrush brushFields ) where
  rnf ( NamedBrush { brushName } )
    = rnf brushName
instance Eq ( NamedBrush brushFields ) where
  NamedBrush { brushName = name1 } == NamedBrush { brushName = name2 }
    = name1 == name2
instance Ord ( NamedBrush brushFields ) where
  compare ( NamedBrush { brushName = name1 } ) ( NamedBrush { brushName = name2 } )
    = compare name1 name2
instance Hashable ( NamedBrush brushFields ) where
  hashWithSalt salt ( NamedBrush { brushName } ) =
    hashWithSalt salt brushName

type PointFields :: [ Symbol ] -> Constraint
class ( KnownSymbols pointFields, Typeable pointFields
      , Serialisable   ( Record pointFields )
      , Show           ( Record pointFields )
      , Show           ( ℝ ( Length pointFields ) )
      , NFData         ( Record pointFields )
      , Representable  Double   ( ℝ ( Length pointFields ) )
      , RepDim ( ℝ ( Length pointFields ) ) ~ Length pointFields
      , DiffInterp 2 NonIV ( Length pointFields )
      , DiffInterp 3 IsIV  ( Length pointFields )
      )
   => PointFields pointFields where { }
instance ( KnownSymbols pointFields, Typeable pointFields
         , Serialisable   ( Record pointFields )
         , Show           ( Record pointFields )
         , Show           ( ℝ ( Length pointFields ) )
         , NFData         ( Record pointFields )
         , Representable  Double   ( ℝ ( Length pointFields ) )
         , RepDim ( ℝ ( Length pointFields ) ) ~ Length pointFields
         , DiffInterp 2 NonIV ( Length pointFields )
         , DiffInterp 3 IsIV  ( Length pointFields )
         )
      => PointFields pointFields where { }

-- | Assumes the input has no duplicates (doesn't check.)
provePointFields :: [ Text ]
                 -> ( forall pointFields. PointFields pointFields => Proxy# pointFields -> r )
                 -> r
provePointFields fieldNames k =
  case fieldNames of
    []
      -> k ( proxy# @'[] )
    [ f1 ]
      | SomeSymbol @f1 _ <- someSymbolVal ( Text.unpack f1 )
      -> k ( proxy# @'[ f1 ] )
    [ f1, f2 ]
      | SomeSymbol @f1 _ <- someSymbolVal ( Text.unpack f1 )
      , SomeSymbol @f2 _ <- someSymbolVal ( Text.unpack f2 )
      -> k ( proxy# @'[ f1, f2 ] )
    [ f1, f2, f3 ]
      | SomeSymbol @f1 _ <- someSymbolVal ( Text.unpack f1 )
      , SomeSymbol @f2 _ <- someSymbolVal ( Text.unpack f2 )
      , SomeSymbol @f3 _ <- someSymbolVal ( Text.unpack f3 )
      -> k ( proxy# @'[ f1, f2, f3 ] )
    [ f1, f2, f3, f4 ]
      | SomeSymbol @f1 _ <- someSymbolVal ( Text.unpack f1 )
      , SomeSymbol @f2 _ <- someSymbolVal ( Text.unpack f2 )
      , SomeSymbol @f3 _ <- someSymbolVal ( Text.unpack f3 )
      , SomeSymbol @f4 _ <- someSymbolVal ( Text.unpack f4 )
      -> k ( proxy# @'[ f1, f2, f3, f4 ] )
    _ -> error $ "I haven't defined ℝ " ++ show ( length fieldNames )
{-# INLINE provePointFields #-}

duplicates :: [ Text ] -> [ Text ]
duplicates = nub . duplicatesAcc [] []
  where
    duplicatesAcc :: [ Text ] -> [ Text ] -> [ Text ] -> [ Text ]
    duplicatesAcc _    dups [] = dups
    duplicatesAcc seen dups ( k : kvs )
      | k `elem` seen
      = duplicatesAcc seen ( k : dups ) kvs
      | otherwise
      = duplicatesAcc ( k : seen ) dups kvs