mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-03 19:45:44 -05:00
Change the way winding contributions are propagated
The new approach preserves segment sequence. Relates to #777
This commit is contained in:
parent
19c9a0e722
commit
d84a84c67f
3 changed files with 96 additions and 85 deletions
|
@ -619,7 +619,7 @@ statics: {
|
||||||
} else if (sy === -1) {
|
} else if (sy === -1) {
|
||||||
ty = tx;
|
ty = tx;
|
||||||
}
|
}
|
||||||
// Use average if we're within epsilon
|
// Use average if we're within curve-time epsilon
|
||||||
if (abs(tx - ty) < /*#=*/Numerical.CURVETIME_EPSILON)
|
if (abs(tx - ty) < /*#=*/Numerical.CURVETIME_EPSILON)
|
||||||
return (tx + ty) * 0.5;
|
return (tx + ty) * 0.5;
|
||||||
}
|
}
|
||||||
|
@ -1333,6 +1333,12 @@ new function() { // Scope for intersection using bezier fat-line clipping
|
||||||
t1 = res[0];
|
t1 = res[0];
|
||||||
t2 = res[1];
|
t2 = res[1];
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
var d1 = p1 ? p1.getDistance(Curve.getPoint(v1, t1)) : 0,
|
||||||
|
d2 = p2 ? p2.getDistance(Curve.getPoint(v2, t2)) : 0;
|
||||||
|
if (!Numerical.isZero(d1) || !Numerical.isZero(d2))
|
||||||
|
debugger;
|
||||||
|
*/
|
||||||
locations.push(
|
locations.push(
|
||||||
new CurveLocation(c1, t1, p1 || Curve.getPoint(v1, t1),
|
new CurveLocation(c1, t1, p1 || Curve.getPoint(v1, t1),
|
||||||
null, overlap,
|
null, overlap,
|
||||||
|
|
|
@ -70,6 +70,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
||||||
// TODO: Remove this once debug logging is removed.
|
// TODO: Remove this once debug logging is removed.
|
||||||
_intersection._other = true;
|
_intersection._other = true;
|
||||||
}
|
}
|
||||||
|
this._segment = null; // To be determined, see #getSegment()
|
||||||
// Also store references to segment1 and segment2, in case path
|
// Also store references to segment1 and segment2, in case path
|
||||||
// splitting / dividing is going to happen, in which case the segments
|
// splitting / dividing is going to happen, in which case the segments
|
||||||
// can be used to determine the new curves, see #getCurve(true)
|
// can be used to determine the new curves, see #getCurve(true)
|
||||||
|
|
|
@ -96,11 +96,9 @@ PathItem.inject(new function() {
|
||||||
// console.timeEnd('intersection');
|
// console.timeEnd('intersection');
|
||||||
splitPath(Curve._filterIntersections(locations, true));
|
splitPath(Curve._filterIntersections(locations, true));
|
||||||
|
|
||||||
var chain = [],
|
var segments = [],
|
||||||
segments = [],
|
|
||||||
// Aggregate of all curves in both operands, monotonic in y
|
// Aggregate of all curves in both operands, monotonic in y
|
||||||
monoCurves = [],
|
monoCurves = [];
|
||||||
epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON;
|
|
||||||
|
|
||||||
function collect(paths) {
|
function collect(paths) {
|
||||||
for (var i = 0, l = paths.length; i < l; i++) {
|
for (var i = 0, l = paths.length; i < l; i++) {
|
||||||
|
@ -116,91 +114,23 @@ PathItem.inject(new function() {
|
||||||
collect(_path2._children || [_path2]);
|
collect(_path2._children || [_path2]);
|
||||||
// Propagate the winding contribution. Winding contribution of curves
|
// Propagate the winding contribution. Winding contribution of curves
|
||||||
// does not change between two intersections.
|
// does not change between two intersections.
|
||||||
// First, sort all segments with an intersection to the beginning.
|
// First, propagate winding contributions for curve chains starting in
|
||||||
segments.sort(function(a, b) {
|
// all intersections:
|
||||||
var _a = a._intersection,
|
for (var i = 0, l = locations.length; i < l; i++) {
|
||||||
_b = b._intersection;
|
propagateWinding(locations[i]._segment, _path1, _path2, monoCurves,
|
||||||
return !_a && !_b || _a && _b ? 0 : _a ? -1 : 1;
|
operation);
|
||||||
});
|
}
|
||||||
|
// Now process the segments that are not part of any intersecting chains
|
||||||
for (var i = 0, l = segments.length; i < l; i++) {
|
for (var i = 0, l = segments.length; i < l; i++) {
|
||||||
var segment = segments[i];
|
var segment = segments[i];
|
||||||
if (segment._winding != null)
|
if (segment._winding == null) {
|
||||||
continue;
|
propagateWinding(segment, _path1, _path2, monoCurves,
|
||||||
// Here we try to determine the most probable winding number
|
operation);
|
||||||
// contribution for this curve-chain. Once we have enough confidence
|
|
||||||
// in the winding contribution, we can propagate it until the
|
|
||||||
// intersection or end of a curve chain.
|
|
||||||
chain.length = 0;
|
|
||||||
var startSeg = segment,
|
|
||||||
totalLength = 0,
|
|
||||||
windingSum = 0;
|
|
||||||
do {
|
|
||||||
var length = segment.getCurve().getLength();
|
|
||||||
chain.push({ segment: segment, length: length });
|
|
||||||
totalLength += length;
|
|
||||||
segment = segment.getNext();
|
|
||||||
} while (segment && !segment._intersection && segment !== startSeg);
|
|
||||||
// Calculate the average winding among three evenly distributed
|
|
||||||
// points along this curve chain as a representative winding number.
|
|
||||||
// This selection gives a better chance of returning a correct
|
|
||||||
// winding than equally dividing the curve chain, with the same
|
|
||||||
// (amortised) time.
|
|
||||||
for (var j = 0; j < 3; j++) {
|
|
||||||
// Try the points at 1/4, 2/4 and 3/4 of the total length:
|
|
||||||
var length = totalLength * (j + 1) / 4;
|
|
||||||
for (var k = 0, m = chain.length; k < m; k++) {
|
|
||||||
var node = chain[k],
|
|
||||||
curveLength = node.length;
|
|
||||||
if (length <= curveLength) {
|
|
||||||
// If the selected location on the curve falls onto its
|
|
||||||
// beginning or end, use the curve's center instead.
|
|
||||||
if (length < epsilon || curveLength - length < epsilon)
|
|
||||||
length = curveLength / 2;
|
|
||||||
var curve = node.segment.getCurve(),
|
|
||||||
pt = curve.getPointAt(length),
|
|
||||||
hor = isHorizontal(curve),
|
|
||||||
path = getMainPath(curve);
|
|
||||||
// While subtracting, we need to omit this curve if this
|
|
||||||
// curve is contributing to the second operand and is
|
|
||||||
// outside the first operand.
|
|
||||||
windingSum += operation === 'subtract' && _path2
|
|
||||||
&& (path === _path1 && _path2._getWinding(pt, hor)
|
|
||||||
|| path === _path2 && !_path1._getWinding(pt, hor))
|
|
||||||
? 0
|
|
||||||
: getWinding(pt, monoCurves, hor);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
length -= curveLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Assign the average winding to the entire curve chain.
|
|
||||||
var winding = Math.round(windingSum / 3);
|
|
||||||
for (var j = chain.length - 1; j >= 0; j--) {
|
|
||||||
var seg = chain[j].segment,
|
|
||||||
inter = seg._intersection,
|
|
||||||
wind = winding;
|
|
||||||
// We need to handle the edge cases of overlapping curves
|
|
||||||
// differently based on the type of operation, and adjust the
|
|
||||||
// winding number accordingly:
|
|
||||||
if (inter && inter._overlap) {
|
|
||||||
switch (operation) {
|
|
||||||
case 'unite':
|
|
||||||
if (wind === 1)
|
|
||||||
wind = 2;
|
|
||||||
break;
|
|
||||||
case 'intersect':
|
|
||||||
if (wind === 2)
|
|
||||||
wind = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
seg._winding = wind;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Trace closed contours and insert them into the result.
|
// Trace closed contours and insert them into the result.
|
||||||
var result = new CompoundPath(Item.NO_INSERT);
|
var result = new CompoundPath(Item.NO_INSERT);
|
||||||
result.addChildren(tracePaths(segments, monoCurves, operation, !_path2),
|
result.addChildren(tracePaths(segments, monoCurves, operation), true);
|
||||||
true);
|
|
||||||
// See if the CompoundPath can be reduced to just a simple Path.
|
// See if the CompoundPath can be reduced to just a simple Path.
|
||||||
result = result.reduce();
|
result = result.reduce();
|
||||||
// Insert the resulting path above whichever of the two paths appear
|
// Insert the resulting path above whichever of the two paths appear
|
||||||
|
@ -427,6 +357,80 @@ PathItem.inject(new function() {
|
||||||
return Math.max(abs(windLeft), abs(windRight));
|
return Math.max(abs(windLeft), abs(windRight));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function propagateWinding(segment, path1, path2, monoCurves, operation) {
|
||||||
|
// Here we try to determine the most probable winding number
|
||||||
|
// contribution for the curve-chain starting with this segment. Once we
|
||||||
|
// have enough confidence in the winding contribution, we can propagate
|
||||||
|
// it until the next intersection or end of a curve chain.
|
||||||
|
var epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON;
|
||||||
|
chain = [],
|
||||||
|
startSeg = segment,
|
||||||
|
totalLength = 0,
|
||||||
|
windingSum = 0;
|
||||||
|
do {
|
||||||
|
var length = segment.getCurve().getLength();
|
||||||
|
chain.push({ segment: segment, length: length });
|
||||||
|
totalLength += length;
|
||||||
|
segment = segment.getNext();
|
||||||
|
} while (segment && !segment._intersection && segment !== startSeg);
|
||||||
|
// Calculate the average winding among three evenly distributed
|
||||||
|
// points along this curve chain as a representative winding number.
|
||||||
|
// This selection gives a better chance of returning a correct
|
||||||
|
// winding than equally dividing the curve chain, with the same
|
||||||
|
// (amortised) time.
|
||||||
|
for (var i = 0; i < 3; i++) {
|
||||||
|
// Try the points at 1/4, 2/4 and 3/4 of the total length:
|
||||||
|
var length = totalLength * (i + 1) / 4;
|
||||||
|
for (var k = 0, m = chain.length; k < m; k++) {
|
||||||
|
var node = chain[k],
|
||||||
|
curveLength = node.length;
|
||||||
|
if (length <= curveLength) {
|
||||||
|
// If the selected location on the curve falls onto its
|
||||||
|
// beginning or end, use the curve's center instead.
|
||||||
|
if (length < epsilon || curveLength - length < epsilon)
|
||||||
|
length = curveLength / 2;
|
||||||
|
var curve = node.segment.getCurve(),
|
||||||
|
pt = curve.getPointAt(length),
|
||||||
|
hor = isHorizontal(curve),
|
||||||
|
path = getMainPath(curve);
|
||||||
|
// While subtracting, we need to omit this curve if this
|
||||||
|
// curve is contributing to the second operand and is
|
||||||
|
// outside the first operand.
|
||||||
|
windingSum += operation === 'subtract' && path2
|
||||||
|
&& (path === path1 && path2._getWinding(pt, hor)
|
||||||
|
|| path === path2 && !path1._getWinding(pt, hor))
|
||||||
|
? 0
|
||||||
|
: getWinding(pt, monoCurves, hor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
length -= curveLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Assign the average winding to the entire curve chain.
|
||||||
|
var winding = Math.round(windingSum / 3);
|
||||||
|
for (var j = chain.length - 1; j >= 0; j--) {
|
||||||
|
var seg = chain[j].segment,
|
||||||
|
inter = seg._intersection,
|
||||||
|
wind = winding;
|
||||||
|
// We need to handle the edge cases of overlapping curves
|
||||||
|
// differently based on the type of operation, and adjust the
|
||||||
|
// winding number accordingly:
|
||||||
|
if (inter && inter._overlap) {
|
||||||
|
switch (operation) {
|
||||||
|
case 'unite':
|
||||||
|
if (wind === 1)
|
||||||
|
wind = 2;
|
||||||
|
break;
|
||||||
|
case 'intersect':
|
||||||
|
if (wind === 2)
|
||||||
|
wind = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seg._winding = wind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var segmentOffset = {};
|
var segmentOffset = {};
|
||||||
var pathIndices = {};
|
var pathIndices = {};
|
||||||
var pathIndex = 0;
|
var pathIndex = 0;
|
||||||
|
@ -523,7 +527,7 @@ PathItem.inject(new function() {
|
||||||
for (var i = 0, seg, startSeg, l = segments.length; i < l; i++) {
|
for (var i = 0, seg, startSeg, l = segments.length; i < l; i++) {
|
||||||
seg = startSeg = segments[i];
|
seg = startSeg = segments[i];
|
||||||
if (seg._visited || !operator(seg._winding)) {
|
if (seg._visited || !operator(seg._winding)) {
|
||||||
drawSegment(seg, 'ignore', i, 'red');
|
drawSegment(seg, seg._visited ? 'visited' : 'ignore', i, 'red');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var path = new Path(Item.NO_INSERT),
|
var path = new Path(Item.NO_INSERT),
|
||||||
|
|
Loading…
Reference in a new issue