Merge pull request #621 from Kienz/selectionFix

Fix isContainedWithinRect and isContainedWithinObject - Closes #610
This commit is contained in:
Juriy Zaytsev 2013-05-09 11:18:20 -07:00
commit 792d40a482
4 changed files with 308 additions and 127 deletions

View file

@ -195,23 +195,11 @@
*/
containsPoint: function (e, target) {
var pointer = this.getPointer(e),
xy = this._normalizePointer(target, pointer),
x = xy.x,
y = xy.y;
xy = this._normalizePointer(target, pointer);
// http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
// http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
// we iterate through each object. If target found, return it.
var iLines = target._getImageLines(target.oCoords),
xpoints = target._findCrossPoints(x, y, iLines);
// if xcount is odd then we clicked inside the object
// For the specific case of square images xcount === 1 in all true cases
if ((xpoints && xpoints % 2 === 1) || target._findTargetCorner(e, this._offset)) {
return true;
}
return false;
return (target.containsPoint(xy) || target._findTargetCorner(e, this._offset));
},
/**
@ -661,7 +649,9 @@
if (!currentObject) continue;
if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) ||
currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2)) {
currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) ||
currentObject.containsPoint(selectionX1Y1) ||
currentObject.containsPoint(selectionX2Y2)) {
if (this.selection && currentObject.selectable) {
currentObject.set('active', true);

View file

@ -5,12 +5,12 @@
fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
/**
* Returns true if object intersects with an area formed by 2 points
* @param {Object} selectionTL
* @param {Object} selectionBR
* @return {Boolean}
* Checks if object intersects with an area formed by 2 points
* @param {Object} pointTL top-left point of area
* @param {Object} pointBR bottom-right point of area
* @return {Boolean} true if object intersects with an area formed by 2 points
*/
intersectsWithRect: function(selectionTL, selectionBR) {
intersectsWithRect: function(pointTL, pointBR) {
var oCoords = this.oCoords,
tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y),
tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y),
@ -19,16 +19,16 @@
var intersection = fabric.Intersection.intersectPolygonRectangle(
[tl, tr, br, bl],
selectionTL,
selectionBR
pointTL,
pointBR
);
return intersection.status === 'Intersection';
},
/**
* Returns true if object intersects with another object
* Checks if object intersects with another object
* @param {Object} other Object to test
* @return {Boolean}
* @return {Boolean} true if object intersects with another object
*/
intersectsWithObject: function(other) {
// extracts coords
@ -52,30 +52,121 @@
},
/**
* Returns true if object is fully contained within area of another object
* Checks if object is fully contained within area of another object
* @param {Object} other Object to test
* @return {Boolean}
* @return {Boolean} true if object is fully contained within area of another object
*/
isContainedWithinObject: function(other) {
return this.isContainedWithinRect(other.oCoords.tl, other.oCoords.br);
var boundingRect = other.getBoundingRect(),
point1 = new fabric.Point(boundingRect.left, boundingRect.top),
point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height);
return this.isContainedWithinRect(point1, point2);
},
/**
* Returns true if object is fully contained within area formed by 2 points
* @param {Object} selectionTL
* @param {Object} selectionBR
* @return {Boolean}
* Checks if object is fully contained within area formed by 2 points
* @param {Object} pointTL top-left point of area
* @param {Object} pointBR bottom-right point of area
* @return {Boolean} true if object is fully contained within area formed by 2 points
*/
isContainedWithinRect: function(selectionTL, selectionBR) {
var oCoords = this.oCoords,
tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y),
tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y),
bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y);
isContainedWithinRect: function(pointTL, pointBR) {
var boundingRect = this.getBoundingRect();
return tl.x > selectionTL.x
&& tr.x < selectionBR.x
&& tl.y > selectionTL.y
&& bl.y < selectionBR.y;
return (
boundingRect.left > pointTL.x &&
boundingRect.left + boundingRect.width < pointBR.x &&
boundingRect.top > pointTL.y &&
boundingRect.top + boundingRect.height < pointBR.y
);
},
/**
* Checks if point is inside the object
* @param {Object} point
* @return {Boolean} true if point is inside the object
*/
containsPoint: function(point) {
var lines = this._getImageLines(this.oCoords),
xPoints = this._findCrossPoints(point, lines);
// if xPoints is odd then point is inside the object
return (xPoints !== 0 && xPoints % 2 === 1);
},
/**
* Method that returns an object with the object edges in it, given the coordinates of the corners
* @private
* @param {Object} oCoords Coordinates of the object corners
*/
_getImageLines: function(oCoords) {
return {
topline: {
o: oCoords.tl,
d: oCoords.tr
},
rightline: {
o: oCoords.tr,
d: oCoords.br
},
bottomline: {
o: oCoords.br,
d: oCoords.bl
},
leftline: {
o: oCoords.bl,
d: oCoords.tl
}
};
},
/**
* Helper method to determine how many cross points are between the 4 object edges
* and the horizontal line determined by a point on canvas
* @private
* @param {Object} point
* @param {Object} oCoords Coordinates of the image being evaluated
*/
_findCrossPoints: function(point, oCoords) {
var b1, b2, a1, a2, xi, yi,
xcount = 0,
iLine;
for (var lineKey in oCoords) {
iLine = oCoords[lineKey];
// optimisation 1: line below point. no cross
if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) {
continue;
}
// optimisation 2: line above point. no cross
if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) {
continue;
}
// optimisation 3: vertical line case
if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) {
xi = iLine.o.x;
yi = point.y;
}
// calculate the intersection point
else {
b1 = 0;
b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x);
a1 = point.y- b1 * point.x;
a2 = iLine.o.y - b2 * iLine.o.x;
xi = - (a1 - a2) / (b1 - b2);
yi = a1 + b1 * xi;
}
// dont count xi < point.x cases
if (xi >= point.x) {
xcount += 1;
}
// optimisation 4: specific for square images
if (xcount === 2) {
break;
}
}
return xcount;
},
/**

View file

@ -18,7 +18,7 @@
var pointer = getPointer(e, this.canvas.upperCanvasEl),
ex = pointer.x - offset.left,
ey = pointer.y - offset.top,
xpoints,
xPoints,
lines;
for (var i in this.oCoords) {
@ -31,7 +31,7 @@
continue;
}
lines = this._getImageLines(this.oCoords[i].corner, i);
lines = this._getImageLines(this.oCoords[i].corner);
// debugging
@ -47,8 +47,8 @@
// canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2);
// canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2);
xpoints = this._findCrossPoints(ex, ey, lines);
if (xpoints % 2 === 1 && xpoints !== 0) {
xPoints = this._findCrossPoints({x: ex, y: ey}, lines);
if (xPoints !== 0 && xPoints % 2 === 1) {
this.__corner = i;
return i;
}
@ -56,82 +56,6 @@
return false;
},
/**
* Helper method to determine how many cross points are between the 4 image edges
* and the horizontal line determined by the position of our mouse when clicked on canvas
* @private
* @param ex {Number} x coordinate of the mouse
* @param ey {Number} y coordinate of the mouse
* @param oCoords {Object} Coordinates of the image being evaluated
*/
_findCrossPoints: function(ex, ey, oCoords) {
var b1, b2, a1, a2, xi, yi,
xcount = 0,
iLine;
for (var lineKey in oCoords) {
iLine = oCoords[lineKey];
// optimisation 1: line below dot. no cross
if ((iLine.o.y < ey) && (iLine.d.y < ey)) {
continue;
}
// optimisation 2: line above dot. no cross
if ((iLine.o.y >= ey) && (iLine.d.y >= ey)) {
continue;
}
// optimisation 3: vertical line case
if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= ex)) {
xi = iLine.o.x;
yi = ey;
}
// calculate the intersection point
else {
b1 = 0;
b2 = (iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x);
a1 = ey-b1*ex;
a2 = iLine.o.y-b2*iLine.o.x;
xi = - (a1-a2)/(b1-b2);
yi = a1+b1*xi;
}
// dont count xi < ex cases
if (xi >= ex) {
xcount += 1;
}
// optimisation 4: specific for square images
if (xcount === 2) {
break;
}
}
return xcount;
},
/**
* Method that returns an object with the object edges in it, given the coordinates of the corners
* @private
* @param {Object} oCoords Coordinates of the image corners
*/
_getImageLines: function(oCoords) {
return {
topline: {
o: oCoords.tl,
d: oCoords.tr
},
rightline: {
o: oCoords.tr,
d: oCoords.br
},
bottomline: {
o: oCoords.br,
d: oCoords.bl
},
leftline: {
o: oCoords.bl,
d: oCoords.tl
}
};
},
/**
* Sets the coordinates of the draggable boxes in the corners of
* the image used to scale/rotate it.

View file

@ -83,7 +83,7 @@
cObj.setScaleX(2.3);
equal(2.3, cObj.getScaleX());
cObj.setOpacity(0.123)
cObj.setOpacity(0.123);
equal(0.123, cObj.getOpacity());
});
@ -239,6 +239,40 @@
ok(typeof cObj.render == 'function');
});
test('getBoundingRect', function() {
var cObj = new fabric.Object(),
boundingRect;
ok(typeof cObj.getBoundingRect == 'function');
cObj.setCoords();
boundingRect = cObj.getBoundingRect();
equal(boundingRect.left, 0);
equal(boundingRect.top, 0);
equal(boundingRect.width, 0);
equal(boundingRect.height, 0);
cObj.set('width', 123).setCoords();
boundingRect = cObj.getBoundingRect();
equal(boundingRect.left, -61.5);
equal(boundingRect.top, 0);
equal(boundingRect.width, 123);
equal(boundingRect.height, 0);
cObj.set('height', 167).setCoords();
boundingRect = cObj.getBoundingRect();
equal(boundingRect.left, -61.5);
equal(boundingRect.top, -83.5);
equal(boundingRect.width, 123);
equal(boundingRect.height, 167);
cObj.scale(2).setCoords();
boundingRect = cObj.getBoundingRect();
equal(boundingRect.left, -123);
equal(boundingRect.top, -167);
equal(boundingRect.width, 246);
equal(boundingRect.height, 334);
});
test('getWidth', function() {
var cObj = new fabric.Object();
ok(typeof cObj.getWidth == 'function');
@ -298,14 +332,14 @@
var obj = new fabric.Object({ height: 100, width: 100 });
obj.rotate(45);
obj.scaleToWidth(200);
equal(Math.round(obj.getBoundingRectWidth()), 200);
equal(Math.round(obj.getBoundingRect().width), 200);
});
test('scaleToHeight on rotated object', function() {
var obj = new fabric.Object({ height: 100, width: 100 });
obj.rotate(45);
obj.scaleToHeight(300);
equal(Math.round(obj.getBoundingRectHeight()), 300);
equal(Math.round(obj.getBoundingRect().height), 300);
});
test('setOpacity', function() {
@ -369,7 +403,7 @@
//let excanvas kick in for IE8 and lower
if (!canvas.getContext && typeof G_vmlCanvasManager != 'undefined') {
G_vmlCanvasManager.initElement(canvas)
G_vmlCanvasManager.initElement(canvas);
}
var dummyContext = canvas.getContext('2d');
@ -384,7 +418,7 @@
//let excanvas kick in for IE8 and lower
if (!canvas.getContext && typeof G_vmlCanvasManager != 'undefined') {
G_vmlCanvasManager.initElement(canvas)
G_vmlCanvasManager.initElement(canvas);
}
var dummyContext = canvas.getContext('2d');
ok(typeof cObj.drawControls == 'function');
@ -711,7 +745,7 @@
ok(barFired);
var firedOptions;
object.on('baz', function(options) { firedOptions = options; })
object.on('baz', function(options) { firedOptions = options; });
object.fire('baz', { param1: 'abrakadabra', param2: 3.1415 });
equal('abrakadabra', firedOptions.param1);
@ -722,7 +756,7 @@
var object = new fabric.Object();
var addedEventFired = false;
object.on('added', function(){ addedEventFired = true; })
object.on('added', function(){ addedEventFired = true; });
canvas.add(object);
ok(addedEventFired);
@ -862,4 +896,146 @@
equal(object.shadow.offsetY, 15);
});
test('intersectsWithRect', function() {
var object = new fabric.Object({ left: 20, top: 30, width: 40, height: 50, angle: 160 }),
point1 = new fabric.Point(0, 0),
point2 = new fabric.Point(20, 30),
point3 = new fabric.Point(10, 15),
point4 = new fabric.Point(30, 35),
point5 = new fabric.Point(50, 60),
point6 = new fabric.Point(70, 80);
object.setCoords();
// object and area intersects
equal(object.intersectsWithRect(point1, point2), true);
// area is contained in object (no intersection)
equal(object.intersectsWithRect(point3, point4), false);
// area is outside of object (no intersection)
equal(object.intersectsWithRect(point5, point6), false);
});
test('intersectsWithObject', function() {
var object = new fabric.Object({ left: 20, top: 30, width: 40, height: 50, angle: 230 }),
object1 = new fabric.Object({ left: 20, top: 30, width: 60, height: 30, angle: 10 }),
object2 = new fabric.Object({ left: 25, top: 35, width: 20, height: 20, angle: 50 }),
object3 = new fabric.Object({ left: 50, top: 50, width: 20, height: 20, angle: 0 });
object.setCoords();
object1.setCoords();
object2.setCoords();
object3.setCoords();
// object and object1 intersects
equal(object.intersectsWithObject(object1), true);
// object2 is contained in object (no intersection)
equal(object.intersectsWithObject(object2), false);
// object3 is outside of object (no intersection)
equal(object.intersectsWithObject(object3), false);
});
test('isContainedWithinObject', function() {
var object = new fabric.Object({ left: 20, top: 30, width: 40, height: 50, angle: 230 }),
object1 = new fabric.Object({ left: 25, top: 35, width: 20, height: 20, angle: 50 }),
object2 = new fabric.Object({ left: 20, top: 30, width: 60, height: 30, angle: 10 }),
object3 = new fabric.Object({ left: 50, top: 50, width: 20, height: 20, angle: 0 });
object.setCoords();
object1.setCoords();
object2.setCoords();
object3.setCoords();
// object1 is fully contained within object
equal(object1.isContainedWithinObject(object), true);
// object2 intersects object (not fully contained)
equal(object2.isContainedWithinObject(object), false);
// object3 is outside of object (not fully contained)
equal(object3.isContainedWithinObject(object), false);
});
test('isContainedWithinRect', function() {
var object = new fabric.Object({ left: 40, top: 40, width: 40, height: 50, angle: 160 }),
point1 = new fabric.Point(0, 0),
point2 = new fabric.Point(80, 80),
point3 = new fabric.Point(0, 0),
point4 = new fabric.Point(80, 60),
point5 = new fabric.Point(80, 80),
point6 = new fabric.Point(90, 90);
object.setCoords();
// area is contained in object (no intersection)
equal(object.isContainedWithinRect(point1, point2), true);
// object and area intersects
equal(object.isContainedWithinRect(point3, point4), false);
// area is outside of object (no intersection)
equal(object.isContainedWithinRect(point5, point6), false);
});
test('isContainedWithinRect', function() {
var object = new fabric.Object({ left: 40, top: 40, width: 40, height: 50, angle: 160 }),
point1 = new fabric.Point(0, 0),
point2 = new fabric.Point(80, 80),
point3 = new fabric.Point(0, 0),
point4 = new fabric.Point(80, 60),
point5 = new fabric.Point(80, 80),
point6 = new fabric.Point(90, 90);
object.setCoords();
// area is contained in object (no intersection)
equal(object.isContainedWithinRect(point1, point2), true);
// object and area intersects
equal(object.isContainedWithinRect(point3, point4), false);
// area is outside of object (no intersection)
equal(object.isContainedWithinRect(point5, point6), false);
});
test('containsPoint', function() {
var object = new fabric.Object({ left: 40, top: 40, width: 40, height: 50, angle: 160 }),
point1 = new fabric.Point(30, 30),
point2 = new fabric.Point(60, 30),
point3 = new fabric.Point(45, 65),
point4 = new fabric.Point(15, 40),
point5 = new fabric.Point(30, 15);
object.setCoords();
// point1 is contained in object
equal(object.containsPoint(point1), true);
// point2 is outside of object (right)
equal(object.containsPoint(point2), false);
// point3 is outside of object (bottom)
equal(object.containsPoint(point3), false);
// point4 is outside of object (left)
equal(object.containsPoint(point4), false);
// point5 is outside of object (top)
equal(object.containsPoint(point5), false);
});
test('containsPoint width padding', function() {
var object = new fabric.Object({ left: 40, top: 40, width: 40, height: 50, angle: 160, padding: 5 }),
point1 = new fabric.Point(30, 30),
point2 = new fabric.Point(10, 20),
point3 = new fabric.Point(65, 30),
point4 = new fabric.Point(45, 75),
point5 = new fabric.Point(10, 40),
point6 = new fabric.Point(30, 5);
object.setCoords();
// point1 is contained in object
equal(object.containsPoint(point1), true);
// point2 is contained in object (padding area)
equal(object.containsPoint(point2), true);
// point2 is outside of object (right)
equal(object.containsPoint(point3), false);
// point3 is outside of object (bottom)
equal(object.containsPoint(point4), false);
// point4 is outside of object (left)
equal(object.containsPoint(point5), false);
// point5 is outside of object (top)
equal(object.containsPoint(point6), false);
});
})();