Merge branch 'winding-fix' into develop

; Conflicts:
;	src/path/PathItem.Boolean.js
This commit is contained in:
Jürg Lehni 2016-02-05 20:25:25 +01:00
commit 5a46620768
2 changed files with 101 additions and 94 deletions

View file

@ -316,7 +316,8 @@ PathItem.inject(new function() {
windRight = 0, windRight = 0,
length = curves.length, length = curves.length,
roots = [], roots = [],
abs = Math.abs; abs = Math.abs,
isOnCurve = false;
// Horizontal curves may return wrong results, since the curves are // Horizontal curves may return wrong results, since the curves are
// monotonic in y direction and this is an indeterminate state. // monotonic in y direction and this is an indeterminate state.
if (horizontal) { if (horizontal) {
@ -360,9 +361,9 @@ PathItem.inject(new function() {
// non-zero winding. // non-zero winding.
// Retrieve and use it here (See _getMonoCurve()). // Retrieve and use it here (See _getMonoCurve()).
if (curve.last) { if (curve.last) {
// Get the values of to the end x coordinate and winding of // Get the end x coordinate and winding of the last
// the last non-horizontal curve, which will be the previous // non-horizontal curve, which will be the previous
// non-horizontal curve for the first curve of the loop. // non-horizontal curve for the first curve in the loop.
prevWinding = curve.last.winding; prevWinding = curve.last.winding;
prevXEnd = curve.last.values[6]; prevXEnd = curve.last.values[6];
} }
@ -370,47 +371,51 @@ PathItem.inject(new function() {
// compare the endpoints of the curve to determine if the // compare the endpoints of the curve to determine if the
// ray from query point along +-x direction will intersect // ray from query point along +-x direction will intersect
// the monotone curve. // the monotone curve.
// Horizontal curves with winding == 0 will be completely if (py >= yStart && py <= yEnd || py >= yEnd && py <= yStart) {
// ignored. if (winding) {
if (winding && (py >= yStart && py <= yEnd // Calculate the x value for the ray's intersection.
|| py >= yEnd && py <= yStart)) { var x = py === yStart ? values[0]
// Calculate the x value for the ray's intersection. : py === yEnd ? values[6]
var x = py === yStart ? values[0] : Curve.solveCubic(values, 1, py, roots, 0, 1) === 1
: py === yEnd ? values[6]
: Curve.solveCubic(values, 1, py, roots, 0, 1) === 1
? Curve.getPoint(values, roots[0]).x ? Curve.getPoint(values, roots[0]).x
: null; : null;
if (x != null) { if (x != null) {
// Count the intersection of the ray with the // Test if the point is on the current mono-curve.
// monotonic curve if: if (x >= xBefore && x <= xAfter) {
// - the crossing is not at the start of the curve isOnCurve = true;
// - or the windings are opposite (intersect at a } else if (
// vertical extremum) // Count the intersection of the ray with the
// - or the start of the current curve and the end // monotonic curve if the crossing is not the
// of the prev curve are on opposite sides of px // start of the curve, except if the winding
var isWindingChange = winding === -prevWinding; // changes...
if (py !== yStart || isWindingChange (py !== yStart || winding !== prevWinding)
|| (x - px) * (prevXEnd - px) < 0) { // ...and the point is not on the curve or on
if (x < xBefore) { // the horizontal connection between the last
windLeft += winding; // non-horizontal curve's end point and the
} else if (x > xAfter) { // current curve's start point.
windRight += winding; && !(py === yStart
} else if (py === yStart && isWindingChange) { && (px - x) * (px - prevXEnd) < 0)) {
// The point is a vertical extremum of the if (x < xBefore) {
// path. windLeft += winding;
++windLeft; } else if (x > xAfter) {
++windRight; windRight += winding;
}
} }
} }
// Update previous winding and end coordinate whenever
// the ray intersects a non-horizontal curve.
prevWinding = winding;
prevXEnd = values[6];
// Test if the point is on the horizontal curve
} else if ((px - values[0]) * (px - values[6]) <= 0) {
isOnCurve = true;
} }
// Update previous winding and end coordinate whenever
// the ray intersects a non-horizontal curve.
prevWinding = winding;
prevXEnd = values[6];
} }
} }
} }
return Math.max(abs(windLeft), abs(windRight)); // If the point was on a monotonic curve, we are on the path by
// definition. In this case ensure that the winding is at least 1.
return Math.max(abs(windLeft), abs(windRight), isOnCurve ? 1 : 0);
} }
function propagateWinding(segment, path1, path2, monoCurves, operator) { function propagateWinding(segment, path1, path2, monoCurves, operator) {

View file

@ -216,67 +216,69 @@ test('Path#contains() (complex shape)', function() {
test('Path#contains() (straight curves with zero-winding)', function() { test('Path#contains() (straight curves with zero-winding)', function() {
var pathPoints = [ var pointData = [
[140, 100], [[250, 230], true, true, false, true],
[140, 10], [[200, 230], true, true, true, true],
[100, 10], [[200, 280], false, true, false, true],
[100, 20], [[190, 280], true, false, false, true],
[120, 20], [[175, 270], true, true, false, true],
[120, 40], [[175, 220], true, true, true, true],
[100, 40], [[175, 270], true, true, false, true],
[100, 60], [[160, 280], false, true, false, true],
[200, 60], [[150, 280], true, false, false, true],
[120, 60], [[150, 220], true, false, true, true],
[120, 100], [[150, 200], true, true, true, true],
[300, 100], [[100, 200], true, false, false, true],
[50, 100] [[100, 190], true, true, true, true],
[[50, 190], true, false, false, false],
[[100, 190], true, true, true, true],
[[100, 180], true, false, true, false],
[[150, 180], true, true, true, true],
[[150, 160], true, false, true, true],
[[150, 100], true, false, true, false],
[[160, 100], false, true, true, false],
[[175, 110], true, true, true, false],
[[175, 160], true, true, true, true],
[[175, 110], true, true, true, false],
[[190, 100], true, false, true, false],
[[200, 100], false, true, true, false],
[[200, 150], true, true, true, true],
[[250, 150], true, true, true, false],
[[270, 120], false, false, true, true],
[[270, 90], false, false, true, false],
[[270, 120], false, false, true, true],
[[290, 150], false, true, true, false],
[[290, 180], true, true, true, true],
[[340, 180], false, true, true, false],
[[340, 190], true, true, true, true],
[[390, 190], false, true, false, false],
[[340, 190], true, true, true, true],
[[340, 200], false, true, false, true],
[[290, 200], true, true, true, true],
[[290, 230], false, true, false, true],
[[270, 260], false, false, true, true],
[[270, 290], false, false, false, true],
[[270, 260], false, false, true, true],
[[250, 230], true, true, false, true]
]; ];
var path1 = new Path({ var points = [];
segments: pathPoints, for (var i = 0; i < pointData.length; i++) {
closed: true, points.push(pointData[i][0]);
fillRule: 'evenodd' }
}); var path = new paper.Path({segments: points, closed: true});
path.setWindingRule("evenodd");
var hitPoints = [ var offsetPoint = function(p, xOffs, yOffs) {
[[30,10], false], return new paper.Point(p.x + xOffs, p.y + yOffs);
[[110,10], true],
[[30,20], false],
[[110,20], true],
[[130,20], true],
[[170,20], false],
[[110,50], true],
[[30,60], false],
[[110,60], true],
[[130,60], true],
[[150,60], false],
[[230,60], false],
[[10,100], false],
[[60,100], false],
[[130,100], true],
[[170,100], false],
[[370,100], false]
];
for (var i = 0; i < hitPoints.length; i++) {
var entry = hitPoints[i];
testPoint(path1, new Point(entry[0]), entry[1]);
} }
// Now test the x-y-reversed shape for (var i = 0; i < pointData.length; i++) {
var p = new paper.Point(points[i]);
for (var i = 0; i < pathPoints.length; i++) { testPoint(path, p, true); // point is a segment of the path, must be inside
pathPoints[i].reverse(); testPoint(path, offsetPoint(p, 10, 0), pointData[i][1]);
} testPoint(path, offsetPoint(p, -10, 0), pointData[i][2]);
testPoint(path, offsetPoint(p, 0, 10), pointData[i][3]);
var path1 = new Path({ testPoint(path, offsetPoint(p, 0, -10), pointData[i][4]);
segments: pathPoints,
closed: true,
fillRule: 'evenodd'
});
for (var i = 0; i < hitPoints.length; i++) {
var entry = hitPoints[i];
testPoint(path1, new Point(entry[0].reverse()), entry[1]);
} }
}) })