_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(' ');
},
_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,
winding = 0;
monoCurves = [];
for (var i = 0, l = children.length; i < l; i++)
winding += children[i]._getWinding(point);
return winding;
monoCurves.push.apply(monoCurves, children[i]._getMonotoneCurves());
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) {
// If we're not specifically asked to returns paths through
// 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++)
this._curves[i]._changed(/*#=*/ Change.GEOMETRY);
}
// Clear cached curves used for winding direction and containment
// calculation.
this._monotoneCurves = undefined;
} else if (flags & /*#=*/ ChangeFlag.STROKE) {
// TODO: We could preserve the purely geometric bounds that are not
// affected by stroke: _bounds.bounds and _bounds.handleBounds
@ -1708,49 +1711,140 @@ var Path = PathItem.extend(/** @lends Path# */{
return null;
},
_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;
/**
* Private method that returns and caches all the curves in this Path, which
* are monotonically decreasing or increasing in the 'y' direction.
* Used by PathItem#_getWinding method.
*/
_getMonotoneCurves: function() {
var monoCurves = this._monotoneCurves,
// TODO: replace instances with constants ( /*#=*/ ?)
INCREASING = 1,
DECREASING = -1;
if (!monoCurves) {
// Insert curve values into a cached array
// Always avoid horizontal curves
function insertValues(v, dir) {
var y0 = v[1], y1 = v[7];
dir = dir || INCREASING;
if (y0 === y1) {
return;
} else if (y0 > y1) {
dir = DECREASING;
}
v.push(dir);
monoCurves.push(v);
}
// Handle bezier curves. We need to chop them into smaller curves
// with defined orientation, by solving the derivative curve for
// Y extrema.
function insertCurves(v, dir) {
var y0 = v[1], y1 = v[3],
y2 = v[5], y3 = v[7],
roots = [], tolerance = /*#=*/ Numerical.TOLERANCE,
i, li;
// 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;
// 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) {
winding += Curve._getWinding(last, previous, point.x, point.y,
roots1, roots2);
}
return winding;
return monoCurves;
},
// _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) {
var that = this,
style = this.getStyle(),