Adding new clipping.

This commit is contained in:
Luigi Rosso
2020-09-22 15:54:53 -07:00
parent d6729c2d84
commit 317f9ca68d
9 changed files with 118 additions and 114 deletions

Binary file not shown.

View File

@ -515,14 +515,14 @@ class RiveCoreContext {
object.distance = value; object.distance = value;
} }
break; break;
case ClippingShapeBase.shapeIdPropertyKey: case ClippingShapeBase.sourceIdPropertyKey:
if (object is ClippingShapeBase && value is int) { if (object is ClippingShapeBase && value is int) {
object.shapeId = value; object.sourceId = value;
} }
break; break;
case ClippingShapeBase.clipOpValuePropertyKey: case ClippingShapeBase.fillRulePropertyKey:
if (object is ClippingShapeBase && value is int) { if (object is ClippingShapeBase && value is int) {
object.clipOpValue = value; object.fillRule = value;
} }
break; break;
case ClippingShapeBase.isVisiblePropertyKey: case ClippingShapeBase.isVisiblePropertyKey:
@ -703,8 +703,8 @@ class RiveCoreContext {
case CubicWeightBase.inIndicesPropertyKey: case CubicWeightBase.inIndicesPropertyKey:
case CubicWeightBase.outValuesPropertyKey: case CubicWeightBase.outValuesPropertyKey:
case CubicWeightBase.outIndicesPropertyKey: case CubicWeightBase.outIndicesPropertyKey:
case ClippingShapeBase.shapeIdPropertyKey: case ClippingShapeBase.sourceIdPropertyKey:
case ClippingShapeBase.clipOpValuePropertyKey: case ClippingShapeBase.fillRulePropertyKey:
case DrawRulesBase.drawTargetIdPropertyKey: case DrawRulesBase.drawTargetIdPropertyKey:
case TendonBase.boneIdPropertyKey: case TendonBase.boneIdPropertyKey:
return uintType; return uintType;
@ -844,10 +844,10 @@ class RiveCoreContext {
return (object as CubicWeightBase).outValues; return (object as CubicWeightBase).outValues;
case CubicWeightBase.outIndicesPropertyKey: case CubicWeightBase.outIndicesPropertyKey:
return (object as CubicWeightBase).outIndices; return (object as CubicWeightBase).outIndices;
case ClippingShapeBase.shapeIdPropertyKey: case ClippingShapeBase.sourceIdPropertyKey:
return (object as ClippingShapeBase).shapeId; return (object as ClippingShapeBase).sourceId;
case ClippingShapeBase.clipOpValuePropertyKey: case ClippingShapeBase.fillRulePropertyKey:
return (object as ClippingShapeBase).clipOpValue; return (object as ClippingShapeBase).fillRule;
case DrawRulesBase.drawTargetIdPropertyKey: case DrawRulesBase.drawTargetIdPropertyKey:
return (object as DrawRulesBase).drawTargetId; return (object as DrawRulesBase).drawTargetId;
case TendonBase.boneIdPropertyKey: case TendonBase.boneIdPropertyKey:
@ -1094,11 +1094,11 @@ class RiveCoreContext {
case CubicWeightBase.outIndicesPropertyKey: case CubicWeightBase.outIndicesPropertyKey:
(object as CubicWeightBase).outIndices = value; (object as CubicWeightBase).outIndices = value;
break; break;
case ClippingShapeBase.shapeIdPropertyKey: case ClippingShapeBase.sourceIdPropertyKey:
(object as ClippingShapeBase).shapeId = value; (object as ClippingShapeBase).sourceId = value;
break; break;
case ClippingShapeBase.clipOpValuePropertyKey: case ClippingShapeBase.fillRulePropertyKey:
(object as ClippingShapeBase).clipOpValue = value; (object as ClippingShapeBase).fillRule = value;
break; break;
case DrawRulesBase.drawTargetIdPropertyKey: case DrawRulesBase.drawTargetIdPropertyKey:
(object as DrawRulesBase).drawTargetId = value; (object as DrawRulesBase).drawTargetId = value;

View File

@ -13,48 +13,46 @@ abstract class ClippingShapeBase extends Component {
Set<int> get coreTypes => {ClippingShapeBase.typeKey, ComponentBase.typeKey}; Set<int> get coreTypes => {ClippingShapeBase.typeKey, ComponentBase.typeKey};
/// -------------------------------------------------------------------------- /// --------------------------------------------------------------------------
/// ShapeId field with key 92. /// SourceId field with key 92.
int _shapeId; int _sourceId;
static const int shapeIdPropertyKey = 92; static const int sourceIdPropertyKey = 92;
/// Identifier used to track the shape to use as a clipping source. /// Identifier used to track the node to use as a clipping source.
int get shapeId => _shapeId; int get sourceId => _sourceId;
/// Change the [_shapeId] field value. /// Change the [_sourceId] field value.
/// [shapeIdChanged] will be invoked only if the field's value has changed. /// [sourceIdChanged] will be invoked only if the field's value has changed.
set shapeId(int value) { set sourceId(int value) {
if (_shapeId == value) { if (_sourceId == value) {
return; return;
} }
int from = _shapeId; int from = _sourceId;
_shapeId = value; _sourceId = value;
shapeIdChanged(from, value); sourceIdChanged(from, value);
} }
void shapeIdChanged(int from, int to); void sourceIdChanged(int from, int to);
/// -------------------------------------------------------------------------- /// --------------------------------------------------------------------------
/// ClipOpValue field with key 93. /// FillRule field with key 93.
int _clipOpValue = 0; int _fillRule = 0;
static const int clipOpValuePropertyKey = 93; static const int fillRulePropertyKey = 93;
/// Backing enum value for the clipping operation type (intersection or /// Backing enum value for the clipping fill rule (nonZero or evenOdd).
/// difference). int get fillRule => _fillRule;
int get clipOpValue => _clipOpValue;
/// Change the [_clipOpValue] field value. /// Change the [_fillRule] field value.
/// [clipOpValueChanged] will be invoked only if the field's value has /// [fillRuleChanged] will be invoked only if the field's value has changed.
/// changed. set fillRule(int value) {
set clipOpValue(int value) { if (_fillRule == value) {
if (_clipOpValue == value) {
return; return;
} }
int from = _clipOpValue; int from = _fillRule;
_clipOpValue = value; _fillRule = value;
clipOpValueChanged(from, value); fillRuleChanged(from, value);
} }
void clipOpValueChanged(int from, int to); void fillRuleChanged(int from, int to);
/// -------------------------------------------------------------------------- /// --------------------------------------------------------------------------
/// IsVisible field with key 94. /// IsVisible field with key 94.

View File

@ -241,25 +241,25 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
_drawables.clear(); _drawables.clear();
_rules.clear(); _rules.clear();
buildDrawOrder(_drawables, null, _rules); buildDrawOrder(_drawables, null, _rules);
Set<DrawTarget> rootRules = {}; var root = DrawTarget();
for (final nodeRules in _rules) { for (final nodeRules in _rules) {
for (final target in nodeRules.targets) { for (final target in nodeRules.targets) {
target.dependents.clear();
}
}
for (final nodeRules in _rules) {
for (final target in nodeRules.targets) {
root.dependents.add(target);
var dependentRules = target.drawable?.flattenedDrawRules; var dependentRules = target.drawable?.flattenedDrawRules;
if (dependentRules != null) { if (dependentRules != null) {
for (final dependentRule in dependentRules.targets) { for (final dependentRule in dependentRules.targets) {
dependentRule.dependents.add(target); dependentRule.dependents.add(target);
} }
} else {
rootRules.add(target);
} }
} }
} }
var sorter = DependencySorter<Component>(); var sorter = DependencySorter<Component>();
sorter.reset(); _sortedDrawRules = sorter.sort(root).cast<DrawTarget>().skip(1).toList();
if (rootRules.isNotEmpty) {
rootRules.forEach(sorter.visit);
}
_sortedDrawRules = sorter.order.cast<DrawTarget>();
sortDrawOrder(); sortDrawOrder();
} }

View File

@ -34,27 +34,7 @@ abstract class Drawable extends DrawableBase {
if (!clip.isVisible) { if (!clip.isVisible) {
continue; continue;
} }
var shape = clip.shape; canvas.clipPath(clip.clippingPath);
var fillInWorld = shape.fillInWorld;
if (!fillInWorld) {
canvas.transform(shape.worldTransform.mat4);
}
if (clip.clipOp == ClipOp.difference) {
var path = Path();
path.fillType = PathFillType.evenOdd;
path.addPath(artboard.path, Offset.zero);
path.addPath(clip.shape.fillPath, Offset.zero);
canvas.clipPath(path);
} else {
canvas.clipPath(clip.shape.fillPath);
}
if (!fillInWorld) {
assert(
clip.shapeInverseWorld != null,
'Expect shapeInverseWorld to have been '
'created by the time we draw');
canvas.transform(clip.shapeInverseWorld.mat4);
}
} }
return true; return true;
} }

