-1

I am trying to see if an imagebox interacts with an e graphics line. my current code:

e.Graphics.DrawLine(SystemPens.ButtonShadow, a, b);

if (pictureBox1.Bounds.IntersectsWith(e.GraphicsLine.Bounds))
{
    dead();
}

I am unsure how to do this.

TaW
  • 53,122
  • 8
  • 69
  • 111
J. Doe
  • 1
  • 1

2 Answers2

2

To test for intersection of a rectangle and a line you can either use one of the many methods from the Wikipedia article or use a GDI+ trick involving GraphicsPaths and Regions..:

using System.Drawing.Drawing2D;
..

bool Intersects(Point a, Point b, Rectangle r)
{
    if (Math.Min(a.X, b.X) > r.Right)  return false;   // *
    if (Math.Max(a.X, b.X) < r.Left)   return false;   // *
    if (Math.Min(a.Y, b.Y) > r.Bottom) return false;   // *
    if (Math.Max(a.Y, b.Y) < r.Top)    return false;   // *

    if (r.Contains(a)) return true;   // **
    if (r.Contains(b)) return true;   // **

    using (GraphicsPath gp = new GraphicsPath())
    using (Pen pen = new Pen(Color.Empty, 0.5f))
    using (Region reg = new Region(r))
    using (Graphics g = CreateGraphics())
    {
        gp.AddLine(a,b);
        gp.Widen(pen);   // we need to widen the line path just a little
        reg.Intersect(gp);
        if (reg.IsEmpty(g)) return false;
    }
    return true;
}

Here is a small test result:

enter image description here

Since Region.IsEmpty is probably not very fast I have prepended the call with a few trivial tests for better speed. For a discussion of performance issues with Region see here. From this I guess one can conclude that testing with a simple rectangle will in fact still be reasonably fast..

For a truely fast test you may need to implement a real clipping method. This looks nice..

But the GDI+ trick with the region has one big advantage over the analytical methods: I will work with any shapes you can put into a GraphicsPath, including circles, ellipes, polygons, all sorts of combinations and also complex tracing paths. And since the Region supports all set operations, your imagination is the limit..

This allows you to test complex shapes of spaceships or monsters; and with a few extra tricks you can even test rotated shapes.

Note that if you go for testing complex shapes you will want to:

  • pass in the GraphicsPath
  • use its bounding rectangle (gp.GetBounds()) for the first four tests (*)
  • remove the other two tests (**) since the bounding rectangle will not work now; in fact they only work with rectangles; I had to remove them for the following ellipse demo.

Here is the same example, simply replacing the rectangle with an ellipse:

enter image description here

