views:

112

answers:

5

According to Daniel, in his answer, there is no easy way to modify the below function so I bit the bullet and started from scratch. Solution is below (as an answer). Actually, ignore my answer. See Tom Sirgedas' answer it's a lot shorter.


I need to modify the solution found here: http://stackoverflow.com/questions/1343346/calculate-a-vector-from-the-center-of-a-square-to-edge-based-on-radius, that calcs the vector from the center of a rectangle, to work for any point within the rectangle.

Here's the previous solution, from the link:

    double magnitude;
    double abs_cos_angle= fabs(cos(angle));
    double abs_sin_angle= fabs(sin(angle));
    if (width/2*abs_sin_angle <= height/2*abs_cos_angle)
    {
            magnitude= width/2/abs_cos_angle;
    }
    else
    {
            magnitude= height/2/abs_sin_angle;
    }

    double check_x= x + cos(angle)*magnitude;
    double check_y= y + sin(angle)*magnitude;

check_x and check_y return the point on the edge of the rectangle that a line drawn from the center, at angle, will intersect.

It's a while since I went to school, so I blindly tried replacing width/2 and height/2 with the point I'm interested in. Unfortunately that didn't work.

Any ideas?

ETA:

This blind modification always returns the correct result if the line intersects the rectangle at the Top or Left. Depending on the quadrant the originating point is in, it returns a point too far away or too close when the line intersects the Right or Bottom.

+1  A: 

The simplest solution is probably to just perform four ray line segment intersection tests. I doubt that a special-purpose solution will be much more efficient or easier to understand and maintain.

Daniel Brückner
I'm thinking that the original solution just needs a small tweek. For instance, my blind modification always gives the correct result if the line intersects the rectangle at the Top or Left. Depending on the quadrant the originating point is in, it returns a result too big or small when the line intersects the Right or Bottom.
Jules
I slight modification will not work because the original solution relies on the symmetry of the original problem.
Daniel Brückner
A: 

Let's clarify one thing right off the bat: it sounds to me like you're worried about vectors in 2D. All your points are in the plane. Is it also true that it's a rectangle you're interested in (four corners, all with 90 degree angles, two pairs of opposite sides have identical lengths)?

The center of your rectangle is given by the average of the four corner points:

alt text

alt text

There's the starting point of your vector.

Any vector is defined by two points, so you just need the second point (e.g., the midpoint of an edge) to calculate the vector in 2D space:

alt text

The problem with starting from the center point and an angle is that it gives you an infinite number of vectors, not just one. (There are an infinite number of ending points along the line passing through your center point at a given angle.) You'll have to pluck the precise one that you're interested in from that infinite set. If it happens to be the one that intersects one of your sides at an arbitrary point, you'll have to calculate that first. It's a root finding problem along the vector line at that point. Maybe you can check out secant or other numerical methods to figure out how to do it.

duffymo
The angle is fixed - the question asks for finding the intersection of a ray originating from a point within a axis-aligned rectangle at a given angle with the rectangle. This problem has a unique solution (if the origin is not allowed to be located on the border of the ractangle).
Daniel Brückner
Yes, I realize that. That's what "pluck the precise one" means. Thanks, Daniel. The OP should realize that there could be two solutions, one in each direction along this line.
duffymo
A: 

The below solution builds a formula for the line that passes through the point provided and crosses the rectangle border at the specified angle. Depending on the angle, I test to see if it intersects with either of 2 rectangle borders. I always base the check on an angle from 0 - 90 degrees. To account for this, the test in Quadrants Q2 and Q4 use a line that is perpendicular to the line in Q1 and Q4.

When angle = 0, the line points eastwards.
I've subtracted the angle from 360 so that the line rotates clockwise instead of anti-clockwise.

