Syncing to latest rive features including click events from rive.

This commit is contained in:
Luigi Rosso
2022-05-11 11:21:42 -07:00
committed by Luigi Rosso
parent 2fe38ca337
commit 92c7af2343
57 changed files with 1695 additions and 390 deletions

View File

@ -46,15 +46,15 @@ abstract class SMIInput<T> {
/// method to change the input value, but calling change(true) is totally
/// valid.
bool change(T value) {
if (controller.inputValues[id] == value) {
if (controller.getInputValue(id) == value) {
return false;
}
controller.inputValues[id] = value;
controller.setInputValue(id, value);
controller.isActive = true;
return true;
}
T get value => controller.inputValues[id] as T;
T get value => controller.getInputValue(id) as T;
set value(T newValue) => change(newValue);
bool _is<K>() {
@ -72,7 +72,7 @@ class SMIBool extends SMIInput<bool> {
SMIType.boolean,
controller,
) {
controller.inputValues[id] = input.value;
controller.setInputValue(id, input.value);
}
}
@ -86,7 +86,7 @@ class SMINumber extends SMIInput<double> {
SMIType.number,
controller,
) {
controller.inputValues[id] = input.value;
controller.setInputValue(id, input.value);
}
}
@ -100,7 +100,7 @@ class SMITrigger extends SMIInput<bool> {
SMIType.trigger,
controller,
) {
controller.inputValues[id] = false;
controller.setInputValue(id, false);
}
void fire() => change(true);

View File

@ -4,8 +4,8 @@ import 'package:flutter/foundation.dart';
import 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exception.dart';
export 'dart:typed_data';
export 'package:flutter/foundation.dart';
export 'package:flutter/foundation.dart';
export 'package:rive/src/animation_list.dart';
export 'package:rive/src/asset_list.dart';
export 'package:rive/src/blend_animations.dart';
@ -17,10 +17,12 @@ export 'package:rive/src/core/importers/keyed_object_importer.dart';
export 'package:rive/src/core/importers/keyed_property_importer.dart';
export 'package:rive/src/core/importers/layer_state_importer.dart';
export 'package:rive/src/core/importers/linear_animation_importer.dart';
export 'package:rive/src/core/importers/state_machine_event_importer.dart';
export 'package:rive/src/core/importers/state_machine_importer.dart';
export 'package:rive/src/core/importers/state_machine_layer_importer.dart';
export 'package:rive/src/core/importers/state_transition_importer.dart';
export 'package:rive/src/generated/rive_core_context.dart';
export 'package:rive/src/input_changes.dart';
export 'package:rive/src/runtime_artboard.dart';
export 'package:rive/src/state_machine_components.dart';
export 'package:rive/src/state_transition_conditions.dart';

View File

@ -0,0 +1,14 @@
import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
import 'package:rive/src/rive_core/animation/event_input_change.dart';
import 'package:rive/src/rive_core/animation/state_machine_event.dart';
class StateMachineEventImporter extends ArtboardImportStackObject {
final StateMachineEvent event;
StateMachineEventImporter(this.event);
void addInputChange(EventInputChange change) {
// Other state machine importers do this, do we really need it?
// event.context.addObject(change);
event.internalAddInputChange(change);
}
}

View File

@ -0,0 +1,44 @@
/// Core automatically generated
/// lib/src/generated/animation/event_bool_change_base.dart.
/// Do not modify manually.
import 'package:rive/src/rive_core/animation/event_input_change.dart';
abstract class EventBoolChangeBase extends EventInputChange {
static const int typeKey = 117;
@override
int get coreType => EventBoolChangeBase.typeKey;
@override
Set<int> get coreTypes =>
{EventBoolChangeBase.typeKey, EventInputChangeBase.typeKey};
/// --------------------------------------------------------------------------
/// Value field with key 228.
static const int valueInitialValue = 1;
int _value = valueInitialValue;
static const int valuePropertyKey = 228;
/// Value to set the input to when the event occurs.
int get value => _value;
/// Change the [_value] field value.
/// [valueChanged] will be invoked only if the field's value has changed.
set value(int value) {
if (_value == value) {
return;
}
int from = _value;
_value = value;
if (hasValidated) {
valueChanged(from, value);
}
}
void valueChanged(int from, int to);
@override
void copy(covariant EventBoolChangeBase source) {
super.copy(source);
_value = source._value;
}
}

View File

@ -0,0 +1,42 @@
/// Core automatically generated
/// lib/src/generated/animation/event_input_change_base.dart.
/// Do not modify manually.
import 'package:rive/src/core/core.dart';
abstract class EventInputChangeBase<T extends CoreContext> extends Core<T> {
static const int typeKey = 116;
@override
int get coreType => EventInputChangeBase.typeKey;
@override
Set<int> get coreTypes => {EventInputChangeBase.typeKey};
/// --------------------------------------------------------------------------
/// InputId field with key 227.
static const int inputIdInitialValue = -1;
int _inputId = inputIdInitialValue;
static const int inputIdPropertyKey = 227;
/// Id of the StateMachineInput referenced.
int get inputId => _inputId;
/// Change the [_inputId] field value.
/// [inputIdChanged] will be invoked only if the field's value has changed.
set inputId(int value) {
if (_inputId == value) {
return;
}
int from = _inputId;
_inputId = value;
if (hasValidated) {
inputIdChanged(from, value);
}
}
void inputIdChanged(int from, int to);
@override
void copy(covariant EventInputChangeBase source) {
_inputId = source._inputId;
}
}

View File

@ -0,0 +1,44 @@
/// Core automatically generated
/// lib/src/generated/animation/event_number_change_base.dart.
/// Do not modify manually.
import 'package:rive/src/rive_core/animation/event_input_change.dart';
abstract class EventNumberChangeBase extends EventInputChange {
static const int typeKey = 118;
@override
int get coreType => EventNumberChangeBase.typeKey;
@override
Set<int> get coreTypes =>
{EventNumberChangeBase.typeKey, EventInputChangeBase.typeKey};
/// --------------------------------------------------------------------------
/// Value field with key 229.
static const double valueInitialValue = 0;
double _value = valueInitialValue;
static const int valuePropertyKey = 229;
/// Value to set the input to when the event occurs.
double get value => _value;
/// Change the [_value] field value.
/// [valueChanged] will be invoked only if the field's value has changed.
set value(double value) {
if (_value == value) {
return;
}
double from = _value;
_value = value;
if (hasValidated) {
valueChanged(from, value);
}
}
void valueChanged(double from, double to);
@override
void copy(covariant EventNumberChangeBase source) {
super.copy(source);
_value = source._value;
}
}

View File

@ -0,0 +1,14 @@
/// Core automatically generated
/// lib/src/generated/animation/event_trigger_change_base.dart.
/// Do not modify manually.
import 'package:rive/src/rive_core/animation/event_input_change.dart';
abstract class EventTriggerChangeBase extends EventInputChange {
static const int typeKey = 115;
@override
int get coreType => EventTriggerChangeBase.typeKey;
@override
Set<int> get coreTypes =>
{EventTriggerChangeBase.typeKey, EventInputChangeBase.typeKey};
}

View File

@ -0,0 +1,70 @@
/// Core automatically generated
/// lib/src/generated/animation/state_machine_event_base.dart.
/// Do not modify manually.
import 'package:rive/src/rive_core/animation/state_machine_component.dart';
abstract class StateMachineEventBase extends StateMachineComponent {
static const int typeKey = 114;
@override
int get coreType => StateMachineEventBase.typeKey;
@override
Set<int> get coreTypes =>
{StateMachineEventBase.typeKey, StateMachineComponentBase.typeKey};
/// --------------------------------------------------------------------------
/// TargetId field with key 224.
static const int targetIdInitialValue = 0;
int _targetId = targetIdInitialValue;
static const int targetIdPropertyKey = 224;
/// Identifier used to track the object use as a target fo this event.
int get targetId => _targetId;
/// Change the [_targetId] field value.
/// [targetIdChanged] will be invoked only if the field's value has changed.
set targetId(int value) {
if (_targetId == value) {
return;
}
int from = _targetId;
_targetId = value;
if (hasValidated) {
targetIdChanged(from, value);
}
}
void targetIdChanged(int from, int to);
/// --------------------------------------------------------------------------
/// EventTypeValue field with key 225.
static const int eventTypeValueInitialValue = 0;
int _eventTypeValue = eventTypeValueInitialValue;
static const int eventTypeValuePropertyKey = 225;
/// Event type (hover, click, etc).
int get eventTypeValue => _eventTypeValue;
/// Change the [_eventTypeValue] field value.
/// [eventTypeValueChanged] will be invoked only if the field's value has
/// changed.
set eventTypeValue(int value) {
if (_eventTypeValue == value) {
return;
}
int from = _eventTypeValue;
_eventTypeValue = value;
if (hasValidated) {
eventTypeValueChanged(from, value);
}
}
void eventTypeValueChanged(int from, int to);
@override
void copy(covariant StateMachineEventBase source) {
super.copy(source);
_targetId = source._targetId;
_eventTypeValue = source._eventTypeValue;
}
}

View File

@ -8,6 +8,7 @@ import 'package:rive/src/core/field_types/core_string_type.dart';
import 'package:rive/src/core/field_types/core_uint_type.dart';
import 'package:rive/src/generated/animation/blend_animation_base.dart';
import 'package:rive/src/generated/animation/cubic_interpolator_base.dart';
import 'package:rive/src/generated/animation/event_input_change_base.dart';
import 'package:rive/src/generated/animation/keyframe_base.dart';
import 'package:rive/src/generated/animation/nested_linear_animation_base.dart';
import 'package:rive/src/generated/animation/state_machine_component_base.dart';
@ -40,6 +41,9 @@ import 'package:rive/src/rive_core/animation/blend_state_direct.dart';
import 'package:rive/src/rive_core/animation/blend_state_transition.dart';
import 'package:rive/src/rive_core/animation/cubic_interpolator.dart';
import 'package:rive/src/rive_core/animation/entry_state.dart';
import 'package:rive/src/rive_core/animation/event_bool_change.dart';
import 'package:rive/src/rive_core/animation/event_number_change.dart';
import 'package:rive/src/rive_core/animation/event_trigger_change.dart';
import 'package:rive/src/rive_core/animation/exit_state.dart';
import 'package:rive/src/rive_core/animation/keyed_object.dart';
import 'package:rive/src/rive_core/animation/keyed_property.dart';
@ -53,6 +57,7 @@ import 'package:rive/src/rive_core/animation/nested_simple_animation.dart';
import 'package:rive/src/rive_core/animation/nested_state_machine.dart';
import 'package:rive/src/rive_core/animation/state_machine.dart';
import 'package:rive/src/rive_core/animation/state_machine_bool.dart';
import 'package:rive/src/rive_core/animation/state_machine_event.dart';
import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
import 'package:rive/src/rive_core/animation/state_machine_number.dart';
import 'package:rive/src/rive_core/animation/state_machine_trigger.dart';
@ -127,6 +132,8 @@ class RiveCoreContext {
return Node();
case NestedArtboardBase.typeKey:
return NestedArtboard();
case EventNumberChangeBase.typeKey:
return EventNumberChange();
case AnimationBase.typeKey:
return Animation();
case LinearAnimationBase.typeKey:
@ -135,6 +142,8 @@ class RiveCoreContext {
return NestedSimpleAnimation();
case AnimationStateBase.typeKey:
return AnimationState();
case StateMachineEventBase.typeKey:
return StateMachineEvent();
case KeyedObjectBase.typeKey:
return KeyedObject();
case BlendAnimationDirectBase.typeKey:
@ -151,8 +160,12 @@ class RiveCoreContext {
return KeyFrameBool();
case TransitionNumberConditionBase.typeKey:
return TransitionNumberCondition();
case EventBoolChangeBase.typeKey:
return EventBoolChange();
case AnyStateBase.typeKey:
return AnyState();
case EventTriggerChangeBase.typeKey:
return EventTriggerChange();
case StateMachineLayerBase.typeKey:
return StateMachineLayer();
case CubicInterpolatorBase.typeKey:
@ -444,6 +457,16 @@ class RiveCoreContext {
object.animationId = value;
}
break;
case EventInputChangeBase.inputIdPropertyKey:
if (object is EventInputChangeBase && value is int) {
object.inputId = value;
}
break;
case EventNumberChangeBase.valuePropertyKey:
if (object is EventNumberChangeBase && value is double) {
object.value = value;
}
break;
case AnimationBase.namePropertyKey:
if (object is AnimationBase && value is String) {
object.name = value;
@ -504,6 +527,21 @@ class RiveCoreContext {
object.animationId = value;
}
break;
case StateMachineComponentBase.namePropertyKey:
if (object is StateMachineComponentBase && value is String) {
object.name = value;
}
break;
case StateMachineEventBase.targetIdPropertyKey:
if (object is StateMachineEventBase && value is int) {
object.targetId = value;
}
break;
case StateMachineEventBase.eventTypeValuePropertyKey:
if (object is StateMachineEventBase && value is int) {
object.eventTypeValue = value;
}
break;
case KeyedObjectBase.objectIdPropertyKey:
if (object is KeyedObjectBase && value is int) {
object.objectId = value;
@ -519,11 +557,6 @@ class RiveCoreContext {
object.inputId = value;
}
break;
case StateMachineComponentBase.namePropertyKey:
if (object is StateMachineComponentBase && value is String) {
object.name = value;
}
break;
case StateMachineNumberBase.valuePropertyKey:
if (object is StateMachineNumberBase && value is double) {
object.value = value;
@ -574,6 +607,11 @@ class RiveCoreContext {
object.value = value;
}
break;
case EventBoolChangeBase.valuePropertyKey:
if (object is EventBoolChangeBase && value is int) {
object.value = value;
}
break;
case CubicInterpolatorBase.x1PropertyKey:
if (object is CubicInterpolatorBase && value is double) {
object.x1 = value;
@ -1108,12 +1146,15 @@ class RiveCoreContext {
case DrawableBase.drawableFlagsPropertyKey:
case NestedArtboardBase.artboardIdPropertyKey:
case NestedAnimationBase.animationIdPropertyKey:
case EventInputChangeBase.inputIdPropertyKey:
case LinearAnimationBase.fpsPropertyKey:
case LinearAnimationBase.durationPropertyKey:
case LinearAnimationBase.loopValuePropertyKey:
case LinearAnimationBase.workStartPropertyKey:
case LinearAnimationBase.workEndPropertyKey:
case AnimationStateBase.animationIdPropertyKey:
case StateMachineEventBase.targetIdPropertyKey:
case StateMachineEventBase.eventTypeValuePropertyKey:
case KeyedObjectBase.objectIdPropertyKey:
case BlendAnimationBase.animationIdPropertyKey:
case BlendAnimationDirectBase.inputIdPropertyKey:
@ -1124,6 +1165,7 @@ class RiveCoreContext {
case KeyFrameBase.interpolatorIdPropertyKey:
case KeyFrameIdBase.valuePropertyKey:
case TransitionValueConditionBase.opValuePropertyKey:
case EventBoolChangeBase.valuePropertyKey:
case StateTransitionBase.stateToIdPropertyKey:
case StateTransitionBase.flagsPropertyKey:
case StateTransitionBase.durationPropertyKey:
@ -1163,6 +1205,7 @@ class RiveCoreContext {
case TransformComponentBase.scaleYPropertyKey:
case NodeBase.xPropertyKey:
case NodeBase.yPropertyKey:
case EventNumberChangeBase.valuePropertyKey:
case LinearAnimationBase.speedPropertyKey:
case NestedLinearAnimationBase.mixPropertyKey:
case NestedSimpleAnimationBase.speedPropertyKey:
@ -1306,6 +1349,8 @@ class RiveCoreContext {
return (object as NestedArtboardBase).artboardId;
case NestedAnimationBase.animationIdPropertyKey:
return (object as NestedAnimationBase).animationId;
case EventInputChangeBase.inputIdPropertyKey:
return (object as EventInputChangeBase).inputId;
case LinearAnimationBase.fpsPropertyKey:
return (object as LinearAnimationBase).fps;
case LinearAnimationBase.durationPropertyKey:
@ -1318,6 +1363,10 @@ class RiveCoreContext {
return (object as LinearAnimationBase).workEnd;
case AnimationStateBase.animationIdPropertyKey:
return (object as AnimationStateBase).animationId;
case StateMachineEventBase.targetIdPropertyKey:
return (object as StateMachineEventBase).targetId;
case StateMachineEventBase.eventTypeValuePropertyKey:
return (object as StateMachineEventBase).eventTypeValue;
case KeyedObjectBase.objectIdPropertyKey:
return (object as KeyedObjectBase).objectId;
case BlendAnimationBase.animationIdPropertyKey:
@ -1338,6 +1387,8 @@ class RiveCoreContext {
return (object as KeyFrameIdBase).value;
case TransitionValueConditionBase.opValuePropertyKey:
return (object as TransitionValueConditionBase).opValue;
case EventBoolChangeBase.valuePropertyKey:
return (object as EventBoolChangeBase).value;
case StateTransitionBase.stateToIdPropertyKey:
return (object as StateTransitionBase).stateToId;
case StateTransitionBase.flagsPropertyKey:
@ -1420,6 +1471,8 @@ class RiveCoreContext {
return (object as NodeBase).x;
case NodeBase.yPropertyKey:
return (object as NodeBase).y;
case EventNumberChangeBase.valuePropertyKey:
return (object as EventNumberChangeBase).value;
case LinearAnimationBase.speedPropertyKey:
return (object as LinearAnimationBase).speed;
case NestedLinearAnimationBase.mixPropertyKey:
@ -1718,6 +1771,11 @@ class RiveCoreContext {
object.animationId = value;
}
break;
case EventInputChangeBase.inputIdPropertyKey:
if (object is EventInputChangeBase) {
object.inputId = value;
}
break;
case LinearAnimationBase.fpsPropertyKey:
if (object is LinearAnimationBase) {
object.fps = value;
@ -1748,6 +1806,16 @@ class RiveCoreContext {
object.animationId = value;
}
break;
case StateMachineEventBase.targetIdPropertyKey:
if (object is StateMachineEventBase) {
object.targetId = value;
}
break;
case StateMachineEventBase.eventTypeValuePropertyKey:
if (object is StateMachineEventBase) {
object.eventTypeValue = value;
}
break;
case KeyedObjectBase.objectIdPropertyKey:
if (object is KeyedObjectBase) {
object.objectId = value;
@ -1798,6 +1866,11 @@ class RiveCoreContext {
object.opValue = value;
}
break;
case EventBoolChangeBase.valuePropertyKey:
if (object is EventBoolChangeBase) {
object.value = value;
}
break;
case StateTransitionBase.stateToIdPropertyKey:
if (object is StateTransitionBase) {
object.stateToId = value;
@ -1993,6 +2066,11 @@ class RiveCoreContext {
object.y = value;
}
break;
case EventNumberChangeBase.valuePropertyKey:
if (object is EventNumberChangeBase) {
object.value = value;
}
break;
case LinearAnimationBase.speedPropertyKey:
if (object is LinearAnimationBase) {
object.speed = value;

View File

@ -0,0 +1,20 @@
import 'dart:collection';
import 'package:rive/src/rive_core/animation/event_input_change.dart';
class InputChanges extends ListBase<EventInputChange> {
final List<EventInputChange?> _values = [];
List<EventInputChange> get values => _values.cast<EventInputChange>();
@override
int get length => _values.length;
@override
set length(int value) => _values.length = value;
@override
EventInputChange operator [](int index) => _values[index]!;
@override
void operator []=(int index, EventInputChange value) =>
_values[index] = value;
}

View File

@ -1,8 +1,7 @@
import 'dart:collection';
import 'package:rive/src/rive_core/animation/blend_animation_1d.dart';
import 'package:rive/src/rive_core/animation/blend_state_1d.dart';
import 'package:rive/src/rive_core/animation/blend_state_instance.dart';
import 'package:rive/src/rive_core/state_machine_controller.dart';
/// [BlendState1D] mixing logic that runs inside the [StateMachine].
class BlendState1DInstance
@ -41,9 +40,10 @@ class BlendState1DInstance
BlendStateAnimationInstance<BlendAnimation1D>? _to;
@override
void advance(double seconds, HashMap<int, dynamic> inputValues) {
super.advance(seconds, inputValues);
dynamic inputValue = inputValues[(state as BlendState1D).inputId];
void advance(double seconds, StateMachineController controller) {
super.advance(seconds, controller);
dynamic inputValue =
controller.getInputValue((state as BlendState1D).inputId);
var value = (inputValue is double
? inputValue
: (state as BlendState1D).input?.value) ??

View File

@ -1,8 +1,7 @@
import 'dart:collection';
import 'package:rive/src/rive_core/animation/blend_animation_direct.dart';
import 'package:rive/src/rive_core/animation/blend_state_direct.dart';
import 'package:rive/src/rive_core/animation/blend_state_instance.dart';
import 'package:rive/src/rive_core/state_machine_controller.dart';
/// [BlendStateDirect] mixing logic that runs inside the [StateMachine].
class BlendStateDirectInstance
@ -10,10 +9,11 @@ class BlendStateDirectInstance
BlendStateDirectInstance(BlendStateDirect state) : super(state);
@override
void advance(double seconds, HashMap<int, dynamic> inputValues) {
super.advance(seconds, inputValues);
void advance(double seconds, StateMachineController controller) {
super.advance(seconds, controller);
for (final animation in animationInstances) {
dynamic inputValue = inputValues[animation.blendAnimation.inputId];
dynamic inputValue =
controller.getInputValue(animation.blendAnimation.inputId);
var value = (inputValue is double
? inputValue
: animation.blendAnimation.input?.value) ??

View File

@ -1,10 +1,9 @@
import 'dart:collection';
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/blend_animation.dart';
import 'package:rive/src/rive_core/animation/blend_state.dart';
import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
import 'package:rive/src/rive_core/animation/state_instance.dart';
import 'package:rive/src/rive_core/state_machine_controller.dart';
/// Individual animation in a blend state instance.
class BlendStateAnimationInstance<T extends BlendAnimation> {
@ -34,7 +33,7 @@ abstract class BlendStateInstance<T extends BlendState<K>,
@mustCallSuper
@override
void advance(double seconds, HashMap<int, dynamic> inputValues) {
void advance(double seconds, StateMachineController controller) {
_keepGoing = false;
// Advance all the animations in the blend state
for (final animation in animationInstances) {

View File

@ -0,0 +1,31 @@
import 'package:rive/src/generated/animation/event_bool_change_base.dart';
import 'package:rive/src/rive_core/state_machine_controller.dart';
export 'package:rive/src/generated/animation/event_bool_change_base.dart';
class EventBoolChange extends EventBoolChangeBase {
@override
void valueChanged(int from, int to) {}
@override
void perform(StateMachineController controller) {
switch (value) {
case 0:
controller.setInputValue(inputId, false);
break;
case 1:
controller.setInputValue(inputId, true);
break;
default:
// Toggle
dynamic existing = controller.getInputValue(inputId);
if (existing is bool) {
controller.setInputValue(inputId, !existing);
} else {
controller.setInputValue(inputId, true);
}
break;
}
}
}

View File

@ -0,0 +1,61 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/generated/animation/event_input_change_base.dart';
import 'package:rive/src/rive_core/animation/state_machine.dart';
import 'package:rive/src/rive_core/animation/state_machine_event.dart';
import 'package:rive/src/rive_core/animation/state_machine_input.dart';
import 'package:rive/src/rive_core/state_machine_controller.dart';
export 'package:rive/src/generated/animation/event_input_change_base.dart';
abstract class EventInputChange extends EventInputChangeBase {
StateMachineInput _input = StateMachineInput.unknown;
StateMachineInput get input => _input;
set input(StateMachineInput value) {
if (value == _input) {
return;
}
_input = value;
inputId = _input.id;
}
@override
void inputIdChanged(int from, int to) {
input = context.resolveWithDefault(to, StateMachineInput.unknown);
}
@override
void onAdded() {}
@override
void onAddedDirty() {
input = context.resolveWithDefault(inputId, StateMachineInput.unknown);
}
/// Make the change to the input values.
void perform(StateMachineController controller);
@override
bool import(ImportStack importStack) {
var importer = importStack
.latest<StateMachineEventImporter>(StateMachineEventBase.typeKey);
if (importer == null) {
return false;
}
importer.addInputChange(this);
var stateMachineImporter =
importStack.latest<StateMachineImporter>(StateMachineBase.typeKey);
if (stateMachineImporter == null) {
return false;
}
if (inputId >= 0 && inputId < stateMachineImporter.machine.inputs.length) {
var found = stateMachineImporter.machine.inputs[inputId];
_input = found;
inputId = found.id;
}
return super.import(importStack);
}
}

View File

@ -0,0 +1,13 @@
import 'package:rive/src/generated/animation/event_number_change_base.dart';
import 'package:rive/src/rive_core/state_machine_controller.dart';
export 'package:rive/src/generated/animation/event_number_change_base.dart';
class EventNumberChange extends EventNumberChangeBase {
@override
void valueChanged(double from, double to) {}
@override
void perform(StateMachineController controller) =>
controller.setInputValue(inputId, value);
}

View File

@ -0,0 +1,10 @@
import 'package:rive/src/generated/animation/event_trigger_change_base.dart';
import 'package:rive/src/rive_core/state_machine_controller.dart';
export 'package:rive/src/generated/animation/event_trigger_change_base.dart';
class EventTriggerChange extends EventTriggerChangeBase {
@override
void perform(StateMachineController controller) =>
controller.setInputValue(inputId, true);
}

View File

@ -1,7 +1,6 @@
import 'dart:collection';
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/layer_state.dart';
import 'package:rive/src/rive_core/state_machine_controller.dart';
/// Represents the instance of a [LayerState] which is being used in a
/// [LayerController] of a [StateMachineController]. Abstract representation of
@ -12,7 +11,7 @@ abstract class StateInstance {
StateInstance(this.state);
void advance(double seconds, HashMap<int, dynamic> inputValues);
void advance(double seconds, StateMachineController controller);
void apply(CoreContext core, double mix);
bool get keepGoing;
@ -25,7 +24,7 @@ abstract class StateInstance {
class SystemStateInstance extends StateInstance {
SystemStateInstance(LayerState state) : super(state);
@override
void advance(double seconds, HashMap<int, dynamic> inputValues) {}
void advance(double seconds, StateMachineController controller) {}
@override
void apply(CoreContext core, double mix) {}

View File

@ -1,5 +1,6 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/generated/animation/state_machine_base.dart';
import 'package:rive/src/rive_core/animation/state_machine_event.dart';
import 'package:rive/src/rive_core/animation/state_machine_input.dart';
import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
import 'package:rive/src/rive_core/artboard.dart';
@ -11,6 +12,8 @@ class StateMachine extends StateMachineBase {
StateMachineComponents<StateMachineInput>();
final StateMachineComponents<StateMachineLayer> layers =
StateMachineComponents<StateMachineLayer>();
final StateMachineComponents<StateMachineEvent> events =
StateMachineComponents<StateMachineEvent>();
@override
bool import(ImportStack stack) {

View File

@ -7,7 +7,4 @@ class StateMachineBool extends StateMachineBoolBase {
@override
bool isValidType<T>() => T == bool;
@override
dynamic get controllerValue => value;
}

View File

@ -7,7 +7,8 @@ import 'package:rive/src/rive_core/animation/state_machine.dart';
export 'package:rive/src/generated/animation/state_machine_component_base.dart';
/// Implemented by state machine inputs and layers.
abstract class StateMachineComponent extends StateMachineComponentBase {
abstract class StateMachineComponent
extends StateMachineComponentBase<RuntimeArtboard> {
StateMachine? _stateMachine;
StateMachine? get stateMachine => _stateMachine;
set stateMachine(StateMachine? machine) {

View File

@ -0,0 +1,70 @@
import 'dart:collection';
import 'package:rive/src/core/core.dart';
import 'package:rive/src/generated/animation/state_machine_event_base.dart';
import 'package:rive/src/rive_core/animation/event_input_change.dart';
import 'package:rive/src/rive_core/animation/state_machine.dart';
import 'package:rive/src/rive_core/animation/state_machine_component.dart';
import 'package:rive/src/rive_core/node.dart';
import 'package:rive/src/rive_core/state_machine_controller.dart';
export 'package:rive/src/generated/animation/state_machine_event_base.dart';
enum EventType { enter, exit, down, up }
class StateMachineEvent extends StateMachineEventBase {
final InputChanges inputChanges = InputChanges();
Node? _target;
Node? get target => _target;
set target(Node? value) {
if (_target == value) {
return;
}
_target = value;
targetId = _target?.id ?? Core.missingId;
}
@override
String get name =>
super.name.isEmpty ? (_target?.name ?? 'Event') : super.name;
@override
void eventTypeValueChanged(int from, int to) {}
EventType get eventType => EventType.values[eventTypeValue];
set eventType(EventType value) => eventTypeValue = value.index;
@override
ListBase<StateMachineComponent> machineComponentList(StateMachine machine) =>
machine.events;
@override
void targetIdChanged(int from, int to) => target = context.resolve(to);
/// Called by rive_core to add an [EventInputChange] to this
/// [StateMachineEvent]. This should be @internal when it's supported.
bool internalAddInputChange(EventInputChange change) {
if (inputChanges.contains(change)) {
return false;
}
inputChanges.add(change);
return true;
}
/// Called by rive_core to remove an [EventInputChange] from this
/// [StateMachineEvent]. This should be @internal when it's supported.
bool internalRemoveInputChange(EventInputChange change) {
var removed = inputChanges.remove(change);
return removed;
}
void performChanges(StateMachineController controller) {
for (final change in inputChanges) {
change.perform(controller);
}
}
}

View File

@ -1,3 +1,5 @@
// ignore_for_file: use_setters_to_change_properties
import 'dart:collection';
import 'package:rive/src/generated/animation/state_machine_input_base.dart';
@ -14,7 +16,6 @@ abstract class StateMachineInput extends StateMachineInputBase {
machine.inputs;
bool isValidType<T>() => false;
dynamic get controllerValue => null;
}
class _StateMachineUnknownInput extends StateMachineInput {}

View File

@ -4,10 +4,4 @@ export 'package:rive/src/generated/animation/state_machine_number_base.dart';
class StateMachineNumber extends StateMachineNumberBase {
@override
void valueChanged(double from, double to) {}
@override
bool isValidType<T>() => T == double;
@override
dynamic get controllerValue => value;
}

View File

@ -2,20 +2,8 @@ import 'package:rive/src/generated/animation/state_machine_trigger_base.dart';
export 'package:rive/src/generated/animation/state_machine_trigger_base.dart';
class StateMachineTrigger extends StateMachineTriggerBase {
bool _triggered = false;
bool get triggered => _triggered;
void fire() {
_triggered = true;
}
void reset() {
_triggered = false;
}
void fire() {}
@override
bool isValidType<T>() => T == bool;
@override
dynamic get controllerValue => _triggered;
}

View File

@ -18,11 +18,6 @@ class TransitionTriggerCondition extends TransitionTriggerConditionBase {
if (providedValue is bool && providedValue) {
return true;
}
var triggerInput = input as StateMachineTrigger;
if (triggerInput.triggered) {
return true;
}
return false;
}
}

View File

@ -255,7 +255,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
Vec2D renderTranslation(Vec2D worldTranslation) {
final wt = originWorld;
return Vec2D.add(Vec2D(), worldTranslation, wt);
return worldTranslation + wt;
}
/// Adds a component to the artboard. Good place for the artboard to check for

View File

@ -17,8 +17,20 @@ abstract class FileAsset extends FileAssetBase {
if (backboardImporter == null) {
return false;
}
backboardImporter.addFileAsset(this);
// When we paste a FileAssetReference (e.g an Image) into a file, we want to
// prevent the asset being readded if there is already a copy of it in the
// file. We check if an asset with the same assetId already exists, and if
// so add it to the backboard importer, any fileAssetReferences will map
// to the existing asset instead.
for (final object in backboardImporter.backboard.assets) {
if (object is FileAsset && object.assetId == assetId) {
backboardImporter.addFileAsset(object);
return false;
}
}
backboardImporter.addFileAsset(this);
return super.import(stack);
}

View File

@ -17,9 +17,14 @@ class ImageAsset extends ImageAssetBase {
@visibleForTesting
ImageAsset.fromTestImage(this._image);
@visibleForTesting
set image(ui.Image? image) {
_image = image;
}
@override
Future<void> decode(Uint8List bytes) {
var completer = Completer<void>();
final completer = Completer<void>();
ui.decodeImageFromList(bytes, (value) {
_image = value;
completer.complete();

View File

@ -75,8 +75,6 @@ class Bone extends BoneBase {
Vec2D get tipWorldTranslation => getTipWorldTranslation(worldTransform);
Vec2D getTipWorldTranslation(Mat2D worldTransform) {
var tip = Vec2D();
Vec2D.transformMat2D(tip, Vec2D.fromValues(length, 0), worldTransform);
return tip;
return worldTransform.mapXY(length, 0);
}
}

View File

@ -23,8 +23,10 @@ class Skin extends SkinBase {
void onDirty(int mask) {
// When the skin is dirty the deformed skinnable will need to regenerate its
// drawing commands.
if (parent is Skinnable) {
(parent as Skinnable).markSkinDirty();
}
}
@override
void update(int dirt) {

View File

@ -41,8 +41,8 @@ class Weight extends WeightBase {
tx += boneTransforms[startBoneTransformIndex++] * normalizedWeight;
ty += boneTransforms[startBoneTransformIndex++] * normalizedWeight;
}
result[0] = xx * rx + yx * ry + tx;
result[1] = xy * rx + yy * ry + ty;
result.x = xx * rx + yx * ry + tx;
result.y = xy * rx + yy * ry + ty;
}
static int encodedWeightValue(int index, int data) {

View File

@ -137,6 +137,8 @@ abstract class WeightedVertex {
int _getRawWeight(int weightIndex) => (weights >> (weightIndex * 8)) & 0xFF;
double getWeightAtIndex(int weightIndex) => _getRawWeight(weightIndex) / 255;
double getWeight(int tendonIndex) {
for (int i = 0; i < 4; i++) {
if (getTendon(i) == tendonIndex + 1) {

View File

@ -20,8 +20,8 @@ class DistanceConstraint extends DistanceConstraintBase {
var targetTranslation = target!.worldTranslation;
var ourTranslation = component.worldTranslation;
var toTarget = Vec2D.subtract(Vec2D(), ourTranslation, targetTranslation);
var currentDistance = Vec2D.length(toTarget);
var toTarget = ourTranslation - targetTranslation;
var currentDistance = toTarget.length();
switch (mode) {
case DistanceConstraintMode.closer:
if (currentDistance < distance) {
@ -45,10 +45,10 @@ class DistanceConstraint extends DistanceConstraintBase {
var world = component.worldTransform;
var position = Vec2D.add(Vec2D(), targetTranslation, toTarget);
var position = targetTranslation + toTarget;
Vec2D.lerp(position, ourTranslation, position, strength);
world[4] = position[0];
world[5] = position[1];
world[4] = position.x;
world[5] = position.y;
}
@override

View File

@ -176,11 +176,11 @@ class IKConstraint extends IKConstraintBase {
var pBT = Vec2D.clone(worldTargetTranslation);
// To target in worldspace
var toTarget = Vec2D.subtract(Vec2D(), pBT, pA);
var toTarget = pBT - pA;
// Note this is directional, hence not transformMat2d
Vec2D toTargetLocal = Vec2D.transformMat2(Vec2D(), toTarget, iworld);
var r = atan2(toTargetLocal[1], toTargetLocal[0]);
var r = toTargetLocal.atan2();
_constrainRotation(fk1, r);
fk1.angle = r;
@ -199,21 +199,21 @@ class IKConstraint extends IKConstraintBase {
var pB = b2.tipWorldTranslation;
var pBT = Vec2D.clone(worldTargetTranslation);
Vec2D.transformMat2D(pA, pA, iworld);
Vec2D.transformMat2D(pC, pC, iworld);
Vec2D.transformMat2D(pB, pB, iworld);
Vec2D.transformMat2D(pBT, pBT, iworld);
pA.apply(iworld);
pC.apply(iworld);
pB.apply(iworld);
pBT.apply(iworld);
// http://mathworld.wolfram.com/LawofCosines.html
var av = Vec2D.subtract(Vec2D(), pB, pC);
var a = Vec2D.length(av);
var av = pB - pC;
var a = av.length();
var bv = Vec2D.subtract(Vec2D(), pC, pA);
var b = Vec2D.length(bv);
var bv = pC - pA;
var b = bv.length();
var cv = Vec2D.subtract(Vec2D(), pBT, pA);
var c = Vec2D.length(cv);
var cv = pBT - pA;
var c = cv.length();
if (b == 0 || c == 0) {
// Cannot solve, would cause divide by zero. Usually means one of the two
// bones has a 0/0 scale.
@ -221,6 +221,7 @@ class IKConstraint extends IKConstraintBase {
}
var A = acos(max(-1, min(1, (-a * a + b * b + c * c) / (2 * b * c))));
var C = acos(max(-1, min(1, (a * a + b * b - c * c) / (2 * a * b))));
final cvAngle = cv.atan2();
double r1, r2;
if (b2.parent != b1) {
@ -231,23 +232,23 @@ class IKConstraint extends IKConstraintBase {
pC = firstChild.bone.worldTranslation;
pB = b2.tipWorldTranslation;
var avec = Vec2D.subtract(Vec2D(), pB, pC);
var avec = pB - pC;
var avLocal = Vec2D.transformMat2(Vec2D(), avec, secondChildWorldInverse);
var angleCorrection = -atan2(avLocal[1], avLocal[0]);
var angleCorrection = -avLocal.atan2();
if (invertDirection) {
r1 = atan2(cv[1], cv[0]) - A;
r1 = cvAngle - A;
r2 = -C + pi + angleCorrection;
} else {
r1 = A + atan2(cv[1], cv[0]);
r1 = A + cvAngle;
r2 = C - pi + angleCorrection;
}
} else if (invertDirection) {
r1 = atan2(cv[1], cv[0]) - A;
r1 = cvAngle - A;
r2 = -C + pi;
} else {
r1 = A + atan2(cv[1], cv[0]);
r1 = A + cvAngle;
r2 = C - pi;
}
_constrainRotation(fk1, r1);

View File

@ -27,34 +27,31 @@ class TranslationConstraint extends TranslationConstraintBase {
}
Mat2D.multiply(transformB, inverse, transformB);
}
translationB[0] = transformB[4];
translationB[1] = transformB[5];
translationB.x = transformB[4];
translationB.y = transformB[5];
if (!doesCopy) {
translationB[0] =
destSpace == TransformSpace.local ? 0 : translationA[0];
translationB.x = destSpace == TransformSpace.local ? 0 : translationA.x;
} else {
translationB[0] *= copyFactor;
translationB.x *= copyFactor;
if (offset) {
translationB[0] += component.x;
translationB.x += component.x;
}
}
if (!doesCopyY) {
translationB[1] =
destSpace == TransformSpace.local ? 0 : translationA[1];
translationB.y = destSpace == TransformSpace.local ? 0 : translationA.y;
} else {
translationB[1] *= copyFactorY;
translationB.y *= copyFactorY;
if (offset) {
translationB[1] += component.y;
translationB.y += component.y;
}
}
if (destSpace == TransformSpace.local) {
// Destination space is in parent transform coordinates.
Vec2D.transformMat2D(
translationB, translationB, parentWorld(component));
translationB.apply(parentWorld(component));
}
}
@ -66,29 +63,29 @@ class TranslationConstraint extends TranslationConstraintBase {
return;
}
// Get our target world coordinates in parent local.
Vec2D.transformMat2D(translationB, translationB, invert);
translationB.apply(invert);
}
if (max && translationB[0] > maxValue) {
translationB[0] = maxValue;
if (max && translationB.x > maxValue) {
translationB.x = maxValue;
}
if (min && translationB[0] < minValue) {
translationB[0] = minValue;
if (min && translationB.x < minValue) {
translationB.x = minValue;
}
if (maxY && translationB[1] > maxValueY) {
translationB[1] = maxValueY;
if (maxY && translationB.y > maxValueY) {
translationB.y = maxValueY;
}
if (minY && translationB[1] < minValueY) {
translationB[1] = minValueY;
if (minY && translationB.y < minValueY) {
translationB.y = minValueY;
}
if (clampLocal) {
// Transform back to world.
Vec2D.transformMat2D(translationB, translationB, parentWorld(component));
translationB.apply(parentWorld(component));
}
var ti = 1 - strength;
// Just interpolate world translation
transformA[4] = translationA[0] * ti + translationB[0] * strength;
transformA[5] = translationA[1] * ti + translationB[1] * strength;
transformA[4] = translationA.x * ti + translationB.x * strength;
transformA[5] = translationA.y * ti + translationB.y * strength;
}
}

View File

@ -3,6 +3,32 @@ import 'dart:typed_data';
import 'package:rive/src/rive_core/math/mat2d.dart';
import 'package:rive/src/rive_core/math/vec2d.dart';
class IAABB {
int left, top, right, bottom;
IAABB(int l, int t, int r, int b)
: left = l,
top = t,
right = r,
bottom = b;
IAABB.zero()
: left = 0,
top = 0,
right = 0,
bottom = 0;
int get width => right - left;
int get height => bottom - top;
bool get empty => left >= right || top >= bottom;
IAABB inset(int dx, int dy) =>
IAABB(left + dx, top + dy, right - dx, bottom - dy);
IAABB offset(int dx, int dy) =>
IAABB(left + dx, top + dy, right + dx, bottom + dy);
}
class AABB {
Float32List _buffer;
@ -35,6 +61,14 @@ class AABB {
double get minY => _buffer[1];
double get maxY => _buffer[3];
double get left => _buffer[0];
double get top => _buffer[1];
double get right => _buffer[2];
double get bottom => _buffer[3];
double get centerX => (_buffer[0] + _buffer[2]) * 0.5;
double get centerY => (_buffer[1] + _buffer[3]) * 0.5;
AABB() : _buffer = Float32List.fromList([0.0, 0.0, 0.0, 0.0]);
AABB.clone(AABB a) : _buffer = Float32List.fromList(a.values);
@ -75,16 +109,24 @@ class AABB {
bool get isEmpty => !AABB.isValid(this);
Vec2D includePoint(Vec2D point, Mat2D? transform) {
var transformedPoint = transform == null
? point
: Vec2D.transformMat2D(Vec2D(), point, transform);
var transformedPoint = transform == null ? point : transform * point;
expandToPoint(transformedPoint);
return transformedPoint;
}
AABB inset(double dx, double dy) {
return AABB.fromValues(
_buffer[0] + dx, _buffer[1] + dy, _buffer[2] - dx, _buffer[3] - dy);
}
AABB offset(double dx, double dy) {
return AABB.fromValues(
_buffer[0] + dx, _buffer[1] + dy, _buffer[2] + dx, _buffer[3] + dy);
}
void expandToPoint(Vec2D point) {
var x = point[0];
var y = point[1];
var x = point.x;
var y = point.y;
if (x < _buffer[0]) {
_buffer[0] = x;
}
@ -100,7 +142,7 @@ class AABB {
}
AABB.fromMinMax(Vec2D min, Vec2D max)
: _buffer = Float32List.fromList([min[0], min[1], max[0], max[1]]);
: _buffer = Float32List.fromList([min.x, min.y, max.x, max.y]);
static bool areEqual(AABB a, AABB b) {
return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3];
@ -118,6 +160,11 @@ class AABB {
_buffer[idx] = v;
}
Vec2D center() {
return Vec2D.fromValues(
(this[0] + this[2]) * 0.5, (this[1] + this[3]) * 0.5);
}
static AABB copy(AABB out, AABB a) {
out[0] = a[0];
out[1] = a[1];
@ -126,21 +173,15 @@ class AABB {
return out;
}
static Vec2D center(Vec2D out, AABB a) {
out[0] = (a[0] + a[2]) * 0.5;
out[1] = (a[1] + a[3]) * 0.5;
return out;
}
static Vec2D size(Vec2D out, AABB a) {
out[0] = a[2] - a[0];
out[1] = a[3] - a[1];
out.x = a[2] - a[0];
out.y = a[3] - a[1];
return out;
}
static Vec2D extents(Vec2D out, AABB a) {
out[0] = (a[2] - a[0]) * 0.5;
out[1] = (a[3] - a[1]) * 0.5;
out.x = (a[2] - a[0]) * 0.5;
out.y = (a[3] - a[1]) * 0.5;
return out;
}
@ -158,8 +199,11 @@ class AABB {
return out;
}
static bool contains(AABB a, AABB b) {
return a[0] <= b[0] && a[1] <= b[1] && b[2] <= a[2] && b[3] <= a[3];
bool containsBounds(AABB b) {
return _buffer[0] <= b[0] &&
_buffer[1] <= b[1] &&
b[2] <= _buffer[2] &&
b[3] <= _buffer[3];
}
static bool isValid(AABB a) {
@ -191,14 +235,18 @@ class AABB {
return true;
}
static bool testOverlapPoint(AABB a, Vec2D b) {
var x = b[0];
var y = b[1];
return x >= a[0] && x <= a[2] && y >= a[1] && y <= a[3];
bool contains(Vec2D point) {
return point.x >= _buffer[0] &&
point.x <= _buffer[2] &&
point.y >= _buffer[1] &&
point.y <= _buffer[3];
}
AABB translate(Vec2D vec) => AABB.fromValues(_buffer[0] + vec[0],
_buffer[1] + vec[1], _buffer[2] + vec[0], _buffer[3] + vec[1]);
AABB translate(Vec2D vec) => AABB.fromValues(_buffer[0] + vec.x,
_buffer[1] + vec.y, _buffer[2] + vec.x, _buffer[3] + vec.y);
IAABB round() =>
IAABB(left.round(), top.round(), right.round(), bottom.round());
@override
String toString() {
@ -208,9 +256,9 @@ class AABB {
AABB transform(Mat2D matrix) {
return AABB.fromPoints([
minimum,
Vec2D.fromValues(maximum[0], minimum[1]),
Vec2D.fromValues(maximum.x, minimum.y),
maximum,
Vec2D.fromValues(minimum[0], maximum[1])
Vec2D.fromValues(minimum.x, maximum.y)
], transform: matrix);
}
@ -227,12 +275,10 @@ class AABB {
double maxY = -double.maxFinite;
for (final point in points) {
var p = transform == null
? point
: Vec2D.transformMat2D(Vec2D(), point, transform);
var p = transform == null ? point : transform * point;
double x = p[0];
double y = p[1];
final x = p.x;
final y = p.y;
if (x < minX) {
minX = x;
}

View File

@ -0,0 +1,317 @@
import 'dart:math';
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/path_types.dart';
import 'package:rive/src/rive_core/math/vec2d.dart';
import 'package:rive/src/rive_core/shapes/path.dart';
class HitTester implements PathInterface {
final List<int> _windings;
double _firstX, _firstY;
double _prevX, _prevY;
final double _offsetX, _offsetY;
final int _iwidth;
final double _height;
final double _tolerance;
bool _expectsMove;
HitTester(IAABB area, [double tolerance = 0.25])
: _windings =
List<int>.filled(area.width * area.height, 0, growable: false),
_firstX = 0,
_firstY = 0,
_prevX = 0,
_prevY = 0,
_offsetX = area.left.toDouble(),
_offsetY = area.top.toDouble(),
_iwidth = area.width,
_height = area.height.toDouble(),
_tolerance = tolerance,
_expectsMove = true;
void _appendLine(
double x0, double y0, double x1, double y1, double slope, int winding) {
assert(winding == 1 || winding == -1);
// we round to see which pixel centers we cross
final int top = y0.round();
final int bottom = y1.round();
if (top == bottom) {
return;
}
assert(top < bottom);
assert(top >= 0);
assert(bottom <= _height);
// we add 0.5 at the end to pre-round the values
double x = x0 + slope * (top - y0 + 0.5) + 0.5;
// start on the correct row
int index = top * _iwidth;
for (int y = top; y < bottom; ++y) {
int ix = max(x, 0.0).floor();
if (ix < _iwidth) {
_windings[index + ix] += winding;
}
x += slope;
index += _iwidth; // bump to next row
}
}
void _clipLine(double x0, double y0, double x1, double y1) {
if (y0 == y1) {
return;
}
int winding = 1;
if (y0 > y1) {
winding = -1;
double tmp = y0;
// ignore: parameter_assignments
y0 = y1;
// ignore: parameter_assignments
y1 = tmp;
}
// now we're monotonic in Y: y0 <= y1
if (y1 <= 0 || y0 >= _height) {
return;
}
final double m = (x1 - x0) / (y1 - y0);
if (y0 < 0) {
// ignore: parameter_assignments
x0 += m * (0 - y0);
// ignore: parameter_assignments
y0 = 0;
}
if (y1 > _height) {
// ignore: parameter_assignments
x1 += m * (_height - y1);
// ignore: parameter_assignments
y1 = _height;
}
assert(y0 <= y1);
assert(y0 >= 0);
assert(y1 <= _height);
_appendLine(x0, y0, x1, y1, m, winding);
}
@override
void moveTo(double x, double y) {
if (!_expectsMove) {
close();
}
_firstX = _prevX = x - _offsetX;
_firstY = _prevY = y - _offsetY;
_expectsMove = false;
}
@override
void lineTo(double x, double y) {
assert(!_expectsMove);
// ignore: parameter_assignments
x -= _offsetX;
// ignore: parameter_assignments
y -= _offsetY;
_clipLine(_prevX, _prevY, x, y);
_prevX = x;
_prevY = y;
}
bool _quickRejectY(double y0, double y1, double y2, double y3) {
final double h = _height;
return y0 <= 0 && y1 <= 0 && y2 <= 0 && y3 <= 0 ||
y0 >= h && y1 >= h && y2 >= h && y3 >= h;
}
/*
* Computes (conservatively) the number of line-segments needed
* to approximate this cubic and stay within the specified tolerance.
*/
static int _countCubicSegments(double ax, double ay, double bx, double by,
double cx, double cy, double dx, double dy, double tolerance) {
final abcX = ax - bx - bx + cx;
final abcY = ay - by - by + cy;
final bcdX = bx - cx - cx + dx;
final bcdY = by - cy - cy + dy;
final errX = max(abcX.abs(), bcdX.abs());
final errY = max(abcY.abs(), bcdY.abs());
final dist = sqrt(errX * errX + errY * errY);
const maxCurveSegments = 1 << 8; // just a big but finite value
final count = sqrt((3 * dist) / (4 * tolerance));
return max(1, min(count.ceil(), maxCurveSegments));
}
@override
void cubicTo(
double x1, double y1, double x2, double y2, double x3, double y3) {
assert(!_expectsMove);
// ignore: parameter_assignments
x1 -= _offsetX;
// ignore: parameter_assignments
x2 -= _offsetX;
// ignore: parameter_assignments
x3 -= _offsetX;
// ignore: parameter_assignments
y1 -= _offsetY;
// ignore: parameter_assignments
y2 -= _offsetY;
// ignore: parameter_assignments
y3 -= _offsetY;
if (_quickRejectY(_prevY, y1, y2, y3)) {
_prevX = x3;
_prevY = y3;
return;
}
final count =
_countCubicSegments(_prevX, _prevY, x1, y1, x2, y2, x3, y3, _tolerance);
final dt = 1.0 / count;
double t = dt;
// Compute simple coefficients for the cubic polynomial
// ... much faster to evaluate multiple times
// ... At^3 + Bt^2 + Ct + D
//
final aX = (x3 - _prevX) + 3 * (x1 - x2);
// final bX = 3 * ((x2 - x1) + (_prevX - x1));
final cX = 3 * (x1 - _prevX);
final dX = _prevX;
final aY = (y3 - _prevY) + 3 * (y1 - y2);
final bY = 3 * ((y2 - y1) + (_prevY - y1));
final cY = 3 * (y1 - _prevY);
final dY = _prevY;
// we don't need the first point eval(0) or the last eval(1)
double px = _prevX;
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 ny = ((aY * t + bY) * t + cY) * t + dY;
_clipLine(px, py, nx, ny);
px = nx;
py = ny;
t += dt;
}
_clipLine(px, py, x3, y3);
_prevX = x3;
_prevY = y3;
}
@override
void close() {
assert(!_expectsMove);
_clipLine(_prevX, _prevY, _firstX, _firstY);
_expectsMove = true;
}
void move(Vec2D v) {
moveTo(v.x, v.y);
}
void line(Vec2D v) {
lineTo(v.x, v.y);
}
void cubic(Vec2D b, Vec2D c, Vec2D d) {
cubicTo(b.x, b.y, c.x, c.y, d.x, d.y);
}
void addRect(AABB r, Mat2D xform, PathDirection dir) {
final p0 = xform * Vec2D.fromValues(r.left, r.top);
final p1 = xform * Vec2D.fromValues(r.right, r.top);
final p2 = xform * Vec2D.fromValues(r.right, r.bottom);
final p3 = xform * Vec2D.fromValues(r.left, r.bottom);
move(p0);
if (dir == PathDirection.clockwise) {
line(p1);
line(p2);
line(p3);
} else {
assert(dir == PathDirection.counterclockwise);
line(p3);
line(p2);
line(p1);
}
close();
}
bool test([PathFillRule rule = PathFillRule.nonZero]) {
if (!_expectsMove) {
close();
}
final int mask = (rule == PathFillRule.nonZero) ? -1 : 1;
int nonzero = 0;
for (final w in _windings) {
nonzero |= w & mask;
}
return nonzero != 0;
}
}
/// A HitTester with a settable transform. We can roll this into HitTester if we
/// like it.
class TransformingHitTester extends HitTester {
TransformingHitTester(
IAABB area, [
double tolerance = 0.25,
]) : super(area, tolerance = tolerance);
Mat2D _transform = Mat2D();
bool _needsTransform = false;
Mat2D get transform => _transform;
set transform(Mat2D value) {
_transform = value;
_needsTransform = !value.isIdentity;
}
@override
void moveTo(double x, double y) {
if (!_needsTransform) {
super.moveTo(x, y);
return;
}
var v = _transform * Vec2D.fromValues(x, y);
super.moveTo(v.x, v.y);
}
@override
void lineTo(double x, double y) {
if (!_needsTransform) {
super.lineTo(x, y);
return;
}
var v = _transform * Vec2D.fromValues(x, y);
super.lineTo(v.x, v.y);
}
@override
void cubicTo(double ox, double oy, double ix, double iy, double x, double y) {
if (!_needsTransform) {
super.cubicTo(ox, oy, ix, iy, x, y);
return;
}
var o = _transform * Vec2D.fromValues(ox, oy);
var i = _transform * Vec2D.fromValues(ix, iy);
var p = _transform * Vec2D.fromValues(x, y);
super.cubicTo(o.x, o.y, i.x, i.y, p.x, p.y);
}
}

View File

@ -53,10 +53,16 @@ class Mat2D {
Mat2D.fromTranslation(Vec2D translation)
: _buffer = Float32List.fromList(
[1.0, 0.0, 0.0, 1.0, translation[0], translation[1]]);
[1.0, 0.0, 0.0, 1.0, translation.x, translation.y]);
Mat2D.fromTranslate(double x, double y)
: _buffer = Float32List.fromList([1.0, 0.0, 0.0, 1.0, x, y]);
Mat2D.fromScaling(Vec2D scaling)
: _buffer = Float32List.fromList([scaling[0], 0, 0, scaling[1], 0, 0]);
: _buffer = Float32List.fromList([scaling.x, 0, 0, scaling.y, 0, 0]);
Mat2D.fromScale(double x, double y)
: _buffer = Float32List.fromList([x, 0, 0, y, 0, 0]);
Mat2D.fromMat4(Float64List mat4)
: _buffer = Float32List.fromList(
@ -64,6 +70,13 @@ class Mat2D {
Mat2D.clone(Mat2D copy) : _buffer = Float32List.fromList(copy.values);
Vec2D mapXY(double x, double y) {
return Vec2D.fromValues(x * _buffer[0] + y * _buffer[2] + _buffer[4],
x * _buffer[1] + y * _buffer[3] + _buffer[5]);
}
Vec2D operator *(Vec2D v) => mapXY(v.x, v.y);
static Mat2D fromRotation(Mat2D o, double rad) {
double s = sin(rad);
double c = cos(rad);
@ -95,18 +108,11 @@ class Mat2D {
}
static void scale(Mat2D o, Mat2D a, Vec2D v) {
double a0 = a[0],
a1 = a[1],
a2 = a[2],
a3 = a[3],
a4 = a[4],
a5 = a[5],
v0 = v[0],
v1 = v[1];
o[0] = a0 * v0;
o[1] = a1 * v0;
o[2] = a2 * v1;
o[3] = a3 * v1;
double a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5];
o[0] = a0 * v.x;
o[1] = a1 * v.x;
o[2] = a2 * v.y;
o[3] = a3 * v.y;
o[4] = a4;
o[5] = a5;
}
@ -165,8 +171,8 @@ class Mat2D {
o[1] = a[1];
o[2] = a[2];
o[3] = a[3];
o[4] = a[4] + b[0];
o[5] = a[5] + b[1];
o[4] = a[4] + b.x;
o[5] = a[5] + b.y;
return o;
}
@ -191,16 +197,16 @@ class Mat2D {
static void getScale(Mat2D m, Vec2D s) {
double x = m[0];
double y = m[1];
s[0] = x.sign * sqrt(x * x + y * y);
s.x = x.sign * sqrt(x * x + y * y);
x = m[2];
y = m[3];
s[1] = y.sign * sqrt(x * x + y * y);
s.y = y.sign * sqrt(x * x + y * y);
}
static Vec2D getTranslation(Mat2D m, Vec2D t) {
t[0] = m[4];
t[1] = m[5];
t.x = m[4];
t.y = m[5];
return t;
}
@ -213,6 +219,14 @@ class Mat2D {
mat[5] = 0.0;
}
bool get isIdentity =>
_buffer[0] == 1.0 &&
_buffer[1] == 0.0 &&
_buffer[2] == 0.0 &&
_buffer[3] == 1.0 &&
_buffer[4] == 0.0 &&
_buffer[5] == 0.0;
static void decompose(Mat2D m, TransformComponents result) {
double m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3];

View File

@ -0,0 +1,22 @@
/*
* Copyright 2022 Rive
*/
enum PathFillRule {
nonZero,
evenOdd,
}
enum PathDirection {
clockwise,
counterclockwise,
}
enum PathVerb {
move,
line,
quad,
conicUnused, // so we match skia's order
cubic,
close,
}

View File

@ -33,8 +33,8 @@ class Segment2D {
// We cache these internally so we can call projectPoint multiple times in
// succession performantly.
if (_diff == null) {
_diff = Vec2D.subtract(Vec2D(), start, end);
lengthSquared = Vec2D.squaredLength(_diff!);
_diff = start - end;
lengthSquared = _diff!.squaredLength();
}
}
@ -49,8 +49,8 @@ class Segment2D {
if (lengthSquared == 0) {
return ProjectionResult(0, start);
}
double t = ((point[0] - start[0]) * (end[0] - start[0]) +
(point[1] - start[1]) * (end[1] - start[1])) /
double t = ((point.x - start.x) * (end.x - start.x) +
(point.y - start.y) * (end.y - start.y)) /
lengthSquared;
if (clamp) {
@ -66,8 +66,8 @@ class Segment2D {
return ProjectionResult(
t,
Vec2D.fromValues(
start[0] + t * (end[0] - start[0]),
start[1] + t * (end[1] - start[1]),
start.x + t * (end.x - start.x),
start.y + t * (end.y - start.y),
),
);
}

View File

@ -1,130 +1,143 @@
import 'dart:math';
import 'dart:math' as math;
import 'dart:typed_data';
import 'package:rive/src/rive_core/math/mat2d.dart';
import 'package:rive/src/utilities/utilities.dart';
class Vec2D {
final Float32List _buffer;
double x, y;
Float32List get values {
return _buffer;
Vec2D()
: x = 0,
y = 0;
Vec2D.clone(Vec2D copy)
: x = copy.x,
y = copy.y;
Vec2D.fromValues(double ox, double oy)
: x = ox,
y = oy;
double at(int index) {
if (index == 0) {
return x;
}
if (index == 1) {
return y;
}
throw RangeError('index out of range');
}
double operator [](int index) {
return _buffer[index];
void setAt(int index, double value) {
if (index == 0) {
x = value;
} else if (index == 1) {
y = value;
} else {
throw RangeError('index out of range');
}
}
void operator []=(int index, double value) {
_buffer[index] = value;
double length() => math.sqrt(x * x + y * y);
double squaredLength() => x * x + y * y;
double atan2() => math.atan2(y, x);
Vec2D operator +(Vec2D v) {
return Vec2D.fromValues(x + v.x, y + v.y);
}
Vec2D() : _buffer = Float32List.fromList([0.0, 0.0]);
Vec2D operator -(Vec2D v) {
return Vec2D.fromValues(x - v.x, y - v.y);
}
Vec2D.clone(Vec2D copy) : _buffer = Float32List.fromList(copy._buffer);
Vec2D.fromValues(double x, double y) : _buffer = Float32List.fromList([x, y]);
// ignore: avoid_returning_this
Vec2D apply(Mat2D m) {
final newX = x * m[0] + y * m[2] + m[4];
final newY = x * m[1] + y * m[3] + m[5];
x = newX;
y = newY;
return this;
}
static void copy(Vec2D o, Vec2D a) {
o[0] = a[0];
o[1] = a[1];
o.x = a.x;
o.y = a.y;
}
static void copyFromList(Vec2D o, Float32List a) {
o[0] = a[0];
o[1] = a[1];
o.x = a[0];
o.y = a[1];
}
static Vec2D transformMat2D(Vec2D o, Vec2D a, Mat2D m) {
double x = a[0];
double y = a[1];
o[0] = m[0] * x + m[2] * y + m[4];
o[1] = m[1] * x + m[3] * y + m[5];
final x = a.x;
final y = a.y;
o.x = m[0] * x + m[2] * y + m[4];
o.y = m[1] * x + m[3] * y + m[5];
return o;
}
static Vec2D transformMat2(Vec2D o, Vec2D a, Mat2D m) {
double x = a[0];
double y = a[1];
o[0] = m[0] * x + m[2] * y;
o[1] = m[1] * x + m[3] * y;
return o;
}
static Vec2D subtract(Vec2D o, Vec2D a, Vec2D b) {
o[0] = a[0] - b[0];
o[1] = a[1] - b[1];
return o;
}
static Vec2D add(Vec2D o, Vec2D a, Vec2D b) {
o[0] = a[0] + b[0];
o[1] = a[1] + b[1];
final x = a.x;
final y = a.y;
o.x = m[0] * x + m[2] * y;
o.y = m[1] * x + m[3] * y;
return o;
}
static Vec2D scale(Vec2D o, Vec2D a, double scale) {
o[0] = a[0] * scale;
o[1] = a[1] * scale;
o.x = a.x * scale;
o.y = a.y * scale;
return o;
}
static Vec2D lerp(Vec2D o, Vec2D a, Vec2D b, double f) {
double ax = a[0];
double ay = a[1];
o[0] = ax + f * (b[0] - ax);
o[1] = ay + f * (b[1] - ay);
double ax = a.x;
double ay = a.y;
o.x = ax + f * (b.x - ax);
o.y = ay + f * (b.y - ay);
return o;
}
static double length(Vec2D a) {
double x = a[0];
double y = a[1];
return sqrt(x * x + y * y);
}
static double squaredLength(Vec2D a) {
double x = a[0];
double y = a[1];
return x * x + y * y;
}
static double distance(Vec2D a, Vec2D b) {
double x = b[0] - a[0];
double y = b[1] - a[1];
return sqrt(x * x + y * y);
double x = b.x - a.x;
double y = b.y - a.y;
return math.sqrt(x * x + y * y);
}
static double squaredDistance(Vec2D a, Vec2D b) {
double x = b[0] - a[0];
double y = b[1] - a[1];
double x = b.x - a.x;
double y = b.y - a.y;
return x * x + y * y;
}
static Vec2D negate(Vec2D result, Vec2D a) {
result[0] = -1 * a[0];
result[1] = -1 * a[1];
result.x = -1 * a.x;
result.y = -1 * a.y;
return result;
}
static void normalize(Vec2D result, Vec2D a) {
double x = a[0];
double y = a[1];
double x = a.x;
double y = a.y;
double len = x * x + y * y;
if (len > 0.0) {
len = 1.0 / sqrt(len);
result[0] = a[0] * len;
result[1] = a[1] * len;
len = 1.0 / math.sqrt(len);
result.x = a.x * len;
result.y = a.y * len;
}
}
static double dot(Vec2D a, Vec2D b) {
return a[0] * b[0] + a[1] * b[1];
return a.x * b.x + a.y * b.y;
}
static Vec2D scaleAndAdd(Vec2D result, Vec2D a, Vec2D b, double scale) {
result[0] = a[0] + b[0] * scale;
result[1] = a[1] + b[1] * scale;
result.x = a.x + b.x * scale;
result.y = a.y + b.y * scale;
return result;
}
@ -133,9 +146,8 @@ class Vec2D {
if (l2 == 0) {
return 0;
}
return ((pt[0] - segmentPoint1[0]) * (segmentPoint2[0] - segmentPoint1[0]) +
(pt[1] - segmentPoint1[1]) *
(segmentPoint2[1] - segmentPoint1[1])) /
return ((pt.x - segmentPoint1.x) * (segmentPoint2.x - segmentPoint1.x) +
(pt.y - segmentPoint1.y) * (segmentPoint2.y - segmentPoint1.y)) /
l2;
}
@ -150,30 +162,30 @@ class Vec2D {
}
Vec2D ptOnSeg = Vec2D.fromValues(
segmentPoint1[0] + t * (segmentPoint2[0] - segmentPoint1[0]),
segmentPoint1[1] + t * (segmentPoint2[1] - segmentPoint1[1]),
segmentPoint1.x + t * (segmentPoint2.x - segmentPoint1.x),
segmentPoint1.y + t * (segmentPoint2.y - segmentPoint1.y),
);
return Vec2D.squaredDistance(ptOnSeg, pt);
}
static bool approximatelyEqual(Vec2D a, Vec2D b, {double threshold = 0.001}) {
var a0 = a[0], a1 = a[1];
var b0 = b[0], b1 = b[1];
return (a0 - b0).abs() <= threshold * max(1.0, max(a0.abs(), b0.abs())) &&
(a1 - b1).abs() <= threshold * max(1.0, max(a1.abs(), b1.abs()));
var a0 = a.x, a1 = a.y;
var b0 = b.x, b1 = b.y;
return (a0 - b0).abs() <=
threshold * math.max(1.0, math.max(a0.abs(), b0.abs())) &&
(a1 - b1).abs() <=
threshold * math.max(1.0, math.max(a1.abs(), b1.abs()));
}
@override
String toString() {
String v = _buffer[0].toString() + ', ';
return v + _buffer[1].toString();
return '$x, $y';
}
@override
bool operator ==(Object o) =>
o is Vec2D && _buffer[0] == o[0] && _buffer[1] == o[1];
bool operator ==(Object o) => o is Vec2D && x == o.x && y == o.y;
@override
int get hashCode => szudzik(_buffer[0].hashCode, _buffer[1].hashCode);
int get hashCode => szudzik(x.hashCode, y.hashCode);
}

View File

@ -1,5 +1,4 @@
import 'package:rive/src/generated/node_base.dart';
import 'package:rive/src/rive_core/math/aabb.dart';
import 'package:rive/src/rive_core/math/vec2d.dart';
export 'package:rive/src/generated/node_base.dart';
@ -11,8 +10,8 @@ class Node extends NodeBase {
/// Sets the position of the Node
set translation(Vec2D pos) {
x = pos[0];
y = pos[1];
x = pos.x;
y = pos.y;
}
@override
@ -24,6 +23,4 @@ class Node extends NodeBase {
void yChanged(double from, double to) {
markTransformDirty();
}
AABB get localBounds => AABB.fromValues(x, y, x, y);
}

View File

@ -18,11 +18,9 @@ class CubicAsymmetricVertex extends CubicAsymmetricVertexBase {
@override
Vec2D get outPoint {
return _outPoint ??= Vec2D.add(
Vec2D(),
translation,
Vec2D.fromValues(
cos(rotation) * outDistance, sin(rotation) * outDistance));
return _outPoint ??= Vec2D.fromValues(
translation.x + cos(rotation) * outDistance,
translation.y + sin(rotation) * outDistance);
}
@override
@ -32,11 +30,9 @@ class CubicAsymmetricVertex extends CubicAsymmetricVertexBase {
@override
Vec2D get inPoint {
return _inPoint ??= Vec2D.add(
Vec2D(),
translation,
Vec2D.fromValues(
cos(rotation) * -inDistance, sin(rotation) * -inDistance));
return _inPoint ??= Vec2D.fromValues(
translation.x + cos(rotation) * -inDistance,
translation.y + sin(rotation) * -inDistance);
}
@override
@ -46,8 +42,7 @@ class CubicAsymmetricVertex extends CubicAsymmetricVertexBase {
@override
String toString() {
return 'in ${inPoint[0]}, ${inPoint[1]} | ${translation.toString()} '
'| out ${outPoint[0]}, ${outPoint[1]}';
return 'in $inPoint | $translation | out $outPoint';
}
@override

View File

@ -25,17 +25,14 @@ class CubicDetachedVertex extends CubicDetachedVertexBase {
InternalCoreHelper.markValid(this);
this.x = x;
this.y = y;
this.inPoint = Vec2D.fromValues(inX ?? inPoint![0], inY ?? inPoint![1]);
this.outPoint =
Vec2D.fromValues(outX ?? outPoint![0], outY ?? outPoint![1]);
this.inPoint = Vec2D.fromValues(inX ?? inPoint!.x, inY ?? inPoint!.y);
this.outPoint = Vec2D.fromValues(outX ?? outPoint!.x, outY ?? outPoint!.y);
}
@override
Vec2D get outPoint => _outPoint ??= Vec2D.add(
Vec2D(),
translation,
Vec2D.fromValues(
cos(outRotation) * outDistance, sin(outRotation) * outDistance));
Vec2D get outPoint => _outPoint ??= Vec2D.fromValues(
translation.x + cos(outRotation) * outDistance,
translation.y + sin(outRotation) * outDistance);
@override
set outPoint(Vec2D value) {
@ -43,11 +40,9 @@ class CubicDetachedVertex extends CubicDetachedVertexBase {
}
@override
Vec2D get inPoint => _inPoint ??= Vec2D.add(
Vec2D(),
translation,
Vec2D.fromValues(
cos(inRotation) * inDistance, sin(inRotation) * inDistance));
Vec2D get inPoint => _inPoint ??= Vec2D.fromValues(
translation.x + cos(inRotation) * inDistance,
translation.y + sin(inRotation) * inDistance);
@override
set inPoint(Vec2D value) {
@ -56,8 +51,7 @@ class CubicDetachedVertex extends CubicDetachedVertexBase {
@override
String toString() {
return 'in ${inPoint[0]}, ${inPoint[1]} | ${translation.toString()} '
'| out ${outPoint[0]}, ${outPoint[1]}';
return 'in $inPoint | $translation | out $outPoint';
}
@override

View File

@ -20,8 +20,9 @@ class CubicMirroredVertex extends CubicMirroredVertexBase {
@override
Vec2D get outPoint {
return _outPoint ??= Vec2D.add(Vec2D(), translation,
Vec2D.fromValues(cos(rotation) * distance, sin(rotation) * distance));
return _outPoint ??= Vec2D.fromValues(
translation.x + cos(rotation) * distance,
translation.y + sin(rotation) * distance);
}
@override
@ -31,20 +32,20 @@ class CubicMirroredVertex extends CubicMirroredVertexBase {
@override
Vec2D get inPoint {
return _inPoint ??= Vec2D.add(Vec2D(), translation,
Vec2D.fromValues(cos(rotation) * -distance, sin(rotation) * -distance));
return _inPoint ??= Vec2D.fromValues(
translation.x + cos(rotation) * -distance,
translation.y + sin(rotation) * -distance);
}
@override
set inPoint(Vec2D value) {
var diffIn = Vec2D.fromValues(value[0] - x, value[1] - y);
outPoint = Vec2D.subtract(Vec2D(), translation, diffIn);
var diffIn = Vec2D.fromValues(value.x - x, value.y - y);
outPoint = translation - diffIn;
}
@override
String toString() {
return 'in ${inPoint[0]}, ${inPoint[1]} | ${translation.toString()} '
'| out ${outPoint[0]}, ${outPoint[1]}';
return 'in $inPoint | $translation | out $outPoint';
}
@override

View File

@ -24,9 +24,9 @@ abstract class CubicVertex extends CubicVertexBase {
void deform(Mat2D world, Float32List boneTransforms) {
super.deform(world, boneTransforms);
Weight.deform(outPoint[0], outPoint[1], weight!.outIndices,
weight!.outValues, world, boneTransforms, weight!.outTranslation);
Weight.deform(inPoint[0], inPoint[1], weight!.inIndices, weight!.inValues,
Weight.deform(outPoint.x, outPoint.y, weight!.outIndices, weight!.outValues,
world, boneTransforms, weight!.outTranslation);
Weight.deform(inPoint.x, inPoint.y, weight!.inIndices, weight!.inValues,
world, boneTransforms, weight!.inTranslation);
}
}

View File

@ -20,7 +20,6 @@ class Image extends ImageBase
Mesh? get mesh => _mesh;
bool get hasMesh => _mesh != null;
@override
AABB get localBounds {
if (hasMesh && _mesh!.draws) {
return _mesh!.bounds;
@ -122,5 +121,11 @@ class Image extends ImageBase
@override
Skinnable<MeshVertex>? get skinnable => _mesh;
Mat2D get renderTransform => _mesh?.worldTransform ?? worldTransform;
Mat2D get renderTransform {
var mesh = _mesh;
if (mesh != null && mesh.draws) {
return mesh.worldTransform;
}
return worldTransform;
}
}

View File

@ -21,6 +21,7 @@ class Mesh extends MeshBase with Skinnable<MeshVertex> {
// be in world space.
final List<MeshVertex> _vertices = [];
List<MeshVertex> get vertices => _vertices;
ui.Vertices? _uiVertices;
@ -60,6 +61,7 @@ class Mesh extends MeshBase with Skinnable<MeshVertex> {
return false;
}
}
return true;
}
@ -101,7 +103,7 @@ class Mesh extends MeshBase with Skinnable<MeshVertex> {
for (final vertex in _vertices) {
var point = vertex.renderTranslation;
vertices.add(ui.Offset(point[0], point[1]));
vertices.add(ui.Offset(point.x, point.y));
bounds.expandToPoint(point);
uv.add(ui.Offset(vertex.u, vertex.v));
}
@ -144,14 +146,6 @@ class Mesh extends MeshBase with Skinnable<MeshVertex> {
@override
void markSkinDirty() => addDirt(ComponentDirt.vertices);
@override
bool addDirt(int value, {bool recurse = false}) {
if (value == ComponentDirt.vertices) {
// throw 'why';
}
return super.addDirt(value, recurse: recurse);
}
Mat2D get worldTransform =>
skin != null ? Mat2D.identity : transformComponent.worldTransform;

View File

@ -1,16 +1,11 @@
import 'package:rive/src/generated/shapes/mesh_vertex_base.dart';
import 'package:rive/src/rive_core/node.dart';
import 'package:rive/src/rive_core/shapes/mesh.dart';
import 'package:rive/src/rive_core/transform_component.dart';
export 'package:rive/src/generated/shapes/mesh_vertex_base.dart';
class MeshVertex extends MeshVertexBase {
Mesh? get mesh => parent as Mesh?;
TransformComponent get transformComponent =>
mesh?.transformComponent ?? Node();
@override
bool validate() => super.validate() && parent is Mesh;

View File

@ -101,10 +101,10 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
// Get the start and end of the gradient in world coordinates (world
// transform of the shape).
var world = shapePaintContainer!.worldTransform;
var worldStart = Vec2D.transformMat2D(Vec2D(), start, world);
var worldEnd = Vec2D.transformMat2D(Vec2D(), end, world);
paint.shader = makeGradient(ui.Offset(worldStart[0], worldStart[1]),
ui.Offset(worldEnd[0], worldEnd[1]), colors, colorPositions);
var worldStart = world * start;
var worldEnd = world * end;
paint.shader = makeGradient(ui.Offset(worldStart.x, worldStart.y),
ui.Offset(worldEnd.x, worldEnd.y), colors, colorPositions);
} else {
paint.shader =
makeGradient(startOffset, endOffset, colors, colorPositions);

View File

@ -5,6 +5,7 @@ import 'package:rive/src/generated/shapes/path_base.dart';
import 'package:rive/src/rive_core/component.dart';
import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/component_flags.dart';
import 'package:rive/src/rive_core/math/aabb.dart';
import 'package:rive/src/rive_core/math/circle_constant.dart';
import 'package:rive/src/rive_core/math/mat2d.dart';
import 'package:rive/src/rive_core/math/vec2d.dart';
@ -110,6 +111,11 @@ abstract class Path extends PathBase {
bool _buildPath() {
_isValid = true;
_renderPath.reset();
return buildPath(_renderPath);
}
/// Pour the path commands into a PathInterface [path].
bool buildPath(PathInterface path) {
List<PathVertex> vertices = this.vertices;
var length = vertices.length;
if (length < 2) {
@ -127,15 +133,15 @@ abstract class Path extends PathBase {
if (firstPoint is CubicVertex) {
startIsCubic = prevIsCubic = true;
var inPoint = firstPoint.renderIn;
startInX = inPoint[0];
startInY = inPoint[1];
startInX = inPoint.x;
startInY = inPoint.y;
var outPoint = firstPoint.renderOut;
outX = outPoint[0];
outY = outPoint[1];
outX = outPoint.x;
outY = outPoint.y;
var translation = firstPoint.renderTranslation;
startX = translation[0];
startY = translation[1];
_renderPath.moveTo(startX, startY);
startX = translation.x;
startY = translation.y;
path.moveTo(startX, startY);
} else {
startIsCubic = prevIsCubic = false;
var point = firstPoint as StraightVertex;
@ -146,25 +152,27 @@ abstract class Path extends PathBase {
var pos = point.renderTranslation;
var toPrev = Vec2D.subtract(Vec2D(),
prev is CubicVertex ? prev.renderOut : prev.renderTranslation, pos);
var toPrevLength = Vec2D.length(toPrev);
toPrev[0] /= toPrevLength;
toPrev[1] /= toPrevLength;
var toPrev =
(prev is CubicVertex ? prev.renderOut : prev.renderTranslation) -
pos;
var toPrevLength = toPrev.length();
toPrev.x /= toPrevLength;
toPrev.y /= toPrevLength;
var next = vertices[1];
var toNext = Vec2D.subtract(Vec2D(),
next is CubicVertex ? next.renderIn : next.renderTranslation, pos);
var toNextLength = Vec2D.length(toNext);
toNext[0] /= toNextLength;
toNext[1] /= toNextLength;
var toNext =
(next is CubicVertex ? next.renderIn : next.renderTranslation) -
pos;
var toNextLength = toNext.length();
toNext.x /= toNextLength;
toNext.y /= toNextLength;
var renderRadius = min(toPrevLength, min(toNextLength, radius));
var translation = Vec2D.scaleAndAdd(Vec2D(), pos, toPrev, renderRadius);
_renderPath.moveTo(startInX = startX = translation[0],
startInY = startY = translation[1]);
path.moveTo(startInX = startX = translation.x,
startInY = startY = translation.y);
var outPoint = Vec2D.scaleAndAdd(
Vec2D(), pos, toPrev, icircleConstant * renderRadius);
@ -173,14 +181,14 @@ abstract class Path extends PathBase {
Vec2D(), pos, toNext, icircleConstant * renderRadius);
var posNext = Vec2D.scaleAndAdd(Vec2D(), pos, toNext, renderRadius);
_renderPath.cubicTo(outPoint[0], outPoint[1], inPoint[0], inPoint[1],
outX = posNext[0], outY = posNext[1]);
path.cubicTo(outPoint.x, outPoint.y, inPoint.x, inPoint.y,
outX = posNext.x, outY = posNext.y);
prevIsCubic = false;
} else {
var translation = point.renderTranslation;
outX = translation[0];
outY = translation[1];
_renderPath.moveTo(startInX = startX = outX, startInY = startY = outY);
outX = translation.x;
outY = translation.y;
path.moveTo(startInX = startX = outX, startInY = startY = outY);
}
}
@ -190,13 +198,13 @@ abstract class Path extends PathBase {
if (vertex is CubicVertex) {
var inPoint = vertex.renderIn;
var translation = vertex.renderTranslation;
_renderPath.cubicTo(
outX, outY, inPoint[0], inPoint[1], translation[0], translation[1]);
path.cubicTo(
outX, outY, inPoint.x, inPoint.y, translation.x, translation.y);
prevIsCubic = true;
var outPoint = vertex.renderOut;
outX = outPoint[0];
outY = outPoint[1];
outX = outPoint.x;
outY = outPoint.y;
} else {
var point = vertex as StraightVertex;
@ -204,31 +212,29 @@ abstract class Path extends PathBase {
if (radius > 0) {
var pos = point.renderTranslation;
var toPrev =
Vec2D.subtract(Vec2D(), Vec2D.fromValues(outX, outY), pos);
var toPrevLength = Vec2D.length(toPrev);
toPrev[0] /= toPrevLength;
toPrev[1] /= toPrevLength;
var toPrev = Vec2D.fromValues(outX - pos.x, outY - pos.y);
var toPrevLength = toPrev.length();
toPrev.x /= toPrevLength;
toPrev.y /= toPrevLength;
var next = vertices[(i + 1) % length];
var toNext = Vec2D.subtract(
Vec2D(),
next is CubicVertex ? next.renderIn : next.renderTranslation,
pos);
var toNextLength = Vec2D.length(toNext);
toNext[0] /= toNextLength;
toNext[1] /= toNextLength;
var toNext =
(next is CubicVertex ? next.renderIn : next.renderTranslation) -
pos;
var toNextLength = toNext.length();
toNext.x /= toNextLength;
toNext.y /= toNextLength;
var renderRadius = min(toPrevLength, min(toNextLength, radius));
var translation =
Vec2D.scaleAndAdd(Vec2D(), pos, toPrev, renderRadius);
if (prevIsCubic) {
_renderPath.cubicTo(outX, outY, translation[0], translation[1],
translation[0], translation[1]);
path.cubicTo(outX, outY, translation.x, translation.y,
translation.x, translation.y);
} else {
_renderPath.lineTo(translation[0], translation[1]);
path.lineTo(translation.x, translation.y);
}
var outPoint = Vec2D.scaleAndAdd(
@ -238,62 +244,252 @@ abstract class Path extends PathBase {
Vec2D(), pos, toNext, icircleConstant * renderRadius);
var posNext = Vec2D.scaleAndAdd(Vec2D(), pos, toNext, renderRadius);
_renderPath.cubicTo(outPoint[0], outPoint[1], inPoint[0], inPoint[1],
outX = posNext[0], outY = posNext[1]);
path.cubicTo(outPoint.x, outPoint.y, inPoint.x, inPoint.y,
outX = posNext.x, outY = posNext.y);
prevIsCubic = false;
} else if (prevIsCubic) {
var translation = point.renderTranslation;
var x = translation[0];
var y = translation[1];
_renderPath.cubicTo(outX, outY, x, y, x, y);
var x = translation.x;
var y = translation.y;
path.cubicTo(outX, outY, x, y, x, y);
prevIsCubic = false;
outX = x;
outY = y;
} else {
var translation = point.renderTranslation;
outX = translation[0];
outY = translation[1];
_renderPath.lineTo(outX, outY);
outX = translation.x;
outY = translation.y;
path.lineTo(outX, outY);
}
}
}
if (isClosed) {
if (prevIsCubic || startIsCubic) {
_renderPath.cubicTo(outX, outY, startInX, startInY, startX, startY);
path.cubicTo(outX, outY, startInX, startInY, startX, startY);
}
_renderPath.close();
path.close();
}
return true;
}
AABB get localBounds => _renderPath.preciseComputeBounds();
AABB computeBounds(Mat2D relativeTo) => preciseComputeBounds(
transform: Mat2D.multiply(
Mat2D(),
relativeTo,
pathTransform,
),
);
AABB preciseComputeBounds({
Mat2D? transform,
}) =>
_renderPath.preciseComputeBounds(
transform: transform,
);
bool get hasBounds => _renderPath.hasBounds;
@override
void pathFlagsChanged(int from, int to) => markPathDirty();
bool get isHidden => (pathFlags & ComponentFlags.hidden) != 0;
}
class RenderPath {
enum _PathCommand { moveTo, lineTo, cubicTo, close }
abstract class PathInterface {
void moveTo(double x, double y);
void lineTo(double x, double y);
void cubicTo(double ox, double oy, double ix, double iy, double x, double y);
void close();
}
class RenderPath implements PathInterface {
final ui.Path _uiPath = ui.Path();
ui.Path get uiPath => _uiPath;
final List<_PathCommand> _commands = [];
final List<double> _positions = [];
void reset() {
_commands.clear();
_positions.clear();
_uiPath.reset();
}
@override
void lineTo(double x, double y) {
_commands.add(_PathCommand.lineTo);
_positions.add(x);
_positions.add(y);
_uiPath.lineTo(x, y);
}
@override
void moveTo(double x, double y) {
_commands.add(_PathCommand.moveTo);
_positions.add(x);
_positions.add(y);
_uiPath.moveTo(x, y);
}
@override
void cubicTo(double ox, double oy, double ix, double iy, double x, double y) {
_commands.add(_PathCommand.cubicTo);
_positions.add(ox);
_positions.add(oy);
_positions.add(ix);
_positions.add(iy);
_positions.add(x);
_positions.add(y);
_uiPath.cubicTo(ox, oy, ix, iy, x, y);
}
void move(Vec2D v) {
moveTo(v.x, v.y);
}
void line(Vec2D v) {
lineTo(v.x, v.y);
}
void cubic(Vec2D b, Vec2D c, Vec2D d) {
cubicTo(b.x, b.y, c.x, c.y, d.x, d.y);
}
@override
void close() {
_commands.add(_PathCommand.close);
_uiPath.close();
}
bool get isClosed =>
_commands.isNotEmpty && _commands.last == _PathCommand.close;
bool get hasBounds {
return _commands.length > 1;
}
AABB preciseComputeBounds({
Mat2D? transform,
}) {
if (_commands.isEmpty) {
return AABB.empty();
}
// Compute the extremas and use them to expand the bounds as detailed here:
// https://pomax.github.io/bezierinfo/#extremities
AABB bounds = AABB.empty();
var idx = 0;
var penPosition = Vec2D();
for (final command in _commands) {
switch (command) {
case _PathCommand.lineTo:
// Pen position already transformed...
bounds.includePoint(penPosition, null);
var to = Vec2D.fromValues(_positions[idx++], _positions[idx++]);
if (transform != null) {
to.apply(transform);
}
penPosition = bounds.includePoint(to, null);
break;
// We only do moveTo at the start, effectively always the start of the
// first line segment (so always include it).
case _PathCommand.moveTo:
penPosition.x = _positions[idx++];
penPosition.y = _positions[idx++];
if (transform != null) {
penPosition.apply(transform);
}
break;
case _PathCommand.cubicTo:
var outPoint = Vec2D.fromValues(_positions[idx++], _positions[idx++]);
var inPoint = Vec2D.fromValues(_positions[idx++], _positions[idx++]);
var point = Vec2D.fromValues(_positions[idx++], _positions[idx++]);
if (transform != null) {
outPoint.apply(transform);
inPoint.apply(transform);
point.apply(transform);
}
_expandBoundsForAxis(
bounds, 0, penPosition.x, outPoint.x, inPoint.x, point.x);
_expandBoundsForAxis(
bounds, 1, penPosition.y, outPoint.y, inPoint.y, point.y);
penPosition = point;
break;
case _PathCommand.close:
break;
}
}
return bounds;
}
}
/// Expand our bounds to a point (in normalized T space) on the Cubic.
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) {
// Check start/end as cubic goes through those.
if (start < bounds[component]) {
bounds[component] = start;
}
if (start > bounds[component + 2]) {
bounds[component + 2] = start;
}
if (end < bounds[component]) {
bounds[component] = end;
}
if (end > bounds[component + 2]) {
bounds[component + 2] = end;
}
// Now check extremas.
// Find the first derivative
var a = 3 * (cp1 - start);
var b = 3 * (cp2 - cp1);
var c = 3 * (end - cp2);
var d = a - 2 * b + c;
// Solve roots for first derivative.
if (d != 0) {
var m1 = -sqrt(b * b - a * c);
var m2 = -a + b;
// First root.
_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);
}
// Derive the first derivative to get the 2nd and use the root of
// that (linear).
var d2a = 2 * (b - a);
var d2b = 2 * (c - b);
if (d2a != b) {
_expandBoundsToCubicPoint(
bounds, component, d2a / (d2a - d2b), start, cp1, cp2, end);
}
}

View File

@ -46,6 +46,12 @@ class PathComposer extends Component {
.mat4);
}
}
// If the world path doesn't get built, we should mark the bounds dirty
// here.
if (!buildWorldPath) {
shape.markBoundsDirty();
}
}
if (buildWorldPath) {
worldPath.reset();
@ -56,6 +62,7 @@ class PathComposer extends Component {
worldPath.addPath(path.uiPath, ui.Offset.zero,
matrix4: path.pathTransform.mat4);
}
shape.markBoundsDirty();
}
_fillPath = shape.fillInWorld ? worldPath : localPath;
}

View File

@ -3,6 +3,9 @@ import 'dart:ui' as ui;
import 'package:collection/collection.dart';
import 'package:rive/src/generated/shapes/shape_base.dart';
import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/math/aabb.dart';
import 'package:rive/src/rive_core/math/hit_test.dart';
import 'package:rive/src/rive_core/math/mat2d.dart';
import 'package:rive/src/rive_core/shapes/paint/linear_gradient.dart' as core;
import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart';
import 'package:rive/src/rive_core/shapes/paint/stroke.dart';
@ -29,6 +32,22 @@ class Shape extends ShapeBase with ShapePaintContainer {
ui.Path get fillPath => pathComposer.fillPath;
// Build the bounds on demand, more efficient than re-computing whenever they
// change as bounds rarely have bearing at runtime (they will in some cases
// with constraints eventually).
AABB? _worldBounds;
AABB? _localBounds;
AABB get worldBounds => _worldBounds ??= computeWorldBounds();
AABB get localBounds => _localBounds ??= computeLocalBounds();
/// Let the shape know that any further call to get world/local bounds will
/// need to rebuild the cached bounds.
void markBoundsDirty() {
_worldBounds = _localBounds = null;
}
bool addPath(Path path) {
paintChanged();
var added = paths.add(path);
@ -157,6 +176,59 @@ class Shape extends ShapeBase with ShapePaintContainer {
return paths.remove(path);
}
AABB computeWorldBounds() {
var boundsPaths = paths.where((path) => path.hasBounds);
if (boundsPaths.isEmpty) {
return AABB.fromMinMax(worldTranslation, worldTranslation);
}
var path = boundsPaths.first;
AABB worldBounds = path.preciseComputeBounds(transform: path.pathTransform);
for (final path in boundsPaths.skip(1)) {
AABB.combine(worldBounds, worldBounds,
path.preciseComputeBounds(transform: path.pathTransform));
}
return worldBounds;
}
AABB computeBounds(Mat2D relativeTo) {
var boundsPaths = paths.where((path) => path.hasBounds);
if (boundsPaths.isEmpty) {
return AABB();
}
var path = boundsPaths.first;
AABB localBounds = path.preciseComputeBounds(
transform: Mat2D.multiply(
Mat2D(),
relativeTo,
path.pathTransform,
),
);
for (final path in paths.skip(1)) {
AABB.combine(
localBounds,
localBounds,
path.preciseComputeBounds(
transform: Mat2D.multiply(
Mat2D(),
relativeTo,
path.pathTransform,
),
),
);
}
return localBounds;
}
AABB computeLocalBounds() {
var toTransform = Mat2D();
if (!Mat2D.invert(toTransform, worldTransform)) {
Mat2D.setIdentity(toTransform);
}
return computeBounds(toTransform);
}
@override
void blendModeValueChanged(int from, int to) => _markBlendModeDirty();
@ -225,4 +297,13 @@ class Shape extends ShapeBase with ShapePaintContainer {
super.buildDependencies();
pathComposer.buildDependencies();
}
/// Prep the [hitTester] for checking collision with the paths inside this
/// shape.
void fillHitTester(TransformingHitTester hitTester) {
for (final path in paths) {
hitTester.transform = path.pathTransform;
path.buildPath(hitTester);
}
}
}

View File

@ -15,8 +15,8 @@ abstract class Vertex<T extends Weight> extends VertexBase {
Vec2D get renderTranslation => weight?.translation ?? translation;
set translation(Vec2D value) {
x = value[0];
y = value[1];
x = value.x;
y = value.y;
}
@override

View File

@ -11,9 +11,16 @@ import 'package:rive/src/rive_core/animation/layer_state.dart';
import 'package:rive/src/rive_core/animation/linear_animation.dart';
import 'package:rive/src/rive_core/animation/state_instance.dart';
import 'package:rive/src/rive_core/animation/state_machine.dart';
import 'package:rive/src/rive_core/animation/state_machine_event.dart';
import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
import 'package:rive/src/rive_core/animation/state_machine_trigger.dart';
import 'package:rive/src/rive_core/animation/state_transition.dart';
import 'package:rive/src/rive_core/math/aabb.dart';
import 'package:rive/src/rive_core/math/hit_test.dart';
import 'package:rive/src/rive_core/math/vec2d.dart';
import 'package:rive/src/rive_core/node.dart';
import 'package:rive/src/rive_core/rive_animation_controller.dart';
import 'package:rive/src/rive_core/shapes/shape.dart';
/// Callback signature for satate machine state changes
typedef OnStateChange = void Function(String, String);
@ -37,7 +44,10 @@ class LayerController {
/// Takes the state machine name and state name
final OnLayerStateChange? onLayerStateChange;
final StateMachineController controller;
LayerController(
this.controller,
this.layer, {
required this.core,
this.onLayerStateChange,
@ -94,10 +104,9 @@ class LayerController {
}
}
bool apply(StateMachineController machineController, CoreContext core,
double elapsedSeconds, HashMap<int, dynamic> inputValues) {
bool apply(CoreContext core, double elapsedSeconds) {
if (_currentState != null) {
_currentState!.advance(elapsedSeconds, inputValues);
_currentState!.advance(elapsedSeconds, controller);
}
_updateMix(elapsedSeconds);
@ -106,11 +115,11 @@ class LayerController {
// This didn't advance during our updateState, but it should now that we
// realize we need to mix it in.
if (!_holdAnimationFrom) {
_stateFrom!.advance(elapsedSeconds, inputValues);
_stateFrom!.advance(elapsedSeconds, controller);
}
}
for (int i = 0; updateState(inputValues, i != 0); i++) {
for (int i = 0; updateState(i != 0); i++) {
_apply(core);
if (i == 100) {
@ -131,27 +140,27 @@ class LayerController {
LinearAnimation? _holdAnimation;
double _holdTime = 0;
bool updateState(HashMap<int, dynamic> inputValues, bool ignoreTriggers) {
bool updateState(bool ignoreTriggers) {
if (isTransitioning) {
return false;
}
_waitingForExit = false;
if (tryChangeState(anyStateInstance, inputValues, ignoreTriggers)) {
if (tryChangeState(anyStateInstance, ignoreTriggers)) {
return true;
}
return tryChangeState(_currentState, inputValues, ignoreTriggers);
return tryChangeState(_currentState, ignoreTriggers);
}
bool tryChangeState(StateInstance? stateFrom,
HashMap<int, dynamic> inputValues, bool ignoreTriggers) {
bool tryChangeState(StateInstance? stateFrom, bool ignoreTriggers) {
if (stateFrom == null) {
return false;
}
var outState = _currentState;
for (final transition in stateFrom.state.transitions) {
var allowed = transition.allowed(stateFrom, inputValues, ignoreTriggers);
var allowed = transition.allowed(
stateFrom, controller._inputValues, ignoreTriggers);
if (allowed == AllowTransition.yes &&
_changeState(transition.stateTo, transition: transition)) {
// Take transition
@ -176,7 +185,7 @@ class LayerController {
}
if (outState is AnimationStateInstance) {
var spilledTime = outState.animationInstance.spilledTime;
_currentState?.advance(spilledTime, inputValues);
_currentState?.advance(spilledTime, controller);
}
_mix = 0;
@ -199,7 +208,7 @@ class LayerController {
class StateMachineController extends RiveAnimationController<CoreContext> {
final StateMachine stateMachine;
final inputValues = HashMap<int, dynamic>();
final _inputValues = HashMap<int, dynamic>();
final layerControllers = <LayerController>[];
/// Optional callback for state changes
@ -232,12 +241,15 @@ class StateMachineController extends RiveAnimationController<CoreContext> {
onStateChange?.call(stateMachine.name, stateName);
});
late List<_HitShape> hitShapes;
@override
bool init(CoreContext core) {
_clearLayerControllers();
for (final layer in stateMachine.layers) {
layerControllers.add(LayerController(
this,
layer,
core: core,
onLayerStateChange: _onStateChange,
@ -247,6 +259,29 @@ class StateMachineController extends RiveAnimationController<CoreContext> {
// Make sure triggers are all reset.
advanceInputs();
// Initialize all events.
HashMap<Shape, _HitShape> hitShapeLookup = HashMap<Shape, _HitShape>();
for (final event in stateMachine.events) {
// Resolve target on this artboard instance.
var node = core.resolve<Node>(event.targetId);
if (node == null) {
continue;
}
node.forAll((component) {
if (component is Shape) {
var hitShape = hitShapeLookup[component];
if (hitShape == null) {
hitShapeLookup[component] = hitShape = _HitShape(component);
}
hitShape.events.add(event);
}
// Keep iterating so we find all shapes.
return true;
});
}
hitShapes = hitShapeLookup.values.toList();
return super.init(core);
}
@ -257,17 +292,104 @@ class StateMachineController extends RiveAnimationController<CoreContext> {
}
@protected
void advanceInputs() {}
void advanceInputs() {
for (final input in stateMachine.inputs) {
if (input is StateMachineTrigger) {
_inputValues[input.id] = false;
}
}
}
dynamic getInputValue(int id) => _inputValues[id];
void setInputValue(int id, dynamic value) {
_inputValues[id] = value;
isActive = true;
}
@override
void apply(CoreContext core, double elapsedSeconds) {
bool keepGoing = false;
for (final layerController in layerControllers) {
if (layerController.apply(this, core, elapsedSeconds, inputValues)) {
if (layerController.apply(core, elapsedSeconds)) {
keepGoing = true;
}
}
advanceInputs();
isActive = keepGoing;
}
void _processEvent(Vec2D position, {EventType? hitEvent}) {
const hitRadius = 2;
var hitArea = IAABB(
(position.x - hitRadius).round(),
(position.y - hitRadius).round(),
(position.x + hitRadius).round(),
(position.y + hitRadius).round(),
);
for (final hitShape in hitShapes) {
// for (final hitShape in event.shapes) {
var shape = hitShape.shape;
var bounds = shape.worldBounds;
// Quick reject
bool isOver = false;
if (bounds.contains(position)) {
// Make hit tester.
var hitTester = TransformingHitTester(hitArea);
shape.fillHitTester(hitTester);
// TODO: figure out where we get the fill rule. We could get it from
// the Shape's first fill or do we want to store it on the event as a
// user-selectable value in the inspector?
// Just use bounds for now
isOver = hitTester.test();
}
bool hoverChange = hitShape.isHovered != isOver;
hitShape.isHovered = isOver;
// iterate all events associated with this hit shape
for (final event in hitShape.events) {
// Always update hover states regardless of which specific event type
// we're trying to trigger.
if (hoverChange) {
if (isOver && event.eventType == EventType.enter) {
event.performChanges(this);
isActive = true;
} else if (!isOver && event.eventType == EventType.exit) {
event.performChanges(this);
isActive = true;
}
}
if (isOver && hitEvent == event.eventType) {
event.performChanges(this);
isActive = true;
}
}
}
}
void pointerMove(Vec2D position) => _processEvent(position);
void pointerDown(Vec2D position) => _processEvent(
position,
hitEvent: EventType.down,
);
void pointerUp(Vec2D position) => _processEvent(
position,
hitEvent: EventType.up,
);
}
/// Representation of a Shape from the Artboard Instance and all the events it
/// triggers. Allows tracking hover and performing hit detection only once on
/// shapes that trigger multiple events.
class _HitShape {
Shape shape;
bool isHovered = false;
List<StateMachineEvent> events = [];
_HitShape(this.shape);
}

View File

@ -76,8 +76,8 @@ abstract class TransformComponent extends TransformComponentBase {
Vec2D get scale => Vec2D.fromValues(scaleX, scaleY);
set scale(Vec2D value) {
scaleX = value[0];
scaleY = value[1];
scaleX = value.x;
scaleY = value.y;
}
@mustCallSuper