如何计算圆和矩形之间的最小平移矢量?

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

我知道如何检测与圆形和矩形的碰撞,但是我不确定如何找到两者之间的最小平移向量。我知道如何使用SAT碰撞检测算法来做到这一点,但是对于我现在的简单实现而言,这太复杂了。

我真的不知道该怎么做,除非适当地更改x坐标。

这里是代码。当您按下向下按钮时,我想要的是当不断按下向下按钮时,圆自动“推”到左侧(因为它已经定位在中心了一点点),即它向下移动但滑动向左转。

import pygame

if __name__ == "__main__":
    pygame.init()
    display = pygame.display.set_mode((500, 500))
    display.fill((255, 255, 255))
    circle_x = 240
    circle_y = 50
    pygame.draw.circle(display, (0, 0, 255), (circle_x, circle_y), 50)
    pygame.draw.rect(display, (0, 255, 255), (240, 250, 20, 250))
    pygame.display.update()    
    vel = 1
    is_down_held = False
    clock = pygame.time.Clock()
    while True:
        pressed_keys = pygame.key.get_pressed()

        if pressed_keys[pygame.K_DOWN]:
            circle_y += vel

        display.fill((255, 255, 255))
        pygame.draw.circle(display, (0, 0, 255), (circle_x, circle_y), 50)
        pygame.draw.rect(display, (0, 255, 255), (240, 250, 20, 250))
        pygame.display.update()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

        dt = clock.tick(60)
        dt /= 1000

我尝试用某个常数移动x坐标,但是,它看起来不切实际,有时实际上正在穿过矩形(因为它实际上未检测到矩形)。

pygame collision-detection collision
1个回答
0
投票

