• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Problem z symulacją fizyki

VPS Starter Arubacloud
+1 głos
301 wizyt
pytanie zadane 6 czerwca 2022 w Python przez NEFOS Początkujący (320 p.)

Witam.

Mam problem z napisanym przeze mnie programem mającym symulować prostą fizykę 2D. Napisałem to w Pythonie, a do wyświetlania grafiki użyłem pygame. Na ten moment program jest w stanie poprawnie obliczyć zachowanie koła, na który działa grawitacja, a my jesteśmy nakładać siły na te koło poprzez m.in. przeciąganie jego po ekranie. Dodałem również opcję dodania nowych obiektów (kolejnych kół), jednak tutaj zaczynają się problemy. Kiedy obecnych jest więcej niż jeden obiekt, to zaczynają one dziwnie reagować na grawitację, która zaczyna być w jakiś całkowicie dziwny sposób liczona, bo choć obiekt, który stworzyłem najwcześniej dobrze reaguje na skrypt grawitacji, to następny praktycznie nie ma żadnego przyspieszenia spowodowanego grawitacją, a już każdy kolejny reaguje na grawitację coraz "lepiej" (jego przyspieszenie grawitacyjne jest coraz szybsze). Naprawdę nie mam pojęcia o co może chodzić. Przypuszczam jedynie, iż może mieć to związek z jakąś ułomnością biblioteki pygame, lub samego Pythona, jednak jest również prawdopodobne, że to ja popełniłem jakiś błąd.

Z góry dziękuję za pomoc.

Kod programu poniżej:

import pygame as pg
import math as m

gravity = 0.98

pg.init()

window = pg.display.set_mode((1600, 900))
clock = pg.time.Clock()
winx, winy = window.get_width(), window.get_height()

run = True

pW = False
pS = False
pA = False
pD = False

left_mouse_pressed = False

# CLASSES
class PhysicsObject:
    x, y = None, None
    acceleration = 0
    mass = None
    velocity = 0
    vel_vector = [0, 0]
    gravity = 0
    friction = 0
    max_velocity = 8
    mode = None
    shape = None
    bounciness = 0
    forces = [[0, 0.98], [0, 0], [0, 0]]
    net_force = [0, 0]
    size = 50
    grounded = False
    # drag
    dragging = False

    def __init__(self, x, y, mass, mode, shape):
        self.x = x
        self.y = y
        self.mass = mass
        self.mode = mode
        self.shape = shape

    def push(self, vx, vy):
        self.forces.append([vx/10, vy/10])

    def drag_start(self):
        mx, my = pg.mouse.get_pos()
        if self.size ** 2 >= ((mx - self.x) ** 2 + (my - self.y) ** 2):
            self.dragging = True

    def drag_end(self):
        self.dragging = False
        self.forces[2] = [0, 0]

    def drag(self):
        mx, my = pg.mouse.get_pos()
        if left_mouse_pressed and self.dragging:
            self.forces[2] = [((mx - self.x)/100), ((my - self.y)/100)]

    def check_ground_collison(self):
        if self.shape == 'circle':
            if self.y + self.size >= winy:
                self.grounded = True
            else:
                self.grounded = False

    def collision_ground(self):
        if self.shape == 'circle':
            if self.grounded:
                if self.net_force[1] > 0:
                    self.forces[1] = [0, -self.net_force[1]]
                self.vel_vector[1] = 0
                self.y = winy - self.size
            else:
                if len(self.forces) > 2:
                    self.forces[1] = [0, 0]

    def border_collision(self):
        if self.shape == 'circle':
            if self.x - self.size < 0:
                self.forces[2] = [0, self.forces[2][1]]
                self.vel_vector[0] = 0
                self.x = self.size
            elif self.x + self.size > winx:
                self.forces[2] = [0, self.forces[2][1]]
                self.vel_vector[0] = 0
                self.x = winx - self.size

    def update(self):
        if self.mode == 'top':
            self.mass = self.mass
            # placeholder
        elif self.mode == 'front':
            netx = 0
            nety = 0
            for x in range(len(self.forces)):
                netx = netx + self.forces[x][0]
                nety = nety + self.forces[x][1]
            self.net_force[0] = netx
            self.net_force[1] = nety
            self.vel_vector[0] = self.vel_vector[0] + self.net_force[0]
            self.vel_vector[1] = self.vel_vector[1] + self.net_force[1]
            self.x = self.x + self.vel_vector[0]
            self.y = self.y + self.vel_vector[1]

            if len(self.forces) > 3:
                for x in range(3, len(self.forces)):
                    self.forces.pop(-1)

    def debug(self):
        pg.draw.line(window, (255, 0, 0), (self.x, self.y), (self.x + (self.net_force[0] * 100), self.y + (self.net_force[1] * 100)), width=2)  # net force
        pg.draw.line(window, (0, 255, 0), (self.x, self.y), (self.x + (self.vel_vector[0] * 5), self.y + (self.vel_vector[1] * 5)), width=2)    # vector of velocity

    def draw(self):
        if self.shape == 'rect':
            pg.draw.rect(window, (0, 0, 255), (self.x, self.y, 100, 100))
        elif self.shape == 'circle':
            pg.draw.circle(window, (0, 0, 255), (self.x, self.y), self.size)


ball = []

ball.append(PhysicsObject(100, 100, 1, 'front', 'circle'))

while run:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            run = False
            pg.quit()

        if event.type == pg.KEYDOWN:
            if event.key == pg.K_w:
                pW = True
            if event.key == pg.K_s:
                pS = True
            if event.key == pg.K_a:
                pA = True
            if event.key == pg.K_d:
                pD = True
        if event.type == pg.KEYUP:
            if event.key == pg.K_w:
                pW = False
            if event.key == pg.K_s:
                pS = False
            if event.key == pg.K_a:
                pA = False
            if event.key == pg.K_d:
                pD = False

        #1 - left click
        #2 - middle click
        #3 - right click
        #4 - scroll up
        #5 - scroll down

        if event.type == pg.MOUSEBUTTONDOWN:
            mx, my = pg.mouse.get_pos()
            if event.button == 3:
                ball.append(PhysicsObject(mx, my, 1, 'front', 'circle'))
            if event.button == 1:
                for x in ball:
                    x.drag_start()
                left_mouse_pressed = True

        if event.type == pg.MOUSEBUTTONUP:
            if event.button == 1:
                for x in ball:
                    x.drag_end()
                left_mouse_pressed = False


    if pW:
        for x in ball:
            x.push(0, -10)
    if pS:
        for x in ball:
            x.push(0, 10)
    if pA:
        for x in ball:
            x.push(-10, 0)
    if pD:
        for x in ball:
            x.push(10, 0)

    for x in ball:
        x.update()
        x.check_ground_collison()
        x.collision_ground()
        x.border_collision()
        x.drag()
        x.draw()
        x.debug()


    pg.display.flip()
    window.fill((0, 0, 0))
    clock.tick(60)

 

komentarz 6 czerwca 2022 przez Oscar Nałogowiec (29,290 p.)
Mogłbyś trochę skomentować ten kod bo niewiele rozumiem.

Po co aż trzy pary składowych siły.

Jak występuje zderzenie to zmiania się znak składowej prędkości a nie siły.

Zdefiniuj jakoś krok czasu, bo może być tak, że od razu wylatuje poza zakres/ekran. No i jakoś lepiej wtedy będzie ze zgodnością jednostek miar.
komentarz 6 czerwca 2022 przez NEFOS Początkujący (320 p.)

Troszkę uporządkowałem ten kod. Zmiany czysto kosmetyczne + parę komentarzy, by łatwiej się było odnaleźć.

import pygame as pg
import math as m

pg.init()

window = pg.display.set_mode((1600, 900))
clock = pg.time.Clock()
winx, winy = window.get_width(), window.get_height()

run = True

pW = False
pS = False
pA = False
pD = False

left_mouse_pressed = False


# CLASSES
class PhysicsObject:
    x, y = None, None
    mass = None
    vel_vector = [0, 0]
    #bounciness = 0
    forces = [[0, 0.98], [0, 0], [0, 0]]  # siły zarezerwowane kolejno dla: grawitacji, reakcji podłoża i przeciągania obiektu - każda kolejna będzie dodawana przez append
    net_force = [0, 0]
    size = 50
    grounded = False
    dragging = False

    def __init__(self, x, y, mass):
        self.x = x
        self.y = y
        self.mass = mass

    def push(self, vx, vy):
        self.forces.append([vx / 10, vy / 10])

    def drag_start(self):   # funkcja ta odpowiada za uchwycenie chwili, w której zaczynamy przeciąganie obiektu
        mx, my = pg.mouse.get_pos()
        if self.size ** 2 >= ((mx - self.x) ** 2 + (my - self.y) ** 2):
            self.dragging = True

    def drag_end(self):     # funkcja informująca o końcu przeciągania
        self.dragging = False
        self.forces[2] = [0, 0]

    def drag(self):         # funkcja nadająca siłe przeciągania
        mx, my = pg.mouse.get_pos()
        if left_mouse_pressed and self.dragging:
            self.forces[2] = [((mx - self.x) / 100), ((my - self.y) / 100)]

    def check_ground_collison(self): # sprawdzenie kolizji z podłożem
        if self.y + self.size >= winy:
            self.grounded = True
        else:
            self.grounded = False

    def collision_ground(self):     # nadawanie siły reakcji podłoża
        if self.grounded:
            if self.net_force[1] > 0:
                self.forces[1] = [0, -self.net_force[1]]
            self.vel_vector[1] = 0
            self.y = winy - self.size
        else:
            if len(self.forces) > 2:
                self.forces[1] = [0, 0]

    def border_collision(self):     # sprawdzanie kolizji z ścianami bocznymi
        if self.x - self.size < 0:
            self.vel_vector[0] = 0
            self.x = self.size
        elif self.x + self.size > winx:
            self.vel_vector[0] = 0
            self.x = winx - self.size

    def update(self):       # przeliczanie siły wypadkowej, wektora prędkości i zmiana położenia
        netx = 0
        nety = 0
        for x in range(len(self.forces)):
            netx = netx + self.forces[x][0]
            nety = nety + self.forces[x][1]
        self.net_force[0] = netx
        self.net_force[1] = nety
        self.vel_vector[0] = self.vel_vector[0] + self.net_force[0]
        self.vel_vector[1] = self.vel_vector[1] + self.net_force[1]
        self.x = self.x + self.vel_vector[0]
        self.y = self.y + self.vel_vector[1]

        if len(self.forces) > 3:
            for x in range(3, len(self.forces)):
                self.forces.pop(-1)

    def debug(self):    # pokazuje wektory prędkości i siły wypadkowej
        pg.draw.line(window, (255, 0, 0), (self.x, self.y),
                     (self.x + (self.net_force[0] * 100), self.y + (self.net_force[1] * 100)), width=3)  # wektor siły wypadkowej
        pg.draw.line(window, (0, 255, 0), (self.x, self.y),
                     (self.x + (self.vel_vector[0] * 5), self.y + (self.vel_vector[1] * 5)),
                     width=3)  # wektor prędkości

    def draw(self):
        pg.draw.circle(window, (0, 0, 255), (self.x, self.y), self.size)


ball = [PhysicsObject(100, 100, 1)]

while run:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            run = False
            pg.quit()

        if event.type == pg.KEYDOWN:    # taki trochę placeholder na ewentualne wprowadzenie kontroli za pomocą WSAD
            if event.key == pg.K_w:
                pW = True
            if event.key == pg.K_s:
                pS = True
            if event.key == pg.K_a:
                pA = True
            if event.key == pg.K_d:
                pD = True
        if event.type == pg.KEYUP:
            if event.key == pg.K_w:
                pW = False
            if event.key == pg.K_s:
                pS = False
            if event.key == pg.K_a:
                pA = False
            if event.key == pg.K_d:
                pD = False

        # 1 - left click
        # 2 - middle click
        # 3 - right click
        # 4 - scroll up
        # 5 - scroll down

        if event.type == pg.MOUSEBUTTONDOWN:
            mx, my = pg.mouse.get_pos()
            if event.button == 3:
                ball.append(PhysicsObject(mx, my, 1))   # dodawanie obiektów
            if event.button == 1:
                for x in ball:
                    x.drag_start()      # sprawdzanie czy przeciąganie się zaczęło...
                left_mouse_pressed = True

        if event.type == pg.MOUSEBUTTONUP:
            if event.button == 1:
                for x in ball:
                    x.drag_end()        # ...i czy się skończyło
                left_mouse_pressed = False

    for x in ball:      # powtarzanie procedur dla każdego obiektu
        x.check_ground_collison()
        x.collision_ground()
        x.border_collision()
        x.update()
        x.drag()
        x.draw()
        x.debug()

    pg.display.flip()
    window.fill((0, 0, 0))
    clock.tick(60)

Zdefiniuj jakoś krok czasu

Co rozumiesz przez to, bo za bardzo nie wiem o co chodzi?

PS. Chciałbym też dodać, że ten program ma bardziej służyć mi do celów czysto rozwojowych, żebym mógł lepiej zrozumieć niektóre prawa fizyczne, dlatego też mogę nie wiedzieć wszystkiego z tej dziedziny.

PS 2. Zauważyłem też, podczas uruchamiania programu, że te koła spadają z normalnym przyspieszeniem i prędkością tak długo jak te pierwsze nie uderzy jeszcze w podłoże, bo po kolizji tego pierwszego cała reszta nagle traci zarówno prędkość jak i przyspieszenie. Zupełnie jakby każdy z tych obiektów jakoś wpływał na inne. Możliwe, że ma to związek z tym, że wszystkie obiekty zawarłem w tablicy, żebym łatwiej mógł dodawać kolejne, jednak nie rozumiem jak miałoby to działać.

komentarz 7 czerwca 2022 przez Oscar Nałogowiec (29,290 p.)

Co rozumiesz przez to, bo za bardzo nie wiem o co chodzi?

Zmiana prędkość to nie jest działająca siła tylko wyrażenie:

siła * czas / masa.

(Zmiana pędu jest równa popędowi siły)

Inaczej się jednostki nie zgadzają. Oczywiście rozumiem, że przyjąłeś masę = 1 i czas = 1 (wetedy mnożenie i dzielenie można sobie odpuścić), ale warto byłoby nawet to jawnie pokazać w kodzie. Ta jednostka czasu to będzie czas jaki symuluje pojedyncze wykonanie update();

 

Zakładając, że przez mały odcinek czasu siła się nie zmieni można takie uproszczenie przyjąć, oczywiście z rzeczywistości mamy nieskończonie mały odcinek czasu i różnanie całkowe, ale w uproszczeniu numerycznym dajemy skończony odcinek czasu i szeregi zamiast całek.

 

Zdecydowanie odradzam liczenie zderzeń poprzez działające siły - trzeba by dokładnie zamodelować przebieg zderzenia, a wystarczy zastosować prawo odbicia i od razu zmienić znak jednej składowej prędkości (tej prostopadłej do krawędzi/ściany).

W szkolnej fizyce zderzenia traktuje się w kategorii  pęd - energia a nie siły.

I tak siły przy odbiciu pojawiają się na chwilę, potem zanikają więc trochę nie rozumiem, dlaczego ciągle tylko dopisujesz.

To "przeciąganie" to ma polegać na tym, że obiekt zaczyna się "przyciągać do myszki", czy ma być bezpośrednio przez nią "wleczony"?

 

Może najpierw zamodeluj coś prostego np. wahadło lub ciężarek na sprężynie.

Kiedyś dla zabawy napisałem programik (niestety w Javie) symulujący ruch planet układu słonecznego, oczywiście uwzględniajacy tylko wzajemne przyciąganie grawitacyjne. Żadnych zderzeń. To w sumie bardzo proste obliczenia, Mogę wrzucić jeśli jesteś zainteresowany i dasz radę z Javą.

Jeśli chodzi o to PS2 - trochę dziwne, przecież liczysz ruch kadego obiektu oddzielnie, nie masz oddziaływań jednego na inne

komentarz 7 czerwca 2022 przez NEFOS Początkujący (320 p.)
Dzięki za te wskazówki na początku, jednak nimi się zajmę dopiero jak wyjaśnię o co chodzi z tym problemem opisanym w PS 2, bo o to właśnie zadałem pytanie na forum. Masz może jakiś pomysł z czego może to wynikać?

1 odpowiedź

0 głosów
odpowiedź 7 czerwca 2022 przez NEFOS Początkujący (320 p.)

Jakimś cudem udało mi się to ogarnąć.

W funkcji update zmieniłem 

self.vel_vector[0] = self.vel_vector[0] + self.net_force[0]
self.vel_vector[1] = self.vel_vector[1] + self.net_force[1]

na

self.vel_vector = [self.vel_vector[0] + self.net_force[0], self.vel_vector[1] + self.net_force[1]]

i z niewiadomych mi powodów nagle zadziałało i obiekty zaczęły normalnie reagować na grawitację.

Podobnie zrobiłem z funkcją drag i teraz zamiast

self.forces[2] = [((mx - self.x) / 100), ((my - self.y) / 100)]

jest

self.forces = [self.forces[0], self.forces[1], [((mx - self.x) / 100), ((my - self.y) / 100)]]

i dzięki temu mogę przeciągać każdy z obiektów z osobna, a nie jak wcześniej kiedy próbowałem i zamiast działać normalnie to nagle wszystkie koła zaczynały wariować.

Według mnie, pod względem treści kodu to jest to dokładnie to samo, z tym że pierwszy zapis jest krótszy (ale on akurat nie wiedzieć czemu nie działa).

Zamknę temat, ale na początku chciałbym, żeby ktoś mnie oświecił, dlaczego ten dłuższy zapis działa, a ten krótszy już nie.

Podobne pytania

+5 głosów
0 odpowiedzi 281 wizyt
pytanie zadane 19 sierpnia 2022 w Nasze projekty przez wojtek_suchy Mądrala (6,880 p.)
0 głosów
1 odpowiedź 563 wizyt
pytanie zadane 30 sierpnia 2020 w Python przez KumberTwo Dyskutant (8,270 p.)
0 głosów
4 odpowiedzi 423 wizyt
pytanie zadane 18 grudnia 2019 w C i C++ przez czowiek Początkujący (390 p.)

92,451 zapytań

141,261 odpowiedzi

319,073 komentarzy

61,854 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Akademia Sekuraka 2024 zapewnia dostęp do minimum 15 szkoleń online z bezpieczeństwa IT oraz dostęp także do materiałów z edycji Sekurak Academy z roku 2023!

Przy zakupie możecie skorzystać z kodu: pasja-akademia - użyjcie go w koszyku, a uzyskacie rabat -30% na bilety w wersji "Standard"! Więcej informacji na temat akademii 2024 znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...