mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-06 04:42:15 -05:00
Move all winding related code to PathItem.Boolean and introduce __options.booleanOperations switch.
Fall back to __options.nativeContains if __options.booleanOperations is not included.
This commit is contained in:
parent
81b3b756c9
commit
64fa328f65
5 changed files with 284 additions and 277 deletions
|
@ -31,7 +31,7 @@ then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
./preprocess.sh $MODE ../src/paper.js "-i '../src/constants.js'" ../dist/paper-full.js
|
./preprocess.sh $MODE ../src/paper.js "-i '../src/constants.js'" ../dist/paper-full.js
|
||||||
./preprocess.sh $MODE ../src/paper.js "-o '{ \"paperscript\": false, \"palette\": false }' -i '../src/constants.js'" ../dist/paper-core.js
|
./preprocess.sh $MODE ../src/paper.js "-o '{ \"paperScript\": false, \"palette\": false }' -i '../src/constants.js'" ../dist/paper-core.js
|
||||||
./preprocess.sh $MODE ../src/paper.js "-o '{ \"environment\": \"node\" }' -i '../src/constants.js'" ../dist/paper-node.js
|
./preprocess.sh $MODE ../src/paper.js "-o '{ \"environment\": \"node\" }' -i '../src/constants.js'" ../dist/paper-node.js
|
||||||
|
|
||||||
# Remove the existing file and copy paper-full.js to paper.js now
|
# Remove the existing file and copy paper-full.js to paper.js now
|
||||||
|
|
|
@ -21,8 +21,9 @@ var __options = {
|
||||||
stats: true,
|
stats: true,
|
||||||
svg: true,
|
svg: true,
|
||||||
fatline: true,
|
fatline: true,
|
||||||
paperscript: true,
|
booleanOperations: true,
|
||||||
palette: true,
|
|
||||||
nativeContains: false,
|
nativeContains: false,
|
||||||
|
paperScript: true,
|
||||||
|
palette: true,
|
||||||
debug: false
|
debug: false
|
||||||
};
|
};
|
||||||
|
|
10
src/paper.js
10
src/paper.js
|
@ -83,7 +83,9 @@ var paper = new function(undefined) {
|
||||||
/*#*/ include('path/CompoundPath.js');
|
/*#*/ include('path/CompoundPath.js');
|
||||||
/*#*/ include('path/PathFlattener.js');
|
/*#*/ include('path/PathFlattener.js');
|
||||||
/*#*/ include('path/PathFitter.js');
|
/*#*/ include('path/PathFitter.js');
|
||||||
|
/*#*/ if (__options.booleanOperations) {
|
||||||
/*#*/ include('path/PathItem.Boolean.js');
|
/*#*/ include('path/PathItem.Boolean.js');
|
||||||
|
/*#*/ } // __options.booleanOperations
|
||||||
|
|
||||||
/*#*/ include('text/TextItem.js');
|
/*#*/ include('text/TextItem.js');
|
||||||
/*#*/ include('text/PointText.js');
|
/*#*/ include('text/PointText.js');
|
||||||
|
@ -120,9 +122,9 @@ var paper = new function(undefined) {
|
||||||
/*#*/ include('tool/Tool.js');
|
/*#*/ include('tool/Tool.js');
|
||||||
|
|
||||||
// Http is used both for PaperScript and SVGImport
|
// Http is used both for PaperScript and SVGImport
|
||||||
/*#*/ if (__options.paperscript || __options.svg) {
|
/*#*/ if (__options.paperScript || __options.svg) {
|
||||||
/*#*/ include('net/Http.js');
|
/*#*/ include('net/Http.js');
|
||||||
/*#*/ } // __options.paperscript || __options.svg
|
/*#*/ } // __options.paperScript || __options.svg
|
||||||
/*#*/ } // __options.environment == 'browser'
|
/*#*/ } // __options.environment == 'browser'
|
||||||
|
|
||||||
/*#*/ include('canvas/CanvasProvider.js');
|
/*#*/ include('canvas/CanvasProvider.js');
|
||||||
|
@ -138,9 +140,9 @@ var paper = new function(undefined) {
|
||||||
/*#*/ include('svg/SVGImport.js');
|
/*#*/ include('svg/SVGImport.js');
|
||||||
/*#*/ } // __options.svg
|
/*#*/ } // __options.svg
|
||||||
|
|
||||||
/*#*/ if (__options.paperscript) {
|
/*#*/ if (__options.paperScript) {
|
||||||
/*#*/ include('core/PaperScript.js');
|
/*#*/ include('core/PaperScript.js');
|
||||||
/*#*/ } // __options.paperscript
|
/*#*/ } // __options.paperScript
|
||||||
|
|
||||||
/*#*/ include('export.js');
|
/*#*/ include('export.js');
|
||||||
return paper;
|
return paper;
|
||||||
|
|
|
@ -90,7 +90,7 @@ PathItem.inject(/** @lends PathItem# */{
|
||||||
if (path2 && !(subtract ^ path2.isClockwise()))
|
if (path2 && !(subtract ^ path2.isClockwise()))
|
||||||
path2.reverse();
|
path2.reverse();
|
||||||
// Split curves at intersections on both paths.
|
// Split curves at intersections on both paths.
|
||||||
PathItem._splitPath(path1.getIntersections(path2 || path1, true));
|
PathItem._splitPath(path1.getIntersections(path2, true));
|
||||||
|
|
||||||
var chain = [],
|
var chain = [],
|
||||||
windings = [],
|
windings = [],
|
||||||
|
@ -182,6 +182,20 @@ PathItem.inject(/** @lends PathItem# */{
|
||||||
return result.reduce();
|
return result.reduce();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the winding contribution of the given point with respect to this
|
||||||
|
* PathItem.
|
||||||
|
*
|
||||||
|
* @param {Point} point the location for which to determine the winding
|
||||||
|
* direction
|
||||||
|
* @param {Boolean} horizontal wether we need to consider this point as
|
||||||
|
* part of a horizontal curve
|
||||||
|
* @return {Number} the winding number
|
||||||
|
*/
|
||||||
|
_getWinding: function(point, horizontal) {
|
||||||
|
return PathItem._getWinding(point, this._getMonoCurves(), horizontal);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@grouptitle Boolean Path Operations}
|
* {@grouptitle Boolean Path Operations}
|
||||||
*
|
*
|
||||||
|
@ -245,5 +259,245 @@ PathItem.inject(/** @lends PathItem# */{
|
||||||
*/
|
*/
|
||||||
divide: function(path) {
|
divide: function(path) {
|
||||||
return new Group([this.subtract(path), this.intersect(path)]);
|
return new Group([this.subtract(path), this.intersect(path)]);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Mess with indentation in order to get more line-space below...
|
||||||
|
statics: {
|
||||||
|
/**
|
||||||
|
* Private method for splitting a PathItem at the given intersections.
|
||||||
|
* The routine works for both self intersections and intersections
|
||||||
|
* between PathItems.
|
||||||
|
* @param {CurveLocation[]} intersections Array of CurveLocation objects
|
||||||
|
*/
|
||||||
|
_splitPath: function(intersections) {
|
||||||
|
var linearSegments;
|
||||||
|
|
||||||
|
function resetLinear() {
|
||||||
|
// Reset linear segments if they were part of a linear curve
|
||||||
|
// and if we are done with the entire curve.
|
||||||
|
for (var i = 0, l = linearSegments.length; i < l; i++) {
|
||||||
|
var segment = linearSegments[i];
|
||||||
|
// FIXME: Don't reset the appropriate handle if the intersection
|
||||||
|
// was on t == 0 && t == 1.
|
||||||
|
segment._handleOut.set(0, 0);
|
||||||
|
segment._handleIn.set(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = intersections.length - 1, curve, prevLoc; i >= 0; i--) {
|
||||||
|
var loc = intersections[i],
|
||||||
|
t = loc._parameter;
|
||||||
|
// Check if we are splitting same curve multiple times
|
||||||
|
if (prevLoc && prevLoc._curve === loc._curve) {
|
||||||
|
// Scale parameter after previous split.
|
||||||
|
t /= prevLoc._parameter;
|
||||||
|
} else {
|
||||||
|
if (linearSegments)
|
||||||
|
resetLinear();
|
||||||
|
curve = loc._curve;
|
||||||
|
linearSegments = curve.isLinear() && [];
|
||||||
|
}
|
||||||
|
var newCurve,
|
||||||
|
segment;
|
||||||
|
// Split the curve at t, while ignoring linearity of curves
|
||||||
|
if (newCurve = curve.divide(t, true, true)) {
|
||||||
|
segment = newCurve._segment1;
|
||||||
|
curve = newCurve.getPrevious();
|
||||||
|
} else {
|
||||||
|
segment = t < 0.5 ? curve._segment1 : curve._segment2;
|
||||||
|
}
|
||||||
|
// Link the new segment with the intersection on the other curve
|
||||||
|
segment._intersection = loc.getIntersection();
|
||||||
|
loc._segment = segment;
|
||||||
|
if (linearSegments)
|
||||||
|
linearSegments.push(segment);
|
||||||
|
prevLoc = loc;
|
||||||
|
}
|
||||||
|
if (linearSegments)
|
||||||
|
resetLinear();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private static method that returns the winding contribution of the
|
||||||
|
* given point with respect to a given set of monotone curves.
|
||||||
|
*/
|
||||||
|
_getWinding: function _getWinding(point, curves, horizontal) {
|
||||||
|
var tolerance = /*#=*/ Numerical.TOLERANCE,
|
||||||
|
x = point.x,
|
||||||
|
y = point.y,
|
||||||
|
xAfter = x + tolerance,
|
||||||
|
xBefore = x - tolerance,
|
||||||
|
windLeft = 0,
|
||||||
|
windRight = 0,
|
||||||
|
roots = [],
|
||||||
|
abs = Math.abs;
|
||||||
|
// Absolutely horizontal curves may return wrong results, since
|
||||||
|
// the curves are monotonic in y direction and this is an
|
||||||
|
// indeterminate state.
|
||||||
|
if (horizontal) {
|
||||||
|
var yTop = -Infinity,
|
||||||
|
yBottom = Infinity;
|
||||||
|
// Find the closest top and bottom intercepts for the same vertical
|
||||||
|
// line.
|
||||||
|
for (var i = 0, l = curves.length; i < l; i++) {
|
||||||
|
v = curves[i];
|
||||||
|
if (Curve.solveCubic(v, 0, x, roots, 0, 1) > 0) {
|
||||||
|
for (var j = roots.length - 1; j >= 0; j--) {
|
||||||
|
var y0 = Curve.evaluate(v, roots[j], 0).y;
|
||||||
|
if (y0 > y + tolerance && y0 < yBottom) {
|
||||||
|
yBottom = y0;
|
||||||
|
} else if (y0 < y - tolerance && y0 > yTop) {
|
||||||
|
yTop = y0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Shift the point lying on the horizontal curves by
|
||||||
|
// half of closest top and bottom intercepts.
|
||||||
|
yTop = (yTop + y) / 2;
|
||||||
|
yBottom = (yBottom + y) / 2;
|
||||||
|
if (yTop > -Infinity)
|
||||||
|
windLeft = _getWinding(new Point(x, yTop), curves);
|
||||||
|
if (yBottom < Infinity)
|
||||||
|
windRight = _getWinding(new Point(x, yBottom), curves);
|
||||||
|
} else {
|
||||||
|
// Find the winding number for right side of the curve, inclusive of
|
||||||
|
// the curve itself, while tracing along its +-x direction.
|
||||||
|
for (var i = 0, l = curves.length; i < l; i++) {
|
||||||
|
var v = curves[i];
|
||||||
|
if (Curve.solveCubic(v, 1, y, roots, 0, 1 - tolerance) === 1) {
|
||||||
|
var t = roots[0],
|
||||||
|
x0 = Curve.evaluate(v, t, 0).x,
|
||||||
|
slope = Curve.evaluate(v, t, 1).y;
|
||||||
|
// Take care of cases where the curve and the preceeding
|
||||||
|
// curve merely touches the ray towards +-x direction, but
|
||||||
|
// proceeds to the same side of the ray. This essentially is
|
||||||
|
// not a crossing.
|
||||||
|
// NOTE: The previous curve is stored at v[9], see
|
||||||
|
// Path#_getMonoCurves() for details.
|
||||||
|
if (abs(slope) < tolerance && !Curve.isLinear(v)
|
||||||
|
|| t < tolerance
|
||||||
|
&& slope * Curve.evaluate(v[9], t, 1).y < 0) {
|
||||||
|
// TODO: Handle stationary points here!
|
||||||
|
} else if (x0 <= xBefore) {
|
||||||
|
windLeft += v[8];
|
||||||
|
} else if (x0 >= xAfter) {
|
||||||
|
windRight += v[8];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Math.max(abs(windLeft), abs(windRight));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private method to trace closed contours from a set of segments according
|
||||||
|
* to a set of constraints—winding contribution and a custom operator.
|
||||||
|
*
|
||||||
|
* @param {Segment[]} segments Array of 'seed' segments for tracing closed
|
||||||
|
* contours
|
||||||
|
* @param {Function} the operator function that receives as argument the
|
||||||
|
* winding number contribution of a curve and returns a boolean value
|
||||||
|
* indicating whether the curve should be included in the final contour or
|
||||||
|
* not
|
||||||
|
* @return {Path[]} the contours traced
|
||||||
|
*/
|
||||||
|
_tracePaths: function(segments, operator, selfIx) {
|
||||||
|
// Choose a default operator which will return all contours
|
||||||
|
operator = operator || function() {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
var paths = [],
|
||||||
|
// Values for getTangentAt() that are almost 0 and 1.
|
||||||
|
// TODO: Correctly support getTangentAt(0) / (1)?
|
||||||
|
ZERO = 1e-3,
|
||||||
|
ONE = 1 - 1e-3;
|
||||||
|
for (var i = 0, seg, startSeg, l = segments.length; i < l; i++) {
|
||||||
|
seg = startSeg = segments[i];
|
||||||
|
if (seg._visited || !operator(seg._winding))
|
||||||
|
continue;
|
||||||
|
var path = new Path({ insert: false }),
|
||||||
|
inter = seg._intersection,
|
||||||
|
startInterSeg = inter && inter._segment,
|
||||||
|
added = false, // Wether a first segment as added already
|
||||||
|
dir = 1;
|
||||||
|
do {
|
||||||
|
var handleIn = dir > 0 ? seg._handleIn : seg._handleOut,
|
||||||
|
handleOut = dir > 0 ? seg._handleOut : seg._handleIn,
|
||||||
|
interSeg;
|
||||||
|
// If the intersection segment is valid, try switching to
|
||||||
|
// it, with an appropriate direction to continue traversal.
|
||||||
|
// Else, stay on the same contour.
|
||||||
|
if (added && (!operator(seg._winding) || selfIx)
|
||||||
|
&& (inter = seg._intersection)
|
||||||
|
&& (interSeg = inter._segment)
|
||||||
|
&& interSeg !== startSeg) {
|
||||||
|
var c1 = seg.getCurve();
|
||||||
|
if (dir > 0)
|
||||||
|
c1 = c1.getPrevious();
|
||||||
|
var t1 = c1.getTangentAt(dir < 1 ? ZERO : ONE, true),
|
||||||
|
// Get both curves at the intersection (except the entry
|
||||||
|
// curves) along with their winding values and tangents.
|
||||||
|
c4 = interSeg.getCurve(),
|
||||||
|
c3 = c4.getPrevious(),
|
||||||
|
t3 = c3.getTangentAt(ONE, true),
|
||||||
|
t4 = c4.getTangentAt(ZERO, true),
|
||||||
|
// Cross product of the entry and exit tangent vectors
|
||||||
|
// at the intersection, will let us select the correct
|
||||||
|
// countour to traverse next.
|
||||||
|
w3 = t1.cross(t3),
|
||||||
|
w4 = t1.cross(t4);
|
||||||
|
// Do not attempt to switch contours if we aren't absolutely
|
||||||
|
// sure that there is a possible candidate.
|
||||||
|
if (w3 * w4 !== 0) {
|
||||||
|
var curve = w3 < w4 ? c3 : c4,
|
||||||
|
nextCurve = operator(curve._segment1._winding)
|
||||||
|
? curve
|
||||||
|
: w3 < w4 ? c4 : c3,
|
||||||
|
nextSeg = nextCurve._segment1;
|
||||||
|
dir = nextCurve === c3 ? -1 : 1;
|
||||||
|
// If we didn't manage to find a suitable direction for
|
||||||
|
// next contour to traverse, stay on the same contour.
|
||||||
|
if (nextSeg._visited && seg._path !== nextSeg._path
|
||||||
|
|| !operator(nextSeg._winding)) {
|
||||||
|
dir = 1;
|
||||||
|
} else {
|
||||||
|
// Switch to the intersection segment.
|
||||||
|
seg._visited = interSeg._visited;
|
||||||
|
seg = interSeg;
|
||||||
|
if (nextSeg._visited)
|
||||||
|
dir = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dir = 1;
|
||||||
|
}
|
||||||
|
handleOut = dir > 0 ? seg._handleOut : seg._handleIn;
|
||||||
|
}
|
||||||
|
// Add the current segment to the path, and mark the added
|
||||||
|
// segment as visited.
|
||||||
|
path.add(new Segment(seg._point, added && handleIn, handleOut));
|
||||||
|
added = true;
|
||||||
|
seg._visited = true;
|
||||||
|
// Move to the next segment according to the traversal direction
|
||||||
|
seg = dir > 0 ? seg.getNext() : seg. getPrevious();
|
||||||
|
} while (seg && !seg._visited
|
||||||
|
&& seg !== startSeg && seg !== startInterSeg
|
||||||
|
&& (seg._intersection || operator(seg._winding)));
|
||||||
|
// Finish with closing the paths if necessary, correctly linking up
|
||||||
|
// curves etc.
|
||||||
|
if (seg && (seg === startSeg || seg === startInterSeg)) {
|
||||||
|
path.firstSegment.setHandleIn((seg === startInterSeg
|
||||||
|
? startInterSeg : seg)._handleIn);
|
||||||
|
path.setClosed(true);
|
||||||
|
} else {
|
||||||
|
path.lastSegment._handleOut.set(0, 0);
|
||||||
|
}
|
||||||
|
// Add the path to the result.
|
||||||
|
// Try to avoid stray segments and incomplete paths.
|
||||||
|
var count = path._segments.length;
|
||||||
|
if (count > 2 || count === 2 && path._closed && !path.isPolygon())
|
||||||
|
paths.push(path);
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
}
|
}
|
||||||
});
|
}});
|
||||||
|
|
|
@ -63,18 +63,22 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
getIntersections: function(path, _expand) {
|
getIntersections: function(path, _expand) {
|
||||||
|
// NOTE: For self-intersection, path is null. This means you can also
|
||||||
|
// just call path.getIntersections() without an argument to get self
|
||||||
|
// intersections.
|
||||||
|
if (this === path)
|
||||||
|
path = null;
|
||||||
// First check the bounds of the two paths. If they don't intersect,
|
// First check the bounds of the two paths. If they don't intersect,
|
||||||
// we don't need to iterate through their curves.
|
// we don't need to iterate through their curves.
|
||||||
var selfOp = this === path;
|
if (path && !this.getBounds().touches(path.getBounds()))
|
||||||
if (!selfOp && !this.getBounds().touches(path.getBounds()))
|
|
||||||
return [];
|
return [];
|
||||||
var locations = [],
|
var locations = [],
|
||||||
curves1 = this.getCurves(),
|
curves1 = this.getCurves(),
|
||||||
curves2 = selfOp ? curves1 : path.getCurves(),
|
curves2 = path ? path.getCurves() : curves1,
|
||||||
matrix1 = this._matrix.orNullIfIdentity(),
|
matrix1 = this._matrix.orNullIfIdentity(),
|
||||||
matrix2 = selfOp ? matrix1 : path._matrix.orNullIfIdentity(),
|
matrix2 = path ? path._matrix.orNullIfIdentity() : matrix1,
|
||||||
length1 = curves1.length,
|
length1 = curves1.length,
|
||||||
length2 = selfOp ? length1 : curves2.length,
|
length2 = path ? curves2.length : length1,
|
||||||
values2 = [],
|
values2 = [],
|
||||||
ZERO = /*#=*/ Numerical.EPSILON,
|
ZERO = /*#=*/ Numerical.EPSILON,
|
||||||
ONE = 1 - /*#=*/ Numerical.EPSILON;
|
ONE = 1 - /*#=*/ Numerical.EPSILON;
|
||||||
|
@ -82,8 +86,8 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
||||||
values2[i] = curves2[i].getValues(matrix2);
|
values2[i] = curves2[i].getValues(matrix2);
|
||||||
for (var i = 0; i < length1; i++) {
|
for (var i = 0; i < length1; i++) {
|
||||||
var curve1 = curves1[i],
|
var curve1 = curves1[i],
|
||||||
values1 = selfOp ? values2[i] : curve1.getValues(matrix1);
|
values1 = path ? curve1.getValues(matrix1) : values2[i];
|
||||||
if (selfOp) {
|
if (!path) {
|
||||||
// First check for self-intersections within the same curve
|
// First check for self-intersections within the same curve
|
||||||
var seg1 = curve1.getSegment1(),
|
var seg1 = curve1.getSegment1(),
|
||||||
seg2 = curve1.getSegment2(),
|
seg2 = curve1.getSegment2(),
|
||||||
|
@ -112,13 +116,14 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check for intersections with other curves
|
// Check for intersections with other curves. For self intersection,
|
||||||
for (var j = selfOp ? i + 1 : 0; j < length2; j++) {
|
// we can start at i + 1 instead of 0
|
||||||
|
for (var j = path ? 0 : i + 1; j < length2; j++) {
|
||||||
Curve.getIntersections(values1, values2[j], curve1,
|
Curve.getIntersections(values1, values2[j], curve1,
|
||||||
curves2[j], locations,
|
curves2[j], locations,
|
||||||
// Avoid end point intersections on consecutive curves
|
// Avoid end point intersections on consecutive curves
|
||||||
// when self intersecting.
|
// when self intersecting.
|
||||||
selfOp && (j === i + 1 || j === length2 - 1 && i === 0)
|
!path && (j === i + 1 || j === length2 - 1 && i === 0)
|
||||||
? ZERO : 0, // tMin
|
? ZERO : 0, // tMin
|
||||||
ONE); // tMax
|
ONE); // tMax
|
||||||
}
|
}
|
||||||
|
@ -278,24 +283,10 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
||||||
return !(this.hasFill() && this.hasStroke());
|
return !(this.hasFill() && this.hasStroke());
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the winding contribution of the given point with respect to this
|
|
||||||
* PathItem.
|
|
||||||
*
|
|
||||||
* @param {Point} point the location for which to determine the winding
|
|
||||||
* direction
|
|
||||||
* @param {Boolean} horizontal wether we need to consider this point as
|
|
||||||
* part of a horizontal curve
|
|
||||||
* @return {Number} the winding number
|
|
||||||
*/
|
|
||||||
_getWinding: function(point, horizontal) {
|
|
||||||
return PathItem._getWinding(point, this._getMonoCurves(), horizontal);
|
|
||||||
},
|
|
||||||
|
|
||||||
_contains: function(point) {
|
_contains: function(point) {
|
||||||
// NOTE: point is reverse transformed by _matrix, so we don't need to
|
// NOTE: point is reverse transformed by _matrix, so we don't need to
|
||||||
// apply here.
|
// apply here.
|
||||||
/*#*/ if (__options.nativeContains) {
|
/*#*/ if (__options.nativeContains || !__options.booleanOperations) {
|
||||||
// To compare with native canvas approach:
|
// To compare with native canvas approach:
|
||||||
var ctx = CanvasProvider.getContext(1, 1);
|
var ctx = CanvasProvider.getContext(1, 1);
|
||||||
// Abuse clip = true to get a shape for ctx.isPointInPath().
|
// Abuse clip = true to get a shape for ctx.isPointInPath().
|
||||||
|
@ -303,253 +294,12 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
||||||
var res = ctx.isPointInPath(point.x, point.y, this.getWindingRule());
|
var res = ctx.isPointInPath(point.x, point.y, this.getWindingRule());
|
||||||
CanvasProvider.release(ctx);
|
CanvasProvider.release(ctx);
|
||||||
return res;
|
return res;
|
||||||
/*#*/ } else { // !__options.nativeContains
|
/*#*/ } else { // !__options.nativeContains && __options.booleanOperations
|
||||||
var winding = this._getWinding(point);
|
var winding = this._getWinding(point);
|
||||||
return !!(this.getWindingRule() === 'evenodd' ? winding & 1 : winding);
|
return !!(this.getWindingRule() === 'evenodd' ? winding & 1 : winding);
|
||||||
/*#*/ } // !__options.nativeContains
|
/*#*/ } // !__options.nativeContains && __options.booleanOperations
|
||||||
},
|
},
|
||||||
|
|
||||||
// Mess with indentation in order to get more line-space below...
|
|
||||||
statics: {
|
|
||||||
/**
|
|
||||||
* Private method for splitting a PathItem at the given intersections.
|
|
||||||
* The routine works for both self intersections and intersections
|
|
||||||
* between PathItems.
|
|
||||||
* @param {CurveLocation[]} intersections Array of CurveLocation objects
|
|
||||||
*/
|
|
||||||
_splitPath: function(intersections) {
|
|
||||||
var linearSegments;
|
|
||||||
|
|
||||||
function resetLinear() {
|
|
||||||
// Reset linear segments if they were part of a linear curve
|
|
||||||
// and if we are done with the entire curve.
|
|
||||||
for (var i = 0, l = linearSegments.length; i < l; i++) {
|
|
||||||
var segment = linearSegments[i];
|
|
||||||
// FIXME: Don't reset the appropriate handle if the intersection
|
|
||||||
// was on t == 0 && t == 1.
|
|
||||||
segment._handleOut.set(0, 0);
|
|
||||||
segment._handleIn.set(0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = intersections.length - 1, curve, prevLoc; i >= 0; i--) {
|
|
||||||
var loc = intersections[i],
|
|
||||||
t = loc._parameter;
|
|
||||||
// Check if we are splitting same curve multiple times
|
|
||||||
if (prevLoc && prevLoc._curve === loc._curve) {
|
|
||||||
// Scale parameter after previous split.
|
|
||||||
t /= prevLoc._parameter;
|
|
||||||
} else {
|
|
||||||
if (linearSegments)
|
|
||||||
resetLinear();
|
|
||||||
curve = loc._curve;
|
|
||||||
linearSegments = curve.isLinear() && [];
|
|
||||||
}
|
|
||||||
var newCurve,
|
|
||||||
segment;
|
|
||||||
// Split the curve at t, while ignoring linearity of curves
|
|
||||||
if (newCurve = curve.divide(t, true, true)) {
|
|
||||||
segment = newCurve._segment1;
|
|
||||||
curve = newCurve.getPrevious();
|
|
||||||
} else {
|
|
||||||
segment = t < 0.5 ? curve._segment1 : curve._segment2;
|
|
||||||
}
|
|
||||||
// Link the new segment with the intersection on the other curve
|
|
||||||
segment._intersection = loc.getIntersection();
|
|
||||||
loc._segment = segment;
|
|
||||||
if (linearSegments)
|
|
||||||
linearSegments.push(segment);
|
|
||||||
prevLoc = loc;
|
|
||||||
}
|
|
||||||
if (linearSegments)
|
|
||||||
resetLinear();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private static method that returns the winding contribution of the
|
|
||||||
* given point with respect to a given set of monotone curves.
|
|
||||||
*/
|
|
||||||
_getWinding: function _getWinding(point, curves, horizontal) {
|
|
||||||
var tolerance = /*#=*/ Numerical.TOLERANCE,
|
|
||||||
x = point.x,
|
|
||||||
y = point.y,
|
|
||||||
xAfter = x + tolerance,
|
|
||||||
xBefore = x - tolerance,
|
|
||||||
windLeft = 0,
|
|
||||||
windRight = 0,
|
|
||||||
roots = [],
|
|
||||||
abs = Math.abs;
|
|
||||||
// Absolutely horizontal curves may return wrong results, since
|
|
||||||
// the curves are monotonic in y direction and this is an
|
|
||||||
// indeterminate state.
|
|
||||||
if (horizontal) {
|
|
||||||
var yTop = -Infinity,
|
|
||||||
yBottom = Infinity;
|
|
||||||
// Find the closest top and bottom intercepts for the same vertical
|
|
||||||
// line.
|
|
||||||
for (var i = 0, l = curves.length; i < l; i++) {
|
|
||||||
v = curves[i];
|
|
||||||
if (Curve.solveCubic(v, 0, x, roots, 0, 1) > 0) {
|
|
||||||
for (var j = roots.length - 1; j >= 0; j--) {
|
|
||||||
var y0 = Curve.evaluate(v, roots[j], 0).y;
|
|
||||||
if (y0 > y + tolerance && y0 < yBottom) {
|
|
||||||
yBottom = y0;
|
|
||||||
} else if (y0 < y - tolerance && y0 > yTop) {
|
|
||||||
yTop = y0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Shift the point lying on the horizontal curves by
|
|
||||||
// half of closest top and bottom intercepts.
|
|
||||||
yTop = (yTop + y) / 2;
|
|
||||||
yBottom = (yBottom + y) / 2;
|
|
||||||
if (yTop > -Infinity)
|
|
||||||
windLeft = _getWinding(new Point(x, yTop), curves);
|
|
||||||
if (yBottom < Infinity)
|
|
||||||
windRight = _getWinding(new Point(x, yBottom), curves);
|
|
||||||
} else {
|
|
||||||
// Find the winding number for right side of the curve, inclusive of
|
|
||||||
// the curve itself, while tracing along its +-x direction.
|
|
||||||
for (var i = 0, l = curves.length; i < l; i++) {
|
|
||||||
var v = curves[i];
|
|
||||||
if (Curve.solveCubic(v, 1, y, roots, 0, 1 - tolerance) === 1) {
|
|
||||||
var t = roots[0],
|
|
||||||
x0 = Curve.evaluate(v, t, 0).x,
|
|
||||||
slope = Curve.evaluate(v, t, 1).y;
|
|
||||||
// Take care of cases where the curve and the preceeding
|
|
||||||
// curve merely touches the ray towards +-x direction, but
|
|
||||||
// proceeds to the same side of the ray. This essentially is
|
|
||||||
// not a crossing.
|
|
||||||
// NOTE: The previous curve is stored at v[9], see
|
|
||||||
// Path#_getMonoCurves() for details.
|
|
||||||
if (abs(slope) < tolerance && !Curve.isLinear(v)
|
|
||||||
|| t < tolerance
|
|
||||||
&& slope * Curve.evaluate(v[9], t, 1).y < 0) {
|
|
||||||
// TODO: Handle stationary points here!
|
|
||||||
} else if (x0 <= xBefore) {
|
|
||||||
windLeft += v[8];
|
|
||||||
} else if (x0 >= xAfter) {
|
|
||||||
windRight += v[8];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Math.max(abs(windLeft), abs(windRight));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private method to trace closed contours from a set of segments according
|
|
||||||
* to a set of constraints—winding contribution and a custom operator.
|
|
||||||
*
|
|
||||||
* @param {Segment[]} segments Array of 'seed' segments for tracing closed
|
|
||||||
* contours
|
|
||||||
* @param {Function} the operator function that receives as argument the
|
|
||||||
* winding number contribution of a curve and returns a boolean value
|
|
||||||
* indicating whether the curve should be included in the final contour or
|
|
||||||
* not
|
|
||||||
* @return {Path[]} the contours traced
|
|
||||||
*/
|
|
||||||
_tracePaths: function(segments, operator, selfIx) {
|
|
||||||
// Choose a default operator which will return all contours
|
|
||||||
operator = operator || function() {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
var paths = [],
|
|
||||||
// Values for getTangentAt() that are almost 0 and 1.
|
|
||||||
// TODO: Correctly support getTangentAt(0) / (1)?
|
|
||||||
ZERO = 1e-3,
|
|
||||||
ONE = 1 - 1e-3;
|
|
||||||
for (var i = 0, seg, startSeg, l = segments.length; i < l; i++) {
|
|
||||||
seg = startSeg = segments[i];
|
|
||||||
if (seg._visited || !operator(seg._winding))
|
|
||||||
continue;
|
|
||||||
var path = new Path({ insert: false }),
|
|
||||||
inter = seg._intersection,
|
|
||||||
startInterSeg = inter && inter._segment,
|
|
||||||
added = false, // Wether a first segment as added already
|
|
||||||
dir = 1;
|
|
||||||
do {
|
|
||||||
var handleIn = dir > 0 ? seg._handleIn : seg._handleOut,
|
|
||||||
handleOut = dir > 0 ? seg._handleOut : seg._handleIn,
|
|
||||||
interSeg;
|
|
||||||
// If the intersection segment is valid, try switching to
|
|
||||||
// it, with an appropriate direction to continue traversal.
|
|
||||||
// Else, stay on the same contour.
|
|
||||||
if (added && (!operator(seg._winding) || selfIx)
|
|
||||||
&& (inter = seg._intersection)
|
|
||||||
&& (interSeg = inter._segment)
|
|
||||||
&& interSeg !== startSeg) {
|
|
||||||
var c1 = seg.getCurve();
|
|
||||||
if (dir > 0)
|
|
||||||
c1 = c1.getPrevious();
|
|
||||||
var t1 = c1.getTangentAt(dir < 1 ? ZERO : ONE, true),
|
|
||||||
// Get both curves at the intersection (except the entry
|
|
||||||
// curves) along with their winding values and tangents.
|
|
||||||
c4 = interSeg.getCurve(),
|
|
||||||
c3 = c4.getPrevious(),
|
|
||||||
t3 = c3.getTangentAt(ONE, true),
|
|
||||||
t4 = c4.getTangentAt(ZERO, true),
|
|
||||||
// Cross product of the entry and exit tangent vectors
|
|
||||||
// at the intersection, will let us select the correct
|
|
||||||
// countour to traverse next.
|
|
||||||
w3 = t1.cross(t3),
|
|
||||||
w4 = t1.cross(t4);
|
|
||||||
// Do not attempt to switch contours if we aren't absolutely
|
|
||||||
// sure that there is a possible candidate.
|
|
||||||
if (w3 * w4 !== 0) {
|
|
||||||
var curve = w3 < w4 ? c3 : c4,
|
|
||||||
nextCurve = operator(curve._segment1._winding)
|
|
||||||
? curve
|
|
||||||
: w3 < w4 ? c4 : c3,
|
|
||||||
nextSeg = nextCurve._segment1;
|
|
||||||
dir = nextCurve === c3 ? -1 : 1;
|
|
||||||
// If we didn't manage to find a suitable direction for
|
|
||||||
// next contour to traverse, stay on the same contour.
|
|
||||||
if (nextSeg._visited && seg._path !== nextSeg._path
|
|
||||||
|| !operator(nextSeg._winding)) {
|
|
||||||
dir = 1;
|
|
||||||
} else {
|
|
||||||
// Switch to the intersection segment.
|
|
||||||
seg._visited = interSeg._visited;
|
|
||||||
seg = interSeg;
|
|
||||||
if (nextSeg._visited)
|
|
||||||
dir = 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dir = 1;
|
|
||||||
}
|
|
||||||
handleOut = dir > 0 ? seg._handleOut : seg._handleIn;
|
|
||||||
}
|
|
||||||
// Add the current segment to the path, and mark the added
|
|
||||||
// segment as visited.
|
|
||||||
path.add(new Segment(seg._point, added && handleIn, handleOut));
|
|
||||||
added = true;
|
|
||||||
seg._visited = true;
|
|
||||||
// Move to the next segment according to the traversal direction
|
|
||||||
seg = dir > 0 ? seg.getNext() : seg. getPrevious();
|
|
||||||
} while (seg && !seg._visited
|
|
||||||
&& seg !== startSeg && seg !== startInterSeg
|
|
||||||
&& (seg._intersection || operator(seg._winding)));
|
|
||||||
// Finish with closing the paths if necessary, correctly linking up
|
|
||||||
// curves etc.
|
|
||||||
if (seg && (seg === startSeg || seg === startInterSeg)) {
|
|
||||||
path.firstSegment.setHandleIn((seg === startInterSeg
|
|
||||||
? startInterSeg : seg)._handleIn);
|
|
||||||
path.setClosed(true);
|
|
||||||
} else {
|
|
||||||
path.lastSegment._handleOut.set(0, 0);
|
|
||||||
}
|
|
||||||
// Add the path to the result.
|
|
||||||
// Try to avoid stray segments and incomplete paths.
|
|
||||||
var count = path._segments.length;
|
|
||||||
if (count > 2 || count === 2 && path._closed && !path.isPolygon())
|
|
||||||
paths.push(path);
|
|
||||||
}
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smooth bezier curves without changing the amount of segments or their
|
* Smooth bezier curves without changing the amount of segments or their
|
||||||
* points, by only smoothing and adjusting their handle points, for both
|
* points, by only smoothing and adjusting their handle points, for both
|
||||||
|
|
Loading…
Reference in a new issue