Hit tests working from state machine
diff --git a/include/rive/animation/state_machine_input_instance.hpp b/include/rive/animation/state_machine_input_instance.hpp index 06af9b9..8c5a786 100644 --- a/include/rive/animation/state_machine_input_instance.hpp +++ b/include/rive/animation/state_machine_input_instance.hpp
@@ -74,6 +74,10 @@ public: void fire(); + +#ifdef TESTING + bool didFire() { return m_Fired; } +#endif }; } // namespace rive #endif \ No newline at end of file
diff --git a/include/rive/animation/state_machine_instance.hpp b/include/rive/animation/state_machine_instance.hpp index 56109e0..b9dcbae 100644 --- a/include/rive/animation/state_machine_instance.hpp +++ b/include/rive/animation/state_machine_instance.hpp
@@ -37,7 +37,8 @@ void markNeedsAdvance(); std::vector<HitShape*> m_HitShapes; - void processEvent(Vec2D position, EventType hitEvent); + /// Provide a hitEvent if you want to process a down or an up for the pointer position too. + void processEvent(Vec2D position, EventType hitEvent = EventType::updateHover); public: StateMachineInstance(const StateMachine* machine, Artboard* artboard); @@ -71,6 +72,10 @@ // previously called advance. If the index of out of range, it returns // the empty string. const LayerState* stateChangedByIndex(size_t index) const; + + void pointerMove(const Vec2D& position); + void pointerDown(const Vec2D& position); + void pointerUp(const Vec2D& position); }; } // namespace rive #endif
diff --git a/include/rive/artboard.hpp b/include/rive/artboard.hpp index ef0f192..0ea013a 100644 --- a/include/rive/artboard.hpp +++ b/include/rive/artboard.hpp
@@ -65,6 +65,9 @@ StatusCode initialize(); Core* resolve(uint32_t id) const override; + + /// Find the index of a component in the artboard the object in the artboard. The artboard + /// itself lives at index 0 so we use that as a flag for not found. uint32_t idOf(Core* object) const; // EXPERIMENTAL -- for internal testing only for now. @@ -87,7 +90,7 @@ // Returns true iff calling popMessage() will return true. bool hasMessages() const; - + // If there are any queued messages... // copies the first message into msg parameter // removes that message from the queue
diff --git a/include/rive/event_type.hpp b/include/rive/event_type.hpp index d50df00..deb1cea 100644 --- a/include/rive/event_type.hpp +++ b/include/rive/event_type.hpp
@@ -1,7 +1,8 @@ #ifndef _RIVE_EVENT_TYPE_HPP_ #define _RIVE_EVENT_TYPE_HPP_ namespace rive { - enum class EventType : unsigned int { + enum class EventType : int { + updateHover = -1, enter = 0, exit = 1, down = 2,
diff --git a/include/rive/shapes/shape.hpp b/include/rive/shapes/shape.hpp index f8933bb..a4e6f37 100644 --- a/include/rive/shapes/shape.hpp +++ b/include/rive/shapes/shape.hpp
@@ -10,6 +10,7 @@ namespace rive { class Path; class PathComposer; + class HitTester; class Shape : public ShapeBase, public ShapePaintContainer { private: PathComposer m_PathComposer; @@ -28,6 +29,7 @@ void update(ComponentDirt value) override; void draw(Renderer* renderer) override; Core* hitTest(HitInfo*, const Mat2D&) override; + bool hitTest(const IAABB& area) const; PathComposer* pathComposer() const { return (PathComposer*)&m_PathComposer; }
diff --git a/src/animation/state_machine_event.cpp b/src/animation/state_machine_event.cpp index fd367ed..d6a3bc4 100644 --- a/src/animation/state_machine_event.cpp +++ b/src/animation/state_machine_event.cpp
@@ -31,19 +31,24 @@ StatusCode StateMachineEvent::onAddedClean(CoreContext* context) { auto artboard = static_cast<Artboard*>(context); + auto target = artboard->resolve(targetId()); + for (auto core : artboard->objects()) { if (core == nullptr) { continue; } - auto target = artboard->resolve(targetId()); // Iterate artboard to find Shapes that are parented to the target if (core->is<Shape>()) { auto shape = core->as<Shape>(); + for (ContainerComponent* component = shape; component != nullptr; component = component->parent()) { if (component == target) { - m_HitShapesIds.push_back(artboard->idOf(shape)); + auto index = artboard->idOf(shape); + if (index != 0) { + m_HitShapesIds.push_back(artboard->idOf(shape)); + } break; } }
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp index bbc923f..79967e8 100644 --- a/src/animation/state_machine_instance.cpp +++ b/src/animation/state_machine_instance.cpp
@@ -16,6 +16,7 @@ #include "rive/animation/state_machine_event.hpp" #include "rive/shapes/shape.hpp" #include "rive/math/aabb.hpp" +#include "rive/math/hit_test.hpp" #include <unordered_map> using namespace rive; @@ -238,15 +239,16 @@ position.x() + hitRadius, position.y() + hitRadius) .round(); + for (auto hitShape : m_HitShapes) { // TODO: quick reject. - // TODO: hit test - bool isOver = false; + bool isOver = hitShape->shape()->hitTest(hitArea); bool hoverChange = hitShape->isHovered != isOver; hitShape->isHovered = isOver; + // iterate all events associated with this hit shape for (auto event : hitShape->events) { // Always update hover states regardless of which specific event type @@ -268,6 +270,16 @@ } } +void StateMachineInstance::pointerMove(const Vec2D& position) { + processEvent(position, EventType::updateHover); +} +void StateMachineInstance::pointerDown(const Vec2D& position) { + processEvent(position, EventType::down); +} +void StateMachineInstance::pointerUp(const Vec2D& position) { + processEvent(position, EventType::up); +} + StateMachineInstance::StateMachineInstance(const StateMachine* machine, Artboard* artboard) : m_Machine(machine), m_Artboard(artboard) { m_InputCount = machine->inputCount();
diff --git a/src/artboard.cpp b/src/artboard.cpp index 98c038e..36fe309 100644 --- a/src/artboard.cpp +++ b/src/artboard.cpp
@@ -286,7 +286,15 @@ return m_Objects[id]; } -uint32_t Artboard::idOf(Core* object) const { return 0; } +uint32_t Artboard::idOf(Core* object) const { + auto it = std::find(m_Objects.begin(), m_Objects.end(), object); + + if (it != m_Objects.end()) { + return it - m_Objects.begin(); + } else { + return 0; + } +} void Artboard::onComponentDirty(Component* component) { m_Dirt |= ComponentDirt::Components; @@ -576,7 +584,8 @@ assert(gButtonIsDown); gButtonIsDown = false; break; - default: break; + default: + break; } #if 0 @@ -602,13 +611,9 @@ #endif } -void Artboard::testing_only_enque_message(const Message& msg) { - m_MessageQueue.push(msg); -} +void Artboard::testing_only_enque_message(const Message& msg) { m_MessageQueue.push(msg); } -bool Artboard::hasMessages() const { - return !m_MessageQueue.empty(); -} +bool Artboard::hasMessages() const { return !m_MessageQueue.empty(); } bool Artboard::nextMessage(Message* msg) { if (m_MessageQueue.empty()) {
diff --git a/src/shapes/shape.cpp b/src/shapes/shape.cpp index b620f93..6851c73 100644 --- a/src/shapes/shape.cpp +++ b/src/shapes/shape.cpp
@@ -57,6 +57,16 @@ } } +bool Shape::hitTest(const IAABB& area) const { + HitTestCommandPath tester(area); + + for (auto path : m_Paths) { + tester.setXform(path->pathTransform()); + path->buildPath(tester); + } + return tester.wasHit(); +} + Core* Shape::hitTest(HitInfo* hinfo, const Mat2D& xform) { if (renderOpacity() == 0.0f) { return nullptr;
diff --git a/test/state_machine_event_test.cpp b/test/state_machine_event_test.cpp index b65b32a..5dbdffa 100644 --- a/test/state_machine_event_test.cpp +++ b/test/state_machine_event_test.cpp
@@ -59,3 +59,25 @@ REQUIRE(inputChange3 != nullptr); REQUIRE(inputChange3->inputId() == 2); } + +TEST_CASE("hit testing via a state machine works", "[file]") { + RiveFileReader reader("../../test/assets/bullet_man.riv"); + + auto artboard = reader.file()->artboard("Bullet Man"); + REQUIRE(artboard != nullptr); + REQUIRE(artboard->stateMachineCount() == 1); + + auto stateMachine = artboard->stateMachineInstance(0); + REQUIRE(stateMachine != nullptr); + // Advance artboard once so design time state is effectively in the transforms. + artboard->advance(0.0f); + stateMachine->advance(0.0f); + // Don't advance artboard again after applying state machine or our pointerDown will be off. The + // coordinates used in this test were from the design-time state. + + auto trigger = stateMachine->getTrigger("Light"); + REQUIRE(trigger != nullptr); + stateMachine->pointerDown(rive::Vec2D(71.0f, 263.0f)); + + REQUIRE(trigger->didFire()); +}