Merge "Ensure animations take TimePeriod into account when running animators" into...
[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
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->GetInitialDelay() + lhs->GetDuration() ) < ( rhs->GetInitialDelay() + 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 : mDurationSeconds(durationSeconds),
69   mSpeedFactor( speedFactor ),
70   mEndAction(endAction),
71   mDisconnectAction(disconnectAction),
72   mState(Stopped),
73   mElapsedSeconds(playRange.x*mDurationSeconds),
74   mPlayedCount(0),
75   mLoopCount(loopCount),
76   mCurrentLoop(0),
77   mPlayRange( playRange )
78 {
79 }
80
81 Animation::~Animation()
82 {
83 }
84
85 void Animation::operator delete( void* ptr )
86 {
87   gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
88 }
89
90 void Animation::SetDuration(float durationSeconds)
91 {
92   mDurationSeconds = durationSeconds;
93 }
94
95 void Animation::SetLoopCount(int loopCount)
96 {
97   mLoopCount = loopCount;
98   mCurrentLoop = 0;
99 }
100
101 void Animation::SetEndAction(Dali::Animation::EndAction action)
102 {
103   mEndAction = action;
104 }
105
106 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
107 {
108   if ( mDisconnectAction != action )
109   {
110     mDisconnectAction = action;
111
112     for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
113     {
114       (*iter)->SetDisconnectAction( action );
115     }
116   }
117 }
118
119 void Animation::SetPlayRange( const Vector2& range )
120 {
121   mPlayRange = range;
122
123   // Make sure mElapsedSeconds is within the new range
124
125   if( mState == Stopped )
126   {
127     // Ensure that the animation starts at the right place
128     mElapsedSeconds = mPlayRange.x * mDurationSeconds;
129   }
130   else
131   {
132     // If already past the end of the range, but before end of duration, then clamp will
133     // ensure that the animation stops on the next update.
134     // If not yet at the start of the range, clamping will jump to the start
135     mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x*mDurationSeconds , mPlayRange.y*mDurationSeconds );
136   }
137 }
138
139 void Animation::Play()
140 {
141   // Sort according to end time with earlier end times coming first, if the end time is the same, then the animators are not moved
142   std::stable_sort( mAnimators.Begin(), mAnimators.End(), CompareAnimatorEndTimes );
143
144   mState = Playing;
145
146   if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
147   {
148     mElapsedSeconds = mPlayRange.y * mDurationSeconds;
149   }
150
151   SetAnimatorsActive( true );
152
153   mCurrentLoop = 0;
154 }
155
156 void Animation::PlayFrom( float progress )
157 {
158   // If the animation is already playing this has no effect
159   // Progress is guaranteed to be in range.
160   if( mState != Playing )
161   {
162     mElapsedSeconds = progress * mDurationSeconds;
163     mState = Playing;
164
165     SetAnimatorsActive( true );
166   }
167 }
168
169 void Animation::Pause()
170 {
171   if (mState == Playing)
172   {
173     mState = Paused;
174   }
175 }
176
177 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
178 {
179   if( action == Dali::Animation::BakeFinal )
180   {
181     if( mSpeedFactor > 0.0f )
182     {
183       mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
184     }
185     else
186     {
187       mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
188     }
189   }
190
191   UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
192 }
193
194 void Animation::SetAnimatorsActive( bool active )
195 {
196   for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
197   {
198     (*iter)->SetActive( active );
199   }
200 }
201
202 bool Animation::Stop(BufferIndex bufferIndex)
203 {
204   bool animationFinished(false);
205
206   if (mState == Playing || mState == Paused)
207   {
208     animationFinished = true; // The actor-thread should be notified of this
209
210     if( mEndAction != Dali::Animation::Discard )
211     {
212       Bake( bufferIndex, mEndAction );
213
214       // Animators are automatically set to inactive in Bake
215     }
216     else
217     {
218       SetAnimatorsActive( false );
219     }
220
221     // The animation has now been played to completion
222     ++mPlayedCount;
223     mCurrentLoop = 0;
224   }
225
226   mElapsedSeconds = mPlayRange.x*mDurationSeconds;
227   mState = Stopped;
228
229   return animationFinished;
230 }
231
232 void Animation::OnDestroy(BufferIndex bufferIndex)
233 {
234   if (mState == Playing || mState == Paused)
235   {
236     if (mEndAction != Dali::Animation::Discard)
237     {
238       Bake( bufferIndex, mEndAction );
239
240       // Animators are automatically set to inactive in Bake
241     }
242     else
243     {
244       SetAnimatorsActive( false );
245     }
246   }
247
248   mState = Destroyed;
249 }
250
251 void Animation::AddAnimator( AnimatorBase* animator )
252 {
253   animator->ConnectToSceneGraph();
254   animator->SetDisconnectAction( mDisconnectAction );
255
256   mAnimators.PushBack( animator );
257 }
258
259 void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished )
260 {
261   looped = false;
262   finished = false;
263
264   if (mState == Stopped || mState == Destroyed)
265   {
266     // Short circuit when animation isn't running
267     return;
268   }
269
270   // The animation must still be applied when Paused/Stopping
271   if (mState == Playing)
272   {
273     mElapsedSeconds += elapsedSeconds * mSpeedFactor;
274   }
275
276   Vector2 playRangeSeconds = mPlayRange * mDurationSeconds;
277
278   if( 0 == mLoopCount )
279   {
280     // loop forever
281     WrapInPlayRange(mElapsedSeconds, playRangeSeconds);
282
283     UpdateAnimators(bufferIndex, false, false);
284
285     // don't increment mPlayedCount as event loop tracks this to indicate animation finished (end of all loops)
286   }
287   else if( mCurrentLoop < mLoopCount - 1) // '-1' here so last loop iteration uses play once below
288   {
289     // looping
290     looped =  (mState == Playing                                                 &&
291                (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
292                 ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
293
294     WrapInPlayRange( mElapsedSeconds, playRangeSeconds );
295
296     UpdateAnimators(bufferIndex, false, false);
297
298     if(looped)
299     {
300       ++mCurrentLoop;
301       // don't increment mPlayedCount until the finished final loop
302     }
303   }
304   else
305   {
306     // playing once (and last mCurrentLoop)
307     finished = (mState == Playing                                                 &&
308                 (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
309                  ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
310
311     // update with bake if finished
312     UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::Discard), finished);
313
314     if(finished)
315     {
316       // The animation has now been played to completion
317       ++mPlayedCount;
318
319       // loop iterations come to this else branch for their final iterations
320       if( mCurrentLoop < mLoopCount)
321       {
322         ++mCurrentLoop;
323         DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
324       }
325
326       mElapsedSeconds = playRangeSeconds.x;
327       mState = Stopped;
328     }
329   }
330 }
331
332 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
333 {
334   const Vector2 playRange( mPlayRange * mDurationSeconds );
335   float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
336
337   //Loop through all animators
338   bool applied(true);
339   for ( AnimatorIter iter = mAnimators.Begin(); iter != mAnimators.End(); )
340   {
341     AnimatorBase *animator = *iter;
342
343     if( animator->Orphan() )
344     {
345       //Remove animators whose PropertyOwner has been destroyed
346       iter = mAnimators.Erase(iter);
347     }
348     else
349     {
350       if( animator->IsEnabled() )
351       {
352         const float initialDelay( animator->GetInitialDelay() );
353         if( elapsedSecondsClamped >= initialDelay )
354         {
355           // Calculate a progress specific to each individual animator
356           float progress(1.0f);
357           const float animatorDuration = animator->GetDuration();
358           if (animatorDuration > 0.0f) // animators can be "immediate"
359           {
360             progress = Clamp((elapsedSecondsClamped - initialDelay) / animatorDuration, 0.0f , 1.0f );
361           }
362           animator->Update(bufferIndex, progress, bake);
363         }
364         applied = true;
365       }
366       else
367       {
368         applied = false;
369       }
370
371       if ( animationFinished )
372       {
373         animator->SetActive( false );
374       }
375
376       if (applied)
377       {
378         INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
379       }
380
381       ++iter;
382     }
383   }
384
385 }
386
387 } // namespace SceneGraph
388
389 } // namespace Internal
390
391 } // namespace Dali