mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-06-22 14:41:27 +08:00
Syncing to latest rive features including click events from rive.
This commit is contained in:
@ -46,15 +46,15 @@ abstract class SMIInput<T> {
|
||||
/// method to change the input value, but calling change(true) is totally
|
||||
/// valid.
|
||||
bool change(T value) {
|
||||
if (controller.inputValues[id] == value) {
|
||||
if (controller.getInputValue(id) == value) {
|
||||
return false;
|
||||
}
|
||||
controller.inputValues[id] = value;
|
||||
controller.setInputValue(id, value);
|
||||
controller.isActive = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
T get value => controller.inputValues[id] as T;
|
||||
T get value => controller.getInputValue(id) as T;
|
||||
set value(T newValue) => change(newValue);
|
||||
|
||||
bool _is<K>() {
|
||||
@ -72,7 +72,7 @@ class SMIBool extends SMIInput<bool> {
|
||||
SMIType.boolean,
|
||||
controller,
|
||||
) {
|
||||
controller.inputValues[id] = input.value;
|
||||
controller.setInputValue(id, input.value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ class SMINumber extends SMIInput<double> {
|
||||
SMIType.number,
|
||||
controller,
|
||||
) {
|
||||
controller.inputValues[id] = input.value;
|
||||
controller.setInputValue(id, input.value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,7 +100,7 @@ class SMITrigger extends SMIInput<bool> {
|
||||
SMIType.trigger,
|
||||
controller,
|
||||
) {
|
||||
controller.inputValues[id] = false;
|
||||
controller.setInputValue(id, false);
|
||||
}
|
||||
|
||||
void fire() => change(true);
|
||||
|
@ -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';
|
||||
|
14
lib/src/core/importers/state_machine_event_importer.dart
Normal file
14
lib/src/core/importers/state_machine_event_importer.dart
Normal file
@ -0,0 +1,14 @@
|
||||
import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
|
||||
import 'package:rive/src/rive_core/animation/event_input_change.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_event.dart';
|
||||
|
||||
class StateMachineEventImporter extends ArtboardImportStackObject {
|
||||
final StateMachineEvent event;
|
||||
StateMachineEventImporter(this.event);
|
||||
|
||||
void addInputChange(EventInputChange change) {
|
||||
// Other state machine importers do this, do we really need it?
|
||||
// event.context.addObject(change);
|
||||
event.internalAddInputChange(change);
|
||||
}
|
||||
}
|
44
lib/src/generated/animation/event_bool_change_base.dart
Normal file
44
lib/src/generated/animation/event_bool_change_base.dart
Normal file
@ -0,0 +1,44 @@
|
||||
/// Core automatically generated
|
||||
/// lib/src/generated/animation/event_bool_change_base.dart.
|
||||
/// Do not modify manually.
|
||||
|
||||
import 'package:rive/src/rive_core/animation/event_input_change.dart';
|
||||
|
||||
abstract class EventBoolChangeBase extends EventInputChange {
|
||||
static const int typeKey = 117;
|
||||
@override
|
||||
int get coreType => EventBoolChangeBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes =>
|
||||
{EventBoolChangeBase.typeKey, EventInputChangeBase.typeKey};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// Value field with key 228.
|
||||
static const int valueInitialValue = 1;
|
||||
int _value = valueInitialValue;
|
||||
static const int valuePropertyKey = 228;
|
||||
|
||||
/// Value to set the input to when the event occurs.
|
||||
int get value => _value;
|
||||
|
||||
/// Change the [_value] field value.
|
||||
/// [valueChanged] will be invoked only if the field's value has changed.
|
||||
set value(int value) {
|
||||
if (_value == value) {
|
||||
return;
|
||||
}
|
||||
int from = _value;
|
||||
_value = value;
|
||||
if (hasValidated) {
|
||||
valueChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void valueChanged(int from, int to);
|
||||
|
||||
@override
|
||||
void copy(covariant EventBoolChangeBase source) {
|
||||
super.copy(source);
|
||||
_value = source._value;
|
||||
}
|
||||
}
|
42
lib/src/generated/animation/event_input_change_base.dart
Normal file
42
lib/src/generated/animation/event_input_change_base.dart
Normal file
@ -0,0 +1,42 @@
|
||||
/// Core automatically generated
|
||||
/// lib/src/generated/animation/event_input_change_base.dart.
|
||||
/// Do not modify manually.
|
||||
|
||||
import 'package:rive/src/core/core.dart';
|
||||
|
||||
abstract class EventInputChangeBase<T extends CoreContext> extends Core<T> {
|
||||
static const int typeKey = 116;
|
||||
@override
|
||||
int get coreType => EventInputChangeBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {EventInputChangeBase.typeKey};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// InputId field with key 227.
|
||||
static const int inputIdInitialValue = -1;
|
||||
int _inputId = inputIdInitialValue;
|
||||
static const int inputIdPropertyKey = 227;
|
||||
|
||||
/// Id of the StateMachineInput referenced.
|
||||
int get inputId => _inputId;
|
||||
|
||||
/// Change the [_inputId] field value.
|
||||
/// [inputIdChanged] will be invoked only if the field's value has changed.
|
||||
set inputId(int value) {
|
||||
if (_inputId == value) {
|
||||
return;
|
||||
}
|
||||
int from = _inputId;
|
||||
_inputId = value;
|
||||
if (hasValidated) {
|
||||
inputIdChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void inputIdChanged(int from, int to);
|
||||
|
||||
@override
|
||||
void copy(covariant EventInputChangeBase source) {
|
||||
_inputId = source._inputId;
|
||||
}
|
||||
}
|
44
lib/src/generated/animation/event_number_change_base.dart
Normal file
44
lib/src/generated/animation/event_number_change_base.dart
Normal file
@ -0,0 +1,44 @@
|
||||
/// Core automatically generated
|
||||
/// lib/src/generated/animation/event_number_change_base.dart.
|
||||
/// Do not modify manually.
|
||||
|
||||
import 'package:rive/src/rive_core/animation/event_input_change.dart';
|
||||
|
||||
abstract class EventNumberChangeBase extends EventInputChange {
|
||||
static const int typeKey = 118;
|
||||
@override
|
||||
int get coreType => EventNumberChangeBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes =>
|
||||
{EventNumberChangeBase.typeKey, EventInputChangeBase.typeKey};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// Value field with key 229.
|
||||
static const double valueInitialValue = 0;
|
||||
double _value = valueInitialValue;
|
||||
static const int valuePropertyKey = 229;
|
||||
|
||||
/// Value to set the input to when the event occurs.
|
||||
double get value => _value;
|
||||
|
||||
/// Change the [_value] field value.
|
||||
/// [valueChanged] will be invoked only if the field's value has changed.
|
||||
set value(double value) {
|
||||
if (_value == value) {
|
||||
return;
|
||||
}
|
||||
double from = _value;
|
||||
_value = value;
|
||||
if (hasValidated) {
|
||||
valueChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void valueChanged(double from, double to);
|
||||
|
||||
@override
|
||||
void copy(covariant EventNumberChangeBase source) {
|
||||
super.copy(source);
|
||||
_value = source._value;
|
||||
}
|
||||
}
|
14
lib/src/generated/animation/event_trigger_change_base.dart
Normal file
14
lib/src/generated/animation/event_trigger_change_base.dart
Normal file
@ -0,0 +1,14 @@
|
||||
/// Core automatically generated
|
||||
/// lib/src/generated/animation/event_trigger_change_base.dart.
|
||||
/// Do not modify manually.
|
||||
|
||||
import 'package:rive/src/rive_core/animation/event_input_change.dart';
|
||||
|
||||
abstract class EventTriggerChangeBase extends EventInputChange {
|
||||
static const int typeKey = 115;
|
||||
@override
|
||||
int get coreType => EventTriggerChangeBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes =>
|
||||
{EventTriggerChangeBase.typeKey, EventInputChangeBase.typeKey};
|
||||
}
|
70
lib/src/generated/animation/state_machine_event_base.dart
Normal file
70
lib/src/generated/animation/state_machine_event_base.dart
Normal file
@ -0,0 +1,70 @@
|
||||
/// Core automatically generated
|
||||
/// lib/src/generated/animation/state_machine_event_base.dart.
|
||||
/// Do not modify manually.
|
||||
|
||||
import 'package:rive/src/rive_core/animation/state_machine_component.dart';
|
||||
|
||||
abstract class StateMachineEventBase extends StateMachineComponent {
|
||||
static const int typeKey = 114;
|
||||
@override
|
||||
int get coreType => StateMachineEventBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes =>
|
||||
{StateMachineEventBase.typeKey, StateMachineComponentBase.typeKey};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// TargetId field with key 224.
|
||||
static const int targetIdInitialValue = 0;
|
||||
int _targetId = targetIdInitialValue;
|
||||
static const int targetIdPropertyKey = 224;
|
||||
|
||||
/// Identifier used to track the object use as a target fo this event.
|
||||
int get targetId => _targetId;
|
||||
|
||||
/// Change the [_targetId] field value.
|
||||
/// [targetIdChanged] will be invoked only if the field's value has changed.
|
||||
set targetId(int value) {
|
||||
if (_targetId == value) {
|
||||
return;
|
||||
}
|
||||
int from = _targetId;
|
||||
_targetId = value;
|
||||
if (hasValidated) {
|
||||
targetIdChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void targetIdChanged(int from, int to);
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// EventTypeValue field with key 225.
|
||||
static const int eventTypeValueInitialValue = 0;
|
||||
int _eventTypeValue = eventTypeValueInitialValue;
|
||||
static const int eventTypeValuePropertyKey = 225;
|
||||
|
||||
/// Event type (hover, click, etc).
|
||||
int get eventTypeValue => _eventTypeValue;
|
||||
|
||||
/// Change the [_eventTypeValue] field value.
|
||||
/// [eventTypeValueChanged] will be invoked only if the field's value has
|
||||
/// changed.
|
||||
set eventTypeValue(int value) {
|
||||
if (_eventTypeValue == value) {
|
||||
return;
|
||||
}
|
||||
int from = _eventTypeValue;
|
||||
_eventTypeValue = value;
|
||||
if (hasValidated) {
|
||||
eventTypeValueChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void eventTypeValueChanged(int from, int to);
|
||||
|
||||
@override
|
||||
void copy(covariant StateMachineEventBase source) {
|
||||
super.copy(source);
|
||||
_targetId = source._targetId;
|
||||
_eventTypeValue = source._eventTypeValue;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
20
lib/src/input_changes.dart
Normal file
20
lib/src/input_changes.dart
Normal file
@ -0,0 +1,20 @@
|
||||
import 'dart:collection';
|
||||
import 'package:rive/src/rive_core/animation/event_input_change.dart';
|
||||
|
||||
class InputChanges extends ListBase<EventInputChange> {
|
||||
final List<EventInputChange?> _values = [];
|
||||
List<EventInputChange> get values => _values.cast<EventInputChange>();
|
||||
|
||||
@override
|
||||
int get length => _values.length;
|
||||
|
||||
@override
|
||||
set length(int value) => _values.length = value;
|
||||
|
||||
@override
|
||||
EventInputChange operator [](int index) => _values[index]!;
|
||||
|
||||
@override
|
||||
void operator []=(int index, EventInputChange value) =>
|
||||
_values[index] = value;
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:rive/src/rive_core/animation/blend_animation_1d.dart';
|
||||
import 'package:rive/src/rive_core/animation/blend_state_1d.dart';
|
||||
import 'package:rive/src/rive_core/animation/blend_state_instance.dart';
|
||||
import 'package:rive/src/rive_core/state_machine_controller.dart';
|
||||
|
||||
/// [BlendState1D] mixing logic that runs inside the [StateMachine].
|
||||
class BlendState1DInstance
|
||||
@ -41,9 +40,10 @@ class BlendState1DInstance
|
||||
BlendStateAnimationInstance<BlendAnimation1D>? _to;
|
||||
|
||||
@override
|
||||
void advance(double seconds, HashMap<int, dynamic> inputValues) {
|
||||
super.advance(seconds, inputValues);
|
||||
dynamic inputValue = inputValues[(state as BlendState1D).inputId];
|
||||
void advance(double seconds, StateMachineController controller) {
|
||||
super.advance(seconds, controller);
|
||||
dynamic inputValue =
|
||||
controller.getInputValue((state as BlendState1D).inputId);
|
||||
var value = (inputValue is double
|
||||
? inputValue
|
||||
: (state as BlendState1D).input?.value) ??
|
||||
|
@ -1,8 +1,7 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:rive/src/rive_core/animation/blend_animation_direct.dart';
|
||||
import 'package:rive/src/rive_core/animation/blend_state_direct.dart';
|
||||
import 'package:rive/src/rive_core/animation/blend_state_instance.dart';
|
||||
import 'package:rive/src/rive_core/state_machine_controller.dart';
|
||||
|
||||
/// [BlendStateDirect] mixing logic that runs inside the [StateMachine].
|
||||
class BlendStateDirectInstance
|
||||
@ -10,10 +9,11 @@ class BlendStateDirectInstance
|
||||
BlendStateDirectInstance(BlendStateDirect state) : super(state);
|
||||
|
||||
@override
|
||||
void advance(double seconds, HashMap<int, dynamic> inputValues) {
|
||||
super.advance(seconds, inputValues);
|
||||
void advance(double seconds, StateMachineController controller) {
|
||||
super.advance(seconds, controller);
|
||||
for (final animation in animationInstances) {
|
||||
dynamic inputValue = inputValues[animation.blendAnimation.inputId];
|
||||
dynamic inputValue =
|
||||
controller.getInputValue(animation.blendAnimation.inputId);
|
||||
var value = (inputValue is double
|
||||
? inputValue
|
||||
: animation.blendAnimation.input?.value) ??
|
||||
|
@ -1,10 +1,9 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/rive_core/animation/blend_animation.dart';
|
||||
import 'package:rive/src/rive_core/animation/blend_state.dart';
|
||||
import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_instance.dart';
|
||||
import 'package:rive/src/rive_core/state_machine_controller.dart';
|
||||
|
||||
/// Individual animation in a blend state instance.
|
||||
class BlendStateAnimationInstance<T extends BlendAnimation> {
|
||||
@ -34,7 +33,7 @@ abstract class BlendStateInstance<T extends BlendState<K>,
|
||||
|
||||
@mustCallSuper
|
||||
@override
|
||||
void advance(double seconds, HashMap<int, dynamic> inputValues) {
|
||||
void advance(double seconds, StateMachineController controller) {
|
||||
_keepGoing = false;
|
||||
// Advance all the animations in the blend state
|
||||
for (final animation in animationInstances) {
|
||||
|
31
lib/src/rive_core/animation/event_bool_change.dart
Normal file
31
lib/src/rive_core/animation/event_bool_change.dart
Normal file
@ -0,0 +1,31 @@
|
||||
import 'package:rive/src/generated/animation/event_bool_change_base.dart';
|
||||
import 'package:rive/src/rive_core/state_machine_controller.dart';
|
||||
|
||||
export 'package:rive/src/generated/animation/event_bool_change_base.dart';
|
||||
|
||||
class EventBoolChange extends EventBoolChangeBase {
|
||||
@override
|
||||
void valueChanged(int from, int to) {}
|
||||
|
||||
@override
|
||||
void perform(StateMachineController controller) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
controller.setInputValue(inputId, false);
|
||||
break;
|
||||
case 1:
|
||||
controller.setInputValue(inputId, true);
|
||||
break;
|
||||
default:
|
||||
// Toggle
|
||||
dynamic existing = controller.getInputValue(inputId);
|
||||
if (existing is bool) {
|
||||
controller.setInputValue(inputId, !existing);
|
||||
} else {
|
||||
controller.setInputValue(inputId, true);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
61
lib/src/rive_core/animation/event_input_change.dart
Normal file
61
lib/src/rive_core/animation/event_input_change.dart
Normal file
@ -0,0 +1,61 @@
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/animation/event_input_change_base.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_event.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_input.dart';
|
||||
import 'package:rive/src/rive_core/state_machine_controller.dart';
|
||||
|
||||
export 'package:rive/src/generated/animation/event_input_change_base.dart';
|
||||
|
||||
abstract class EventInputChange extends EventInputChangeBase {
|
||||
StateMachineInput _input = StateMachineInput.unknown;
|
||||
StateMachineInput get input => _input;
|
||||
set input(StateMachineInput value) {
|
||||
if (value == _input) {
|
||||
return;
|
||||
}
|
||||
|
||||
_input = value;
|
||||
|
||||
inputId = _input.id;
|
||||
}
|
||||
|
||||
@override
|
||||
void inputIdChanged(int from, int to) {
|
||||
input = context.resolveWithDefault(to, StateMachineInput.unknown);
|
||||
}
|
||||
|
||||
@override
|
||||
void onAdded() {}
|
||||
|
||||
@override
|
||||
void onAddedDirty() {
|
||||
input = context.resolveWithDefault(inputId, StateMachineInput.unknown);
|
||||
}
|
||||
|
||||
/// Make the change to the input values.
|
||||
void perform(StateMachineController controller);
|
||||
|
||||
@override
|
||||
bool import(ImportStack importStack) {
|
||||
var importer = importStack
|
||||
.latest<StateMachineEventImporter>(StateMachineEventBase.typeKey);
|
||||
if (importer == null) {
|
||||
return false;
|
||||
}
|
||||
importer.addInputChange(this);
|
||||
|
||||
var stateMachineImporter =
|
||||
importStack.latest<StateMachineImporter>(StateMachineBase.typeKey);
|
||||
if (stateMachineImporter == null) {
|
||||
return false;
|
||||
}
|
||||
if (inputId >= 0 && inputId < stateMachineImporter.machine.inputs.length) {
|
||||
var found = stateMachineImporter.machine.inputs[inputId];
|
||||
_input = found;
|
||||
inputId = found.id;
|
||||
}
|
||||
|
||||
return super.import(importStack);
|
||||
}
|
||||
}
|
13
lib/src/rive_core/animation/event_number_change.dart
Normal file
13
lib/src/rive_core/animation/event_number_change.dart
Normal file
@ -0,0 +1,13 @@
|
||||
import 'package:rive/src/generated/animation/event_number_change_base.dart';
|
||||
import 'package:rive/src/rive_core/state_machine_controller.dart';
|
||||
|
||||
export 'package:rive/src/generated/animation/event_number_change_base.dart';
|
||||
|
||||
class EventNumberChange extends EventNumberChangeBase {
|
||||
@override
|
||||
void valueChanged(double from, double to) {}
|
||||
|
||||
@override
|
||||
void perform(StateMachineController controller) =>
|
||||
controller.setInputValue(inputId, value);
|
||||
}
|
10
lib/src/rive_core/animation/event_trigger_change.dart
Normal file
10
lib/src/rive_core/animation/event_trigger_change.dart
Normal file
@ -0,0 +1,10 @@
|
||||
import 'package:rive/src/generated/animation/event_trigger_change_base.dart';
|
||||
import 'package:rive/src/rive_core/state_machine_controller.dart';
|
||||
|
||||
export 'package:rive/src/generated/animation/event_trigger_change_base.dart';
|
||||
|
||||
class EventTriggerChange extends EventTriggerChangeBase {
|
||||
@override
|
||||
void perform(StateMachineController controller) =>
|
||||
controller.setInputValue(inputId, true);
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/rive_core/animation/layer_state.dart';
|
||||
import 'package:rive/src/rive_core/state_machine_controller.dart';
|
||||
|
||||
/// Represents the instance of a [LayerState] which is being used in a
|
||||
/// [LayerController] of a [StateMachineController]. Abstract representation of
|
||||
@ -12,7 +11,7 @@ abstract class StateInstance {
|
||||
|
||||
StateInstance(this.state);
|
||||
|
||||
void advance(double seconds, HashMap<int, dynamic> inputValues);
|
||||
void advance(double seconds, StateMachineController controller);
|
||||
void apply(CoreContext core, double mix);
|
||||
|
||||
bool get keepGoing;
|
||||
@ -25,7 +24,7 @@ abstract class StateInstance {
|
||||
class SystemStateInstance extends StateInstance {
|
||||
SystemStateInstance(LayerState state) : super(state);
|
||||
@override
|
||||
void advance(double seconds, HashMap<int, dynamic> inputValues) {}
|
||||
void advance(double seconds, StateMachineController controller) {}
|
||||
|
||||
@override
|
||||
void apply(CoreContext core, double mix) {}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/animation/state_machine_base.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_event.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_input.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
|
||||
import 'package:rive/src/rive_core/artboard.dart';
|
||||
@ -11,6 +12,8 @@ class StateMachine extends StateMachineBase {
|
||||
StateMachineComponents<StateMachineInput>();
|
||||
final StateMachineComponents<StateMachineLayer> layers =
|
||||
StateMachineComponents<StateMachineLayer>();
|
||||
final StateMachineComponents<StateMachineEvent> events =
|
||||
StateMachineComponents<StateMachineEvent>();
|
||||
|
||||
@override
|
||||
bool import(ImportStack stack) {
|
||||
|
@ -7,7 +7,4 @@ class StateMachineBool extends StateMachineBoolBase {
|
||||
|
||||
@override
|
||||
bool isValidType<T>() => T == bool;
|
||||
|
||||
@override
|
||||
dynamic get controllerValue => value;
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ import 'package:rive/src/rive_core/animation/state_machine.dart';
|
||||
export 'package:rive/src/generated/animation/state_machine_component_base.dart';
|
||||
|
||||
/// Implemented by state machine inputs and layers.
|
||||
abstract class StateMachineComponent extends StateMachineComponentBase {
|
||||
abstract class StateMachineComponent
|
||||
extends StateMachineComponentBase<RuntimeArtboard> {
|
||||
StateMachine? _stateMachine;
|
||||
StateMachine? get stateMachine => _stateMachine;
|
||||
set stateMachine(StateMachine? machine) {
|
||||
|
70
lib/src/rive_core/animation/state_machine_event.dart
Normal file
70
lib/src/rive_core/animation/state_machine_event.dart
Normal file
@ -0,0 +1,70 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/animation/state_machine_event_base.dart';
|
||||
import 'package:rive/src/rive_core/animation/event_input_change.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_component.dart';
|
||||
import 'package:rive/src/rive_core/node.dart';
|
||||
import 'package:rive/src/rive_core/state_machine_controller.dart';
|
||||
|
||||
export 'package:rive/src/generated/animation/state_machine_event_base.dart';
|
||||
|
||||
enum EventType { enter, exit, down, up }
|
||||
|
||||
class StateMachineEvent extends StateMachineEventBase {
|
||||
final InputChanges inputChanges = InputChanges();
|
||||
|
||||
Node? _target;
|
||||
Node? get target => _target;
|
||||
set target(Node? value) {
|
||||
if (_target == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
_target = value;
|
||||
|
||||
targetId = _target?.id ?? Core.missingId;
|
||||
}
|
||||
|
||||
@override
|
||||
String get name =>
|
||||
super.name.isEmpty ? (_target?.name ?? 'Event') : super.name;
|
||||
@override
|
||||
void eventTypeValueChanged(int from, int to) {}
|
||||
|
||||
EventType get eventType => EventType.values[eventTypeValue];
|
||||
set eventType(EventType value) => eventTypeValue = value.index;
|
||||
|
||||
@override
|
||||
ListBase<StateMachineComponent> machineComponentList(StateMachine machine) =>
|
||||
machine.events;
|
||||
|
||||
@override
|
||||
void targetIdChanged(int from, int to) => target = context.resolve(to);
|
||||
|
||||
/// Called by rive_core to add an [EventInputChange] to this
|
||||
/// [StateMachineEvent]. This should be @internal when it's supported.
|
||||
bool internalAddInputChange(EventInputChange change) {
|
||||
if (inputChanges.contains(change)) {
|
||||
return false;
|
||||
}
|
||||
inputChanges.add(change);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Called by rive_core to remove an [EventInputChange] from this
|
||||
/// [StateMachineEvent]. This should be @internal when it's supported.
|
||||
bool internalRemoveInputChange(EventInputChange change) {
|
||||
var removed = inputChanges.remove(change);
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
void performChanges(StateMachineController controller) {
|
||||
for (final change in inputChanges) {
|
||||
change.perform(controller);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: use_setters_to_change_properties
|
||||
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:rive/src/generated/animation/state_machine_input_base.dart';
|
||||
@ -14,7 +16,6 @@ abstract class StateMachineInput extends StateMachineInputBase {
|
||||
machine.inputs;
|
||||
|
||||
bool isValidType<T>() => false;
|
||||
dynamic get controllerValue => null;
|
||||
}
|
||||
|
||||
class _StateMachineUnknownInput extends StateMachineInput {}
|
||||
|
@ -4,10 +4,4 @@ export 'package:rive/src/generated/animation/state_machine_number_base.dart';
|
||||
class StateMachineNumber extends StateMachineNumberBase {
|
||||
@override
|
||||
void valueChanged(double from, double to) {}
|
||||
|
||||
@override
|
||||
bool isValidType<T>() => T == double;
|
||||
|
||||
@override
|
||||
dynamic get controllerValue => value;
|
||||
}
|
||||
|
@ -2,20 +2,8 @@ import 'package:rive/src/generated/animation/state_machine_trigger_base.dart';
|
||||
export 'package:rive/src/generated/animation/state_machine_trigger_base.dart';
|
||||
|
||||
class StateMachineTrigger extends StateMachineTriggerBase {
|
||||
bool _triggered = false;
|
||||
bool get triggered => _triggered;
|
||||
|
||||
void fire() {
|
||||
_triggered = true;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
_triggered = false;
|
||||
}
|
||||
void fire() {}
|
||||
|
||||
@override
|
||||
bool isValidType<T>() => T == bool;
|
||||
|
||||
@override
|
||||
dynamic get controllerValue => _triggered;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,14 @@ class ImageAsset extends ImageAssetBase {
|
||||
@visibleForTesting
|
||||
ImageAsset.fromTestImage(this._image);
|
||||
|
||||
@visibleForTesting
|
||||
set image(ui.Image? image) {
|
||||
_image = image;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> decode(Uint8List bytes) {
|
||||
var completer = Completer<void>();
|
||||
final completer = Completer<void>();
|
||||
ui.decodeImageFromList(bytes, (value) {
|
||||
_image = value;
|
||||
completer.complete();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
317
lib/src/rive_core/math/hit_test.dart
Normal file
317
lib/src/rive_core/math/hit_test.dart
Normal file
@ -0,0 +1,317 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:rive/src/rive_core/math/aabb.dart';
|
||||
import 'package:rive/src/rive_core/math/mat2d.dart';
|
||||
import 'package:rive/src/rive_core/math/path_types.dart';
|
||||
import 'package:rive/src/rive_core/math/vec2d.dart';
|
||||
import 'package:rive/src/rive_core/shapes/path.dart';
|
||||
|
||||
class HitTester implements PathInterface {
|
||||
final List<int> _windings;
|
||||
double _firstX, _firstY;
|
||||
double _prevX, _prevY;
|
||||
final double _offsetX, _offsetY;
|
||||
final int _iwidth;
|
||||
final double _height;
|
||||
final double _tolerance;
|
||||
bool _expectsMove;
|
||||
|
||||
HitTester(IAABB area, [double tolerance = 0.25])
|
||||
: _windings =
|
||||
List<int>.filled(area.width * area.height, 0, growable: false),
|
||||
_firstX = 0,
|
||||
_firstY = 0,
|
||||
_prevX = 0,
|
||||
_prevY = 0,
|
||||
_offsetX = area.left.toDouble(),
|
||||
_offsetY = area.top.toDouble(),
|
||||
_iwidth = area.width,
|
||||
_height = area.height.toDouble(),
|
||||
_tolerance = tolerance,
|
||||
_expectsMove = true;
|
||||
|
||||
void _appendLine(
|
||||
double x0, double y0, double x1, double y1, double slope, int winding) {
|
||||
assert(winding == 1 || winding == -1);
|
||||
|
||||
// we round to see which pixel centers we cross
|
||||
final int top = y0.round();
|
||||
final int bottom = y1.round();
|
||||
if (top == bottom) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(top < bottom);
|
||||
assert(top >= 0);
|
||||
assert(bottom <= _height);
|
||||
|
||||
// we add 0.5 at the end to pre-round the values
|
||||
double x = x0 + slope * (top - y0 + 0.5) + 0.5;
|
||||
// start on the correct row
|
||||
int index = top * _iwidth;
|
||||
for (int y = top; y < bottom; ++y) {
|
||||
int ix = max(x, 0.0).floor();
|
||||
if (ix < _iwidth) {
|
||||
_windings[index + ix] += winding;
|
||||
}
|
||||
x += slope;
|
||||
index += _iwidth; // bump to next row
|
||||
}
|
||||
}
|
||||
|
||||
void _clipLine(double x0, double y0, double x1, double y1) {
|
||||
if (y0 == y1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int winding = 1;
|
||||
if (y0 > y1) {
|
||||
winding = -1;
|
||||
double tmp = y0;
|
||||
// ignore: parameter_assignments
|
||||
y0 = y1;
|
||||
// ignore: parameter_assignments
|
||||
y1 = tmp;
|
||||
}
|
||||
// now we're monotonic in Y: y0 <= y1
|
||||
if (y1 <= 0 || y0 >= _height) {
|
||||
return;
|
||||
}
|
||||
|
||||
final double m = (x1 - x0) / (y1 - y0);
|
||||
if (y0 < 0) {
|
||||
// ignore: parameter_assignments
|
||||
x0 += m * (0 - y0);
|
||||
// ignore: parameter_assignments
|
||||
y0 = 0;
|
||||
}
|
||||
if (y1 > _height) {
|
||||
// ignore: parameter_assignments
|
||||
x1 += m * (_height - y1);
|
||||
// ignore: parameter_assignments
|
||||
y1 = _height;
|
||||
}
|
||||
|
||||
assert(y0 <= y1);
|
||||
assert(y0 >= 0);
|
||||
assert(y1 <= _height);
|
||||
|
||||
_appendLine(x0, y0, x1, y1, m, winding);
|
||||
}
|
||||
|
||||
@override
|
||||
void moveTo(double x, double y) {
|
||||
if (!_expectsMove) {
|
||||
close();
|
||||
}
|
||||
_firstX = _prevX = x - _offsetX;
|
||||
_firstY = _prevY = y - _offsetY;
|
||||
_expectsMove = false;
|
||||
}
|
||||
|
||||
@override
|
||||
void lineTo(double x, double y) {
|
||||
assert(!_expectsMove);
|
||||
// ignore: parameter_assignments
|
||||
x -= _offsetX;
|
||||
// ignore: parameter_assignments
|
||||
y -= _offsetY;
|
||||
_clipLine(_prevX, _prevY, x, y);
|
||||
_prevX = x;
|
||||
_prevY = y;
|
||||
}
|
||||
|
||||
bool _quickRejectY(double y0, double y1, double y2, double y3) {
|
||||
final double h = _height;
|
||||
return y0 <= 0 && y1 <= 0 && y2 <= 0 && y3 <= 0 ||
|
||||
y0 >= h && y1 >= h && y2 >= h && y3 >= h;
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes (conservatively) the number of line-segments needed
|
||||
* to approximate this cubic and stay within the specified tolerance.
|
||||
*/
|
||||
static int _countCubicSegments(double ax, double ay, double bx, double by,
|
||||
double cx, double cy, double dx, double dy, double tolerance) {
|
||||
final abcX = ax - bx - bx + cx;
|
||||
final abcY = ay - by - by + cy;
|
||||
|
||||
final bcdX = bx - cx - cx + dx;
|
||||
final bcdY = by - cy - cy + dy;
|
||||
|
||||
final errX = max(abcX.abs(), bcdX.abs());
|
||||
final errY = max(abcY.abs(), bcdY.abs());
|
||||
final dist = sqrt(errX * errX + errY * errY);
|
||||
|
||||
const maxCurveSegments = 1 << 8; // just a big but finite value
|
||||
final count = sqrt((3 * dist) / (4 * tolerance));
|
||||
return max(1, min(count.ceil(), maxCurveSegments));
|
||||
}
|
||||
|
||||
@override
|
||||
void cubicTo(
|
||||
double x1, double y1, double x2, double y2, double x3, double y3) {
|
||||
assert(!_expectsMove);
|
||||
|
||||
// ignore: parameter_assignments
|
||||
x1 -= _offsetX;
|
||||
// ignore: parameter_assignments
|
||||
x2 -= _offsetX;
|
||||
// ignore: parameter_assignments
|
||||
x3 -= _offsetX;
|
||||
|
||||
// ignore: parameter_assignments
|
||||
y1 -= _offsetY;
|
||||
// ignore: parameter_assignments
|
||||
y2 -= _offsetY;
|
||||
// ignore: parameter_assignments
|
||||
y3 -= _offsetY;
|
||||
|
||||
if (_quickRejectY(_prevY, y1, y2, y3)) {
|
||||
_prevX = x3;
|
||||
_prevY = y3;
|
||||
return;
|
||||
}
|
||||
|
||||
final count =
|
||||
_countCubicSegments(_prevX, _prevY, x1, y1, x2, y2, x3, y3, _tolerance);
|
||||
|
||||
final dt = 1.0 / count;
|
||||
double t = dt;
|
||||
|
||||
// Compute simple coefficients for the cubic polynomial
|
||||
// ... much faster to evaluate multiple times
|
||||
// ... At^3 + Bt^2 + Ct + D
|
||||
//
|
||||
final aX = (x3 - _prevX) + 3 * (x1 - x2);
|
||||
// final bX = 3 * ((x2 - x1) + (_prevX - x1));
|
||||
final cX = 3 * (x1 - _prevX);
|
||||
final dX = _prevX;
|
||||
|
||||
final aY = (y3 - _prevY) + 3 * (y1 - y2);
|
||||
final bY = 3 * ((y2 - y1) + (_prevY - y1));
|
||||
final cY = 3 * (y1 - _prevY);
|
||||
final dY = _prevY;
|
||||
|
||||
// we don't need the first point eval(0) or the last eval(1)
|
||||
double px = _prevX;
|
||||
double py = _prevY;
|
||||
for (int i = 1; i < count - 1; ++i) {
|
||||
// Horner's method for evaluating the simple polynomial
|
||||
final nx = ((aX * t + aX) * t + cX) * t + dX;
|
||||
final ny = ((aY * t + bY) * t + cY) * t + dY;
|
||||
_clipLine(px, py, nx, ny);
|
||||
px = nx;
|
||||
py = ny;
|
||||
t += dt;
|
||||
}
|
||||
_clipLine(px, py, x3, y3);
|
||||
_prevX = x3;
|
||||
_prevY = y3;
|
||||
}
|
||||
|
||||
@override
|
||||
void close() {
|
||||
assert(!_expectsMove);
|
||||
|
||||
_clipLine(_prevX, _prevY, _firstX, _firstY);
|
||||
_expectsMove = true;
|
||||
}
|
||||
|
||||
void move(Vec2D v) {
|
||||
moveTo(v.x, v.y);
|
||||
}
|
||||
|
||||
void line(Vec2D v) {
|
||||
lineTo(v.x, v.y);
|
||||
}
|
||||
|
||||
void cubic(Vec2D b, Vec2D c, Vec2D d) {
|
||||
cubicTo(b.x, b.y, c.x, c.y, d.x, d.y);
|
||||
}
|
||||
|
||||
void addRect(AABB r, Mat2D xform, PathDirection dir) {
|
||||
final p0 = xform * Vec2D.fromValues(r.left, r.top);
|
||||
final p1 = xform * Vec2D.fromValues(r.right, r.top);
|
||||
final p2 = xform * Vec2D.fromValues(r.right, r.bottom);
|
||||
final p3 = xform * Vec2D.fromValues(r.left, r.bottom);
|
||||
|
||||
move(p0);
|
||||
if (dir == PathDirection.clockwise) {
|
||||
line(p1);
|
||||
line(p2);
|
||||
line(p3);
|
||||
} else {
|
||||
assert(dir == PathDirection.counterclockwise);
|
||||
line(p3);
|
||||
line(p2);
|
||||
line(p1);
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
bool test([PathFillRule rule = PathFillRule.nonZero]) {
|
||||
if (!_expectsMove) {
|
||||
close();
|
||||
}
|
||||
|
||||
final int mask = (rule == PathFillRule.nonZero) ? -1 : 1;
|
||||
|
||||
int nonzero = 0;
|
||||
|
||||
for (final w in _windings) {
|
||||
nonzero |= w & mask;
|
||||
}
|
||||
return nonzero != 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// A HitTester with a settable transform. We can roll this into HitTester if we
|
||||
/// like it.
|
||||
class TransformingHitTester extends HitTester {
|
||||
TransformingHitTester(
|
||||
IAABB area, [
|
||||
double tolerance = 0.25,
|
||||
]) : super(area, tolerance = tolerance);
|
||||
|
||||
Mat2D _transform = Mat2D();
|
||||
bool _needsTransform = false;
|
||||
|
||||
Mat2D get transform => _transform;
|
||||
set transform(Mat2D value) {
|
||||
_transform = value;
|
||||
_needsTransform = !value.isIdentity;
|
||||
}
|
||||
|
||||
@override
|
||||
void moveTo(double x, double y) {
|
||||
if (!_needsTransform) {
|
||||
super.moveTo(x, y);
|
||||
return;
|
||||
}
|
||||
var v = _transform * Vec2D.fromValues(x, y);
|
||||
super.moveTo(v.x, v.y);
|
||||
}
|
||||
|
||||
@override
|
||||
void lineTo(double x, double y) {
|
||||
if (!_needsTransform) {
|
||||
super.lineTo(x, y);
|
||||
return;
|
||||
}
|
||||
var v = _transform * Vec2D.fromValues(x, y);
|
||||
super.lineTo(v.x, v.y);
|
||||
}
|
||||
|
||||
@override
|
||||
void cubicTo(double ox, double oy, double ix, double iy, double x, double y) {
|
||||
if (!_needsTransform) {
|
||||
super.cubicTo(ox, oy, ix, iy, x, y);
|
||||
return;
|
||||
}
|
||||
var o = _transform * Vec2D.fromValues(ox, oy);
|
||||
var i = _transform * Vec2D.fromValues(ix, iy);
|
||||
var p = _transform * Vec2D.fromValues(x, y);
|
||||
super.cubicTo(o.x, o.y, i.x, i.y, p.x, p.y);
|
||||
}
|
||||
}
|
@ -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];
|
||||
|
||||
|
22
lib/src/rive_core/math/path_types.dart
Normal file
22
lib/src/rive_core/math/path_types.dart
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2022 Rive
|
||||
*/
|
||||
|
||||
enum PathFillRule {
|
||||
nonZero,
|
||||
evenOdd,
|
||||
}
|
||||
|
||||
enum PathDirection {
|
||||
clockwise,
|
||||
counterclockwise,
|
||||
}
|
||||
|
||||
enum PathVerb {
|
||||
move,
|
||||
line,
|
||||
quad,
|
||||
conicUnused, // so we match skia's order
|
||||
cubic,
|
||||
close,
|
||||
}
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ class Image extends ImageBase
|
||||
Mesh? get mesh => _mesh;
|
||||
bool get hasMesh => _mesh != null;
|
||||
|
||||
@override
|
||||
AABB get localBounds {
|
||||
if (hasMesh && _mesh!.draws) {
|
||||
return _mesh!.bounds;
|
||||
@ -122,5 +121,11 @@ class Image extends ImageBase
|
||||
@override
|
||||
Skinnable<MeshVertex>? get skinnable => _mesh;
|
||||
|
||||
Mat2D get renderTransform => _mesh?.worldTransform ?? worldTransform;
|
||||
Mat2D get renderTransform {
|
||||
var mesh = _mesh;
|
||||
if (mesh != null && mesh.draws) {
|
||||
return mesh.worldTransform;
|
||||
}
|
||||
return worldTransform;
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ class Mesh extends MeshBase with Skinnable<MeshVertex> {
|
||||
// be in world space.
|
||||
|
||||
final List<MeshVertex> _vertices = [];
|
||||
|
||||
List<MeshVertex> get vertices => _vertices;
|
||||
|
||||
ui.Vertices? _uiVertices;
|
||||
@ -60,6 +61,7 @@ class Mesh extends MeshBase with Skinnable<MeshVertex> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -101,7 +103,7 @@ class Mesh extends MeshBase with Skinnable<MeshVertex> {
|
||||
for (final vertex in _vertices) {
|
||||
var point = vertex.renderTranslation;
|
||||
|
||||
vertices.add(ui.Offset(point[0], point[1]));
|
||||
vertices.add(ui.Offset(point.x, point.y));
|
||||
bounds.expandToPoint(point);
|
||||
uv.add(ui.Offset(vertex.u, vertex.v));
|
||||
}
|
||||
@ -144,14 +146,6 @@ class Mesh extends MeshBase with Skinnable<MeshVertex> {
|
||||
@override
|
||||
void markSkinDirty() => addDirt(ComponentDirt.vertices);
|
||||
|
||||
@override
|
||||
bool addDirt(int value, {bool recurse = false}) {
|
||||
if (value == ComponentDirt.vertices) {
|
||||
// throw 'why';
|
||||
}
|
||||
return super.addDirt(value, recurse: recurse);
|
||||
}
|
||||
|
||||
Mat2D get worldTransform =>
|
||||
skin != null ? Mat2D.identity : transformComponent.worldTransform;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -5,6 +5,7 @@ import 'package:rive/src/generated/shapes/path_base.dart';
|
||||
import 'package:rive/src/rive_core/component.dart';
|
||||
import 'package:rive/src/rive_core/component_dirt.dart';
|
||||
import 'package:rive/src/rive_core/component_flags.dart';
|
||||
import 'package:rive/src/rive_core/math/aabb.dart';
|
||||
import 'package:rive/src/rive_core/math/circle_constant.dart';
|
||||
import 'package:rive/src/rive_core/math/mat2d.dart';
|
||||
import 'package:rive/src/rive_core/math/vec2d.dart';
|
||||
@ -110,6 +111,11 @@ abstract class Path extends PathBase {
|
||||
bool _buildPath() {
|
||||
_isValid = true;
|
||||
_renderPath.reset();
|
||||
return buildPath(_renderPath);
|
||||
}
|
||||
|
||||
/// Pour the path commands into a PathInterface [path].
|
||||
bool buildPath(PathInterface path) {
|
||||
List<PathVertex> vertices = this.vertices;
|
||||
var length = vertices.length;
|
||||
if (length < 2) {
|
||||
@ -127,15 +133,15 @@ abstract class Path extends PathBase {
|
||||
if (firstPoint is CubicVertex) {
|
||||
startIsCubic = prevIsCubic = true;
|
||||
var inPoint = firstPoint.renderIn;
|
||||
startInX = inPoint[0];
|
||||
startInY = inPoint[1];
|
||||
startInX = inPoint.x;
|
||||
startInY = inPoint.y;
|
||||
var outPoint = firstPoint.renderOut;
|
||||
outX = outPoint[0];
|
||||
outY = outPoint[1];
|
||||
outX = outPoint.x;
|
||||
outY = outPoint.y;
|
||||
var translation = firstPoint.renderTranslation;
|
||||
startX = translation[0];
|
||||
startY = translation[1];
|
||||
_renderPath.moveTo(startX, startY);
|
||||
startX = translation.x;
|
||||
startY = translation.y;
|
||||
path.moveTo(startX, startY);
|
||||
} else {
|
||||
startIsCubic = prevIsCubic = false;
|
||||
var point = firstPoint as StraightVertex;
|
||||
@ -146,25 +152,27 @@ abstract class Path extends PathBase {
|
||||
|
||||
var pos = point.renderTranslation;
|
||||
|
||||
var toPrev = Vec2D.subtract(Vec2D(),
|
||||
prev is CubicVertex ? prev.renderOut : prev.renderTranslation, pos);
|
||||
var toPrevLength = Vec2D.length(toPrev);
|
||||
toPrev[0] /= toPrevLength;
|
||||
toPrev[1] /= toPrevLength;
|
||||
var toPrev =
|
||||
(prev is CubicVertex ? prev.renderOut : prev.renderTranslation) -
|
||||
pos;
|
||||
var toPrevLength = toPrev.length();
|
||||
toPrev.x /= toPrevLength;
|
||||
toPrev.y /= toPrevLength;
|
||||
|
||||
var next = vertices[1];
|
||||
|
||||
var toNext = Vec2D.subtract(Vec2D(),
|
||||
next is CubicVertex ? next.renderIn : next.renderTranslation, pos);
|
||||
var toNextLength = Vec2D.length(toNext);
|
||||
toNext[0] /= toNextLength;
|
||||
toNext[1] /= toNextLength;
|
||||
var toNext =
|
||||
(next is CubicVertex ? next.renderIn : next.renderTranslation) -
|
||||
pos;
|
||||
var toNextLength = toNext.length();
|
||||
toNext.x /= toNextLength;
|
||||
toNext.y /= toNextLength;
|
||||
|
||||
var renderRadius = min(toPrevLength, min(toNextLength, radius));
|
||||
|
||||
var translation = Vec2D.scaleAndAdd(Vec2D(), pos, toPrev, renderRadius);
|
||||
_renderPath.moveTo(startInX = startX = translation[0],
|
||||
startInY = startY = translation[1]);
|
||||
path.moveTo(startInX = startX = translation.x,
|
||||
startInY = startY = translation.y);
|
||||
|
||||
var outPoint = Vec2D.scaleAndAdd(
|
||||
Vec2D(), pos, toPrev, icircleConstant * renderRadius);
|
||||
@ -173,14 +181,14 @@ abstract class Path extends PathBase {
|
||||
Vec2D(), pos, toNext, icircleConstant * renderRadius);
|
||||
|
||||
var posNext = Vec2D.scaleAndAdd(Vec2D(), pos, toNext, renderRadius);
|
||||
_renderPath.cubicTo(outPoint[0], outPoint[1], inPoint[0], inPoint[1],
|
||||
outX = posNext[0], outY = posNext[1]);
|
||||
path.cubicTo(outPoint.x, outPoint.y, inPoint.x, inPoint.y,
|
||||
outX = posNext.x, outY = posNext.y);
|
||||
prevIsCubic = false;
|
||||
} else {
|
||||
var translation = point.renderTranslation;
|
||||
outX = translation[0];
|
||||
outY = translation[1];
|
||||
_renderPath.moveTo(startInX = startX = outX, startInY = startY = outY);
|
||||
outX = translation.x;
|
||||
outY = translation.y;
|
||||
path.moveTo(startInX = startX = outX, startInY = startY = outY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,13 +198,13 @@ abstract class Path extends PathBase {
|
||||
if (vertex is CubicVertex) {
|
||||
var inPoint = vertex.renderIn;
|
||||
var translation = vertex.renderTranslation;
|
||||
_renderPath.cubicTo(
|
||||
outX, outY, inPoint[0], inPoint[1], translation[0], translation[1]);
|
||||
path.cubicTo(
|
||||
outX, outY, inPoint.x, inPoint.y, translation.x, translation.y);
|
||||
|
||||
prevIsCubic = true;
|
||||
var outPoint = vertex.renderOut;
|
||||
outX = outPoint[0];
|
||||
outY = outPoint[1];
|
||||
outX = outPoint.x;
|
||||
outY = outPoint.y;
|
||||
} else {
|
||||
var point = vertex as StraightVertex;
|
||||
|
||||
@ -204,31 +212,29 @@ abstract class Path extends PathBase {
|
||||
if (radius > 0) {
|
||||
var pos = point.renderTranslation;
|
||||
|
||||
var toPrev =
|
||||
Vec2D.subtract(Vec2D(), Vec2D.fromValues(outX, outY), pos);
|
||||
var toPrevLength = Vec2D.length(toPrev);
|
||||
toPrev[0] /= toPrevLength;
|
||||
toPrev[1] /= toPrevLength;
|
||||
var toPrev = Vec2D.fromValues(outX - pos.x, outY - pos.y);
|
||||
var toPrevLength = toPrev.length();
|
||||
toPrev.x /= toPrevLength;
|
||||
toPrev.y /= toPrevLength;
|
||||
|
||||
var next = vertices[(i + 1) % length];
|
||||
|
||||
var toNext = Vec2D.subtract(
|
||||
Vec2D(),
|
||||
next is CubicVertex ? next.renderIn : next.renderTranslation,
|
||||
pos);
|
||||
var toNextLength = Vec2D.length(toNext);
|
||||
toNext[0] /= toNextLength;
|
||||
toNext[1] /= toNextLength;
|
||||
var toNext =
|
||||
(next is CubicVertex ? next.renderIn : next.renderTranslation) -
|
||||
pos;
|
||||
var toNextLength = toNext.length();
|
||||
toNext.x /= toNextLength;
|
||||
toNext.y /= toNextLength;
|
||||
|
||||
var renderRadius = min(toPrevLength, min(toNextLength, radius));
|
||||
|
||||
var translation =
|
||||
Vec2D.scaleAndAdd(Vec2D(), pos, toPrev, renderRadius);
|
||||
if (prevIsCubic) {
|
||||
_renderPath.cubicTo(outX, outY, translation[0], translation[1],
|
||||
translation[0], translation[1]);
|
||||
path.cubicTo(outX, outY, translation.x, translation.y,
|
||||
translation.x, translation.y);
|
||||
} else {
|
||||
_renderPath.lineTo(translation[0], translation[1]);
|
||||
path.lineTo(translation.x, translation.y);
|
||||
}
|
||||
|
||||
var outPoint = Vec2D.scaleAndAdd(
|
||||
@ -238,62 +244,252 @@ abstract class Path extends PathBase {
|
||||
Vec2D(), pos, toNext, icircleConstant * renderRadius);
|
||||
|
||||
var posNext = Vec2D.scaleAndAdd(Vec2D(), pos, toNext, renderRadius);
|
||||
_renderPath.cubicTo(outPoint[0], outPoint[1], inPoint[0], inPoint[1],
|
||||
outX = posNext[0], outY = posNext[1]);
|
||||
path.cubicTo(outPoint.x, outPoint.y, inPoint.x, inPoint.y,
|
||||
outX = posNext.x, outY = posNext.y);
|
||||
prevIsCubic = false;
|
||||
} else if (prevIsCubic) {
|
||||
var translation = point.renderTranslation;
|
||||
var x = translation[0];
|
||||
var y = translation[1];
|
||||
_renderPath.cubicTo(outX, outY, x, y, x, y);
|
||||
var x = translation.x;
|
||||
var y = translation.y;
|
||||
path.cubicTo(outX, outY, x, y, x, y);
|
||||
|
||||
prevIsCubic = false;
|
||||
outX = x;
|
||||
outY = y;
|
||||
} else {
|
||||
var translation = point.renderTranslation;
|
||||
outX = translation[0];
|
||||
outY = translation[1];
|
||||
_renderPath.lineTo(outX, outY);
|
||||
outX = translation.x;
|
||||
outY = translation.y;
|
||||
path.lineTo(outX, outY);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isClosed) {
|
||||
if (prevIsCubic || startIsCubic) {
|
||||
_renderPath.cubicTo(outX, outY, startInX, startInY, startX, startY);
|
||||
path.cubicTo(outX, outY, startInX, startInY, startX, startY);
|
||||
}
|
||||
_renderPath.close();
|
||||
path.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
AABB get localBounds => _renderPath.preciseComputeBounds();
|
||||
AABB computeBounds(Mat2D relativeTo) => preciseComputeBounds(
|
||||
transform: Mat2D.multiply(
|
||||
Mat2D(),
|
||||
relativeTo,
|
||||
pathTransform,
|
||||
),
|
||||
);
|
||||
AABB preciseComputeBounds({
|
||||
Mat2D? transform,
|
||||
}) =>
|
||||
_renderPath.preciseComputeBounds(
|
||||
transform: transform,
|
||||
);
|
||||
bool get hasBounds => _renderPath.hasBounds;
|
||||
|
||||
@override
|
||||
void pathFlagsChanged(int from, int to) => markPathDirty();
|
||||
|
||||
bool get isHidden => (pathFlags & ComponentFlags.hidden) != 0;
|
||||
}
|
||||
|
||||
class RenderPath {
|
||||
enum _PathCommand { moveTo, lineTo, cubicTo, close }
|
||||
|
||||
abstract class PathInterface {
|
||||
void moveTo(double x, double y);
|
||||
void lineTo(double x, double y);
|
||||
void cubicTo(double ox, double oy, double ix, double iy, double x, double y);
|
||||
void close();
|
||||
}
|
||||
|
||||
class RenderPath implements PathInterface {
|
||||
final ui.Path _uiPath = ui.Path();
|
||||
ui.Path get uiPath => _uiPath;
|
||||
final List<_PathCommand> _commands = [];
|
||||
final List<double> _positions = [];
|
||||
|
||||
void reset() {
|
||||
_commands.clear();
|
||||
_positions.clear();
|
||||
_uiPath.reset();
|
||||
}
|
||||
|
||||
@override
|
||||
void lineTo(double x, double y) {
|
||||
_commands.add(_PathCommand.lineTo);
|
||||
_positions.add(x);
|
||||
_positions.add(y);
|
||||
_uiPath.lineTo(x, y);
|
||||
}
|
||||
|
||||
@override
|
||||
void moveTo(double x, double y) {
|
||||
_commands.add(_PathCommand.moveTo);
|
||||
_positions.add(x);
|
||||
_positions.add(y);
|
||||
_uiPath.moveTo(x, y);
|
||||
}
|
||||
|
||||
@override
|
||||
void cubicTo(double ox, double oy, double ix, double iy, double x, double y) {
|
||||
_commands.add(_PathCommand.cubicTo);
|
||||
_positions.add(ox);
|
||||
_positions.add(oy);
|
||||
_positions.add(ix);
|
||||
_positions.add(iy);
|
||||
_positions.add(x);
|
||||
_positions.add(y);
|
||||
_uiPath.cubicTo(ox, oy, ix, iy, x, y);
|
||||
}
|
||||
|
||||
void move(Vec2D v) {
|
||||
moveTo(v.x, v.y);
|
||||
}
|
||||
|
||||
void line(Vec2D v) {
|
||||
lineTo(v.x, v.y);
|
||||
}
|
||||
|
||||
void cubic(Vec2D b, Vec2D c, Vec2D d) {
|
||||
cubicTo(b.x, b.y, c.x, c.y, d.x, d.y);
|
||||
}
|
||||
|
||||
@override
|
||||
void close() {
|
||||
_commands.add(_PathCommand.close);
|
||||
_uiPath.close();
|
||||
}
|
||||
|
||||
bool get isClosed =>
|
||||
_commands.isNotEmpty && _commands.last == _PathCommand.close;
|
||||
|
||||
bool get hasBounds {
|
||||
return _commands.length > 1;
|
||||
}
|
||||
|
||||
AABB preciseComputeBounds({
|
||||
Mat2D? transform,
|
||||
}) {
|
||||
if (_commands.isEmpty) {
|
||||
return AABB.empty();
|
||||
}
|
||||
// Compute the extremas and use them to expand the bounds as detailed here:
|
||||
// https://pomax.github.io/bezierinfo/#extremities
|
||||
|
||||
AABB bounds = AABB.empty();
|
||||
var idx = 0;
|
||||
var penPosition = Vec2D();
|
||||
for (final command in _commands) {
|
||||
switch (command) {
|
||||
case _PathCommand.lineTo:
|
||||
// Pen position already transformed...
|
||||
bounds.includePoint(penPosition, null);
|
||||
|
||||
var to = Vec2D.fromValues(_positions[idx++], _positions[idx++]);
|
||||
if (transform != null) {
|
||||
to.apply(transform);
|
||||
}
|
||||
penPosition = bounds.includePoint(to, null);
|
||||
|
||||
break;
|
||||
// We only do moveTo at the start, effectively always the start of the
|
||||
// first line segment (so always include it).
|
||||
case _PathCommand.moveTo:
|
||||
penPosition.x = _positions[idx++];
|
||||
penPosition.y = _positions[idx++];
|
||||
if (transform != null) {
|
||||
penPosition.apply(transform);
|
||||
}
|
||||
|
||||
break;
|
||||
case _PathCommand.cubicTo:
|
||||
var outPoint = Vec2D.fromValues(_positions[idx++], _positions[idx++]);
|
||||
var inPoint = Vec2D.fromValues(_positions[idx++], _positions[idx++]);
|
||||
var point = Vec2D.fromValues(_positions[idx++], _positions[idx++]);
|
||||
if (transform != null) {
|
||||
outPoint.apply(transform);
|
||||
inPoint.apply(transform);
|
||||
point.apply(transform);
|
||||
}
|
||||
_expandBoundsForAxis(
|
||||
bounds, 0, penPosition.x, outPoint.x, inPoint.x, point.x);
|
||||
_expandBoundsForAxis(
|
||||
bounds, 1, penPosition.y, outPoint.y, inPoint.y, point.y);
|
||||
penPosition = point;
|
||||
|
||||
break;
|
||||
case _PathCommand.close:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand our bounds to a point (in normalized T space) on the Cubic.
|
||||
void _expandBoundsToCubicPoint(AABB bounds, int component, double t, double a,
|
||||
double b, double c, double d) {
|
||||
if (t >= 0 && t <= 1) {
|
||||
var ti = 1 - t;
|
||||
double extremaY = ((ti * ti * ti) * a) +
|
||||
((3 * ti * ti * t) * b) +
|
||||
((3 * ti * t * t) * c) +
|
||||
(t * t * t * d);
|
||||
if (extremaY < bounds[component]) {
|
||||
bounds[component] = extremaY;
|
||||
}
|
||||
if (extremaY > bounds[component + 2]) {
|
||||
bounds[component + 2] = extremaY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _expandBoundsForAxis(AABB bounds, int component, double start, double cp1,
|
||||
double cp2, double end) {
|
||||
// Check start/end as cubic goes through those.
|
||||
if (start < bounds[component]) {
|
||||
bounds[component] = start;
|
||||
}
|
||||
if (start > bounds[component + 2]) {
|
||||
bounds[component + 2] = start;
|
||||
}
|
||||
if (end < bounds[component]) {
|
||||
bounds[component] = end;
|
||||
}
|
||||
if (end > bounds[component + 2]) {
|
||||
bounds[component + 2] = end;
|
||||
}
|
||||
// Now check extremas.
|
||||
|
||||
// Find the first derivative
|
||||
var a = 3 * (cp1 - start);
|
||||
var b = 3 * (cp2 - cp1);
|
||||
var c = 3 * (end - cp2);
|
||||
var d = a - 2 * b + c;
|
||||
|
||||
// Solve roots for first derivative.
|
||||
if (d != 0) {
|
||||
var m1 = -sqrt(b * b - a * c);
|
||||
var m2 = -a + b;
|
||||
|
||||
// First root.
|
||||
_expandBoundsToCubicPoint(
|
||||
bounds, component, -(m1 + m2) / d, start, cp1, cp2, end);
|
||||
_expandBoundsToCubicPoint(
|
||||
bounds, component, -(-m1 + m2) / d, start, cp1, cp2, end);
|
||||
} else if (b != c && d == 0) {
|
||||
_expandBoundsToCubicPoint(
|
||||
bounds, component, (2 * b - c) / (2 * (b - c)), start, cp1, cp2, end);
|
||||
}
|
||||
|
||||
// Derive the first derivative to get the 2nd and use the root of
|
||||
// that (linear).
|
||||
var d2a = 2 * (b - a);
|
||||
var d2b = 2 * (c - b);
|
||||
if (d2a != b) {
|
||||
_expandBoundsToCubicPoint(
|
||||
bounds, component, d2a / (d2a - d2b), start, cp1, cp2, end);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,8 @@ abstract class Vertex<T extends Weight> extends VertexBase {
|
||||
Vec2D get renderTranslation => weight?.translation ?? translation;
|
||||
|
||||
set translation(Vec2D value) {
|
||||
x = value[0];
|
||||
y = value[1];
|
||||
x = value.x;
|
||||
y = value.y;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -11,9 +11,16 @@ import 'package:rive/src/rive_core/animation/layer_state.dart';
|
||||
import 'package:rive/src/rive_core/animation/linear_animation.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_instance.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_event.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_trigger.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_transition.dart';
|
||||
import 'package:rive/src/rive_core/math/aabb.dart';
|
||||
import 'package:rive/src/rive_core/math/hit_test.dart';
|
||||
import 'package:rive/src/rive_core/math/vec2d.dart';
|
||||
import 'package:rive/src/rive_core/node.dart';
|
||||
import 'package:rive/src/rive_core/rive_animation_controller.dart';
|
||||
import 'package:rive/src/rive_core/shapes/shape.dart';
|
||||
|
||||
/// Callback signature for satate machine state changes
|
||||
typedef OnStateChange = void Function(String, String);
|
||||
@ -37,7 +44,10 @@ class LayerController {
|
||||
/// Takes the state machine name and state name
|
||||
final OnLayerStateChange? onLayerStateChange;
|
||||
|
||||
final StateMachineController controller;
|
||||
|
||||
LayerController(
|
||||
this.controller,
|
||||
this.layer, {
|
||||
required this.core,
|
||||
this.onLayerStateChange,
|
||||
@ -94,10 +104,9 @@ class LayerController {
|
||||
}
|
||||
}
|
||||
|
||||
bool apply(StateMachineController machineController, CoreContext core,
|
||||
double elapsedSeconds, HashMap<int, dynamic> inputValues) {
|
||||
bool apply(CoreContext core, double elapsedSeconds) {
|
||||
if (_currentState != null) {
|
||||
_currentState!.advance(elapsedSeconds, inputValues);
|
||||
_currentState!.advance(elapsedSeconds, controller);
|
||||
}
|
||||
|
||||
_updateMix(elapsedSeconds);
|
||||
@ -106,11 +115,11 @@ class LayerController {
|
||||
// This didn't advance during our updateState, but it should now that we
|
||||
// realize we need to mix it in.
|
||||
if (!_holdAnimationFrom) {
|
||||
_stateFrom!.advance(elapsedSeconds, inputValues);
|
||||
_stateFrom!.advance(elapsedSeconds, controller);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; updateState(inputValues, i != 0); i++) {
|
||||
for (int i = 0; updateState(i != 0); i++) {
|
||||
_apply(core);
|
||||
|
||||
if (i == 100) {
|
||||
@ -131,27 +140,27 @@ class LayerController {
|
||||
LinearAnimation? _holdAnimation;
|
||||
double _holdTime = 0;
|
||||
|
||||
bool updateState(HashMap<int, dynamic> inputValues, bool ignoreTriggers) {
|
||||
bool updateState(bool ignoreTriggers) {
|
||||
if (isTransitioning) {
|
||||
return false;
|
||||
}
|
||||
_waitingForExit = false;
|
||||
if (tryChangeState(anyStateInstance, inputValues, ignoreTriggers)) {
|
||||
if (tryChangeState(anyStateInstance, ignoreTriggers)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return tryChangeState(_currentState, inputValues, ignoreTriggers);
|
||||
return tryChangeState(_currentState, ignoreTriggers);
|
||||
}
|
||||
|
||||
bool tryChangeState(StateInstance? stateFrom,
|
||||
HashMap<int, dynamic> inputValues, bool ignoreTriggers) {
|
||||
bool tryChangeState(StateInstance? stateFrom, bool ignoreTriggers) {
|
||||
if (stateFrom == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var outState = _currentState;
|
||||
for (final transition in stateFrom.state.transitions) {
|
||||
var allowed = transition.allowed(stateFrom, inputValues, ignoreTriggers);
|
||||
var allowed = transition.allowed(
|
||||
stateFrom, controller._inputValues, ignoreTriggers);
|
||||
if (allowed == AllowTransition.yes &&
|
||||
_changeState(transition.stateTo, transition: transition)) {
|
||||
// Take transition
|
||||
@ -176,7 +185,7 @@ class LayerController {
|
||||
}
|
||||
if (outState is AnimationStateInstance) {
|
||||
var spilledTime = outState.animationInstance.spilledTime;
|
||||
_currentState?.advance(spilledTime, inputValues);
|
||||
_currentState?.advance(spilledTime, controller);
|
||||
}
|
||||
|
||||
_mix = 0;
|
||||
@ -199,7 +208,7 @@ class LayerController {
|
||||
|
||||
class StateMachineController extends RiveAnimationController<CoreContext> {
|
||||
final StateMachine stateMachine;
|
||||
final inputValues = HashMap<int, dynamic>();
|
||||
final _inputValues = HashMap<int, dynamic>();
|
||||
final layerControllers = <LayerController>[];
|
||||
|
||||
/// Optional callback for state changes
|
||||
@ -232,12 +241,15 @@ class StateMachineController extends RiveAnimationController<CoreContext> {
|
||||
onStateChange?.call(stateMachine.name, stateName);
|
||||
});
|
||||
|
||||
late List<_HitShape> hitShapes;
|
||||
|
||||
@override
|
||||
bool init(CoreContext core) {
|
||||
_clearLayerControllers();
|
||||
|
||||
for (final layer in stateMachine.layers) {
|
||||
layerControllers.add(LayerController(
|
||||
this,
|
||||
layer,
|
||||
core: core,
|
||||
onLayerStateChange: _onStateChange,
|
||||
@ -247,6 +259,29 @@ class StateMachineController extends RiveAnimationController<CoreContext> {
|
||||
// Make sure triggers are all reset.
|
||||
advanceInputs();
|
||||
|
||||
// Initialize all events.
|
||||
HashMap<Shape, _HitShape> hitShapeLookup = HashMap<Shape, _HitShape>();
|
||||
for (final event in stateMachine.events) {
|
||||
// Resolve target on this artboard instance.
|
||||
var node = core.resolve<Node>(event.targetId);
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
node.forAll((component) {
|
||||
if (component is Shape) {
|
||||
var hitShape = hitShapeLookup[component];
|
||||
if (hitShape == null) {
|
||||
hitShapeLookup[component] = hitShape = _HitShape(component);
|
||||
}
|
||||
hitShape.events.add(event);
|
||||
}
|
||||
// Keep iterating so we find all shapes.
|
||||
return true;
|
||||
});
|
||||
}
|
||||
hitShapes = hitShapeLookup.values.toList();
|
||||
|
||||
return super.init(core);
|
||||
}
|
||||
|
||||
@ -257,17 +292,104 @@ class StateMachineController extends RiveAnimationController<CoreContext> {
|
||||
}
|
||||
|
||||
@protected
|
||||
void advanceInputs() {}
|
||||
void advanceInputs() {
|
||||
for (final input in stateMachine.inputs) {
|
||||
if (input is StateMachineTrigger) {
|
||||
_inputValues[input.id] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynamic getInputValue(int id) => _inputValues[id];
|
||||
void setInputValue(int id, dynamic value) {
|
||||
_inputValues[id] = value;
|
||||
isActive = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void apply(CoreContext core, double elapsedSeconds) {
|
||||
bool keepGoing = false;
|
||||
for (final layerController in layerControllers) {
|
||||
if (layerController.apply(this, core, elapsedSeconds, inputValues)) {
|
||||
if (layerController.apply(core, elapsedSeconds)) {
|
||||
keepGoing = true;
|
||||
}
|
||||
}
|
||||
advanceInputs();
|
||||
isActive = keepGoing;
|
||||
}
|
||||
|
||||
void _processEvent(Vec2D position, {EventType? hitEvent}) {
|
||||
const hitRadius = 2;
|
||||
var hitArea = IAABB(
|
||||
(position.x - hitRadius).round(),
|
||||
(position.y - hitRadius).round(),
|
||||
(position.x + hitRadius).round(),
|
||||
(position.y + hitRadius).round(),
|
||||
);
|
||||
|
||||
for (final hitShape in hitShapes) {
|
||||
// for (final hitShape in event.shapes) {
|
||||
var shape = hitShape.shape;
|
||||
var bounds = shape.worldBounds;
|
||||
|
||||
// Quick reject
|
||||
bool isOver = false;
|
||||
if (bounds.contains(position)) {
|
||||
// Make hit tester.
|
||||
|
||||
var hitTester = TransformingHitTester(hitArea);
|
||||
shape.fillHitTester(hitTester);
|
||||
|
||||
// TODO: figure out where we get the fill rule. We could get it from
|
||||
// the Shape's first fill or do we want to store it on the event as a
|
||||
// user-selectable value in the inspector?
|
||||
|
||||
// Just use bounds for now
|
||||
isOver = hitTester.test();
|
||||
}
|
||||
|
||||
bool hoverChange = hitShape.isHovered != isOver;
|
||||
hitShape.isHovered = isOver;
|
||||
// iterate all events associated with this hit shape
|
||||
for (final event in hitShape.events) {
|
||||
// Always update hover states regardless of which specific event type
|
||||
// we're trying to trigger.
|
||||
if (hoverChange) {
|
||||
if (isOver && event.eventType == EventType.enter) {
|
||||
event.performChanges(this);
|
||||
isActive = true;
|
||||
} else if (!isOver && event.eventType == EventType.exit) {
|
||||
event.performChanges(this);
|
||||
isActive = true;
|
||||
}
|
||||
}
|
||||
if (isOver && hitEvent == event.eventType) {
|
||||
event.performChanges(this);
|
||||
isActive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pointerMove(Vec2D position) => _processEvent(position);
|
||||
|
||||
void pointerDown(Vec2D position) => _processEvent(
|
||||
position,
|
||||
hitEvent: EventType.down,
|
||||
);
|
||||
|
||||
void pointerUp(Vec2D position) => _processEvent(
|
||||
position,
|
||||
hitEvent: EventType.up,
|
||||
);
|
||||
}
|
||||
|
||||
/// Representation of a Shape from the Artboard Instance and all the events it
|
||||
/// triggers. Allows tracking hover and performing hit detection only once on
|
||||
/// shapes that trigger multiple events.
|
||||
class _HitShape {
|
||||
Shape shape;
|
||||
bool isHovered = false;
|
||||
List<StateMachineEvent> events = [];
|
||||
_HitShape(this.shape);
|
||||
}
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user