2 * Copyright (c) 2017 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 Dali::Vector2& playRangeSeconds)
35 if( elapsed > playRangeSeconds.y )
37 elapsed = playRangeSeconds.x + fmodf((elapsed-playRangeSeconds.x), (playRangeSeconds.y-playRangeSeconds.x));
39 else if( elapsed < playRangeSeconds.x )
41 elapsed = playRangeSeconds.y - fmodf( (playRangeSeconds.x - elapsed), (playRangeSeconds.y-playRangeSeconds.x) );
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, int 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, int 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 )
84 Animation::~Animation()
88 void Animation::operator delete( void* ptr )
90 gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
93 void Animation::SetDuration(float durationSeconds)
95 mDurationSeconds = durationSeconds;
98 void Animation::SetProgressNotification( float progress )
100 mProgressMarker = progress;
101 if ( mProgressMarker > 0.0f )
103 mProgressReachedSignalRequired = true;
107 void Animation::SetLoopCount(int loopCount)
109 mLoopCount = loopCount;
113 void Animation::SetEndAction(Dali::Animation::EndAction action)
118 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
120 if ( mDisconnectAction != action )
122 mDisconnectAction = action;
124 for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
126 (*iter)->SetDisconnectAction( action );
131 void Animation::SetPlayRange( const Vector2& range )
135 // Make sure mElapsedSeconds is within the new range
137 if( mState == Stopped )
139 // Ensure that the animation starts at the right place
140 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
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 );
151 void Animation::Play()
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 );
158 if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
160 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
163 SetAnimatorsActive( true );
168 void Animation::PlayFrom( float progress )
170 // If the animation is already playing this has no effect
171 // Progress is guaranteed to be in range.
172 if( mState != Playing )
174 mElapsedSeconds = progress * mDurationSeconds;
177 SetAnimatorsActive( true );
181 void Animation::PlayAfter( float delaySeconds )
183 if( mState != Playing )
185 mDelaySeconds = delaySeconds;
188 if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
190 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
193 SetAnimatorsActive( true );
199 void Animation::Pause()
201 if (mState == Playing)
207 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
209 if( action == Dali::Animation::BakeFinal )
211 if( mSpeedFactor > 0.0f )
213 mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
217 mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
221 UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
224 void Animation::SetAnimatorsActive( bool active )
226 for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
228 (*iter)->SetActive( active );
232 bool Animation::Stop(BufferIndex bufferIndex)
234 bool animationFinished(false);
236 if (mState == Playing || mState == Paused)
238 animationFinished = true; // The actor-thread should be notified of this
240 if( mEndAction != Dali::Animation::Discard )
242 Bake( bufferIndex, mEndAction );
244 // Animators are automatically set to inactive in Bake
248 SetAnimatorsActive( false );
251 // The animation has now been played to completion
256 mElapsedSeconds = mPlayRange.x*mDurationSeconds;
259 return animationFinished;
262 void Animation::OnDestroy(BufferIndex bufferIndex)
264 if (mState == Playing || mState == Paused)
266 if (mEndAction != Dali::Animation::Discard)
268 Bake( bufferIndex, mEndAction );
270 // Animators are automatically set to inactive in Bake
274 SetAnimatorsActive( false );
281 void Animation::AddAnimator( OwnerPointer<AnimatorBase>& animator )
283 animator->ConnectToSceneGraph();
284 animator->SetDisconnectAction( mDisconnectAction );
286 mAnimators.PushBack( animator.Release() );
289 void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished, bool& progressReached )
294 if (mState == Stopped || mState == Destroyed)
296 // Short circuit when animation isn't running
300 // The animation must still be applied when Paused/Stopping
301 if (mState == Playing)
303 // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
304 if( mDelaySeconds > 0)
306 mDelaySeconds = mDelaySeconds - ( elapsedSeconds * mSpeedFactor );
310 mElapsedSeconds += elapsedSeconds * mSpeedFactor;
312 if ( mProgressReachedSignalRequired && ( mElapsedSeconds >= mProgressMarker ) )
314 // The application should be notified by NotificationManager, in another thread
315 progressReached = true;
316 mProgressReachedSignalRequired = false;
321 Vector2 playRangeSeconds = mPlayRange * mDurationSeconds;
323 if( 0 == mLoopCount )
326 WrapInPlayRange(mElapsedSeconds, playRangeSeconds);
328 UpdateAnimators(bufferIndex, false, false );
330 // don't increment mPlayedCount as event loop tracks this to indicate animation finished (end of all loops)
332 else if( mCurrentLoop < mLoopCount - 1) // '-1' here so last loop iteration uses play once below
335 looped = (mState == Playing &&
336 (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y ) ||
337 ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
339 WrapInPlayRange( mElapsedSeconds, playRangeSeconds );
341 UpdateAnimators(bufferIndex, false, false );
346 mProgressReachedSignalRequired = mProgressMarker > 0.0f;
347 // don't increment mPlayedCount until the finished final loop
352 // playing once (and last mCurrentLoop)
353 finished = (mState == Playing &&
354 (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y ) ||
355 ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
357 // update with bake if finished
358 UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::Discard), finished );
362 // The animation has now been played to completion
365 // loop iterations come to this else branch for their final iterations
366 if( mCurrentLoop < mLoopCount)
369 DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
372 mProgressReachedSignalRequired = mProgressMarker > 0.0f;
373 mElapsedSeconds = playRangeSeconds.x;
379 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
381 const Vector2 playRange( mPlayRange * mDurationSeconds );
382 float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
384 //Loop through all animators
386 for ( AnimatorIter iter = mAnimators.Begin(); iter != mAnimators.End(); )
388 AnimatorBase *animator = *iter;
390 if( animator->Orphan() )
392 //Remove animators whose PropertyOwner has been destroyed
393 iter = mAnimators.Erase(iter);
397 if( animator->IsEnabled() )
399 const float intervalDelay( animator->GetIntervalDelay() );
401 if( elapsedSecondsClamped >= intervalDelay )
403 // Calculate a progress specific to each individual animator
404 float progress(1.0f);
405 const float animatorDuration = animator->GetDuration();
406 if (animatorDuration > 0.0f) // animators can be "immediate"
408 progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f , 1.0f );
410 animator->Update(bufferIndex, progress, bake);
419 if ( animationFinished )
421 animator->SetActive( false );
426 INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
435 } // namespace SceneGraph
437 } // namespace Internal