Refactoring Animation Update 67/159467/5
authorEunki Hong <eunkiki.hong@samsung.com>
Thu, 9 Nov 2017 05:22:19 +0000 (14:22 +0900)
committerEunki Hong <eunkiki.hong@samsung.com>
Wed, 15 Nov 2017 09:10:50 +0000 (18:10 +0900)
Refactor animation update rootine so defeat some bugs.

1) ProgressReachedSignal works fine when speedfactor < 0
2) ProgressReachedSignal not works when marker value is out of range
3) ProgressReachedSignal works fine when looped

Also, use signvalue of speedfactor so reduce if-else branch of arithmetic comparision

Change-Id: Ia8a9d242b3d094f1acaa87633ac3107e6220ecfa
Signed-off-by: Eunki Hong <eunkiki.hong@samsung.com>
automated-tests/src/dali/utc-Dali-Animation.cpp
dali/internal/update/animation/scene-graph-animation.cpp

index cd87b7c..66dba1b 100644 (file)
@@ -12317,6 +12317,121 @@ int UtcDaliAnimationMultipleProgressSignalsP(void)
   END_TEST;
 }
 
+int UtcDaliAnimationMultipleProgressSignalsP2(void)
+{
+  tet_infoline( "Multiple animations with different progress markers and big step time" );
+
+  TestApplication application;
+
+  Actor actor = Actor::New();
+  Stage::GetCurrent().Add(actor);
+
+  // Build the animation
+  Animation animationAlpha = Animation::New(0.0f);
+  Animation animationBeta = Animation::New(0.0f);
+
+  //Set duration
+  const float durationSeconds(1.0f);
+  animationAlpha.SetDuration(durationSeconds);
+  animationBeta.SetDuration(durationSeconds);
+
+  bool progressSignalReceivedAlpha(false);
+  bool progressSignalReceivedBeta(false);
+
+  AnimationProgressCheck progressCheckAlpha(progressSignalReceivedAlpha, "animation:Alpha");
+  AnimationProgressCheck progressCheckBeta(progressSignalReceivedBeta, "animation:Beta" );
+
+  DevelAnimation::ProgressReachedSignal( animationAlpha ).Connect( &application, progressCheckAlpha );
+  DevelAnimation::ProgressReachedSignal( animationBeta ).Connect( &application, progressCheckBeta);
+  application.SendNotification();
+
+  Vector3 targetPosition(100.0f, 100.0f, 100.0f);
+  animationAlpha.AnimateTo(Property(actor, Actor::Property::POSITION), targetPosition, AlphaFunction::LINEAR);
+  animationBeta.AnimateTo(Property(actor, Actor::Property::POSITION), targetPosition, AlphaFunction::LINEAR);
+
+  tet_infoline( "AnimationAlpha Progress notification set to 1%" );
+  DevelAnimation::SetProgressNotification( animationAlpha, 0.01f );
+
+  tet_infoline( "AnimationBeta Progress notification set to 99%" );
+  DevelAnimation::SetProgressNotification( animationBeta, 0.99f );
+
+  application.SendNotification();
+  application.Render( );
+
+  progressCheckAlpha.CheckSignalNotReceived();
+  progressCheckBeta.CheckSignalNotReceived();
+
+  // Start the animations unlimited looping
+  animationAlpha.SetLooping( true );
+  animationBeta.SetLooping( true );
+  animationAlpha.Play();
+  animationBeta.Play();
+
+  application.SendNotification();
+  application.Render(0); // start animation
+  application.Render(durationSeconds*20.0f ); // 2% progress
+  application.SendNotification();
+  DALI_TEST_EQUALS( 0.02f, animationAlpha.GetCurrentProgress(), TEST_LOCATION );
+
+  tet_infoline( "Animation at 2% - Alpha signals should be received, Beta should not." );
+
+  progressCheckAlpha.CheckSignalReceived();
+  progressCheckBeta.CheckSignalNotReceived();
+
+  tet_infoline( "Progress check reset" );
+  progressCheckAlpha.Reset();
+  progressCheckBeta.Reset();
+
+  application.SendNotification();
+  application.Render(durationSeconds*960.0f ); // 98% progress
+  application.SendNotification();
+  tet_infoline( "Animation at 98% - No signal received" );
+  DALI_TEST_EQUALS( 0.98f, animationAlpha.GetCurrentProgress(), TEST_LOCATION );
+
+  progressCheckAlpha.CheckSignalNotReceived();
+  progressCheckBeta.CheckSignalNotReceived();
+
+  application.SendNotification();
+  application.Render(durationSeconds*40.0f ); // 2% progress
+  application.SendNotification();
+  tet_infoline( "Animation loop once and now 2% - Alpha and Beta should receive signal" );
+  application.SendNotification();
+
+  DALI_TEST_EQUALS( 0.02f, animationBeta.GetCurrentProgress(), TEST_LOCATION );
+
+  progressCheckAlpha.CheckSignalReceived();
+  progressCheckBeta.CheckSignalReceived();
+
+  tet_infoline( "Progress check reset" );
+  progressCheckAlpha.Reset();
+  progressCheckBeta.Reset();
+
+  application.SendNotification();
+  application.Render(durationSeconds*980.0f ); // 100% progress
+  application.SendNotification();
+  tet_infoline( "Animation loop one more time. and now 100% - Beta should receive signal, Alhpa sholud not" );
+  application.SendNotification();
+
+  progressCheckAlpha.CheckSignalNotReceived();
+  progressCheckBeta.CheckSignalReceived();
+
+  tet_infoline( "Progress check reset" );
+  progressCheckAlpha.Reset();
+  progressCheckBeta.Reset();
+
+  animationAlpha.SetLooping( false );
+  animationBeta.SetLooping( false );
+
+  application.SendNotification();
+  application.Render(static_cast<unsigned int>(durationSeconds*2000.0f) + 1u/*just beyond the animation duration*/);
+  application.SendNotification();
+
+  // We did expect the animation to finish
+  tet_infoline( "Animation finished" );
+
+  END_TEST;
+}
+
 int UtcDaliAnimationProgressSignalWithPlayAfterP(void)
 {
   tet_infoline( "Multiple animations with different progress markers" );
@@ -12441,11 +12556,11 @@ int UtcDaliAnimationProgressCallbackWithLoopingP(void)
   Animation animation = Animation::New(0.0f);
 
   //Set duration
-  float durationSeconds(1.0f);
+  const float durationSeconds(1.0f);
   animation.SetDuration(durationSeconds);
 
   // Set Looping Count
-  int loopCount( 4 );
+  const int loopCount( 4 );
   animation.SetLoopCount( loopCount );
 
   bool finishedSignalReceived(false);
@@ -12529,7 +12644,7 @@ int UtcDaliAnimationProgressCallbackWithLoopingP2(void)
   Animation animation = Animation::New(0.0f);
 
   //Set duration
-  float durationSeconds(1.0f);
+  const float durationSeconds(1.0f);
   animation.SetDuration(durationSeconds);
 
   // Set Looping Unlmited
@@ -12611,6 +12726,224 @@ int UtcDaliAnimationProgressCallbackWithLoopingP2(void)
   END_TEST;
 }
 
