MathGroup Archive 2002

[Date Index] [Thread Index] [Author Index]

Search the Archive

RE (long): ... RE: rectangle intersection

  • To: mathgroup at smc.vnet.net
  • Subject: [mg36238] RE (long): [mg36199]... RE: [mg36093] rectangle intersection
  • From: "Wolf, Hartmut" <Hartmut.Wolf at t-systems.com>
  • Date: Wed, 28 Aug 2002 04:15:58 -0400 (EDT)
  • Sender: owner-wri-mathgroup at wolfram.com

Bobby,

here my solution(s) as promised. As you stated, my published solution was
only for rectangles oriented parallel to the X and Y axis, as this is
perhaps the predominant application.

The idea was, to decide whether all vertices of a rectangle lie to one side
of the other. This idea expands to the general case. And effectively it is
the same idea as of your suggestion, as well as of the improved version
after Garry Helzer, i.e. the "constructive" solutions, as opposed to those
involving more abstract mathematical reasoning as of David Park and Andrzej
Kozlowski. And these, esp. Andrzej's, are very interesting, as they show how
to derive a solution just from this kind of reasoning.


First some preliminaries: random rectangles

I had chosen to describe the rectangles as a list of counterclockwise
vertices. To generate them, I had used a similar description as David Park:

toRectangle[corner_, \[Theta]_, base_, side_] :=
  Module[{vb = {Cos[\[Theta]], Sin[\[Theta]]}* base,
      vs = {-Sin[\[Theta]], Cos[\[Theta]]}* side},
    {corner, corner + vb, corner + vb + vs, corner + vs}]

makeRect[] :=
  toRectangle[{Random[], Random[]}, \[Pi]/2 Random[], Random[], 
Random[]]

rectLine[{pFirst_, pRest__}] := Line[{pFirst, pRest, pFirst}]

