Use same stroke tolerance approach for hit-testing joins and caps as well.

This commit is contained in:
Jürg Lehni 2013-12-10 14:23:05 +01:00
parent 7c28c7e9e3
commit 9177bac125
2 changed files with 31 additions and 49 deletions

View file

@ -71,7 +71,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
_initialize: function(props, point) {
// Define this Item's unique id. But allow the creation of internally
// used paths with no ids.
var internal = props && props._internal === true;
var internal = props && props.internal === true;
if (!internal)
this._id = Item._id = (Item._id || 0) + 1;
// Handle matrix before everything else, to avoid issues with
@ -1619,18 +1619,22 @@ var Item = Base.extend(Callback, /** @lends Item# */{
var matrix = this._matrix,
parentTotalMatrix = options._totalMatrix,
// Keep the accumulated matrices up to this item in options, so we
// can keep calculating the correct _padding values.
// can keep calculating the correct _tolerancePadding values.
totalMatrix = options._totalMatrix = parentTotalMatrix.clone()
.concatenate(matrix),
// Calculate the transformed padding as 2D size that describes the
// transformed tolerance circle / ellipse.
padding = options._padding = new Size(Path._getPenPadding(1,
totalMatrix.inverted())).multiply(options.tolerance);
// transformed tolerance circle / ellipse. Make sure it's never 0
// since we're using it for division.
tolerancePadding = options._tolerancePadding = new Size(
Path._getPenPadding(1, totalMatrix.inverted())
).multiply(
Math.max(options.tolerance, /*#=*/ Numerical.TOLERANCE)
);
// Transform point to local coordinates.
point = matrix._inverseTransform(point);
if (!this._children && !this.getInternalRoughBounds()
.expand(padding.multiply(2))._containsPoint(point))
.expand(tolerancePadding.multiply(2))._containsPoint(point))
return null;
// Filter for type, guides and selected items if that's required.
var checkSelf = !(options.guides && !this._guide
@ -1643,7 +1647,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
var pt = bounds['get' + part]();
// Since there are transformations, we cannot simply use a numerical
// tolerance value. Instead, we divide by a padding size, see above.
if (point.subtract(pt).divide(padding).length <= 1)
if (point.subtract(pt).divide(tolerancePadding).length <= 1)
return new HitResult(type, that,
{ name: Base.hyphenate(part), point: pt });
}

View file

@ -1758,7 +1758,8 @@ var Path = PathItem.extend(/** @lends Path# */{
closed = this._closed,
// transformed tolerance padding, see Item#hitTest. We will add
// stroke padding on top if stroke is defined.
padding = options._padding,
tolerancePadding = options._tolerancePadding,
strokePadding = tolerancePadding,
join, cap, miterLimit,
area, loc, res,
hasStroke = options.stroke && style.hasStroke(),
@ -1773,7 +1774,7 @@ var Path = PathItem.extend(/** @lends Path# */{
cap = style.getStrokeCap();
miterLimit = radius * style.getMiterLimit();
// Add the stroke radius to tolerance padding.
padding = padding.add(new Size(radius, radius));
strokePadding = tolerancePadding.add(new Point(radius, radius));
} else {
join = cap = 'round';
}
@ -1782,12 +1783,12 @@ var Path = PathItem.extend(/** @lends Path# */{
// locations to extend the fill area by tolerance.
}
function isCloseEnough(pt) {
function isCloseEnough(pt, padding) {
return point.subtract(pt).divide(padding).length <= 1;
}
function checkPoint(seg, pt, name) {
if (isCloseEnough(pt))
if (isCloseEnough(pt), strokePadding)
return new HitResult(name, that, { segment: seg, point: pt });
}
@ -1804,37 +1805,16 @@ var Path = PathItem.extend(/** @lends Path# */{
// Code to check stroke join / cap areas
function addAreaPoint(point) {
area.push(point);
}
// In order to be able to reuse crossings counting code, we describe
// each line as a curve values array.
function getAreaCurve(index) {
var p1 = area[index],
p2 = area[(index + 1) % area.length];
return [p1.x, p1.y, p1.x, p1.y, p2.x, p2.y, p2.x ,p2.y];
}
function isInArea(point) {
var length = area.length,
previous = getAreaCurve(length - 1),
roots1 = [],
roots2 = [],
winding = 0;
for (var i = 0; i < length; i++) {
var curve = getAreaCurve(i);
winding += Curve._getWinding(curve, previous, point.x, point.y,
roots1, roots2);
previous = curve;
}
return !!winding;
area.add(point);
}
function checkSegmentStroke(segment) {
// Handle joins / caps that are not round specificelly, by
// hit-testing their polygon areas.
if (join !== 'round' || cap !== 'round') {
area = [];
// Create an 'internal' path without id and outside the DOM
// to run the hit-test on it.
area = new Path({ internal: true, closed: true });
if (closed || segment._index > 0
&& segment._index < segments.length - 1) {
// It's a join. See that it's not a round one (one of
@ -1848,11 +1828,18 @@ var Path = PathItem.extend(/** @lends Path# */{
Path._addSquareCap(segment, cap, radius, addAreaPoint, true);
}
// See if the above produced an area to check for
if (area.length > 0)
return isInArea(point);
if (!area.isEmpty()) {
// Also use stroke check with tolerancePadding if the point
// is not inside the area itself, to use test caps and joins
// with same tolerance.
var loc;
return area.contains(point)
|| (loc = area.getNearestLocation(point))
&& isCloseEnough(loc.getPoint(), tolerancePadding);
}
}
// Fallback scenario is a round join / cap.
return isCloseEnough(segment._point);
return isCloseEnough(segment._point, strokePadding);
}
// If we're asked to query for segments, ends or handles, do all that
@ -1878,7 +1865,7 @@ var Path = PathItem.extend(/** @lends Path# */{
if (parameter === 0 || parameter === 1) {
if (!checkSegmentStroke(loc.getSegment()))
loc = null;
} else if (!isCloseEnough(loc.getPoint())) {
} else if (!isCloseEnough(loc.getPoint(), strokePadding)) {
loc = null;
}
}
@ -1895,15 +1882,6 @@ var Path = PathItem.extend(/** @lends Path# */{
}
}
}
if (loc) {
var circle = new Path.Ellipse({
center: loc.getPoint(),
radius: padding,
strokeColor: 'green',
guide: true
});
circle.transform(that.globalMatrix);
}
// Don't process loc yet, as we also need to query for stroke after fill
// in some cases. Simply skip fill query if we already have a matching
// stroke. If we have a loc and no stroke then it's a result for fill.