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>
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->GetInitialDelay() + lhs->GetDuration() ) < ( rhs->GetInitialDelay() + 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 : mDurationSeconds(durationSeconds),
69 mSpeedFactor( speedFactor ),
70 mEndAction(endAction),
71 mDisconnectAction(disconnectAction),
73 mElapsedSeconds(playRange.x*mDurationSeconds),
75 mLoopCount(loopCount),
77 mPlayRange( playRange ),
78 mProgressMarker(0.0f),
79 mProgressReachedSignalRequired( false )
83 Animation::~Animation()
87 void Animation::operator delete( void* ptr )
89 gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
92 void Animation::SetDuration(float durationSeconds)
94 mDurationSeconds = durationSeconds;
97 void Animation::SetProgressNotification( float progress )
99 mProgressMarker = progress;
100 if ( mProgressMarker > 0.0f )
102 mProgressReachedSignalRequired = true;
106 void Animation::SetLoopCount(int loopCount)
108 mLoopCount = loopCount;
112 void Animation::SetEndAction(Dali::Animation::EndAction action)
117 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
119 if ( mDisconnectAction != action )
121 mDisconnectAction = action;
123 for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
125 (*iter)->SetDisconnectAction( action );
130 void Animation::SetPlayRange( const Vector2& range )
134 // Make sure mElapsedSeconds is within the new range
136 if( mState == Stopped )
138 // Ensure that the animation starts at the right place
139 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
143 // If already past the end of the range, but before end of duration, then clamp will
144 // ensure that the animation stops on the next update.
145 // If not yet at the start of the range, clamping will jump to the start
146 mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x*mDurationSeconds , mPlayRange.y*mDurationSeconds );
150 void Animation::Play()
152 // Sort according to end time with earlier end times coming first, if the end time is the same, then the animators are not moved
153 std::stable_sort( mAnimators.Begin(), mAnimators.End(), CompareAnimatorEndTimes );
157 if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
159 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
162 SetAnimatorsActive( true );
167 void Animation::PlayFrom( float progress )
169 // If the animation is already playing this has no effect
170 // Progress is guaranteed to be in range.
171 if( mState != Playing )
173 mElapsedSeconds = progress * mDurationSeconds;
176 SetAnimatorsActive( true );
180 void Animation::Pause()
182 if (mState == Playing)
188 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
190 if( action == Dali::Animation::BakeFinal )
192 if( mSpeedFactor > 0.0f )
194 mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
198 mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
202 UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
205 void Animation::SetAnimatorsActive( bool active )
207 for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
209 (*iter)->SetActive( active );
213 bool Animation::Stop(BufferIndex bufferIndex)
215 bool animationFinished(false);
217 if (mState == Playing || mState == Paused)
219 animationFinished = true; // The actor-thread should be notified of this
221 if( mEndAction != Dali::Animation::Discard )
223 Bake( bufferIndex, mEndAction );
225 // Animators are automatically set to inactive in Bake
229 SetAnimatorsActive( false );
232 // The animation has now been played to completion
237 mElapsedSeconds = mPlayRange.x*mDurationSeconds;
240 return animationFinished;
243 void Animation::OnDestroy(BufferIndex bufferIndex)
245 if (mState == Playing || mState == Paused)
247 if (mEndAction != Dali::Animation::Discard)
249 Bake( bufferIndex, mEndAction );
251 // Animators are automatically set to inactive in Bake
255 SetAnimatorsActive( false );
262 void Animation::AddAnimator( OwnerPointer<AnimatorBase>& animator )
264 animator->ConnectToSceneGraph();
265 animator->SetDisconnectAction( mDisconnectAction );
267 mAnimators.PushBack( animator.Release() );
270 void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished, bool& progressReached )
275 if (mState == Stopped || mState == Destroyed)
277 // Short circuit when animation isn't running
281 // The animation must still be applied when Paused/Stopping
282 if (mState == Playing)
284 mElapsedSeconds += elapsedSeconds * mSpeedFactor;
286 if ( mProgressReachedSignalRequired && ( mElapsedSeconds >= mProgressMarker ) )
288 // The application should be notified by NotificationManager, in another thread
289 progressReached = true;
290 mProgressReachedSignalRequired = false;
294 Vector2 playRangeSeconds = mPlayRange * mDurationSeconds;
296 if( 0 == mLoopCount )
299 WrapInPlayRange(mElapsedSeconds, playRangeSeconds);
301 UpdateAnimators(bufferIndex, false, false );
303 // don't increment mPlayedCount as event loop tracks this to indicate animation finished (end of all loops)
305 else if( mCurrentLoop < mLoopCount - 1) // '-1' here so last loop iteration uses play once below
308 looped = (mState == Playing &&
309 (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y ) ||
310 ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
312 WrapInPlayRange( mElapsedSeconds, playRangeSeconds );
314 UpdateAnimators(bufferIndex, false, false );
319 mProgressReachedSignalRequired = mProgressMarker > 0.0f;
320 // don't increment mPlayedCount until the finished final loop
325 // playing once (and last mCurrentLoop)
326 finished = (mState == Playing &&
327 (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y ) ||
328 ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
330 // update with bake if finished
331 UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::Discard), finished );
335 // The animation has now been played to completion
338 // loop iterations come to this else branch for their final iterations
339 if( mCurrentLoop < mLoopCount)
342 DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
345 mProgressReachedSignalRequired = mProgressMarker > 0.0f;
346 mElapsedSeconds = playRangeSeconds.x;
352 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
354 const Vector2 playRange( mPlayRange * mDurationSeconds );
355 float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
357 //Loop through all animators
359 for ( AnimatorIter iter = mAnimators.Begin(); iter != mAnimators.End(); )
361 AnimatorBase *animator = *iter;
363 if( animator->Orphan() )
365 //Remove animators whose PropertyOwner has been destroyed
366 iter = mAnimators.Erase(iter);
370 if( animator->IsEnabled() )
372 const float initialDelay( animator->GetInitialDelay() );
373 if( elapsedSecondsClamped >= initialDelay )
375 // Calculate a progress specific to each individual animator
376 float progress(1.0f);
377 const float animatorDuration = animator->GetDuration();
378 if (animatorDuration > 0.0f) // animators can be "immediate"
380 progress = Clamp((elapsedSecondsClamped - initialDelay) / animatorDuration, 0.0f , 1.0f );
382 animator->Update(bufferIndex, progress, bake);
391 if ( animationFinished )
393 animator->SetActive( false );
398 INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
407 } // namespace SceneGraph
409 } // namespace Internal