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 b995265..1c7ecf2 100644 --- a/include/rive/animation/state_machine_instance.hpp +++ b/include/rive/animation/state_machine_instance.hpp
@@ -35,7 +35,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, ArtboardInstance* instance); @@ -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 297cedb..2266d57 100644 --- a/include/rive/artboard.hpp +++ b/include/rive/artboard.hpp
@@ -67,6 +67,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.
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 1129c4f..5831ef2 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; @@ -241,15 +242,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 @@ -271,10 +273,21 @@ } } +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, ArtboardInstance* instance) : m_Machine(machine), m_ArtboardInstance(instance) { assert(instance->isInstance()); + m_InputCount = machine->inputCount(); m_InputInstances = new SMIInput*[m_InputCount]; for (size_t i = 0; i < m_InputCount; i++) {
diff --git a/src/artboard.cpp b/src/artboard.cpp index b52587f..f264595 100644 --- a/src/artboard.cpp +++ b/src/artboard.cpp
@@ -288,7 +288,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;
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()); +}