Created
February 13, 2015 05:13
-
-
Save triplefox/d017b47f32de68ac6c2b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
discard """ | |
Slactuate - a rude tweening library. | |
Copyright © 2015 James W. Hofmann | |
Permission is hereby granted, free of charge, to any person obtaining | |
a copy of this software and associated documentation files (the “Software”), | |
to deal in the Software without restriction, including without limitation the | |
rights to use, copy, modify, merge, publish, distribute, sublicense, | |
and/or sell copies of the Software, and to permit persons to whom the Software | |
is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | |
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE | |
OR OTHER DEALINGS IN THE SOFTWARE. | |
How to use: | |
Create some tweens with newTween(). Each tween has an output target, an | |
easing function, a start and goal time, a low and high output range, and an id. | |
When tween is update()'d to a time, the current input and output are written | |
according to the relative start/goal time and the easing and output constraints. | |
The tween target is also written at the same time. | |
Sequences of tweens have an update(time) and also a clear(); clear() | |
walks through the sequence and nils the tweens that exceeded their goal time; | |
Tweens that match their goal exactly are not cleared. | |
alloc() reuses the nil sections or grows the sequence if necessary. | |
You are in charge of: | |
Making ref float variables on desired targets | |
Calling update() with an appropriate time for your application | |
Calling clear() when you are ready to clean up finished tweens | |
Assigning tween ids(if desired) | |
Built-in tween functionality consists of: | |
linear interpolation | |
smoothstep and smootherstep | |
nearest-neighbor and linear interpolated lookup table(as seq[float]) | |
You may also write your own easing functions of the form | |
fn(0 to 1 tween pct):0 to 1 output | |
where both input and output may exceed these ranges. | |
All easing is linearly scaled to the low/high output afterwards. | |
Things this library does not try to do: | |
Be really fast (this would involve a move towards unmanaged pointers) | |
""" | |
import math | |
type | |
Easing* = proc(a0:float):float | |
Tween* = ref object | |
tt : ref float # tween target | |
eg : Easing # easing function | |
st : float # start time | |
gt : float # goal time | |
lo : float # low output | |
ho : float # high output | |
ci : float # current input | |
co : float # current output | |
id : int # user id number | |
proc clamp(v0 : float):float {.inline.}=clamp(v0, 0.0, 1.0) | |
proc lerp(v0 : float, v1 : float, a0 : float):float {.inline.}=(v1 - v0) * a0 + v0 | |
proc newTween*(tt,eg,st,gt,lo,ho,id):Tween= | |
new(result); result.tt = tt; result.eg = eg; result.st = st; | |
result.gt = gt; result.lo = lo; result.ho = ho; result.id = id | |
proc newTween*(tt,eg,st,gt,id):Tween= | |
new(result); result.tt = tt; result.eg = eg; result.st = st; | |
result.gt = gt; result.lo = 0.0; result.ho = 1.0; result.id = id | |
proc update*(t0 : Tween, time : float)= # update to the given time | |
t0.ci = (time - t0.st) / (t0.gt - t0.st) | |
t0.co = lerp(t0.lo, t0.ho, t0.eg(t0.ci)); t0.tt[] = t0.co | |
proc update*(c0 : seq[Tween], time : float)= # update all to the given time | |
for t0 in c0: | |
if t0 != nil: | |
t0.update(time) | |
proc clear*(c0 : var seq[Tween]):seq[Tween]= | |
# clear tweens updated to past their end time; return the removed ones | |
result = @[] | |
if c0.len == 0: | |
return | |
var i0 = c0.high | |
while i0 >= 0: | |
let n0 = c0[i0] | |
if n0 != nil and n0.ci > 1.0: | |
result.add(n0) | |
c0[i0] = nil | |
dec(i0) | |
proc alloc*(c0 : var seq[Tween], t0 : Tween)= # compactly add to sequence | |
for i0 in 0..c0.high: | |
if c0[i0] == nil: | |
c0[i0] = t0 | |
c0.add(t0) | |
proc `$`*(c0 : seq[Tween]):string= | |
result = "" | |
for n0 in c0: | |
if n0 == nil: | |
result = result & "nil " | |
else: | |
result = result & "(" & $(n0.ci) & "," & $(n0.co) & ")" | |
proc linear*(a0 : float):float=clamp(a0) | |
proc smoothstep*(a0 : float):float= | |
let x = clamp(a0); x*x*(3 - 2*x) | |
proc smootherstep*(a0 : float):float= | |
let x = clamp(a0); x*x*x*(x*(x*6-15)+10) | |
proc lutnearest*(s0 : seq[float]):Easing = | |
return proc (a0:float):float= | |
s0[math.round(clamp(a0) * float(s0.high))] | |
proc lutlinear*(s0 : seq[float]):Easing = | |
return proc (a0:float):float= | |
let k0 = clamp(a0) * float(s0.high); let i0 = int(k0) | |
if unlikely(i0>=s0.high): | |
return s0[s0.high] | |
else: | |
return lerp(k0 - float(i0), s0[i0], s0[i0 + 1]); | |
when isMainModule: | |
var tt : ref float | |
new(tt) | |
block: # ref assignment | |
tt[] = 1.5; | |
doAssert(tt[] == 1.5) | |
block: # basic tween | |
var t0 = newTween(tt, linear, 0.0, 1.0, 123) | |
t0.update(-1.0) | |
doAssert(tt[] == 0.0) | |
t0.update(0.0) | |
doAssert(tt[] == 0.0) | |
t0.update(0.5) | |
doAssert(tt[] == 0.5) | |
t0.update(1.0) | |
doAssert(tt[] == 1.0) | |
t0.update(1.5) | |
doAssert(tt[] == 1.0) | |
block: # scaled tween | |
var t0 = newTween(tt, linear, 0.0, 1.0, -10.0, 10.0, 123) | |
t0.update(-1.0) | |
doAssert(tt[] == -10.0) | |
t0.update(0.0) | |
doAssert(tt[] == -10.0) | |
t0.update(0.5) | |
doAssert(tt[] == 0.0) | |
t0.update(1.0) | |
doAssert(tt[] == 10.0) | |
t0.update(1.5) | |
doAssert(tt[] == 10.0) | |
block: # time-scaled tween | |
var t0 = newTween(tt, linear, 0.0, 10.0, -10.0, 10.0, 123) | |
t0.update(-1.0) | |
doAssert(tt[] == -10.0) | |
t0.update(0.0) | |
doAssert(tt[] == -10.0) | |
t0.update(5.0) | |
doAssert(tt[] == 0.0) | |
t0.update(10.0) | |
doAssert(tt[] == 10.0) | |
t0.update(15.0) | |
doAssert(tt[] == 10.0) | |
block: # smoothstep | |
var t0 = newTween(tt, smoothstep, 0.0, 1.0, 123) | |
t0.update(-1.0) | |
doAssert(tt[] == 0.0) | |
t0.update(0.0) | |
doAssert(tt[] == 0.0) | |
t0.update(0.5) | |
doAssert(tt[] == 0.5*0.5*(3 - 2*0.5)) | |
t0.update(1.0) | |
doAssert(tt[] == 1.0) | |
t0.update(1.5) | |
doAssert(tt[] == 1.0) | |
block: # smootherstep | |
var t0 = newTween(tt, smootherstep, 0.0, 1.0, 123) | |
t0.update(-1.0) | |
doAssert(tt[] == 0.0) | |
t0.update(0.0) | |
doAssert(tt[] == 0.0) | |
t0.update(0.5) | |
doAssert(tt[] == 0.5*0.5*0.5*(0.5*(0.5*6-15)+10)) | |
t0.update(1.0) | |
doAssert(tt[] == 1.0) | |
t0.update(1.5) | |
doAssert(tt[] == 1.0) | |
block: # lutnearest | |
var t0 = newTween(tt, lutnearest(@[-1.0,1,2,3,4]), 0.0, 1.0, 123) | |
t0.update(-1.0) | |
doAssert(tt[] == -1.0) | |
t0.update(0.0) | |
doAssert(tt[] == -1.0) | |
t0.update(0.5) | |
doAssert(tt[] == 2.0) | |
t0.update(1.0) | |
doAssert(tt[] == 4.0) | |
t0.update(1.5) | |
doAssert(tt[] == 4.0) | |
block: # lutlinear | |
var t0 = newTween(tt, lutlinear(@[1.0,1,3,4]), 0.0, 1.0, 123) | |
t0.update(-1.0) | |
doAssert(tt[] == 1.0) | |
t0.update(0.0) | |
doAssert(tt[] == 1.0) | |
t0.update(0.5) | |
doAssert(tt[] == 2.0) | |
t0.update(1.0) | |
doAssert(tt[] == 4.0) | |
t0.update(1.5) | |
doAssert(tt[] == 4.0) | |
block: # tween seq | |
type MyTweenObject = ref object | |
tt* : ref float | |
var o0 : MyTweenObject; new(o0); new(o0.tt) | |
var o1 : MyTweenObject; new(o1); new(o1.tt) | |
var tt0 : ref float = o0.tt | |
var tt1 : ref float = o1.tt | |
var t0 = newTween(tt0, linear, 0.0, 1.0, 0) | |
var t1 = newTween(tt1, linear, 0.0, 2.0, 1) | |
var tc : seq[Tween] = @[t0, t1] | |
tc.update(0.0) | |
doAssert(t0.ci == 0.0) | |
doAssert(t1.ci == 0.0) | |
doAssert(o0.tt[] == 0.0) | |
doAssert(o1.tt[] == 0.0) | |
doAssert(o0.tt[] == tt0[]) | |
doAssert(o1.tt[] == tt1[]) | |
tc.update(1.0) | |
doAssert(t0.ci == 1.0) | |
doAssert(t1.ci == 0.5) | |
doAssert(o0.tt[] == 1.0) | |
doAssert(o1.tt[] == 0.5) | |
doAssert(o0.tt[] == tt0[]) | |
doAssert(o1.tt[] == tt1[]) | |
tc.update(2.0) | |
var c0 = tc.clear() | |
doAssert(c0.len == 1) | |
doAssert(c0[0].id == 0) | |
tc.update(3.0) | |
var c1 = tc.clear() | |
doAssert(c1[0].id == 1) | |
tc.alloc(newTween(tt, linear, 3.0, 5.0, 2)) | |
doAssert(tc[0].id == 2) | |
doAssert(o0.tt[] == 1.0) | |
doAssert(o1.tt[] == 1.0) | |
doAssert(o0.tt[] == tt0[]) | |
doAssert(o1.tt[] == tt1[]) | |
echo("OK") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment