{-# LANGUAGE OverloadedStrings #-} ----------------------------------------------------------------------------- -- | -- Copyright : (C) 2015 Dimitri Sabadie -- License : BSD3 -- -- Maintainer : Dimitri Sabadie -- Stability : experimental -- Portability : portable ----------------------------------------------------------------------------- module Data.Spline.Key ( -- * Key type Key(..) , keyValue -- * Interpolation , interpolateKeys , normalizeSampling ) where import Data.Aeson import Data.Text ( Text ) import Linear -- |A 'Key' is a point on the spline with extra information added. It can be, -- for instance, left and right handles for a 'Bezier' curve, or whatever the -- interpolation might need. -- -- @H'old' v@ is used to express no interpolation and holds its latest value -- until the next key. -- -- @'Linear' v@ represents a linear interpolation until the next key. -- -- @'Cosine' v@ represents a cosine interpolation until the next key. -- -- @'CubicHermite' v@ represents a cubic hermitian interpolation until the next -- key. -- -- @'Bezier' l v r@ represents a cubic 'Bezier' interpolation, where 'l' refers -- to the input – left – tangent of the key and 'r' is the -- output – right – tangent of the key. data Key a = Hold a | Linear a | Cosine a | CubicHermite a | Bezier a a a deriving (Eq,Functor,Show) instance (FromJSON a) => FromJSON (Key a) where parseJSON = withObject "key" $ \o -> do interpolation :: String <- o .: "interpolation" value <- o .: "value" if | interpolation == "hold" -> pure (Hold value) | interpolation == "linear" -> pure (Linear value) | interpolation == "cosine" -> pure (Cosine value) | interpolation == "cubic-hermite" -> pure (CubicHermite value) | interpolation == "bezier" -> do left <- o .: "left" right <- o .: "right" pure $ Bezier left value right | otherwise -> fail "unknown interpolation mode" instance (ToJSON a) => ToJSON (Key a) where toJSON k = object $ ["value" .= value,"interpolation" .= interpolation] ++ tangents where value = keyValue k interpolation = keyInterpolation k tangents = case k of Bezier l _ r -> ["left" .= l,"right" .= r] _ -> [] -- |Extract the value out of a 'Key'. keyValue :: Key a -> a keyValue k = case k of Hold a -> a Linear a -> a Cosine a -> a CubicHermite a -> a Bezier _ a _ -> a -- |Extract the interpolation mode from a 'Key'. keyInterpolation :: Key a -> Text keyInterpolation k = case k of Hold{} -> "hold" Linear{} -> "linear" Cosine{} -> "cosine" CubicHermite{} -> "cubic-hermite" Bezier{} -> "bezier" -- |@'interpolateKeys' t start end@ interpolates between 'start' and 'end' using -- 's' as a normalized sampling value. -- -- Satisfies the following laws: -- -- @ -- 'interpolateKeys' 0 start _ = start -- 'interpolateKeys' 1 _ end = end -- @ interpolateKeys :: (Additive a,Floating s) => s -> Key (a s) -> Key (a s) -> a s interpolateKeys s start end = case start of Hold k -> k Linear k -> lerp s b k Cosine k -> lerp ((1 - cos (s * pi)) * 0.5) b k CubicHermite k -> lerp (s * s * (3 - 2 * s)) b k Bezier _ k0 r0 -> case end of Bezier l1 k1 _ -> interpolateBezier s k0 r0 l1 k1 _ -> interpolateBezier s k0 r0 r0 b where b = keyValue end -- @'interpolateBezier' s k0 r0 l1 k1@ performs a 'Bezier' interpolation -- between keys 'k0' and 'k1' using their respective right and left tangents. interpolateBezier :: (Additive a,Floating s) => s -> a s -> a s -> a s -> a s -> a s interpolateBezier s k0 r0 l1 k1 = k0 ^* ms3 ^+^ r0 ^* (3 * ms2 * s) ^+^ l1 ^* (3 * ms * s2) ^+^ k1 ^* s3 where ms = 1 - s ms2 = ms * ms ms3 = ms2 * ms s2 = s * s s3 = s2 * s -- |Normalize a sampling value by clamping and scaling it between two 'Key's. -- -- The following laws should be satisfied in order to get a coherent output: -- -- @ -- sampler :: a s -> s -- -- sampler ('keyValue' k1) <= s >= sampler ('keyValue' k0) -- 0 <= 'normalizeSampling' sampler s k0 k1 <= 1 -- @ normalizeSampling :: (Fractional s) => (a s -> s) -> s -> Key (a s) -> Key (a s) -> s normalizeSampling sampler s k0 k1 = (s - s0) / (s1 - s0) where s0 = sampler (keyValue k0) s1 = sampler (keyValue k1)