Fix handling of hit-test tolerance on scaled items with #applyMatrix = false

Closes #1195
This commit is contained in:
Jürg Lehni 2017-01-08 14:34:58 +01:00
parent 50c910b03a
commit 0ae2ded9cc
2 changed files with 76 additions and 24 deletions

View file

@ -1881,15 +1881,16 @@ new function() { // Injection scope for hit-test functions shared with project
// 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 = this.getStrokeScaling()
? null
: viewMatrix.inverted()._shiftless(),
// 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.
tolerance = Math.max(options.tolerance, /*#=*/Numerical.TOLERANCE),
// Hit-tests are performed in the item's local coordinate space.
// To calculate the correct 2D padding for tolerance, we therefore
// need to apply the inverted item matrix.
tolerancePadding = options._tolerancePadding = new Size(
Path._getStrokePadding(tolerance, strokeMatrix));
Path._getStrokePadding(tolerance,
matrix.inverted()._shiftless()));
// Transform point to local coordinates.
point = matrix._inverseTransform(point);
// If the matrix is non-reversible, point will now be `null`:
@ -1958,7 +1959,10 @@ new function() { // Injection scope for hit-test functions shared with project
// it is already called internally.
|| checkSelf
&& filter(this._hitTestSelf(point, options, viewMatrix,
strokeMatrix))
// If the item has a non-scaling stroke, we need to
// apply the inverted viewMatrix to stroke dimensions.
this.getStrokeScaling() ? null
: viewMatrix.inverted()._shiftless()))
|| null;
}
// Transform the point back to the outer coordinate system.

View file

@ -29,32 +29,32 @@ test('Hit-testing options', function() {
equals(HitResult.getOptions(), defaultOptions, 'Default options');
});
function testHitResult(hitResult, options, message) {
equals(!!(!!hitResult ^ !!options), false, message
function testHitResult(hitResult, expeced, message) {
equals(!!hitResult, !!expeced, message
? message
: options
: expeced
? 'A HitResult should be returned.'
: 'No HitResult should be returned.');
if (hitResult && options) {
if (options.type) {
equals(hitResult.type, options.type,
'hitResult.type == \'' + options.type + '\'');
if (hitResult && expeced) {
if (expeced.type) {
equals(hitResult.type, expeced.type,
'hitResult.type == \'' + expeced.type + '\'');
}
if (options.item) {
equals(hitResult.item == options.item, true,
'hitResult.item == ' + options.item);
if (expeced.item) {
equals(hitResult.item == expeced.item, true,
'hitResult.item == ' + expeced.item);
}
if (options.name) {
equals(hitResult.name, options.name,
'hitResult.name == \'' + options.name + '\'');
if (expeced.name) {
equals(hitResult.name, expeced.name,
'hitResult.name == \'' + expeced.name + '\'');
}
if (options.point) {
equals(hitResult.point.toString(), options.point.toString(),
'hitResult.point == \'' + options.point + '\'');
if (expeced.point) {
equals(hitResult.point.toString(), expeced.point.toString(),
'hitResult.point == \'' + expeced.point + '\'');
}
if (options.segment) {
equals(hitResult.segment == options.segment, true,
'hitResult.segment == ' + options.segment);
if (expeced.segment) {
equals(hitResult.segment == expeced.segment, true,
'hitResult.segment == ' + expeced.segment);
}
}
}
@ -791,4 +791,52 @@ test('hit-testing shapes with strokes and rounded corners (#1207)', function() {
});
}
});
test('hit-testing scaled items with different settings of view.zoom and item.strokeScaling (#1195)', function() {
function testItem(ctor, zoom, strokeScaling) {
var item = new ctor.Rectangle({
point: [100, 100],
size: [100, 100],
fillColor: 'red',
strokeColor: 'black',
strokeWidth: 10,
strokeScaling: strokeScaling,
applyMatrix: true
});
item.scale(2);
view.zoom = zoom;
var tolerance = 10,
options = { tolerance: tolerance, fill: true, stroke: true },
bounds = item.strokeBounds,
point = bounds.leftCenter,
name = ctor.name + '.Rectangle, strokeScaling = ' + strokeScaling
+ ', zoom = ' + zoom;
testHitResult(project.hitTest(point.subtract(tolerance + 1, 0), options),
null,
name + ' outside of stroke'
);
testHitResult(project.hitTest(point.subtract(tolerance, 0), options),
{ type: 'stroke' },
name + ' on stroke within tolerance'
);
testHitResult(project.hitTest(point, options),
{ type: 'stroke' },
name + ' on stroke'
);
item.remove();
}
testItem(Shape, 1, false);
testItem(Shape, 1, true);
testItem(Shape, 2, false);
testItem(Shape, 2, true);
testItem(Path, 1, false);
testItem(Path, 1, true);
testItem(Path, 2, false);
testItem(Path, 2, true);
});
// TODO: project.hitTest(point, {type: AnItemType});