Fix hit-testing on Shape items and #strokeScaling.

Closes #697.
This commit is contained in:
Jürg Lehni 2016-01-18 00:41:48 +01:00
parent 1ac8e46d55
commit 4351ca310f

View file

@ -309,14 +309,20 @@ new function() { // Scope for _contains() and _hitTestSelf() code.
} }
} }
// Calculates the length of the ellipse radius that passes through the point function isOnEllipseStroke(point, radius, padding, quadrant) {
function getEllipseRadius(point, radius) { // Translate the ellipse / circle to the unit circle with radius 1, and
var angle = point.getAngleInRadians(), // translate the point along by dividing with radius (a number for
width = radius.width * 2, // circle, a size for ellipse). Then subtract the radius with the same
height = radius.height * 2, // direction as point (vector.normalize()), to get a vector that
x = width * Math.sin(angle), // describe proximity and direction to the stroke. Translate this back
y = height * Math.cos(angle); // by multiplying with radius, then divide by strokePadding to get a new
return width * height / (2 * Math.sqrt(x * x + y * y)); // vector in stroke space, and finally check its length.
var vector = point.divide(radius);
// We also have to check the vector's quadrant in case we're matching
// quarter ellipses in the corners.
return (!quadrant || vector.quadrant === quadrant) &&
vector.subtract(vector.normalize()).multiply(radius)
.divide(padding).length <= 1;
} }
return /** @lends Shape# */{ return /** @lends Shape# */{
@ -335,36 +341,35 @@ new function() { // Scope for _contains() and _hitTestSelf() code.
}, },
_hitTestSelf: function _hitTestSelf(point, options) { _hitTestSelf: function _hitTestSelf(point, options) {
var hit = false; var hit = false,
// TODO: Correctly support strokeScaling here too! style = this._style;
if (this.hasStroke()) { if (style.hasStroke()) {
var type = this._type, var type = this._type,
radius = this._radius, radius = this._radius,
strokeWidth = this.getStrokeWidth() + 2 * options.tolerance; strokeWidth = style.getStrokeWidth(),
strokePadding = options._tolerancePadding.add(
Path._getStrokePadding(strokeWidth / 2,
!style.getStrokeScaling() &&
options._strokeMatrix));
if (type === 'rectangle') { if (type === 'rectangle') {
var center = getCornerCenter(this, point, strokeWidth); var padding = strokePadding.multiply(2),
center = getCornerCenter(this, point, padding);
if (center) { if (center) {
// Check the stroke of the quarter corner ellipse, // Check the stroke of the quarter corner ellipse:
// similar to the ellipse check further down: hit = isOnEllipseStroke(point.subtract(center), radius,
var pt = point.subtract(center); strokePadding, center.getQuadrant());
hit = 2 * Math.abs(pt.getLength()
- getEllipseRadius(pt, radius)) <= strokeWidth;
} else { } else {
var rect = new Rectangle(this._size).setCenter(0, 0), var rect = new Rectangle(this._size).setCenter(0, 0),
outer = rect.expand(strokeWidth), outer = rect.expand(padding),
inner = rect.expand(-strokeWidth); inner = rect.expand(padding.negate());
hit = outer._containsPoint(point) hit = outer._containsPoint(point)
&& !inner._containsPoint(point); && !inner._containsPoint(point);
} }
} else { } else {
if (type === 'ellipse') hit = isOnEllipseStroke(point, radius, strokePadding);
radius = getEllipseRadius(point, radius);
hit = 2 * Math.abs(point.getLength() - radius)
<= strokeWidth;
} }
} }
return hit return hit ? new HitResult('stroke', this)
? new HitResult('stroke', this)
: _hitTestSelf.base.apply(this, arguments); : _hitTestSelf.base.apply(this, arguments);
} }
}; };