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 }