0

I saw a video by 3blue1brown on calculating pi using just colliding blocks so I tried to implement my own version of this.

The way the simulation works is that 2 blocks are generated on a horizontal line alongside a wall, and the bigger block (which starts at the right) is set in motion and the simulation unfolds using equations to find the final velocity after elastic collisions for both blocks and reversing velocity if the block collides into a wall. The code is shown here:

import time
import pygame
import sys

pygame.init()
WIDTH = 1280
HEIGHT = 800
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Collision Pi Simulation")
clock = pygame.time.Clock()


class Block:
    def __init__(self, mass, x, y, dim, color, initial_velocity):
        self.mass = mass
        self.x = x
        self.y = y
        self.img = pygame.Surface((dim, dim))
        self.dim = dim
        pygame.draw.rect(self.img, color, (0, 0, dim, dim))
        self.velocity = initial_velocity

    def draw(self, screen):
        screen.blit(self.img, (self.x - self.dim, self.y - self.dim))

    def move(self, dt):
        self.x += self.velocity * dt

    def center(self):
        return (self.x + self.dim / 2 - self.dim, self.y + self.dim / 2 - self.dim) # get center of block relative to where it is drawn on screen

    def get_dim(self):
        return self.dim

    def has_collided(self, other_block):
        if (
            self.center()[0] + self.dim / 2
            >= other_block.center()[0] - other_block.get_dim() / 2
        ):
            if (
                self.center()[0] - self.dim / 2
                <= other_block.center()[0] + other_block.get_dim() / 2
            ):
                return True
        return False

    def get_mass(self):
        return self.mass

    def get_velocity(self):
        return self.velocity

    def change_velocity(self, new_velocity):
        self.velocity = new_velocity


def mass_two_final_velocity(m1, m2, v1, v2): #equation for mass two final velocity in elastic collision
    term_one = (2 * m1 * v1) / (m1 + m2)
    term_two = ((m1 - m2) * v2) / (m1 + m2)
    return term_one - term_two


def mass_one_final_velocity(m1, m2, v1, v2): #equation for mass one final velocity in elastic collision
    term_one = ((m1 - m2) * v1) / (m1 + m2)
    term_two = (2 * m2 * v2) / (m1 + m2)
    return term_one + term_two


small_block = Block(1, 400, 700, 50, "BLUE", 0)
big_block = Block(16, 600, 700, 100, "RED", -60)

previous_time = time.perf_counter()
while True:

    dt = time.perf_counter() - previous_time #delta time
    previous_time = time.perf_counter()
    screen.fill("BLACK")
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    if small_block.center()[0] - small_block.get_dim() / 2 <= 200: # check if block has collided with line
        small_block.change_velocity(-small_block.get_velocity())

    if small_block.has_collided(big_block): #check if 2 blocks collided and change velocities appropriately
        small_block.change_velocity(
            mass_one_final_velocity(
                small_block.get_mass(),
                big_block.get_mass(),
                small_block.get_velocity(),
                big_block.get_velocity(),
            )
        )
        big_block.change_velocity(
            mass_two_final_velocity(
                small_block.get_mass(),
                big_block.get_mass(),
                small_block.get_velocity(),
                big_block.get_velocity(),
            )
        )

    small_block.draw(screen)
    big_block.draw(screen)

    small_block.move(dt)
    big_block.move(dt)

    pygame.draw.line(screen, "WHITE", [200, 0], [200, 700]) # draw wall
    pygame.draw.line(screen, "WHITE", [200, 700], [1280, 700]) # draw floor
    pygame.display.update()
    clock.tick(200)

Due to dt being different every time, however, the code just fails to work every time. Sometimes the smaller block just gets stuck in the middle of the line until the big block crashes into it. Other times the small block just gets stuck to the big block which also should not happen. The most common thing however, which occurs even if I remove dt, is that when the small block collides many times and just flies off the screen extremely fast.

How could I fix this problem such that the block never gets past the line or anything like it while still keeping the "physics" part of the simulation so that I get the desired output?

The failed simulation looks like this:

Failed Simulation

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Aayush
  • 84
  • 1
  • 9
  • What do you mean by "remove dt"? – mkrieger1 Jul 20 '22 at 08:54
  • I mean even if I make it frame-dependent(deterministic) the same thing happens. With dt the simulation is not deterministic the way that I implemented it. – Aayush Jul 20 '22 at 09:07
  • I think the problem is that dt eventually is too large so that you are not properly simulating many collisions within one dt. – mkrieger1 Jul 20 '22 at 09:08
  • Have you tried using the .colliderect function instead of creating it yourself? Making the line also a rect-object would be necessary, but this function usually works very reliably. – ductTapeIsMagic Jul 20 '22 at 10:33

0 Answers0