imj-animation-0.1.0.2: src/Imj/Graphics/Animation/Design/UpdateAnimatedPoints.hs
{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE LambdaCase #-}
module Imj.Graphics.Animation.Design.UpdateAnimatedPoints
( updateAnimatedPoints
) where
import Imj.Prelude
import Data.List( length )
import Data.Maybe( fromMaybe )
import Imj.Geo.Discrete
import Imj.Graphics.Animation.Design.Types
import Imj.Iteration
{- | Given a length \( n \) list of animation functions, updates the \( n \)
first levels of an 'AnimatedPoints' using one animation function per level.
An 'AnimatedPoint' at level \( k <= n \) can mutate to an 'AnimatedPoints' by
interacting with its environment:
* if \( k = n \) , the new 'AnimatedPoints' will remain empty.
* if \( k < n \), the new 'AnimatedPoints' will be populated by 'AnimatedPoints'
using the \( k+1 \)th animation function.
-}
updateAnimatedPoints :: [Coords Pos -> Frame -> [AnimatedPoint]]
-- ^ The animation function at index @i@ updates
-- 'AnimatedPoints' at level @i@.
-> (Coords Pos -> InteractionResult)
-- ^ Interaction function
-> Frame
-- ^ Current iteration
-> AnimatedPoints
-> AnimatedPoints
updateAnimatedPoints [] _ _ aps = aps
updateAnimatedPoints (f:fs) interaction globalFrame (AnimatedPoints branches center startFrame) =
let relativeFrame = globalFrame - startFrame
branchesLevel1Mutated = updatePointsAndMutateIfNeeded f center relativeFrame interaction branches
newBranches = map (\case
-- recurse for the 'AnimatedPoints's
Left aps -> Left $ updateAnimatedPoints fs interaction relativeFrame aps
-- the 'AnimatedPoint's are already up-to-date due to updatePointsAndMutateIfNeeded:
Right ap -> Right ap
) branchesLevel1Mutated
in AnimatedPoints (Just newBranches) center startFrame
-- | Doesn't change the existing /level 1/ 'AnimatedPoints's, but can convert some
-- 'AnimatedPoint's to 'AnimatedPoints's.
updatePointsAndMutateIfNeeded :: (Coords Pos -> Frame -> [AnimatedPoint])
-- ^ Geometric animation function
-> Coords Pos
-- ^ Center of the animation
-> Frame
-- ^ Relative frame
-> (Coords Pos -> InteractionResult)
-- ^ Interaction function
-> Maybe [Either AnimatedPoints AnimatedPoint]
-- ^ Current branches
-> [Either AnimatedPoints AnimatedPoint]
-- ^ Updated branches
updatePointsAndMutateIfNeeded animation root frame interaction branches =
let points = animation (assert (interaction root == Stable) root) frame
defaultState = map (\(AnimatedPoint canInteract _ _) -> Right $ AnimatedPoint canInteract root Nothing) points
previousState = fromMaybe defaultState branches
-- if previousState contains only Left(s), the animation does not need to be computed.
-- I wonder if lazyness takes care of that or not?
in combine points previousState frame interaction
combine :: [AnimatedPoint]
-> [Either AnimatedPoints AnimatedPoint]
-> Frame
-> (Coords Pos -> InteractionResult)
-> [Either AnimatedPoints AnimatedPoint]
combine points previousState frame interaction =
let check = allowedPointCountVariation (length previousState) (length points)
in assert check $
zipWith
(combinePoints interaction frame)
points previousState
combinePoints :: (Coords Pos -> InteractionResult)
-> Frame
-> AnimatedPoint
-> Either AnimatedPoints AnimatedPoint
-> Either AnimatedPoints AnimatedPoint
combinePoints interaction frame point@(AnimatedPoint onWall coords _) =
either
Left
(\(AnimatedPoint prevOnWall prevCoords' _) ->
case assert (prevOnWall == onWall) onWall of
DontInteract -> Right point
Interact ->
-- The assert verifies that we can drop the first point of the trajectory.
-- This is because the environment is static.
let prevCoords = assert (interaction prevCoords' == Stable) prevCoords'
trajectory = bresenham $ mkSegment prevCoords coords
in maybe
(Right $ assert (interaction coords == Stable) point)
(\preCollision ->
Left $ AnimatedPoints Nothing preCollision frame)
$ getCoordsBeforeMutation trajectory interaction
)
-- The first point of the trajectory is expected to be stable
getCoordsBeforeMutation :: [Coords Pos] -> (Coords Pos -> InteractionResult) -> Maybe (Coords Pos)
getCoordsBeforeMutation [] _ = error "not supposed to happen"
getCoordsBeforeMutation [_] _ = Nothing
getCoordsBeforeMutation (a:as@(b:_)) interaction =
case interaction b of
Stable -> getCoordsBeforeMutation as interaction
Mutation -> Just a
{- | Verifies that the variation in number of points is allowed:
Number of points generated by /animation functions/ should be always the same,
or change from non-zero to 0 (to indicate the end of the animation).
-}
allowedPointCountVariation :: Int
-- ^ From
-> Int
-- ^ To
-> Bool
allowedPointCountVariation from to =
to == from || to == 0