You can find the side of the rectangle by finding the point on the rectangle that lies on the straight line given by the center of the circle and the center of the rectangle.
The point on the rectangle and the circle can be computed by the minimum relation of the offset between the center points and the size of the rectangle.
In the following algorithm, the rectangle is defined by the center point (r_cpt) and the size (r_size) and the circle is defined by the center point (c_cpt) and the radius (c_rad):
def intersectRectangleCircle(r_cpt, r_size, c_cpt, c_rad):
v2_c_cpt = pygame.math.Vector2(c_cpt)
v2_r_cpt = pygame.math.Vector2(r_cpt)
offset = v2_c_cpt - v2_r_cpt
if offset.x == 0 and offset.y == 0:
return [v2_c_cpt, v2_r_cpt]
if offset.x == 0:
ratio = r_size[1] / abs(offset.y)
elif offset.y == 0:
ratio = r_size[0] / abs(offset.x)
else:
ratio = min(r_size[0] / abs(offset.x), r_size[1] / abs(offset.y))
ratio *= 0.5
p1 = v2_r_cpt + (offset * ratio)
offset.scale_to_length(c_rad)
p2 = v2_c_cpt - offset
return [p1, p2]
The direction to the circle is the given by the vector from the center point of the rectangle to the point on the rectangle contour:
isect_pts = intersectRectangleCircle(rect_center, rect_size, circle_center, circle_diameter/2)
dx, dy = isect_pts[0].x - rect_center[0], isect_pts[1].y - rect_center[1]
See the example, (dx, dy) is represented by the magenta colored line:
repl.it/@Rabbid76/PyGame-NearestPointOnRectangle

import pygame
import math
pygame.init()
screen = pygame.display.set_mode((500, 500))
def intersectRectangleCircle(r_cpt, r_size, c_cpt, c_rad):
v2_c_cpt = pygame.math.Vector2(c_cpt)
v2_r_cpt = pygame.math.Vector2(r_cpt)
offset = v2_c_cpt - v2_r_cpt
if offset.x == 0 and offset.y == 0:
return [v2_c_cpt, v2_r_cpt]
if offset.x == 0:
ratio = r_size[1] / abs(offset.y)
elif offset.y == 0:
ratio = r_size[0] / abs(offset.x)
else:
ratio = min(r_size[0] / abs(offset.x), r_size[1] / abs(offset.y))
ratio *= 0.5
p1 = v2_r_cpt + (offset * ratio)
offset.scale_to_length(c_rad)
p2 = v2_c_cpt - offset
return [p1, p2]
def inBetween(p1, p2, px):
v = pygame.math.Vector2(p2) - pygame.math.Vector2(p1)
d = v.length()
if d == 0:
return False
v.normalize_ip()
vx = pygame.math.Vector2(px) - pygame.math.Vector2(p1)
dx = v.dot(vx)
return dx >= 0 and dx <= d
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
rect_center = screen.get_rect().center
rect_size = screen.get_width() // 5, screen.get_height() // 10
rect = pygame.Rect(rect_center[0] - rect_size[0] // 2, rect_center[1] - rect_size[1] // 2, *rect_size)
circle_center = pygame.mouse.get_pos()
circle_diameter = min(*screen.get_size()) // 5
isect_pts = intersectRectangleCircle(rect_center, rect_size, circle_center, circle_diameter/2)
dx, dy = isect_pts[0].x - rect_center[0], isect_pts[1].y - rect_center[1]
screen.fill((255,255,255))
pygame.draw.rect(screen, (0, 0, 0), rect, 3)
pygame.draw.circle(screen, (0, 0, 0), circle_center, circle_diameter // 2, 3)
pygame.draw.line(screen, (0, 0, 255), rect_center, circle_center, 1)
pygame.draw.line(screen, (255, 0, 255), rect_center, (round(isect_pts[0].x), round(isect_pts[0].y)), 3)
for i in range(2):
px, py = round(isect_pts[i].x), round(isect_pts[i].y)
col = (255, 0, 0) if inBetween(rect_center, circle_center, (px, py)) else (0, 255, 0)
pygame.draw.line(screen, col, (px-5, py), (px+5, py), 3)
pygame.draw.line(screen, col, (px, py-5), (px, py+5), 3)
pygame.display.flip()
pygame.quit()
quit()