Private Function GetIntersectionPoint(ByVal rectangleSize As SizeF, ByVal p As Point, ByVal degreeAngle As Single) As PointF

    Dim w = CInt(rectangleSize.Width)
    Dim h = CInt(rectangleSize.Height)
    degreeAngle = ((360 - degreeAngle) Mod 360)

    If degreeAngle = 0 Then
        Return New Point(w, p.Y)
    ElseIf degreeAngle = 90 Then
        Return New Point(p.X, 0)
    ElseIf degreeAngle = 180 Then
        Return New Point(0, p.Y)
    ElseIf degreeAngle = 270 Then
        Return New Point(p.X, h)
    End If

    Dim x, y As Integer

    If (degreeAngle > 0 AndAlso degreeAngle < 90) Then
        y = YFromX(degreeAngle, w, p)
        If y <= 0 AndAlso y >= -h Then
            Return New Point(w, -y)
        End If
        x = XFromY(degreeAngle, 0, p)
        Return New Point(x, 0)
    End If

    If (degreeAngle > 90 AndAlso degreeAngle < 180) Then
        degreeAngle -= 90
        y = YFromX_Perpedicular(degreeAngle, 0, p)
        If y <= 0 AndAlso y >= -h Then
            Return New Point(0, -y)
        End If
        x = XFromY_Perpendicular(degreeAngle, 0, p)
        Return New Point(x, 0)
    End If

    If (degreeAngle > 180 AndAlso degreeAngle < 270) Then
        degreeAngle -= 180
        y = YFromX(degreeAngle, 0, p)
        If y <= 0 AndAlso y >= -h Then
            Return New Point(0, -y)
        End If
        x = XFromY(degreeAngle, -h, p)
        Return New Point(x, h)
    End If

    If (degreeAngle > 270 AndAlso degreeAngle < 360) Then
        degreeAngle -= 270
        y = YFromX_Perpedicular(degreeAngle, w, p)
        If y <= 0 AndAlso y >= -h Then
            Return New Point(w, -y)
        End If
        x = XFromY_Perpendicular(degreeAngle, -h, p)
        Return New Point(x, h)
    End If

End Function

Private Function YFromX(ByVal degreeAngle As Single, ByVal x As Integer, ByVal p As Point) As Integer
    Dim alpha As Double = degreeAngle * Math.PI / 180
    Dim sinAlpha = Sin(alpha)
    Dim cosAlpha = Cos(alpha)
    Return CInt(sinAlpha / cosAlpha * (x - p.X) - p.Y)
End Function

Private Function XFromY(ByVal degreeAngle As Single, ByVal y As Integer, ByVal p As Point) As Integer
    Dim alpha As Double = degreeAngle * Math.PI / 180
    Dim sinAlpha = Sin(alpha)
    Dim cosAlpha = Cos(alpha)
    Return CInt(cosAlpha / sinAlpha * (y + p.Y) + p.X)
End Function

Private Function YFromX_Perpedicular(ByVal degreeAngle As Single, ByVal x As Integer, ByVal p As Point) As Integer
    Dim alpha As Double = degreeAngle * Math.PI / 180
    Dim sinAlpha = Sin(alpha)
    Dim cosAlpha = Cos(alpha)
    Return CInt((cosAlpha / sinAlpha) * (p.X - x) - p.Y)
End Function

Private Function XFromY_Perpendicular(ByVal degreeAngle As Single, ByVal y As Integer, ByVal p As Point) As Integer
    Dim alpha As Double = degreeAngle * Math.PI / 180
    Dim sinAlpha = Sin(alpha)
    Dim cosAlpha = Cos(alpha)
    Return CInt(p.X - sinAlpha / cosAlpha * (y + p.Y))
End Function
Jules
A: 

You are basically asking for the polar equation of a rectangle, and it is

r(t) = min(R, w*abs(sec(t)), h*abs(csc(t))); t = [0, 2pi]

where w and h are the half-width and half-height, and R is any number greater than or equal to sqrt(w^2+h^2). This assumes the rectangle is at the origin, and if it's not, all you have to do is add the coordinates the center to the result, which you obtain by (r*cos(t), r*sin(t)). Of course, this approach is nasty since sec and csc have singularities, but you can cap them at max(w,h).

Victor Liu
+1  A: 

Let's say the rectangle is defined by (x1,y1,x2,y2) and let's say the ray starts at (px,py).

Let vx = cos(angle)

Let vy = sin(angle)

Traveling a distance of t along the ray will bring you to the point (px+t*vx, py+t*vy).

Traveling along the ray,

  • we hit the left wall when px+t*vx = x1, or t=(x1-px)/vx
  • we hit the right wall when px+t*vx = x2, or t=(x2-px)/vx
  • we hit the top wall when py+t*vy = y1, or t=(y1-py)/vy
  • we hit the bottom wall when py+t*vy = y2, or t=(y2-py)/vy

So, there are four possible solutions for t. The correct value of t (among the four) is the smallest positive one. The actual intersection is at the point (px+t*vx, py+t*vy). Just be careful not to divide by zero!

Tom Sirgedas
Hi, this looks promising and would certainly cut down on my code, but I'm getting some crazy values. Do I need to be taking the Absolute values anywhere?
Jules
No, absolute values aren't necessary. A few things to check:Are you multiplying the angle by PI/180 (converting to radians)?Are you negating the angle (to match your convention)?Are you making sure to ignore negative values of t?Are you avoiding division by 0?If you did all these, maybe you could post your code, and give an example of an incorrect result?
Tom Sirgedas
Thanks. After excluding -'ve values of t from the check it works. I'll mark this as the answer.
Jules