+int UtcDaliAnimationProgressCallbackNegativeSpeed(void)
+{
+  TestApplication application;
+
+  Actor actor = Actor::New();
+  Stage::GetCurrent().Add(actor);
+
+  // Build the animation
+  Animation animation = Animation::New(0.0f);
+
+  //Set duration
+  const float durationSeconds(1.0f);
+  animation.SetDuration(durationSeconds);
+
+  //Set speed negative
+  animation.SetSpeedFactor( -1.0f );
+
+  // Set Looping Unlmited
+  animation.SetLooping( true );
+
+  bool finishedSignalReceived(false);
+  bool progressSignalReceived(false);
+
+  AnimationFinishCheck finishCheck(finishedSignalReceived);
+  animation.FinishedSignal().Connect(&application, finishCheck);
+
+  AnimationProgressCheck progressCheck(progressSignalReceived);
+  DevelAnimation::ProgressReachedSignal( animation ).Connect( &application, progressCheck);
+  application.SendNotification();
+
+  Vector3 targetPosition(100.0f, 100.0f, 100.0f);
+  animation.AnimateTo(Property(actor, Actor::Property::POSITION), targetPosition, AlphaFunction::LINEAR);
+
+  tet_infoline( "Animation Progress notification set to 50%" );
+  DevelAnimation::SetProgressNotification( animation, 0.5f );
+
+  application.SendNotification();
+  application.Render( );
+
+  progressCheck.CheckSignalNotReceived();
+
+  animation.Play();
+
+  for(int count = 0; count < 4; count++)
+  {
+    application.SendNotification();
+    application.Render(0); // start animation
+    progressCheck.CheckSignalNotReceived();
+
+    application.SendNotification();
+    application.Render(durationSeconds*0.25*1000.0f ); // 25% progress
+    DALI_TEST_EQUALS( 0.75f, animation.GetCurrentProgress(), TEST_LOCATION );
+
+    tet_infoline( "Animation at 25%" );
+
+    progressCheck.CheckSignalNotReceived();
+
+    application.SendNotification();
+    application.Render(durationSeconds*0.25*1000.0f ); // 50% progress
+    application.SendNotification();
+    tet_infoline( "Animation at 50%" );
+    DALI_TEST_EQUALS( 0.5f, animation.GetCurrentProgress(), TEST_LOCATION );
+
+    progressCheck.CheckSignalReceived();
+
+    tet_infoline( "Progress check reset" );
+    progressCheck.Reset();
+
+    application.Render(durationSeconds*0.25*1000.0f ); // 75% progress
+    tet_infoline( "Animation at 75%" );
+    application.SendNotification();
+
+    DALI_TEST_EQUALS( 0.25f, animation.GetCurrentProgress(), TEST_LOCATION );
+
+    progressCheck.CheckSignalNotReceived();
+
+    application.Render(durationSeconds*0.25*1000.0f ); // 100% progress
+    tet_infoline( "Animation at 100%" );
+    application.SendNotification();
+
+    //Nothing check at 100% progress. cause It can be both 100% and 0%.
+    finishCheck.CheckSignalNotReceived();
+    application.SendNotification();
+  }
+  finishCheck.CheckSignalNotReceived();
+
+  animation.Stop();
+  animation.SetLooping( false );
+  animation.SetLoopCount( 4 );
+  animation.Play();
+  application.Render(0u);
+  application.SendNotification();
+
+  for(int count = 0; count < 4; count++)
+  {
+    application.SendNotification();
+    application.Render(0); // start animation
+    progressCheck.CheckSignalNotReceived();
+
+    application.SendNotification();
+    application.Render(durationSeconds*0.25*1000.0f ); // 25% progress
+    DALI_TEST_EQUALS( 0.75f, animation.GetCurrentProgress(), TEST_LOCATION );
+
+    tet_infoline( "Animation at 25%" );
+
+    progressCheck.CheckSignalNotReceived();
+
+    application.SendNotification();
+    application.Render(durationSeconds*0.25*1000.0f ); // 50% progress
+    application.SendNotification();
+    tet_infoline( "Animation at 50%" );
+    DALI_TEST_EQUALS( 0.5f, animation.GetCurrentProgress(), TEST_LOCATION );
+
+    progressCheck.CheckSignalReceived();
+
+    tet_infoline( "Progress check reset" );
+    progressCheck.Reset();
+
+    application.Render(durationSeconds*0.25*1000.0f ); // 75% progress
+    tet_infoline( "Animation at 75%" );
+    application.SendNotification();
+
+    DALI_TEST_EQUALS( 0.25f, animation.GetCurrentProgress(), TEST_LOCATION );
+
+    progressCheck.CheckSignalNotReceived();
+
+    application.Render(durationSeconds*0.25*1000.0f ); // 100% progress
+    tet_infoline( "Animation at 100%" );
+    application.SendNotification();
+
+    //Nothing check at 100% progress. cause It can be both 100% and 0%.
+    application.SendNotification();
+  }
+  application.Render(10u);
+  application.SendNotification();
+  application.Render(0u);
+  application.SendNotification();
+
+  finishCheck.CheckSignalReceived();
+
+  END_TEST;
+}
+
+int UtcDaliAnimationProgressCallbackInvalidSignalN(void)
+{
+  TestApplication application;
+
+  Actor actor = Actor::New();
+  Stage::GetCurrent().Add(actor);
+
+  // Build the animation
+  Animation animation = Animation::New(0.0f);
+
+  //Set duration
+  const float durationSeconds(1.0f);
+  animation.SetDuration(durationSeconds);
+
+  bool finishedSignalReceived(false);
+  bool progressSignalReceived(false);
+
+  AnimationFinishCheck finishCheck(finishedSignalReceived);
+  animation.FinishedSignal().Connect(&application, finishCheck);
+
+  AnimationProgressCheck progressCheck(progressSignalReceived);
+  DevelAnimation::ProgressReachedSignal( animation ).Connect( &application, progressCheck);
+  application.SendNotification();
+
+  Vector3 targetPosition(100.0f, 100.0f, 100.0f);
+  animation.AnimateTo(Property(actor, Actor::Property::POSITION), targetPosition, AlphaFunction::LINEAR);
+
+  tet_infoline( "Animation Progress PlayRange as 10% ~ 90%" );
+  animation.SetPlayRange( Vector2( 0.1f, 0.9f ) );
+
+  tet_infoline( "Animation Progress notification set to >90% that never can notificated" );
+  DevelAnimation::SetProgressNotification( animation, 0.9f + Math::MACHINE_EPSILON_1 );
+
+  application.SendNotification();
+  application.Render( );
+
+  progressCheck.CheckSignalNotReceived();
+
+  animation.Play();
+
+  application.SendNotification();
+  application.Render(0); // start animation
+  application.Render(durationSeconds*0.25*1000.0f ); // 35% progress
+  DALI_TEST_EQUALS( 0.35f, animation.GetCurrentProgress(), TEST_LOCATION );
+
+  tet_infoline( "Animation at 35%" );
+
+  progressCheck.CheckSignalNotReceived();
+
+  application.SendNotification();
+  application.Render(durationSeconds*0.25*1000.0f ); // 60% progress
+  application.SendNotification();
+  DALI_TEST_EQUALS( 0.6f, animation.GetCurrentProgress(), TEST_LOCATION );
+
+  tet_infoline( "Animation at 60%" );
+
+  progressCheck.CheckSignalNotReceived();
+
+  application.Render(durationSeconds*0.25*1000.0f ); // 85% progress
+  tet_infoline( "Animation at 85%" );
+  application.SendNotification();
+  DALI_TEST_EQUALS( 0.85f, animation.GetCurrentProgress(), TEST_LOCATION );
+
+  progressCheck.CheckSignalNotReceived();
+
+  application.Render(durationSeconds*0.25*1000.0f ); // 90% progress
+  tet_infoline( "Animation over 90%" );
+  application.SendNotification();
+
+  // progress never signaled because playrange is 90%
+  progressCheck.CheckSignalNotReceived();
+
+  END_TEST;
+}
+
 int UtcDaliAnimationProgressCallbackLongDurationP(void)
 {
   TestApplication application;
index 260be92..73317ae 100644 (file)
@@ -30,15 +30,15 @@ namespace //Unnamed namespace
 //Memory pool used to allocate new animations. Memory used by this pool will be released when shutting down DALi
 Dali::Internal::MemoryPoolObjectAllocator<Dali::Internal::SceneGraph::Animation> gAnimationMemoryPool;
 
-inline void WrapInPlayRange( float& elapsed, const Dali::Vector2& playRangeSeconds)
+inline void WrapInPlayRange( float& elapsed, const float& playRangeStartSeconds, const float& playRangeEndSeconds)
 {
-  if( elapsed > playRangeSeconds.y )
+  if( elapsed > playRangeEndSeconds )
   {
-    elapsed = playRangeSeconds.x + fmodf((elapsed-playRangeSeconds.x), (playRangeSeconds.y-playRangeSeconds.x));
+    elapsed = playRangeStartSeconds + fmodf( ( elapsed - playRangeStartSeconds ), ( playRangeEndSeconds - playRangeStartSeconds ) );
   }
-  else if( elapsed < playRangeSeconds.x )
+  else if( elapsed < playRangeStartSeconds )
   {
-    elapsed = playRangeSeconds.y - fmodf( (playRangeSeconds.x - elapsed), (playRangeSeconds.y-playRangeSeconds.x) );
+    elapsed = playRangeEndSeconds - fmodf( ( playRangeStartSeconds - elapsed ), ( playRangeEndSeconds - playRangeStartSeconds ) );
   }
 }
 
@@ -315,20 +315,19 @@ void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& loop
   // The animation must still be applied when Paused/Stopping
   if (mState == Playing)
   {
+    // Sign value of speed factor. It can optimize many arithmetic comparision
+    int signSpeedFactor = ( mSpeedFactor < 0.0f ) ? -1 : 1;
+
     // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
     if( mDelaySeconds > 0.0f )
     {
       float reduceSeconds = fabsf( elapsedSeconds * mSpeedFactor );
       if( reduceSeconds > mDelaySeconds )
       {
-        if( mSpeedFactor < 0.0f )
-        {
-          mElapsedSeconds -= reduceSeconds - mDelaySeconds;
-        }
-        else
-        {
-          mElapsedSeconds += reduceSeconds - mDelaySeconds;
-        }
+        // add overflowed time to mElapsedSecond.
+        // If speed factor > 0, add it. if speed factor < 0, subtract it.
+        float overflowSeconds = reduceSeconds - mDelaySeconds;
+        mElapsedSeconds += signSpeedFactor * overflowSeconds;
         mDelaySeconds = 0.0f;
       }
       else
@@ -341,63 +340,86 @@ void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& loop
       mElapsedSeconds += ( elapsedSeconds * mSpeedFactor );
     }
 
-    if ( mProgressReachedSignalRequired && ( mElapsedSeconds >= mProgressMarker ) )
-    {
-      // The application should be notified by NotificationManager, in another thread
-      progressReached = true;
-      mProgressReachedSignalRequired = false;
-    }
-  }
+    const float playRangeStartSeconds = mPlayRange.x * mDurationSeconds;
+    const float playRangeEndSeconds = mPlayRange.y * mDurationSeconds;
+    // Final reached seconds. It can optimize many arithmetic comparision
+    float edgeRangeSeconds = ( mSpeedFactor < 0.0f ) ? playRangeStartSeconds : playRangeEndSeconds;
 
-  Vector2 playRangeSeconds = mPlayRange * mDurationSeconds;
+    // Optimized Factors.
+    // elapsed >  edge   --> check if looped
+    // elapsed >= marker --> check if elapsed reached to marker in normal case
+    // edge    >= marker --> check if elapsed reached to marker in looped case
+    float elapsedFactor = signSpeedFactor * mElapsedSeconds;
+    float edgeFactor = signSpeedFactor * edgeRangeSeconds;
+    float markerFactor = signSpeedFactor * mProgressMarker;
 
-  if( 0 == mLoopCount || mCurrentLoop < mLoopCount - 1) // '-1' here so last loop iteration uses play once below
-  {
-    // looping
-    looped =  (mState == Playing                                                 &&
-               (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
-                ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
+    // check it is looped
+    looped = ( elapsedFactor > edgeFactor );
 
-    WrapInPlayRange( mElapsedSeconds, playRangeSeconds );
+    if( looped )
+    {
+      WrapInPlayRange( mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds );
 
-    UpdateAnimators(bufferIndex, false, false );
+      // Recalculate elapsedFactor here
+      elapsedFactor = signSpeedFactor * mElapsedSeconds;
 
-    if(looped)
-    {
       if( mLoopCount != 0 )
       {
+        // Check If this animation is finished
         ++mCurrentLoop;
-      }
-      mProgressReachedSignalRequired = mProgressMarker > 0.0f;
-      // don't increment mPlayedCount until the finished final loop
-    }
-  }
-  else
-  {
-    // playing once (and last mCurrentLoop)
-    finished = (mState == Playing                                                 &&
-                (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
-                 ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
+        if( mCurrentLoop >= mLoopCount )
+        {
+          DALI_ASSERT_DEBUG( mCurrentLoop == mLoopCount );
+          finished = true;
 
-    // update with bake if finished
-    UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::Discard), finished );
+          // The animation has now been played to completion
+          ++mPlayedCount;
 
-    if(finished)
-    {
-      // The animation has now been played to completion
-      ++mPlayedCount;
+          // Make elapsed second as edge of range forcely.
+          mElapsedSeconds = edgeRangeSeconds + signSpeedFactor * Math::MACHINE_EPSILON_10;
+          UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::Discard), finished );
 
-      // loop iterations come to this else branch for their final iterations
-      if( mCurrentLoop < mLoopCount)
-      {
-        ++mCurrentLoop;
-        DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
+          // After update animation, mElapsedSeconds must be begin of value
+          mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
+          mState = Stopped;
+        }
       }
 
-      mProgressReachedSignalRequired = mProgressMarker > 0.0f;
-      mElapsedSeconds = playRangeSeconds.x;
-      mState = Stopped;
+      // when it is on looped state, 2 case to send progress signal.
+      // (require && range_value >= marker) ||         << Signal at previous loop
+      // (marker > 0 && !finished && elaped >= marker) << Signal at current loop
+      if( ( mProgressMarker > 0.0f ) && !finished && ( elapsedFactor >= markerFactor ) )
+      {
+        // The application should be notified by NotificationManager, in another thread
+        progressReached = true;
+        mProgressReachedSignalRequired = false;
+      }
+      else
+      {
+        if( mProgressReachedSignalRequired && ( edgeFactor >= markerFactor ) )
+        {
+          progressReached = true;
+        }
+        mProgressReachedSignalRequired = mProgressMarker > 0.0f;
+      }
     }
+    else
+    {
+      // when it is not on looped state, only 1 case to send progress signal.
+      // (require && elaped >= marker)
+      if( mProgressReachedSignalRequired && ( elapsedFactor >= markerFactor ) )
+      {
+        // The application should be notified by NotificationManager, in another thread
+        progressReached = true;
+        mProgressReachedSignalRequired = false;
+      }
+    }
+  }
+
+  // Already updated when finished. So skip.
+  if( !finished )
+  {
+    UpdateAnimators(bufferIndex, false, false );
   }
 }