Adding exit time to state machine.

This commit is contained in:
Luigi Rosso
2021-03-31 16:16:15 -07:00
parent dcabe9a336
commit 1c7bccc36c
11 changed files with 133 additions and 6 deletions

Binary file not shown.

View File

@ -65,7 +65,8 @@ abstract class StateTransitionBase extends StateMachineLayerComponent {
int _duration = durationInitialValue;
static const int durationPropertyKey = 158;
/// Duration of the trasition (mix time) in milliseconds.
/// Duration of the trasition (mix time) in milliseconds or percentage (0-100)
/// based on flags.
int get duration => _duration;
/// Change the [_duration] field value.
@ -82,4 +83,30 @@ abstract class StateTransitionBase extends StateMachineLayerComponent {
}
void durationChanged(int from, int to);
/// --------------------------------------------------------------------------
/// ExitTime field with key 160.
static const int exitTimeInitialValue = 0;
int _exitTime = exitTimeInitialValue;
static const int exitTimePropertyKey = 160;
/// Duration in milliseconds that must elapse before allowing the state to
/// change. If the flags mark this property as being percentage based, the
/// value is in 0-100% of the outgoing animation's duration
int get exitTime => _exitTime;
/// Change the [_exitTime] field value.
/// [exitTimeChanged] will be invoked only if the field's value has changed.
set exitTime(int value) {
if (_exitTime == value) {
return;
}
int from = _exitTime;
_exitTime = value;
if (hasValidated) {
exitTimeChanged(from, value);
}
}
void exitTimeChanged(int from, int to);
}

View File

