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