1 module re.util.tweens.tween; 2 3 import re.util.tweens.tween_manager; 4 import re.util.tweens.ease; 5 import re.gfx.raytypes; 6 7 public enum TweenState { 8 Running, 9 Paused, 10 Complete 11 } 12 13 interface ITween { 14 @property TweenState state(); 15 void update(float dt); 16 void start(bool attach = true); 17 void add_chain(ITween[] tw...); 18 @property ITween[] get_chain(); 19 @property void delegate(ITween) callback(); 20 void set_callback(void delegate(ITween) cb); 21 } 22 23 /// represents a tween, to be used for easings/interpolation 24 class Tween(T) : ITween { 25 alias TRef = T*; 26 private TRef[] _datas; 27 public const(float[]) from; 28 public const(float[]) to; 29 public const(float) duration; 30 public const(float) delay; 31 public const(EaseFunction) ease; 32 private ITween[] _chain; 33 private float elapsed = 0; 34 private TweenState _state; 35 private void delegate(ITween) _callback; 36 37 this(TRef[] data, T[] from, T[] to, float duration, EaseFunction ease, float delay = 0) { 38 import std.algorithm : map; 39 import std.array : array; 40 41 this._datas = data; 42 this.from = from.map!(x => cast(float) x).array; 43 this.to = to.map!(x => cast(float) x).array; 44 this.duration = duration; 45 this.ease = ease; 46 this.delay = delay; 47 } 48 49 @property TweenState state() { 50 return _state; 51 } 52 53 @property ITween[] get_chain() { 54 return _chain; 55 } 56 57 @property void delegate(ITween) callback() { 58 return _callback; 59 } 60 61 public void set_callback(void delegate(ITween) cb) { 62 _callback = cb; 63 } 64 65 public void update(float dt) { 66 if (_state == TweenState.Paused) 67 return; 68 69 import std.algorithm.comparison : clamp; 70 71 elapsed += dt; 72 73 // check delay 74 if (elapsed < delay) 75 return; 76 77 auto run_time = elapsed - delay; 78 79 // clamp the elapsed time (t) value 80 auto t = clamp(run_time, 0, duration); 81 82 // - update values 83 foreach (i, data; _datas) { 84 // get value from function 85 immutable auto v = ease(t, from[i], to[i] - from[i], duration); 86 // set the value of our data pointer 87 *data = cast(T) v; 88 89 } 90 91 if (run_time >= duration) { 92 _state = TweenState.Complete; 93 } 94 95 // import std.stdio : writefln; 96 97 // writefln("F: %s, T: %s, E: %s, V: %s", from, to, run_time, v); 98 } 99 100 public void start(bool attach) { 101 _state = TweenState.Running; 102 if (attach) { 103 import re.core : Core; 104 105 // add tween to global manager 106 auto tween_mgr = Core.get_manager!TweenManager; 107 assert(!tween_mgr.isNull, "global tween manager not available"); 108 tween_mgr.get.register(this); 109 } 110 } 111 112 public void add_chain(ITween[] tw...) { 113 _chain ~= tw; 114 } 115 } 116 117 /// utility class for starting tweens 118 class Tweener { 119 public static ITween tween(T)(ref T data, T from, T to, float duration, 120 EaseFunction ease, float delay = 0) { 121 import re.math; 122 123 ITween res; 124 static if (is(T == float)) { 125 res = new Tween!float([&data], [from], [to], duration, ease, delay); 126 } else static if (is(T == int)) { 127 res = new Tween!int([&data], [from], [to], duration, ease, delay); 128 } else static if (is(T == Color)) { 129 res = new Tween!ubyte([&data.r, &data.g, &data.b, &data.a], 130 [from.r, from.g, from.b, from.a], [to.r, to.g, to.b, to.a], 131 duration, ease, delay); 132 } else static if (is(T == Vector3)) { 133 res = new Tween!float([&data.x, &data.y, &data.z], [ 134 from.x, from.y, from.z 135 ], [to.x, to.y, to.z], duration, ease, delay); 136 } else { 137 assert(0, "tweening this type is not supported"); 138 } 139 return res; 140 } 141 } 142 143 @("tween-basic") 144 unittest { 145 import std.math : abs; 146 import std.string : format; 147 148 float start = 0; 149 float data = start; 150 float goal = 1; 151 float duration = 1; 152 auto tw = new Tween!float([&data], [start], [goal], duration, &Ease.LinearNone); 153 assert(abs(data - start) < float.epsilon, "tween did not match start"); 154 tw.update(duration); 155 assert(abs(data - goal) < float.epsilon, format("tween did not match goal (was %f)", data)); 156 assert(tw.state == TweenState.Complete); 157 } 158 159 @("tween-int") 160 unittest { 161 import std.string : format; 162 163 int start = 0; 164 int data = start; 165 int goal = 100; 166 float duration = 1; 167 auto tw = Tweener.tween(data, start, goal, duration, &Ease.LinearNone, 0); 168 tw.start(false); // start, but do not attach 169 assert(data == start, "tween did not match start"); 170 tw.update(duration); 171 assert(data == goal, format("tween did not match goal (was %f)", data)); 172 } 173 174 @("tween-color") 175 unittest { 176 import std.string : format; 177 178 Color start = Colors.BLACK; 179 Color data = start; 180 Color goal = Colors.RAYWHITE; 181 float duration = 1; 182 auto tw = Tweener.tween(data, start, goal, duration, &Ease.LinearNone, 0); 183 tw.start(false); // start, but do not attach 184 // auto tw_r = new Tween!ubyte(&data.r, start.r, goal.r, duration, &Ease.LinearNone); 185 // auto tw_g = new Tween!ubyte(&data.g, start.g, goal.g, duration, &Ease.LinearNone); 186 // auto tw_b = new Tween!ubyte(&data.b, start.b, goal.b, duration, &Ease.LinearNone); 187 // auto tw_a = new Tween!ubyte(&data.a, start.a, goal.a, duration, &Ease.LinearNone); 188 assert(data.r == start.r && data.g == start.g && data.b == start.b 189 && data.a == start.a, "tween did not match start"); 190 tw.update(duration); 191 // tw_r.update(duration); 192 // tw_g.update(duration); 193 // tw_b.update(duration); 194 // tw_a.update(duration); 195 assert(data.r == goal.r && data.g == goal.g && data.b == goal.b 196 && data.a == goal.a, format("tween did not match goal (was %f)", data)); 197 }