Merge "DALi Version 1.9.34" into devel/master
[platform/core/uifw/dali-core.git] / dali / internal / update / animation / scene-graph-animation.cpp
1 /*
2  * Copyright (c) 2020 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   mIsActive{ false }
83 {
84 }
85
86 Animation::~Animation()
87 {
88 }
89
90 void Animation::operator delete( void* ptr )
91 {
92   gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
93 }
94
95 void Animation::SetDuration(float durationSeconds)
96 {
97   mDurationSeconds = durationSeconds;
98 }
99
100 void Animation::SetProgressNotification( float progress )
101 {
102   mProgressMarker = progress;
103   if ( mProgressMarker > 0.0f )
104   {
105     mProgressReachedSignalRequired = true;
106   }
107 }
108
109 void Animation::SetLoopCount(int32_t loopCount)
110 {
111   mLoopCount = loopCount;
112   mCurrentLoop = 0;
113 }
114
115 void Animation::SetEndAction(Dali::Animation::EndAction action)
116 {
117   mEndAction = action;
118 }
119
120 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
121 {
122   if ( mDisconnectAction != action )
123   {
124     mDisconnectAction = action;
125
126     for ( auto&& item : mAnimators )
127     {
128       item->SetDisconnectAction( action );
129     }
130   }
131 }
132
133 void Animation::SetPlayRange( const Vector2& range )
134 {
135   mPlayRange = range;
136
137   // Make sure mElapsedSeconds is within the new range
138
139   if( mState == Stopped )
140   {
141     // Ensure that the animation starts at the right place
142     mElapsedSeconds = mPlayRange.x * mDurationSeconds;
143   }
144   else
145   {
146     // If already past the end of the range, but before end of duration, then clamp will
147     // ensure that the animation stops on the next update.
148     // If not yet at the start of the range, clamping will jump to the start
149     mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x*mDurationSeconds , mPlayRange.y*mDurationSeconds );
150   }
151 }
152
153 void Animation::Play()
154 {
155   // Sort according to end time with earlier end times coming first, if the end time is the same, then the animators are not moved
156   std::stable_sort( mAnimators.Begin(), mAnimators.End(), CompareAnimatorEndTimes );
157
158   mState = Playing;
159
160   if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
161   {
162     mElapsedSeconds = mPlayRange.y * mDurationSeconds;
163   }
164
165   SetAnimatorsActive( true );
166
167   mCurrentLoop = 0;
168 }
169
170 void Animation::PlayFrom( float progress )
171 {
172   // If the animation is already playing this has no effect
173   // Progress is guaranteed to be in range.
174   if( mState != Playing )
175   {
176     mElapsedSeconds = progress * mDurationSeconds;
177     mState = Playing;
178
179     SetAnimatorsActive( true );
180
181     mCurrentLoop = 0;
182   }
183 }
184
185 void Animation::PlayAfter( float delaySeconds )
186 {
187   if( mState != Playing )
188   {
189     mDelaySeconds = delaySeconds;
190     mState = Playing;
191
192     if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
193     {
194       mElapsedSeconds = mPlayRange.y * mDurationSeconds;
195     }
196
197     SetAnimatorsActive( true );
198
199     mCurrentLoop = 0;
200   }
201 }
202
203 void Animation::Pause()
204 {
205   if (mState == Playing)
206   {
207     mState = Paused;
208   }
209 }
210
211 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
212 {
213   if( action == Dali::Animation::BAKE_FINAL )
214   {
215     if( mSpeedFactor > 0.0f )
216     {
217       mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
218     }
219     else
220     {
221       mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
222     }
223   }
224
225   UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
226 }
227
228 void Animation::SetAnimatorsActive( bool active )
229 {
230   for ( auto&& item : mAnimators )
231   {
232     item->SetActive( active );
233   }
234 }
235
236 bool Animation::Stop(BufferIndex bufferIndex)
237 {
238   bool animationFinished(false);
239
240   if (mState == Playing || mState == Paused)
241   {
242     animationFinished = true; // The actor-thread should be notified of this
243
244     if( mEndAction != Dali::Animation::DISCARD )
245     {
246       Bake( bufferIndex, mEndAction );
247
248       // Animators are automatically set to inactive in Bake
249     }
250     else
251     {
252       SetAnimatorsActive( false );
253     }
254
255     // The animation has now been played to completion
256     ++mPlayedCount;
257     mCurrentLoop = 0;
258   }
259
260   mElapsedSeconds = mPlayRange.x*mDurationSeconds;
261   mState = Stopped;
262
263   return animationFinished;
264 }
265
266 void Animation::OnDestroy(BufferIndex bufferIndex)
267 {
268   if (mState == Playing || mState == Paused)
269   {
270     if (mEndAction != Dali::Animation::DISCARD)
271     {
272       Bake( bufferIndex, mEndAction );
273
274       // Animators are automatically set to inactive in Bake
275     }
276     else
277     {
278       SetAnimatorsActive( false );
279     }
280   }
281
282   mState = Destroyed;
283 }
284
285 void Animation::SetLoopingMode( bool loopingMode )
286 {
287   mAutoReverseEnabled = loopingMode;
288
289   for ( auto&& item : mAnimators )
290   {
291     // Send some variables together to figure out the Animation status
292     item->SetSpeedFactor( mSpeedFactor );
293     item->SetLoopCount( mLoopCount );
294     item->SetLoopingMode( loopingMode );
295   }
296 }
297
298 void Animation::AddAnimator( OwnerPointer<AnimatorBase>& animator )
299 {
300   animator->ConnectToSceneGraph();
301   animator->SetDisconnectAction( mDisconnectAction );
302
303   mAnimators.PushBack( animator.Release() );
304 }
305
306 void Animation::Update( BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished, bool& progressReached )
307 {
308   looped = false;
309   finished = false;
310
311   if (mState == Stopped || mState == Destroyed)
312   {
313     // Short circuit when animation isn't running
314     return;
315   }
316
317   // The animation must still be applied when Paused/Stopping
318   if (mState == Playing)
319   {
320     // Sign value of speed factor. It can optimize many arithmetic comparision
321     float signSpeedFactor = ( mSpeedFactor < 0.0f ) ? -1.f : 1.f;
322
323     // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
324     if( mDelaySeconds > 0.0f )
325     {
326       float reduceSeconds = fabsf( elapsedSeconds * mSpeedFactor );
327       if( reduceSeconds > mDelaySeconds )
328       {
329         // add overflowed time to mElapsedSecond.
330         // If speed factor > 0, add it. if speed factor < 0, subtract it.
331         float overflowSeconds = reduceSeconds - mDelaySeconds;
332         mElapsedSeconds += signSpeedFactor * overflowSeconds;
333         mDelaySeconds = 0.0f;
334       }
335       else
336       {
337         mDelaySeconds -= reduceSeconds;
338       }
339     }
340     else
341     {
342       mElapsedSeconds += ( elapsedSeconds * mSpeedFactor );
343     }
344
345     const float playRangeStartSeconds = mPlayRange.x * mDurationSeconds;
346     const float playRangeEndSeconds = mPlayRange.y * mDurationSeconds;
347     // Final reached seconds. It can optimize many arithmetic comparision
348     float edgeRangeSeconds = ( mSpeedFactor < 0.0f ) ? playRangeStartSeconds : playRangeEndSeconds;
349
350     // Optimized Factors.
351     // elapsed >  edge   --> check if looped
352     // elapsed >= marker --> check if elapsed reached to marker in normal case
353     // edge    >= marker --> check if elapsed reached to marker in looped case
354     float elapsedFactor = signSpeedFactor * mElapsedSeconds;
355     float edgeFactor = signSpeedFactor * edgeRangeSeconds;
356     float markerFactor = signSpeedFactor * mProgressMarker;
357
358     // check it is looped
359     looped = ( elapsedFactor > edgeFactor );
360
361     if( looped )
362     {
363       WrapInPlayRange( mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds );
364
365       // Recalculate elapsedFactor here
366       elapsedFactor = signSpeedFactor * mElapsedSeconds;
367
368       if( mLoopCount != 0 )
369       {
370         // Check If this animation is finished
371         ++mCurrentLoop;
372         if( mCurrentLoop >= mLoopCount )
373         {
374           DALI_ASSERT_DEBUG( mCurrentLoop == mLoopCount );
375           finished = true;
376
377           // The animation has now been played to completion
378           ++mPlayedCount;
379
380           // Make elapsed second as edge of range forcely.
381           mElapsedSeconds = edgeRangeSeconds + signSpeedFactor * Math::MACHINE_EPSILON_10;
382           UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::DISCARD), finished );
383
384           // After update animation, mElapsedSeconds must be begin of value
385           mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
386           mState = Stopped;
387         }
388       }
389
390       // when it is on looped state, 2 case to send progress signal.
391       // (require && range_value >= marker) ||         << Signal at previous loop
392       // (marker > 0 && !finished && elaped >= marker) << Signal at current loop
393       if( ( mProgressMarker > 0.0f ) && !finished && ( elapsedFactor >= markerFactor ) )
394       {
395         // The application should be notified by NotificationManager, in another thread
396         progressReached = true;
397         mProgressReachedSignalRequired = false;
398       }
399       else
400       {
401         if( mProgressReachedSignalRequired && ( edgeFactor >= markerFactor ) )
402         {
403           progressReached = true;
404         }
405         mProgressReachedSignalRequired = mProgressMarker > 0.0f;
406       }
407     }
408     else
409     {
410       // when it is not on looped state, only 1 case to send progress signal.
411       // (require && elaped >= marker)
412       if( mProgressReachedSignalRequired && ( elapsedFactor >= markerFactor ) )
413       {
414         // The application should be notified by NotificationManager, in another thread
415         progressReached = true;
416         mProgressReachedSignalRequired = false;
417       }
418     }
419   }
420
421   // Already updated when finished. So skip.
422   if( !finished )
423   {
424     UpdateAnimators(bufferIndex, false, false );
425   }
426 }
427
428 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
429 {
430   mIsActive[bufferIndex] = false;
431
432   const Vector2 playRange( mPlayRange * mDurationSeconds );
433   float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
434
435   //Loop through all animators
436   bool applied(true);
437   for ( auto&& iter = mAnimators.Begin(); iter != mAnimators.End(); )
438   {
439     AnimatorBase *animator = *iter;
440
441     if( animator->Orphan() )
442     {
443       //Remove animators whose PropertyOwner has been destroyed
444       iter = mAnimators.Erase(iter);
445     }
446     else
447     {
448       if( animator->IsEnabled() )
449       {
450         const float intervalDelay( animator->GetIntervalDelay() );
451
452         if( elapsedSecondsClamped >= intervalDelay )
453         {
454           // Calculate a progress specific to each individual animator
455           float progress(1.0f);
456           const float animatorDuration = animator->GetDuration();
457           if (animatorDuration > 0.0f) // animators can be "immediate"
458           {
459             progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f , 1.0f );
460           }
461           animator->Update(bufferIndex, progress, bake);
462
463           if (animatorDuration > 0.0f && (elapsedSecondsClamped - intervalDelay) <= animatorDuration)
464           {
465             mIsActive[bufferIndex] = true;
466           }
467         }
468         applied = true;
469       }
470       else
471       {
472         applied = false;
473       }
474
475       if ( animationFinished )
476       {
477         animator->SetActive( false );
478       }
479
480       if (applied)
481       {
482         INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
483       }
484
485       ++iter;
486     }
487   }
488
489 }
490
491 } // namespace SceneGraph
492
493 } // namespace Internal
494
495 } // namespace Dali