schell / varying

Continuously varying values, made easy :)
MIT License
40 stars 5 forks source link

[Q] Tween yields strange values as suffix? #27

Closed ggreif closed 6 years ago

ggreif commented 6 years ago
*Control.Varying.Tween> scanTween (tween linear 0 1 6) 0.1 [0.0, 0.1 .. 1] :: Maybe [Double]
Just [0.0,1.6666666666666666e-2,5.000000000000001e-2,0.10000000000000002,0.1666666666666667,0.25000000000000006,0.3500000000000001,0.4666666666666668,0.6000000000000001,0.7500000000000001,0.9166666666666669]

Where is the last (very odd) 0.9166666666666669 value coming from? Maybe I am using it wrongly :-(

Also: what is the semantics of the second argument to scanTween?

schell commented 6 years ago

The semantics could be much clearer. Tweens are splines - they are only piece-wise continuous, so they're never guaranteed to produce a value. The second argument to scanTween is the "last known value" of the stream. It's the same parameter as the second one in outputSteam. Indeed it is the exact same parameter as it is passed directly to outputStream.

The tween you're creating with tween linear 0 1 6 is a stream from 0 to 1 over 6 seconds. The last known value is 0.1, as passed in the second argument (the created tween/spline happens to be defined at the first value and so this parameter is never needed). The stream is being evaluated with 11 steps of dt being [0.0,0.1,0.2,0.30000000000000004,0.4000000000000001,0.5000000000000001,0.6000000000000001,0.7000000000000001,0.8,0.9,1.0]. Each of those is a delta time, not the absolute time elapsed. The tween maintains state between evaluations, so the first evaluation (dt = 0.0) produces 0.0 since no time has elapsed and 0.0 is the starting value. The second step (dt = 0.1) produces 1.6e-2 since 0.1 time has now elapsed. The third (dt = 0.2) produces 5.e-2 since 0.3 time has elapsed, etc. At the final step the tween has only elapsed a total of sum [0.0, 0.1 .. 1] = 5.5 seconds but the tween is set up to last for 6.0 seconds. This is why you're not seeing the value 1.0 you're expecting as the last value.

Typically scanTween would only be used for doctest'ing and inspecting values in ghci. The "idiomatic" way to run your tween would be to turn it into a varying value with tweenStream:

-- in ghci
let twn = tween linear 0 1 1
let v   = tweenStream twn 0 -- giving the stream an initial value of "0" if it ends up undefined during eval
(b, v2) <- runVarT v 0.5
b -- 0.5
schell commented 6 years ago

In general it's really easy to get mixed up about whether the input to streams are deltas or absolutes. I've made that same mistake over and over again. I've even written it in the docs for tween! I'm not surprised it's confusing to others (I'm actually relieved that it's not just me, lol). I'm sorry for it though - do you have any suggestions for clarification? I've considered making a newtype TimeDelta a with a Num instance, but that would still riddle the call sites of runVarT with TimeDelta constructors in most cases.

schell commented 6 years ago

One thing to keep in mind is that if we were using absolute elapsed time we could get rid of VarT (and the rest of this library) altogether and just use the tweening/interpolation functions. At that point the value is purely dependent on the starting, ending and elapsed time.

ggreif commented 6 years ago

Thanks for the explanations, I think I grokked the concept now.