2 * Copyright (c) 2019 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali/internal/update/animation/scene-graph-animation.h>
22 #include <cmath> // fmod
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
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;
33 inline void WrapInPlayRange( float& elapsed, const float& playRangeStartSeconds, const float& playRangeEndSeconds)
35 if( elapsed > playRangeEndSeconds )
37 elapsed = playRangeStartSeconds + fmodf( ( elapsed - playRangeStartSeconds ), ( playRangeEndSeconds - playRangeStartSeconds ) );
39 else if( elapsed < playRangeStartSeconds )
41 elapsed = playRangeEndSeconds - fmodf( ( playRangeStartSeconds - elapsed ), ( playRangeEndSeconds - playRangeStartSeconds ) );
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 )
48 return ( ( lhs->GetIntervalDelay() + lhs->GetDuration() ) < ( rhs->GetIntervalDelay() + rhs->GetDuration() ) );
51 } // unnamed namespace
62 Animation* Animation::New( float durationSeconds, float speedFactor, const Vector2& playRange, int32_t loopCount, EndAction endAction, EndAction disconnectAction )
64 return new ( gAnimationMemoryPool.AllocateRawThreadSafe() ) Animation( durationSeconds, speedFactor, playRange, loopCount, endAction, disconnectAction );
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 ),
75 mLoopCount(loopCount),
77 mEndAction(endAction),
78 mDisconnectAction(disconnectAction),
80 mProgressReachedSignalRequired( false ),
81 mAutoReverseEnabled( false )
85 Animation::~Animation()
89 void Animation::operator delete( void* ptr )
91 gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
94 void Animation::SetDuration(float durationSeconds)
96 mDurationSeconds = durationSeconds;
99 void Animation::SetProgressNotification( float progress )
101 mProgressMarker = progress;
102 if ( mProgressMarker > 0.0f )
104 mProgressReachedSignalRequired = true;
108 void Animation::SetLoopCount(int32_t loopCount)
110 mLoopCount = loopCount;
114 void Animation::SetEndAction(Dali::Animation::EndAction action)
119 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
121 if ( mDisconnectAction != action )
123 mDisconnectAction = action;
125 for ( auto&& item : mAnimators )
127 item->SetDisconnectAction( action );
132 void Animation::SetPlayRange( const Vector2& range )
136 // Make sure mElapsedSeconds is within the new range
138 if( mState == Stopped )
140 // Ensure that the animation starts at the right place
141 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
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 );
152 void Animation::Play()
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 );
159 if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
161 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
164 SetAnimatorsActive( true );
169 void Animation::PlayFrom( float progress )
171 // If the animation is already playing this has no effect
172 // Progress is guaranteed to be in range.
173 if( mState != Playing )
175 mElapsedSeconds = progress * mDurationSeconds;
178 SetAnimatorsActive( true );
184 void Animation::PlayAfter( float delaySeconds )
186 if( mState != Playing )
188 mDelaySeconds = delaySeconds;
191 if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
193 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
196 SetAnimatorsActive( true );
202 void Animation::Pause()
204 if (mState == Playing)
210 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
212 if( action == Dali::Animation::BAKE_FINAL )
214 if( mSpeedFactor > 0.0f )
216 mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
220 mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
224 UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
227 void Animation::SetAnimatorsActive( bool active )
229 for ( auto&& item : mAnimators )
231 item->SetActive( active );
235 bool Animation::Stop(BufferIndex bufferIndex)
237 bool animationFinished(false);
239 if (mState == Playing || mState == Paused)
241 animationFinished = true; // The actor-thread should be notified of this
243 if( mEndAction != Dali::Animation::DISCARD )
245 Bake( bufferIndex, mEndAction );
247 // Animators are automatically set to inactive in Bake
251 SetAnimatorsActive( false );
254 // The animation has now been played to completion
259 mElapsedSeconds = mPlayRange.x*mDurationSeconds;
262 return animationFinished;
265 void Animation::OnDestroy(BufferIndex bufferIndex)
267 if (mState == Playing || mState == Paused)
269 if (mEndAction != Dali::Animation::DISCARD)
271 Bake( bufferIndex, mEndAction );
273 // Animators are automatically set to inactive in Bake
277 SetAnimatorsActive( false );
284 void Animation::SetLoopingMode( bool loopingMode )
286 mAutoReverseEnabled = loopingMode;
288 for ( auto&& item : mAnimators )
290 // Send some variables together to figure out the Animation status
291 item->SetSpeedFactor( mSpeedFactor );
292 item->SetLoopCount( mLoopCount );
293 item->SetLoopingMode( loopingMode );
297 void Animation::AddAnimator( OwnerPointer<AnimatorBase>& animator )
299 animator->ConnectToSceneGraph();
300 animator->SetDisconnectAction( mDisconnectAction );
302 mAnimators.PushBack( animator.Release() );
305 void Animation::Update( BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished, bool& progressReached )
310 if (mState == Stopped || mState == Destroyed)
312 // Short circuit when animation isn't running
316 // The animation must still be applied when Paused/Stopping
317 if (mState == Playing)
319 // Sign value of speed factor. It can optimize many arithmetic comparision
320 float signSpeedFactor = ( mSpeedFactor < 0.0f ) ? -1.f : 1.f;
322 // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
323 if( mDelaySeconds > 0.0f )
325 float reduceSeconds = fabsf( elapsedSeconds * mSpeedFactor );
326 if( reduceSeconds > mDelaySeconds )
328 // add overflowed time to mElapsedSecond.
329 // If speed factor > 0, add it. if speed factor < 0, subtract it.
330 float overflowSeconds = reduceSeconds - mDelaySeconds;
331 mElapsedSeconds += signSpeedFactor * overflowSeconds;
332 mDelaySeconds = 0.0f;
336 mDelaySeconds -= reduceSeconds;
341 mElapsedSeconds += ( elapsedSeconds * mSpeedFactor );
344 const float playRangeStartSeconds = mPlayRange.x * mDurationSeconds;
345 const float playRangeEndSeconds = mPlayRange.y * mDurationSeconds;
346 // Final reached seconds. It can optimize many arithmetic comparision
347 float edgeRangeSeconds = ( mSpeedFactor < 0.0f ) ? playRangeStartSeconds : playRangeEndSeconds;
349 // Optimized Factors.
350 // elapsed > edge --> check if looped
351 // elapsed >= marker --> check if elapsed reached to marker in normal case
352 // edge >= marker --> check if elapsed reached to marker in looped case
353 float elapsedFactor = signSpeedFactor * mElapsedSeconds;
354 float edgeFactor = signSpeedFactor * edgeRangeSeconds;
355 float markerFactor = signSpeedFactor * mProgressMarker;
357 // check it is looped
358 looped = ( elapsedFactor > edgeFactor );
362 WrapInPlayRange( mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds );
364 // Recalculate elapsedFactor here
365 elapsedFactor = signSpeedFactor * mElapsedSeconds;
367 if( mLoopCount != 0 )
369 // Check If this animation is finished
371 if( mCurrentLoop >= mLoopCount )
373 DALI_ASSERT_DEBUG( mCurrentLoop == mLoopCount );
376 // The animation has now been played to completion
379 // Make elapsed second as edge of range forcely.
380 mElapsedSeconds = edgeRangeSeconds + signSpeedFactor * Math::MACHINE_EPSILON_10;
381 UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::DISCARD), finished );
383 // After update animation, mElapsedSeconds must be begin of value
384 mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
389 // when it is on looped state, 2 case to send progress signal.
390 // (require && range_value >= marker) || << Signal at previous loop
391 // (marker > 0 && !finished && elaped >= marker) << Signal at current loop
392 if( ( mProgressMarker > 0.0f ) && !finished && ( elapsedFactor >= markerFactor ) )
394 // The application should be notified by NotificationManager, in another thread
395 progressReached = true;
396 mProgressReachedSignalRequired = false;
400 if( mProgressReachedSignalRequired && ( edgeFactor >= markerFactor ) )
402 progressReached = true;
404 mProgressReachedSignalRequired = mProgressMarker > 0.0f;
409 // when it is not on looped state, only 1 case to send progress signal.
410 // (require && elaped >= marker)
411 if( mProgressReachedSignalRequired && ( elapsedFactor >= markerFactor ) )
413 // The application should be notified by NotificationManager, in another thread
414 progressReached = true;
415 mProgressReachedSignalRequired = false;
420 // Already updated when finished. So skip.
423 UpdateAnimators(bufferIndex, false, false );
427 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
429 const Vector2 playRange( mPlayRange * mDurationSeconds );
430 float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
432 //Loop through all animators
434 for ( auto&& iter = mAnimators.Begin(); iter != mAnimators.End(); )
436 AnimatorBase *animator = *iter;
438 if( animator->Orphan() )
440 //Remove animators whose PropertyOwner has been destroyed
441 iter = mAnimators.Erase(iter);
445 if( animator->IsEnabled() )
447 const float intervalDelay( animator->GetIntervalDelay() );
449 if( elapsedSecondsClamped >= intervalDelay )
451 // Calculate a progress specific to each individual animator
452 float progress(1.0f);
453 const float animatorDuration = animator->GetDuration();
454 if (animatorDuration > 0.0f) // animators can be "immediate"
456 progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f , 1.0f );
458 animator->Update(bufferIndex, progress, bake);
467 if ( animationFinished )
469 animator->SetActive( false );
474 INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
483 } // namespace SceneGraph
485 } // namespace Internal