ShowRects[r1_, r2_] :=
  Show[Graphics[{PointSize[0.05], Hue[.5], rectLine[r1],
        MapIndexed[{Point[#1], GrayLevel[0], Text[First[#2], #1]} &, 
r1],
        Hue[0], rectLine[r2],
        MapIndexed[{Point[#1], GrayLevel[0], Text[First[#2], #1]} &, 
r2]}],
    Background -> GrayLevel[.8], AspectRatio -> Automatic, PlotRange -> 
All]


test cases:

rect[1] = toRectangle[{0, 0}, 0, 1, 1];			(* from David *)
rect[2] = toRectangle[{1, 1}*0.9, \[Pi]/4, 2, 1];
ShowRects[rect[1], rect[2]]

rx = rect[2] /. {x_?NumericQ, y_} :> {x, y} + {-0.1, 0.15};
ShowRects[rect[1], rx]

rxx = {{-.5, .2}, {1.5, .2}, {1.5, .8}, {-.5, .8}};
ShowRects[rect[1], rxx]


test data:

recs1000 = Table[makeRect[],{1000}];


Now here my solution. It is written such as to communicate the idea, 
call it
elegant:

offSide[r2_][{p1_, p2_}] := And @@ ((p2 - p1).(# - p1) <= 0 &) /@ 
r2

rectOverlap[r1_, r2_] :=
  =AC (Or @@ offSide[r2] /@ Partition[r1, 2, 1, {1, 1}] \[Or]
        Or @@ offSide[r1] /@ Partition[r2, 2, 1, {1, 1}])

I came to this, when I tried to expand my solution for evenly oriented
rectangles to the general case. I first tried oblique coordinates, sighting
along the sides of the rectangles, then saw that giving them in the dual
base is computationally simpler, to recognize i.e. just the distance of a
point from the side's (straight line).

Correctness:

rectOverlap[rect[1], rect[2]]
True

rectOverlap[rect[1], rx]
False

rectOverlap[rect[1], rxx]
True

Performance:

(testr1 =
        MapThread[
          rectOverlap, {recs1000, RotateLeft[recs1000, 1]}]); // Timing
{3.635 Second, Null}

The costs for RotateLeft are negligable in Timing.



Garry Helzer had proposed a solution which wasn't correct (e.g. for rect[1},
rxx) as he noted himself. Bobby Treat however noticed that it can be fixed.
Here my version thereof. (You can see how similar it is, starting from
different reasoning.)

rightSide[a_, {b_, c_}] := Det[Prepend[#, 1] & /@ {a, b, c}] < 0

vertexExcluded[r1_, r2_] :=
  =AC (Or @@ And @@@
        Outer[rightSide[#2, #1] &, Partition[r2, 2, 1, {1, 1}], r1, 1]
     \[Or]
     Or @@ And @@@
        Outer[rightSide[#2, #1] &, Partition[r1, 2, 1, {1, 1}], r2, 1])

(testr4 =
        MapThread[
          vertexExcluded, {recs1000, RotateLeft[recs1000, 1]}]); // 
Timing

{3.646 Second, Null}

testr1 === testr4
True

of quite similar performance.

Bobby Treat gave a different implementation of this (as seen below) which is
marginally faster.


Now here Bobby's solution, rewritten in my style (which makes it slightly
faster):

extent[r1_, r2_] := {Min[#], Max[#]} & /@
          ((r1[[{1, 2}]] - r1[[{2, 3}]]).Transpose[r2])

cannotIntersect[{{min1_, max1_}, {min2_, max2_}}] :=
  max2 < min1 || max1 < min2

intersects[r1_, r2_] :=
  Catch[If[cannotIntersect[#], Throw[False]] & /@
      Flatten[Transpose[
          Join[Outer[extent, {r1}, {r1, r2}, 1],
            Outer[extent, {r2}, {r2, r1}, 1]], {1, 3, 2}], 1]; True]

(testr5 =
        MapThread[intersects, {recs1000, RotateLeft[recs1000, 1]}]); //
Timing
{1.502 Second, Null}

testr5 === testr1
True



Now, this here is a rewrite of my rectOverlap. It explicitely writes down
the tests for all sides. Such we come to use of the non-strict evaluation of
And[ ] and Or[ ]. This effectively corresponds to a Catch and Throw.

rectOverlap2[r1 : {p1_, p2_, p3_, p4_}, r2 : {q1_, q2_, q3_, q4_}] :=
! Or[
      ((p2 - p1).(# - p1) <= 0 &) /@ And @@ r2,
      ((p3 - p2).(# - p2) <= 0 &) /@ And @@ r2,
      ((p4 - p3).(# - p3) <= 0 &) /@ And @@ r2,
      ((p1 - p4).(# - p4) <= 0 &) /@ And @@ r2,
      ((q2 - q1).(# - q1) <= 0 &) /@ And @@ r1,
      ((q3 - q2).(# - q2) <= 0 &) /@ And @@ r1,
      ((q4 - q3).(# - q3) <= 0 &) /@ And @@ r1,
      ((q1 - q4).(# - q4) <= 0 &) /@ And @@ r1]

(testr2 =
        MapThread[
          rectOverlap2, {recs1000, RotateLeft[recs1000, 1]}]); // Timing
{1.502 Second, Null}

testr2 === testr1
True

Equal in performance to Bobby's solution. The use of And with ((p2 - p1).(#
- p1) <= 0 &) /@ And @@ r2, etc. is a bit tricky: it effectively prevents
the evaluation of (p2 - p1).(q1 - p1) <= 0 etc., such that this is evaluated
within And (non-standard evaluation) after mapping. And @@ ((p2 - p1).(# -
p1) <= 0 &) /@ r2 to the contrary is less performant. (You can see here how
clever the language is designed, to allow use of And as a container, until
the time comes to execute!)

This solution can be improved a little bit; we replace mapping by threading
and delay execution of the pure functions' body to the application of And
now by a different method (since we don't Map):

rectOverlap3[r1 : {p1_, p2_, p3_, p4_}, r2 : {q1_, q2_, q3_, q4_}] :=
! Or[
      Block[{v = p2 - p1},
        Or[And @@ Thread[Unevaluated[(Unevaluated[v.(# - p1) <= 0] 
&)[r2]]],

          And @@ Thread[Unevaluated[(Unevaluated[v.(# - p2) >= 0]
&)[r2]]]]],
      Block[{v = p4 - p1},
        Or[And @@ Thread[Unevaluated[(Unevaluated[v.(# - p1) <= 0] 
&)[r2]]],

          And @@ Thread[Unevaluated[(Unevaluated[v.(# - p4) >= 0]
&)[r2]]]]],
      Block[{v = q2 - q1},
        Or[And @@ Thread[Unevaluated[(Unevaluated[v.(# - q1) <= 0] 
&)[r1]]],

          And @@ Thread[Unevaluated[(Unevaluated[v.(# - q2) >= 0]
&)[r1]]]]],
      Block[{v = q4 - q1},
        Or[And @@ Thread[Unevaluated[(Unevaluated[v.(# - q1) <= 0] 
&)[r1]]],

          And @@ Thread[Unevaluated[(Unevaluated[v.(# - q4) >= 0]
&)[r1]]]]]]

(testr3 =
        MapThread[
          rectOverlap3, {recs1000, RotateLeft[recs1000, 1]}]); // Timing
{1.442 Second, Null}

testr3 === testr1
True

So this is fastest by a small margin, but has lost almost all elegance. 
The
outer Unevaluated is necessary to prevent evaluation within Thread, the
inner does the essential trick noted above.

Hope this was of some interest,

Hartmut



>-----Original Message-----
>From: DrBob [mailto:majort at cox-internet.com]
To: mathgroup at smc.vnet.net
>Sent: Monday, August 26, 2002 10:16 AM
>Subject: [mg36238] [mg36199] RE: [mg36162] Fwd: [mg36140] Fwd: [mg36124] RE:
>[mg36093] rectangle intersection
>
>
>Garry,
>
>Also note your solution requires rectangle points to be in clockwise
>order (mine doesn't), but yours works for arbitrary convex polygons as
>written.
>
>Bobby
>
>-----Original Message-----
>From: DrBob [mailto:majort at cox-internet.com]
To: mathgroup at smc.vnet.net
>Subject: [mg36238] [mg36199] RE: [mg36162] Fwd: [mg36140] Fwd: [mg36124]
>RE: [mg36093]
>rectangle intersection
>
>Garry,
>
>Here's a solution using your "LeftSide" concept; it works perfectly but
>takes twice as much time as my solution.  Both solutions look at every
>vertex of both rectangles, but mine uses two sides from each and yours
>requires looking at all four sides of each rectangle.  I'd think yours
>should be a trifle faster than this, though.  There may be efficiencies
>I'm missing (in both solutions).
>
>ClearAll[cis, rect, pickRect, extent, cannotIntersect, intersects,
>daveRect]
>cis[t_] := {Cos@t, Sin@t}
>rect[{pt : {_, _}, angle_, {len1_, len2_}}] := Module[{pt2},
>    {pt, pt2 =
> pt + len1 cis[angle],
>   pt2 - len2 cis[angle - Pi/2], pt - len2 cis[angle - Pi/2]}]
>daveRect := {{Random[], Random[]}, Random[] + Pi/2, {Random[],
>Random[]}}
>pickRect := rect@daveRect
>extent[r1_,
>      r2_] := {Min@#, Max@#} & /@ ((Take[r1, 2] - r1[[{2,
>3}]]).Transpose@r2)
>cannotIntersect[{{min1_, max1_}, {min2_,
>      max2_}}] := max2 < min1 || min2 > max1
>intersects[r1_, r2_] := Catch[
>    If[cannotIntersect[#], Throw[False]] & /@
>Flatten[Transpose[Outer[extent, \
>{r1}, {r1, r2}, 1]~Join~Outer[extent, {r2}, {r2, r1}, 1], {1, 3, 2}],
>1];
>    Throw[True]]
>
>ClearAll[leftSide,leftIntersects,sides]
>sides[a_List]:=Partition[Join[a,{First@a}],2,1]
>leftSide[{a_,b_},{{c_,d_},{e_,f_}}]:=-b c+a d+b e-d e-a f+c f>0
>leftSide[a:{{_,_}..},b:{{_,_},{_,_}}]:=leftSide[#,b]&/@a
>leftSide[a_List,b:{{{_,_},{_,_}}..}]:=leftSide[a,#]&/@b
>leftIntersects[a_,b_]:=!Or@@(And@@#&/@leftSide[a,sides@b])&&!
>      Or@@(And@@#&/@leftSide[b,sides@a])
>
>davePairs={daveRect,daveRect}&/@Range[10000];
>rectanglePairs=Map[Reverse@rect[#]&,davePairs,{2}];
>Timing[right=intersects[Sequence@@#]&/@rectanglePairs;]
>Timing[test=leftIntersects[Sequence@@#]&/@rectanglePairs;]
>right\[Equal]test
>
>{3.187999999999999*Second,   Null}
>{6.765000000000001*Second,   Null}
>True
>
>Bobby Treat
>
>-----Original Message-----
>From: Garry Helzer [mailto:gah at math.umd.edu]
To: mathgroup at smc.vnet.net
>Subject: [mg36238] [mg36199] [mg36162] Fwd: [mg36140] Fwd: [mg36124] RE:
>[mg36093] rectangle
>intersection
>
>As Daniel Lichtblau pointed out, the statement below about vertices is 
>nonsense. Consider two overlapping rectangles arranged as a cross. You 
>need to compute intersections and test them instead of vertices.
>
>Begin forwarded message:
>
>> From: Garry Helzer <gah at math.umd.edu>
To: mathgroup at smc.vnet.net
>> Date: Fri Aug 23, 2002  12:25:13  AM US/Eastern
>> Subject: [mg36238] [mg36199] [mg36162] [mg36140] Fwd: [mg36124] RE:
>[mg36093] rectangle
>intersection
>>
>> Begin forwarded message:
>>
>> Dear colleagues,
>>
>> any hints on how to implement a very fast routine in Mathematica for
>> testing if two rectangles have an intersection area?
>> Thanks in advance
>> Frank Brand
>>
>>
>> Here is one approach.
>>
>> Given three points {x1,y1},{x2,y2},{x3,y3}, switch to homogenous
>> coordinates a={1,x1,y1}, b={1,x2,y2}, c={1,x3,y3}. Then
>> Sign[Det[{a,b,c}]] is +1 if and only if the point a lies on your left
>as
>> you walk along the line though b and c in the direction from b to c.
>> ( If the result is zero, then a lies on the line.)
>>
>> The value of the determinant is
>x2y3-x3y2-x1y3+x3y1+x1y2-x2y1, and the
>> speed of the algorithm depends essentially on how fast this quantity
>can
>> be computed. Suppose we write a function LeftSide[a,{b,c}] that
>computes
>> the sign of the determinant.
>>
>> Now let {p1,p2, . . ., pn} be a list of vertices (pi={xi,yi}) of a
>> convex polygon traced counterclockwise. Then a lies within or on the
>> boundary of the polygon if and only if none of the numbers
>> LeftSide[a,{pi,p(i+1)}] are -1. That is, if -1 does not appear in the
>> list LeftSide[a,#]&/@Partition[{p1,p2,. . .,pn,p1},2,1].
>>
>> Now use the fact that if two convex polynomials overlap, then some
>> vertex of one of them must lie inside or on the boundary of
>the other.
>>
>> If an overlap of positive area is required, then the check is that
>only
>> +1 appears--not that -1 does not appear.
>>
>> For two rectangles ( or parallelograms) this approach requires the
>> evaluation of 16 determinants, so it may be a bit expensive. If the
>> points have rational coordinates, then (positive) denominators may 
be
>> cleared in the homogeneous coordinates and the computations can be
>done
>> in integer arithmetic, at the cost of at least three more
>> multiplications per determinant.
>>
>>
>Garry Helzer
>Department of Mathematics
>University of Maryland
>College Park, MD 20742
>301-405-5176
>gah at math.umd.edu
>>
>>
>
>
>
>
>
>
>



  • Prev by Date: Re: NDSolve with integral equation
  • Next by Date: Transpose problem
  • Previous by thread: Re: FindRoot and vector equations
  • Next by thread: Transpose problem