Correctly handle #strokeScaling in Path item hit-tests.

Hit-testing of Shape items with #strokeScaling still needs fixing too.
Relates to #697.
This commit is contained in:
Jürg Lehni 2016-01-17 21:46:35 +01:00
parent 43a6c57cd6
commit fd72ad0937
4 changed files with 25 additions and 18 deletions

View file

@ -1715,16 +1715,17 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
parentViewMatrix = options._viewMatrix,
// Keep the accumulated matrices up to this item in options, so we
// can keep calculating the correct _tolerancePadding values.
viewMatrix = options._viewMatrix = parentViewMatrix
viewMatrix = parentViewMatrix
? parentViewMatrix.appended(matrix)
// If this is the first one in the recursion, factor in the
// zoom of the view and the globalMatrix of the item.
: this.getGlobalMatrix().prepend(this.getView()._matrix),
strokeMatrix = viewMatrix.inverted(),
// Calculate the transformed padding as 2D size that describes the
// transformed tolerance circle / ellipse. Make sure it's never 0
// since we're using it for division.
tolerancePadding = options._tolerancePadding = new Size(
Path._getStrokePadding(1, viewMatrix.inverted())
Path._getStrokePadding(1, strokeMatrix)
).multiply(
Math.max(options.tolerance, /*#=*/Numerical.TOLERANCE)
);
@ -1774,17 +1775,18 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
var children = !res && this._children;
if (children) {
var opts = this._getChildHitTestOptions(options);
opts._viewMatrix = viewMatrix;
// Loop backwards, so items that get drawn last are tested first
for (var i = children.length - 1; i >= 0 && !res; i--)
res = children[i]._hitTest(point, opts);
// Restore viewMatrix for next child, as opts === options sometimes.
opts._viewMatrix = parentViewMatrix;
}
if (!res && checkSelf)
res = this._hitTestSelf(point, options);
res = this._hitTestSelf(point, options, strokeMatrix);
// Transform the point back to the outer coordinate system.
if (res && res.point)
res.point = matrix.transform(res.point);
// Restore viewMatrix for next child.
options._viewMatrix = parentViewMatrix;
return res;
},

View file

@ -280,14 +280,13 @@ var Shape = Item.extend(/** @lends Shape# */{
style = this._style,
strokeWidth = style.hasStroke() &&
/^getStrokeBounds$|^get.*RoughBounds$/.test(getter) &&
style.getStrokeWidth(),
strokePadding = strokeWidth && Path._getStrokePadding(
strokeWidth, style._getStrokeMatrix(matrix));
style.getStrokeWidth();
// If we're getting the strokeBounds, include the stroke width before
// or after transforming the rect, based on strokeScaling.
if (matrix)
rect = matrix._transformBounds(rect);
return strokePadding ? rect.expand(strokePadding) : rect;
return strokeWidth ? rect.expand(Path._getStrokePadding(
strokeWidth, style._getStrokeMatrix(matrix))) : rect;
}
},
new function() { // Scope for _contains() and _hitTestSelf() code.
@ -337,6 +336,7 @@ new function() { // Scope for _contains() and _hitTestSelf() code.
_hitTestSelf: function _hitTestSelf(point, options) {
var hit = false;
// TODO: Correctly support strokeScaling here too!
if (this.hasStroke()) {
var type = this._type,
radius = this._radius,

View file

@ -761,6 +761,7 @@ statics: {
* NOTE: padding is only used for Path.getBounds().
*/
_addBounds: function(v0, v1, v2, v3, coord, padding, min, max, roots) {
padding /= 2; // strokePadding is in width, not radius
// Code ported and further optimised from:
// http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
function add(value, padding) {

View file

@ -1518,7 +1518,7 @@ var Path = PathItem.extend(/** @lends Path# */{
toPath: '#clone',
_hitTestSelf: function(point, options) {
_hitTestSelf: function(point, options, strokeMatrix) {
var that = this,
style = this.getStyle(),
segments = this._segments,
@ -1545,8 +1545,11 @@ var Path = PathItem.extend(/** @lends Path# */{
join = style.getStrokeJoin();
cap = style.getStrokeCap();
miterLimit = radius * style.getMiterLimit();
// Add the stroke radius to tolerance padding.
strokePadding = tolerancePadding.add(new Point(radius, radius));
// Add the stroke radius to tolerance padding, taking
// #strokeScaling into account through _getStrokeMatrix().
strokePadding = tolerancePadding.add(
Path._getStrokePadding(radius,
!style.getStrokeScaling() && strokeMatrix));
} else {
join = cap = 'round';
}
@ -2768,19 +2771,20 @@ statics: {
if (!style.hasStroke())
return Path.getBounds(segments, closed, style, matrix);
var length = segments.length - (closed ? 0 : 1),
radius = style.getStrokeWidth() / 2,
strokeWidth = style.getStrokeWidth(),
strokeRadius = strokeWidth / 2,
strokeMatrix = style._getStrokeMatrix(matrix),
strokePadding = Path._getStrokePadding(radius, strokeMatrix),
strokePadding = Path._getStrokePadding(strokeWidth, strokeMatrix),
// Start with normal path bounds with added stroke padding. Then we
// only need to look at each segment and handle join / cap / miter.
bounds = Path.getBounds(segments, closed, style, matrix,
strokePadding),
join = style.getStrokeJoin(),
cap = style.getStrokeCap(),
miterLimit = radius * style.getMiterLimit(),
miterLimit = strokeRadius * style.getMiterLimit(),
// Create a rectangle of padding size, used for union with bounds
// further down
joinBounds = new Rectangle(new Size(strokePadding).multiply(2));
joinBounds = new Rectangle(new Size(strokePadding));
// helper function that is passed to _addBevelJoin() and _addSquareCap()
// to handle the point transformations. Use strokeMatrix here!
@ -2804,7 +2808,7 @@ statics: {
&& handleIn.isCollinear(handleOut)) {
addRound(segment);
} else {
Path._addBevelJoin(segment, join, radius, miterLimit, add);
Path._addBevelJoin(segment, join, strokeRadius, miterLimit, add);
}
}
@ -2812,7 +2816,7 @@ statics: {
if (cap === 'round') {
addRound(segment);
} else {
Path._addSquareCap(segment, cap, radius, add);
Path._addSquareCap(segment, cap, strokeRadius, add);
}
}