I thought it might be of interest to provide a solution that does not make use of the ruby2d
gem, so show how the required calculations are performed.
Example data
Suppose
center = [2, 7]
radius = 4
corners = [[1,3], [5,3], [3,1], [3,5]]
This looks like the following, with Y
the center of the circle and the X
's the corners of the square.
7 Y
6
5 X
4
3 X X
2
1 X
0 1 2 3 4 5
Determine the sides of the rectangle
Select any of these corners (the first, say):
first, *rest = corners
#=> [[1, 3], [5, 3], [3, 1], [3, 5]]
first
#=> [1, 3]
rest
#=> [[5, 3], [3, 1], [3, 5]]
Determine which corner is farthest from c1
:
x1, y1 = first
farthest = rest.max_by { |x,y| (x-x1)**2 + (y-y1)**2 }
#=> [5, 3]
Compute the sides of the square as arrays of endpoints:
rest.delete(farthest)
#=> [5, 3]
rest
#=> [[3, 1], [3, 5]]
sides = [first,farthest].product(rest)
#=> [[[1, 3], [3, 1]],
# [[1, 3], [3, 5]],
# [[5, 3], [3, 1]],
# [[5, 3], [3, 5]]]
Let's make a method to do this.
def sides(corners)
first, *rest = corners
x1, y1 = first
farthest = rest.max_by { |x,y| (x-x1)**2 + (y-y1)**2 }
rest.delete(farthest)
[first,farthest].product(rest)
end
sides(corners)
#=> <as above>
Compute intercept and slope for the lines coincident with the sides of the square
For each of these sides there is a line in space that is coincide with the side. Each of these lines is described by an intercept i
and a slope b
, such that for any value of x
the point [x, y]
is on the line if y = i + b*x
. We can compute the intercept and slope for each of these lines.
def compute_line(side)
(ux,uy), (vx,vy) = side
b = ux==uy ? 0.0 : (uy - vy).fdiv(ux - vx)
[uy - b*ux, b]
end
sides.map { |side| compute_line(side) }
#=> [[4.0, -1.0], [2.0, 1.0], [-2.0, 1.0], [8.0, -1.0]]
Note:
i, b = lines.first
#=> [4.0, -1.0]
i + b*0
#=> 4.0 (the point [0, 4.0])
i + b*1
#=> 3.0 (the point [1, 3.0])
i + b*2
#=> 2.0 (the point [2, 2.0])
i + b*3
#=> 1.0 (the point [3, 1.0])
i + b*4
#=> 0.0 (the point [4, 0.0])
Compute the points where the circle intersects lines coincident with the sides
Let
cx, cy = center
#=> [2, 7]
Suppose we consider a side for which the coincident line has intercept i
and slope s
. We then have the quadratic expression:
(cx-x)2 + (cy-i-s*x)2 = radius2
By defining:
e = cy - i
the equation reduces to:
cx2 - 2*cx*x + x2 + e2 - 2*e*s*x + s2*x2 = radius2
or
(1 + s2)*x2 + 2*(-cx -e*s)*x + cx2 + e2 - radius2 = 0
or
ax2 + bx + c = 0
where:
a = (1 + s2)
b = -2*(cx + e*s)
c = cx2 + e2 - radius2
The real root or roots, if it/they exist, are given by the quadratic equation. First compute the discriminate:
d = b2 - 4*a*c
If the discriminate is negative the quadratic has no real roots (only complex roots). Here that means the circle is not large enough to intersect the line that is coincident with this side.
If the discriminate d
is positive, there are two real roots (one real root only if d
is zero). Let:
w = d1/2
The roots (values of x
) are:
(-b + w)/(2*a)
and
(-b - w)/(2*a)
Let's wrap this in a method:
def circle_and_line_intersections(center, radius, side)
i, s = compute_line(side)
cx, cy = center
e = cy - i
a = 1 + s**2
b = -2*(cx + e*s)
c = cx**2 + e**2 - radius**2
d = b**2 - 4*a*c
return [] if d < 0
return [-b/(2*a)] if d.zero?
w = Math.sqrt(d)
r1 = (-b + w)/(2*a)
r2 = (-b - w)/(2*a)
[[r1, i + s*r1], [r2, i + s*r2]]
end
sides.map { |side| circle_and_line_intersections(center, radius, side) }
#=> [[[ 0.82287, 3.17712], [-1.82287, 5.82287]],
# [[ 5.89791, 7.89791], [ 1.10208, 3.10208]],
# [],
# [[4.28388, 3.71611], [-1.28388, 9.28388]]]
It remains to do the following.
Determine if one of the points of intersection is on a side
That is simple and straightforward:
def on_side?(pt, side)
low, high = side.map(&:first).sort
(low..high).cover?(pt.first)
end
For example:
on_side?([0.82287, 3.17712], sides[0])
#=> false
on_side?([1.10208, 3.10208], sides[1])
#=> true
Putting it all together
def intersect?(center, radius, corners)
sides(corners).any? do |side|
circle_and_line_intersections(center, radius, side).any? { |pt|
on_side?(pt, side) }
end
end
intersect?(center, radius, corners)
#=> true