| #include "rive/artboard.hpp" | |
| #include "rive/animation/keyed_object.hpp" | |
| #include "rive/animation/linear_animation.hpp" | |
| #include "rive/animation/linear_animation_instance.hpp" | |
| #include "rive/animation/keyed_callback_reporter.hpp" | |
| #include "utils/no_op_factory.hpp" | |
| #include "rive_file_reader.hpp" | |
| #include "rive_testing.hpp" | |
| #include "rive/shapes/shape.hpp" | |
| #include <catch.hpp> | |
| #include <cstdio> | |
| #include <memory> | |
| TEST_CASE( | |
| "LinearAnimation with positive speed have normal start and end seconds", | |
| "[animation]") | |
| { | |
| rive::NoOpFactory emptyFactory; | |
| // For each of these tests, we cons up a dummy artboard/instance | |
| // just to make the animations happy. | |
| rive::Artboard ab(&emptyFactory); | |
| auto abi = ab.instance(); | |
| rive::LinearAnimation* linearAnimation = new rive::LinearAnimation(); | |
| // duration in seconds is 5 | |
| linearAnimation->duration(10); | |
| linearAnimation->fps(2); | |
| linearAnimation->speed(1); | |
| REQUIRE(linearAnimation->startSeconds() == 0.0); | |
| REQUIRE(linearAnimation->endSeconds() == 5.0); | |
| REQUIRE(linearAnimation->startTime() == 0.0); | |
| REQUIRE(linearAnimation->endTime() == 5.0); | |
| REQUIRE(linearAnimation->durationSeconds() == 5.0); | |
| delete linearAnimation; | |
| } | |
| TEST_CASE( | |
| "LinearAnimation with negative speed have reversed start and end seconds", | |
| "[animation]") | |
| { | |
| rive::NoOpFactory emptyFactory; | |
| // For each of these tests, we cons up a dummy artboard/instance | |
| // just to make the animations happy. | |
| rive::Artboard ab(&emptyFactory); | |
| auto abi = ab.instance(); | |
| rive::LinearAnimation* linearAnimation = new rive::LinearAnimation(); | |
| // duration in seconds is 5 | |
| linearAnimation->duration(10); | |
| linearAnimation->fps(2); | |
| linearAnimation->speed(-1); | |
| REQUIRE(linearAnimation->startSeconds() == 0.0); | |
| REQUIRE(linearAnimation->endSeconds() == 5.0); | |
| REQUIRE(linearAnimation->startTime() == 5.0); | |
| REQUIRE(linearAnimation->endTime() == 0.0); | |
| REQUIRE(linearAnimation->durationSeconds() == 5.0); | |
| delete linearAnimation; | |
| } | |
| TEST_CASE("quantize goes to whole frames", "[animation]") | |
| { | |
| auto file = ReadRiveFile("assets/quantize_test.riv"); | |
| auto artboard = file->artboard(); | |
| auto animation = artboard->animation(0); | |
| REQUIRE(animation->quantize()); | |
| auto shapes = artboard->find<rive::Shape>(); | |
| REQUIRE(shapes.size() == 1); | |
| auto ellipse = shapes[0]; | |
| animation->apply(artboard, 0.0f); | |
| REQUIRE(ellipse->x() == 0.0f); | |
| // Animation has 5 frames and lasts 1 second. So going to 0.5 seconds should | |
| // show frame 2.5 which should floor to frame 2's value of 160 on x. Without | |
| // quantize this would be 200. | |
| animation->apply(artboard, 0.5f); | |
| REQUIRE(ellipse->x() == 160.0f); | |
| // Make sure our assumption with quantize off is true. | |
| animation->quantize(false); | |
| animation->apply(artboard, 0.5f); | |
| REQUIRE(ellipse->x() == 200.0f); | |
| } | |
| TEST_CASE("LinearAnimation reports when to keep going correctly", "[animation]") | |
| { | |
| rive::NoOpFactory emptyFactory; | |
| // For each of these tests, we cons up a dummy artboard/instance | |
| // just to make the animations happy. | |
| rive::Artboard ab(&emptyFactory); | |
| auto abi = ab.instance(); | |
| rive::LinearAnimation* linearAnimation = new rive::LinearAnimation(); | |
| linearAnimation->duration(60); | |
| linearAnimation->fps(60); | |
| linearAnimation->speed(1); | |
| linearAnimation->enableWorkArea(true); | |
| linearAnimation->workStart(30); | |
| linearAnimation->workEnd(42); | |
| auto animationInstance = | |
| rive::LinearAnimationInstance(linearAnimation, abi.get()); | |
| REQUIRE(!animationInstance.advance(0.0f)); | |
| REQUIRE(animationInstance.time() == 0.5f); | |
| REQUIRE(animationInstance.advance(0.1f)); | |
| REQUIRE(animationInstance.time() == 0.6f); | |
| REQUIRE(!animationInstance.advance(0.2f)); | |
| REQUIRE(animationInstance.time() == 0.7f); | |
| delete linearAnimation; | |
| } | |
| class MockKeyedObject : public rive::KeyedObject | |
| { | |
| public: | |
| MockKeyedObject(rive::StatusCode status, int* callCount) : | |
| m_status(status), m_callCount(callCount) | |
| {} | |
| rive::StatusCode onAddedDirty(rive::CoreContext*) override | |
| { | |
| (*m_callCount)++; | |
| return m_status; | |
| } | |
| private: | |
| rive::StatusCode m_status; | |
| int* m_callCount; | |
| }; | |
| TEST_CASE("LinearAnimation keeps initializing after missing keyed object", | |
| "[animation]") | |
| { | |
| rive::LinearAnimation animation; | |
| int missingObjectCalls = 0; | |
| int validObjectCalls = 0; | |
| animation.addKeyedObject(std::unique_ptr<rive::KeyedObject>( | |
| new MockKeyedObject(rive::StatusCode::MissingObject, | |
| &missingObjectCalls))); | |
| animation.addKeyedObject(std::unique_ptr<rive::KeyedObject>( | |
| new MockKeyedObject(rive::StatusCode::Ok, &validObjectCalls))); | |
| REQUIRE(animation.onAddedDirty(nullptr) == rive::StatusCode::Ok); | |
| REQUIRE(missingObjectCalls == 1); | |
| REQUIRE(validObjectCalls == 1); | |
| REQUIRE(animation.numKeyedObjects() == 1); | |
| } | |
| class TestReporter : public rive::KeyedCallbackReporter | |
| { | |
| private: | |
| std::vector<uint32_t> m_reportedObjects; | |
| public: | |
| void reportKeyedCallback(uint32_t objectId, | |
| uint32_t propertyKey, | |
| float elapsedSeconds) override | |
| { | |
| m_reportedObjects.push_back(objectId); | |
| } | |
| size_t count() { return m_reportedObjects.size(); } | |
| }; | |
| TEST_CASE("Looping timeline events load correctly and report", "[events]") | |
| { | |
| auto file = ReadRiveFile("assets/looping_timeline_events.riv"); | |
| auto artboard = file->artboard()->instance(); | |
| REQUIRE(artboard != nullptr); | |
| REQUIRE(artboard->animationCount() == 1); | |
| auto animationInstance = artboard->animationAt(0); | |
| REQUIRE(animationInstance != nullptr); | |
| TestReporter reporter; | |
| // Report (at frame 0) fires from 0-0.1f | |
| animationInstance->advance(0.1f, &reporter); | |
| REQUIRE(animationInstance->time() == 0.1f); | |
| REQUIRE(reporter.count() == 1); | |
| // Report (at frame 25) fires | |
| animationInstance->advance(0.32f, &reporter); | |
| REQUIRE(animationInstance->time() == 0.42f); | |
| REQUIRE(reporter.count() == 2); | |
| // No report from .42 to .72 seconds. | |
| animationInstance->advance(0.3f, &reporter); | |
| REQUIRE(animationInstance->time() == 0.72f); | |
| REQUIRE(reporter.count() == 2); | |
| // Report at 1s fires from .72 to 1.0 seconds. | |
| animationInstance->advance(0.28f, &reporter); | |
| REQUIRE(animationInstance->time() == 0.0f); | |
| REQUIRE(reporter.count() == 3); | |
| // All 3 events fire (and first one again too). | |
| animationInstance->advance(1.01f, &reporter); | |
| REQUIRE(animationInstance->time() == Approx(0.01f)); | |
| REQUIRE(reporter.count() == 7); | |
| } |