Avoid loading clipped out tiles. Fix #889.

This commit is contained in:
Antoine Vandecreme 2016-05-13 15:18:37 -04:00
parent 6099962e40
commit 352bfbc3a5
3 changed files with 255 additions and 9 deletions

View File

@ -307,6 +307,132 @@ $.Rect.prototype = {
bottom - top);
},
/**
* Returns the bounding box of the intersection of this rectangle with the
* given rectangle.
* @param {OpenSeadragon.Rect} rect
* @return {OpenSeadragon.Rect} the bounding box of the intersection
* or null if the rectangles don't intersect.
*/
intersection: function(rect) {
// Simplified version of Weiler Atherton clipping algorithm
// https://en.wikipedia.org/wiki/Weiler%E2%80%93Atherton_clipping_algorithm
// Because we just want the bounding box of the intersection,
// we can just compute the bounding box of:
// 1. all the summits of this which are inside rect
// 2. all the summits of rect which are inside this
// 3. all the intersections of rect and this
var EPSILON = 0.0000000001;
var intersectionPoints = [];
var thisTopLeft = this.getTopLeft();
if (rect.containsPoint(thisTopLeft, EPSILON)) {
intersectionPoints.push(thisTopLeft);
}
var thisTopRight = this.getTopRight();
if (rect.containsPoint(thisTopRight, EPSILON)) {
intersectionPoints.push(thisTopRight);
}
var thisBottomLeft = this.getBottomLeft();
if (rect.containsPoint(thisBottomLeft, EPSILON)) {
intersectionPoints.push(thisBottomLeft);
}
var thisBottomRight = this.getBottomRight();
if (rect.containsPoint(thisBottomRight, EPSILON)) {
intersectionPoints.push(thisBottomRight);
}
var rectTopLeft = rect.getTopLeft();
if (this.containsPoint(rectTopLeft, EPSILON)) {
intersectionPoints.push(rectTopLeft);
}
var rectTopRight = rect.getTopRight();
if (this.containsPoint(rectTopRight, EPSILON)) {
intersectionPoints.push(rectTopRight);
}
var rectBottomLeft = rect.getBottomLeft();
if (this.containsPoint(rectBottomLeft, EPSILON)) {
intersectionPoints.push(rectBottomLeft);
}
var rectBottomRight = rect.getBottomRight();
if (this.containsPoint(rectBottomRight, EPSILON)) {
intersectionPoints.push(rectBottomRight);
}
var thisSegments = this._getSegments();
var rectSegments = rect._getSegments();
for (var i = 0; i < thisSegments.length; i++) {
var thisSegment = thisSegments[i];
for (var j = 0; j < rectSegments.length; j++) {
var rectSegment = rectSegments[j];
var point = getIntersection(thisSegment[0], thisSegment[1],
rectSegment[0], rectSegment[1]);
if (point) {
intersectionPoints.push(point);
}
}
}
// Get intersection point of segments [a,b] and [c,d]
function getIntersection(a, b, c, d) {
// http://stackoverflow.com/a/1968345/1440403
var abVector = b.minus(a);
var cdVector = d.minus(c);
var denom = -cdVector.x * abVector.y + abVector.x * cdVector.y;
if (denom === 0) {
return null;
}
var s = (abVector.x * (a.y - c.y) - abVector.y * (a.x - c.x)) / denom;
var t = (cdVector.x * (a.y - c.y) - cdVector.y * (a.x - c.x)) / denom;
if (-EPSILON <= s && s <= 1 - EPSILON &&
-EPSILON <= t && t <= 1 - EPSILON) {
return new $.Point(a.x + t * abVector.x, a.y + t * abVector.y);
}
return null;
}
if (intersectionPoints.length === 0) {
return null;
}
var minX = intersectionPoints[0].x;
var maxX = intersectionPoints[0].x;
var minY = intersectionPoints[0].y;
var maxY = intersectionPoints[0].y;
for (var i = 1; i < intersectionPoints.length; i++) {
var point = intersectionPoints[i];
if (point.x < minX) {
minX = point.x;
}
if (point.x > maxX) {
maxX = point.x;
}
if (point.y < minY) {
minY = point.y;
}
if (point.y > maxY) {
maxY = point.y;
}
}
return new $.Rect(minX, minY, maxX - minX, maxY - minY);
},
// private
_getSegments: function() {
var topLeft = this.getTopLeft();
var topRight = this.getTopRight();
var bottomLeft = this.getBottomLeft();
var bottomRight = this.getBottomRight();
return [[topLeft, topRight],
[topRight, bottomRight],
[bottomRight, bottomLeft],
[bottomLeft, topLeft]];
},
/**
* Rotates a rectangle around a point.
* @function
@ -381,6 +507,37 @@ $.Rect.prototype = {
return new $.Rect(x, y, width, height);
},
/**
* Determines whether a point is inside this rectangle (edge included).
* @function
* @param {OpenSeadragon.Point} point
* @param {Number} [epsilon=0] the margin of error allowed
* @returns {Boolean} true if the point is inside this rectangle, false
* otherwise.
*/
containsPoint: function(point, epsilon) {
epsilon = epsilon || 0;
// See http://stackoverflow.com/a/2752754/1440403 for explanation
var topLeft = this.getTopLeft();
var topRight = this.getTopRight();
var bottomLeft = this.getBottomLeft();
var topDiff = topRight.minus(topLeft);
var leftDiff = bottomLeft.minus(topLeft);
return ((point.x - topLeft.x) * topDiff.x +
(point.y - topLeft.y) * topDiff.y >= -epsilon) &&
((point.x - topRight.x) * topDiff.x +
(point.y - topRight.y) * topDiff.y <= epsilon) &&
((point.x - topLeft.x) * leftDiff.x +
(point.y - topLeft.y) * leftDiff.y >= -epsilon) &&
((point.x - bottomLeft.x) * leftDiff.x +
(point.y - bottomLeft.y) * leftDiff.y <= epsilon);
},
/**
* Provides a string representation of the rectangle which is useful for
* debugging.

View File

@ -787,7 +787,6 @@ function updateViewport( tiledImage ) {
Math.log( 2 )
))
),
degrees = tiledImage.viewport.degrees,
renderPixelRatioC,
renderPixelRatioT,
zeroRatioT,
@ -795,16 +794,24 @@ function updateViewport( tiledImage ) {
levelOpacity,
levelVisibility;
viewportBounds = viewportBounds.getBoundingBox();
viewportBounds.x -= tiledImage._xSpring.current.value;
viewportBounds.y -= tiledImage._ySpring.current.value;
// Reset tile's internal drawn state
while (tiledImage.lastDrawn.length > 0) {
tile = tiledImage.lastDrawn.pop();
tile.beingDrawn = false;
}
if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) {
var tiledImageBounds = tiledImage.getClippedBounds(true);
var intersection = viewportBounds.intersection(tiledImageBounds);
if (intersection === null) {
return;
}
viewportBounds = intersection;
}
viewportBounds = viewportBounds.getBoundingBox();
viewportBounds.x -= tiledImage._xSpring.current.value;
viewportBounds.y -= tiledImage._ySpring.current.value;
var viewportTL = viewportBounds.getTopLeft();
var viewportBR = viewportBounds.getBottomRight();

View File

@ -161,6 +161,65 @@
"Incorrect union with non horizontal rectangles.");
});
test('intersection', function() {
var rect1 = new OpenSeadragon.Rect(2, 2, 2, 3);
var rect2 = new OpenSeadragon.Rect(0, 1, 1, 1);
var expected = null;
var actual = rect1.intersection(rect2);
equal(expected, actual,
"Rectangle " + rect2 + " should not intersect " + rect1);
actual = rect2.intersection(rect1);
equal(expected, actual,
"Rectangle " + rect1 + " should not intersect " + rect2);
rect1 = new OpenSeadragon.Rect(0, 0, 2, 1);
rect2 = new OpenSeadragon.Rect(1, 0, 2, 2);
expected = new OpenSeadragon.Rect(1, 0, 1, 1);
actual = rect1.intersection(rect2);
Util.assertRectangleEquals(expected, actual, precision,
"Intersection of " + rect2 + " with " + rect1 + " should be " +
expected);
actual = rect2.intersection(rect1);
Util.assertRectangleEquals(expected, actual, precision,
"Intersection of " + rect1 + " with " + rect2 + " should be " +
expected);
rect1 = new OpenSeadragon.Rect(0, 0, 3, 3);
rect2 = new OpenSeadragon.Rect(1, 1, 1, 1);
expected = new OpenSeadragon.Rect(1, 1, 1, 1);
actual = rect1.intersection(rect2);
Util.assertRectangleEquals(expected, actual, precision,
"Intersection of " + rect2 + " with " + rect1 + " should be " +
expected);
actual = rect2.intersection(rect1);
Util.assertRectangleEquals(expected, actual, precision,
"Intersection of " + rect1 + " with " + rect2 + " should be " +
expected);
rect1 = new OpenSeadragon.Rect(2, 2, 2, 3, 45);
rect2 = new OpenSeadragon.Rect(0, 1, 1, 1);
expected = null;
actual = rect1.intersection(rect2);
equal(expected, actual,
"Rectangle " + rect2 + " should not intersect " + rect1);
actual = rect2.intersection(rect1);
equal(expected, actual,
"Rectangle " + rect1 + " should not intersect " + rect2);
rect1 = new OpenSeadragon.Rect(2, 0, 2, 3, 45);
rect2 = new OpenSeadragon.Rect(0, 1, 1, 1);
expected = new OpenSeadragon.Rect(0, 1, 1, 1);
actual = rect1.intersection(rect2);
Util.assertRectangleEquals(expected, actual, precision,
"Intersection of " + rect2 + " with " + rect1 + " should be " +
expected);
actual = rect2.intersection(rect1);
Util.assertRectangleEquals(expected, actual, precision,
"Intersection of " + rect1 + " with " + rect2 + " should be " +
expected);
});
test('rotate', function() {
var rect = new OpenSeadragon.Rect(0, 0, 2, 1);
@ -218,4 +277,27 @@
"Bounding box of rect rotated 270deg.");
});
test('containsPoint', function() {
var rect = new OpenSeadragon.Rect(0, 0, 1, 1, 45);
ok(rect.containsPoint(new OpenSeadragon.Point(0, 0)),
'Point 0,0 should be inside ' + rect);
ok(rect.containsPoint(rect.getTopRight()),
'Top right vertex should be inside ' + rect);
ok(rect.containsPoint(rect.getBottomRight()),
'Bottom right vertex should be inside ' + rect);
ok(rect.containsPoint(rect.getBottomLeft()),
'Bottom left vertex should be inside ' + rect);
ok(rect.containsPoint(rect.getCenter()),
'Center should be inside ' + rect);
notOk(rect.containsPoint(new OpenSeadragon.Point(1, 0)),
'Point 1,0 should not be inside ' + rect);
ok(rect.containsPoint(new OpenSeadragon.Point(0.5, 0.5)),
'Point 0.5,0.5 should be inside ' + rect);
ok(rect.containsPoint(new OpenSeadragon.Point(0.4, 0.5)),
'Point 0.4,0.5 should be inside ' + rect);
notOk(rect.containsPoint(new OpenSeadragon.Point(0.6, 0.5)),
'Point 0.6,0.5 should not be inside ' + rect);
});
})();