mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
Fix handling of hit-test tolerance on scaled items with #applyMatrix = false
Closes #1195
This commit is contained in:
parent
50c910b03a
commit
0ae2ded9cc
2 changed files with 76 additions and 24 deletions
|
@ -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
|
// If this is the first one in the recursion, factor in the
|
||||||
// zoom of the view and the globalMatrix of the item.
|
// zoom of the view and the globalMatrix of the item.
|
||||||
: this.getGlobalMatrix().prepend(this.getView()._matrix),
|
: this.getGlobalMatrix().prepend(this.getView()._matrix),
|
||||||
strokeMatrix = this.getStrokeScaling()
|
|
||||||
? null
|
|
||||||
: viewMatrix.inverted()._shiftless(),
|
|
||||||
// Calculate the transformed padding as 2D size that describes the
|
// Calculate the transformed padding as 2D size that describes the
|
||||||
// transformed tolerance circle / ellipse. Make sure it's never 0
|
// transformed tolerance circle / ellipse. Make sure it's never 0
|
||||||
// since we're using it for division.
|
// since we're using it for division.
|
||||||
tolerance = Math.max(options.tolerance, /*#=*/Numerical.TOLERANCE),
|
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(
|
tolerancePadding = options._tolerancePadding = new Size(
|
||||||
Path._getStrokePadding(tolerance, strokeMatrix));
|
Path._getStrokePadding(tolerance,
|
||||||
|
matrix.inverted()._shiftless()));
|
||||||
// Transform point to local coordinates.
|
// Transform point to local coordinates.
|
||||||
point = matrix._inverseTransform(point);
|
point = matrix._inverseTransform(point);
|
||||||
// If the matrix is non-reversible, point will now be `null`:
|
// 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.
|
// it is already called internally.
|
||||||
|| checkSelf
|
|| checkSelf
|
||||||
&& filter(this._hitTestSelf(point, options, viewMatrix,
|
&& 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;
|
|| null;
|
||||||
}
|
}
|
||||||
// Transform the point back to the outer coordinate system.
|
// Transform the point back to the outer coordinate system.
|
||||||
|
|
|
@ -29,32 +29,32 @@ test('Hit-testing options', function() {
|
||||||
equals(HitResult.getOptions(), defaultOptions, 'Default options');
|
equals(HitResult.getOptions(), defaultOptions, 'Default options');
|
||||||
});
|
});
|
||||||
|
|
||||||
function testHitResult(hitResult, options, message) {
|
function testHitResult(hitResult, expeced, message) {
|
||||||
equals(!!(!!hitResult ^ !!options), false, message
|
equals(!!hitResult, !!expeced, message
|
||||||
? message
|
? message
|
||||||
: options
|
: expeced
|
||||||
? 'A HitResult should be returned.'
|
? 'A HitResult should be returned.'
|
||||||
: 'No HitResult should be returned.');
|
: 'No HitResult should be returned.');
|
||||||
if (hitResult && options) {
|
if (hitResult && expeced) {
|
||||||
if (options.type) {
|
if (expeced.type) {
|
||||||
equals(hitResult.type, options.type,
|
equals(hitResult.type, expeced.type,
|
||||||
'hitResult.type == \'' + options.type + '\'');
|
'hitResult.type == \'' + expeced.type + '\'');
|
||||||
}
|
}
|
||||||
if (options.item) {
|
if (expeced.item) {
|
||||||
equals(hitResult.item == options.item, true,
|
equals(hitResult.item == expeced.item, true,
|
||||||
'hitResult.item == ' + options.item);
|
'hitResult.item == ' + expeced.item);
|
||||||
}
|
}
|
||||||
if (options.name) {
|
if (expeced.name) {
|
||||||
equals(hitResult.name, options.name,
|
equals(hitResult.name, expeced.name,
|
||||||
'hitResult.name == \'' + options.name + '\'');
|
'hitResult.name == \'' + expeced.name + '\'');
|
||||||
}
|
}
|
||||||
if (options.point) {
|
if (expeced.point) {
|
||||||
equals(hitResult.point.toString(), options.point.toString(),
|
equals(hitResult.point.toString(), expeced.point.toString(),
|
||||||
'hitResult.point == \'' + options.point + '\'');
|
'hitResult.point == \'' + expeced.point + '\'');
|
||||||
}
|
}
|
||||||
if (options.segment) {
|
if (expeced.segment) {
|
||||||
equals(hitResult.segment == options.segment, true,
|
equals(hitResult.segment == expeced.segment, true,
|
||||||
'hitResult.segment == ' + options.segment);
|
'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});
|
// TODO: project.hitTest(point, {type: AnItemType});
|
||||||
|
|
Loading…
Reference in a new issue