22483bf74103ba64dfc4a9376fe43eae31639aa4
[platform/core/uifw/dali-core.git] / dali / internal / update / animation / scene-graph-animation.cpp
1 /*
2  * Copyright (c) 2017 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali/internal/update/animation/scene-graph-animation.h>
20
21 // EXTERNAL INCLUDES
22 #include <cmath> // fmod
23
24 // INTERNAL INCLUDES
25 #include <dali/internal/common/memory-pool-object-allocator.h>
26 #include <dali/internal/render/common/performance-monitor.h>
27 #include <dali/public-api/math/math-utils.h>
28 namespace //Unnamed namespace
29 {
30 //Memory pool used to allocate new animations. Memory used by this pool will be released when shutting down DALi
31 Dali::Internal::MemoryPoolObjectAllocator<Dali::Internal::SceneGraph::Animation> gAnimationMemoryPool;
32
33 inline void WrapInPlayRange( float& elapsed, const Dali::Vector2& playRangeSeconds)
34 {
35   if( elapsed > playRangeSeconds.y )
36   {
37     elapsed = playRangeSeconds.x + fmodf((elapsed-playRangeSeconds.x), (playRangeSeconds.y-playRangeSeconds.x));
38   }
39   else if( elapsed < playRangeSeconds.x )
40   {
41     elapsed = playRangeSeconds.y - fmodf( (playRangeSeconds.x - elapsed), (playRangeSeconds.y-playRangeSeconds.x) );
42   }
43 }
44
45 /// Compares the end times of the animators and if the end time is less, then it is moved earlier in the list. If end times are the same, then no change.
46 bool CompareAnimatorEndTimes( const Dali::Internal::SceneGraph::AnimatorBase* lhs, const Dali::Internal::SceneGraph::AnimatorBase* rhs )
47 {
48   return ( ( lhs->GetIntervalDelay() + lhs->GetDuration() ) < ( rhs->GetIntervalDelay() + rhs->GetDuration() ) );
49 }
50
51 } // unnamed namespace
52
53 namespace Dali
54 {
55
56 namespace Internal
57 {
58
59 namespace SceneGraph
60 {
61
62 Animation* Animation::New( float durationSeconds, float speedFactor, const Vector2& playRange, int loopCount, EndAction endAction, EndAction disconnectAction )
63 {
64   return new ( gAnimationMemoryPool.AllocateRawThreadSafe() ) Animation( durationSeconds, speedFactor, playRange, loopCount, endAction, disconnectAction );
65 }
66
67 Animation::Animation( float durationSeconds, float speedFactor, const Vector2& playRange, int loopCount, Dali::Animation::EndAction endAction, Dali::Animation::EndAction disconnectAction )
68 : mPlayRange( playRange ),
69   mDurationSeconds( durationSeconds ),
70   mDelaySeconds( 0.0f ),
71   mElapsedSeconds( playRange.x*mDurationSeconds ),
72   mSpeedFactor( speedFactor ),
73   mProgressMarker( 0.0f ),
74   mPlayedCount( 0 ),
75   mLoopCount(loopCount),
76   mCurrentLoop(0),
77   mEndAction(endAction),
78   mDisconnectAction(disconnectAction),
79   mState(Stopped),
80   mProgressReachedSignalRequired( false ),
81   mAutoReverseEnabled( false )
82 {
83 }
84
85 Animation::~Animation()
86 {
87 }
88
89 void Animation::operator delete( void* ptr )
90 {
91   gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
92 }
93
94 void Animation::SetDuration(float durationSeconds)
95 {
96   mDurationSeconds = durationSeconds;
97 }
98
99 void Animation::SetProgressNotification( float progress )
100 {
101   mProgressMarker = progress;
102   if ( mProgressMarker > 0.0f )
103   {
104     mProgressReachedSignalRequired = true;
105   }
106 }
107
108 void Animation::SetLoopCount(int loopCount)
109 {
110   mLoopCount = loopCount;
111   mCurrentLoop = 0;
112 }
113
114 void Animation::SetEndAction(Dali::Animation::EndAction action)
115 {
116   mEndAction = action;
117 }
118
119 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
120 {
121   if ( mDisconnectAction != action )
122   {
123     mDisconnectAction = action;
124
125     for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
126     {
127       (*iter)->SetDisconnectAction( action );
128     }
129   }
130 }
131
132 void Animation::SetPlayRange( const Vector2& range )
133 {
134   mPlayRange = range;
135
136   // Make sure mElapsedSeconds is within the new range
137
138   if( mState == Stopped )
139   {
140     // Ensure that the animation starts at the right place
141     mElapsedSeconds = mPlayRange.x * mDurationSeconds;
142   }
143   else
144   {
145     // If already past the end of the range, but before end of duration, then clamp will
146     // ensure that the animation stops on the next update.
147     // If not yet at the start of the range, clamping will jump to the start
148     mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x*mDurationSeconds , mPlayRange.y*mDurationSeconds );
149   }
150 }
151
152 void Animation::Play()
153 {
154   // Sort according to end time with earlier end times coming first, if the end time is the same, then the animators are not moved
155   std::stable_sort( mAnimators.Begin(), mAnimators.End(), CompareAnimatorEndTimes );
156
157   mState = Playing;
158
159   if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
160   {
161     mElapsedSeconds = mPlayRange.y * mDurationSeconds;
162   }
163
164   SetAnimatorsActive( true );
165
166   mCurrentLoop = 0;
167 }
168
169 void Animation::PlayFrom( float progress )
170 {
171   // If the animation is already playing this has no effect
172   // Progress is guaranteed to be in range.
173   if( mState != Playing )
174   {
175     mElapsedSeconds = progress * mDurationSeconds;
176     mState = Playing;
177
178     SetAnimatorsActive( true );
179   }
180 }
181
182 void Animation::PlayAfter( float delaySeconds )
183 {
184   if( mState != Playing )
185   {
186     mDelaySeconds = delaySeconds;
187     mState = Playing;
188
189     if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
190     {
191       mElapsedSeconds = mPlayRange.y * mDurationSeconds;
192     }
193
194     SetAnimatorsActive( true );
195
196     mCurrentLoop = 0;
197   }
198 }
199
200 void Animation::Pause()
201 {
202   if (mState == Playing)
203   {
204     mState = Paused;
205   }
206 }
207
208 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
209 {
210   if( action == Dali::Animation::BakeFinal )
211   {
212     if( mSpeedFactor > 0.0f )
213     {
214       mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
215     }
216     else
217     {
218       mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
219     }
220   }
221
222   UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
223 }
224
225 void Animation::SetAnimatorsActive( bool active )
226 {
227   for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
228   {
229     (*iter)->SetActive( active );
230   }
231 }
232
233 bool Animation::Stop(BufferIndex bufferIndex)
234 {
235   bool animationFinished(false);
236
237   if (mState == Playing || mState == Paused)
238   {
239     animationFinished = true; // The actor-thread should be notified of this
240
241     if( mEndAction != Dali::Animation::Discard )
242     {
243       Bake( bufferIndex, mEndAction );
244
245       // Animators are automatically set to inactive in Bake
246     }
247     else
248     {
249       SetAnimatorsActive( false );
250     }
251
252     // The animation has now been played to completion
253     ++mPlayedCount;
254     mCurrentLoop = 0;
255   }
256
257   mElapsedSeconds = mPlayRange.x*mDurationSeconds;
258   mState = Stopped;
259
260   return animationFinished;
261 }
262
263 void Animation::OnDestroy(BufferIndex bufferIndex)
264 {
265   if (mState == Playing || mState == Paused)
266   {
267     if (mEndAction != Dali::Animation::Discard)
268     {
269       Bake( bufferIndex, mEndAction );
270
271       // Animators are automatically set to inactive in Bake
272     }
273     else
274     {
275       SetAnimatorsActive( false );
276     }
277   }
278
279   mState = Destroyed;
280 }
281
282 void Animation::SetLoopingMode( bool loopingMode )
283 {
284   mAutoReverseEnabled = loopingMode;
285
286   for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
287   {
288     // Send some variables together to figure out the Animation status
289     (*iter)->SetSpeedFactor( mSpeedFactor );
290     (*iter)->SetLoopCount( mLoopCount );
291
292     (*iter)->SetLoopingMode( loopingMode );
293   }
294 }
295
296 void Animation::AddAnimator( OwnerPointer<AnimatorBase>& animator )
297 {
298   animator->ConnectToSceneGraph();
299   animator->SetDisconnectAction( mDisconnectAction );
300
301   mAnimators.PushBack( animator.Release() );
302 }
303
304 void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished, bool& progressReached )
305 {
306   looped = false;
307   finished = false;
308
309   if (mState == Stopped || mState == Destroyed)
310   {
311     // Short circuit when animation isn't running
312     return;
313   }
314
315   // The animation must still be applied when Paused/Stopping
316   if (mState == Playing)
317   {
318     // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
319     if( mDelaySeconds > 0.0f )
320     {
321       float reduceSeconds = fabsf( elapsedSeconds * mSpeedFactor );
322       if( reduceSeconds > mDelaySeconds )
323       {
324         if( mSpeedFactor < 0.0f )
325         {
326           mElapsedSeconds -= reduceSeconds - mDelaySeconds;
327         }
328         else
329         {
330           mElapsedSeconds += reduceSeconds - mDelaySeconds;
331         }
332         mDelaySeconds = 0.0f;
333       }
334       else
335       {
336         mDelaySeconds -= reduceSeconds;
337       }
338     }
339     else
340     {
341       mElapsedSeconds += ( elapsedSeconds * mSpeedFactor );
342     }
343
344     if ( mProgressReachedSignalRequired && ( mElapsedSeconds >= mProgressMarker ) )
345     {
346       // The application should be notified by NotificationManager, in another thread
347       progressReached = true;
348       mProgressReachedSignalRequired = false;
349     }
350   }
351
352   Vector2 playRangeSeconds = mPlayRange * mDurationSeconds;
353
354   if( 0 == mLoopCount )
355   {
356     // loop forever
357     WrapInPlayRange(mElapsedSeconds, playRangeSeconds);
358
359     UpdateAnimators(bufferIndex, false, false );
360
361     // don't increment mPlayedCount as event loop tracks this to indicate animation finished (end of all loops)
362   }
363   else if( mCurrentLoop < mLoopCount - 1) // '-1' here so last loop iteration uses play once below
364   {
365     // looping
366     looped =  (mState == Playing                                                 &&
367                (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
368                 ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
369
370     WrapInPlayRange( mElapsedSeconds, playRangeSeconds );
371
372     UpdateAnimators(bufferIndex, false, false );
373
374     if(looped)
375     {
376       ++mCurrentLoop;
377       mProgressReachedSignalRequired = mProgressMarker > 0.0f;
378       // don't increment mPlayedCount until the finished final loop
379     }
380   }
381   else
382   {
383     // playing once (and last mCurrentLoop)
384     finished = (mState == Playing                                                 &&
385                 (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
386                  ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
387
388     // update with bake if finished
389     UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::Discard), finished );
390
391     if(finished)
392     {
393       // The animation has now been played to completion
394       ++mPlayedCount;
395
396       // loop iterations come to this else branch for their final iterations
397       if( mCurrentLoop < mLoopCount)
398       {
399         ++mCurrentLoop;
400         DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
401       }
402
403       mProgressReachedSignalRequired = mProgressMarker > 0.0f;
404       mElapsedSeconds = playRangeSeconds.x;
405       mState = Stopped;
406     }
407   }
408 }
409
410 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
411 {
412   const Vector2 playRange( mPlayRange * mDurationSeconds );
413   float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
414
415   //Loop through all animators
416   bool applied(true);
417   for ( AnimatorIter iter = mAnimators.Begin(); iter != mAnimators.End(); )
418   {
419     AnimatorBase *animator = *iter;
420
421     if( animator->Orphan() )
422     {
423       //Remove animators whose PropertyOwner has been destroyed
424       iter = mAnimators.Erase(iter);
425     }
426     else
427     {
428       if( animator->IsEnabled() )
429       {
430         const float intervalDelay( animator->GetIntervalDelay() );
431
432         if( elapsedSecondsClamped >= intervalDelay )
433         {
434           // Calculate a progress specific to each individual animator
435           float progress(1.0f);
436           const float animatorDuration = animator->GetDuration();
437           if (animatorDuration > 0.0f) // animators can be "immediate"
438           {
439             progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f , 1.0f );
440           }
441           animator->Update(bufferIndex, progress, bake);
442         }
443         applied = true;
444       }
445       else
446       {
447         applied = false;
448       }
449
450       if ( animationFinished )
451       {
452         animator->SetActive( false );
453       }
454
455       if (applied)
456       {
457         INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
458       }
459
460       ++iter;
461     }
462   }
463
464 }
465
466 } // namespace SceneGraph
467
468 } // namespace Internal
469
470 } // namespace Dali