_getMonotoneCurves method which returns and caches curves that are monotonic in Y direction

This commit is contained in:
hkrish 2013-12-25 20:38:48 +01:00
parent b4c7bcae5e
commit 0134596f66
2 changed files with 150 additions and 43 deletions

View file

@ -236,14 +236,27 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
return paths.join(' '); return paths.join(' ');
}, },
_getWinding: function(point) { /**
* Private method that returns all the curves in this CompoundPath, which
* are monotonically decreasing or increasing in the 'y' direction.
* Used by PathItem#_getWinding method.
*/
_getMonotoneCurves: function() {
var children = this._children, var children = this._children,
winding = 0; monoCurves = [];
for (var i = 0, l = children.length; i < l; i++) for (var i = 0, l = children.length; i < l; i++)
winding += children[i]._getWinding(point); monoCurves.push.apply(monoCurves, children[i]._getMonotoneCurves());
return winding; return monoCurves;
}, },
// _getWinding: function(point) {
// var children = this._children,
// winding = 0;
// for (var i = 0, l = children.length; i < l; i++)
// winding += children[i]._getWinding(point);
// return winding;
// },
_getChildHitTestOptions: function(options) { _getChildHitTestOptions: function(options) {
// If we're not specifically asked to returns paths through // If we're not specifically asked to returns paths through
// options.type == 'path' do not test children for fill, since a // options.type == 'path' do not test children for fill, since a

View file

@ -145,6 +145,9 @@ var Path = PathItem.extend(/** @lends Path# */{
for (var i = 0, l = this._curves.length; i < l; i++) for (var i = 0, l = this._curves.length; i < l; i++)
this._curves[i]._changed(/*#=*/ Change.GEOMETRY); this._curves[i]._changed(/*#=*/ Change.GEOMETRY);
} }
// Clear cached curves used for winding direction and containment
// calculation.
this._monotoneCurves = undefined;
} else if (flags & /*#=*/ ChangeFlag.STROKE) { } else if (flags & /*#=*/ ChangeFlag.STROKE) {
// TODO: We could preserve the purely geometric bounds that are not // TODO: We could preserve the purely geometric bounds that are not
// affected by stroke: _bounds.bounds and _bounds.handleBounds // affected by stroke: _bounds.bounds and _bounds.handleBounds
@ -1708,49 +1711,140 @@ var Path = PathItem.extend(/** @lends Path# */{
return null; return null;
}, },
_getWinding: function(point) { /**
var closed = this._closed; * Private method that returns and caches all the curves in this Path, which
// If the path is not closed, we should not bail out in case it has a * are monotonically decreasing or increasing in the 'y' direction.
// fill color! * Used by PathItem#_getWinding method.
if (!closed && !this.hasFill() */
|| !this.getInternalRoughBounds()._containsPoint(point)) _getMonotoneCurves: function() {
return 0; var monoCurves = this._monotoneCurves,
// Use the crossing number algorithm, by counting the crossings of the // TODO: replace instances with constants ( /*#=*/ ?)
// beam in right y-direction with the shape, and see if it's an odd INCREASING = 1,
// number, meaning the starting point is inside the shape. DECREASING = -1;
// http://en.wikipedia.org/wiki/Point_in_polygon if (!monoCurves) {
var curves = this.getCurves(), // Insert curve values into a cached array
segments = this._segments, // Always avoid horizontal curves
winding = 0, function insertValues(v, dir) {
// Reuse arrays for root-finding, give garbage collector a break var y0 = v[1], y1 = v[7];
roots1 = [], dir = dir || INCREASING;
roots2 = [], if (y0 === y1) {
last = (closed return;
? curves[curves.length - 1] } else if (y0 > y1) {
// Create a straight closing line for open paths, just like dir = DECREASING;
// how filling open paths works. }
: new Curve(segments[segments.length - 1]._point, v.push(dir);
segments[0]._point)).getValues(), monoCurves.push(v);
previous = last; }
for (var i = 0, l = curves.length; i < l; i++) { // Handle bezier curves. We need to chop them into smaller curves
var curve = curves[i].getValues(), // with defined orientation, by solving the derivative curve for
x = curve[0], // Y extrema.
y = curve[1]; function insertCurves(v, dir) {
// Filter out curves with 0-length (all 4 points in the same place): var y0 = v[1], y1 = v[3],
if (!(x === curve[2] && y === curve[3] && x === curve[4] y2 = v[5], y3 = v[7],
&& y === curve[5] && x === curve[6] && y === curve[7])) { roots = [], tolerance = /*#=*/ Numerical.TOLERANCE,
winding += Curve._getWinding(curve, previous, point.x, point.y, i, li;
roots1, roots2); // Split the curve at y extrema, to get bezier curves with clear
previous = curve; // orientation: Calculate the derivative and find its roots.
var a = 3 * (y1 - y2) - y0 + y3,
b = 2 * (y0 + y2) - 4 * y1,
c = y1 - y0;
// Keep then range to 0 .. 1 (excluding) in the search
// for y extrema
var count = Numerical.solveQuadratic(a, b, c, roots, tolerance,
1 - tolerance);
if (count === 0) {
insertValues(v, dir);
} else {
roots.sort();
var parts, t = roots[0];
parts = Curve.subdivide(v, t);
if (count > 1) {
// Now renormalize t1 to the range of the next part.
t = (roots[1] - t) / (1 - t);
var subparts = Curve.subdivide(parts[1], t);
parts.splice(1, 1, subparts[0], subparts[1]);
}
for (i = 0, li = parts.length; i < li; i++)
insertValues(parts[i]);
}
}
// Insert curves that are monotonic in y direction into a cached array
monoCurves = this._monotoneCurves = [];
var curves = this.getCurves(),
crv, vals, i, li;
// If the path is not closed, we should join the end points
// with a straight line, just like how filling open paths works.
if (!this._closed) {
// if (!this.hasFill()
// || !this.getInternalRoughBounds()._containsPoint(point))
// return 0;
var segments = this._segments;
curves.push(new Curve(segments[segments.length - 1]._point,
segments[0]._point).getValues());
}
for (i = 0, li = curves.length; i < li; i++) {
crv = curves[i];
vals = crv.getValues();
if (crv.isLinear()) {
insertValues(vals);
} else {
var y0 = vals[1], y1 = vals[7];
if (y0 > y1) {
insertCurves(vals, DECREASING);
} else if (y0 == y1 && y0 == vals[3] && y0 == vals[5]) {
continue;
} else {
insertCurves(vals, INCREASING);
}
}
} }
} }
if (!closed) { return monoCurves;
winding += Curve._getWinding(last, previous, point.x, point.y,
roots1, roots2);
}
return winding;
}, },
// _getWinding: function(point) {
// var closed = this._closed;
// // If the path is not closed, we should not bail out in case it has a
// // fill color!
// if (!closed && !this.hasFill()
// || !this.getInternalRoughBounds()._containsPoint(point))
// return 0;
// // Use the crossing number algorithm, by counting the crossings of the
// // beam in right y-direction with the shape, and see if it's an odd
// // number, meaning the starting point is inside the shape.
// // http://en.wikipedia.org/wiki/Point_in_polygon
// var curves = this.getCurves(),
// segments = this._segments,
// winding = 0,
// // Reuse arrays for root-finding, give garbage collector a break
// roots1 = [],
// roots2 = [],
// last = (closed
// ? curves[curves.length - 1]
// // Create a straight closing line for open paths, just like
// // how filling open paths works.
// : new Curve(segments[segments.length - 1]._point,
// segments[0]._point)).getValues(),
// previous = last;
// for (var i = 0, l = curves.length; i < l; i++) {
// var curve = curves[i].getValues(),
// x = curve[0],
// y = curve[1];
// // Filter out curves with 0-length (all 4 points in the same place):
// if (!(x === curve[2] && y === curve[3] && x === curve[4]
// && y === curve[5] && x === curve[6] && y === curve[7])) {
// winding += Curve._getWinding(curve, previous, point.x, point.y,
// roots1, roots2);
// previous = curve;
// }
// }
// if (!closed) {
// winding += Curve._getWinding(last, previous, point.x, point.y,
// roots1, roots2);
// }
// return winding;
// },
_hitTest: function(point, options) { _hitTest: function(point, options) {
var that = this, var that = this,
style = this.getStyle(), style = this.getStyle(),