haggis
haggis

Reputation: 417

Rectangles intersecting even they should not

If I create two rectangles next to each other and test them for intersection, it always returns true, even though it shouldn't.

As you can see from the fiddle, if there's a rect starting at x=0 and width=50px, the second rect needs to start at x=50 to avoid a gap in between. However, to avoid intersection there must be a gap of 2px.

Why is that and how I can I work around this?

canvas = new fabric.Canvas('canvas');

// Starting at x=0
var rect1 = new fabric.Rect({
      left: 0, top: 0, fill: 'black', width: 50, height: 50, selectable: false,
    });

// Starting 50px right of the previous
var rect2 = new fabric.Rect({
      left: 50, top: 0, fill: 'blue', width: 50, height: 50, selectable: false,
    });

// Starting 51px right of the previous
var rect3 = new fabric.Rect({
      left: 101, top: 0, fill: 'black', width: 50, height: 50, selectable: false,
    });

// Starting 52px right of the previous
var rect4 = new fabric.Rect({
      left: 153, top: 0, fill: 'blue', width: 50, height: 50, selectable: false,
    });

canvas.add(rect1, rect2, rect3, rect4);

console.log(rect1.aCoords.tr, rect2.aCoords.tr, rect3.aCoords.tr, rect4.aCoords.tr);

if (rect1.intersectsWithObject(rect2))
    console.log("1st intersecting with 2nd.");
else
  console.log("No intersection between 1st and 2nd.");

if (rect2.intersectsWithObject(rect3))
    console.log("2nd intersecting with 3rd.");
else
  console.log("No intersection between 2nd and 3rd.");

if (rect3.intersectsWithObject(rect4))
    console.log("3rd intersecting with 4th.");
else
  console.log("No intersection between 3rd and 4th.");
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.2.3/fabric.js"></script>
<p>Open console to see the rectangles absolute positions.</p>
<canvas id="canvas" width="300px" height="75px"></canvas>

My jsfiddle

Upvotes: 0

Views: 1376

Answers (3)

AndreaBogazzi
AndreaBogazzi

Reputation: 14741

@Durga has centered the point, the stroke is creating the intersections.

To add more details, you should consider that the intersection is between paths and not pixels.

A rect that is wide 50 and and start from 0, effectively is intersecting with another that is starting at 50, consider their sides are perfectly overlapping, there is not space between the 2 rects, they are touching each other.

I agree that a parameter could be added to do not consider the overlap when returning the boolean.

Upvotes: 1

haggis
haggis

Reputation: 417

I solved it by using low-level functions of Fabric.js. If an intersection is found, I check whether there is only 1 intersection point and if this point is a corner of one of our objects. If true, we have a "fake intersection".

    function isIntersecting(obj1, obj2) {
  var result = new fabric.Intersection(),
	points1 = obj1.getCoords(true, false),
	points2 = obj2.getCoords(true, false),
	length1 = points1.length,
	length2 = points2.length,
	a1, a2, b1, b2;
      
  // Check if one object lies within the other.
  if (obj1.isContainedWithinObject(obj2, false, true) || obj2.isContainedWithinObject(obj1, false, true))
  	return true;
  
  // Check for intersection between all edges.
  for (var i = 0; i < length1; i++) {
        a1 = points1[i];
        a2 = points1[(i + 1) % length1];
      
      for (var j = 0; j < length2; j++) {
        b1 = points2[j];
        b2 = points2[(j + 1) % length2];
      	result = fabric.Intersection.intersectLineLine(a1, a2, b1, b2);
        
        if (result.status == 'Intersection') {
          // It's not an intersection if we have only 1 intersection point and if this one is the end of one of our lines.
          let allPoints = [a1, a2, b1, b2];
          if (result.points.length > 1 || !polygonIncludesPoint(allPoints, result.points[0])) {
          	return true;
          }
          else if (obj2.containsPoint(a1) || obj2.containsPoint(a2)	||
          		obj1.containsPoint(b1) || obj1.containsPoint(b2)) {
          	// But it is if one corner of an edge is within the other object.	
            return true;
          }
        }
      }
  }
  
  return false;
}

function polygonIncludesPoint(points, point) {
  for (var i = 0; i < points.length; i++) {
        if (points[i].eq(point))
    	  return true;
  }
  return false;
}

var canvas = new fabric.Canvas('canvas');

var center = new fabric.Rect({
      left: 50, top: 50, fill: 'black', width: 50, height: 50, selectable: false, strokeWidth: 0
    });

var tl = new fabric.Rect({
      left: 0, top: 0, fill: 'blue', width: 50, height: 50, selectable: false, strokeWidth: 0
    });

var tm = new fabric.Rect({
      left: 50, top: 0, fill: 'grey', width: 50, height: 50, selectable: false, strokeWidth: 0
    });

