From d2fd7ff2d8fbf878f33c253ba65b67b09918bc72 Mon Sep 17 00:00:00 2001 From: Seungho Baek Date: Tue, 22 Apr 2025 14:15:09 +0900 Subject: [PATCH] AlphaFunction for spring animation Change-Id: Ie25f268977c6b5e87ad57e5cae2279adbbba36c5 Signed-off-by: Seungho Baek --- .../src/dali/utc-Dali-Animation.cpp | 337 +++++++++++++++--- .../update/animation/scene-graph-animator.h | 34 ++ dali/public-api/animation/alpha-function.cpp | 56 ++- dali/public-api/animation/alpha-function.h | 69 +++- dali/public-api/animation/spring-data.cpp | 76 ++++ dali/public-api/animation/spring-data.h | 67 ++++ dali/public-api/dali-core.h | 1 + dali/public-api/file.list | 2 + 8 files changed, 586 insertions(+), 56 deletions(-) create mode 100644 dali/public-api/animation/spring-data.cpp create mode 100644 dali/public-api/animation/spring-data.h diff --git a/automated-tests/src/dali/utc-Dali-Animation.cpp b/automated-tests/src/dali/utc-Dali-Animation.cpp index 0839a11c0..cf9dfaaba 100644 --- a/automated-tests/src/dali/utc-Dali-Animation.cpp +++ b/automated-tests/src/dali/utc-Dali-Animation.cpp @@ -245,7 +245,7 @@ int UtcDaliAnimationMoveConstructor(void) { TestApplication application; - //Animation + // Animation Animation animation = Animation::New(1.0f); DALI_TEST_CHECK(animation); @@ -1665,7 +1665,7 @@ int UtcDaliAnimationSetCurrentProgressP(void) // Build the animation Animation animation = Animation::New(0.0f); - //Set duration + // Set duration float durationSeconds(1.0f); animation.SetDuration(durationSeconds); @@ -1693,7 +1693,7 @@ int UtcDaliAnimationSetCurrentProgressP(void) animation.Play(); // Test that calling play has no effect, when animation is already playing application.SendNotification(); - //Set the progress to 70% + // Set the progress to 70% animation.SetCurrentProgress(0.7f); application.SendNotification(); application.Render(static_cast(durationSeconds * 100.0f) /* 80% progress */); @@ -1728,7 +1728,7 @@ int UtcDaliAnimationSetCurrentProgressN(void) // Build the animation Animation animation = Animation::New(0.0f); - //Set duration + // Set duration float durationSeconds(1.0f); animation.SetDuration(durationSeconds); @@ -1740,7 +1740,7 @@ int UtcDaliAnimationSetCurrentProgressN(void) Vector3 targetPosition(100.0f, 100.0f, 100.0f); animation.AnimateTo(Property(actor, Actor::Property::POSITION), targetPosition, AlphaFunction::LINEAR); - //Trying to set the current cursor outside the range [0..1] is ignored + // Trying to set the current cursor outside the range [0..1] is ignored animation.SetCurrentProgress(-1.0f); application.SendNotification(); DALI_TEST_EQUALS(0.0f, animation.GetCurrentProgress(), TEST_LOCATION); @@ -1762,17 +1762,17 @@ int UtcDaliAnimationGetCurrentProgressP(void) Animation animation = Animation::New(0.0f); animation.Play(); - //Test GetCurrentProgress return 0.0 as the duration is 0.0 + // Test GetCurrentProgress return 0.0 as the duration is 0.0 DALI_TEST_EQUALS(0.0f, animation.GetCurrentProgress(), TEST_LOCATION); animation.SetCurrentProgress(0.5f); application.SendNotification(); application.Render(static_cast(100.0f)); - //Progress should still be 0.0 + // Progress should still be 0.0 DALI_TEST_EQUALS(0.0f, animation.GetCurrentProgress(), TEST_LOCATION); - //Set duration + // Set duration float durationSeconds(1.0f); animation.SetDuration(durationSeconds); application.SendNotification(); @@ -1800,7 +1800,7 @@ int UtcDaliAnimationGetCurrentProgressP(void) animation.Play(); // Test that calling play has no effect, when animation is already playing application.SendNotification(); - //Set the progress to 70% + // Set the progress to 70% animation.SetCurrentProgress(0.7f); application.SendNotification(); application.Render(static_cast(durationSeconds * 100.0f) /* 80% progress */); @@ -1838,7 +1838,7 @@ int UtcDaliAnimationSetSpeedFactorP1(void) keyframes.Add(1.0f, targetPosition); animation.AnimateBetween(Property(actor, Actor::Property::POSITION), keyframes, AlphaFunction::LINEAR); - //Set speed to be x2 + // Set speed to be x2 animation.SetSpeedFactor(2.0f); // Start the animation @@ -1978,7 +1978,7 @@ int UtcDaliAnimationSetSpeedFactorP3(void) tet_printf("Test half speed factor. Animation will take twice the duration\n"); - //Set speed to be half of normal speed + // Set speed to be half of normal speed animation.SetSpeedFactor(0.5f); // Start the animation @@ -2610,7 +2610,7 @@ int UtcDaliAnimationSetPlayRangeN(void) Animation animation = Animation::New(0); application.SendNotification(); - //PlayRange out of bounds + // PlayRange out of bounds animation.SetPlayRange(Vector2(-1.0f, 1.0f)); application.SendNotification(); DALI_TEST_EQUALS(Vector2(0.0f, 1.0f), animation.GetPlayRange(), TEST_LOCATION); @@ -2618,7 +2618,7 @@ int UtcDaliAnimationSetPlayRangeN(void) application.SendNotification(); DALI_TEST_EQUALS(Vector2(0.0f, 1.0f), animation.GetPlayRange(), TEST_LOCATION); - //If playRange is not in the correct order it has to be ordered + // If playRange is not in the correct order it has to be ordered animation.SetPlayRange(Vector2(0.8f, 0.2f)); application.SendNotification(); DALI_TEST_EQUALS(Vector2(0.2f, 0.8f), animation.GetPlayRange(), TEST_LOCATION); @@ -2637,7 +2637,7 @@ int UtcDaliAnimationGetPlayRangeP(void) Animation animation = Animation::New(1.0f); application.SendNotification(); - //If PlayRange not specified it should be 0.0-1.0 by default + // If PlayRange not specified it should be 0.0-1.0 by default DALI_TEST_EQUALS(Vector2(0.0f, 1.0f), animation.GetPlayRange(), TEST_LOCATION); // Set range between 0.4 and 0.8 @@ -3256,7 +3256,7 @@ int UtcDaliAnimationPlayRangeP(void) AnimationFinishCheck finishCheck(signalReceived); animation.FinishedSignal().Connect(&application, finishCheck); - //Test that setting progress outside the range doesn't work + // Test that setting progress outside the range doesn't work animation.SetCurrentProgress(0.9f); application.SendNotification(); application.Render(0); @@ -3289,7 +3289,7 @@ int UtcDaliAnimationPlayRangeP(void) application.Render(0); DALI_TEST_EQUALS(targetPosition * 0.8f, actor.GetCurrentProperty(Actor::Property::POSITION), TEST_LOCATION); - //Loop inside the range + // Loop inside the range finishCheck.Reset(); animation.SetLooping(true); animation.Play(); @@ -3313,7 +3313,7 @@ int UtcDaliAnimationPlayRangeP(void) application.SendNotification(); finishCheck.CheckSignalNotReceived(); - //Test change range on the fly + // Test change range on the fly animation.SetPlayRange(Vector2(0.2f, 0.9f)); application.SendNotification(); @@ -3402,7 +3402,7 @@ int UtcDaliAnimationPlayFromN(void) Vector3 targetPosition(100.0f, 100.0f, 100.0f); animation.AnimateTo(Property(actor, Actor::Property::POSITION), targetPosition, AlphaFunction::LINEAR); - //PlayFrom with an argument outside the range [0..1] will be ignored + // PlayFrom with an argument outside the range [0..1] will be ignored animation.PlayFrom(-1.0f); application.SendNotification(); DALI_TEST_EQUALS(0.0f, animation.GetCurrentProgress(), TEST_LOCATION); @@ -10811,7 +10811,7 @@ int UtcDaliAnimationAnimateBetweenActorVisibleCubicP(void) keyFrames.Add(0.8f, false); keyFrames.Add(1.0f, true); - //Cubic interpolation for boolean values should be ignored + // Cubic interpolation for boolean values should be ignored animation.AnimateBetween(Property(actor, Actor::Property::VISIBLE), keyFrames, Animation::CUBIC); // Start the animation @@ -10964,7 +10964,7 @@ int UtcDaliAnimationAnimateBetweenActorOrientation01CubicP(void) KeyFrames keyFrames = KeyFrames::New(); keyFrames.Add(0.0f, AngleAxis(Degree(60), Vector3::ZAXIS)); - //Cubic interpolation should be ignored for quaternions + // Cubic interpolation should be ignored for quaternions animation.AnimateBetween(Property(actor, Actor::Property::ORIENTATION), keyFrames, Animation::CUBIC); // Start the animation @@ -11010,7 +11010,7 @@ int UtcDaliAnimationAnimateBetweenActorOrientation02CubicP(void) keyFrames.Add(0.5f, AngleAxis(Degree(120), Vector3::XAXIS)); keyFrames.Add(1.0f, AngleAxis(Degree(120), Vector3::YAXIS)); - //Cubic interpolation should be ignored for quaternions + // Cubic interpolation should be ignored for quaternions animation.AnimateBetween(Property(actor, Actor::Property::ORIENTATION), keyFrames, Animation::CUBIC); // Start the animation @@ -11542,7 +11542,7 @@ int UtcDaliAnimationAnimateP(void) Actor actor = Actor::New(); application.GetScene().Add(actor); - //Build the path + // Build the path Vector3 position0(30.0, 80.0, 0.0); Vector3 position1(70.0, 120.0, 0.0); Vector3 position2(100.0, 100.0, 0.0); @@ -11552,11 +11552,11 @@ int UtcDaliAnimationAnimateP(void) path.AddPoint(position1); path.AddPoint(position2); - //Control points for first segment + // Control points for first segment path.AddControlPoint(Vector3(39.0, 90.0, 0.0)); path.AddControlPoint(Vector3(56.0, 119.0, 0.0)); - //Control points for second segment + // Control points for second segment path.AddControlPoint(Vector3(78.0, 120.0, 0.0)); path.AddControlPoint(Vector3(93.0, 104.0, 0.0)); @@ -11621,7 +11621,7 @@ int UtcDaliAnimationAnimateAlphaFunctionP(void) Actor actor = Actor::New(); application.GetScene().Add(actor); - //Build the path + // Build the path Vector3 position0(30.0, 80.0, 0.0); Vector3 position1(70.0, 120.0, 0.0); Vector3 position2(100.0, 100.0, 0.0); @@ -11631,11 +11631,11 @@ int UtcDaliAnimationAnimateAlphaFunctionP(void) path.AddPoint(position1); path.AddPoint(position2); - //Control points for first segment + // Control points for first segment path.AddControlPoint(Vector3(39.0, 90.0, 0.0)); path.AddControlPoint(Vector3(56.0, 119.0, 0.0)); - //Control points for second segment + // Control points for second segment path.AddControlPoint(Vector3(78.0, 120.0, 0.0)); path.AddControlPoint(Vector3(93.0, 104.0, 0.0)); @@ -11700,7 +11700,7 @@ int UtcDaliAnimationAnimateTimePeriodP(void) Actor actor = Actor::New(); application.GetScene().Add(actor); - //Build the path + // Build the path Vector3 position0(30.0, 80.0, 0.0); Vector3 position1(70.0, 120.0, 0.0); Vector3 position2(100.0, 100.0, 0.0); @@ -11710,11 +11710,11 @@ int UtcDaliAnimationAnimateTimePeriodP(void) path.AddPoint(position1); path.AddPoint(position2); - //Control points for first segment + // Control points for first segment path.AddControlPoint(Vector3(39.0, 90.0, 0.0)); path.AddControlPoint(Vector3(56.0, 119.0, 0.0)); - //Control points for second segment + // Control points for second segment path.AddControlPoint(Vector3(78.0, 120.0, 0.0)); path.AddControlPoint(Vector3(93.0, 104.0, 0.0)); @@ -11779,7 +11779,7 @@ int UtcDaliAnimationAnimateAlphaFunctionTimePeriodP(void) Actor actor = Actor::New(); application.GetScene().Add(actor); - //Build the path + // Build the path Vector3 position0(30.0, 80.0, 0.0); Vector3 position1(70.0, 120.0, 0.0); Vector3 position2(100.0, 100.0, 0.0); @@ -11789,11 +11789,11 @@ int UtcDaliAnimationAnimateAlphaFunctionTimePeriodP(void) path.AddPoint(position1); path.AddPoint(position2); - //Control points for first segment + // Control points for first segment path.AddControlPoint(Vector3(39.0, 90.0, 0.0)); path.AddControlPoint(Vector3(56.0, 119.0, 0.0)); - //Control points for second segment + // Control points for second segment path.AddControlPoint(Vector3(78.0, 120.0, 0.0)); path.AddControlPoint(Vector3(93.0, 104.0, 0.0)); @@ -12355,7 +12355,7 @@ int UtcDaliAnimationSetAndGetTargetBeforePlayP(void) // Build the animation Animation animation = Animation::New(2.0f); - //Test GetCurrentProgress return 0.0 as the duration is 0.0 + // Test GetCurrentProgress return 0.0 as the duration is 0.0 DALI_TEST_EQUALS(0.0f, animation.GetCurrentProgress(), TEST_LOCATION); DALI_TEST_EQUALS(Vector3(0.0f, 0.0f, 0.0f), actor.GetCurrentProperty(Actor::Property::POSITION), TEST_LOCATION); @@ -12420,7 +12420,7 @@ int UtcDaliAnimationSetAndGetTargetBeforePlayMulitpleAnimatorsPositionP(void) // Build the animation Animation animation = Animation::New(2.0f); - //Test GetCurrentProgress return 0.0 as the duration is 0.0 + // Test GetCurrentProgress return 0.0 as the duration is 0.0 DALI_TEST_EQUALS(0.0f, animation.GetCurrentProgress(), TEST_LOCATION); DALI_TEST_EQUALS(Vector3(0.0f, 0.0f, 0.0f), actor.GetCurrentProperty(Actor::Property::POSITION), TEST_LOCATION); @@ -12824,7 +12824,7 @@ int UtcDaliAnimationProgressCallbackP(void) // Build the animation Animation animation = Animation::New(0.0f); - //Set duration + // Set duration float durationSeconds(1.0f); animation.SetDuration(durationSeconds); @@ -13105,7 +13105,7 @@ int UtcDaliAnimationPlayAfterP2(void) float durationSeconds(1.0f); Animation animation = Animation::New(durationSeconds); animation.SetLooping(true); - animation.SetSpeedFactor(-1.0f); //Set SpeedFactor as < 0 + animation.SetSpeedFactor(-1.0f); // Set SpeedFactor as < 0 bool signalReceived(false); AnimationFinishCheck finishCheck(signalReceived); @@ -13843,7 +13843,7 @@ int UtcDaliAnimationProgressSignalConnectionWithoutProgressMarkerP(void) // Build the animation Animation animation = Animation::New(0.0f); - //Set duration + // Set duration float durationSeconds(1.0f); animation.SetDuration(durationSeconds); @@ -13896,7 +13896,7 @@ int UtcDaliAnimationMultipleProgressSignalsP(void) Animation animationAlpha = Animation::New(0.0f); Animation animationBeta = Animation::New(0.0f); - //Set duration + // Set duration float durationSeconds(1.0f); animationAlpha.SetDuration(durationSeconds); animationBeta.SetDuration(durationSeconds); @@ -14004,7 +14004,7 @@ int UtcDaliAnimationMultipleProgressSignalsP2(void) Animation animationAlpha = Animation::New(0.0f); Animation animationBeta = Animation::New(0.0f); - //Set duration + // Set duration const float durationSeconds(1.0f); animationAlpha.SetDuration(durationSeconds); animationBeta.SetDuration(durationSeconds); @@ -14119,7 +14119,7 @@ int UtcDaliAnimationProgressSignalWithPlayAfterP(void) Animation animationAlpha = Animation::New(0.0f); Animation animationBeta = Animation::New(0.0f); - //Set duration + // Set duration float durationSeconds(1.0f); float delaySeconds(0.5f); animationAlpha.SetDuration(durationSeconds); @@ -14229,7 +14229,7 @@ int UtcDaliAnimationProgressCallbackWithLoopingP(void) // Build the animation Animation animation = Animation::New(0.0f); - //Set duration + // Set duration const float durationSeconds(1.0f); animation.SetDuration(durationSeconds); @@ -14296,7 +14296,7 @@ int UtcDaliAnimationProgressCallbackWithLoopingP(void) tet_infoline("Animation at 100%"); application.SendNotification(); - //Nothing check at 100% progress. cause It can be both 100% and 0%. + // Nothing check at 100% progress. cause It can be both 100% and 0%. application.SendNotification(); } application.Render(10u); @@ -14319,7 +14319,7 @@ int UtcDaliAnimationProgressCallbackWithLoopingP2(void) // Build the animation Animation animation = Animation::New(0.0f); - //Set duration + // Set duration const float durationSeconds(1.0f); animation.SetDuration(durationSeconds); @@ -14383,7 +14383,7 @@ int UtcDaliAnimationProgressCallbackWithLoopingP2(void) tet_infoline("Animation at 100%"); application.SendNotification(); - //Nothing check at 100% progress. cause It can be both 100% and 0%. + // Nothing check at 100% progress. cause It can be both 100% and 0%. finishCheck.CheckSignalNotReceived(); application.SendNotification(); } @@ -14412,11 +14412,11 @@ int UtcDaliAnimationProgressCallbackNegativeSpeed(void) // Build the animation Animation animation = Animation::New(0.0f); - //Set duration + // Set duration const float durationSeconds(1.0f); animation.SetDuration(durationSeconds); - //Set speed negative + // Set speed negative animation.SetSpeedFactor(-1.0f); // Set Looping Unlmited @@ -14482,7 +14482,7 @@ int UtcDaliAnimationProgressCallbackNegativeSpeed(void) tet_infoline("Animation at 100%"); application.SendNotification(); - //Nothing check at 100% progress. cause It can be both 100% and 0%. + // Nothing check at 100% progress. cause It can be both 100% and 0%. finishCheck.CheckSignalNotReceived(); application.SendNotification(); } @@ -14537,7 +14537,7 @@ int UtcDaliAnimationProgressCallbackNegativeSpeed(void) tet_infoline("Animation at 100%"); application.SendNotification(); - //Nothing check at 100% progress. cause It can be both 100% and 0%. + // Nothing check at 100% progress. cause It can be both 100% and 0%. application.SendNotification(); } application.Render(10u); @@ -14560,7 +14560,7 @@ int UtcDaliAnimationProgressCallbackInvalidSignalN(void) // Build the animation Animation animation = Animation::New(0.0f); - //Set duration + // Set duration const float durationSeconds(1.0f); animation.SetDuration(durationSeconds); @@ -14635,7 +14635,7 @@ int UtcDaliAnimationProgressCallbackLongDurationP(void) // Build the animation Animation animation = Animation::New(0.0f); - //Set duration + // Set duration float durationSeconds(5.0f); animation.SetDuration(durationSeconds); @@ -17454,5 +17454,240 @@ int UtcDaliAnimationStressTest(void) // Restart the animation, with a different duration finishCheck.Reset(); + END_TEST; +} + +int UtcDaliAnimationSpringFinished(void) +{ + TestApplication application; + tet_infoline("UtcDaliAnimation spring test finished in expected time."); + + Actor actor = Actor::New(); + actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + actor.SetProperty(Dali::Actor::Property::SIZE, Vector2(100, 100)); + actor.SetProperty(Dali::Actor::Property::POSITION_X, 0); + application.GetScene().Add(actor); + + SpringData springData{100.0f, 10.0f, 1.0f}; + float convergeDuration = SpringData::GetDuration(springData); + DALI_TEST_CHECK(convergeDuration < 2.0f); + tet_printf("convergeDuration : %f\n", convergeDuration); + + Animation animation = Animation::New(convergeDuration); + animation.AnimateTo(Property(actor, Dali::Actor::Property::POSITION_X), 100.0f, AlphaFunction(springData)); + animation.Play(); + + application.SendNotification(); + application.Render(400); + tet_printf("400ms : %f\n", actor.GetCurrentProperty(Actor::Property::POSITION_X)); + DALI_TEST_CHECK(actor.GetCurrentProperty(Actor::Property::POSITION_X) > 100.0f); + + application.SendNotification(); + application.Render(400); + tet_printf("800ms : %f\n", actor.GetCurrentProperty(Actor::Property::POSITION_X)); + DALI_TEST_CHECK(actor.GetCurrentProperty(Actor::Property::POSITION_X) < 100.0f); + + application.SendNotification(); + application.Render(600); + tet_printf("1400ms : %f\n", actor.GetCurrentProperty(Actor::Property::POSITION_X)); + DALI_TEST_EQUALS(100.0f, actor.GetCurrentProperty(Actor::Property::POSITION_X), 0.01f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliAnimationSpringSetGet(void) +{ + TestApplication application; + tet_infoline("UtcDaliAnimation spring test finished in expected time."); + + AlphaFunction springGentle(AlphaFunction::SpringType::GENTLE); + DALI_TEST_EQUALS(100.0f, springGentle.GetSpringData().stiffness, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(15.0f, springGentle.GetSpringData().damping, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(1.0f, springGentle.GetSpringData().mass, 0.01f, TEST_LOCATION); + + AlphaFunction springQuick(AlphaFunction::SpringType::QUICK); + DALI_TEST_EQUALS(300.0f, springQuick.GetSpringData().stiffness, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(20.0f, springQuick.GetSpringData().damping, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(1.0f, springQuick.GetSpringData().mass, 0.01f, TEST_LOCATION); + + AlphaFunction springBouncy(AlphaFunction::SpringType::BOUNCY); + DALI_TEST_EQUALS(600.0f, springBouncy.GetSpringData().stiffness, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(15.0f, springBouncy.GetSpringData().damping, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(1.0f, springBouncy.GetSpringData().mass, 0.01f, TEST_LOCATION); + + AlphaFunction springSlow(AlphaFunction::SpringType::SLOW); + DALI_TEST_EQUALS(94.0f, springSlow.GetSpringData().stiffness, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(18.5f, springSlow.GetSpringData().damping, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(1.0f, springSlow.GetSpringData().mass, 0.01f, TEST_LOCATION); + + AlphaFunction springCustom1({-10.0f, -9.0f, -8.0f}); + DALI_TEST_EQUALS(0.1f, springCustom1.GetSpringData().stiffness, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(0.1f, springCustom1.GetSpringData().damping, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(0.1f, springCustom1.GetSpringData().mass, 0.01f, TEST_LOCATION); + + AlphaFunction springCustom2({10.0f, 9.0f, 8.0f}); + DALI_TEST_EQUALS(10.0f, springCustom2.GetSpringData().stiffness, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(9.0f, springCustom2.GetSpringData().damping, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(8.0f, springCustom2.GetSpringData().mass, 0.01f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliAnimationSpringPreset(void) +{ + TestApplication application; + tet_infoline("UtcDaliAnimation spring test, preset case"); + + Actor actor = Actor::New(); + actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + actor.SetProperty(Dali::Actor::Property::SIZE, Vector2(100, 100)); + actor.SetProperty(Dali::Actor::Property::POSITION_X, 0); + application.GetScene().Add(actor); + + Animation animation = Animation::New(1.0f); + animation.AnimateTo(Property(actor, Dali::Actor::Property::POSITION_X), 100.0f, AlphaFunction(Dali::AlphaFunction::SpringType::SLOW)); + animation.Play(); + + application.SendNotification(); + application.Render(200); + float position200 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position200 > 0.0f); + + application.SendNotification(); + application.Render(200); + float position400 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position200 < position400); + + application.SendNotification(); + application.Render(200); + float position600 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position400 < position600); + + application.SendNotification(); + application.Render(200); + float position800 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position600 < position800); + + application.SendNotification(); + application.Render(200); + float position1000 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position800 < position1000); + tet_infoline("800 : %f, 1000 : %f position800, position1000"); + DALI_TEST_EQUALS(100.0f, position1000, 0.01f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliAnimationSpringOverdamped(void) +{ + TestApplication application; + tet_infoline("UtcDaliAnimation spring test, overdamped case"); + + Actor actor = Actor::New(); + actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + actor.SetProperty(Dali::Actor::Property::SIZE, Vector2(100, 100)); + actor.SetProperty(Dali::Actor::Property::POSITION_X, 0); + application.GetScene().Add(actor); + + SpringData springData{80.0f, 20.0f, 1.0f}; + float convergeDuration = SpringData::GetDuration(springData); + DALI_TEST_CHECK(convergeDuration > 0.0f); + + Animation animation = Animation::New(convergeDuration); + animation.AnimateTo(Property(actor, Dali::Actor::Property::POSITION_X), 100.0f, AlphaFunction(springData)); + animation.Play(); + + uint32_t timestep = static_cast(convergeDuration * 0.2f * 1000.0f); + + application.SendNotification(); + application.Render(timestep); + float position200 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position200 > 0.0f); + + application.SendNotification(); + application.Render(timestep); + float position400 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position200 < position400); + + application.SendNotification(); + application.Render(timestep); + float position600 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position400 < position600); + + application.SendNotification(); + application.Render(timestep); + float position800 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position600 < position800); + + application.SendNotification(); + application.Render(timestep); + float position1000 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position800 < position1000); + DALI_TEST_EQUALS(99.8f, position1000, 0.1f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliAnimationSpringUnderdamped(void) +{ + TestApplication application; + tet_infoline("UtcDaliAnimation spring test, overdamped case"); + + Actor actor = Actor::New(); + actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + actor.SetProperty(Dali::Actor::Property::SIZE, Vector2(100, 100)); + actor.SetProperty(Dali::Actor::Property::POSITION_X, 0); + application.GetScene().Add(actor); + + SpringData springData{600.0f, 15.0f, 1.0f}; + float convergeDuration = SpringData::GetDuration(springData); + DALI_TEST_CHECK(convergeDuration > 0.0f); + tet_printf("convergeDuration : %f\n", convergeDuration); + + Animation animation = Animation::New(convergeDuration); + animation.AnimateTo(Property(actor, Dali::Actor::Property::POSITION_X), 100.0f, AlphaFunction(springData)); + animation.Play(); + + application.SendNotification(); + application.Render(50); + float position50 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position50 > 0.0f); + DALI_TEST_CHECK(position50 < 100.0f); + + application.SendNotification(); + application.Render(100); // 150ms + float position150 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position150 > 100.0f); + + application.SendNotification(); + application.Render(150); // 300ms + float position300 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position300 < 100.0f); + + application.SendNotification(); + application.Render(150); // 450ms + float position450 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position450 > 100.0f); + + application.SendNotification(); + application.Render(100); // 550ms + float position550 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position550 < 100.0f); + + application.SendNotification(); + application.Render(150); // 700ms + float position700 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position700 > 100.0f); + + application.SendNotification(); + application.Render(150); // 850ms + float position850 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_CHECK(position850 < 100.0f); + + application.SendNotification(); + application.Render(150); // 1000ms + float position1000 = actor.GetCurrentProperty(Actor::Property::POSITION_X); + DALI_TEST_EQUALS(position1000, 100.0f, 0.01f, TEST_LOCATION); + END_TEST; } \ No newline at end of file diff --git a/dali/internal/update/animation/scene-graph-animator.h b/dali/internal/update/animation/scene-graph-animator.h index f5f8ad5de..8d65ddb37 100644 --- a/dali/internal/update/animation/scene-graph-animator.h +++ b/dali/internal/update/animation/scene-graph-animator.h @@ -332,6 +332,40 @@ public: result = customFunction(progress); } } + else if(alphaFunctionMode == AlphaFunction::SPRING || alphaFunctionMode == AlphaFunction::CUSTOM_SPRING) + { + double stiffness = mAlphaFunction.GetSpringData().stiffness; + double damping = mAlphaFunction.GetSpringData().damping; + double mass = mAlphaFunction.GetSpringData().mass; + + double omega0 = std::sqrt(stiffness / mass); + double zeta = damping / (2.0f * std::sqrt(stiffness * mass)); + + double springProgress = (mAlphaFunction.GetMode() == Dali::AlphaFunction::Mode::SPRING) ? progress : progress * mDurationSeconds; + + if(zeta < 1.0f) + { + double omegaTemp = std::sqrt(1.0f - zeta * zeta); + double omegaD = omega0 * omegaTemp; + double envelope = std::exp(-zeta * omega0 * springProgress); + result = static_cast(1.0 - envelope * (std::cos(omegaD * springProgress) + (zeta / omegaTemp) * std::sin(omegaD * springProgress))); + } + else + { + double sqrtTerm = std::sqrt(std::max(zeta * zeta - 1.0, 1e-6)); + double r1 = -omega0 * (zeta - sqrtTerm); + double r2 = -omega0 * (zeta + sqrtTerm); + double A = r2 / (r2 - r1); + double B = 1.0 - A; + result = 1.0 - A * std::exp(r1 * springProgress) - B * std::exp(r2 * springProgress); + } + + // Heuristic. if the progress variable becomes 1.0 and the result is almost 1.0 too, return 1.0f. + if((1.0f - progress) < Math::MACHINE_EPSILON_1 && std::abs(1.0 - result) < static_cast(Math::MACHINE_EPSILON_10000)) + { + result = 1.0f; + } + } else { // If progress is very close to 0 or very close to 1 we don't need to evaluate the curve as the result will diff --git a/dali/public-api/animation/alpha-function.cpp b/dali/public-api/animation/alpha-function.cpp index 990cd8f1c..048fabbe4 100644 --- a/dali/public-api/animation/alpha-function.cpp +++ b/dali/public-api/animation/alpha-function.cpp @@ -20,17 +20,50 @@ // INTERNAL INCLUDES +namespace +{ + +Dali::SpringData GetSpringDefaultData(Dali::AlphaFunction::SpringType springType) +{ + switch(springType) + { + case Dali::AlphaFunction::SpringType::GENTLE: + { + return {100.0f, 15.0f, 1.0f}; + } + case Dali::AlphaFunction::SpringType::QUICK: + { + return {300.0f, 20.0f, 1.0f}; + } + case Dali::AlphaFunction::SpringType::BOUNCY: + { + return {600.0f, 15.0f, 1.0f}; + } + case Dali::AlphaFunction::SpringType::SLOW: + { + return {94.0f, 18.5f, 1.0f}; + } + default: + { + return {100.0f, 15.0f, 1.0f}; + } + } +} +} + namespace Dali { AlphaFunction::AlphaFunction() : mMode(BUILTIN_FUNCTION), - mBuiltin(DEFAULT) + mBuiltin(DEFAULT), + mSpringData({100.0f, 15.0f, 1.0f}) { } AlphaFunction::AlphaFunction(BuiltinFunction function) : mMode(BUILTIN_FUNCTION), - mBuiltin(function) + mBuiltin(function), + mSpringData({100.0f, 15.0f, 1.0f}) { } @@ -52,6 +85,20 @@ AlphaFunction::AlphaFunction(const Vector2& controlPoint0, const Vector2& contro { } +AlphaFunction::AlphaFunction(SpringType springType) +: mMode(SPRING), + mBuiltin(DEFAULT), + mSpringData(GetSpringDefaultData(springType)) +{ +} + +AlphaFunction::AlphaFunction(Dali::SpringData springData) +: mMode(CUSTOM_SPRING), + mBuiltin(DEFAULT), + mSpringData(springData) +{ +} + Vector4 AlphaFunction::GetBezierControlPoints() const { return (mMode == BEZIER) ? mBezierControlPoints : Vector4::ZERO; @@ -72,4 +119,9 @@ AlphaFunction::Mode AlphaFunction::GetMode() const return mMode; } +const Dali::SpringData& AlphaFunction::GetSpringData() const +{ + return mSpringData; +} + } // namespace Dali diff --git a/dali/public-api/animation/alpha-function.h b/dali/public-api/animation/alpha-function.h index 3e006dce2..61a8a6c84 100644 --- a/dali/public-api/animation/alpha-function.h +++ b/dali/public-api/animation/alpha-function.h @@ -22,6 +22,7 @@ #include // uint8_t // INTERNAL INCLUDES +#include #include #include #include @@ -79,7 +80,41 @@ public: { BUILTIN_FUNCTION, ///< The user has specified a built-in function @SINCE_1_0.0 CUSTOM_FUNCTION, ///< The user has provided a custom function @SINCE_1_0.0 - BEZIER ///< The user has provided the control points of a bezier curve @SINCE_1_0.0 + BEZIER, ///< The user has provided the control points of a bezier curve @SINCE_1_0.0 + SPRING, ///< The user has provided the spring type @SINCE_2_4.17 + CUSTOM_SPRING ///< The user has provided the spring data @SINCE_2_4.17 + }; + + /** + * @brief Enumeration for predefined spring animation types. + * This presets are based on typical spring behavior tuned for different motion effects. + * @SINCE_2_4.17 + */ + enum SpringType : uint8_t + { + /** + * @brief Gentle spring. slower and smoother motion with less oscillation. + * @SINCE_2_4.17 + */ + GENTLE, + + /** + * @brief Quick spring, Fast settling animation with minimal overshoot. + * @SINCE_2_4.17 + */ + QUICK, + + /** + * @brief Bouncy spring. Highly elastic and oscillatory animation. + * @SINCE_2_4.17 + */ + BOUNCY, + + /** + * @brief Slow spring. Smooth and relaxed motion with longer settling + * @SINCE_2_4.17 + */ + SLOW }; /** @@ -120,6 +155,26 @@ public: */ AlphaFunction(const Dali::Vector2& controlPoint0, const Dali::Vector2& controlPoint1); + /** + * @brief Constructor for spring-based AlphaFunction using a predefined SpringType. + * + * @SINCE_2_4.17 + * @param[in] springType The spring preset type to use (e.g., GENTLE, QUICK, etc.). + */ + AlphaFunction(SpringType springType); + + /** + * @brief Constructor for spring-based AlphaFunction using custom spring parameters. + * + * This allows creating a spring easing function with fully customizable physics behavior. + * @SINCE_2_4.17 + * @param[in] springData The custom spring configuration (stiffness, damping, mass) + * @note Since this AlphaFunction follows a physics-based motion model, it does not guarantee + * that the value will exactly reach 1 at the end of the animation duration. + * To ensure the animation ends at 1, you may need to adjust the duration according to the spring configuration. + */ + AlphaFunction(Dali::SpringData springData); + /** * @brief Returns the control points of the alpha function. * @SINCE_1_0.0 @@ -150,14 +205,22 @@ public: */ Mode GetMode() const; + /** + * @brief Returns SpringData of spring animation + * @SINCE_2_4.17 + * @return The SpringData of spring animation + */ + const Dali::SpringData& GetSpringData() const; + private: - Mode mMode; //< Enum indicating the functioning mode of the AlphaFunction - BuiltinFunction mBuiltin; //< Enum indicating the built-in alpha function + Mode mMode; //< Enum indicating the functioning mode of the AlphaFunction + BuiltinFunction mBuiltin; //< Enum indicating the built-in alpha function union { Vector4 mBezierControlPoints; //< Control points for the bezier alpha function AlphaFunctionPrototype mCustom; //< Pointer to an alpha function + Dali::SpringData mSpringData; }; }; diff --git a/dali/public-api/animation/spring-data.cpp b/dali/public-api/animation/spring-data.cpp new file mode 100644 index 000000000..caf92d615 --- /dev/null +++ b/dali/public-api/animation/spring-data.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include + +// EXTERNAL INCLUDES +#include +#include + +// INTERNAL INCLUDES +#include + +namespace +{ +static constexpr float MIN_STIFFNESS = 0.1f; +static constexpr float MIN_DAMPING = 0.1f; +static constexpr float MIN_MASS = 0.1f; +static constexpr double TIME_STEP = 1.0 / 60.0; +static constexpr double EPSILON = 0.001; +static constexpr double MINIMUM_DIFFERENCE = static_cast(Dali::Math::MACHINE_EPSILON_10); +} // namespace + +namespace Dali +{ +SpringData::SpringData(float stiffness, float damping, float mass) +: stiffness(std::max(stiffness, MIN_STIFFNESS)), + damping(std::max(damping, MIN_DAMPING)), + mass(std::max(mass, MIN_MASS)) +{ +} + +float SpringData::GetDuration(const SpringData& springData) +{ + if(springData.stiffness < MIN_STIFFNESS || springData.damping < MIN_DAMPING || springData.mass < MIN_MASS) + { + return 0.0f; + } + + double stiffness = static_cast(springData.stiffness); + double damping = static_cast(springData.damping); + double mass = static_cast(springData.mass); + + double omega0 = std::sqrt(stiffness / mass); + double zeta = damping / (2.0 * std::sqrt(stiffness * mass)); + double time = 0.0; + + if(zeta < 1.0) + { + time = -std::log(EPSILON) / (zeta * omega0); + } + else + { + double sqrtTerm = std::sqrt(std::max(zeta * zeta - 1.0f, 1e-6)); + double r1 = -omega0 * (zeta - sqrtTerm); // dominant(Slower) root + time = std::log(1.0 / EPSILON) / std::abs(r1); + } + + return static_cast(time); +} + +} // namespace Dali diff --git a/dali/public-api/animation/spring-data.h b/dali/public-api/animation/spring-data.h new file mode 100644 index 000000000..152ce79d4 --- /dev/null +++ b/dali/public-api/animation/spring-data.h @@ -0,0 +1,67 @@ +#ifndef DALI_SPRING_DATA_H +#define DALI_SPRING_DATA_H + +/* + * Copyright (c) 2025 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// INTERNAL INCLUDES +#include + +namespace Dali +{ +/** + * @addtogroup dali_core_animation + * @{ + */ + +/** + * @brief Structure for custom spring parameters. + * This allows defining a custom spring-based easing curve using physics parameters. + * @SINCE_2_4.17 + */ +struct DALI_CORE_API SpringData +{ + /** + * @brief Creates a spring data + * + * @SINCE_2_4.17 + * @param[in] stiffness Spring stiffness(Hooke's constant). Higher values make the spring snap back faster. Minimum value is 0.1. + * @param[in] damping Damping coefficient. Controls oscillation and settling. Minimum value is 0.1. + * @param[in] mass Mass of the object. Affects inertia and the duration of the motion. Minimum value is 0.1. + */ + SpringData(float stiffness, float damping, float mass); + + float stiffness; ///< Spring stiffness(Hooke's constant). Higher values make the spring snap back faster. Minimum value is 0.1. + float damping; ///< Damping coefficient. Controls oscillation and settling. Minimum value is 0.1. + float mass; ///< Mass of the object. Affects inertia and the duration of the motion. Minimum value is 0.1. + + /** + * @brief Returns the time in seconds it takes for a Spring Animation to converge based on the input SpringData. + * The maximum value for the returned duration is 100 secons. + * Since this value is calculated in an incremental manner, it may take longer if used frequently. + * + * @SINCE_2_4.17 + */ + static float GetDuration(const SpringData& springData); +}; + +/** + * @} + */ +} // namespace Dali + +#endif // DALI_SPRING_DATA_H diff --git a/dali/public-api/dali-core.h b/dali/public-api/dali-core.h index 3d8f2402d..23a0c2315 100644 --- a/dali/public-api/dali-core.h +++ b/dali/public-api/dali-core.h @@ -37,6 +37,7 @@ #include #include #include +#include #include #include diff --git a/dali/public-api/file.list b/dali/public-api/file.list index fd87dc50f..ab79513cf 100644 --- a/dali/public-api/file.list +++ b/dali/public-api/file.list @@ -16,6 +16,7 @@ SET( public_api_src_files ${public_api_src_dir}/animation/key-frames.cpp ${public_api_src_dir}/animation/linear-constrainer.cpp ${public_api_src_dir}/animation/path.cpp + ${public_api_src_dir}/animation/spring-data.cpp ${public_api_src_dir}/animation/time-period.cpp ${public_api_src_dir}/common/dali-common.cpp ${public_api_src_dir}/common/dali-vector.cpp @@ -119,6 +120,7 @@ SET( public_api_core_animation_header_files ${public_api_src_dir}/animation/key-frames.h ${public_api_src_dir}/animation/linear-constrainer.h ${public_api_src_dir}/animation/path.h + ${public_api_src_dir}/animation/spring-data.h ${public_api_src_dir}/animation/time-period.h ) -- 2.34.1