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());
+}