packages feed

mischief-render-0.1.0.0: src/Mischief/Animation/Spritesheet.hs

module Mischief.Animation.Spritesheet where

import Control.Applicative
import Data.List (find, (!?))
import Data.Text (Text)
import GHC.Stack
import GHC.TypeLits
import KDL qualified
import Linear (V2 (V2))
import Mischief.Assets
import Mischief.Assets.Image
import Mischief.ECS
import Mischief.ECS.Utils
import Mischief.Render.Sprite
import Mischief.Render.Sprite.Common

data Spritesheet = Spritesheet
  { frameSize :: V2 Int,
    padding :: V2 Int,
    size :: V2 Int,
    image :: AssetRef Image
  }
  deriving (Component, Show)

noSpritesheet :: KdlRef Spritesheet
noSpritesheet = KdlValue $ Spritesheet {frameSize = V2 0 0, padding = V2 0 0, size = V2 0 0, image = NullRef}

sheetCoords :: Int -> Spritesheet -> (V2 Int, V2 Int)
sheetCoords index sheet = do
  let boxSize = sheet.padding + sheet.frameSize
  let framesPerRow = sheet.size.x `div` boxSize.x
  let start = V2 (index `mod` framesPerRow) (index `div` framesPerRow)
  (start * (sheet.frameSize + 1), sheet.frameSize)

data AnimationFrames = RangedFrames Int Int | ManualFrames [Int] deriving (Show)

data AnimationTime = FpsTime Float | TotalTime Float deriving (Show)

data Animation = Animation {dummy :: Bool, time :: AnimationTime, frames :: AnimationFrames, loop :: Bool, spritesheet :: KdlRef Spritesheet} deriving (Component, Show)

noAnimation :: AssetRef Animation
noAnimation = pure $ Animation {dummy = True, time = TotalTime 0, frames = ManualFrames [0], loop = False, spritesheet = noSpritesheet}

nframes :: Animation -> Int
nframes Animation {frames = RangedFrames start end} = end - start + 1
nframes Animation {frames = ManualFrames f} = length f

animFrameIndex :: Int -> AnimationFrames -> Maybe Int
animFrameIndex frame (RangedFrames start end) =
  let f = start + frame
   in if f <= end then Just f else Nothing
animFrameIndex frame (ManualFrames f) = f !? frame

animFrameCoords :: Int -> Animation -> System (Maybe SpriteSlice)
animFrameCoords frame animation = do
  let index = animFrameIndex frame animation.frames
  case index of
    Nothing -> pure Nothing
    Just index -> do
      sheet <- asset $ toRef animation.spritesheet
      case sheet of
        Nothing -> pure Nothing
        Just sheet -> do
          pure $ Just $ uncurry SpriteSlice $ sheetCoords index sheet

qanimFrameCoords :: Int -> Animation -> Query System (Maybe SpriteSlice)
qanimFrameCoords frame animation = do
  let index = animFrameIndex frame animation.frames
  case index of
    Nothing -> pure Nothing
    Just index ->
      Just <$> do
        sheet <- qasset $ kdlToAssetRef animation.spritesheet
        pure $ uncurry SpriteSlice $ sheetCoords index sheet

qanimImage :: Animation -> Query System Entity
qanimImage animation = do
  sheet <- qasset $ kdlToAssetRef animation.spritesheet
  qmaybe =<< qlift (getRefEntity sheet.image)

instance Asset Spritesheet where
  type Saveable Spritesheet = False
  loadAsset = loadKdl "spritesheet"

instance KdlAsset Spritesheet where
  decodeKdl path = KDL.children $ do
    frameSize <- KDL.node "frameSize"
    padding <- KDL.node "padding"
    size <- KDL.node "size"
    image <- KDL.nodeWith "image" $ decodeKdl path

    pure $ Spritesheet {frameSize, padding, size, image}

instance Asset Animation where
  type Saveable Animation = False
  loadAsset = loadKdl "animation"

instance KdlAsset Animation where
  decodeKdl path = KDL.children $ do
    time <- animationTimeDecoder
    frames <- animationFramesDecoder
    loop <- animationLoopDecoder
    spritesheet <- KDL.nodeWith "spritesheet" $ decodeKdl path
    pure $ Animation {dummy = False, time, frames, loop, spritesheet}

animationTimeDecoder :: KDL.NodeListDecoder AnimationTime
animationTimeDecoder = f1 <|> f2
  where
    f1 = FpsTime <$> KDL.argAt "fps"
    f2 = TotalTime <$> KDL.argAt "time"

animationFramesDecoder :: KDL.NodeListDecoder AnimationFrames
animationFramesDecoder = f1 <|> f2
  where
    f1 = do
      KDL.nodeWith "range" $ do
        start <- KDL.arg
        RangedFrames start <$> KDL.arg
    f2 = ManualFrames <$> KDL.argsAt "frames"

animationLoopDecoder :: KDL.NodeListDecoder Bool
animationLoopDecoder = f1 <|> f2
  where
    f1 = KDL.argAt "loop"
    f2 = pure False