var tr = new fabric.Rect({
      left: 100, top: 0, fill: 'blue', width: 50, height: 50, selectable: false, strokeWidth: 0
    });
    
var ml = new fabric.Rect({
      left: 0, top: 50, fill: 'grey', width: 50, height: 50, selectable: false, strokeWidth: 0
    });
    
var mr = new fabric.Rect({
      left: 100, top: 50, fill: 'grey', width: 50, height: 50, selectable: false, strokeWidth: 0
    });
    
var bl = new fabric.Rect({
      left: 0, top: 100, fill: 'blue', width: 50, height: 50, selectable: false, strokeWidth: 0
    });
    
var bm = new fabric.Rect({
      left: 50, top: 100, fill: 'grey', width: 50, height: 50, selectable: false, strokeWidth: 0
    });
    
var br = new fabric.Rect({
      left: 80, top: 80, fill: 'blue', width: 50, height: 50, selectable: false, strokeWidth: 0
    });
    
var center2 = new fabric.Rect({
      left: 180, top: 20, fill: 'black', width: 50, height: 50, selectable: false, strokeWidth: 0
    });
    
var bigRect = new fabric.Rect({
      left: 180, top: 20, fill: 'rgba(0, 0, 255, 0.3)', width: 90, height: 90, selectable: false, strokeWidth: 0
    });
    
var center3 = new fabric.Rect({
      left: 310, top: 40, fill: 'black', width: 50, height: 50, selectable: false, strokeWidth: 0
    });
    
var bigRect2 = new fabric.Rect({
      left: 290, top: 20, fill: 'rgba(0, 0, 255, 0.3)', width: 90, height: 90, selectable: false, strokeWidth: 0
    });
    
    

canvas.add(center, tl, tm, tr, ml, mr, bl, bm, br, center2, bigRect, center3, bigRect2);

var objects = canvas.getObjects('rect');

// Check 1st case
console.log("1st case:");
objects.forEach(function (targ) {
  if (targ === center || targ === center2 || targ === bigRect || targ === center3 || targ === bigRect2) return;

  if (isIntersecting(center, targ))
  	console.log("Intersection");
  else
  	console.log("No intersection");
});

// Check 2nd case
console.log("2nd case:");
if (isIntersecting(center2, bigRect))
	console.log("Intersection");
else
  console.log("No intersection");
  
// Check 3rd case
console.log("3rd case");
if (isIntersecting(center3, bigRect2))
	console.log("Intersection");
else
	console.log("No intersection");
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.2.3/fabric.min.js"></script>
<p>See console for results.</p>
<canvas id="canvas" width="400px" height="200px"></canvas>

My jsfiddle

Upvotes: 1

Marcelo Macedo
Marcelo Macedo

Reputation: 91

I believe that this is happen because of the invisible Stroke Width (that is for default 1).

So in the rectangles they need a gap of 2px ( 1px for left and 1px for right ). I set on your sample the Stroke Width for 0px and run the test again.

canvas = new fabric.Canvas('canvas');

// Starting at x=0
var rect1 = new fabric.Rect({
      left: 0, top: 0, fill: 'black', width: 50, height: 50, selectable: false, strokeWidth: 0
    });

// Starting 50px right of the previous
var rect2 = new fabric.Rect({
      left: 51, top: 0, fill: 'blue', width: 50, height: 50, selectable: false, strokeWidth: 0
    });

// Starting 51px right of the previous
var rect3 = new fabric.Rect({
      left: 102, top: 0, fill: 'black', width: 50, height: 50, selectable: false, strokeWidth: 0
    });

// Starting 52px right of the previous
var rect4 = new fabric.Rect({
      left: 153, top: 0, fill: 'blue', width: 50, height: 50, selectable: false, strokeWidth: 0
    });

//canvas.add(rect1, rect2, rect3, rect4);
canvas.add(rect1);
canvas.add(rect2);
canvas.add(rect3);
canvas.add(rect4);

console.log(rect1.aCoords.tr, rect2.aCoords.tr, rect3.aCoords.tr, rect4.aCoords.tr);

if (rect1.intersectsWithObject(rect2))
    console.log("1st intersecting with 2nd.");
else
  console.log("No intersection between 1st and 2nd.");

if (rect2.intersectsWithObject(rect3))
    console.log("2nd intersecting with 3rd.");
else
  console.log("No intersection between 2nd and 3rd.");

if (rect3.intersectsWithObject(rect4))
    console.log("3rd intersecting with 4th.");
else
  console.log("No intersection between 3rd and 4th.");
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.2.3/fabric.min.js"></script>
<p>Open console to see the rectangles absolute positions.</p>
<canvas id="canvas" width="300px" height="75px"></canvas>

Check this fiddle.

Upvotes: 1

Related Questions