image image


image

The Nature of Code: Newtons Bewegungsgesetze in Python simuliert

Im zweiten Kapitel seines Buches The Nature of Code simuliert Daniel Shiffman die Newtonschen Gesetze – genauer: die ersten beiden – in Processing (Java) und portiert diese Simulation aktuell nach P5.js, dem JavaScript-Mode von Processing. Mich hat das dazu animiert, diese Skripte nach Python zu portieren, und zwar mit Hilfe der Python Arcade Bibliothek und meinem PVector-Modul.

Shiffman simuliert ein Objekt, auf das sowohl die Gravitation als auch ein Wind von der Seite einwirken. Dafür greift er auf die schon in Kapitel 1 verwendete Klasse Mover zurück und ergänzt sie entsprechend. In Python sieht das so aus:

class Mover():
    
    def __init__(self, m, x, y):
        self.mass = m
        self.radius = 16
        self.color = (239, 242, 63)
        self.location = PVector(x, y)
        self.velocity = PVector(0, 0)
        self.acceleration = PVector(0, 0)
    
    def draw(self):
        arcade.draw_circle_filled(self.location.x, self.location.y, self.radius, self.color)
        arcade.draw_circle_outline(self.location.x, self.location.y, self.radius, arcade.color.BLACK)
    
    def apply_force(self, force):
        f = PVector.sdiv(force, self.mass)
        self.acceleration.add(f)
    
    def update(self):
        self.velocity.add(self.acceleration)
        self.location.add(self.velocity)
        self.acceleration.mult(0)
    
        if (self.location.x >= WIDTH - self.radius):
            self.location.x = WIDTH - self.radius
            self.velocity.x *= -1
        elif (self.location.x <= self.radius):
            self.location.x = self.radius
            self.velocity.x *= -1
        if (self.location.y >= HEIGHT - self.radius):
            self.location.y = HEIGHT - self.radius
            self.velocity.y *= -1
        elif (self.location.y <= self.radius):
            self.location.y = self.radius
            self.velocity.y *= -1

Neu ist eigentlich nur die Methode apply_force(), und um diese zu implementieren, mußte ich auch mein PVector-Modul erweitern. Obwohl ich aktuell nur die Klassen-Methode sdiv() benötige, die einen Vektor durch einen Skalar teilt und das Ergebnis einem neuen Vektor zuteilt, habe ich der Vollständigkeit halber auch gleich die Schwestermethode smult() implementiert. Diese multipliziert einen Vektor mit einem Skalar und weist das Resultat einem neuen Vektor zu:

    # Multiplikation mit einem Skalar
    def smult(v, n):
        x = v.x*n
        y = v.y*n
        result = PVector(x, y)
        return(result)

    # Division mit einem Skalar
    def sdiv(v, n):
        if n != 0:
            x = v.x/n
            y = v.y/n
            result = PVector(x, y)
            return(result)
        else:
            print("Error. Divison durch Null!")

Die Hauptklasse MyWindow ist für die Ausführung der Simulation verantwortlich:

class MyWindow(arcade.Window):
    
    def __init__(self):
        super().__init__(WIDTH, HEIGHT, TITLE)
        arcade.set_background_color((149, 224, 245))
        self.mover = Mover(1, 40, HEIGHT - 40)
        self.wind = PVector(0.01, 0.0)
        self.gravity = PVector(0, -0.1)
    
    def on_draw(self):
        arcade.start_render()
        self.mover.draw()
    
    def on_update(self, delta_time):
        self.mover.apply_force(self.wind)
        self.mover.apply_force(self.gravity)
        self.mover.update()

Gewöhnungsbedürftig – zumindest für mich – ist hier nur, daß, bedingt dadurch, daß bei der Python-Arcade-Bibliothek der Ursprung des Koordinatensystems in der rechten, unteren Ecke liegt, die y-Komponente für die Gravitation tatsächlich negativ sein muß, sonst bewegt sie den Mover nach oben.

Wenn man nun Shiffman folgt (und das wollte ich ja), ist der nächste Schritt, viele Mover-Objekte zu erzeugen und sie auf die Reise zu schicken. Sie sollten eine zufällige Masse, besitzen, die Größe der Kreise sollte von der Masse abhängen und zufällig ausgewählte Farben aus meiner Coding Train Farbpalette wollte ich auch verwenden. Daher drucke ich dieses Skript hier fast vollständig ab. Zuerst die Importe und einige Konstanten:

import arcade
import random
from pvector import PVector

WIDTH = 400
HEIGHT = 400
TITLE = "Forces for many Objects"
NO_MOVERS = 12

colorlist = [(239, 242, 63), (198, 102, 230), (151, 87, 165), (129, 122, 198), (98, 199, 119), (239, 242, 63)]

Dann die Klasse Mover. Hier hat sich gegenüber obigem Beispiel nur der Konstruktor verändert, die Methoden draw(), apply_force() und update() isnd unverändert geblieben:

class Mover():
    
    def __init__(self, m, x, y):
        self.mass = m
        self.radius = m*5
        self.color = random.choice(colorlist)
        self.location = PVector(x, y)
        self.velocity = PVector(0, 0)
        self.acceleration = PVector(0, 0)

Einmal wird dem Konstruktor nun die Masse übergeben und der Radius des Kreises wird abhängig von der Masse gezeichnet. Und die Methode random.choice() wird verwendet, um aus der colorlist eine zufällige Farbe auszuwählen.

In der Hauptklasse MyWindow wird eine Liste der einzelnen Mover in einer Schleife erzeugt und auch die Methoden on_draw() und on_update() verwenden je eine Schleife, um die Liste abzuarbeiten, respektive die Objekte zu zeichnen:

class MyWindow(arcade.Window):
    
    def __init__(self):
        super().__init__(WIDTH, HEIGHT, TITLE)
        arcade.set_background_color((149, 224, 245))
        self.movers = []
        for _ in range(NO_MOVERS):
            self.movers.append(Mover(random.uniform(0.5, 2.5), 15, HEIGHT - 15))
        self.wind = PVector(0.01, 0.0)
        self.gravity = PVector(0, -0.1)
    
    def on_draw(self):
        arcade.start_render()
        for mover in self.movers:
            mover.draw()
    
    def on_update(self, delta_time):
        for mover in self.movers:
            mover.apply_force(self.wind)
            mover.apply_force(self.gravity)
            mover.update()

Dann folgen noch die in jedem Arcade-Skript notwendigen zwei Zeilen, um die Hauptschleife abzuarbeiten:

MyWindow()
arcade.run()

In obigem Screenshot kann man gerade noch erkennen, daß die einzelnen Kreise einer Art Parabel folgen. In Bewegung sieht man das deutlicher und sie sieht richtig harmonisch aus.

Wie immer gibt es die beiden Sketche e_2_1_forces.py und e_2_2_many_forces.py in meinem GitHub-Repositorium zum Nachvollziehen und -programmieren. Habt Spaß damit.


(Kommentieren) 

image image



Über …

Der Schockwellenreiter ist seit dem 24. April 2000 das Weblog digitale Kritzelheft von Jörg Kantel (Neuköllner, EDV-Leiter Rentner, Autor, Netzaktivist und Hundesportler — Reihenfolge rein zufällig). Hier steht, was mir gefällt. Wem es nicht gefällt, der braucht ja nicht mitzulesen. Wer aber mitliest, ist herzlich willkommen und eingeladen, mitzudiskutieren!

Alle eigenen Inhalte des Schockwellenreiters stehen unter einer Creative-Commons-Lizenz, jedoch können fremde Inhalte (speziell Videos, Photos und sonstige Bilder) unter einer anderen Lizenz stehen.

Der Besuch dieser Webseite wird aktuell von der Piwik Webanalyse erfaßt. Hier können Sie der Erfassung widersprechen.

Diese Seite verwendet keine Cookies. Warum auch? Was allerdings die iframes von Amazon, YouTube und Co. machen, entzieht sich meiner Kenntnis.


Werbung

Diese Spalte wurde absichtlich leergelassen!


Werbung


image  image  image
image  image  image


image