Merge "use modern construct '= default' for special functions." 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() = default;
87
88 void Animation::operator delete( void* ptr )
89 {
90   gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
91 }
92
93 void Animation::SetDuration(float durationSeconds)
94 {
95   mDurationSeconds = durationSeconds;
96 }
97
98 void Animation::SetProgressNotification( float progress )
99 {
100   mProgressMarker = progress;
101   if ( mProgressMarker > 0.0f )
102   {
103     mProgressReachedSignalRequired = true;
104   }
105 }
106
107 void Animation::SetLoopCount(int32_t loopCount)
108 {
109   mLoopCount = loopCount;
110   mCurrentLoop = 0;
111 }
112
113 void Animation::SetEndAction(Dali::Animation::EndAction action)
114 {
115   mEndAction = action;
116 }
117
118 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
119 {
120   if ( mDisconnectAction != action )
121   {
122     mDisconnectAction = action;
123
124     for ( auto&& item : mAnimators )
125     {
126       item->SetDisconnectAction( action );
127     }
128   }
129 }
130
131 void Animation::SetPlayRange( const Vector2& range )
132 {
133   mPlayRange = range;
134
135   // Make sure mElapsedSeconds is within the new range
136
137   if( mState == Stopped )
138   {
139     // Ensure that the animation starts at the right place
140     mElapsedSeconds = mPlayRange.x * mDurationSeconds;
141   }
142   else
143   {
144     // If already past the end of the range, but before end of duration, then clamp will
145     // ensure that the animation stops on the next update.
146     // If not yet at the start of the range, clamping will jump to the start
147     mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x*mDurationSeconds , mPlayRange.y*mDurationSeconds );
148   }
149 }
150
151 void Animation::Play()
152 {
153   // Sort according to end time with earlier end times coming first, if the end time is the same, then the animators are not moved
154   std::stable_sort( mAnimators.Begin(), mAnimators.End(), CompareAnimatorEndTimes );
155
156   mState = Playing;
157
158   if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
159   {
160     mElapsedSeconds = mPlayRange.y * mDurationSeconds;
161   }
162
163   SetAnimatorsActive( true );
164
165   mCurrentLoop = 0;
166 }
167
168 void Animation::PlayFrom( float progress )
169 {
170   // If the animation is already playing this has no effect
171   // Progress is guaranteed to be in range.
172   if( mState != Playing )
173   {
174     mElapsedSeconds = progress * mDurationSeconds;
175     mState = Playing;
176
177     SetAnimatorsActive( true );
178
179     mCurrentLoop = 0;
180   }
181 }
182
183 void Animation::PlayAfter( float delaySeconds )
184 {
185   if( mState != Playing )
186   {
187     mDelaySeconds = delaySeconds;
188     mState = Playing;
189
190     if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
191     {
192       mElapsedSeconds = mPlayRange.y * mDurationSeconds;
193     }
194
195     SetAnimatorsActive( true );
196
197     mCurrentLoop = 0;
198   }
199 }
200
201 void Animation::Pause()
202 {
203   if (mState == Playing)
204   {
205     mState = Paused;
206   }
207 }
208
209 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
210 {
211   if( action == Dali::Animation::BAKE_FINAL )
212   {
213     if( mSpeedFactor > 0.0f )
214     {
215       mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
216     }
217     else
218     {
219       mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
220     }
221   }
222
223   UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
224 }
225
226 void Animation::SetAnimatorsActive( bool active )
227 {
228   for ( auto&& item : mAnimators )
229   {
230     item->SetActive( active );
231   }
232 }
233
234 bool Animation::Stop(BufferIndex bufferIndex)
235 {
236   bool animationFinished(false);
237
238   if (mState == Playing || mState == Paused)
239   {
240     animationFinished = true; // The actor-thread should be notified of this
241
242     if( mEndAction != Dali::Animation::DISCARD )
243     {
244       Bake( bufferIndex, mEndAction );
245
246       // Animators are automatically set to inactive in Bake
247     }
248     else
249     {
250       SetAnimatorsActive( false );
251     }
252
253     // The animation has now been played to completion
254     ++mPlayedCount;
255     mCurrentLoop = 0;
256   }
257
258   mElapsedSeconds = mPlayRange.x*mDurationSeconds;
259   mState = Stopped;
260
261   return animationFinished;
262 }
263
264 void Animation::OnDestroy(BufferIndex bufferIndex)
265 {
266   if (mState == Playing || mState == Paused)
267   {
268     if (mEndAction != Dali::Animation::DISCARD)
269     {
270       Bake( bufferIndex, mEndAction );
271
272       // Animators are automatically set to inactive in Bake
273     }
274     else
275     {
276       SetAnimatorsActive( false );
277     }
278   }
279
280   mState = Destroyed;
281 }
282
283 void Animation::SetLoopingMode( bool loopingMode )
284 {
285   mAutoReverseEnabled = loopingMode;
286
287   for ( auto&& item : mAnimators )
288   {
289     // Send some variables together to figure out the Animation status
290     item->SetSpeedFactor( mSpeedFactor );
291     item->SetLoopCount( mLoopCount );
292     item->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   mIsActive[bufferIndex] = false;
429
430   const Vector2 playRange( mPlayRange * mDurationSeconds );
431   float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
432
433   //Loop through all animators
434   bool applied(true);
435   for ( auto&& iter = mAnimators.Begin(); iter != mAnimators.End(); )
436   {
437     AnimatorBase *animator = *iter;
438
439     if( animator->Orphan() )
440     {
441       //Remove animators whose PropertyOwner has been destroyed
442       iter = mAnimators.Erase(iter);
443     }
444     else
445     {
446       if( animator->IsEnabled() )
447       {
448         const float intervalDelay( animator->GetIntervalDelay() );
449
450         if( elapsedSecondsClamped >= intervalDelay )
451         {
452           // Calculate a progress specific to each individual animator
453           float progress(1.0f);
454           const float animatorDuration = animator->GetDuration();
455           if (animatorDuration > 0.0f) // animators can be "immediate"
456           {
457             progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f , 1.0f );
458           }
459           animator->Update(bufferIndex, progress, bake);
460
461           if (animatorDuration > 0.0f && (elapsedSecondsClamped - intervalDelay) <= animatorDuration)
462           {
463             mIsActive[bufferIndex] = true;
464           }
465         }
466         applied = true;
467       }
468       else
469       {
470         applied = false;
471       }
472
473       if ( animationFinished )
474       {
475         animator->SetActive( false );
476       }
477
478       if (applied)
479       {
480         INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
481       }
482
483       ++iter;
484     }
485   }
486
487 }
488
489 } // namespace SceneGraph
490
491 } // namespace Internal
492
493 } // namespace Dali