mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-08-28 22:08:54 -04:00
Use same stroke tolerance approach for hit-testing joins and caps as well.
This commit is contained in:
parent
7c28c7e9e3
commit
9177bac125
2 changed files with 31 additions and 49 deletions
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue