mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-22 05:06:09 +03:00
Merge pull request #939 from avandecreme/perf
Avoid loading clipped out tiles. Fix #889.
This commit is contained in:
commit
bd743d688a
163
src/rectangle.js
163
src/rectangle.js
@ -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 intersect = getIntersection(thisSegment[0], thisSegment[1],
|
||||
rectSegment[0], rectSegment[1]);
|
||||
if (intersect) {
|
||||
intersectionPoints.push(intersect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 k = 1; k < intersectionPoints.length; k++) {
|
||||
var point = intersectionPoints[k];
|
||||
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.
|
||||
@ -389,10 +546,10 @@ $.Rect.prototype = {
|
||||
*/
|
||||
toString: function() {
|
||||
return "[" +
|
||||
(Math.round(this.x * 100) / 100) + "," +
|
||||
(Math.round(this.y * 100) / 100) + "," +
|
||||
(Math.round(this.x * 100) / 100) + ", " +
|
||||
(Math.round(this.y * 100) / 100) + ", " +
|
||||
(Math.round(this.width * 100) / 100) + "x" +
|
||||
(Math.round(this.height * 100) / 100) + "," +
|
||||
(Math.round(this.height * 100) / 100) + ", " +
|
||||
(Math.round(this.degrees * 100) / 100) + "deg" +
|
||||
"]";
|
||||
}
|
||||
|
@ -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 ) {
|
||||
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();
|
||||
|
||||
|
@ -664,15 +664,15 @@ $.Viewport.prototype = {
|
||||
var oldBounds = this.getBounds();
|
||||
var oldZoom = this.getZoom();
|
||||
|
||||
if (Math.abs(newZoom - oldZoom) < 0.00000001 ||
|
||||
Math.abs(newBounds.width - oldBounds.width) < 0.00000001) {
|
||||
if (oldZoom === 0 || Math.abs(newZoom / oldZoom - 1) < 0.00000001) {
|
||||
this.zoomTo(newZoom, true);
|
||||
return this.panTo(center, immediately);
|
||||
}
|
||||
|
||||
newBounds = newBounds.rotate(-this.getRotation());
|
||||
var referencePoint = newBounds.getTopLeft().divide(newBounds.width)
|
||||
.minus(oldBounds.getTopLeft().divide(oldBounds.width))
|
||||
.divide(1 / newBounds.width - 1 / oldBounds.width);
|
||||
var referencePoint = newBounds.getTopLeft().times(newZoom)
|
||||
.minus(oldBounds.getTopLeft().times(oldZoom))
|
||||
.divide(newZoom - oldZoom);
|
||||
|
||||
return this.zoomTo(newZoom, referencePoint, immediately);
|
||||
},
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
})();
|
||||
|
@ -587,6 +587,44 @@
|
||||
viewer.open(DZI_PATH);
|
||||
});
|
||||
|
||||
asyncTest('fitBounds with almost same zoom', function() {
|
||||
var openHandler = function() {
|
||||
var viewport = viewer.viewport;
|
||||
var rect1 = new OpenSeadragon.Rect(0, 0, 1, 1);
|
||||
viewport.fitBounds(rect1, true);
|
||||
Util.assertRectangleEquals(rect1, viewport.getBounds(), 1e-6,
|
||||
'Bounds should be ' + rect1);
|
||||
|
||||
// Zoom and pan
|
||||
var rect2 = new OpenSeadragon.Rect(1, 1, 1 + 1e-8, 1 + 1e-8);
|
||||
viewport.fitBounds(rect2);
|
||||
Util.assertRectangleEquals(rect2, viewport.getBounds(), 1e-6,
|
||||
'Bounds should be ' + rect2);
|
||||
start();
|
||||
};
|
||||
viewer.addOnceHandler('open', openHandler);
|
||||
viewer.open(DZI_PATH);
|
||||
});
|
||||
|
||||
asyncTest('fitBounds with big rectangle', function() {
|
||||
var openHandler = function() {
|
||||
var viewport = viewer.viewport;
|
||||
var rect1 = new OpenSeadragon.Rect(0, 0, 1e9, 1e9);
|
||||
viewport.fitBounds(rect1, true);
|
||||
Util.assertRectangleEquals(rect1, viewport.getBounds(), 1e-6,
|
||||
'Bounds should be ' + rect1);
|
||||
|
||||
// Zoom and pan
|
||||
var rect2 = new OpenSeadragon.Rect(1, 1, 2e9, 2e9);
|
||||
viewport.fitBounds(rect2);
|
||||
Util.assertRectangleEquals(rect2, viewport.getBounds(), 1e-6,
|
||||
'Bounds should be ' + rect2);
|
||||
start();
|
||||
};
|
||||
viewer.addOnceHandler('open', openHandler);
|
||||
viewer.open(DZI_PATH);
|
||||
});
|
||||
|
||||
asyncTest('fitHorizontally', function(){
|
||||
var openHandler = function(event) {
|
||||
viewer.removeHandler('open', openHandler);
|
||||
|
Loading…
Reference in New Issue
Block a user