1 module inochi2d.core.animation.player;
2 import inochi2d.core.puppet;
3 import inochi2d.core.animation;
4 import inochi2d.core.param;
5 import inmath;
6 
7 class AnimationPlayer {
8 private:
9     Puppet puppet;
10     AnimationPlaybackRef[] playingAnimations;
11 
12 public:
13     /**
14         Whether to snap to framerate
15     */
16     bool snapToFramerate = false;
17 
18     /**
19         Construct animation player
20     */
21     this(Puppet puppet) {
22         this.puppet = puppet;
23     }
24 
25     /**
26         Run an update step for the animation player
27     */
28     void update(float delta) {
29         foreach(ref anim; playingAnimations) {
30             if (anim.valid) anim.update(delta);
31         }
32     }
33 
34     /**
35         Gets an animation
36     */
37     AnimationPlaybackRef createOrGet(string name) {
38 
39         // Try fetching from pre-existing
40         foreach(ref AnimationPlaybackRef anim; playingAnimations) {
41             if (anim._name == name) return anim;
42         }
43 
44         // Create new playback
45         if (Animation* anim = name in puppet.getAnimations()) {
46             playingAnimations ~= new AnimationPlayback(this, anim, name);
47             return playingAnimations[$-1];
48         }
49 
50         // Invalid state
51         return null;
52     }
53 
54     /**
55         Convenience function which plays an animation
56     */
57     AnimationPlaybackRef play(string name) {
58         auto anim = createOrGet(name);
59         if (anim) {
60             anim.play();
61         }
62 
63         return anim;
64     }
65 
66     /**
67         pre-render one frame of all animations
68     */
69     void prerenderAll() {
70         foreach(anim; playingAnimations) {
71             anim.render();
72         }
73     }
74 
75     /**
76         Stop all animations
77     */
78     void stopAll(bool immediate=false) {
79         foreach(anim; playingAnimations) {
80             anim.stop(immediate);
81         }
82     }
83 
84     /**
85         Destroy all animations
86     */
87     void destroyAll() {
88         foreach(anim; playingAnimations) {
89             anim.valid = false;
90         }
91         playingAnimations.length = 0;
92     }
93 }
94 
95 struct AnimationPlayback {
96 private:
97     // Base Refs
98     AnimationPlayer player;
99     Animation*      anim;
100     bool            valid = true;
101     string          _name;
102 
103     // Runtime
104     bool    _playLeadOut = false;
105     bool    _paused = false;
106     bool    _playing = false;
107     bool    _looping = false;
108     bool    _stopping = false;
109     float   _time = 0;
110     float   _strength = 1;
111     float   _speed = 1;
112     int     _looped = 0;
113 
114     ref Puppet getPuppet() { return player.puppet; }
115 
116     this(AnimationPlayer player, Animation* anim, string name) {
117         this.player = player;
118         this.anim = anim;
119         this._name = name;
120         this._paused = false;
121         this._playing = false;
122         this._looping = false;
123         this._playLeadOut = false;
124     }
125     
126     // Internal functions
127 
128     void update(float delta) {
129         if (!valid || !isRunning) return;
130         if (_paused) {
131             render();
132             return;
133         }
134 
135         // Time step
136         _time += delta;
137 
138         // Handle looping
139         if (!isPlayingLeadOut && looping && frame >= loopPointEnd) {
140             _time = cast(float)loopPointBegin*anim.timestep;
141             _looped++;
142         }
143 
144         render();
145 
146         // Handle stopping animation completely on lead-out end
147         if (!_looping && isPlayingLeadOut()) {
148             if (frame+1 >= anim.length) {
149                 _playing = false;
150                 _playLeadOut = false;
151                 _stopping = false;
152                 _time = 0;
153                 _looped = 0;
154             }
155         }
156     }
157 
158 public:
159     /// Gets the name of the animation
160     string name() { return _name; }
161 
162     /// Gets whether the animation has run to end
163     bool eof() { return frame >= anim.length; }
164 
165     /// Gets whether this instance is valid
166     bool isValid() { return valid; }
167 
168     /// Gets whether this instance is currently playing
169     bool playing() { return _playing; }
170 
171     /// Gets whether this instance is currently stopping
172     bool stopping() { return _stopping; }
173 
174     /// Gets whether this instance is currently paused
175     bool paused() { return _paused; }
176 
177     /// Gets or sets whether this instance is looping
178     bool looping() { return _looping; }
179     bool looping(bool value) { _looping = value; return value; }
180     
181     /// Gets how many times the animation has looped
182     int looped() { return _looped; }
183 
184     /// Gets or sets the speed multiplier for the animation
185     float speed() { return _speed; }
186     float speed(bool value) { _speed = clamp(value, 1, 10); return value; }
187 
188     /// Gets or sets the strength multiplier (0..1) for the animation
189     float strength() { return _strength; }
190     float strength(float value) { _strength = clamp(value, 0, 1); return value; }
191 
192     /// Gets the current frame of animation
193     int frame() { return cast(int)round(_time / anim.timestep); }
194 
195     /// Gets the current floating point (half-)frame of animation
196     float hframe() { return _time / anim.timestep; }
197     
198     /// Gets the frame looping ends at
199     int loopPointEnd() { return hasLeadOut ? anim.leadOut : anim.length; }
200     
201     /// Gets the frame looping begins at
202     int loopPointBegin() { return hasLeadIn ? anim.leadIn : 0; }
203 
204     /// Gets whether the animation has lead-in
205     bool hasLeadIn() { return anim.leadIn > 0 && anim.leadIn+1 < anim.length; }
206 
207     /// Gets whether the animation has lead-out
208     bool hasLeadOut() { return anim.leadOut > 0 && anim.leadOut+1 < anim.length; }
209 
210     /// Gets whether the animation is playing the leadout
211     bool isPlayingLeadOut() { return ((_playing && !_looping) || _stopping) && _playLeadOut && frame < anim.length; }
212 
213     /// Gets whether the animation is playing the main part or lead out
214     bool isRunning() { return _playing || isPlayingLeadOut; }
215 
216     /// Gets the framerate of the animation
217     int fps() { return cast(int)(1000.0 / (anim.timestep * 1000.0)); }
218 
219     /// Gets playback seconds
220     int seconds() { return cast(int)_time; }
221 
222     /// Gets playback miliseconds
223     int miliseconds() { return cast(int)((_time - cast(float)seconds) * 1000); }
224 
225     /// Gets length in frames
226     int frames() { return anim.length; }
227 
228     /// Gets the backing animation for the current playback
229     Animation* animation() { return anim; }
230 
231     /// Gets the playback ID
232     ptrdiff_t playbackId() {
233         ptrdiff_t idx = -1;
234         foreach(i, ref sanim; player.playingAnimations) 
235             if (sanim._name == this._name) idx = i;
236         
237         return idx;
238     }
239 
240     /**
241         Destroys this animation instance
242     */
243     void destroy() {
244         import std.algorithm.mutation : remove;
245         import std.algorithm.searching : countUntil;
246 
247         this.valid = false;
248         if (playbackId > -1) player.playingAnimations = player.playingAnimations.remove(playbackId);
249     }
250 
251     /**
252         Plays the animation
253     */
254     void play(bool loop=false, bool playLeadOut=true) {
255         if (_paused) _paused = false;
256         else {
257             _looped = 0;
258             _time = 0;
259             _stopping = false;
260             _playing = true;
261             _looping = loop;
262             _playLeadOut = playLeadOut;
263         }
264     }
265 
266     /**
267         Pauses the animation
268     */
269     void pause() {
270         _paused = true;
271     }
272 
273     /**
274         Stops the animation
275     */
276     void stop(bool immediate=false) {
277         if (_stopping) return;
278 
279         bool shouldStopImmediate = immediate || frame == 0 || _paused || !hasLeadOut;
280         _stopping = !shouldStopImmediate;
281         _looping = false;
282         _paused = false;
283         _playing = false;
284         _playLeadOut = !shouldStopImmediate;
285         if (shouldStopImmediate) {
286             _time = 0;
287             _looped = 0;
288         }
289     }
290 
291     /**
292         Seeks the animation
293     */
294     void seek(int frame) {
295         float frameTime = clamp(frame, 0, frames);
296         _time = frameTime*anim.timestep;
297         _looped = 0;
298     }
299     
300     /**
301         Renders the current frame of animation
302 
303         Called internally automatically by the animation player
304     */
305     void render() {
306 
307         // Apply lanes
308         float realStrength = clamp(_strength, 0, 1);
309         foreach(lane; anim.lanes) {
310             lane.paramRef.targetParam.pushIOffsetAxis(
311                 lane.paramRef.targetAxis, 
312                 lane.get(hframe, player.snapToFramerate)*realStrength,
313                 lane.mergeMode
314             );
315         }
316     }
317 
318 }
319 
320 alias AnimationPlaybackRef = AnimationPlayback*;