{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE FlexibleInstances #-}
module Art.Interpreter ( interpret ) where
import Control.Arrow
import Data.List
import Data.List.NonEmpty hiding (reverse)
import Data.Maybe
import System.Random
import Text.Blaze
import Text.Blaze.Svg11 ((!))
import qualified Text.Blaze.Svg11 as S
import qualified Text.Blaze.Svg11.Attributes as A
import Art.Geometry
import Art.Grammar
import Art.Util
type Bound = (Float, Float, Float, Float)
type BoundRes = Maybe Bound
type Res = (BoundRes, S.Svg)
type State = Vec
emptyRes :: Res
emptyRes = (Nothing, mempty)
combineBounds :: [BoundRes] -> BoundRes
combineBounds boundsM =
let bounds = catMaybes boundsM
(x1, y1, x2, y2) = unzip4 bounds
in if null bounds then Nothing else
Just (minimum x1, minimum y1, maximum x2, maximum y2)
poly :: State -> [Vec] -> Res
poly pos pts =
let newPts = pos : pts
(x, y) = pos
(_, b) = foldl nextRes (pos, Just (x, y, x, y)) newPts
in (b, S.path ! A.d (toValue $ toPath newPts))
where
nextRes ((x, y), b) (dx, dy)
= let (i, j) = (x + dx, y + dy)
in ( (i, j)
, combineBounds [b, Just (i, j, i, j)]
)
circle :: Float -> Vec -> Res
circle rad (x, y)
= ( Just (x - rad, y - rad, x + rad, y + rad)
, S.circle
! A.r (toValue rad)
! A.cx (toValue x)
! A.cy (toValue y))
modifyGroup :: Modifier -> Maybe (S.Svg -> S.Svg)
modifyGroup = \case
Color c -> Just (! A.fill (toValue c))
_ -> Nothing
modifyState :: State -> Modifier -> State
modifyState pos = \case
Move p -> addVecs pos p
_ -> pos
modifySubs :: Modifier -> Symbol -> Symbol
modifySubs (Move _) subs = subs
modifySubs (Scale s) (Circle r) = Circle $ s * r
modifySubs (Scale s) (Poly vs) = Poly $ scaleVec s <$> vs
modifySubs (Rotate r) (Poly vs) = Poly $ rotateZero r <$> vs
modifySubs m (NonTerminal prods)
= NonTerminal $ second (modifySubs m) <$> prods
modifySubs mo (Mod ms a)
= Mod (modifyMod mo <$> ms) $ modifySubs mo a
where
modifyMod (Scale s) (Move m) = Move $ scaleVec s m
modifyMod (Rotate r) (Move m) = Move $ rotateZero r m
modifyMod _ m = m
modifySubs _ subs = subs
in100 :: Int -> Int
in100 = (`mod` 100) . abs
joinRes :: Res -> Res -> Res
joinRes (b1, s1) (b2, s2) = (combineBounds [b1, b2], s1 >> s2)
sequenceRes :: (Monad m, Traversable t) => t (m Res) -> m Res
sequenceRes rs = foldl joinRes emptyRes <$> sequence rs
interpretNonTerminal :: State -> Production -> IO Res
interpretNonTerminal state (prob, sym)
= (< prob) . fromIntegral . in100 <$> randomIO
>>= \case
True -> interpretSymbol state sym
False -> pure emptyRes
interpretSymbol :: State -> Symbol -> IO Res
interpretSymbol state = \case
NonTerminal (x :| []) -> interpretNonTerminal state x
NonTerminal (x :| (y: ys)) ->
sequenceRes (interpretNonTerminal state <$> (x :| y : ys))
Circle r -> pure $ circle r state
Poly pts -> pure $ poly state pts
Mod [] sym -> interpretSymbol state sym
Mod ms sym ->
let groupMods = catMaybes $ modifyGroup <$> ms
ed = if null groupMods then id else foldl (flip fmap) S.g groupMods
sub = interpretMods state ms sym
in second ed <$> sub
where
interpretMods state' [] sym = interpretSymbol state' sym
interpretMods state' (m : ms) sym =
let newState = modifyState state' m
newMods = modifySubs m $ Mod ms sym
in interpretSymbol newState newMods
fourTupLst :: (a, a, a, a) -> [a]
fourTupLst (a, b, c, d) = [a, b, c, d]
toSVG :: Bound -> S.Svg -> S.Svg
toSVG bound
= S.docTypeSvg
! A.version "1.1"
! A.viewbox (toValue $ unwords $ show <$> fourTupLst bound)
boundsToViewBox :: Bound -> Bound
boundsToViewBox (x1, y1, x2, y2) = (x1, y1, x2 - x1, y2 - y1)
interpret :: Symbol -> IO S.Svg
interpret sym =
finalise <$> interpretSymbol (0, 0) sym
where
finalise :: Res -> S.Svg
finalise (bounds, svg) = toSVG (boundsToViewBox (fromMaybe (0, 0, 0, 0) bounds)) svg