mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-06 04:42:15 -05:00
Merge branch 'new-winding' into develop
This commit is contained in:
commit
0b672cfb62
6 changed files with 511 additions and 408 deletions
|
@ -618,6 +618,57 @@ statics: /** @lends Curve */{
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits the specified curve values into curves that are monotone in the
|
||||||
|
* specified coordinate direction.
|
||||||
|
*
|
||||||
|
* @param {Number[]} v the curve values, as returned by
|
||||||
|
* {@link Curve#getValues()}
|
||||||
|
* @param {Number} [dir=0] the direction in which the curves should be
|
||||||
|
* monotone, `0`: monotone in x-direction, `1`: monotone in y-direction
|
||||||
|
* @return {Number[][]} an array of curve value arrays of the resulting
|
||||||
|
* monotone curve. If the original curve was already monotone, an array
|
||||||
|
* only containing its values are returned.
|
||||||
|
*/
|
||||||
|
getMonoCurves: function(v, dir) {
|
||||||
|
var curves = [],
|
||||||
|
// Determine the ordinate index in the curve values array.
|
||||||
|
io = dir ? 0 : 1,
|
||||||
|
o0 = v[io],
|
||||||
|
o1 = v[io + 2],
|
||||||
|
o2 = v[io + 4],
|
||||||
|
o3 = v[io + 6];
|
||||||
|
if ((o0 >= o1) === (o1 >= o2) && (o1 >= o2) === (o2 >= o3)
|
||||||
|
|| Curve.isStraight(v)) {
|
||||||
|
// Straight curves and curves with all involved points ordered
|
||||||
|
// in coordinate direction are guaranteed to be monotone.
|
||||||
|
curves.push(v);
|
||||||
|
} else {
|
||||||
|
var a = 3 * (o1 - o2) - o0 + o3,
|
||||||
|
b = 2 * (o0 + o2) - 4 * o1,
|
||||||
|
c = o1 - o0,
|
||||||
|
tMin = 4e-7,
|
||||||
|
tMax = 1 - tMin,
|
||||||
|
roots = [],
|
||||||
|
n = Numerical.solveQuadratic(a, b, c, roots, tMin, tMax);
|
||||||
|
if (n === 0) {
|
||||||
|
curves.push(v);
|
||||||
|
} else {
|
||||||
|
roots.sort();
|
||||||
|
var t = roots[0],
|
||||||
|
parts = Curve.subdivide(v, t);
|
||||||
|
curves.push(parts[0]);
|
||||||
|
if (n > 1) {
|
||||||
|
t = (roots[1] - t) / (1 - t);
|
||||||
|
parts = Curve.subdivide(parts[1], t);
|
||||||
|
curves.push(parts[0]);
|
||||||
|
}
|
||||||
|
curves.push(parts[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return curves;
|
||||||
|
},
|
||||||
|
|
||||||
// Converts from the point coordinates (p1, c1, c2, p2) for one axis to
|
// Converts from the point coordinates (p1, c1, c2, p2) for one axis to
|
||||||
// the polynomial coefficients and solves the polynomial for val
|
// the polynomial coefficients and solves the polynomial for val
|
||||||
solveCubic: function (v, coord, val, roots, min, max) {
|
solveCubic: function (v, coord, val, roots, min, max) {
|
||||||
|
|
|
@ -144,8 +144,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
if (flags & /*#=*/ChangeFlag.GEOMETRY) {
|
if (flags & /*#=*/ChangeFlag.GEOMETRY) {
|
||||||
// Clockwise state becomes undefined as soon as geometry changes.
|
// Clockwise state becomes undefined as soon as geometry changes.
|
||||||
// Also clear cached mono curves used for winding calculations.
|
// Also clear cached mono curves used for winding calculations.
|
||||||
this._length = this._area = this._clockwise = this._monoCurves =
|
this._length = this._area = this._clockwise = undefined;
|
||||||
undefined;
|
|
||||||
if (flags & /*#=*/ChangeFlag.SEGMENTS) {
|
if (flags & /*#=*/ChangeFlag.SEGMENTS) {
|
||||||
this._version++; // See CurveLocation
|
this._version++; // See CurveLocation
|
||||||
} else if (this._curves) {
|
} else if (this._curves) {
|
||||||
|
|
|
@ -28,13 +28,16 @@
|
||||||
* http://hkrish.com/playground/paperjs/booleanStudy.html
|
* http://hkrish.com/playground/paperjs/booleanStudy.html
|
||||||
*/
|
*/
|
||||||
PathItem.inject(new function() {
|
PathItem.inject(new function() {
|
||||||
// Set up lookup tables for each operator, to decide if a given segment is
|
var min = Math.min,
|
||||||
// to be considered a part of the solution, or to be discarded, based on its
|
max = Math.max,
|
||||||
// winding contribution, as calculated by propagateWinding().
|
abs = Math.abs,
|
||||||
|
// Set up lookup tables for each operator, to decide if a given segment
|
||||||
|
// is to be considered a part of the solution, or to be discarded, based
|
||||||
|
// on its winding contribution, as calculated by propagateWinding().
|
||||||
// Boolean operators return true if a segment with the given winding
|
// Boolean operators return true if a segment with the given winding
|
||||||
// contribution contributes to the final result or not. They are applied to
|
// contribution contributes to the final result or not. They are applied
|
||||||
// for each segment after the paths are split at crossings.
|
// to for each segment after the paths are split at crossings.
|
||||||
var operators = {
|
operators = {
|
||||||
unite: { 1: true },
|
unite: { 1: true },
|
||||||
intersect: { 2: true },
|
intersect: { 2: true },
|
||||||
subtract: { 1: true },
|
subtract: { 1: true },
|
||||||
|
@ -52,7 +55,7 @@ PathItem.inject(new function() {
|
||||||
.transform(null, true, true);
|
.transform(null, true, true);
|
||||||
if (closed)
|
if (closed)
|
||||||
res.setClosed(true);
|
res.setClosed(true);
|
||||||
return closed ? res.resolveCrossings() : res;
|
return closed ? res.resolveCrossings().reorient() : res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createResult(ctor, paths, reduce, path1, path2) {
|
function createResult(ctor, paths, reduce, path1, path2) {
|
||||||
|
@ -97,14 +100,14 @@ PathItem.inject(new function() {
|
||||||
var crossings = divideLocations(
|
var crossings = divideLocations(
|
||||||
CurveLocation.expand(_path1.getCrossings(_path2))),
|
CurveLocation.expand(_path1.getCrossings(_path2))),
|
||||||
segments = [],
|
segments = [],
|
||||||
// Aggregate of all curves in both operands, monotonic in y.
|
// Aggregate of all curves in both operands.
|
||||||
monoCurves = [];
|
curves = [];
|
||||||
|
|
||||||
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++) {
|
||||||
var path = paths[i];
|
var path = paths[i];
|
||||||
segments.push.apply(segments, path._segments);
|
segments.push.apply(segments, path._segments);
|
||||||
monoCurves.push.apply(monoCurves, path._getMonoCurves());
|
curves.push.apply(curves, path.getCurves());
|
||||||
// Keep track if there are valid intersections other than
|
// Keep track if there are valid intersections other than
|
||||||
// overlaps in each path.
|
// overlaps in each path.
|
||||||
path._overlapsOnly = path._validOverlapsOnly = true;
|
path._overlapsOnly = path._validOverlapsOnly = true;
|
||||||
|
@ -120,7 +123,7 @@ PathItem.inject(new function() {
|
||||||
// First, propagate winding contributions for curve chains starting in
|
// First, propagate winding contributions for curve chains starting in
|
||||||
// all crossings:
|
// all crossings:
|
||||||
for (var i = 0, l = crossings.length; i < l; i++) {
|
for (var i = 0, l = crossings.length; i < l; i++) {
|
||||||
propagateWinding(crossings[i]._segment, _path1, _path2, monoCurves,
|
propagateWinding(crossings[i]._segment, _path1, _path2, curves,
|
||||||
operator);
|
operator);
|
||||||
}
|
}
|
||||||
// Now process the segments that are not part of any intersecting chains
|
// Now process the segments that are not part of any intersecting chains
|
||||||
|
@ -128,7 +131,7 @@ PathItem.inject(new function() {
|
||||||
var segment = segments[i],
|
var segment = segments[i],
|
||||||
inter = segment._intersection;
|
inter = segment._intersection;
|
||||||
if (segment._winding == null) {
|
if (segment._winding == null) {
|
||||||
propagateWinding(segment, _path1, _path2, monoCurves, operator);
|
propagateWinding(segment, _path1, _path2, curves, operator);
|
||||||
}
|
}
|
||||||
// See if there are any valid segments that aren't part of overlaps.
|
// See if there are any valid segments that aren't part of overlaps.
|
||||||
// This information is used to determine where to start tracing the
|
// This information is used to determine where to start tracing the
|
||||||
|
@ -222,6 +225,10 @@ PathItem.inject(new function() {
|
||||||
*
|
*
|
||||||
* @param {CurveLocation[]} locations an array of the locations to split the
|
* @param {CurveLocation[]} locations an array of the locations to split the
|
||||||
* path-item at.
|
* path-item at.
|
||||||
|
* @param {Function} [include] a function that determines if dividing should
|
||||||
|
* happen at a given location.
|
||||||
|
* @return {CurveLocation[]} the locations at which the involved path-items
|
||||||
|
* were divided
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function divideLocations(locations, include) {
|
function divideLocations(locations, include) {
|
||||||
|
@ -300,143 +307,221 @@ PathItem.inject(new function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private method that returns the winding contribution of the given point
|
* Returns the winding contribution number of the given point in respect
|
||||||
* with respect to a given set of monotonic curves.
|
* to the shapes described by the passed curves.
|
||||||
|
*
|
||||||
|
* See #1073#issuecomment-226942348 and #1073#issuecomment-226946965 for a
|
||||||
|
* detailed description of the approach developed by @iconexperience to
|
||||||
|
* precisely determine the winding contribution in all known edge cases.
|
||||||
|
*
|
||||||
|
* @param {Point} point the location for which to determine the winding
|
||||||
|
* contribution
|
||||||
|
* @param {Curve[]} curves the curves that describe the shape against which
|
||||||
|
* to check, as returned by {@link Path#getCurves()} or
|
||||||
|
* {@link CompoundPath#getCurves()}
|
||||||
|
* @param {Number} [dir=0] the direction in which to determine the
|
||||||
|
* winding contribution, `0`: in x-direction, `1`: in y-direction
|
||||||
|
* @return {Object} an object containing the calculated winding number, as
|
||||||
|
* well as an indication whether the point was situated on the contour
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
function getWinding(point, curves, horizontal) {
|
function getWinding(point, curves, dir) {
|
||||||
var epsilon = /*#=*/Numerical.WINDING_EPSILON,
|
var epsilon = /*#=*/Numerical.WINDING_EPSILON,
|
||||||
px = point.x,
|
// Determine the index of the abscissa and ordinate values in the
|
||||||
py = point.y,
|
// curve values arrays, based on the direction:
|
||||||
windLeft = 0,
|
ia = dir ? 1 : 0, // the abscissa index
|
||||||
windRight = 0,
|
io = dir ? 0 : 1, // the ordinate index
|
||||||
length = curves.length,
|
pv = [point.x, point.y],
|
||||||
roots = [],
|
pa = pv[ia], // the point's abscissa
|
||||||
abs = Math.abs;
|
po = pv[io], // the point's ordinate
|
||||||
// Horizontal curves may return wrong results, since the curves are
|
paL = pa - epsilon,
|
||||||
// monotonic in y direction and this is an indeterminate state.
|
paR = pa + epsilon,
|
||||||
if (horizontal) {
|
windingL = 0,
|
||||||
var yTop = -Infinity,
|
windingR = 0,
|
||||||
yBottom = Infinity,
|
pathWindingL = 0,
|
||||||
yBefore = py - epsilon,
|
pathWindingR = 0,
|
||||||
yAfter = py + epsilon;
|
onPathWinding = 0,
|
||||||
// Find the closest top and bottom intercepts for the vertical line.
|
isOnPath = false,
|
||||||
for (var i = 0; i < length; i++) {
|
vPrev,
|
||||||
var values = curves[i].values,
|
vClose;
|
||||||
count = Curve.solveCubic(values, 0, px, roots, 0, 1);
|
|
||||||
for (var j = count - 1; j >= 0; j--) {
|
function addWinding(v) {
|
||||||
var y = Curve.getPoint(values, roots[j]).y;
|
var o0 = v[io],
|
||||||
if (y < yBefore && y > yTop) {
|
o3 = v[io + 6];
|
||||||
yTop = y;
|
if (o0 > po && o3 > po || o0 < po && o3 < po) {
|
||||||
} else if (y > yAfter && y < yBottom) {
|
// If curve is outside the ordinates' range, no intersection
|
||||||
yBottom = y;
|
// with the ray is possible.
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
|
var a0 = v[ia],
|
||||||
|
a1 = v[ia + 2],
|
||||||
|
a2 = v[ia + 4],
|
||||||
|
a3 = v[ia + 6];
|
||||||
|
if (o0 === o3) {
|
||||||
|
// A horizontal curve is not necessarily between two non-
|
||||||
|
// horizontal curves. We have to take cases like these into
|
||||||
|
// account:
|
||||||
|
// +-----+
|
||||||
|
// +----+ |
|
||||||
|
// +-----+
|
||||||
|
if (a1 < paR && a3 > paL || a3 < paR && a1 > paL) {
|
||||||
|
isOnPath = true;
|
||||||
}
|
}
|
||||||
|
// If curve does not change in ordinate direction, windings will
|
||||||
|
// be added by adjacent curves.
|
||||||
|
return vPrev;
|
||||||
}
|
}
|
||||||
// Shift the point lying on the horizontal curves by half of the
|
var roots = [],
|
||||||
// closest top and bottom intercepts.
|
a = po === o0 ? a0
|
||||||
yTop = (yTop + py) / 2;
|
: po === o3 ? a3
|
||||||
yBottom = (yBottom + py) / 2;
|
: paL > max(a0, a1, a2, a3) || paR < min(a0, a1, a2, a3)
|
||||||
if (yTop > -Infinity)
|
? (a0 + a3) / 2
|
||||||
windLeft = getWinding(new Point(px, yTop), curves).winding;
|
: Curve.solveCubic(v, io, po, roots, 0, 1) === 1
|
||||||
if (yBottom < Infinity)
|
? Curve.getPoint(v, roots[0])[dir ? 'y' : 'x']
|
||||||
windRight = getWinding(new Point(px, yBottom), curves).winding;
|
: (a0 + a3) / 2;
|
||||||
|
var winding = o0 > o3 ? 1 : -1,
|
||||||
|
windingPrev = vPrev[io] > vPrev[io + 6] ? 1 : -1,
|
||||||
|
a3Prev = vPrev[ia + 6];
|
||||||
|
if (po !== o0) {
|
||||||
|
// Standard case, curve is crossed by not at its start point.
|
||||||
|
if (a < paL) {
|
||||||
|
pathWindingL += winding;
|
||||||
|
} else if (a > paR) {
|
||||||
|
pathWindingR += winding;
|
||||||
} else {
|
} else {
|
||||||
var xBefore = px - epsilon,
|
isOnPath = true;
|
||||||
xAfter = px + epsilon,
|
pathWindingL += winding;
|
||||||
prevWinding,
|
pathWindingR += winding;
|
||||||
prevXEnd,
|
}
|
||||||
// Separately count the windings for points on curves.
|
} else if (winding !== windingPrev) {
|
||||||
windLeftOnCurve = 0,
|
// Curve is crossed at start point and winding changes from
|
||||||
windRightOnCurve = 0,
|
// previous. Cancel winding contribution from previous curve.
|
||||||
isOnCurve = false;
|
if (a3Prev < paR) {
|
||||||
for (var i = 0; i < length; i++) {
|
pathWindingL += winding;
|
||||||
|
}
|
||||||
|
if (a3Prev > paL) {
|
||||||
|
pathWindingR += winding;
|
||||||
|
}
|
||||||
|
} else if (a3Prev < paL && a > paL || a3Prev > paR && a < paR) {
|
||||||
|
// Point is on a horizontal curve between the previous non-
|
||||||
|
// horizontal and the current curve.
|
||||||
|
isOnPath = true;
|
||||||
|
if (a3Prev < paL) {
|
||||||
|
// left winding was added before, now add right winding.
|
||||||
|
pathWindingR += winding;
|
||||||
|
} else if (a3Prev > paR) {
|
||||||
|
// right winding was added before, not add left winding.
|
||||||
|
pathWindingL += winding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCurve(v) {
|
||||||
|
// Get the ordinates:
|
||||||
|
var o0 = v[io],
|
||||||
|
o1 = v[io + 2],
|
||||||
|
o2 = v[io + 4],
|
||||||
|
o3 = v[io + 6];
|
||||||
|
// Only handle curves that can cross the point's ordinate.
|
||||||
|
if (po <= max(o0, o1, o2, o3) && po >= min(o0, o1, o2, o3)) {
|
||||||
|
// Get the abscissas:
|
||||||
|
var a0 = v[ia],
|
||||||
|
a1 = v[ia + 2],
|
||||||
|
a2 = v[ia + 4],
|
||||||
|
a3 = v[ia + 6],
|
||||||
|
// Get monotone curves. If the curve is outside the point's
|
||||||
|
// abscissa, it can be treated as a monotone curve:
|
||||||
|
monoCurves = paL > max(a0, a1, a2, a3) ||
|
||||||
|
paR < min(a0, a1, a2, a3)
|
||||||
|
? [v] : Curve.getMonoCurves(v, dir);
|
||||||
|
for (var i = 0, l = monoCurves.length; i < l; i++) {
|
||||||
|
vPrev = addWinding(monoCurves[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0, l = curves.length; i < l; i++) {
|
||||||
var curve = curves[i],
|
var curve = curves[i],
|
||||||
winding = curve.winding,
|
path = curve._path,
|
||||||
values = curve.values,
|
v = curve.getValues();
|
||||||
yStart = values[1],
|
if (i === 0 || curves[i - 1]._path !== path) {
|
||||||
yEnd = values[7];
|
// We're on a new (sub-)path, so we need to determine values of
|
||||||
// The first curve of a loop holds the last curve with non-zero
|
// the last non-horizontal curve on this path.
|
||||||
// winding. Retrieve and use it here (See _getMonoCurve()).
|
vPrev = null;
|
||||||
if (curve.last) {
|
// If the path is not closed, connect the end points with a
|
||||||
// Get the end x coordinate and winding of the last
|
// straight curve, just like how filling open paths works.
|
||||||
// non-horizontal curve, which will be the previous
|
if (!path._closed) {
|
||||||
// non-horizontal curve for the first curve in the loop.
|
var p1 = path.getLastCurve().getPoint2(),
|
||||||
prevWinding = curve.last.winding;
|
p2 = curve.getPoint1(),
|
||||||
prevXEnd = curve.last.values[6];
|
x1 = p1._x, y1 = p1._y,
|
||||||
// Reset the on curve flag for each loop.
|
x2 = p2._x, y2 = p2._y;
|
||||||
isOnCurve = false;
|
vClose = [x1, y1, x1, y1, x2, y2, x2, y2];
|
||||||
|
// This closing curve is a potential candidate for the last
|
||||||
|
// non-horizontal curve.
|
||||||
|
if (vClose[io] !== vClose[io + 6]) {
|
||||||
|
vPrev = vClose;
|
||||||
}
|
}
|
||||||
// Since the curves are monotonic in y direction, we can just
|
}
|
||||||
// compare the endpoints of the curve to determine if the ray
|
|
||||||
// from query point along +-x direction will intersect the
|
if (!vPrev) {
|
||||||
// monotonic curve.
|
// Walk backwards through list of the path's curves until we
|
||||||
if (py >= yStart && py <= yEnd || py >= yEnd && py <= yStart) {
|
// find one that is not horizontal.
|
||||||
if (winding) {
|
// Fall-back to the first curve's values if none is found:
|
||||||
// Calculate the x value for the ray's intersection.
|
vPrev = v;
|
||||||
var x = py === yStart ? values[0]
|
var prev = path.getLastCurve();
|
||||||
: py === yEnd ? values[6]
|
while (prev && prev !== curve) {
|
||||||
: Curve.solveCubic(values, 1, py, roots, 0, 1) === 1
|
var v2 = prev.getValues();
|
||||||
? Curve.getPoint(values, roots[0]).x
|
if (v2[io] !== v2[io + 6]) {
|
||||||
: null;
|
vPrev = v2;
|
||||||
if (x != null) {
|
break;
|
||||||
// Test if the point is on the current mono-curve.
|
}
|
||||||
if (x >= xBefore && x <= xAfter) {
|
prev = prev.getPrevious();
|
||||||
isOnCurve = true;
|
|
||||||
} else if (
|
|
||||||
// Count the intersection of the ray with the
|
|
||||||
// monotonic curve if the crossing is not the
|
|
||||||
// start of the curve, except if the winding
|
|
||||||
// changes...
|
|
||||||
(py !== yStart || winding !== prevWinding)
|
|
||||||
// ...and the point is not on the curve or on
|
|
||||||
// the horizontal connection between the last
|
|
||||||
// non-horizontal curve's end point and the
|
|
||||||
// current curve's start point.
|
|
||||||
&& !(py === yStart
|
|
||||||
&& (px - x) * (px - prevXEnd) < 0)) {
|
|
||||||
if (x < xBefore) {
|
|
||||||
windLeft += winding;
|
|
||||||
} else if (x > xAfter) {
|
|
||||||
windRight += winding;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update previous winding and end coordinate whenever
|
|
||||||
// the ray intersects a non-horizontal curve.
|
handleCurve(v);
|
||||||
prevWinding = winding;
|
|
||||||
prevXEnd = values[6];
|
if (i + 1 === l || curves[i + 1]._path !== path) {
|
||||||
// Test if the point is on the horizontal curve.
|
// We're at the last curve of the current (sub-)path. If a
|
||||||
} else if ((px - values[0]) * (px - values[6]) <= 0) {
|
// closing curve was calculated at the beginning of it, handle
|
||||||
isOnCurve = true;
|
// it now to treat the path as closed:
|
||||||
|
if (vClose) {
|
||||||
|
handleCurve(vClose);
|
||||||
|
vClose = null;
|
||||||
|
}
|
||||||
|
if (!pathWindingL && !pathWindingR && isOnPath) {
|
||||||
|
// Use the on-path windings if no other intersections
|
||||||
|
// were found or if they canceled each other.
|
||||||
|
var add = path.isClockwise() ? 1 : -1;
|
||||||
|
// windingL += add;
|
||||||
|
// windingR -= add;
|
||||||
|
onPathWinding += add;
|
||||||
|
} else {
|
||||||
|
windingL += pathWindingL;
|
||||||
|
windingR += pathWindingR;
|
||||||
|
pathWindingL = pathWindingR = 0;
|
||||||
|
}
|
||||||
|
isOnPath = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we are at the end of a loop and the point was on a curve
|
if (!windingL && !windingR) {
|
||||||
// of the loop, we increment / decrement the on-curve winding
|
windingL = windingR = onPathWinding;
|
||||||
// numbers as if the point was inside the path.
|
|
||||||
if (isOnCurve && (i >= length - 1 || curves[i + 1].last)) {
|
|
||||||
windLeftOnCurve += 1;
|
|
||||||
windRightOnCurve -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Use the on-curve windings if no other intersections were found or
|
|
||||||
// if they canceled each other. On single paths this ensures that
|
|
||||||
// the overall winding is 1 if the point was on a monotonic curve.
|
|
||||||
if (windLeft === 0 && windRight === 0) {
|
|
||||||
windLeft = windLeftOnCurve;
|
|
||||||
windRight = windRightOnCurve;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
windingL = windingL && (2 - abs(windingL) % 2);
|
||||||
|
windingR = windingR && (2 - abs(windingR) % 2);
|
||||||
// Return both the calculated winding contribution, and also detect if
|
// Return both the calculated winding contribution, and also detect if
|
||||||
// we are on the contour of the area by comparing windLeft & windRight.
|
// we are on the contour of the area by comparing windingL and windingR.
|
||||||
// This is required when handling unite operations, where a winding
|
// This is required when handling unite operations, where a winding
|
||||||
// contribution of 2 is not part of the result unless it's the contour:
|
// contribution of 2 is not part of the result unless it's the contour:
|
||||||
return {
|
return {
|
||||||
winding: Math.max(abs(windLeft), abs(windRight)),
|
winding: max(windingL, windingR),
|
||||||
contour: !windLeft ^ !windRight
|
contour: !windingL ^ !windingR
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function propagateWinding(segment, path1, path2, monoCurves, operator) {
|
function propagateWinding(segment, path1, path2, curves, operator) {
|
||||||
// Here we try to determine the most likely winding number contribution
|
// Here we try to determine the most likely winding number contribution
|
||||||
// for the curve-chain starting with this segment. Once we have enough
|
// for the curve-chain starting with this segment. Once we have enough
|
||||||
// confidence in the winding contribution, we can propagate it until the
|
// confidence in the winding contribution, we can propagate it until the
|
||||||
|
@ -463,17 +548,20 @@ PathItem.inject(new function() {
|
||||||
parent = path._parent,
|
parent = path._parent,
|
||||||
t = curve.getTimeAt(length),
|
t = curve.getTimeAt(length),
|
||||||
pt = curve.getPointAtTime(t),
|
pt = curve.getPointAtTime(t),
|
||||||
hor = Math.abs(curve.getTangentAtTime(t).y)
|
// Determine the direction in which to check the winding
|
||||||
< /*#=*/Numerical.TRIGONOMETRIC_EPSILON;
|
// from the point (horizontal or vertical), based on the
|
||||||
|
// curve's direction at that point.
|
||||||
|
dir = abs(curve.getTangentAtTime(t).normalize().y) < 0.5
|
||||||
|
? 1 : 0;
|
||||||
if (parent instanceof CompoundPath)
|
if (parent instanceof CompoundPath)
|
||||||
path = parent;
|
path = parent;
|
||||||
// While subtracting, we need to omit this curve if it is
|
// While subtracting, we need to omit this curve if it is
|
||||||
// contributing to the second operand and is outside the
|
// contributing to the second operand and is outside the
|
||||||
// first operand.
|
// first operand.
|
||||||
winding = !(operator.subtract && path2 && (
|
winding = !(operator.subtract && path2 && (
|
||||||
path === path1 && path2._getWinding(pt, hor) ||
|
path === path1 && path2._getWinding(pt, dir) ||
|
||||||
path === path2 && !path1._getWinding(pt, hor)))
|
path === path2 && !path1._getWinding(pt, dir)))
|
||||||
? getWinding(pt, monoCurves, hor)
|
? getWinding(pt, curves, dir)
|
||||||
: { winding: 0 };
|
: { winding: 0 };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -545,6 +633,17 @@ PathItem.inject(new function() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort segments to give non-ambiguous segments the preference as
|
||||||
|
// starting points when tracing: prefer segments with no intersections
|
||||||
|
// over intersections, and process intersections with overlaps last:
|
||||||
|
segments.sort(function(a, b) {
|
||||||
|
var i1 = a._intersection,
|
||||||
|
i2 = b._intersection,
|
||||||
|
o1 = !!(i1 && i1._overlap),
|
||||||
|
o2 = !!(i2 && i2._overlap);
|
||||||
|
return !i1 && !i2 ? -1 : o1 ^ o2 ? o1 ? 1 : -1 : 0;
|
||||||
|
});
|
||||||
|
|
||||||
for (var i = 0, l = segments.length; i < l; i++) {
|
for (var i = 0, l = segments.length; i < l; i++) {
|
||||||
var path = null,
|
var path = null,
|
||||||
finished = false,
|
finished = false,
|
||||||
|
@ -579,8 +678,7 @@ PathItem.inject(new function() {
|
||||||
// contribution but are part of the contour (excludeContour=true).
|
// contribution but are part of the contour (excludeContour=true).
|
||||||
// - Do not start in overlaps, unless all segments are part of
|
// - Do not start in overlaps, unless all segments are part of
|
||||||
// overlaps, in which case we have no other choice.
|
// overlaps, in which case we have no other choice.
|
||||||
if (!isValid(seg, true)
|
if (!isValid(seg, true))
|
||||||
|| !seg._path._validOverlapsOnly && inter && inter._overlap)
|
|
||||||
continue;
|
continue;
|
||||||
start = otherStart = null;
|
start = otherStart = null;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -657,7 +755,7 @@ PathItem.inject(new function() {
|
||||||
// location, but the winding calculation still produces a valid
|
// location, but the winding calculation still produces a valid
|
||||||
// number due to their slight differences producing a tiny area.
|
// number due to their slight differences producing a tiny area.
|
||||||
var area = path.getArea(true);
|
var area = path.getArea(true);
|
||||||
if (Math.abs(area) >= /*#=*/Numerical.GEOMETRIC_EPSILON) {
|
if (abs(area) >= /*#=*/Numerical.GEOMETRIC_EPSILON) {
|
||||||
// This path wasn't finished and is hence invalid.
|
// This path wasn't finished and is hence invalid.
|
||||||
// Report the error to the console for the time being.
|
// Report the error to the console for the time being.
|
||||||
console.error('Boolean operation resulted in open path',
|
console.error('Boolean operation resulted in open path',
|
||||||
|
@ -682,17 +780,17 @@ PathItem.inject(new function() {
|
||||||
|
|
||||||
return /** @lends PathItem# */{
|
return /** @lends PathItem# */{
|
||||||
/**
|
/**
|
||||||
* Returns the winding contribution of the given point with respect to
|
* Returns the winding contribution number of the given point in respect
|
||||||
* this PathItem.
|
* to this PathItem.
|
||||||
*
|
*
|
||||||
* @param {Point} point the location for which to determine the winding
|
* @param {Point} point the location for which to determine the winding
|
||||||
* direction
|
* contribution
|
||||||
* @param {Boolean} horizontal whether we need to consider this point as
|
* @param {Number} [dir=0] the direction in which to determine the
|
||||||
* part of a horizontal curve
|
* winding contribution, `0`: in x-direction, `1`: in y-direction
|
||||||
* @return {Number} the winding number
|
* @return {Number} the winding number
|
||||||
*/
|
*/
|
||||||
_getWinding: function(point, horizontal) {
|
_getWinding: function(point, dir) {
|
||||||
return getWinding(point, this._getMonoCurves(), horizontal).winding;
|
return getWinding(point, this.getCurves(), dir).winding;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -756,17 +854,13 @@ PathItem.inject(new function() {
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Resolves all crossings of a path item, first by splitting the path or
|
* Resolves all crossings of a path item by splitting the path or
|
||||||
* compound-path in each self-intersection and tracing the result, then
|
* compound-path in each self-intersection and tracing the result.
|
||||||
* fixing the orientation of the resulting sub-paths by making sure that
|
|
||||||
* all sub-paths are of different winding direction than the first path,
|
|
||||||
* except for when individual sub-paths are disjoint, i.e. islands,
|
|
||||||
* which are reoriented so that:
|
|
||||||
* - The holes have opposite winding direction.
|
|
||||||
* - Islands have to have the same winding direction as the first child.
|
|
||||||
* If possible, the existing path / compound-path is modified if the
|
* If possible, the existing path / compound-path is modified if the
|
||||||
* amount of resulting paths allows so, otherwise a new path /
|
* amount of resulting paths allows so, otherwise a new path /
|
||||||
* compound-path is created, replacing the current one.
|
* compound-path is created, replacing the current one.
|
||||||
|
*
|
||||||
|
* @return {PahtItem} the resulting path item
|
||||||
*/
|
*/
|
||||||
resolveCrossings: function() {
|
resolveCrossings: function() {
|
||||||
var children = this._children,
|
var children = this._children,
|
||||||
|
@ -783,8 +877,8 @@ PathItem.inject(new function() {
|
||||||
var hasOverlaps = false,
|
var hasOverlaps = false,
|
||||||
hasCrossings = false,
|
hasCrossings = false,
|
||||||
intersections = this.getIntersections(null, function(inter) {
|
intersections = this.getIntersections(null, function(inter) {
|
||||||
return inter._overlap && (hasOverlaps = true)
|
return inter._overlap && (hasOverlaps = true) ||
|
||||||
|| inter.isCrossing() && (hasCrossings = true);
|
inter.isCrossing() && (hasCrossings = true);
|
||||||
});
|
});
|
||||||
intersections = CurveLocation.expand(intersections);
|
intersections = CurveLocation.expand(intersections);
|
||||||
if (hasOverlaps) {
|
if (hasOverlaps) {
|
||||||
|
@ -834,72 +928,11 @@ PathItem.inject(new function() {
|
||||||
this.push.apply(this, path._segments);
|
this.push.apply(this, path._segments);
|
||||||
}, []));
|
}, []));
|
||||||
}
|
}
|
||||||
// By now, all paths are non-overlapping, but might be fully
|
// Determine how to return the paths: First try to recycle the
|
||||||
// contained inside each other.
|
// current path / compound-path, if the amount of paths does not
|
||||||
// Next we adjust their orientation based on on further checks:
|
// require a conversion.
|
||||||
var length = paths.length,
|
var length = paths.length,
|
||||||
item;
|
item;
|
||||||
if (length > 1) {
|
|
||||||
// First order the paths by the area of their bounding boxes.
|
|
||||||
// Make a clone of paths as it may still be the children array.
|
|
||||||
paths = paths.slice().sort(function (a, b) {
|
|
||||||
return b.getBounds().getArea() - a.getBounds().getArea();
|
|
||||||
});
|
|
||||||
var first = paths[0],
|
|
||||||
items = [first],
|
|
||||||
excluded = {},
|
|
||||||
isNonZero = this.getFillRule() === 'nonzero',
|
|
||||||
windings = isNonZero && Base.each(paths, function(path) {
|
|
||||||
this.push(path.isClockwise() ? 1 : -1);
|
|
||||||
}, []);
|
|
||||||
// Walk through paths, from largest to smallest.
|
|
||||||
// The first, largest child can be skipped.
|
|
||||||
for (var i = 1; i < length; i++) {
|
|
||||||
var path = paths[i],
|
|
||||||
point = path.getInteriorPoint(),
|
|
||||||
isContained = false,
|
|
||||||
container = null,
|
|
||||||
exclude = false;
|
|
||||||
for (var j = i - 1; j >= 0 && !container; j--) {
|
|
||||||
// We run through the paths from largest to smallest,
|
|
||||||
// meaning that for any current path, all potentially
|
|
||||||
// containing paths have already been processed and
|
|
||||||
// their orientation has been fixed. Since we want to
|
|
||||||
// achieve alternating orientation of contained paths,
|
|
||||||
// all we have to do is to find one include path that
|
|
||||||
// contains the current path, and then set the
|
|
||||||
// orientation to the opposite of the containing path.
|
|
||||||
if (paths[j].contains(point)) {
|
|
||||||
if (isNonZero && !isContained) {
|
|
||||||
windings[i] += windings[j];
|
|
||||||
// Remove path if rule is nonzero and winding
|
|
||||||
// of path and containing path is not zero.
|
|
||||||
if (windings[i] && windings[j]) {
|
|
||||||
exclude = excluded[i] = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isContained = true;
|
|
||||||
// If the containing path is not excluded, we're
|
|
||||||
// done searching for the orientation defining path.
|
|
||||||
container = !excluded[j] && paths[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!exclude) {
|
|
||||||
// Set to the opposite orientation of containing path,
|
|
||||||
// or the same orientation as the first path if the path
|
|
||||||
// is not contained in any other path.
|
|
||||||
path.setClockwise(container ? !container.isClockwise()
|
|
||||||
: first.isClockwise());
|
|
||||||
items.push(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Replace paths with the processed items list:
|
|
||||||
paths = items;
|
|
||||||
length = items.length;
|
|
||||||
}
|
|
||||||
// First try to recycle the current path / compound-path, if the
|
|
||||||
// amount of paths do not require a conversion.
|
|
||||||
if (length > 1 && children) {
|
if (length > 1 && children) {
|
||||||
if (paths !== children) {
|
if (paths !== children) {
|
||||||
// TODO: Fix automatic child-orientation in CompoundPath,
|
// TODO: Fix automatic child-orientation in CompoundPath,
|
||||||
|
@ -922,111 +955,79 @@ PathItem.inject(new function() {
|
||||||
this.replaceWith(item);
|
this.replaceWith(item);
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
},
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
Path.inject(/** @lends Path# */{
|
|
||||||
/**
|
/**
|
||||||
* Private method that returns and caches all the curves in this Path,
|
* Fixes the orientation of the sub-paths of a compound-path, by first
|
||||||
* which are monotonically decreasing or increasing in the y-direction.
|
* ordering them according to the area they cover, and then making sure
|
||||||
* Used by getWinding().
|
* that all sub-paths are of different winding direction than the first,
|
||||||
|
* biggest path, except for when individual sub-paths are disjoint,
|
||||||
|
* i.e. islands, which are reoriented so that:
|
||||||
|
*
|
||||||
|
* - The holes have opposite winding direction.
|
||||||
|
* - Islands have to have the same winding direction as the first child.
|
||||||
|
*
|
||||||
|
* @return {PahtItem} a reference to the item itself, reoriented
|
||||||
*/
|
*/
|
||||||
_getMonoCurves: function() {
|
reorient: function() {
|
||||||
var monoCurves = this._monoCurves,
|
var children = this._children;
|
||||||
last;
|
if (children && children.length > 1) {
|
||||||
|
// First order the paths by their areas.
|
||||||
// Insert curve values into a cached array
|
children = this.removeChildren().sort(function (a, b) {
|
||||||
function insertCurve(v) {
|
return abs(b.getArea()) - abs(a.getArea());
|
||||||
var y0 = v[1],
|
});
|
||||||
y1 = v[7],
|
var first = children[0],
|
||||||
// Look at the slope of the line between the mono-curve's anchor
|
paths = [first],
|
||||||
// points with some tolerance to decide if it is horizontal.
|
excluded = {},
|
||||||
winding = Math.abs((y0 - y1) / (v[0] - v[6]))
|
isNonZero = this.getFillRule() === 'nonzero',
|
||||||
< /*#=*/Numerical.GEOMETRIC_EPSILON
|
windings = isNonZero && Base.each(children, function(path) {
|
||||||
? 0 // Horizontal
|
this.push(path.isClockwise() ? 1 : -1);
|
||||||
: y0 > y1
|
}, []);
|
||||||
? -1 // Decreasing
|
// Walk through children, from largest to smallest.
|
||||||
: 1, // Increasing
|
// The first, largest child can be skipped.
|
||||||
curve = { values: v, winding: winding };
|
for (var i = 1, l = children.length; i < l; i++) {
|
||||||
monoCurves.push(curve);
|
var path = children[i],
|
||||||
// Keep track of the last non-horizontal curve (with winding).
|
point = path.getInteriorPoint(),
|
||||||
if (winding)
|
isContained = false,
|
||||||
last = curve;
|
container = null,
|
||||||
}
|
exclude = false;
|
||||||
|
for (var j = i - 1; j >= 0 && !container; j--) {
|
||||||
// Handle bezier curves. We need to chop them into smaller curves with
|
// We run through the paths from largest to smallest,
|
||||||
// defined orientation, by solving the derivative curve for y extrema.
|
// meaning that for any current path, all potentially
|
||||||
function handleCurve(v) {
|
// containing paths have already been processed and
|
||||||
// Filter out curves of zero length.
|
// their orientation has been fixed. Since we want to
|
||||||
// TODO: Do not filter this here.
|
// achieve alternating orientation of contained paths,
|
||||||
if (Curve.getLength(v) === 0)
|
// all we have to do is to find one include path that
|
||||||
return;
|
// contains the current path, and then set the
|
||||||
var y0 = v[1],
|
// orientation to the opposite of the containing path.
|
||||||
y1 = v[3],
|
if (children[j].contains(point)) {
|
||||||
y2 = v[5],
|
if (isNonZero && !isContained) {
|
||||||
y3 = v[7];
|
windings[i] += windings[j];
|
||||||
if (Curve.isStraight(v)
|
// Remove path if rule is nonzero and winding
|
||||||
|| y0 >= y1 === y1 >= y2 && y1 >= y2 === y2 >= y3) {
|
// of path and containing path is not zero.
|
||||||
// Straight curves and curves with end and control points sorted
|
if (windings[i] && windings[j]) {
|
||||||
// in y direction are guaranteed to be monotonic in y direction.
|
exclude = excluded[i] = true;
|
||||||
insertCurve(v);
|
break;
|
||||||
} else {
|
|
||||||
// Split the curve at y extrema, to get bezier curves with clear
|
|
||||||
// orientation: Calculate the derivative and find its roots.
|
|
||||||
var a = 3 * (y1 - y2) - y0 + y3,
|
|
||||||
b = 2 * (y0 + y2) - 4 * y1,
|
|
||||||
c = y1 - y0,
|
|
||||||
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
|
||||||
tMax = 1 - tMin,
|
|
||||||
roots = [],
|
|
||||||
// Keep then range to 0 .. 1 (excluding) in the search for y
|
|
||||||
// extrema.
|
|
||||||
n = Numerical.solveQuadratic(a, b, c, roots, tMin, tMax);
|
|
||||||
if (n < 1) {
|
|
||||||
insertCurve(v);
|
|
||||||
} else {
|
|
||||||
roots.sort();
|
|
||||||
var t = roots[0],
|
|
||||||
parts = Curve.subdivide(v, t);
|
|
||||||
insertCurve(parts[0]);
|
|
||||||
if (n > 1) {
|
|
||||||
// If there are two extrema, renormalize t to the range
|
|
||||||
// of the second range and split again.
|
|
||||||
t = (roots[1] - t) / (1 - t);
|
|
||||||
// Since we already processed parts[0], we can override
|
|
||||||
// the parts array with the new pair now.
|
|
||||||
parts = Curve.subdivide(parts[1], t);
|
|
||||||
insertCurve(parts[0]);
|
|
||||||
}
|
|
||||||
insertCurve(parts[1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
isContained = true;
|
||||||
|
// If the containing path is not excluded, we're
|
||||||
if (!monoCurves) {
|
// done searching for the orientation defining path.
|
||||||
// Insert curves that are monotonic in y direction into cached array
|
container = !excluded[j] && children[j];
|
||||||
monoCurves = this._monoCurves = [];
|
|
||||||
var curves = this.getCurves(),
|
|
||||||
segments = this._segments;
|
|
||||||
for (var i = 0, l = curves.length; i < l; i++)
|
|
||||||
handleCurve(curves[i].getValues());
|
|
||||||
// If the path is not closed, we need to join the end points with a
|
|
||||||
// straight line, just like how filling open paths works.
|
|
||||||
if (!this._closed && segments.length > 1) {
|
|
||||||
var p1 = segments[segments.length - 1]._point,
|
|
||||||
p2 = segments[0]._point,
|
|
||||||
p1x = p1._x, p1y = p1._y,
|
|
||||||
p2x = p2._x, p2y = p2._y;
|
|
||||||
handleCurve([p1x, p1y, p1x, p1y, p2x, p2y, p2x, p2y]);
|
|
||||||
}
|
|
||||||
if (monoCurves.length > 0) {
|
|
||||||
// Add information about the last curve with non-zero winding,
|
|
||||||
// as required in getWinding().
|
|
||||||
monoCurves[0].last = last;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return monoCurves;
|
if (!exclude) {
|
||||||
|
// Set to the opposite orientation of containing path,
|
||||||
|
// or the same orientation as the first path if the path
|
||||||
|
// is not contained in any other path.
|
||||||
|
path.setClockwise(container ? !container.isClockwise()
|
||||||
|
: first.isClockwise());
|
||||||
|
paths.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setChildren(paths, true); // Preserve orientation
|
||||||
|
}
|
||||||
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1040,42 +1041,47 @@ Path.inject(/** @lends Path# */{
|
||||||
point = bounds.getCenter(true);
|
point = bounds.getCenter(true);
|
||||||
if (!this.contains(point)) {
|
if (!this.contains(point)) {
|
||||||
// Since there is no guarantee that a poly-bezier path contains
|
// Since there is no guarantee that a poly-bezier path contains
|
||||||
// the center of its bounding rectangle, we shoot a ray in
|
// the center of its bounding rectangle, we shoot a ray in x
|
||||||
// +x direction from the center and select a point between
|
// direction and select a point between the first consecutive
|
||||||
// consecutive intersections of the ray.
|
// intersections of the ray on the left.
|
||||||
var curves = this._getMonoCurves(),
|
var curves = this.getCurves(),
|
||||||
roots = [],
|
|
||||||
y = point.y,
|
y = point.y,
|
||||||
intercepts = [];
|
intercepts = [],
|
||||||
|
roots = [];
|
||||||
|
// Process all y-monotone curves that intersect the ray at y:
|
||||||
for (var i = 0, l = curves.length; i < l; i++) {
|
for (var i = 0, l = curves.length; i < l; i++) {
|
||||||
var values = curves[i].values;
|
var v = curves[i].getValues(),
|
||||||
if (curves[i].winding === 1
|
o0 = v[1],
|
||||||
&& y > values[1] && y <= values[7]
|
o1 = v[3],
|
||||||
|| y >= values[7] && y < values[1]) {
|
o2 = v[5],
|
||||||
var count = Curve.solveCubic(values, 1, y, roots, 0, 1);
|
o3 = v[7];
|
||||||
for (var j = count - 1; j >= 0; j--) {
|
if (y >= min(o0, o1, o2, o3) && y <= max(o0, o1, o2, o3)) {
|
||||||
intercepts.push(Curve.getPoint(values, roots[j]).x);
|
var monos = Curve.getMonoCurves(v);
|
||||||
|
for (var j = 0, m = monos.length; j < m; j++) {
|
||||||
|
var mv = monos[j],
|
||||||
|
mo0 = mv[1],
|
||||||
|
mo3 = mv[7];
|
||||||
|
// Only handle curves that are not horizontal and
|
||||||
|
// that can cross the point's ordinate.
|
||||||
|
if ((mo0 !== mo3) &&
|
||||||
|
(y >= mo0 && y <= mo3 || y >= mo3 && y <= mo0)){
|
||||||
|
var x = y === mo0 ? mv[0]
|
||||||
|
: y === mo3 ? mv[6]
|
||||||
|
: Curve.solveCubic(mv, 1, y, roots, 0, 1)
|
||||||
|
=== 1
|
||||||
|
? Curve.getPoint(mv, roots[0]).x
|
||||||
|
: (mv[0] + mv[6]) / 2;
|
||||||
|
intercepts.push(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (intercepts.length > 1) {
|
||||||
intercepts.sort(function(a, b) { return a - b; });
|
intercepts.sort(function(a, b) { return a - b; });
|
||||||
point.x = (intercepts[0] + intercepts[1]) / 2;
|
point.x = (intercepts[0] + intercepts[1]) / 2;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return point;
|
return point;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
CompoundPath.inject(/** @lends CompoundPath# */{
|
|
||||||
/**
|
|
||||||
* Private method that returns all the curves in this CompoundPath, which
|
|
||||||
* are monotonically decreasing or increasing in the 'y' direction.
|
|
||||||
* Used by getWinding().
|
|
||||||
*/
|
|
||||||
_getMonoCurves: function() {
|
|
||||||
var children = this._children,
|
|
||||||
monoCurves = [];
|
|
||||||
for (var i = 0, l = children.length; i < l; i++)
|
|
||||||
monoCurves.push.apply(monoCurves, children[i]._getMonoCurves());
|
|
||||||
return monoCurves;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -146,21 +146,21 @@ var Numerical = new function() {
|
||||||
* The epsilon to be used when performing "geometric" checks, such as
|
* The epsilon to be used when performing "geometric" checks, such as
|
||||||
* distances between points and lines.
|
* distances between points and lines.
|
||||||
*/
|
*/
|
||||||
GEOMETRIC_EPSILON: 2e-7, // NOTE: 1e-7 doesn't work in some edge-cases
|
GEOMETRIC_EPSILON: 1e-7,
|
||||||
/**
|
/**
|
||||||
* The epsilon to be used when performing winding contribution checks.
|
* The epsilon to be used when performing winding contribution checks.
|
||||||
*/
|
*/
|
||||||
WINDING_EPSILON: 2e-7, // NOTE: 1e-7 doesn't work in some edge-cases
|
WINDING_EPSILON: 1e-8,
|
||||||
/**
|
/**
|
||||||
* The epsilon to be used when performing "trigonometric" checks, such
|
* The epsilon to be used when performing "trigonometric" checks, such
|
||||||
* as examining cross products to check for collinearity.
|
* as examining cross products to check for collinearity.
|
||||||
*/
|
*/
|
||||||
TRIGONOMETRIC_EPSILON: 1e-7,
|
TRIGONOMETRIC_EPSILON: 1e-8,
|
||||||
/**
|
/**
|
||||||
* The epsilon to be used when comparing curve-time parameters in the
|
* The epsilon to be used when comparing curve-time parameters in the
|
||||||
* fat-line clipping code.
|
* fat-line clipping code.
|
||||||
*/
|
*/
|
||||||
CLIPPING_EPSILON: 1e-9,
|
CLIPPING_EPSILON: 1e-10,
|
||||||
/**
|
/**
|
||||||
* Kappa is the value which which to scale the curve handles when
|
* Kappa is the value which which to scale the curve handles when
|
||||||
* drawing a circle with bezier curves.
|
* drawing a circle with bezier curves.
|
||||||
|
|
|
@ -282,6 +282,7 @@ test('Path#contains() (straight curves with zero-winding: #943)', function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
test('CompoundPath#contains() (nested touching circles: #944)', function() {
|
test('CompoundPath#contains() (nested touching circles: #944)', function() {
|
||||||
var c1 = new Path.Circle({
|
var c1 = new Path.Circle({
|
||||||
center: [200, 200],
|
center: [200, 200],
|
||||||
|
@ -294,21 +295,22 @@ test('CompoundPath#contains() (nested touching circles: #944)', function() {
|
||||||
var cp = new CompoundPath([c1, c2]);
|
var cp = new CompoundPath([c1, c2]);
|
||||||
testPoint(cp, new Point(100, 200), true);
|
testPoint(cp, new Point(100, 200), true);
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
test('Path#contains() with Path#interiorPoint', function() {
|
test('Path#contains() with Path#interiorPoint: #854, #1064', function() {
|
||||||
var path = new paper.Path({
|
var paths = [
|
||||||
segments: [
|
'M100,100l50,0l0,80l50,0l0,-80l50,0l0,100l-150,0z',
|
||||||
[100, 100],
|
'M214.48881,363.27884c-0.0001,-0.00017 -0.0001,-0.00017 0,0z',
|
||||||
[150, 100],
|
'M289.92236,384.04631c0.00002,0.00023 0.00002,0.00023 0,0z',
|
||||||
[150, 180],
|
'M195.51448,280.25264c-0.00011,0.00013 -0.00011,0.00013 0,0z',
|
||||||
[200, 180],
|
'M514.7818,183.0217c-0.00011,-0.00026 -0.00011,-0.00026 0,0z',
|
||||||
[200, 100],
|
'M471.91288,478.44229c-0.00018,0.00022 -0.00018,0.00022 0,0z'
|
||||||
[250, 100],
|
];
|
||||||
[250, 200],
|
for (var i = 0; i < paths.length; i++) {
|
||||||
[100, 200]
|
var path = PathItem.create(paths[i]);
|
||||||
],
|
testPoint(path, path.interiorPoint, true, 'The path[' + i +
|
||||||
closed: true
|
']\'s interior point should actually be inside the path');
|
||||||
});
|
}
|
||||||
testPoint(path, path.interiorPoint, true,
|
|
||||||
'The path\'s interior point should actually be inside the path');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -524,6 +524,19 @@ test('#968', function() {
|
||||||
'M352,280l0,64c0,0 -13.69105,1.79261 -31.82528,4.17778c-15.66463,-26.96617 31.82528,-89.12564 31.82528,-68.17778z');
|
'M352,280l0,64c0,0 -13.69105,1.79261 -31.82528,4.17778c-15.66463,-26.96617 31.82528,-89.12564 31.82528,-68.17778z');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('#973', function() {
|
||||||
|
var path = new Path.Ellipse(100, 100, 150, 110);
|
||||||
|
path.segments[1].point.y += 60;
|
||||||
|
path.segments[3].point.y -= 60;
|
||||||
|
|
||||||
|
var resolved = path.resolveCrossings();
|
||||||
|
var orientation = resolved.children.map(function(child) {
|
||||||
|
return child.isClockwise();
|
||||||
|
});
|
||||||
|
equals(orientation, [true, false, true],
|
||||||
|
'children orientation after calling path.resolveCrossings()');
|
||||||
|
});
|
||||||
|
|
||||||
test('#1054', function() {
|
test('#1054', function() {
|
||||||
var p1 = new Path({
|
var p1 = new Path({
|
||||||
segments: [
|
segments: [
|
||||||
|
@ -574,6 +587,38 @@ test('#1059', function() {
|
||||||
'M428.48409,189.03444c-21.46172,0 -42.92343,8.188 -59.29943,24.56401c-32.75202,32.75202 -32.75202,85.84686 0,118.59888l-160,0c0,0 -32.75202,-85.84686 0,-118.59888l0,0c16.37601,-16.37601 37.83772,-24.56401 59.29944,-24.56401z');
|
'M428.48409,189.03444c-21.46172,0 -42.92343,8.188 -59.29943,24.56401c-32.75202,32.75202 -32.75202,85.84686 0,118.59888l-160,0c0,0 -32.75202,-85.84686 0,-118.59888l0,0c16.37601,-16.37601 37.83772,-24.56401 59.29944,-24.56401z');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('#1075', function() {
|
||||||
|
var p1 = new paper.Path({
|
||||||
|
segments: [
|
||||||
|
[150, 120],
|
||||||
|
[150, 85],
|
||||||
|
[178, 85],
|
||||||
|
[178, 110],
|
||||||
|
[315, 110],
|
||||||
|
[315, 85],
|
||||||
|
[342, 85],
|
||||||
|
[342, 120],
|
||||||
|
],
|
||||||
|
closed: true
|
||||||
|
});
|
||||||
|
var p2 = new paper.Path({
|
||||||
|
segments: [
|
||||||
|
[350, 60],
|
||||||
|
[350, 125],
|
||||||
|
[315, 125],
|
||||||
|
[315, 85],
|
||||||
|
[178, 85],
|
||||||
|
[178, 125],
|
||||||
|
[140, 125],
|
||||||
|
[140, 60]
|
||||||
|
],
|
||||||
|
closed: true
|
||||||
|
});
|
||||||
|
|
||||||
|
compareBoolean(function() { return p1.unite(p2); },
|
||||||
|
'M140,125l0,-65l210,0l0,65l-35,0l0,-5l-137,0l0,5z M315,85l-137,0l0,25l137,0z');
|
||||||
|
});
|
||||||
|
|
||||||
test('frame.intersect(rect);', function() {
|
test('frame.intersect(rect);', function() {
|
||||||
var frame = new CompoundPath();
|
var frame = new CompoundPath();
|
||||||
frame.addChild(new Path.Rectangle(new Point(140, 10), [100, 300]));
|
frame.addChild(new Path.Rectangle(new Point(140, 10), [100, 300]));
|
||||||
|
|
Loading…
Reference in a new issue