TaW
  • 53,122
  • 8
  • 69
  • 111
  • That is not actually correct. Or maybe it is not correct in many cases. You are just taking a rect with the line being the diagonal. There is too much area in this rect that the picbox intersects but not with the actuall line. – γηράσκω δ' αεί πολλά διδασκόμε Jul 06 '18 at 22:50
  • Take a look at this: [Line clipping](https://en.wikipedia.org/wiki/Line_clipping). There are `C` and `C++` implementations for the main algorithms which can be easily translated to `C#`. – Jimi Jul 07 '18 at 00:18
  • @Jim : Well, you are right if he want to test the line intersecting the rectangle. He talked about the line's bounds, though. But I guess you are right about what he wants.. – TaW Jul 07 '18 at 06:07
  • I didn't know the region solution. I always used lines equations and some complex staff in the past. I am going to test how fast it is. Nice solution though! +1 – γηράσκω δ' αεί πολλά διδασκόμε Jul 07 '18 at 16:06
  • Yeah, well it is cheating in a way. Not sure about speed; I think it'll depend on sizes, so it may be not so good performance-wise for large rectangles.. – TaW Jul 07 '18 at 16:08
  • Sadly the region doesn't work. It is exactly the same as the rectangle with the line as a diagonal, your first method. Too good to be true. – γηράσκω δ' αεί πολλά διδασκόμε Jul 07 '18 at 21:43
  • @γηράσκω: Interesting. We have to widen the line path a little to make it work. I didn't know that, always worked with closed figures with areas before, I guess. I have updated the answer with examples, removed the original part and added notes on a few pros and cons.. – TaW Jul 08 '18 at 08:46
  • 1
    I don't know why but i really stack to this problem. Your solution works great! I tested the speed and for 1200 lines: *time = ~30 millisecs*. I then search for a faster method and i found the jackpot. *Andre LaMothe's code from Tricks of the Windows Game Programming Gurus*. It checks if two line segments intersect. So i checked the 1200 lines with all 4 line segments of the rectangle. The results were amazing: *~200microsecs or better 0.2 millisecs!!!* – γηράσκω δ' αεί πολλά διδασκόμε Jul 08 '18 at 18:39
  • Well, why not post the code? A really good solution for strict line-rectangle testing is surely welcome. I wanted to implement one but got distracted.. And ~100x is very nice. Btw: The speed of the region version totally depends on the data, even with a rectangle it will depend on the length of the line; yours should be completely data-independent. – TaW Jul 08 '18 at 20:33
  • @TaW I guess we do the hard work and the op doesn't give a sh..! – γηράσκω δ' αεί πολλά διδασκόμε Jul 09 '18 at 22:15
  • Well, now there's a more generic, recyclable GraphicsPath solution and @γηράσκω δ' αεί πολλά διδασκόμε has a specialized speedy solution. If the OP doesn't give a sh... about it, his loss. – Jimi Jul 10 '18 at 02:08
2

Here you go. The original code was in C and i translated to vb.net. The code works with a line and a rectangle.

enter image description here

'//min_clip_x is the distance of the left side of rect
'//min_clip_y is the distance of the upper side of rect
'//max_clip_x is the distance of the right side of rect
'//min_clip_y is the distance of the down side of rect

Private Function Clip_Line(ByVal x1 As Integer, ByVal y1 As Integer, ByVal x2 As Integer, ByVal y2 As Integer,
                             ByVal min_clip_x As Integer, ByVal max_clip_x As Integer, ByVal min_clip_y As Integer,
                             ByVal max_clip_y As Integer) As Boolean

    Const CLIP_CODE_C As Integer = 0
    Const CLIP_CODE_N As Integer = 8
    Const CLIP_CODE_S As Integer = 4
    Const CLIP_CODE_E As Integer = 2
    Const CLIP_CODE_W As Integer = 1
    Const CLIP_CODE_NE As Integer = 10
    Const CLIP_CODE_SE As Integer = 6
    Const CLIP_CODE_NW As Integer = 9
    Const CLIP_CODE_SW As Integer = 5

    Dim p1_code As Integer = 0
    Dim p2_code As Integer = 0
    Dim xc1, yc1, xc2, yc2 As Integer

    xc1 = x1
    yc1 = y1
    xc2 = x2
    yc2 = y2

    '//determine codes for p1 And p2
    If y1 < min_clip_y Then
        p1_code = p1_code Or CLIP_CODE_N
    Else
        If y1 > max_clip_y Then
            p1_code = p1_code Or CLIP_CODE_S
        End If
    End If

    If (x1 < min_clip_x) Then
        p1_code = p1_code Or CLIP_CODE_W
    Else
        If (x1 > max_clip_x) Then
            p1_code = p1_code Or CLIP_CODE_E
        End If
    End If

    If (y2 < min_clip_y) Then
        p2_code = p2_code Or CLIP_CODE_N
    Else
        If (y2 > max_clip_y) Then
            p2_code = p2_code Or CLIP_CODE_S
        End If
    End If

    If (x2 < min_clip_x) Then
        p2_code = p2_code Or CLIP_CODE_W
    Else
        If (x2 > max_clip_x) Then
            p2_code = p2_code Or CLIP_CODE_E
        End If
    End If

    '//try And trivially reject
    If CBool(p1_code And p2_code) Then
        Return False
    End If

    '//test for totally visible, if so leave points untouched
    If (p1_code = 0 And p2_code = 0) Then
        Return True
    End If

    '//determine end clip point for p1
    Select Case p1_code
        Case CLIP_CODE_C
            Exit Select
        Case CLIP_CODE_N
            yc1 = min_clip_y
            xc1 = CInt(x1 + 0.5 + (min_clip_y - y1) * (x2 - x1) / (y2 - y1))

            Exit Select
        Case CLIP_CODE_S
            yc1 = max_clip_y
            xc1 = CInt(x1 + 0.5 + (max_clip_y - y1) * (x2 - x1) / (y2 - y1))

            Exit Select
        Case CLIP_CODE_W
            xc1 = min_clip_x
            yc1 = CInt(y1 + 0.5 + (min_clip_x - x1) * (y2 - y1) / (x2 - x1))

            Exit Select
        Case CLIP_CODE_E
            xc1 = max_clip_x
            yc1 = CInt(y1 + 0.5 + (max_clip_x - x1) * (y2 - y1) / (x2 - x1))

            Exit Select
        Case CLIP_CODE_NE
            '//north hline intersection
            yc1 = min_clip_y
            xc1 = CInt(x1 + 0.5 + (min_clip_y - y1) * (x2 - x1) / (y2 - y1))
            '//test if intersection Is valid,
            '//if so then done, else compute next
            If (xc1 < min_clip_x) Or (xc1 > max_clip_x) Then
                '//east vline intersection
                xc1 = max_clip_x
                yc1 = CInt(y1 + 0.5 + (max_clip_x - x1) * (y2 - y1) / (x2 - x1))
            End If

            Exit Select
        Case CLIP_CODE_SE
            '//south hline intersection
            yc1 = max_clip_y
            xc1 = CInt(x1 + 0.5 + (max_clip_y - y1) * (x2 - x1) / (y2 - y1))
            '//test if intersection Is valid,
            '//if so then done, else compute next
            If (xc1 < min_clip_x) Or (xc1 > max_clip_x) Then
                '//east vline intersection
                xc1 = max_clip_x
                yc1 = CInt(y1 + 0.5 + (max_clip_x - x1) * (y2 - y1) / (x2 - x1))
            End If

            Exit Select
        Case CLIP_CODE_NW
            '//north hline intersection
            yc1 = min_clip_y
            xc1 = CInt(x1 + 0.5 + (min_clip_y - y1) * (x2 - x1) / (y2 - y1))
            '//test if intersection Is valid,
            '//if so then done, else compute next
            If (xc1 < min_clip_x) Or (xc1 > max_clip_x) Then
                xc1 = min_clip_x
                yc1 = CInt(y1 + 0.5 + (min_clip_x - x1) * (y2 - y1) / (x2 - x1))
            End If

            Exit Select
        Case CLIP_CODE_SW
            '//south hline intersection
            yc1 = max_clip_y
            xc1 = CInt(x1 + 0.5 + (max_clip_y - y1) * (x2 - x1) / (y2 - y1))
            '//test if intersection Is valid,
            '//if so then done, else compute next
            If (xc1 < min_clip_x) Or (xc1 > max_clip_x) Then
                xc1 = min_clip_x
                yc1 = CInt(CDbl(y1) + 0.5 + CDbl(min_clip_x - x1) * CDbl(y2 - y1) / CDbl(x2 - x1))
            End If

            Exit Select
        Case Else
            Exit Select
    End Select

    '//determine end clip point for p2
    Select Case p2_code
        Case CLIP_CODE_C
            Exit Select
        Case CLIP_CODE_N
            yc2 = min_clip_y
            xc2 = CInt(x2 + (min_clip_y - y2) * (x1 - x2) / (y1 - y2))

            Exit Select
        Case CLIP_CODE_S
            yc2 = max_clip_y
            xc2 = CInt(x2 + (max_clip_y - y2) * (x1 - x2) / (y1 - y2))

            Exit Select
        Case CLIP_CODE_W
            xc2 = min_clip_x
            yc2 = CInt(y2 + (min_clip_x - x2) * (y1 - y2) / (x1 - x2))

            Exit Select
        Case CLIP_CODE_E
            xc2 = max_clip_x
            yc2 = CInt(y2 + (max_clip_x - x2) * (y1 - y2) / (x1 - x2))

            Exit Select
        Case CLIP_CODE_NE
            '//north hline intersection
            yc2 = min_clip_y
            xc2 = CInt(x2 + 0.5 + (min_clip_y - y2) * (x1 - x2) / (y1 - y2))
            '//test if intersection Is valid,
            '//if so then done, else compute next
            If (xc2 < min_clip_x) Or (xc2 > max_clip_x) Then
                '//east vline intersection
                xc2 = max_clip_x
                yc2 = CInt(y2 + 0.5 + (max_clip_x - x2) * (y1 - y2) / (x1 - x2))
            End If

            Exit Select
        Case CLIP_CODE_SE
            '//south hline intersection
            yc2 = max_clip_y
            xc2 = CInt(x2 + 0.5 + (max_clip_y - y2) * (x1 - x2) / (y1 - y2))
            '//test if intersection Is valid,
            '//if so then done, else compute next
            If (xc2 < min_clip_x) Or (xc2 > max_clip_x) Then
                '//east vline intersection
                xc2 = max_clip_x
                yc2 = CInt(y2 + 0.5 + (max_clip_x - x2) * (y1 - y2) / (x1 - x2))
            End If

            Exit Select
        Case CLIP_CODE_NW
            '//north hline intersection
            yc2 = min_clip_y
            xc2 = CInt(x2 + 0.5 + (min_clip_y - y2) * (x1 - x2) / (y1 - y2))
            '//test if intersection Is valid,
            '//if so then done, else compute next
            If (xc2 < min_clip_x) Or (xc2 > max_clip_x) Then
                xc2 = min_clip_x
                yc2 = CInt(y2 + 0.5 + (min_clip_x - x2) * (y1 - y2) / (x1 - x2))
            End If

            Exit Select
        Case CLIP_CODE_SW
            '//south hline intersection
            yc2 = max_clip_y
            xc2 = CInt(x2 + 0.5 + (max_clip_y - y2) * (x1 - x2) / (y1 - y2))
            '//test if intersection Is valid,
            '//if so then done, else compute next
            If (xc2 < min_clip_x) Or (xc2 > max_clip_x) Then
                xc2 = min_clip_x
                yc2 = CInt(y2 + 0.5 + (min_clip_x - x2) * (y1 - y2) / (x1 - x2))
            End If

            Exit Select
        Case Else
            Exit Select
    End Select

    '//do bounds check
    If (xc1 < min_clip_x) Or (xc1 > max_clip_x) Or (yc1 < min_clip_y) Or (yc1 > max_clip_y) Or
        (xc2 < min_clip_x) Or (xc2 > max_clip_x) Or (yc2 < min_clip_y) Or (yc2 > max_clip_y) Then

        Return False '//no collision
    End If

    Return True '//collision
End Function