圆矩形碰撞的问题发现侧

问题描述 投票:1回答:1

我发现了一个功能很好的函数,它可以检测圆形和矩形之间的碰撞,并找到接触点。然后,我使用该点来确定圆形击中矩形的哪一侧,以便反映圆形。但是,当圆的中心位于矩形内部时,该函数将最接近的点作为圆的中心,并且将其处理为碰到顶点而不是边。这是我的代码:

def collide_rect(box, ball_): 
    #convenience
    left = box.rect.left
    right = left + box.rect.width
    top = box.rect.top
    bottom = top + box.rect.height

    #find the closest point
    closest = (max(left, min(ball_.center[0], right)), max(top, min(ball_.center[1], bottom)))
    dx = ball_.center[0] - closest[0]
    dy = ball_.center[1] - closest[1]

    #handle the collsion
    if math.hypot(dx, dy) <= ball.radius:
        #Hit on the top or bottom
        if left <= closest[0] <= right and (closest[1] == top or closest[1] == bottom):
            ball_.vector = (ball_.vector[0], -1*ball_.vector[1])
        #Hit on the side
        elif top <= closest[1] <= bottom and (closest[0] == left or closest[0] == right):
            ball_.vector = (-1*ball_.vector[0], ball_.vector[1])
        #Hit a vertex
        else:
            ball_.vector = (-1*ball_.vector[0], -1*ball_.vector[1])

        return True
    else:
        return False 

注意ball_.vector是圆的方向向量,ball.radius是类变量。任何以更好的方式找到碰撞面的帮助将不胜感激!

python pygame collision
1个回答
1
投票

您可以通过在矩形上找到圆形的,该矩形位于直线上,该直线由圆的中心和矩形的中心给出。

矩形和圆上的点可以通过中心点之间的偏移与矩形大小的最小关系来计算。在以下算法中,矩形由中心点(r_cpt)和大小(r_size)定义,圆由中心点(c_cpt)和半径(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]

圆的方向是从矩形的中心点到矩形轮廓上的点的向量给定的:

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] 

请参见示例,(dxdy)用洋红色线表示:

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()
© www.soinside.com 2019 - 2024. All rights reserved.