Skip to content

Commit

Permalink
use world bounds for coarse grained collision test
Browse files Browse the repository at this point in the history
fixes #7286
I'm creating this PR mostly to align the way both runtimes behave, although the performance gain is a plus probably.
Duo and others have reported that this difference of two pixels between runtime and editor are critical to how they build their projects.

Diffs=
405b8ef90 use world bounds for coarse grained collision test (#7287)

Co-authored-by: hernan <[email protected]>
  • Loading branch information
bodymovin and bodymovin committed May 17, 2024
1 parent e682a6f commit 2c45ce8
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3734d9bac071052acbcfdbec576ba9532bc84e3b
405b8ef907d29cf480422b94d4717fdcdfad0824
4 changes: 4 additions & 0 deletions include/rive/drawable_flag.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ enum class DrawableFlag : unsigned short

/// Whether this Component lets hit events pass through to components behind it
Opaque = 1 << 3,

/// Whether the computed world bounds for a shape need to be recalculated
/// Using Clean instead of dirty so it doesn't need to be initialized to 1
WorldBoundsClean = 1 << 4,
};
RIVE_MAKE_ENUM_BITSET(DrawableFlag)
} // namespace rive
Expand Down
2 changes: 2 additions & 0 deletions include/rive/math/aabb.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ class AABB
return Vec2D(width() == 0.0f ? 0.0f : (point.x - left()) * 2.0f / width() - 1.0f,
(height() == 0.0f ? 0.0f : point.y - top()) * 2.0f / height() - 1.0f);
}

bool contains(Vec2D position) const;
};

} // namespace rive
Expand Down
14 changes: 14 additions & 0 deletions include/rive/shapes/shape.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "rive/generated/shapes/shape_base.hpp"
#include "rive/shapes/path_composer.hpp"
#include "rive/shapes/shape_paint_container.hpp"
#include "rive/drawable_flag.hpp"
#include <vector>

namespace rive
Expand All @@ -17,6 +18,7 @@ class Shape : public ShapeBase, public ShapePaintContainer
private:
PathComposer m_PathComposer;
std::vector<Path*> m_Paths;
AABB m_WorldBounds;

bool m_WantDifferencePath = false;

Expand Down Expand Up @@ -47,6 +49,18 @@ class Shape : public ShapeBase, public ShapePaintContainer
bool isEmpty();
void pathCollapseChanged();

AABB worldBounds()
{
if ((static_cast<DrawableFlag>(drawableFlags()) & DrawableFlag::WorldBoundsClean) !=
DrawableFlag::WorldBoundsClean)
{
drawableFlags(drawableFlags() |
static_cast<unsigned short>(DrawableFlag::WorldBoundsClean));
m_WorldBounds = computeWorldBounds();
}
return m_WorldBounds;
}

AABB computeWorldBounds(const Mat2D* xform = nullptr) const;
AABB computeLocalBounds() const;
};
Expand Down
17 changes: 15 additions & 2 deletions src/animation/state_machine_instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,15 +418,28 @@ class HitShape : public HitComponent
float hitRadius = 2;
Vec2D previousPosition;
std::vector<const StateMachineListener*> listeners;
HitResult processEvent(Vec2D position, ListenerType hitType, bool canHit) override

bool hitTest(Vec2D position) const
{

auto shape = m_component->as<Shape>();
auto worldBounds = shape->worldBounds();
if (!worldBounds.contains(position))
{
return false;
}
auto hitArea = AABB(position.x - hitRadius,
position.y - hitRadius,
position.x + hitRadius,
position.y + hitRadius)
.round();
bool isOver = canHit ? shape->hitTest(hitArea) : false;
return shape->hitTest(hitArea);
}

HitResult processEvent(Vec2D position, ListenerType hitType, bool canHit) override
{
auto shape = m_component->as<Shape>();
bool isOver = canHit ? hitTest(position) : false;
bool hoverChange = isHovered != isOver;
isHovered = isOver;
if (hoverChange && isHovered)
Expand Down
5 changes: 5 additions & 0 deletions src/math/aabb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,8 @@ void AABB::join(AABB& out, const AABB& a, const AABB& b)
out.maxX = std::max(a.maxX, b.maxX);
out.maxY = std::max(a.maxY, b.maxY);
}

bool AABB::contains(Vec2D point) const
{
return point.x >= left() && point.x <= right() && point.y >= top() && point.y <= bottom();
}
1 change: 1 addition & 0 deletions src/shapes/shape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ bool Shape::collapse(bool value)

void Shape::pathChanged()
{
drawableFlags(drawableFlags() & ~static_cast<unsigned short>(DrawableFlag::WorldBoundsClean));
m_PathComposer.addDirt(ComponentDirt::Path, true);
for (auto constraint : constraints())
{
Expand Down
15 changes: 15 additions & 0 deletions test/aabb_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,19 @@ TEST_CASE("isEmptyOrNaN", "[AABB]")
CHECK(AABB{nan, nan, nan, 10}.isEmptyOrNaN());
CHECK(AABB{nan, nan, nan, nan}.isEmptyOrNaN());
}

TEST_CASE("AABB contains", "[AABB]")
{
CHECK(AABB{0, 0, 100, 100}.contains(Vec2D(20, 20)));
CHECK(AABB{0, 0, 100, 100}.contains(Vec2D(0, 0)));
CHECK(AABB{0, 0, 100, 100}.contains(Vec2D(100, 100)));
CHECK(!AABB{0, 0, 100, 100}.contains(Vec2D(200, 200)));
CHECK(!AABB{0, 0, 100, 100}.contains(Vec2D(-200, -200)));
auto leftBoundary = 0.f;
auto rightBoundary = 100.f;
CHECK(!AABB{leftBoundary, 0, rightBoundary, 100.0}.contains(
Vec2D(leftBoundary - std::numeric_limits<float>::epsilon(), 50)));
CHECK(!AABB{leftBoundary, 0, rightBoundary, 100.0}.contains(
Vec2D(rightBoundary + rightBoundary * std::numeric_limits<float>::epsilon(), 50)));
}
} // namespace rive
5 changes: 5 additions & 0 deletions test/hittest_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ TEST_CASE("hit test on opaque nested artboard", "[hittest]")
// toggle changes value because it is not under an opaque nested artboard
REQUIRE(secondGrayToggle->value() == true);

stateMachineInstance->pointerDown(rive::Vec2D(301.0f, 50.0f));
// toggle does not change because it is beyond the area of the square by 1 pixel
// And the 2px padding is unly used after the coarse grained test passes
REQUIRE(secondGrayToggle->value() == true);

stateMachineInstance->pointerDown(rive::Vec2D(100.0f, 50.0f));
// toggle does not change because it is under an opaque nested artboard
REQUIRE(secondGrayToggle->value() == true);
Expand Down

0 comments on commit 2c45ce8

Please sign in to comment.