1

so let me preface this by saying that I am only moderately experienced with Python and pygame. With that being said, I wanted to implement a collision system using SAT (Separating Axis Thoerem) so my player could collide with rotated rectangles. I have created code that will take the local coordinates of the rectangle and apply a rotation matrix so I can get the world coordinates of the rotated rectangle. Here is that code, it works pretty well.

def getRotation(angle):
    angle = np.radians(angle)
    return np.array(
        [
            [np.cos(angle), -np.sin(angle), 0],
            [np.sin(angle), np.cos(angle), 0],
            [0, 0, 1],
        ]
    )


def getTranslation(Tx, Ty):
    return np.array([[1, 0, Tx], [0, 1, Ty], [0, 0, 1]])


def getWorldCoord(points, Tx, Ty, angle):
    transMat = getTranslation(Tx, Ty)
    rotMat = getRotation(angle)
    A = transMat @ rotMat @ np.linalg.inv(transMat)
    for i in range(points.shape[0]):
        A.dot(points[i, :])

    points_world = A.dot(points.T)
    return points_world


Now, my issue is implementing SAT and also getting the player to stop when collided (that is, finding the Minimum translation vector or MTV). And this is the part I am struggling with. The collision bool turns True when it gets close to the rectangle but not hitting. The MTV also has this odd behavior where it sort of bounces off the rectangle very unnaturally. I wanted it in such a way that the rotated rectangle behaves like a solid wall. Any help on this would be greatly appreciated, I've been at this for almost a week now.

Here is the code for my SAT collision system.

def normalize(v):
    norm = np.sqrt(v[0] ** 2 + v[1] ** 2)
    return (v[0] / norm, v[1] / norm)


def arrayToVector(p):
    vecs = []
    for i in range(p.shape[0]):
        vecs.append((p[0][i], p[1][i]))
    return vecs


def dot(a, b):
    return a[0] * b[0] + a[1] * b[1]


def orthogonal(v):
    return (v[1], -v[0])


def edge_direction(p0, p1):
    return (p1[0] - p0[0], p1[1] - p1[0])


def vertices_to_edges(vertices):
    return [
        edge_direction(vertices[i], vertices[(i + 1) % len(vertices)])
        for i in range(len(vertices))
    ]


def project(vertices, axis):
    dots = [dot(vertex, axis) for vertex in vertices]
    return [min(dots), max(dots)]


def contains(n, range_):
    a = range_[0]
    b = range_[1]
    if b < a:
        a = range_[1]
        b = range_[0]
    return (n >= a) and (n <= b)


def overlap(a, b):
    if contains(a[0], b) or contains(a[1], b) or contains(b[0], a) or contains(b[1], a):
        return True
    return False


# region (SAT_coll_fxn)
# def SAT_collision(vertices_a, vertices_b):
#     edges_a = vertices_to_edges(vertices_a)
#     edges_b = vertices_to_edges(vertices_b)
#     proj_a = [float("inf"), float("-inf")]
#     proj_b = [float("inf"), float("-inf")]
#     overlap_val = float("inf")

#     edges = edges_a + edges_b

#     axes = [normalize(orthogonal(edge)) for edge in edges]

#     for i in range(len(axes)):
#         proj_a = project(vertices_a, axes[i])
#         proj_b = project(vertices_b, axes[i])
#         overlapping = overlap(proj_a, proj_b)
#         overlap_val = min(proj_a[1], proj_b[1]) - max(proj_a[0], proj_b[0])
#         if not overlapping:
#             return False
#     return True
# endregion


class SAT_Collision:
    def __init__(self, poly1, poly2):
        self.poly1 = poly1
        self.poly2 = poly2
        self.collide = False
        self.overlap_val = float("inf")

    def collision_dect(self):
        edges_a = vertices_to_edges(self.poly1)
        edges_b = vertices_to_edges(self.poly2)
        edges = edges_a + edges_b
        axes = [normalize(orthogonal(edge)) for edge in edges]

        for i in range(len(axes)):
            proj_a = project(self.poly1, axes[i])
            proj_b = project(self.poly2, axes[i])
            overlapping = overlap(proj_a, proj_b)
            self.overlap_val = min(proj_a[1], proj_b[1]) - max(proj_a[0], proj_b[0])
            if not overlapping:
                return False

        d = vec()

        return True