View File

@ -1,62 +1,83 @@
import 'dart:ui';
import 'package:rive/src/rive_core/component_dirt.dart'; import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/math/mat2d.dart'; import 'package:rive/src/rive_core/node.dart';
import 'package:rive/src/rive_core/shapes/shape.dart'; import 'package:rive/src/rive_core/shapes/shape.dart';
import 'package:rive/src/generated/shapes/clipping_shape_base.dart'; import 'package:rive/src/generated/shapes/clipping_shape_base.dart';
export 'package:rive/src/generated/shapes/clipping_shape_base.dart'; export 'package:rive/src/generated/shapes/clipping_shape_base.dart';
enum ClipOp { intersection, difference }
class ClippingShape extends ClippingShapeBase { class ClippingShape extends ClippingShapeBase {
ClipOp get clipOp => ClipOp.values[clipOpValue]; final Path clippingPath = Path();
set clipOp(ClipOp value) => clipOpValue = value.index; final List<Shape> _shapes = [];
Mat2D _shapeInverseWorld; PathFillType get fillType => PathFillType.values[fillRule];
Mat2D get shapeInverseWorld => _shapeInverseWorld; set fillType(PathFillType type) => fillRule = type.index;
Shape _shape; Node _source;
Shape get shape => _shape; Node get source => _source;
set shape(Shape value) { set source(Node value) {
if (_shape == value) { if (_source == value) {
return; return;
} }
_shape = value; _source = value;
shapeId = value?.id; sourceId = value?.id;
} }
@override @override
void clipOpValueChanged(int from, int to) { void fillRuleChanged(int from, int to) {
parent?.addDirt(ComponentDirt.clip, recurse: true); parent?.addDirt(ComponentDirt.clip, recurse: true);
addDirt(ComponentDirt.path);
} }
@override @override
void shapeIdChanged(int from, int to) { void sourceIdChanged(int from, int to) {
shape = context?.resolve(to); _source = context?.resolve(to);
} }
@override @override
void onAddedDirty() { void onAddedDirty() {
super.onAddedDirty(); super.onAddedDirty();
if (shapeId != null) { if (sourceId != null) {
shape = context?.resolve(shapeId); _source = context?.resolve(sourceId);
} }
} }
@override @override
void buildDependencies() { void buildDependencies() {
super.buildDependencies(); super.buildDependencies();
shape?.addDependent(this); _shapes.clear();
_source?.forAll((component) {
if (component is Shape) {
_shapes.add(component);
component.pathComposer.addDependent(this);
}
return true;
});
addDirt(ComponentDirt.path);
}
@override
void onRemoved() {
super.onRemoved();
_shapes.clear();
} }
@override @override
void update(int dirt) { void update(int dirt) {
if (dirt & ComponentDirt.worldTransform != 0 && if (dirt & (ComponentDirt.worldTransform | ComponentDirt.path) != 0 &&
shape != null && source != null) {
!shape.fillInWorld) { clippingPath.reset();
_shapeInverseWorld ??= Mat2D(); clippingPath.fillType = fillType;
Mat2D.invert(_shapeInverseWorld, shape.worldTransform); for (final shape in _shapes) {
if (!shape.fillInWorld) {
clippingPath.addPath(shape.fillPath, Offset.zero,
matrix4: shape.worldTransform.mat4);
} else {
clippingPath.addPath(shape.fillPath, Offset.zero);
}
}
} }
} }
@override @override
void isVisibleChanged(bool from, bool to) { void isVisibleChanged(bool from, bool to) {
_shape?.addDirt(ComponentDirt.paint); _source?.addDirt(ComponentDirt.paint);
} }
} }

