From 92c7af2343243c9216d25f7d449d33011dfed18b Mon Sep 17 00:00:00 2001 From: Luigi Rosso Date: Wed, 11 May 2022 11:21:42 -0700 Subject: [PATCH] Syncing to latest rive features including click events from rive. --- .../controllers/state_machine_controller.dart | 12 +- lib/src/core/core.dart | 4 +- .../state_machine_event_importer.dart | 14 + .../animation/event_bool_change_base.dart | 44 +++ .../animation/event_input_change_base.dart | 42 +++ .../animation/event_number_change_base.dart | 44 +++ .../animation/event_trigger_change_base.dart | 14 + .../animation/state_machine_event_base.dart | 70 ++++ lib/src/generated/rive_core_context.dart | 88 ++++- lib/src/input_changes.dart | 20 ++ .../animation/blend_state_1d_instance.dart | 10 +- .../blend_state_direct_instance.dart | 10 +- .../animation/blend_state_instance.dart | 5 +- .../animation/event_bool_change.dart | 31 ++ .../animation/event_input_change.dart | 61 ++++ .../animation/event_number_change.dart | 13 + .../animation/event_trigger_change.dart | 10 + .../rive_core/animation/state_instance.dart | 7 +- .../rive_core/animation/state_machine.dart | 3 + .../animation/state_machine_bool.dart | 3 - .../animation/state_machine_component.dart | 3 +- .../animation/state_machine_event.dart | 70 ++++ .../animation/state_machine_input.dart | 3 +- .../animation/state_machine_number.dart | 6 - .../animation/state_machine_trigger.dart | 14 +- .../transition_trigger_condition.dart | 5 - lib/src/rive_core/artboard.dart | 2 +- lib/src/rive_core/assets/file_asset.dart | 14 +- lib/src/rive_core/assets/image_asset.dart | 7 +- lib/src/rive_core/bones/bone.dart | 4 +- lib/src/rive_core/bones/skin.dart | 4 +- lib/src/rive_core/bones/weight.dart | 4 +- lib/src/rive_core/bones/weighted_vertex.dart | 2 + .../constraints/distance_constraint.dart | 10 +- .../rive_core/constraints/ik_constraint.dart | 37 +- .../constraints/translation_constraint.dart | 45 ++- lib/src/rive_core/math/aabb.dart | 108 ++++-- lib/src/rive_core/math/hit_test.dart | 317 ++++++++++++++++++ lib/src/rive_core/math/mat2d.dart | 54 +-- lib/src/rive_core/math/path_types.dart | 22 ++ lib/src/rive_core/math/segment2d.dart | 12 +- lib/src/rive_core/math/vec2d.dart | 178 +++++----- lib/src/rive_core/node.dart | 7 +- .../shapes/cubic_asymmetric_vertex.dart | 19 +- .../shapes/cubic_detached_vertex.dart | 24 +- .../shapes/cubic_mirrored_vertex.dart | 17 +- lib/src/rive_core/shapes/cubic_vertex.dart | 6 +- lib/src/rive_core/shapes/image.dart | 9 +- lib/src/rive_core/shapes/mesh.dart | 12 +- lib/src/rive_core/shapes/mesh_vertex.dart | 5 - .../shapes/paint/linear_gradient.dart | 8 +- lib/src/rive_core/shapes/path.dart | 304 ++++++++++++++--- lib/src/rive_core/shapes/path_composer.dart | 7 + lib/src/rive_core/shapes/shape.dart | 81 +++++ lib/src/rive_core/shapes/vertex.dart | 4 +- .../rive_core/state_machine_controller.dart | 152 ++++++++- lib/src/rive_core/transform_component.dart | 4 +- 57 files changed, 1695 insertions(+), 390 deletions(-) create mode 100644 lib/src/core/importers/state_machine_event_importer.dart create mode 100644 lib/src/generated/animation/event_bool_change_base.dart create mode 100644 lib/src/generated/animation/event_input_change_base.dart create mode 100644 lib/src/generated/animation/event_number_change_base.dart create mode 100644 lib/src/generated/animation/event_trigger_change_base.dart create mode 100644 lib/src/generated/animation/state_machine_event_base.dart create mode 100644 lib/src/input_changes.dart create mode 100644 lib/src/rive_core/animation/event_bool_change.dart create mode 100644 lib/src/rive_core/animation/event_input_change.dart create mode 100644 lib/src/rive_core/animation/event_number_change.dart create mode 100644 lib/src/rive_core/animation/event_trigger_change.dart create mode 100644 lib/src/rive_core/animation/state_machine_event.dart create mode 100644 lib/src/rive_core/math/hit_test.dart create mode 100644 lib/src/rive_core/math/path_types.dart diff --git a/lib/src/controllers/state_machine_controller.dart b/lib/src/controllers/state_machine_controller.dart index 456a137..1942b66 100644 --- a/lib/src/controllers/state_machine_controller.dart +++ b/lib/src/controllers/state_machine_controller.dart @@ -46,15 +46,15 @@ abstract class SMIInput { /// 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() { @@ -72,7 +72,7 @@ class SMIBool extends SMIInput { SMIType.boolean, controller, ) { - controller.inputValues[id] = input.value; + controller.setInputValue(id, input.value); } } @@ -86,7 +86,7 @@ class SMINumber extends SMIInput { SMIType.number, controller, ) { - controller.inputValues[id] = input.value; + controller.setInputValue(id, input.value); } } @@ -100,7 +100,7 @@ class SMITrigger extends SMIInput { SMIType.trigger, controller, ) { - controller.inputValues[id] = false; + controller.setInputValue(id, false); } void fire() => change(true); diff --git a/lib/src/core/core.dart b/lib/src/core/core.dart index d604b76..8573afe 100644 --- a/lib/src/core/core.dart +++ b/lib/src/core/core.dart @@ -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'; diff --git a/lib/src/core/importers/state_machine_event_importer.dart b/lib/src/core/importers/state_machine_event_importer.dart new file mode 100644 index 0000000..c0e65d6 --- /dev/null +++ b/lib/src/core/importers/state_machine_event_importer.dart @@ -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); + } +} diff --git a/lib/src/generated/animation/event_bool_change_base.dart b/lib/src/generated/animation/event_bool_change_base.dart new file mode 100644 index 0000000..e9eba7c --- /dev/null +++ b/lib/src/generated/animation/event_bool_change_base.dart @@ -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 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; + } +} diff --git a/lib/src/generated/animation/event_input_change_base.dart b/lib/src/generated/animation/event_input_change_base.dart new file mode 100644 index 0000000..78bf9ac --- /dev/null +++ b/lib/src/generated/animation/event_input_change_base.dart @@ -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 extends Core { + static const int typeKey = 116; + @override + int get coreType => EventInputChangeBase.typeKey; + @override + Set 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; + } +} diff --git a/lib/src/generated/animation/event_number_change_base.dart b/lib/src/generated/animation/event_number_change_base.dart new file mode 100644 index 0000000..97dda95 --- /dev/null +++ b/lib/src/generated/animation/event_number_change_base.dart @@ -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 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; + } +} diff --git a/lib/src/generated/animation/event_trigger_change_base.dart b/lib/src/generated/animation/event_trigger_change_base.dart new file mode 100644 index 0000000..e00874e --- /dev/null +++ b/lib/src/generated/animation/event_trigger_change_base.dart @@ -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 get coreTypes => + {EventTriggerChangeBase.typeKey, EventInputChangeBase.typeKey}; +} diff --git a/lib/src/generated/animation/state_machine_event_base.dart b/lib/src/generated/animation/state_machine_event_base.dart new file mode 100644 index 0000000..8acdd29 --- /dev/null +++ b/lib/src/generated/animation/state_machine_event_base.dart @@ -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 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; + } +} diff --git a/lib/src/generated/rive_core_context.dart b/lib/src/generated/rive_core_context.dart index dbf0c52..ad03230 100644 --- a/lib/src/generated/rive_core_context.dart +++ b/lib/src/generated/rive_core_context.dart @@ -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; diff --git a/lib/src/input_changes.dart b/lib/src/input_changes.dart new file mode 100644 index 0000000..fcbf264 --- /dev/null +++ b/lib/src/input_changes.dart @@ -0,0 +1,20 @@ +import 'dart:collection'; +import 'package:rive/src/rive_core/animation/event_input_change.dart'; + +class InputChanges extends ListBase { + final List _values = []; + List get values => _values.cast(); + + @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; +} diff --git a/lib/src/rive_core/animation/blend_state_1d_instance.dart b/lib/src/rive_core/animation/blend_state_1d_instance.dart index 29dd85b..db2d7fc 100644 --- a/lib/src/rive_core/animation/blend_state_1d_instance.dart +++ b/lib/src/rive_core/animation/blend_state_1d_instance.dart @@ -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? _to; @override - void advance(double seconds, HashMap 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) ?? diff --git a/lib/src/rive_core/animation/blend_state_direct_instance.dart b/lib/src/rive_core/animation/blend_state_direct_instance.dart index 4105754..1f974b0 100644 --- a/lib/src/rive_core/animation/blend_state_direct_instance.dart +++ b/lib/src/rive_core/animation/blend_state_direct_instance.dart @@ -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 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) ?? diff --git a/lib/src/rive_core/animation/blend_state_instance.dart b/lib/src/rive_core/animation/blend_state_instance.dart index 66abf24..b71d895 100644 --- a/lib/src/rive_core/animation/blend_state_instance.dart +++ b/lib/src/rive_core/animation/blend_state_instance.dart @@ -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 { @@ -34,7 +33,7 @@ abstract class BlendStateInstance, @mustCallSuper @override - void advance(double seconds, HashMap inputValues) { + void advance(double seconds, StateMachineController controller) { _keepGoing = false; // Advance all the animations in the blend state for (final animation in animationInstances) { diff --git a/lib/src/rive_core/animation/event_bool_change.dart b/lib/src/rive_core/animation/event_bool_change.dart new file mode 100644 index 0000000..24743c4 --- /dev/null +++ b/lib/src/rive_core/animation/event_bool_change.dart @@ -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; + } + } +} diff --git a/lib/src/rive_core/animation/event_input_change.dart b/lib/src/rive_core/animation/event_input_change.dart new file mode 100644 index 0000000..6a4941a --- /dev/null +++ b/lib/src/rive_core/animation/event_input_change.dart @@ -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(StateMachineEventBase.typeKey); + if (importer == null) { + return false; + } + importer.addInputChange(this); + + var stateMachineImporter = + importStack.latest(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); + } +} diff --git a/lib/src/rive_core/animation/event_number_change.dart b/lib/src/rive_core/animation/event_number_change.dart new file mode 100644 index 0000000..407e42d --- /dev/null +++ b/lib/src/rive_core/animation/event_number_change.dart @@ -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); +} diff --git a/lib/src/rive_core/animation/event_trigger_change.dart b/lib/src/rive_core/animation/event_trigger_change.dart new file mode 100644 index 0000000..9368bb4 --- /dev/null +++ b/lib/src/rive_core/animation/event_trigger_change.dart @@ -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); +} diff --git a/lib/src/rive_core/animation/state_instance.dart b/lib/src/rive_core/animation/state_instance.dart index 44f5393..7fb032d 100644 --- a/lib/src/rive_core/animation/state_instance.dart +++ b/lib/src/rive_core/animation/state_instance.dart @@ -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 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 inputValues) {} + void advance(double seconds, StateMachineController controller) {} @override void apply(CoreContext core, double mix) {} diff --git a/lib/src/rive_core/animation/state_machine.dart b/lib/src/rive_core/animation/state_machine.dart index 208050b..b957eb2 100644 --- a/lib/src/rive_core/animation/state_machine.dart +++ b/lib/src/rive_core/animation/state_machine.dart @@ -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(); final StateMachineComponents layers = StateMachineComponents(); + final StateMachineComponents events = + StateMachineComponents(); @override bool import(ImportStack stack) { diff --git a/lib/src/rive_core/animation/state_machine_bool.dart b/lib/src/rive_core/animation/state_machine_bool.dart index 3288484..2f65860 100644 --- a/lib/src/rive_core/animation/state_machine_bool.dart +++ b/lib/src/rive_core/animation/state_machine_bool.dart @@ -7,7 +7,4 @@ class StateMachineBool extends StateMachineBoolBase { @override bool isValidType() => T == bool; - - @override - dynamic get controllerValue => value; } diff --git a/lib/src/rive_core/animation/state_machine_component.dart b/lib/src/rive_core/animation/state_machine_component.dart index b8a909e..c300ab2 100644 --- a/lib/src/rive_core/animation/state_machine_component.dart +++ b/lib/src/rive_core/animation/state_machine_component.dart @@ -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 { StateMachine? _stateMachine; StateMachine? get stateMachine => _stateMachine; set stateMachine(StateMachine? machine) { diff --git a/lib/src/rive_core/animation/state_machine_event.dart b/lib/src/rive_core/animation/state_machine_event.dart new file mode 100644 index 0000000..996fdca --- /dev/null +++ b/lib/src/rive_core/animation/state_machine_event.dart @@ -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 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); + } + } +} diff --git a/lib/src/rive_core/animation/state_machine_input.dart b/lib/src/rive_core/animation/state_machine_input.dart index e25a051..51e159f 100644 --- a/lib/src/rive_core/animation/state_machine_input.dart +++ b/lib/src/rive_core/animation/state_machine_input.dart @@ -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() => false; - dynamic get controllerValue => null; } class _StateMachineUnknownInput extends StateMachineInput {} diff --git a/lib/src/rive_core/animation/state_machine_number.dart b/lib/src/rive_core/animation/state_machine_number.dart index db019db..ea614f6 100644 --- a/lib/src/rive_core/animation/state_machine_number.dart +++ b/lib/src/rive_core/animation/state_machine_number.dart @@ -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 == double; - - @override - dynamic get controllerValue => value; } diff --git a/lib/src/rive_core/animation/state_machine_trigger.dart b/lib/src/rive_core/animation/state_machine_trigger.dart index 805112a..d1c0a23 100644 --- a/lib/src/rive_core/animation/state_machine_trigger.dart +++ b/lib/src/rive_core/animation/state_machine_trigger.dart @@ -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 == bool; - - @override - dynamic get controllerValue => _triggered; } diff --git a/lib/src/rive_core/animation/transition_trigger_condition.dart b/lib/src/rive_core/animation/transition_trigger_condition.dart index 80b5bcf..217b522 100644 --- a/lib/src/rive_core/animation/transition_trigger_condition.dart +++ b/lib/src/rive_core/animation/transition_trigger_condition.dart @@ -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; } } diff --git a/lib/src/rive_core/artboard.dart b/lib/src/rive_core/artboard.dart index 948ddb8..2aea45d 100644 --- a/lib/src/rive_core/artboard.dart +++ b/lib/src/rive_core/artboard.dart @@ -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 diff --git a/lib/src/rive_core/assets/file_asset.dart b/lib/src/rive_core/assets/file_asset.dart index e1947d9..619ba5a 100644 --- a/lib/src/rive_core/assets/file_asset.dart +++ b/lib/src/rive_core/assets/file_asset.dart @@ -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); } diff --git a/lib/src/rive_core/assets/image_asset.dart b/lib/src/rive_core/assets/image_asset.dart index 2ec6db6..03789cf 100644 --- a/lib/src/rive_core/assets/image_asset.dart +++ b/lib/src/rive_core/assets/image_asset.dart @@ -17,9 +17,14 @@ class ImageAsset extends ImageAssetBase { @visibleForTesting ImageAsset.fromTestImage(this._image); + @visibleForTesting + set image(ui.Image? image) { + _image = image; + } + @override Future decode(Uint8List bytes) { - var completer = Completer(); + final completer = Completer(); ui.decodeImageFromList(bytes, (value) { _image = value; completer.complete(); diff --git a/lib/src/rive_core/bones/bone.dart b/lib/src/rive_core/bones/bone.dart index 828ac6e..17b1a66 100644 --- a/lib/src/rive_core/bones/bone.dart +++ b/lib/src/rive_core/bones/bone.dart @@ -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); } } diff --git a/lib/src/rive_core/bones/skin.dart b/lib/src/rive_core/bones/skin.dart index 689f52d..07cf16e 100644 --- a/lib/src/rive_core/bones/skin.dart +++ b/lib/src/rive_core/bones/skin.dart @@ -23,7 +23,9 @@ class Skin extends SkinBase { void onDirty(int mask) { // When the skin is dirty the deformed skinnable will need to regenerate its // drawing commands. - (parent as Skinnable).markSkinDirty(); + if (parent is Skinnable) { + (parent as Skinnable).markSkinDirty(); + } } @override diff --git a/lib/src/rive_core/bones/weight.dart b/lib/src/rive_core/bones/weight.dart index 8151184..794b2c5 100644 --- a/lib/src/rive_core/bones/weight.dart +++ b/lib/src/rive_core/bones/weight.dart @@ -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) { diff --git a/lib/src/rive_core/bones/weighted_vertex.dart b/lib/src/rive_core/bones/weighted_vertex.dart index 473cd67..315a86c 100644 --- a/lib/src/rive_core/bones/weighted_vertex.dart +++ b/lib/src/rive_core/bones/weighted_vertex.dart @@ -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) { diff --git a/lib/src/rive_core/constraints/distance_constraint.dart b/lib/src/rive_core/constraints/distance_constraint.dart index 6b75a1a..2409ccc 100644 --- a/lib/src/rive_core/constraints/distance_constraint.dart +++ b/lib/src/rive_core/constraints/distance_constraint.dart @@ -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 diff --git a/lib/src/rive_core/constraints/ik_constraint.dart b/lib/src/rive_core/constraints/ik_constraint.dart index fa0476a..a09dec4 100644 --- a/lib/src/rive_core/constraints/ik_constraint.dart +++ b/lib/src/rive_core/constraints/ik_constraint.dart @@ -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); diff --git a/lib/src/rive_core/constraints/translation_constraint.dart b/lib/src/rive_core/constraints/translation_constraint.dart index 7573370..29b1964 100644 --- a/lib/src/rive_core/constraints/translation_constraint.dart +++ b/lib/src/rive_core/constraints/translation_constraint.dart @@ -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; } } diff --git a/lib/src/rive_core/math/aabb.dart b/lib/src/rive_core/math/aabb.dart index a26a175..a83b2df 100644 --- a/lib/src/rive_core/math/aabb.dart +++ b/lib/src/rive_core/math/aabb.dart @@ -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; } diff --git a/lib/src/rive_core/math/hit_test.dart b/lib/src/rive_core/math/hit_test.dart new file mode 100644 index 0000000..0fb97dd --- /dev/null +++ b/lib/src/rive_core/math/hit_test.dart @@ -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 _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.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); + } +} diff --git a/lib/src/rive_core/math/mat2d.dart b/lib/src/rive_core/math/mat2d.dart index e49a6ef..8ff46bf 100644 --- a/lib/src/rive_core/math/mat2d.dart +++ b/lib/src/rive_core/math/mat2d.dart @@ -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]; diff --git a/lib/src/rive_core/math/path_types.dart b/lib/src/rive_core/math/path_types.dart new file mode 100644 index 0000000..6b2762a --- /dev/null +++ b/lib/src/rive_core/math/path_types.dart @@ -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, +} diff --git a/lib/src/rive_core/math/segment2d.dart b/lib/src/rive_core/math/segment2d.dart index 10fe5ec..5fb67ac 100644 --- a/lib/src/rive_core/math/segment2d.dart +++ b/lib/src/rive_core/math/segment2d.dart @@ -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), ), ); } diff --git a/lib/src/rive_core/math/vec2d.dart b/lib/src/rive_core/math/vec2d.dart index 7b5042d..8e160e8 100644 --- a/lib/src/rive_core/math/vec2d.dart +++ b/lib/src/rive_core/math/vec2d.dart @@ -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); } diff --git a/lib/src/rive_core/node.dart b/lib/src/rive_core/node.dart index e16f95b..e8af4ac 100644 --- a/lib/src/rive_core/node.dart +++ b/lib/src/rive_core/node.dart @@ -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); } diff --git a/lib/src/rive_core/shapes/cubic_asymmetric_vertex.dart b/lib/src/rive_core/shapes/cubic_asymmetric_vertex.dart index a20ecea..3351d06 100644 --- a/lib/src/rive_core/shapes/cubic_asymmetric_vertex.dart +++ b/lib/src/rive_core/shapes/cubic_asymmetric_vertex.dart @@ -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 diff --git a/lib/src/rive_core/shapes/cubic_detached_vertex.dart b/lib/src/rive_core/shapes/cubic_detached_vertex.dart index 48394a1..64329ea 100644 --- a/lib/src/rive_core/shapes/cubic_detached_vertex.dart +++ b/lib/src/rive_core/shapes/cubic_detached_vertex.dart @@ -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 diff --git a/lib/src/rive_core/shapes/cubic_mirrored_vertex.dart b/lib/src/rive_core/shapes/cubic_mirrored_vertex.dart index fb964ce..16b0082 100644 --- a/lib/src/rive_core/shapes/cubic_mirrored_vertex.dart +++ b/lib/src/rive_core/shapes/cubic_mirrored_vertex.dart @@ -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 diff --git a/lib/src/rive_core/shapes/cubic_vertex.dart b/lib/src/rive_core/shapes/cubic_vertex.dart index e00a74f..fe5d779 100644 --- a/lib/src/rive_core/shapes/cubic_vertex.dart +++ b/lib/src/rive_core/shapes/cubic_vertex.dart @@ -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); } } diff --git a/lib/src/rive_core/shapes/image.dart b/lib/src/rive_core/shapes/image.dart index ef81121..7b213f7 100644 --- a/lib/src/rive_core/shapes/image.dart +++ b/lib/src/rive_core/shapes/image.dart @@ -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? 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; + } } diff --git a/lib/src/rive_core/shapes/mesh.dart b/lib/src/rive_core/shapes/mesh.dart index 9d574e5..6f827a9 100644 --- a/lib/src/rive_core/shapes/mesh.dart +++ b/lib/src/rive_core/shapes/mesh.dart @@ -21,6 +21,7 @@ class Mesh extends MeshBase with Skinnable { // be in world space. final List _vertices = []; + List get vertices => _vertices; ui.Vertices? _uiVertices; @@ -60,6 +61,7 @@ class Mesh extends MeshBase with Skinnable { return false; } } + return true; } @@ -101,7 +103,7 @@ class Mesh extends MeshBase with Skinnable { 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 { @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; diff --git a/lib/src/rive_core/shapes/mesh_vertex.dart b/lib/src/rive_core/shapes/mesh_vertex.dart index 5b642ee..f550d42 100644 --- a/lib/src/rive_core/shapes/mesh_vertex.dart +++ b/lib/src/rive_core/shapes/mesh_vertex.dart @@ -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; diff --git a/lib/src/rive_core/shapes/paint/linear_gradient.dart b/lib/src/rive_core/shapes/paint/linear_gradient.dart index 488409d..2379069 100644 --- a/lib/src/rive_core/shapes/paint/linear_gradient.dart +++ b/lib/src/rive_core/shapes/paint/linear_gradient.dart @@ -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); diff --git a/lib/src/rive_core/shapes/path.dart b/lib/src/rive_core/shapes/path.dart index 92e3f03..1b57c1c 100644 --- a/lib/src/rive_core/shapes/path.dart +++ b/lib/src/rive_core/shapes/path.dart @@ -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 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 _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); + } } diff --git a/lib/src/rive_core/shapes/path_composer.dart b/lib/src/rive_core/shapes/path_composer.dart index c85e6b9..1237d62 100644 --- a/lib/src/rive_core/shapes/path_composer.dart +++ b/lib/src/rive_core/shapes/path_composer.dart @@ -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; } diff --git a/lib/src/rive_core/shapes/shape.dart b/lib/src/rive_core/shapes/shape.dart index 8c4df11..1f148ed 100644 --- a/lib/src/rive_core/shapes/shape.dart +++ b/lib/src/rive_core/shapes/shape.dart @@ -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); + } + } } diff --git a/lib/src/rive_core/shapes/vertex.dart b/lib/src/rive_core/shapes/vertex.dart index 696f2f6..5823cd1 100644 --- a/lib/src/rive_core/shapes/vertex.dart +++ b/lib/src/rive_core/shapes/vertex.dart @@ -15,8 +15,8 @@ abstract class Vertex 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 diff --git a/lib/src/rive_core/state_machine_controller.dart b/lib/src/rive_core/state_machine_controller.dart index 3f56168..81965e7 100644 --- a/lib/src/rive_core/state_machine_controller.dart +++ b/lib/src/rive_core/state_machine_controller.dart @@ -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 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 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 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 { final StateMachine stateMachine; - final inputValues = HashMap(); + final _inputValues = HashMap(); final layerControllers = []; /// Optional callback for state changes @@ -232,12 +241,15 @@ class StateMachineController extends RiveAnimationController { 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 { // Make sure triggers are all reset. advanceInputs(); + // Initialize all events. + HashMap hitShapeLookup = HashMap(); + for (final event in stateMachine.events) { + // Resolve target on this artboard instance. + var node = core.resolve(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 { } @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 events = []; + _HitShape(this.shape); } diff --git a/lib/src/rive_core/transform_component.dart b/lib/src/rive_core/transform_component.dart index d47b439..88cd230 100644 --- a/lib/src/rive_core/transform_component.dart +++ b/lib/src/rive_core/transform_component.dart @@ -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