好吧,看来我还是必须在最后实现SAT碰撞检测。要计算矩形和圆形之间的最小平移矢量,您需要经过形状的必要轴(有关其说明,请参见https://jcharry.com/blog/physengine10https://www.sevenson.com.au/actionscript/sat/),然后在发生碰撞时采用最小的重叠轴。下面是使用pygame GUI库的代码:

import math
import pygame


class Vector:

    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.magnitude = math.sqrt(x ** 2 + y ** 2)
        if self.magnitude != 0:
            self.direction_x = -x / self.magnitude
            self.direction_y = -y / self.magnitude
        else:
            self.direction_x = 0
            self.direction_y = 0

    def normalize(self):
        if self.magnitude != 0:
            self.x /= self.magnitude
            self.y /= self.magnitude
            self.magnitude = 1


def project_vector(vector1, vector2):
    return get_dot_product(vector1, get_unit_vector(vector2))


def get_dot_product(vector1, vector2):
    return (vector1.x * vector2.x) + (vector1.y * vector2.y)


def get_normal(vector):
    return Vector(vector.y, -vector.x)


def get_vector(point):
    return Vector(point[0], point[1])


def scale_vector(vector, magnitude):
    return Vector(vector.x*magnitude, vector.y*magnitude)


def get_unit_vector(vector):
    if vector.magnitude != 0:
        return scale_vector(vector, 1 / vector.magnitude)
    else:
        return scale_vector(vector, 0)


def get_closest_point(circle_centre, rectangle_points):
    closest_distance = float('inf')
    closest_point = None
    for point in rectangle_points:
        distance = (circle_centre[0] - point[0])**2 + (circle_centre[1] - point[1])**2
        if distance <= closest_distance:
            closest_distance = distance
            closest_point = point
    return closest_point


def is_collision(circle_centre, rectangle_points):
    closest_point = get_closest_point(circle_centre, rectangle_points)
    rectangle_edge_vectors = []
    for point in rectangle_points:
        rectangle_edge_vectors += [get_vector(point)]
    rectangle_edge_normals = []
    for i in range(len(rectangle_points) - 1):
        rectangle_edge_normals += [get_normal(get_vector((rectangle_points[i + 1][0] - rectangle_points[i][0], rectangle_points[i + 1][1] - rectangle_points[i][1])))]
    rectangle_edge_normals += [get_normal(get_vector((rectangle_points[0][0] - rectangle_points[len(rectangle_points) - 1][0], rectangle_points[0][1] - rectangle_points[len(rectangle_points) - 1][1])))]
    rectangle_edge_normals += [get_vector((circle_centre[0] - closest_point[0], circle_centre[1] - closest_point[1]))]
    axes = rectangle_edge_normals
    vectors = rectangle_edge_vectors
    for axis in axes:
        current_rect_max_x = float('-inf')
        current_rect_min_x = float('inf')
        for vector in vectors:
            current_rect_projection = project_vector(vector, axis)
            if current_rect_projection >= current_rect_max_x:
                current_rect_max_x = current_rect_projection
            if current_rect_projection <= current_rect_min_x:
                current_rect_min_x = current_rect_projection
        current_circle_projection = project_vector(get_vector(circle_centre), axis)
        current_circle_max_x = current_circle_projection + 25
        current_circle_min_x = current_circle_projection - 25
        if current_rect_min_x > current_circle_max_x or current_circle_min_x > current_rect_max_x:
            return False
    return True


def get_minimum_translation_vector(circle_centre, rectangle_points):
    closest_point = get_closest_point(circle_centre, rectangle_points)
    rectangle_edge_vectors = []
    for point in rectangle_points:
        rectangle_edge_vectors += [get_vector(point)]
    rectangle_edge_normals = []
    for i in range(len(rectangle_points) - 1):
        rectangle_edge_normals += [get_normal(get_vector((rectangle_points[i + 1][0] - rectangle_points[i][0], rectangle_points[i + 1][1] - rectangle_points[i][1])))]
    rectangle_edge_normals += [get_normal(get_vector((rectangle_points[0][0] - rectangle_points[len(rectangle_points) - 1][0], rectangle_points[0][1] - rectangle_points[len(rectangle_points) - 1][1])))]
    rectangle_edge_normals += [get_vector((circle_centre[0] - closest_point[0], circle_centre[1] - closest_point[1]))]
    axes = rectangle_edge_normals
    for axis in axes:
        axis.normalize()
    vectors = rectangle_edge_vectors
    minimum_translation_vector = Vector(axes[0].x, axes[0].y)
    minimum_translation_vector.magnitude = float('inf')
    current_minimum_translation_vector = Vector(axes[0].x, axes[0].y)
    current_minimum_translation_vector.magnitude = float('inf')
    for axis in axes:
        current_rect_max_x = float('-inf')
        current_rect_min_x = float('inf')
        for vector in vectors:
            current_rect_projection = project_vector(vector, axis)
            if current_rect_projection >= current_rect_max_x:
                current_rect_max_x = current_rect_projection
            if current_rect_projection <= current_rect_min_x:
                current_rect_min_x = current_rect_projection
        current_circle_projection = project_vector(get_vector(circle_centre), axis)
        current_circle_max_x = current_circle_projection + 25
        current_circle_min_x = current_circle_projection - 25
        current_minimum_translation_vector = axis
        current_minimum_translation_vector.magnitude = abs(current_circle_min_x - current_rect_max_x)
        if current_minimum_translation_vector.magnitude <= minimum_translation_vector.magnitude:
            minimum_translation_vector = axis
            minimum_translation_vector.magnitude = current_minimum_translation_vector.magnitude
    return minimum_translation_vector


if __name__ == "__main__":
    pygame.init()
    display = pygame.display.set_mode((500, 500))
    rectangle_points_main = [(250, 250), (300, 250), (300, 300), (250, 300)]
    circle_centre_main = (0, 0)
    clock = pygame.time.Clock()
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()
            circle_centre_main = (pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1])
        display.fill((255, 255, 255))
        if is_collision(circle_centre_main, rectangle_points_main):
            pygame.draw.circle(display, (255, 0, 0), circle_centre_main, 25)
            minimum_translation_vector_main = get_minimum_translation_vector(circle_centre_main, rectangle_points_main)
            dx = minimum_translation_vector_main.magnitude * minimum_translation_vector_main.direction_x
            dy = minimum_translation_vector_main.magnitude * minimum_translation_vector_main.direction_y
            rectangle_points_main = [(rectangle_points_main[0][0] + dx, rectangle_points_main[0][1] + dy),
                                     (rectangle_points_main[1][0] + dx, rectangle_points_main[1][1] + dy),
                                     (rectangle_points_main[2][0] + dx, rectangle_points_main[2][1] + dy),
                                     (rectangle_points_main[3][0] + dx, rectangle_points_main[3][1] + dy)]
        else:
            pygame.draw.circle(display, (0, 0, 255), circle_centre_main, 25)
        pygame.draw.rect(display, (0, 255, 0), (rectangle_points_main[0][0], rectangle_points_main[0][1], 50, 50))
        dt = clock.tick(60)
        dt /= 1000
        pygame.display.update()
© www.soinside.com 2019 - 2024. All rights reserved.