View File

@ -1,4 +1,4 @@
import 'package:flutter/material.dart'; import 'dart:ui';
import 'package:rive/src/rive_core/component_dirt.dart'; import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/shapes/shape_paint_container.dart'; import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
import 'package:rive/src/generated/shapes/paint/fill_base.dart'; import 'package:rive/src/generated/shapes/paint/fill_base.dart';

View File

@ -34,7 +34,7 @@ class Shape extends ShapeBase with ShapePaintContainer {
} }
void _markComposerDirty() { void _markComposerDirty() {
_pathComposer?.addDirt(ComponentDirt.path); _pathComposer?.addDirt(ComponentDirt.path, recurse: true);
invalidateStrokeEffects(); invalidateStrokeEffects();
} }

View File

@ -83,22 +83,7 @@ class TarjansDependencySorter<T extends DependencyGraphNode<T>>
if (!visit(root)) { if (!visit(root)) {
// if we detect cycles, go find them all // if we detect cycles, go find them all
_perm.clear(); findCycles(root);
_temp.clear();
_cycleNodes.clear();
_order.clear();
var cycles =
stronglyConnectedComponents<T>([root], (T node) => node.dependents);
cycles.forEach((cycle) {
// cycles of len 1 are not cycles.
if (cycle.length > 1) {
cycle.forEach((cycleMember) {
_cycleNodes.add(cycleMember);
});
}
});
// revisit the tree, skipping nodes on any cycle. // revisit the tree, skipping nodes on any cycle.
visit(root); visit(root);
@ -107,6 +92,26 @@ class TarjansDependencySorter<T extends DependencyGraphNode<T>>
return _order; return _order;
} }
HashSet<T> findCycles(T n) {
_perm.clear();
_temp.clear();
_cycleNodes.clear();
_order.clear();
var cycles =
stronglyConnectedComponents<T>([n], (T node) => node.dependents);
cycles.forEach((cycle) {
// cycles of len 1 are not cycles.
if (cycle.length > 1) {
cycle.forEach((cycleMember) {
_cycleNodes.add(cycleMember);
});
}
});
return _cycleNodes;
}
@override @override
bool visit(T n) { bool visit(T n) {
if (cycleNodes.contains(n)) { if (cycleNodes.contains(n)) {