diff --git a/example/pubspec.lock b/example/pubspec.lock index fec5648..5ca8c76 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -94,7 +94,7 @@ packages: path: ".." relative: true source: path - version: "0.0.1+3" + version: "0.0.2" sky_engine: dependency: transitive description: flutter diff --git a/lib/rive.dart b/lib/rive.dart index 807036f..3a7f369 100644 --- a/lib/rive.dart +++ b/lib/rive.dart @@ -2,9 +2,11 @@ library rive; export 'package:rive/src/rive_file.dart'; export 'package:rive/src/rive.dart'; +export 'package:rive/src/runtime_artboard.dart'; +export 'package:rive/src/controllers/simple_controller.dart'; export 'package:rive/src/rive_core/rive_animation_controller.dart'; -export 'package:rive/src/controllers/simple_controller.dart'; -export 'package:rive/src/controllers/simple_controller.dart'; +export 'package:rive/src/rive_core/animation/linear_animation.dart'; +export 'package:rive/src/rive_core/animation/linear_animation_instance.dart'; export 'package:rive/src/rive_core/artboard.dart'; export 'package:rive/src/rive_core/shapes/shape.dart'; export 'package:rive/src/rive_core/shapes/paint/fill.dart'; diff --git a/lib/src/controllers/simple_controller.dart b/lib/src/controllers/simple_controller.dart index 62d3e49..790d793 100644 --- a/lib/src/controllers/simple_controller.dart +++ b/lib/src/controllers/simple_controller.dart @@ -1,5 +1,5 @@ import 'package:rive/src/rive_core/animation/linear_animation.dart'; -import 'package:rive/src/rive_core/animation/loop.dart'; +import 'package:rive/src/rive_core/animation/linear_animation_instance.dart'; import 'package:rive/src/rive_core/rive_animation_controller.dart'; import 'package:rive/src/runtime_artboard.dart'; @@ -7,70 +7,29 @@ import 'package:rive/src/runtime_artboard.dart'; /// by an artist. All playback parameters (looping, speed, keyframes) are artist /// defined in the Rive editor. class SimpleAnimation extends RiveAnimationController { - LinearAnimation _animation; - double _time = 0; - int _direction = 1; + LinearAnimationInstance _instance; final String animationName; SimpleAnimation(this.animationName); @override bool init(RuntimeArtboard artboard) { - _animation = artboard.animations.firstWhere( - (animation) => animation.name == animationName, + var animation = artboard.animations.firstWhere( + (animation) => + animation is LinearAnimation && animation.name == animationName, orElse: () => null, - ) as LinearAnimation; + ); + if (animation != null) { + _instance = LinearAnimationInstance(animation as LinearAnimation); + } isActive = true; - return _animation != null; + return _instance != null; } @override void apply(RuntimeArtboard artboard, double elapsedSeconds) { - _animation.apply(_time, coreContext: artboard); - _time += elapsedSeconds * _animation.speed * _direction; - - double frames = _time * _animation.fps; - - var start = _animation.enableWorkArea ? _animation.workStart : 0; - var end = - _animation.enableWorkArea ? _animation.workEnd : _animation.duration; - var range = end - start; - - switch (_animation.loop) { - case Loop.oneShot: - if (frames > end) { - isActive = false; - frames = end.toDouble(); - _time = frames / _animation.fps; - } - break; - case Loop.loop: - if (frames >= end) { - frames = _time * _animation.fps; - frames = start + (frames - start) % range; - _time = frames / _animation.fps; - } - break; - case Loop.pingPong: - // ignore: literal_only_boolean_expressions - while (true) { - if (_direction == 1 && frames >= end) { - _direction = -1; - frames = end + (end - frames); - _time = frames / _animation.fps; - } else if (_direction == -1 && frames < start) { - _direction = 1; - frames = start + (start - frames); - _time = frames / _animation.fps; - } else { - // we're within the range, we can stop fixing. We do this in a - // loop to fix conditions when time has advanced so far that we've - // ping-ponged back and forth a few times in a single frame. We - // want to accomodate for this in cases where animations are not - // advanced on regular intervals. - break; - } - } - break; + _instance.animation.apply(_instance.time, coreContext: artboard); + if (!_instance.advance(elapsedSeconds)) { + isActive = false; } } diff --git a/lib/src/rive_core/animation/linear_animation_instance.dart b/lib/src/rive_core/animation/linear_animation_instance.dart new file mode 100644 index 0000000..1b1465f --- /dev/null +++ b/lib/src/rive_core/animation/linear_animation_instance.dart @@ -0,0 +1,58 @@ +import 'package:rive/src/rive_core/animation/linear_animation.dart'; +import 'package:rive/src/rive_core/animation/loop.dart'; + +class LinearAnimationInstance { + final LinearAnimation animation; + double _time = 0; + int direction = 1; + LinearAnimationInstance(this.animation); + double get time => _time; + set time(double value) { + if (_time == value) { + return; + } + _time = value; + direction = 1; + } + + bool advance(double elapsedSeconds) { + _time += elapsedSeconds * animation.speed * direction; + double frames = _time * animation.fps; + var start = animation.enableWorkArea ? animation.workStart : 0; + var end = animation.enableWorkArea ? animation.workEnd : animation.duration; + var range = end - start; + bool keepGoing = true; + switch (animation.loop) { + case Loop.oneShot: + if (frames > end) { + keepGoing = false; + frames = end.toDouble(); + _time = frames / animation.fps; + } + break; + case Loop.loop: + if (frames >= end) { + frames = _time * animation.fps; + frames = start + (frames - start) % range; + _time = frames / animation.fps; + } + break; + case Loop.pingPong: + while (true) { + if (direction == 1 && frames >= end) { + direction = -1; + frames = end + (end - frames); + _time = frames / animation.fps; + } else if (direction == -1 && frames < start) { + direction = 1; + frames = start + (start - frames); + _time = frames / animation.fps; + } else { + break; + } + } + break; + } + return keepGoing; + } +} diff --git a/lib/src/rive_core/artboard.dart b/lib/src/rive_core/artboard.dart index fda90d6..813206d 100644 --- a/lib/src/rive_core/artboard.dart +++ b/lib/src/rive_core/artboard.dart @@ -189,6 +189,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer { } canvas.save(); canvas.clipRect(Rect.fromLTWH(0, 0, width, height)); + canvas.translate(width * (originX ?? 0), height * (originY ?? 0)); for (final drawable in _drawables) { drawable.draw(canvas); } @@ -202,9 +203,15 @@ class Artboard extends ArtboardBase with ShapePaintContainer { @override Mat2D get worldTransform => Mat2D(); @override - void originXChanged(double from, double to) {} + void originXChanged(double from, double to) { + addDirt(ComponentDirt.worldTransform); + } + @override - void originYChanged(double from, double to) {} + void originYChanged(double from, double to) { + addDirt(ComponentDirt.worldTransform); + } + bool internalAddAnimation(Animation animation) { if (_animations.contains(animation)) { return false; diff --git a/lib/src/rive_core/math/segment2d.dart b/lib/src/rive_core/math/segment2d.dart index 14eb98c..a852574 100644 --- a/lib/src/rive_core/math/segment2d.dart +++ b/lib/src/rive_core/math/segment2d.dart @@ -12,7 +12,7 @@ class Segment2D { Vec2D diff; double lengthSquared; Segment2D(this.start, this.end); - ProjectionResult projectPoint(Vec2D point) { + ProjectionResult projectPoint(Vec2D point, {bool clamp = true}) { if (diff == null) { diff = Vec2D.subtract(Vec2D(), start, end); lengthSquared = Vec2D.squaredLength(diff); @@ -23,11 +23,13 @@ class Segment2D { double t = ((point[0] - start[0]) * (end[0] - start[0]) + (point[1] - start[1]) * (end[1] - start[1])) / lengthSquared; - if (t < 0.0) { - return ProjectionResult(0, start); - } - if (t > 1.0) { - return ProjectionResult(1, end); + if (clamp) { + if (t < 0.0) { + return ProjectionResult(0, start); + } + if (t > 1.0) { + return ProjectionResult(1, end); + } } return ProjectionResult( t, diff --git a/lib/src/rive_core/node.dart b/lib/src/rive_core/node.dart index f65a258..4460500 100644 --- a/lib/src/rive_core/node.dart +++ b/lib/src/rive_core/node.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:rive/src/rive_core/component_dirt.dart'; import 'package:rive/src/rive_core/container_component.dart'; +import 'package:rive/src/rive_core/math/aabb.dart'; import 'package:rive/src/rive_core/math/mat2d.dart'; import 'package:rive/src/rive_core/math/vec2d.dart'; import 'package:rive/src/generated/node_base.dart'; @@ -127,4 +128,6 @@ class Node extends NodeBase { super.parentChanged(from, to); markWorldTransformDirty(); } + + AABB get localBounds => AABB.fromValues(x, y, x, y); } diff --git a/lib/src/rive_core/shapes/path.dart b/lib/src/rive_core/shapes/path.dart index b507885..62a6382 100644 --- a/lib/src/rive_core/shapes/path.dart +++ b/lib/src/rive_core/shapes/path.dart @@ -204,7 +204,10 @@ abstract class Path extends PathBase { return true; } - AABB preciseComputeBounds(List renderPoints, Mat2D transform) { + @override + AABB get localBounds => preciseComputeBounds(renderVertices, Mat2D()); + AABB preciseComputeBounds(List renderPoints, Mat2D transform, + {bool debug = false}) { if (renderPoints.isEmpty) { return AABB(); } @@ -230,7 +233,6 @@ abstract class Path extends PathBase { cin = Vec2D.transformMat2D(Vec2D(), cin, transform); cout = Vec2D.transformMat2D(Vec2D(), cout, transform); } - const double epsilon = 0.000000001; final double startX = lastPoint[0]; final double startY = lastPoint[1]; final double cpX1 = cout[0]; @@ -240,121 +242,55 @@ abstract class Path extends PathBase { final double endX = next[0]; final double endY = next[1]; lastPoint = next; - double extremaX; - double extremaY; - double a, b, c; - if (!(((startX < cpX1) && (cpX1 < cpX2) && (cpX2 < endX)) || - ((startX > cpX1) && (cpX1 > cpX2) && (cpX2 > endX)))) { - a = -startX + (3 * (cpX1 - cpX2)) + endX; - b = 2 * (startX - (2 * cpX1) + cpX2); - c = -startX + cpX1; - double s = (b * b) - (4 * a * c); - if ((s >= 0.0) && (a.abs() > epsilon)) { - if (s == 0.0) { - final double t = -b / (2 * a); - final double tprime = 1.0 - t; - if ((t >= 0.0) && (t <= 1.0)) { - extremaX = ((tprime * tprime * tprime) * startX) + - ((3 * tprime * tprime * t) * cpX1) + - ((3 * tprime * t * t) * cpX2) + - (t * t * t * endX); - if (extremaX < bounds[0]) { - bounds[0] = extremaX; - } - if (extremaX > bounds[2]) { - bounds[2] = extremaX; - } - } - } else { - s = sqrt(s); - double t = (-b - s) / (2 * a); - double tprime = 1.0 - t; - if ((t >= 0.0) && (t <= 1.0)) { - extremaX = ((tprime * tprime * tprime) * startX) + - ((3 * tprime * tprime * t) * cpX1) + - ((3 * tprime * t * t) * cpX2) + - (t * t * t * endX); - if (extremaX < bounds[0]) { - bounds[0] = extremaX; - } - if (extremaX > bounds[2]) { - bounds[2] = extremaX; - } - } - t = (-b + s) / (2 * a); - tprime = 1.0 - t; - if ((t >= 0.0) && (t <= 1.0)) { - extremaX = ((tprime * tprime * tprime) * startX) + - ((3 * tprime * tprime * t) * cpX1) + - ((3 * tprime * t * t) * cpX2) + - (t * t * t * endX); - if (extremaX < bounds[0]) { - bounds[0] = extremaX; - } - if (extremaX > bounds[2]) { - bounds[2] = extremaX; - } - } - } - } - } - if (!(((startY < cpY1) && (cpY1 < cpY2) && (cpY2 < endY)) || - ((startY > cpY1) && (cpY1 > cpY2) && (cpY2 > endY)))) { - a = -startY + (3 * (cpY1 - cpY2)) + endY; - b = 2 * (startY - (2 * cpY1) + cpY2); - c = -startY + cpY1; - double s = (b * b) - (4 * a * c); - if ((s >= 0.0) && (a.abs() > epsilon)) { - if (s == 0.0) { - final double t = -b / (2 * a); - final double tprime = 1.0 - t; - if ((t >= 0.0) && (t <= 1.0)) { - extremaY = ((tprime * tprime * tprime) * startY) + - ((3 * tprime * tprime * t) * cpY1) + - ((3 * tprime * t * t) * cpY2) + - (t * t * t * endY); - if (extremaY < bounds[1]) { - bounds[1] = extremaY; - } - if (extremaY > bounds[3]) { - bounds[3] = extremaY; - } - } - } else { - s = sqrt(s); - final double t = (-b - s) / (2 * a); - final double tprime = 1.0 - t; - if ((t >= 0.0) && (t <= 1.0)) { - extremaY = ((tprime * tprime * tprime) * startY) + - ((3 * tprime * tprime * t) * cpY1) + - ((3 * tprime * t * t) * cpY2) + - (t * t * t * endY); - if (extremaY < bounds[1]) { - bounds[1] = extremaY; - } - if (extremaY > bounds[3]) { - bounds[3] = extremaY; - } - } - final double t2 = (-b + s) / (2 * a); - final double tprime2 = 1.0 - t2; - if ((t2 >= 0.0) && (t2 <= 1.0)) { - extremaY = ((tprime2 * tprime2 * tprime2) * startY) + - ((3 * tprime2 * tprime2 * t2) * cpY1) + - ((3 * tprime2 * t2 * t2) * cpY2) + - (t2 * t2 * t2 * endY); - if (extremaY < bounds[1]) { - bounds[1] = extremaY; - } - if (extremaY > bounds[3]) { - bounds[3] = extremaY; - } - } - } - } - } + _expandBoundsForAxis(bounds, 0, startX, cpX1, cpX2, endX); + _expandBoundsForAxis(bounds, 1, startY, cpY1, cpY2, endY); } } return bounds; } } + +void _expandBoundsToCubicPoint(AABB bounds, int component, double t, double a, + double b, double c, double d) { + if (t >= 0 && t <= 1) { + var ti = 1 - t; + double extremaY = ((ti * ti * ti) * a) + + ((3 * ti * ti * t) * b) + + ((3 * ti * t * t) * c) + + (t * t * t * d); + if (extremaY < bounds[component]) { + bounds[component] = extremaY; + } + if (extremaY > bounds[component + 2]) { + bounds[component + 2] = extremaY; + } + } +} + +void _expandBoundsForAxis(AABB bounds, int component, double start, double cp1, + double cp2, double end) { + if (!(((start < cp1) && (cp1 < cp2) && (cp2 < end)) || + ((start > cp1) && (cp1 > cp2) && (cp2 > end)))) { + var a = 3 * (cp1 - start); + var b = 3 * (cp2 - cp1); + var c = 3 * (end - cp2); + var d = a - 2 * b + c; + if (d != 0) { + var m1 = -sqrt(b * b - a * c); + var m2 = -a + b; + _expandBoundsToCubicPoint( + bounds, component, -(m1 + m2) / d, start, cp1, cp2, end); + _expandBoundsToCubicPoint( + bounds, component, -(-m1 + m2) / d, start, cp1, cp2, end); + } else if (b != c && d == 0) { + _expandBoundsToCubicPoint( + bounds, component, (2 * b - c) / (2 * (b - c)), start, cp1, cp2, end); + } + var d2a = 2 * (b - a); + var d2b = 2 * (c - b); + if (d2a != b) { + _expandBoundsToCubicPoint( + bounds, component, d2a / (d2a - d2b), start, cp1, cp2, end); + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index cd58401..e0ba403 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: rive description: Rive 2 Flutter Runtime -version: 0.0.1+3 +version: 0.0.2 repository: https://github.com/rive-app/rive-flutter homepage: https://rive.app