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*;