[dali_1.3.47] Merge branch 'devel/master'
[platform/core/uifw/dali-core.git] / dali / internal / update / animation / scene-graph-animation.cpp
1 /*
2  * Copyright (c) 2018 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 float& playRangeStartSeconds, const float& playRangeEndSeconds)
34 {
35   if( elapsed > playRangeEndSeconds )
36   {
37     elapsed = playRangeStartSeconds + fmodf( ( elapsed - playRangeStartSeconds ), ( playRangeEndSeconds - playRangeStartSeconds ) );
38   }
39   else if( elapsed < playRangeStartSeconds )
40   {
41     elapsed = playRangeEndSeconds - fmodf( ( playRangeStartSeconds - elapsed ), ( playRangeEndSeconds - playRangeStartSeconds ) );
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, int32_t 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, int32_t 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(int32_t 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     // Sign value of speed factor. It can optimize many arithmetic comparision
319     float signSpeedFactor = ( mSpeedFactor < 0.0f ) ? -1.f : 1.f;
320
321     // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
322     if( mDelaySeconds > 0.0f )
323     {
324       float reduceSeconds = fabsf( elapsedSeconds * mSpeedFactor );
325       if( reduceSeconds > mDelaySeconds )
326       {
327         // add overflowed time to mElapsedSecond.
328         // If speed factor > 0, add it. if speed factor < 0, subtract it.
329         float overflowSeconds = reduceSeconds - mDelaySeconds;
330         mElapsedSeconds += signSpeedFactor * overflowSeconds;
331         mDelaySeconds = 0.0f;
332       }
333       else
334       {
335         mDelaySeconds -= reduceSeconds;
336       }
337     }
338     else
339     {
340       mElapsedSeconds += ( elapsedSeconds * mSpeedFactor );
341     }
342
343     const float playRangeStartSeconds = mPlayRange.x * mDurationSeconds;
344     const float playRangeEndSeconds = mPlayRange.y * mDurationSeconds;
345     // Final reached seconds. It can optimize many arithmetic comparision
346     float edgeRangeSeconds = ( mSpeedFactor < 0.0f ) ? playRangeStartSeconds : playRangeEndSeconds;
347
348     // Optimized Factors.
349     // elapsed >  edge   --> check if looped
350     // elapsed >= marker --> check if elapsed reached to marker in normal case
351     // edge    >= marker --> check if elapsed reached to marker in looped case
352     float elapsedFactor = signSpeedFactor * mElapsedSeconds;
353     float edgeFactor = signSpeedFactor * edgeRangeSeconds;
354     float markerFactor = signSpeedFactor * mProgressMarker;
355
356     // check it is looped
357     looped = ( elapsedFactor > edgeFactor );
358
359     if( looped )
360     {
361       WrapInPlayRange( mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds );
362
363       // Recalculate elapsedFactor here
364       elapsedFactor = signSpeedFactor * mElapsedSeconds;
365
366       if( mLoopCount != 0 )
367       {
368         // Check If this animation is finished
369         ++mCurrentLoop;
370         if( mCurrentLoop >= mLoopCount )
371         {
372           DALI_ASSERT_DEBUG( mCurrentLoop == mLoopCount );
373           finished = true;
374
375           // The animation has now been played to completion
376           ++mPlayedCount;
377
378           // Make elapsed second as edge of range forcely.
379           mElapsedSeconds = edgeRangeSeconds + signSpeedFactor * Math::MACHINE_EPSILON_10;
380           UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::Discard), finished );
381
382           // After update animation, mElapsedSeconds must be begin of value
383           mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
384           mState = Stopped;
385         }
386       }
387
388       // when it is on looped state, 2 case to send progress signal.
389       // (require && range_value >= marker) ||         << Signal at previous loop
390       // (marker > 0 && !finished && elaped >= marker) << Signal at current loop
391       if( ( mProgressMarker > 0.0f ) && !finished && ( elapsedFactor >= markerFactor ) )
392       {
393         // The application should be notified by NotificationManager, in another thread
394         progressReached = true;
395         mProgressReachedSignalRequired = false;
396       }
397       else
398       {
399         if( mProgressReachedSignalRequired && ( edgeFactor >= markerFactor ) )
400         {
401           progressReached = true;
402         }
403         mProgressReachedSignalRequired = mProgressMarker > 0.0f;
404       }
405     }
406     else
407     {
408       // when it is not on looped state, only 1 case to send progress signal.
409       // (require && elaped >= marker)
410       if( mProgressReachedSignalRequired && ( elapsedFactor >= markerFactor ) )
411       {
412         // The application should be notified by NotificationManager, in another thread
413         progressReached = true;
414         mProgressReachedSignalRequired = false;
415       }
416     }
417   }
418
419   // Already updated when finished. So skip.
420   if( !finished )
421   {
422     UpdateAnimators(bufferIndex, false, false );
423   }
424 }
425
426 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
427 {
428   const Vector2 playRange( mPlayRange * mDurationSeconds );
429   float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
430
431   //Loop through all animators
432   bool applied(true);
433   for ( AnimatorIter iter = mAnimators.Begin(); iter != mAnimators.End(); )
434   {
435     AnimatorBase *animator = *iter;
436
437     if( animator->Orphan() )
438     {
439       //Remove animators whose PropertyOwner has been destroyed
440       iter = mAnimators.Erase(iter);
441     }
442     else
443     {
444       if( animator->IsEnabled() )
445       {
446         const float intervalDelay( animator->GetIntervalDelay() );
447
448         if( elapsedSecondsClamped >= intervalDelay )
449         {
450           // Calculate a progress specific to each individual animator
451           float progress(1.0f);
452           const float animatorDuration = animator->GetDuration();
453           if (animatorDuration > 0.0f) // animators can be "immediate"
454           {
455             progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f , 1.0f );
456           }
457           animator->Update(bufferIndex, progress, bake);
458         }
459         applied = true;
460       }
461       else
462       {
463         applied = false;
464       }
465
466       if ( animationFinished )
467       {
468         animator->SetActive( false );
469       }
470
471       if (applied)
472       {
473         INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
474       }
475
476       ++iter;
477     }
478   }
479
480 }
481
482 } // namespace SceneGraph
483
484 } // namespace Internal
485
486 } // namespace Dali