From 73731ffa6efaf8d5c53d0b1967c7b3ac13034515 Mon Sep 17 00:00:00 2001 From: mjtalbot Date: Fri, 3 Nov 2023 17:15:23 +0000 Subject: [PATCH] Fix follow path 6070 fixes #6070 thank you @philter for sorting out the path mess, i got myself extra confused with `addToRawPath` (i thought we were simply passing the variables along and one of them got corrupted, i didnt notice that it was just down to a bad static cast when we were a path!) vid of this behaving in dart & cpp Added a rive-flutter test to this. one interesting thing, essentially follow path currently follows the "first" path in a shape. https://github.com/rive-app/rive/assets/1216025/d59026f6-c901-439c-aff2-2214a021ee75 Diffs= ef8a4e7f7 Fix follow path 6070 (#6182) 3927ea695 add support for rendering static scene (#6192) Co-authored-by: Maxwell Talbot Co-authored-by: Phil Chung --- .rive_head | 2 +- .../constraints/follow_path_constraint.dart | 132 ++++++++++-------- test/assets/follow_path_path.riv | 3 + .../follow_path/golden_follow_path_test.dart | 43 +++++- .../images/follow_path_path_01.png | 3 + .../images/follow_path_path_02.png | 3 + .../images/follow_path_path_03.png | 3 + 7 files changed, 129 insertions(+), 60 deletions(-) create mode 100644 test/assets/follow_path_path.riv create mode 100644 test/goldens/follow_path/images/follow_path_path_01.png create mode 100644 test/goldens/follow_path/images/follow_path_path_02.png create mode 100644 test/goldens/follow_path/images/follow_path_path_03.png diff --git a/.rive_head b/.rive_head index 62760740..b089dd87 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -351838fb575bb2533ef60fa4caab0071a1f50771 +ef8a4e7f711aef6b2d468a885a79f919118bae70 diff --git a/lib/src/rive_core/constraints/follow_path_constraint.dart b/lib/src/rive_core/constraints/follow_path_constraint.dart index d03bf180..b6a1df33 100644 --- a/lib/src/rive_core/constraints/follow_path_constraint.dart +++ b/lib/src/rive_core/constraints/follow_path_constraint.dart @@ -4,6 +4,7 @@ import 'dart:ui' as ui; import 'package:rive/src/generated/constraints/follow_path_constraint_base.dart'; import 'package:rive/src/rive_core/component.dart'; import 'package:rive/src/rive_core/constraints/constraint.dart'; +import 'package:rive/src/rive_core/shapes/path.dart'; import 'package:rive/src/rive_core/shapes/shape.dart'; import 'package:rive/src/rive_core/transform_component.dart'; import 'package:rive/src/rive_core/transform_space.dart'; @@ -17,61 +18,63 @@ class FollowPathConstraint extends FollowPathConstraintBase { final ui.Path _worldPath = ui.Path(); Mat2D get targetTransform { - if (target is! Shape) { - return target!.worldTransform; - } - var metrics = _worldPath.computeMetrics().toList(growable: false); - if (metrics.isEmpty) { - return Mat2D(); - } - double totalLength = 0.0; - for (final metric in metrics) { - totalLength += metric.length; - } - // Normalize distance value to 0-1 since we need to support values - // <0 and >1 - // Negative values follow path in reverse direction - var actualDistance = distance % 1; - // This handles the case where distance is any whole number other than 0. - // In those cases we want actualDistance to return 1 - if (distance != 0 && actualDistance == 0) { - actualDistance = 1; - } - double distanceUnits = totalLength * actualDistance.clamp(0, 1); - var itr = metrics.iterator; - - // We already checked it wasn't empty. - itr.moveNext(); - ui.PathMetric metric; - do { - metric = itr.current; - if (distanceUnits <= metric.length) { - break; + if (target is Shape || target is Path) { + var metrics = _worldPath.computeMetrics().toList(growable: false); + if (metrics.isEmpty) { + return Mat2D(); + } + double totalLength = 0.0; + for (final metric in metrics) { + totalLength += metric.length; } + // Normalize distance value to 0-1 since we need to support values + // <0 and >1 + // Negative values follow path in reverse direction + var actualDistance = distance % 1; + // This handles the case where distance is any whole number other than 0. + // In those cases we want actualDistance to return 1 + if (distance != 0 && actualDistance == 0) { + actualDistance = 1; + } + double distanceUnits = totalLength * actualDistance.clamp(0, 1); + var itr = metrics.iterator; - distanceUnits -= metric.length; - } while (itr.moveNext()); + // We already checked it wasn't empty. + itr.moveNext(); + ui.PathMetric metric; + do { + metric = itr.current; + if (distanceUnits <= metric.length) { + break; + } - var tangent = metric.getTangentForOffset(distanceUnits); + distanceUnits -= metric.length; + } while (itr.moveNext()); - if (tangent == null) { - return Mat2D(); - } + var tangent = metric.getTangentForOffset(distanceUnits); - Vec2D position = Vec2D.fromValues(tangent.position.dx, tangent.position.dy); - Mat2D transformB = Mat2D.clone(target!.worldTransform); + if (tangent == null) { + return Mat2D(); + } - if (orient) { - Mat2D.fromRotation( - transformB, atan2(tangent.vector.dy, tangent.vector.dx)); + Vec2D position = + Vec2D.fromValues(tangent.position.dx, tangent.position.dy); + Mat2D transformB = Mat2D.clone(target!.worldTransform); + + if (orient) { + Mat2D.fromRotation( + transformB, atan2(tangent.vector.dy, tangent.vector.dx)); + } + final offsetPosition = offset + ? Vec2D.fromValues(constrainedComponent!.transform[4], + constrainedComponent!.transform[5]) + : Vec2D(); + transformB[4] = position.x + offsetPosition.x; + transformB[5] = position.y + offsetPosition.y; + return transformB; + } else { + return target!.worldTransform; } - final offsetPosition = offset - ? Vec2D.fromValues(constrainedComponent!.transform[4], - constrainedComponent!.transform[5]) - : Vec2D(); - transformB[4] = position.x + offsetPosition.x; - transformB[5] = position.y + offsetPosition.y; - return transformB; } @override @@ -125,29 +128,41 @@ class FollowPathConstraint extends FollowPathConstraintBase { var shape = target as Shape; // Follow path should update after the target's path composer shape.pathComposer.addDependent(this); + } else if (target is Path) { + var path = target as Path; + path.addDependent(this); } + if (constrainedComponent != null) { // The constrained component should update after follow path addDependent(constrainedComponent!, via: this); } } - @override - void update(int dirt) { - if (target is! Shape) { - return; - } - var shape = target as Shape; + void _resetWorldPath(Set paths) { _worldPath.reset(); - for (final path in shape.paths) { + for (final path in paths) { _worldPath.addPath(path.uiPath, ui.Offset.zero, matrix4: path.pathTransform.mat4); } } @override - Component? get targetDependencyParent => - target != null ? (target as Shape) : null; + void update(int dirt) { + final _target = target; + if (_target is Shape) { + _resetWorldPath(_target.paths); + } else if (_target is Path) { + _resetWorldPath({_target}); + } + } + + @override + Component? get targetDependencyParent => target is Shape + ? (target as Shape) + : target is Path + ? (target as Path) + : null; @override void distanceChanged(double from, double to) => markConstraintDirty(); @@ -159,5 +174,6 @@ class FollowPathConstraint extends FollowPathConstraintBase { void offsetChanged(bool from, bool to) => markConstraintDirty(); @override - bool validate() => super.validate() && (target == null || target is Shape); + bool validate() => + super.validate() && (target == null || target is Shape || target is Path); } diff --git a/test/assets/follow_path_path.riv b/test/assets/follow_path_path.riv new file mode 100644 index 00000000..bb9eb859 --- /dev/null +++ b/test/assets/follow_path_path.riv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54020ef6a20855612b904334a59c4f65649f77215aa062c9d0c5618f3ddca5d6 +size 818701 diff --git a/test/goldens/follow_path/golden_follow_path_test.dart b/test/goldens/follow_path/golden_follow_path_test.dart index 844c9ad2..df3d6207 100644 --- a/test/goldens/follow_path/golden_follow_path_test.dart +++ b/test/goldens/follow_path/golden_follow_path_test.dart @@ -6,7 +6,7 @@ import '../golden_comparator.dart'; void main() { group('Golden - follow path tests', () { - testWidgets('Follow path over time', (WidgetTester tester) async { + testWidgets('Follow path - shape over time', (WidgetTester tester) async { final riveBytes = loadFile('assets/follow_path_shapes.riv'); final file = RiveFile.import(riveBytes); late Artboard artboard; @@ -55,5 +55,46 @@ void main() { reason: 'Follow path should work as animation advances', ); }); + + testWidgets('Follow path - path over time', (WidgetTester tester) async { + final riveBytes = loadFile('assets/follow_path_path.riv'); + final file = RiveFile.import(riveBytes); + late Artboard artboard; + + final widget = RiveAnimation.direct( + file, + stateMachines: const ['State Machine 1'], + onInit: (a) { + artboard = a; + }, + ); + await tester.pumpWidget(widget); + + await tester.pump(); + + await expectGoldenMatches( + find.byType(RiveAnimation), + 'follow_path_path_01.png', + reason: 'Follow path should work as animation advances', + ); + + artboard.advance(0.5, nested: true); + await tester.pump(); + + await expectGoldenMatches( + find.byType(RiveAnimation), + 'follow_path_path_02.png', + reason: 'Follow path should work as animation advances', + ); + + artboard.advance(2.5, nested: true); + await tester.pump(); + + await expectGoldenMatches( + find.byType(RiveAnimation), + 'follow_path_path_03.png', + reason: 'Follow path should work as animation advances', + ); + }); }); } diff --git a/test/goldens/follow_path/images/follow_path_path_01.png b/test/goldens/follow_path/images/follow_path_path_01.png new file mode 100644 index 00000000..5a9fc782 --- /dev/null +++ b/test/goldens/follow_path/images/follow_path_path_01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de10247ebbb538316eae7226dc221a20536194bbd35cc9b76e735cd18cec31b8 +size 110190 diff --git a/test/goldens/follow_path/images/follow_path_path_02.png b/test/goldens/follow_path/images/follow_path_path_02.png new file mode 100644 index 00000000..7bdc4360 --- /dev/null +++ b/test/goldens/follow_path/images/follow_path_path_02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee9b67581d39a4f72f358c2b5237596309ef582d55653861d4286194ffcf01be +size 110509 diff --git a/test/goldens/follow_path/images/follow_path_path_03.png b/test/goldens/follow_path/images/follow_path_path_03.png new file mode 100644 index 00000000..b59be8be --- /dev/null +++ b/test/goldens/follow_path/images/follow_path_path_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d07e5795cf24a6f7589f01cfeac4ca3fe80bf5e3f8619592684f8698c72fa15 +size 104064