mirror of
https://github.com/rive-app/rive-flutter
synced 2025-07-05 21:55:58 +00:00
Fix hittest
This commit is contained in:
@ -67,11 +67,19 @@ class HitTester implements PathInterface {
|
||||
int winding = 1;
|
||||
if (y0 > y1) {
|
||||
winding = -1;
|
||||
|
||||
// Swap our two points (is there a swap utility?)
|
||||
double tmp = y0;
|
||||
// ignore: parameter_assignments
|
||||
y0 = y1;
|
||||
// ignore: parameter_assignments
|
||||
y1 = tmp;
|
||||
|
||||
tmp = x0;
|
||||
// ignore: parameter_assignments
|
||||
x0 = x1;
|
||||
// ignore: parameter_assignments
|
||||
x1 = tmp;
|
||||
}
|
||||
// now we're monotonic in Y: y0 <= y1
|
||||
if (y1 <= 0 || y0 >= _height) {
|
||||
@ -184,7 +192,7 @@ class HitTester implements PathInterface {
|
||||
// ... At^3 + Bt^2 + Ct + D
|
||||
//
|
||||
final aX = (x3 - _prevX) + 3 * (x1 - x2);
|
||||
// final bX = 3 * ((x2 - x1) + (_prevX - x1));
|
||||
final bX = 3 * ((x2 - x1) + (_prevX - x1));
|
||||
final cX = 3 * (x1 - _prevX);
|
||||
final dX = _prevX;
|
||||
|
||||
@ -198,7 +206,7 @@ class HitTester implements PathInterface {
|
||||
double py = _prevY;
|
||||
for (int i = 1; i < count - 1; ++i) {
|
||||
// Horner's method for evaluating the simple polynomial
|
||||
final nx = ((aX * t + aX) * t + cX) * t + dX;
|
||||
final nx = ((aX * t + bX) * t + cX) * t + dX;
|
||||
final ny = ((aY * t + bY) * t + cY) * t + dY;
|
||||
_clipLine(px, py, nx, ny);
|
||||
px = nx;
|
||||
|
91
test/hittest_test.dart
Normal file
91
test/hittest_test.dart
Normal file
@ -0,0 +1,91 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:rive/src/rive_core/math/aabb.dart';
|
||||
import 'package:rive/src/rive_core/math/hit_test.dart' as HT;
|
||||
import 'package:rive/src/rive_core/math/vec2d.dart';
|
||||
|
||||
// We compare the output of HitTester against analytic predicates.
|
||||
// When we near an edge, we don't always get precisely what we expect
|
||||
// (and *on* an edge, its not clear if we should "hit" it.
|
||||
// To handle this in test, we allow the 'expected' value to return
|
||||
// kEdgeCase, which means we will accept either result from the HitTester.
|
||||
enum Fuzzy {
|
||||
kTrue,
|
||||
kFalse,
|
||||
kEdgeCase,
|
||||
}
|
||||
|
||||
Fuzzy fromBool(bool b) {
|
||||
return b ? Fuzzy.kTrue : Fuzzy.kFalse;
|
||||
}
|
||||
|
||||
void addRect(HT.HitTester ht, AABB rect) {
|
||||
ht.moveTo(rect.left, rect.top);
|
||||
ht.lineTo(rect.right, rect.top);
|
||||
ht.lineTo(rect.right, rect.bottom);
|
||||
ht.lineTo(rect.left, rect.bottom);
|
||||
ht.close();
|
||||
}
|
||||
|
||||
// Circle of radius 64 centered at origin. This is pasted from our
|
||||
// analytic circle logic.
|
||||
void addCubicCircle(HT.HitTester ht) {
|
||||
ht.moveTo(0.0, -64.0);
|
||||
ht.cubicTo(35.346223989184, -64.0, 64.0, -35.346223989184, 64.0, 0.0);
|
||||
ht.cubicTo(64.0, 35.346223989184, 35.346223989184, 64.0, 0.0, 64.0);
|
||||
ht.cubicTo(-35.346223989184, 64.0, -64.0, 35.346223989184, -64.0, 0.0);
|
||||
ht.cubicTo(-64.0, -35.346223989184, -35.346223989184, -64.0, 0.0, -64.0);
|
||||
}
|
||||
|
||||
typedef HitTestBuilder = void Function(HT.HitTester);
|
||||
typedef HitTestChecker = Fuzzy Function(double x, double);
|
||||
|
||||
// Tests all of the pixel-centers inside 'domain' with optional margin
|
||||
// (to make it easy to also test pixels outside the bounds). Takes
|
||||
// two lambdas:
|
||||
// builder : to rebuild the hittester for each test
|
||||
// checker : to return the expected value for a given x,y
|
||||
//
|
||||
void doTest(AABB domain, HitTestBuilder builder, HitTestChecker checker,
|
||||
{int margin = 4}) {
|
||||
final y0 = domain.top.floor() - margin;
|
||||
final y1 = domain.bottom.ceil() - margin;
|
||||
final x0 = domain.left.floor() + margin;
|
||||
final x1 = domain.right.ceil() + margin;
|
||||
for (int y = y0; y < y1; ++y) {
|
||||
for (int x = x0; x < x1; ++x) {
|
||||
final area = IAABB(x, y, x + 1, y + 1);
|
||||
final ht = HT.HitTester(area);
|
||||
builder(ht);
|
||||
bool result = ht.test();
|
||||
final expected = checker(x + 0.5, y + 0.5);
|
||||
if (expected != Fuzzy.kEdgeCase) {
|
||||
expect(result, expected == Fuzzy.kTrue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('rect', () {
|
||||
final rect = AABB.fromValues(-5, -5, 5, 5);
|
||||
|
||||
doTest(rect, (ht) => addRect(ht, rect),
|
||||
(x, y) => fromBool(rect.contains(Vec2D.fromValues(x, y))));
|
||||
});
|
||||
|
||||
test('cubic_circle', () {
|
||||
final rect = AABB.fromValues(-64, -64, 64, 64);
|
||||
|
||||
doTest(rect, addCubicCircle, (x, y) {
|
||||
final radius = math.sqrt(x * x + y * y);
|
||||
if (radius <= 63) {
|
||||
return Fuzzy.kTrue;
|
||||
}
|
||||
if (radius >= 65) {
|
||||
return Fuzzy.kFalse;
|
||||
}
|
||||
return Fuzzy.kEdgeCase;
|
||||
});
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user