2 * Copyright (c) 2020 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 ),
86 Animation::~Animation() = default;
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(int32_t 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 ( auto&& item : mAnimators )
126 item->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 );
183 void Animation::PlayAfter( float delaySeconds )
185 if( mState != Playing )
187 mDelaySeconds = delaySeconds;
190 if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
192 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
195 SetAnimatorsActive( true );
201 void Animation::Pause()
203 if (mState == Playing)
209 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
211 if( action == Dali::Animation::BAKE_FINAL )
213 if( mSpeedFactor > 0.0f )
215 mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
219 mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
223 UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
226 void Animation::SetAnimatorsActive( bool active )
228 for ( auto&& item : mAnimators )
230 item->SetActive( active );
234 bool Animation::Stop(BufferIndex bufferIndex)
236 bool animationFinished(false);
238 if (mState == Playing || mState == Paused)
240 animationFinished = true; // The actor-thread should be notified of this
242 if( mEndAction != Dali::Animation::DISCARD )
244 Bake( bufferIndex, mEndAction );
246 // Animators are automatically set to inactive in Bake
250 SetAnimatorsActive( false );
253 // The animation has now been played to completion
258 mElapsedSeconds = mPlayRange.x*mDurationSeconds;
261 return animationFinished;
264 void Animation::OnDestroy(BufferIndex bufferIndex)
266 if (mState == Playing || mState == Paused)
268 if (mEndAction != Dali::Animation::DISCARD)
270 Bake( bufferIndex, mEndAction );
272 // Animators are automatically set to inactive in Bake
276 SetAnimatorsActive( false );
283 void Animation::SetLoopingMode( bool loopingMode )
285 mAutoReverseEnabled = loopingMode;
287 for ( auto&& item : mAnimators )
289 // Send some variables together to figure out the Animation status
290 item->SetSpeedFactor( mSpeedFactor );
291 item->SetLoopCount( mLoopCount );
292 item->SetLoopingMode( loopingMode );
296 void Animation::AddAnimator( OwnerPointer<AnimatorBase>& animator )
298 animator->ConnectToSceneGraph();
299 animator->SetDisconnectAction( mDisconnectAction );
301 mAnimators.PushBack( animator.Release() );
304 void Animation::Update( BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished, bool& progressReached )
309 if (mState == Stopped || mState == Destroyed)
311 // Short circuit when animation isn't running
315 // The animation must still be applied when Paused/Stopping
316 if (mState == Playing)
318 // Sign value of speed factor. It can optimize many arithmetic comparision
319 float signSpeedFactor = ( mSpeedFactor < 0.0f ) ? -1.f : 1.f;
321 // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
322 if( mDelaySeconds > 0.0f )
324 float reduceSeconds = fabsf( elapsedSeconds * mSpeedFactor );
325 if( reduceSeconds > mDelaySeconds )
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;
335 mDelaySeconds -= reduceSeconds;
340 mElapsedSeconds += ( elapsedSeconds * mSpeedFactor );
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;
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;
356 // check it is looped
357 looped = ( elapsedFactor > edgeFactor );
361 WrapInPlayRange( mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds );
363 // Recalculate elapsedFactor here
364 elapsedFactor = signSpeedFactor * mElapsedSeconds;
366 if( mLoopCount != 0 )
368 // Check If this animation is finished
370 if( mCurrentLoop >= mLoopCount )
372 DALI_ASSERT_DEBUG( mCurrentLoop == mLoopCount );
375 // The animation has now been played to completion
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 );
382 // After update animation, mElapsedSeconds must be begin of value
383 mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
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 ) )
393 // The application should be notified by NotificationManager, in another thread
394 progressReached = true;
395 mProgressReachedSignalRequired = false;
399 if( mProgressReachedSignalRequired && ( edgeFactor >= markerFactor ) )
401 progressReached = true;
403 mProgressReachedSignalRequired = mProgressMarker > 0.0f;
408 // when it is not on looped state, only 1 case to send progress signal.
409 // (require && elaped >= marker)
410 if( mProgressReachedSignalRequired && ( elapsedFactor >= markerFactor ) )
412 // The application should be notified by NotificationManager, in another thread
413 progressReached = true;
414 mProgressReachedSignalRequired = false;
419 // Already updated when finished. So skip.
422 UpdateAnimators(bufferIndex, false, false );
426 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
428 mIsActive[bufferIndex] = false;
430 const Vector2 playRange( mPlayRange * mDurationSeconds );
431 float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
433 //Loop through all animators
435 for ( auto&& iter = mAnimators.Begin(); iter != mAnimators.End(); )
437 AnimatorBase *animator = *iter;
439 if( animator->Orphan() )
441 //Remove animators whose PropertyOwner has been destroyed
442 iter = mAnimators.Erase(iter);
446 if( animator->IsEnabled() )
448 const float intervalDelay( animator->GetIntervalDelay() );
450 if( elapsedSecondsClamped >= intervalDelay )
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"
457 progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f , 1.0f );
459 animator->Update(bufferIndex, progress, bake);
461 if (animatorDuration > 0.0f && (elapsedSecondsClamped - intervalDelay) <= animatorDuration)
463 mIsActive[bufferIndex] = true;
473 if ( animationFinished )
475 animator->SetActive( false );
480 INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
489 } // namespace SceneGraph
491 } // namespace Internal