mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
Merge branch 'winding-fix' into develop
; Conflicts: ; src/path/PathItem.Boolean.js
This commit is contained in:
commit
5a46620768
2 changed files with 101 additions and 94 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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]);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue