packages feed

brush-stroking-0.1.0.0: src/BrushStroking/Asset/Brushes.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE PolyKinds           #-}
{-# LANGUAGE RebindableSyntax    #-}
{-# LANGUAGE ScopedTypeVariables #-}

module BrushStroking.Asset.Brushes
  ( SomeBrush(..)
  , lookupBrush, brushes, brushesList, brushNames
  ,   CircleBrushFields       , circle
  ,  EllipseBrushFields       , ellipse
  , TearDropBrushFields       , tearDrop
  , RoundedTearDropBrushFields, roundedTearDrop
  ) where

-- base
import Prelude
  hiding
    ( Num(..), Floating(..), (^), (/), fromInteger, fromRational )
import Data.Coerce
  ( coerce )
import Data.Kind
  ( Type )
import GHC.Exts
  ( fromString )

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

-- unordered-containers
import Data.HashMap.Strict
  ( HashMap )
import qualified Data.HashMap.Strict as HashMap
  ( fromList, lookup )

-- brush-strokes
import Calligraphy.Brushes
  ( circleBrush, ellipseBrush
  , tearDropBrush
  , roundTearDropBrush )
import Math.Linear
import Math.Ring

-- MetaBrush
import BrushStroking.Brush
  ( BrushName(..), NamedBrush(..), WithParams(..) )
import BrushStroking.Records
  ( KnownSymbols )

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

type SomeBrush :: Type
data SomeBrush where
  SomeBrush
    :: forall brushFields
    .  KnownSymbols brushFields
    => { someBrush :: !( NamedBrush brushFields )
       }
    -> SomeBrush

lookupBrush :: Text -> Maybe SomeBrush
lookupBrush nm = HashMap.lookup ( Text.toLower nm ) brushes

-- | All brushes supported by this application.
brushes :: HashMap Text SomeBrush
brushes =
  HashMap.fromList
    [ ( nm, b )
    | b@( SomeBrush ( NamedBrush { brushName = BrushName nm } ) ) <- brushesList
    ]

-- | Like 'brushes', but in a list (for when order matters).
brushesList :: [ SomeBrush ]
brushesList = [ SomeBrush circle
              , SomeBrush ellipse
              , SomeBrush tearDrop
              , SomeBrush roundedTearDrop ]

brushNames :: [ Text ]
brushNames = map getName brushesList
  where
    getName :: SomeBrush -> Text
    getName ( SomeBrush ( NamedBrush { brushName = BrushName nm } ) ) = nm

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

type CircleBrushFields = '[ "a" ]
-- | A circular brush with the given radius.
circle :: NamedBrush CircleBrushFields
circle =
  NamedBrush
    { brushName     = BrushName "circle"
    , brushFunction = WithParams deflts $ coerce circleBrush
    }
  where
    deflts = ℝ1 10
{-# INLINE circle #-}

type EllipseBrushFields = '[ "a", "b", "phi" ]
-- | An elliptical brush with the given semi-major and semi-minor axes and
-- angle of rotation.
ellipse :: NamedBrush EllipseBrushFields
ellipse =
  NamedBrush
    { brushName     = BrushName "ellipse"
    , brushFunction = WithParams deflts $ coerce ellipseBrush
    }
  where
    deflts = ℝ3 10 7 0
{-# INLINE ellipse #-}

type TearDropBrushFields = '[ "a", "b", "phi" ]
-- | A tear-drop shape with the given half-width, half-height and angle of rotation.
tearDrop :: NamedBrush TearDropBrushFields
tearDrop =
  NamedBrush
    { brushName     = BrushName "tear-drop"
    , brushFunction = WithParams deflts $ coerce tearDropBrush
    }
  where
    deflts = ℝ3 10 7 0
{-# INLINE tearDrop #-}

type RoundedTearDropBrushFields = '[ "a", "b", "phi" ]
-- | A rounded tear-drop shape, inspired by the paper
-- "Calligraphy Brush Trajectory Control of by a Robotic Arm".
roundedTearDrop :: NamedBrush RoundedTearDropBrushFields
roundedTearDrop =
  NamedBrush
    { brushName     = BrushName "rounded tear-drop"
    , brushFunction = WithParams deflts $ coerce roundTearDropBrush
    }
  where
    deflts = ℝ3 14 5 ( 3 * pi / 4 )
{-# INLINE roundedTearDrop #-}