9

I am hoping that I am confused in some way. I am getting some inconsistent behavior with TRect.Intersect and TRect.IntersectsWith. Here is some code that demonstrates the problem.

program RectCheck;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Types,
  Vcl.Dialogs;

var
  rect1: TRect;
  rect2: TRect;
  combinedRect: TRect;
begin
  Rect1 := Rect(0,0,200,101);
  Rect2 := Rect(0,100,200,200);
  if Rect1.IntersectsWith(Rect2) then
  begin
    // We have interesected, get the combined rect
    combinedRect := TRect.Intersect(Rect1, Rect2);
    if not combinedRect.IsEmpty then
      ShowMessage(Format('Our new rect (%d, %d), (%d, %d)',
          [combinedRect.Left, combinedRect.Top, combinedRect.Right, combinedRect.Bottom]))
    else
      raise Exception.Create('They were supposed to intersect!');
  end;

  Rect1 := Rect(0,0,200,100);
  Rect2 := Rect(0,100,200,200);
  if Rect1.IntersectsWith(Rect2) then
  begin
    // We have interesected, get the combined rect
    combinedRect := TRect.Intersect(Rect1, Rect2);

    if not combinedRect.IsEmpty then
      ShowMessage(Format('Our new rect (%d, %d), (%d, %d)',
          [combinedRect.Left, combinedRect.Top, combinedRect.Right, combinedRect.Bottom]))
    else
      raise Exception.Create('They were supposed to intersect!');
  end;
end.

The second exception is raised. TRect.IntersectsWith indicates that the rects intersect but when I call TRect.Intersect to get the new intersected rect then it returns an empty rect.

The code in IntersectsWith (which isn't written very clearly) is returning true in the second case because Self.BottomRight.Y = R.TopLeft.Y (100).

function TRect.IntersectsWith(const R: TRect): Boolean;
begin
  Result := not ( (Self.BottomRight.X < R.TopLeft.X) or
                  (Self.BottomRight.Y < R.TopLeft.Y) or
                  (R.BottomRight.X < Self.TopLeft.X) or
                  (R.BottomRight.Y < Self.TopLeft.Y) );
end;

The problem is that IsRectEmpty which is called by Intersect checks to see if either the top and bottom of the rect or the left and right of the rect have the same values and when that passes Intersect sets the result to an empty rect.

function IsRectEmpty(const Rect: TRect): Boolean;
begin
  Result := (Rect.Right <= Rect.Left) or (Rect.Bottom <= Rect.Top);
end;

Is this the expected behavior and if not what should be changed. My understanding is that TRects exclude the bottom and right "edges" and if that's the case shouldn't TRect.IntersectsWith look something like this?

function TRect.IntersectsWith(const R: TRect): Boolean;
begin
  Result := not ( (Self.BottomRight.X <= R.TopLeft.X) or
                  (Self.BottomRight.Y <= R.TopLeft.Y) or
                  (R.BottomRight.X <= Self.TopLeft.X) or
                  (R.BottomRight.Y <= Self.TopLeft.Y) );
end;
Graymatter
  • 6,529
  • 2
  • 30
  • 50

1 Answers1

6

It's a bug; this cannot be the expected behavior. In the current implementation, RTL thinks two empty rects can intersect (e.g. (0,0,0,0), (0,0,0,0), or one non-empty rect with an empty one), which does not make any sense.

Assert(Rect(0, 0, 0, 0).IntersectsWith(Rect(0, 0, 0, 0)));

The above assertion does not fail.

Also, it does not work in accordance with the Windows api. The below assertion fails: winapi thinks (0,0,200,100) and (0,100,200,200) do not intersect.

Assert(winapi.windows.IntersectRect(OutRect, Rect(0,0,200,100), Rect(0,100,200,200)));

The overload of System.Types.IntersectRect() that returns a boolean is equally broken.

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • Thanks, I have added a QC with some of your info (http://qc.embarcadero.com/wc/qcmain.aspx?d=127696) – Graymatter Sep 19 '14 at 07:02
  • Hmmm... Two empty recs are identical, so of course they intersect, IMO. The intersection is, of course, the empty rect. But `Rect(0, 0, 100, 200)` and `Rect(0, 100, 200, 200)` should not intersect, IMO, even if they, nominally, have the point `(0, 100)` in common (but not in reality, since that point is, in Windows, not part of the first rect). – Rudy Velthuis Sep 20 '14 at 10:50
  • @Rudy - An empty rect is a rect that isn't. It cannot intersect with anything, it doesn't even have an interior/area. – Sertac Akyuz Sep 20 '14 at 11:08
  • An empty rect is a TRect with values `(0, 0, 0, 0)`. It is still a TRect. And identical rects always intersect, IMO. – Rudy Velthuis Sep 20 '14 at 11:17
  • @Rudy - Have a test with `winapi.Windows.IntersectRect(OutRect, Rect(0, 0, 10, 10), Rect(5, 5, 5, 5))` to see if they intersect. But your intersection definition might be different than mine, or the OS'. That wouldn't be relevant for the purposes of this question though. – Sertac Akyuz Sep 20 '14 at 11:22
  • It is a philosophical matter, I guess. In some languages, the equivalent of `Pos('', 'ABC')` returns the equivalent of ``, in some it returns ``. – Rudy Velthuis Sep 20 '14 at 11:24
  • @Rudy - FWIW I don't disagree (0, 0, 0, 0) is a TRect, I disagree it is a rectangle. – Sertac Akyuz Sep 20 '14 at 11:25