@ -352,6 +352,11 @@ class RiveCoreContext {
object.duration = value;
}
break;
case StateTransitionBase.exitTimePropertyKey:
if (object is StateTransitionBase && value is int) {
object.exitTime = value;
}
break;
case KeyFrameDoubleBase.valuePropertyKey:
if (object is KeyFrameDoubleBase && value is double) {
object.value = value;
@ -831,6 +836,7 @@ class RiveCoreContext {
case StateTransitionBase.stateToIdPropertyKey:
case StateTransitionBase.flagsPropertyKey:
case StateTransitionBase.durationPropertyKey:
case StateTransitionBase.exitTimePropertyKey:
case LinearAnimationBase.fpsPropertyKey:
case LinearAnimationBase.durationPropertyKey:
case LinearAnimationBase.loopValuePropertyKey:
@ -980,6 +986,8 @@ class RiveCoreContext {
return (object as StateTransitionBase).flags;
case StateTransitionBase.durationPropertyKey:
return (object as StateTransitionBase).duration;
case StateTransitionBase.exitTimePropertyKey:
return (object as StateTransitionBase).exitTime;
case LinearAnimationBase.fpsPropertyKey:
return (object as LinearAnimationBase).fps;
case LinearAnimationBase.durationPropertyKey:
@ -1255,6 +1263,9 @@ class RiveCoreContext {
case StateTransitionBase.durationPropertyKey:
(object as StateTransitionBase).duration = value;
break;
case StateTransitionBase.exitTimePropertyKey:
(object as StateTransitionBase).exitTime = value;
break;
case LinearAnimationBase.fpsPropertyKey:
(object as LinearAnimationBase).fps = value;
break;

View File

@ -25,6 +25,10 @@ class LinearAnimation extends LinearAnimationBase {
return true;
}
double get startSeconds => (enableWorkArea ? workStart : 0).toDouble() / fps;
double get endSeconds =>
(enableWorkArea ? workEnd : duration).toDouble() / fps;
double get durationSeconds => endSeconds - startSeconds;
void apply(double time, {required CoreContext coreContext, double mix = 1}) {
for (final keyedObject in _keyedObjects.values) {
keyedObject.apply(time, mix, coreContext);

View File

@ -4,11 +4,15 @@ import 'package:rive/src/rive_core/animation/loop.dart';
class LinearAnimationInstance {
final LinearAnimation animation;
double _time = 0;
double _totalTime = 0;
double _lastTotalTime = 0;
int _direction = 1;
bool _didLoop = false;
bool get didLoop => _didLoop;
double _spilledTime = 0;
double get spilledTime => _spilledTime;
double get totalTime => _totalTime;
double get lastTotalTime => _lastTotalTime;
LinearAnimationInstance(this.animation)
: _time =
(animation.enableWorkArea ? animation.workStart : 0).toDouble() /
@ -17,7 +21,9 @@ class LinearAnimationInstance {
if (_time == value) {
return;
}
_time = value;
var diff = _totalTime - _lastTotalTime;
_time = _totalTime = value;
_lastTotalTime = _totalTime - diff;
_direction = 1;
}
@ -35,7 +41,10 @@ class LinearAnimationInstance {
void reset() => _time = startTime;
bool get keepGoing => animation.loop != Loop.oneShot || !_didLoop;
bool advance(double elapsedSeconds) {
_time += elapsedSeconds * animation.speed * _direction;
var deltaSeconds = elapsedSeconds * animation.speed * _direction;
_lastTotalTime = _totalTime;
_totalTime += deltaSeconds;
_time += deltaSeconds;
double frames = _time * animation.fps;
var start = animation.enableWorkArea ? animation.workStart : 0;
var end = animation.enableWorkArea ? animation.workEnd : animation.duration;

View File

@ -1,4 +1,5 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/animation_state.dart';
import 'package:rive/src/rive_core/animation/layer_state.dart';
import 'package:rive/src/rive_core/animation/transition_condition.dart';
import 'package:rive/src/generated/animation/state_transition_base.dart';
@ -24,6 +25,38 @@ class StateTransition extends StateTransitionBase {
}
bool get isDisabled => (flags & StateTransitionFlags.disabled) != 0;
bool get pauseOnExit => (flags & StateTransitionFlags.pauseOnExit) != 0;
bool get enableExitTime => (flags & StateTransitionFlags.enableExitTime) != 0;
double mixTime(LayerState stateFrom) {
if (duration == 0) {
return 0;
}
if ((flags & StateTransitionFlags.durationIsPercentage) != 0) {
var animationDuration = 0.0;
if (stateFrom is AnimationState) {
animationDuration = stateFrom.animation?.durationSeconds ?? 0;
}
return duration / 100 * animationDuration;
} else {
return duration / 1000;
}
}
double exitTimeSeconds(LayerState stateFrom) {
if (exitTime == 0) {
return 0;
}
if ((flags & StateTransitionFlags.exitTimeIsPercentage) != 0) {
var animationDuration = 0.0;
if (stateFrom is AnimationState) {
animationDuration = stateFrom.animation?.durationSeconds ?? 0;
}
return exitTime / 100 * animationDuration;
} else {
return exitTime / 1000;
}
}
@override
bool import(ImportStack importStack) {
var importer =
@ -53,5 +86,7 @@ class StateTransition extends StateTransitionBase {
@override
void durationChanged(int from, int to) {}
@override
void exitTimeChanged(int from, int to) {}
@override
void stateToIdChanged(int from, int to) {}
}

View File

@ -5,6 +5,8 @@ import 'package:rive/src/generated/animation/transition_bool_condition_base.dart
export 'package:rive/src/generated/animation/transition_bool_condition_base.dart';
class TransitionBoolCondition extends TransitionBoolConditionBase {
@override
bool validate() => super.validate() && (input is StateMachineBool);
@override
bool evaluate(HashMap<int, dynamic> values) {
var boolInput = input as StateMachineBool;

View File

@ -8,6 +8,8 @@ class TransitionDoubleCondition extends TransitionDoubleConditionBase {
@override
void valueChanged(double from, double to) {}
@override
bool validate() => super.validate() && (input is StateMachineDouble);
@override
bool evaluate(HashMap<int, dynamic> values) {
var doubleInput = input as StateMachineDouble;
dynamic providedValue = values[input.id];

View File

@ -4,6 +4,8 @@ import 'package:rive/src/generated/animation/transition_trigger_condition_base.d
export 'package:rive/src/generated/animation/transition_trigger_condition_base.dart';
class TransitionTriggerCondition extends TransitionTriggerConditionBase {
@override
bool validate() => super.validate() && (input is StateMachineTrigger);
@override
bool evaluate(HashMap<int, dynamic> values) {
dynamic providedValue = values[input.id];

View File

@ -11,6 +11,8 @@ import 'package:rive/src/rive_core/rive_animation_controller.dart';
class LayerController {
final StateMachineLayer layer;
LayerState? _currentState;
LayerState? _stateFrom;
bool _holdAnimationFrom = false;
LinearAnimationInstance? _animationInstanceFrom;
StateTransition? _transition;
double _mix = 1.0;
@ -41,8 +43,10 @@ class LayerController {
return false;
}
}
if (_transition != null && _transition!.duration != 0) {
_mix = (_mix + elapsedSeconds / (_transition!.duration / 1000))
if (_transition != null &&
_stateFrom != null &&
_transition!.duration != 0) {
_mix = (_mix + elapsedSeconds / _transition!.mixTime(_stateFrom!))
.clamp(0, 1)
.toDouble();
} else {
@ -50,7 +54,9 @@ class LayerController {
}
var keepGoing = _mix != 1;
if (_animationInstanceFrom != null && _mix < 1) {
_animationInstanceFrom!.advance(elapsedSeconds);
if (!_holdAnimationFrom) {
_animationInstanceFrom!.advance(elapsedSeconds);
}
_animationInstanceFrom!.animation.apply(_animationInstanceFrom!.time,
mix: 1 - _mix, coreContext: core);
}
@ -77,6 +83,9 @@ class LayerController {
return false;
}
for (final transition in stateFrom.transitions) {
if (transition.isDisabled) {
continue;
}
bool valid = true;
for (final condition in transition.conditions) {
if (!condition.evaluate(inputValues)) {
@ -84,9 +93,32 @@ class LayerController {
break;
}
}
if (valid && stateFrom is AnimationState && transition.enableExitTime) {
var fromAnimation = stateFrom.animation!;
if (_animationInstance != null &&
fromAnimation == _animationInstance!.animation) {
var lastTime = _animationInstance!.lastTotalTime;
var time = _animationInstance!.totalTime;
var exitTime = transition.exitTimeSeconds(stateFrom);
if (exitTime < fromAnimation.durationSeconds) {
exitTime += (lastTime / fromAnimation.durationSeconds).floor() *
fromAnimation.durationSeconds;
}
if (time < exitTime) {
valid = false;
}
}
}
if (valid && _changeState(transition.stateTo)) {
_transition = transition;
_stateFrom = stateFrom;
if (transition.pauseOnExit &&
transition.enableExitTime &&
_animationInstance != null) {
_animationInstance!.time = transition.exitTimeSeconds(stateFrom);
}
if (_mix != 0) {
_holdAnimationFrom = transition.pauseOnExit;
_animationInstanceFrom = _animationInstance;
}
if (_currentState is AnimationState) {

View File

@ -1,4 +1,7 @@
class StateTransitionFlags {
static const int disabled = 1 << 0;
static const int durationIsPercentage = 1 << 1;
static const int enableExitTime = 1 << 2;
static const int exitTimeIsPercentage = 1 << 3;
static const int pauseOnExit = 1 << 4;
}