I translated some C code by Oren Becker that determines if two rotated RectangleFs are overlapping. The rectangles are defined by their centre, size and angle of rotation. In the original code the size was actually (Width / 2, Height / 2) which was confusing.
RotatedRectangleF.vb
Public Structure RotatedRectangleF Public Const Pi As Single = CType(Math.PI, Single) Private m_centre As PointF ''' <summary> ''' Gets or sets the coordinates of the centre of this RotatedRectangleF structure. ''' </summary> ''' <returns> ''' A System.Drawing.PointF that represents the centre of this RotatedRectangleF structure. ''' </returns> Public Property Centre() As PointF Get Return m_centre End Get Set(ByVal value As PointF) m_centre = value End Set End Property Private m_size As SizeF ''' <summary> ''' Gets or sets the size of this RotatedRectangleF. ''' </summary> ''' <returns> ''' A System.Drawing.SizeF that represents the width and height of this RotatedRectangleF structure. ''' </returns> Public Property Size() As SizeF Get Return m_size End Get Set(ByVal value As SizeF) m_size = value End Set End Property Private m_angle As Single ''' <summary> ''' Gets or sets the angle in degrees measured clockwise from the x-axis that this RotatedRectangleF structure is rotated. ''' </summary> ''' <returns> ''' A Single representing the angle in degrees measured clockwise from the x-axis that this RotatedRectangleF structure is rotated. ''' </returns> Public Property Angle() As Single Get Return m_angle End Get Set(ByVal value As Single) m_angle = value End Set End Property ''' <summary> ''' Initializes a new instance of the RotatedRectangleF stucture with the specified centre, size and angle. ''' </summary> ''' <param name="centre">The centre of the RotatedRectangleF instance.</param> ''' <param name="size">The size of the RotatedRectangleF instance.</param> ''' <param name="angle">The angle in degrees clockwise from the x-axis that this RotatedRectangleF is rotated.</param> ''' <remarks></remarks> Sub New(ByVal centre As PointF, ByVal size As SizeF, ByVal angle As Single) Me.Centre = centre Me.Size = size Me.Angle = angle End Sub ''' <summary> ''' Render this RotatedRectangle using the provided System.Drawing.Graphics object. ''' </summary> ''' <param name="g">The System.Drawing.Graphics object with which to draw the RotatedRectangleF.</param> Public Sub Render(ByVal g As Graphics, ByVal p As Pen) g.TranslateTransform(Me.Centre.X, Me.Centre.Y) g.RotateTransform(Me.Angle) g.DrawRectangle(p, -Me.Size.Width / 2.0F, -Me.Size.Height / 2.0F, Me.Size.Width, Me.Size.Height) g.ResetTransform() End Sub ''' <summary> ''' Determines whether two RotatedRectangleF structures intersect. ''' </summary> ''' <param name="rr1">The first RotatedRectangleF structure.</param> ''' <param name="rr2">The second RotatedRectangleF structure.</param> ''' <returns>True if the two RotatedRectangleF structures intersect, False otherwise.</returns> ''' <remarks> ''' Conversion of code by Oren Becker, 2001 ''' http://www.ragestorm.net/tutorial?id=22 ''' </remarks> Public Shared Function Intersect(ByVal rr1 As RotatedRectangleF, ByVal rr2 As RotatedRectangleF) As Boolean ' Change our structure to match the one in the other code. ' Angle in radians, size is (width / 2, height / 2) rr1 = New RotatedRectangleF(rr1.Centre, New SizeF(rr1.Size.Width / 2, rr1.Size.Height / 2), DegreesToRadians(rr1.Angle)) rr2 = New RotatedRectangleF(rr2.Centre, New SizeF(rr2.Size.Width / 2, rr2.Size.Height / 2), DegreesToRadians(rr2.Angle)) Dim ang As Single = rr1.Angle - rr2.Angle ' orientation of rotated rr1 Dim cosA As Single = CType(Math.Cos(ang), Single) ' precalculated trigonometic - Dim sinA As Single = CType(Math.Sin(ang), Single) ' - values for repeated use Dim x, a1 As Single ' temporary variables for various uses Dim dx As Single ' deltaX for linear equations Dim ext1, ext2 As Single ' // min/max vertical values ' move rr2 to make rr1 cannonic Dim C As New PointF(rr2.Centre.X - rr1.Centre.X, rr2.Centre.Y - rr1.Centre.Y) ' rotate rr2 clockwise by rr2->ang to make rr2 axis-aligned RotatePointFClockwise(C, rr2.Angle) ' calculate vertices of (moved and axis-aligned := 'ma') rr2 Dim BL As PointF = C - rr2.Size Dim TR As PointF = C + rr2.Size ' calculate vertices of (rotated := 'r') rr1 Dim A, B As PointF ' vertices of rr2 A.X = -rr1.Size.Height * sinA B.X = A.X Dim temp1 As Single = rr1.Size.Width * cosA A.X = A.X + temp1 B.X = B.X - temp1 A.Y = rr1.Size.Height * cosA B.Y = A.Y temp1 = rr1.Size.Width * sinA A.Y = A.Y + temp1 B.Y = B.Y - temp1 temp1 = sinA * cosA ' verify that A is vertical min/max, B is horizontal min/max If temp1 < 0 Then temp1 = A.X A.X = B.X B.X = temp1 temp1 = A.Y A.Y = B.Y B.Y = temp1 End If ' verify that B is horizontal minimum (leftest-vertex) If sinA < 0 Then B.X = -B.X B.Y = -B.Y End If ' if rr2(ma) isn't in the horizontal range of ' colliding with rr1(r), collision is impossible If (B.X > TR.X) OrElse (B.X > -BL.X) Then Return False ' if rr1(r) is axis-aligned, vertical min/max are easy to get If (temp1 = 0) Then ext1 = A.Y ext2 = -ext1 Else ' else, find vertical min/max in the range [BL.x, TR.x] x = BL.X - A.X a1 = TR.X - A.X ext1 = A.Y ' if the first vertical min/max isn't in (BL.x, TR.x), then ' find the vertical min/max on BL.x or on TR.x If a1 * x > 0 Then dx = A.X If x < 0 Then dx -= B.X ext1 -= B.Y x = a1 Else dx += B.X ext1 += B.Y End If ext1 *= x ext1 /= dx ext1 += A.Y End If x = BL.X + A.X a1 = TR.X + A.X ext2 = -A.Y ' if the second vertical min/max isn't in (BL.x, TR.x), then ' find the local vertical min/max on BL.x or on TR.x If a1 * x > 0 Then dx = -A.X If x < 0 Then dx -= B.X ext2 -= B.Y x = a1 Else dx += B.X ext2 += B.Y End If ext2 *= x ext2 /= dx ext2 -= A.Y End If End If ' check whether rr2(ma) is in the vertical range of colliding with rr1(r) ' (for the horizontal range of rr2) Return Not ((ext1 < BL.Y AndAlso ext2 < BL.Y) OrElse (ext1 > TR.Y AndAlso ext2 > TR.Y)) End Function Private Shared Function DegreesToRadians(ByVal degrees As Single) As Single Return degrees * Pi / 180 End Function Private Shared Sub RotatePointFClockwise(ByRef point As PointF, ByVal radians As Single) Dim temp As Single = point.X Dim cosAngle As Single = CType(Math.Cos(radians), Single) Dim sinAngle As Single = CType(Math.Sin(radians), Single) point.X = temp * cosAngle + point.Y * sinAngle point.Y = -temp * sinAngle + point.Y * cosAngle End Sub Public Shared Operator =(ByVal rr1 As RotatedRectangleF, ByVal rr2 As RotatedRectangleF) As Boolean Return (rr1.Centre = rr2.Centre) AndAlso (rr1.Angle = rr2.Angle) AndAlso (rr1.Size = rr2.Size) End Operator Public Shared Operator <>(ByVal rr1 As RotatedRectangleF, ByVal rr2 As RotatedRectangleF) As Boolean Return Not (rr1 = rr2) End Operator End Structure
And a Form1.vb to demonstrate it:
Imports System.Drawing.Drawing2D Public Class Form1 Private selected As Integer = 1 Private rec1 As New RotatedRectangleF(New PointF(50, -150), New SizeF(100, 100), 10) Private rec2 As New RotatedRectangleF(New Point(50, -200), New SizeF(25, 100), 55) Private message As String Private messageFont As New Font("Courier New", 10, FontStyle.Regular) Private selectedRectanglePen As Pen = Pens.Red Private unselectedRectanglePen As Pen = Pens.Black Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load message = "Arrow keys / mouse down to move" & vbCrLf message &= "Space to select other rectangle." & vbCrLf message &= "z x / mouse wheel to rotate." Me.DoubleBuffered = True Me.ClientSize = New Size(800, 800) Me.Text = RotatedRectangleF.Intersect(rec1, rec2).ToString End Sub Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown Select Case e.KeyData Case Keys.Left MoveSelectedHorizontally(-1) Case Keys.Right MoveSelectedHorizontally(1) Case Keys.Up MoveSelectedVertically(-1) Case Keys.Down MoveSelectedVertically(1) Case Keys.Z RotateSelected(-1) Case Keys.X RotateSelected(1) Case Keys.Space If selected = 1 Then selected = 2 Else selected = 1 End If Me.Refresh() End Select End Sub Private Sub MouseDidSomething(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseMove, Me.MouseDown If e.Button = Windows.Forms.MouseButtons.Left Then Dim pos As New PointF(CSng(e.X - Me.ClientSize.Width / 2), CSng(e.Y - Me.ClientSize.Height / 2)) If selected = 1 Then rec1.Centre = pos Else rec2.Centre = pos End If Me.Refresh() Me.Text = RotatedRectangleF.Intersect(rec1, rec2).ToString End If End Sub Private Sub Form1_MouseWheel(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseWheel If e.Delta > 0 Then RotateSelected(1) Else RotateSelected(-1) End If End Sub Private Sub RotateSelected(ByVal change As Single) If selected = 1 Then rec1.Angle += change Else rec2.Angle += change End If Me.Refresh() Me.Text = RotatedRectangleF.Intersect(rec1, rec2).ToString End Sub Private Sub MoveSelectedHorizontally(ByVal change As Single) If selected = 1 Then rec1.Centre = New PointF(rec1.Centre.X + change, rec1.Centre.Y) Else rec2.Centre = New PointF(rec2.Centre.X + change, rec2.Centre.Y) End If Me.Refresh() Me.Text = RotatedRectangleF.Intersect(rec1, rec2).ToString End Sub Private Sub MoveSelectedVertically(ByVal change As Single) If selected = 1 Then rec1.Centre = New PointF(rec1.Centre.X, rec1.Centre.Y + change) Else rec2.Centre = New PointF(rec2.Centre.X, rec2.Centre.Y + change) End If Me.Refresh() Me.Text = RotatedRectangleF.Intersect(rec1, rec2).ToString End Sub Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint e.Graphics.DrawString(message, messageFont, Brushes.Gray, 10, 10) e.Graphics.TranslateTransform(Me.ClientSize.Width \ 2, Me.ClientSize.Height \ 2) e.Graphics.DrawLine(Pens.Gray, -Me.ClientSize.Width \ 2, 0, Me.ClientSize.Width \ 2, 0) e.Graphics.DrawLine(Pens.Gray, 0, -Me.ClientSize.Height \ 2, 0, +Me.ClientSize.Height \ 2) Dim store As Matrix = e.Graphics.Transform If selected = 1 Then rec2.Render(e.Graphics, unselectedRectanglePen) e.Graphics.Transform = store rec1.Render(e.Graphics, selectedRectanglePen) Else rec1.Render(e.Graphics, unselectedRectanglePen) e.Graphics.Transform = store rec2.Render(e.Graphics, selectedRectanglePen) End If End Sub End Class
Looks good, but I found a case where it produces a false positive. You can verify it yourself with the following values:
ReplyDeleterr1: angle=10, center (151,-196), size (100,100)
rr2: angle=55, center ( 50, 200), size (25, 100)
Those two do not overlap yet the routine says they do.
win32mfc@gmail.com
// CHRIS