{-# 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