Here is my main loop. I have my MTV code commented out temporarily.

rect1 = Rectangle(10, 10, 50, 30)
rect2 = pg.Rect(100, 100, 10, 100)
rectangle_dragging = False

w = rect2.width
h = rect2.height
Tx = rect2.left
Ty = rect2.top
x0 = Tx
y0 = Ty
angle = 333.44
points = np.array([[x0, y0, 1], [x0 + w, y0, 1], [x0, y0 + h, 1], [x0 + w, y0 + h, 1]])
rect_coor = getWorldCoord(points, Tx, Ty, angle)
rect_coor_vec = arrayToVector(rect_coor)
rect_center = rect2.centerx

line_coor = [(200, 200), (100, 100)]

collision_font = pg.font.Font(None, 30)
collision_message = "collided!"
collision_render = collision_font.render(collision_message, True, BLACK)
collision_text_rect = collision_render.get_rect()
collision_text_rect.center = (WIDTH - 100, HEIGHT - 100)


# - mainloop -

clock = pg.time.Clock()

running = True

while running:
    screen.fill(LIGHTGREY)
    surf = pg.Surface(rect2.size).convert_alpha()

    # - events -

    for event in pg.event.get():
        if event.type == pg.QUIT:
            running = False

        elif event.type == pg.MOUSEBUTTONDOWN:
            if event.button == 1:
                if rect1.rect.collidepoint(event.pos):
                    rectangle_dragging = True
                    mouse_x, mouse_y = event.pos
                    offset_x = rect1.rect.x - mouse_x
                    offset_y = rect1.rect.y - mouse_y

        elif event.type == pg.MOUSEBUTTONUP:
            if event.button == 1:
                rectangle_dragging = False

        elif event.type == pg.MOUSEMOTION:
            if rectangle_dragging:
                mouse_x, mouse_y = event.pos
                rect1.rect.x = mouse_x + offset_x
                rect1.rect.y = mouse_y + offset_y

    # - updates (without draws) -
    player_coor = [
        (rect1.rect.left, rect1.rect.top),
        (rect1.rect.right, rect1.rect.top),
        (rect1.rect.left, rect1.rect.bottom),
        (rect1.rect.right, rect1.rect.bottom),
    ]
    collision_ob = SAT_Collision(player_coor, rect_coor_vec)
    collision_chk = collision_ob.collision_dect()
    overlap_val = collision_ob.overlap_val

    if collision_chk:
        screen.blit(collision_render, collision_text_rect)
        # d = vec(rect2.left - rect1.rect.left, rect2.top - rect1.rect.top)
        # s = np.sqrt(d.x * d.x + d.y * d.y)
        # rect1.rect.left -= 10 * d.x / s
        # rect1.rect.top -= 10 * d.y / s

    # empty

    # - draws (without updates) -

    pg.draw.rect(surf, (RED), rect2)

    rotSurf = pg.transform.rotate(surf, angle)

    screen.blit(rotSurf, (Tx, Ty))
    pg.draw.rect(screen, BLUE, rect1)
    # pg.draw.line(screen, RED, line_coor[1], line_coor[0], 3)
    # pg.draw.rect(screen, RED, rect2)
    pg.display.flip()
    rect_center = rect2.center
    rect_coor = getWorldCoord(points, Tx, Ty, angle)

    # - constant game speed / FPS -

    clock.tick(FPS)


# - end -

pg.quit()


Here is an image of the results. Note that this is a test environment just to try out the code. I didn't want to alter the real game code.

Collision bool turned True even though it is not colliding. It only does this when its close.

Thanks!

Kingsley
  • 14,398
  • 5
  • 31
  • 53
MBA20
  • 73
  • 5

0 Answers0