mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-19 14:10:14 -05:00
Merge remote-tracking branch 'origin/master' into boolean-operations
Conflicts: src/path/PathItem.Boolean.js
This commit is contained in:
commit
5c184e381b
8 changed files with 122 additions and 98 deletions
|
@ -3,7 +3,7 @@ windowTitle = "$TM_DISPLAYNAME — Paper.js"
|
|||
excludeDirectories = "{$excludeDirectories,dist,build/jsdoc-toolkit,node_modules,bower_components,patch,projects}"
|
||||
|
||||
# JavaScript coding conventions
|
||||
showInvisibles = false
|
||||
invisiblesMap = "~ "
|
||||
softTabs = true
|
||||
tabSize = 4
|
||||
softWrap = false
|
||||
|
|
|
@ -702,7 +702,7 @@ var Point = Base.extend(/** @lends Point# */{
|
|||
* @returns {Boolean} {@true it is colinear}
|
||||
*/
|
||||
isColinear: function(point) {
|
||||
return Math.abs(this.cross(point)) < /*#=*/Numerical.TOLERANCE;
|
||||
return Math.abs(this.cross(point)) < /*#=*/Numerical.EPSILON;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -713,7 +713,7 @@ var Point = Base.extend(/** @lends Point# */{
|
|||
* @returns {Boolean} {@true it is orthogonal}
|
||||
*/
|
||||
isOrthogonal: function(point) {
|
||||
return Math.abs(this.dot(point)) < /*#=*/Numerical.TOLERANCE;
|
||||
return Math.abs(this.dot(point)) < /*#=*/Numerical.EPSILON;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -131,6 +131,18 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
|||
this._children[i].smooth();
|
||||
},
|
||||
|
||||
reduce: function reduce() {
|
||||
if (this._children.length === 0) { // Replace with a simple empty Path
|
||||
var path = new Path(Item.NO_INSERT);
|
||||
path.insertAbove(this);
|
||||
path.setStyle(this._style);
|
||||
this.remove();
|
||||
return path;
|
||||
} else {
|
||||
return reduce.base.call(this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Specifies whether the compound path is oriented clock-wise.
|
||||
*
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
||||
* http://paperjs.org/
|
||||
*
|
||||
|
@ -301,11 +301,6 @@ var Curve = Base.extend(/** @lends Curve# */{
|
|||
&& this._segment2._handleIn.isZero();
|
||||
},
|
||||
|
||||
isHorizontal: function() {
|
||||
return this.isLinear() && Numerical.isZero(
|
||||
this._segment1._point._y - this._segment2._point._y);
|
||||
},
|
||||
|
||||
// DOCS: Curve#getIntersections()
|
||||
getIntersections: function(curve) {
|
||||
return Curve.filterIntersections(Curve.getIntersections(
|
||||
|
@ -671,6 +666,18 @@ statics: {
|
|||
+ 1.5 * p2y * c1x + 3.0 * p2y * c2x) / 10;
|
||||
},
|
||||
|
||||
getEdgeSum: function(v) {
|
||||
// Method derived from:
|
||||
// http://stackoverflow.com/questions/1165647
|
||||
// We treat the curve points and handles as the outline of a polygon of
|
||||
// which we determine the orientation using the method of calculating
|
||||
// the sum over the edges. This will work even with non-convex polygons,
|
||||
// telling you whether it's mostly clockwise
|
||||
return (v[0] - v[2]) * (v[3] + v[1])
|
||||
+ (v[2] - v[4]) * (v[5] + v[3])
|
||||
+ (v[4] - v[6]) * (v[7] + v[5]);
|
||||
},
|
||||
|
||||
getBounds: function(v) {
|
||||
var min = v.slice(0, 2), // Start with values of point1
|
||||
max = min.slice(), // clone
|
||||
|
@ -705,9 +712,9 @@ statics: {
|
|||
b = 2 * (v0 + v2) - 4 * v1,
|
||||
c = v1 - v0,
|
||||
count = Numerical.solveQuadratic(a, b, c, roots),
|
||||
// Add some tolerance for good roots, as t = 0 / 1 are added
|
||||
// separately anyhow, and we don't want joins to be added with
|
||||
// radiuses in getStrokeBounds()
|
||||
// Add some tolerance for good roots, as t = 0, 1 are added
|
||||
// separately anyhow, and we don't want joins to be added with radii
|
||||
// in getStrokeBounds()
|
||||
tMin = /*#=*/Numerical.TOLERANCE,
|
||||
tMax = 1 - tMin;
|
||||
// Only add strokeWidth to bounds for points which lie within 0 < t < 1
|
||||
|
@ -1134,10 +1141,7 @@ new function() { // Scope for methods that require numerical integration
|
|||
parts[1], v1, curve2, curve1, locations, include,
|
||||
t, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion);
|
||||
}
|
||||
} else if (Math.max(uMax - uMin, tMaxNew - tMinNew) < tolerance / 2) {
|
||||
// NOTE: Not sure why we compare with half the tolerance here, but
|
||||
// it appears to be needed to fix issue #568 (intersection is found
|
||||
// twice): https://github.com/paperjs/paper.js/issues/568
|
||||
} else if (Math.max(uMax - uMin, tMaxNew - tMinNew) < tolerance) {
|
||||
// We have isolated the intersection with sufficient precision
|
||||
var t1 = tMinNew + (tMaxNew - tMinNew) / 2,
|
||||
t2 = uMin + (uMax - uMin) / 2;
|
||||
|
|
|
@ -1144,12 +1144,14 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
*/
|
||||
split: function(index, parameter) {
|
||||
if (parameter === null)
|
||||
return;
|
||||
return null;
|
||||
if (arguments.length === 1) {
|
||||
var arg = index;
|
||||
// split(offset), convert offset to location
|
||||
if (typeof arg === 'number')
|
||||
arg = this.getLocationAt(arg);
|
||||
if (!arg)
|
||||
return null
|
||||
// split(location)
|
||||
index = arg.index;
|
||||
parameter = arg.parameter;
|
||||
|
@ -1182,7 +1184,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
// Just have path point to this. The moving around of segments
|
||||
// will happen below.
|
||||
path = this;
|
||||
} else if (index > 0) {
|
||||
} else {
|
||||
// Pass true for _preserve, in case of CompoundPath, to avoid
|
||||
// reversing of path direction, which would mess with segs!
|
||||
// Use _clone to copy over all other attributes, including style
|
||||
|
@ -2415,7 +2417,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
x = pt.x,
|
||||
y = pt.y,
|
||||
abs = Math.abs,
|
||||
EPSILON = /*#=*/Numerical.EPSILON,
|
||||
epsilon = /*#=*/Numerical.EPSILON,
|
||||
rx = abs(radius.width),
|
||||
ry = abs(radius.height),
|
||||
rxSq = rx * rx,
|
||||
|
@ -2432,7 +2434,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
}
|
||||
factor = (rxSq * rySq - rxSq * ySq - rySq * xSq) /
|
||||
(rxSq * ySq + rySq * xSq);
|
||||
if (abs(factor) < EPSILON)
|
||||
if (abs(factor) < epsilon)
|
||||
factor = 0;
|
||||
if (factor < 0)
|
||||
throw new Error(
|
||||
|
@ -2610,19 +2612,10 @@ statics: {
|
|||
*/
|
||||
isClockwise: function(segments) {
|
||||
var sum = 0;
|
||||
// Method derived from:
|
||||
// http://stackoverflow.com/questions/1165647
|
||||
// We treat the curve points and handles as the outline of a polygon of
|
||||
// which we determine the orientation using the method of calculating
|
||||
// the sum over the edges. This will work even with non-convex polygons,
|
||||
// telling you whether it's mostly clockwise
|
||||
// TODO: Check if this works correctly for all open paths.
|
||||
for (var i = 0, l = segments.length; i < l; i++) {
|
||||
var v = Curve.getValues(
|
||||
segments[i], segments[i + 1 < l ? i + 1 : 0]);
|
||||
for (var j = 2; j < 8; j += 2)
|
||||
sum += (v[j - 2] - v[j]) * (v[j + 1] + v[j - 1]);
|
||||
}
|
||||
for (var i = 0, l = segments.length; i < l; i++)
|
||||
sum += Curve.getEdgeSum(Curve.getValues(
|
||||
segments[i], segments[i + 1 < l ? i + 1 : 0]));
|
||||
return sum > 0;
|
||||
},
|
||||
|
||||
|
|
|
@ -62,11 +62,10 @@ PathItem.inject(new function() {
|
|||
splitPath(_path1.getIntersections(_path2, null, true));
|
||||
|
||||
var chain = [],
|
||||
windings = [],
|
||||
lengths = [],
|
||||
segments = [],
|
||||
// Aggregate of all curves in both operands, monotonic in y
|
||||
monoCurves = [];
|
||||
monoCurves = [],
|
||||
tolerance = /*#=*/Numerical.TOLERANCE;
|
||||
|
||||
function collect(paths) {
|
||||
for (var i = 0, l = paths.length; i < l; i++) {
|
||||
|
@ -96,49 +95,59 @@ PathItem.inject(new function() {
|
|||
// 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 = windings.length = lengths.length = 0;
|
||||
var totalLength = 0,
|
||||
startSeg = segment;
|
||||
chain.length = 0;
|
||||
var startSeg = segment,
|
||||
totalLength = 0,
|
||||
windingSum = 0;
|
||||
do {
|
||||
chain.push(segment);
|
||||
lengths.push(totalLength += segment.getCurve().getLength());
|
||||
var length = segment.getCurve().getLength();
|
||||
chain.push({ segment: segment, length: length });
|
||||
totalLength += length;
|
||||
segment = segment.getNext();
|
||||
} while (segment && !segment._intersection && segment !== startSeg);
|
||||
// Select the median winding of three random points along this curve
|
||||
// chain, as a representative winding number. The random selection
|
||||
// gives a better chance of returning a correct winding than equally
|
||||
// dividing the curve chain, with the same (amortised) time.
|
||||
// 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++) {
|
||||
var length = totalLength * Math.random(),
|
||||
amount = lengths.length,
|
||||
k = 0;
|
||||
do {
|
||||
if (lengths[k] >= length) {
|
||||
if (k > 0)
|
||||
length -= lengths[k - 1];
|
||||
// Try the points at 1/4, 2/4 and 3/4 of the total length:
|
||||
var length = totalLength * (j + 1) / 4;
|
||||
for (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 <= tolerance
|
||||
|| curveLength - length <= tolerance)
|
||||
length = curveLength / 2;
|
||||
var curve = node.segment.getCurve(),
|
||||
pt = curve.getPointAt(length),
|
||||
// Determine if the curve is a horizontal linear
|
||||
// curve by checking the slope of it's tangent.
|
||||
hor = curve.isLinear() && Math.abs(curve
|
||||
.getTangentAt(0.5, true).y) <= tolerance,
|
||||
path = curve._path;
|
||||
if (path._parent instanceof CompoundPath)
|
||||
path = path._parent;
|
||||
// While subtracting, we need to omit this curve if this
|
||||
// curve is contributing to the second operand and is
|
||||
// outside the first operand.
|
||||
windingSum += subtract && _path2
|
||||
&& (path === _path1 && _path2._getWinding(pt, hor)
|
||||
|| path === _path2 && !_path1._getWinding(pt, hor))
|
||||
? 0
|
||||
: getWinding(pt, monoCurves, hor);
|
||||
break;
|
||||
}
|
||||
} while (++k < amount);
|
||||
var curve = chain[k].getCurve(),
|
||||
point = curve.getPointAt(length),
|
||||
hor = curve.isHorizontal(),
|
||||
path = curve._path;
|
||||
if (path._parent instanceof CompoundPath)
|
||||
path = path._parent;
|
||||
// While subtracting, we need to omit this curve if this
|
||||
// curve is contributing to the second operand and is outside
|
||||
// the first operand.
|
||||
windings[j] = subtract && _path2
|
||||
&& (path === _path1 && _path2._getWinding(point, hor)
|
||||
|| path === _path2 && !_path1._getWinding(point, hor))
|
||||
? 0
|
||||
: getWinding(point, monoCurves, hor);
|
||||
length -= curveLength;
|
||||
}
|
||||
}
|
||||
windings.sort();
|
||||
// Assign the median winding to the entire curve chain.
|
||||
var winding = windings[1];
|
||||
// Assign the average winding to the entire curve chain.
|
||||
var winding = Math.round(windingSum / 3);
|
||||
for (var j = chain.length - 1; j >= 0; j--)
|
||||
chain[j]._winding = winding;
|
||||
chain[j].segment._winding = winding;
|
||||
}
|
||||
// Trace closed contours and insert them into the result.
|
||||
var result = new CompoundPath();
|
||||
|
@ -222,9 +231,16 @@ PathItem.inject(new function() {
|
|||
* with respect to a given set of monotone curves.
|
||||
*/
|
||||
function getWinding(point, curves, horizontal, testContains) {
|
||||
var tolerance = /*#=*/Numerical.TOLERANCE,
|
||||
// We need to use a smaller tolerance here than in the rest of the
|
||||
// library when dealing with curve time parameters and coordinates, in
|
||||
// order to get really precise values for winding tests. 1e-7 was
|
||||
// determined through a lot of trial and error, and boolean-test suites.
|
||||
// Further decreasing it produces new errors.
|
||||
// The value of 1e-7 also solves issue #559:
|
||||
// https://github.com/paperjs/paper.js/issues/559
|
||||
var tolerance = 1e-7,
|
||||
tMin = tolerance,
|
||||
tMax = 1 - tolerance,
|
||||
tMax = 1 - tMin,
|
||||
x = point.x,
|
||||
y = point.y,
|
||||
windLeft = 0,
|
||||
|
@ -294,8 +310,8 @@ PathItem.inject(new function() {
|
|||
// curve merely touches the ray towards +-x direction, but
|
||||
// proceeds to the same side of the ray. This essentially is
|
||||
// not a crossing.
|
||||
if (abs(slope) < tolerance && !Curve.isLinear(values)
|
||||
|| t < tolerance && slope * Curve.evaluate(
|
||||
if (Numerical.isZero(slope) && !Curve.isLinear(values)
|
||||
|| t < tMin && slope * Curve.evaluate(
|
||||
curve.previous.values, t, 1).y < 0) {
|
||||
if (testContains && x0 >= xBefore && x0 <= xAfter) {
|
||||
++windLeft;
|
||||
|
@ -335,8 +351,8 @@ PathItem.inject(new function() {
|
|||
var paths = [],
|
||||
// Values for getTangentAt() that are almost 0 and 1.
|
||||
// TODO: Correctly support getTangentAt(0) / (1)?
|
||||
ZERO = 1e-3,
|
||||
ONE = 1 - 1e-3;
|
||||
tMin = /*#=*/Numerical.TOLERANCE,
|
||||
tMax = 1 - tMin;
|
||||
for (var i = 0, seg, startSeg, l = segments.length; i < l; i++) {
|
||||
seg = startSeg = segments[i];
|
||||
if (seg._visited || !operator(seg._winding))
|
||||
|
@ -367,14 +383,14 @@ PathItem.inject(new function() {
|
|||
var c1 = seg.getCurve();
|
||||
if (dir > 0)
|
||||
c1 = c1.getPrevious();
|
||||
var t1 = c1.getTangentAt(dir < 1 ? ZERO : ONE, true),
|
||||
var t1 = c1.getTangentAt(dir < 1 ? tMin : tMax, true),
|
||||
// Get both curves at the intersection (except the
|
||||
// entry curves).
|
||||
c4 = interSeg.getCurve(),
|
||||
c3 = c4.getPrevious(),
|
||||
// Calculate their winding values and tangents.
|
||||
t3 = c3.getTangentAt(ONE, true),
|
||||
t4 = c4.getTangentAt(ZERO, true),
|
||||
t3 = c3.getTangentAt(tMax, true),
|
||||
t4 = c4.getTangentAt(tMin, true),
|
||||
// Cross product of the entry and exit tangent
|
||||
// vectors at the intersection, will let us select
|
||||
// the correct contour to traverse next.
|
||||
|
@ -702,16 +718,19 @@ CompoundPath.inject(/** @lends CompoundPath# */{
|
|||
var children = this.removeChildren().sort(function(a, b) {
|
||||
return b.getBounds().getArea() - a.getBounds().getArea();
|
||||
});
|
||||
this.addChildren(children);
|
||||
var clockwise = children[0].isClockwise();
|
||||
for (var i = 1, l = children.length; i < l; i++) { // Skip first child
|
||||
var point = children[i].getInteriorPoint(),
|
||||
counters = 0;
|
||||
for (var j = i - 1; j >= 0; j--) {
|
||||
if (children[j].contains(point))
|
||||
counters++;
|
||||
if (children.length > 0) {
|
||||
this.addChildren(children);
|
||||
var clockwise = children[0].isClockwise();
|
||||
// Skip the first child
|
||||
for (var i = 1, l = children.length; i < l; i++) {
|
||||
var point = children[i].getInteriorPoint(),
|
||||
counters = 0;
|
||||
for (var j = i - 1; j >= 0; j--) {
|
||||
if (children[j].contains(point))
|
||||
counters++;
|
||||
}
|
||||
children[i].setClockwise(counters % 2 === 0 && clockwise);
|
||||
}
|
||||
children[i].setClockwise(counters % 2 === 0 && clockwise);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
length1 = curves1.length,
|
||||
length2 = path ? curves2.length : length1,
|
||||
values2 = [],
|
||||
tMin = /*#=*/Numerical.EPSILON,
|
||||
tMin = /*#=*/Numerical.TOLERANCE,
|
||||
tMax = 1 - tMin;
|
||||
// First check the bounds of the two paths. If they don't intersect,
|
||||
// we don't need to iterate through their curves.
|
||||
|
|
|
@ -64,7 +64,7 @@ var Numerical = new function() {
|
|||
PI = Math.PI,
|
||||
isFinite = Number.isFinite,
|
||||
TOLERANCE = 1e-5,
|
||||
EPSILON = 1e-12,
|
||||
EPSILON = 1e-13,
|
||||
MACHINE_EPSILON = 1.12e-16;
|
||||
|
||||
return /** @lends Numerical */{
|
||||
|
@ -229,13 +229,10 @@ var Numerical = new function() {
|
|||
// nRoots = D > MACHINE_EPSILON ? 2 : 1;
|
||||
}
|
||||
}
|
||||
var unbound = min == null,
|
||||
minE = min - MACHINE_EPSILON,
|
||||
maxE = max + MACHINE_EPSILON;
|
||||
if (isFinite(x1) && (unbound || (x1 >= minE && x1 <= maxE)))
|
||||
if (isFinite(x1) && (min == null || x1 >= min && x1 <= max))
|
||||
roots[nRoots++] = x1 < min ? min : x1 > max ? max : x1;
|
||||
if (x2 !== x1 && isFinite(x2)
|
||||
&& (unbound || (x2 >= minE && x2 <= maxE)))
|
||||
if (x2 !== x1
|
||||
&& isFinite(x2) && (min == null || x2 >= min && x2 <= max))
|
||||
roots[nRoots++] = x2 < min ? min : x2 > max ? max : x2;
|
||||
return nRoots;
|
||||
},
|
||||
|
@ -330,8 +327,7 @@ var Numerical = new function() {
|
|||
// The cubic has been deflated to a quadratic.
|
||||
var nRoots = Numerical.solveQuadratic(a, b1, c2, roots, min, max);
|
||||
if (isFinite(x) && (nRoots === 0 || x !== roots[nRoots - 1])
|
||||
&& (min == null || (x >= min - MACHINE_EPSILON
|
||||
&& x <= max + MACHINE_EPSILON)))
|
||||
&& (min == null || x >= min && x <= max))
|
||||
roots[nRoots++] = x < min ? min : x > max ? max : x;
|
||||
return nRoots;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue