{-# LANGUAGE CPP, OverloadedStrings #-} ----------------------------------------------------------------------------- -- | -- Copyright : (C) 2015 Dimitri Sabadie -- License : BSD3 -- -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> -- Stability : experimental -- Portability : portable -- -- This module defines 'Spline's and related functions. Because a 'Spline' -- requires 'Key's, the "Data.Spline.Key" module is also exported. ----------------------------------------------------------------------------- module Data.Spline.Curve ( -- * Splines Spline , splineKeys -- * Building splines , spline -- * Sampling splines , sample -- * Re-exported , module Data.Spline.Key ) where import Control.Monad ( guard ) import Data.Aeson import Data.List ( sortBy ) import Data.Ord ( comparing ) import Data.Spline.Key import Data.Vector ( Vector, (!?), fromList ) import Linear ( Additive ) import GHC.Generics ( Generic ) -- |A @'Spline' a s@ is a collection of 'Key's with associated interpolation -- modes. -- -- Given two 'Key's which indices are /i/ and /i+1/, the interpolation mode on -- the resulting curve is performed using the interpolation mode of the key /i/. -- Thus, the interpolation mode of the latest key might be ignored. There’s an -- exception, though, when using the 'Bezier' interpolation mode. Feel free -- to dig in the "Key" documentation. newtype Spline a s = Spline { -- |Extract the 'Key's. splineKeys :: Vector (Key (a s)) } deriving (Eq,Generic,Functor,Show) instance (FromJSON (a s), Ord s) => FromJSON ((a s -> s) -> Spline a s) where parseJSON value = do keys <- parseJSON value pure $ \sampler -> spline sampler keys instance (ToJSON (a s)) => ToJSON (Spline a s) where #if MIN_VERSION_aeson(0,10,0) toJSON = genericToJSON defaultOptions toEncoding = genericToEncoding defaultOptions #else toJSON = Array . fmap toJSON . splineKeys #endif -- |Build a @'Spline' a s@. -- -- @a s@ is the type held by 'Key's. For instance, @'V2' Float@. -- -- The first argument of the function, which has type @a s -> s@, is a function -- used to extract the sampling value of each 'Key's. In most cases, that value -- represents the time or the frame of a simulation. That value is used to -- perform sampling comparison. spline :: (Ord s) => (a s -> s) -> [Key (a s)] -> Spline a s spline sampler = Spline . fromList . sortBy (comparing $ sampler . keyValue) -- |Sample a 'Spline' at a given 's' sampling value. If no sample exists, -- yields 'Nothing'. -- -- The first parameter is a /sampler/ function used to extract a comparison -- value. For most curves, the reflected value should be the time or the frame. sample :: (Additive a,Floating s,Ord s) => (a s -> s) -> Spline a s -> s -> Maybe (a s) sample sampler (Spline keys) at = do i <- bsearchLower (\k -> compare at (sampler $ keyValue k)) keys k0 <- keys !? i k1 <- keys !? (i + 1) pure $ interpolateKeys (normalizeSampling sampler at k0 k1) k0 k1 -- Helper binary search that searches the ceiling index for the -- value to be searched according to the predicate. bsearchLower :: (a -> Ordering) -> Vector a -> Maybe Int bsearchLower p v = go 0 (length v - 1) where go start end = do guard (start <= end) ma <- v !? m ma1 <- v !? succ m case p ma of LT -> go start (pred m) EQ -> Just m GT -> if p ma1 == LT then Just m else go (succ m) end where m = (end + start) `div` 2