image image


image

Ein Partikelsystem in Processing.py (1)

Heute möchte ich mal wieder den Spuren Daniel Shiffmans folgen, die er in seinem wunderbaren Buch »The Nature of Code« hinterlassen hat, und ein Partikelsystem programmieren, wie er es im 4. Kapitel (Seiten 143 - 188) vorstellt. Doch zuerst einmal: Was ist eigentlich ein Partikelsystem? Ich habe die Wikipedia befragt:

Ein Partikelsystem ist eine Funktion im Bereich der Computeranimation, mit der sich eine große Anzahl von Objekten animieren läßt. Partikelsysteme werden beispielsweise eingesetzt, um Feuer-, Rauch- oder Explosionseffekte zu simulieren. […] Bei einem Partikelsystem werden von einem so genannten Emitter Partikel ausgestoßen, deren Bewegung über zahlreiche Parameter beeinflußt werden kann.

Okay, ich möchte erst einmal ein einfaches Partikelsystem bauen, dessen Emitter Partikel ausstößt, die den Gesetzen der Gravitation folgend nach unten fallen und dabei langsam verblassen und schließlich »sterben«. Dazu habe ich in einem eigenen Reiter die Klasse Particle definiert:

# coding=utf-8

class Particle(object):
    
    def __init__(self, l):
        self.acceleration = PVector(0, 0.05)
        self.velocity = PVector(random(-1, 1), random(-2, 0))
        self.location = l.get()
        self.lifespan = 255.0
    
    def run(self):
        self.update()
        self.display()
    
    def update(self):
        self.velocity.add(self.acceleration)
        self.location.add(self.velocity)
        self.lifespan -= random(0.5, 1.0)
    
    def display(self):
        stroke(0, self.lifespan)
        fill(107, 142, 35, self.lifespan)
        ellipse(self.location.x, self.location.y, 20, 20)
        
    def isDead(self):
        if self.lifespan <= 0:
            return True
        else:
            return False

Der Konstruktor bekommt die Startposition des Partikels (l) übergeben und initialisiert sich eine eigene Acceleration und eine eigene (zufällige) Velocity. Zusätzlich initialisiert er eine Lebensspanne von 255.0. Diese Zahl wurde ausgewählt, weil man damit ohne Verrenkungen den Alpha-Kanal langsam erhöhen kann, indem man die Lebensspanne verringert. Dies wird neben anderem in der Methode update() vorgenommen, die bei jedem Durchlauf zufällig eine Zahl zwischen 0.5 und 1.0 bestimmt und diese von der Lebensspanne abzieht.

Die beiden anderen Aufrufe in update() folgen einem gewohnten Ritual. Die Beschleunigung (àcceleration) wird zur Geschwindigkeit (velocity) hinzugezählt und dann wird die Geschwindigkeit zum Ortsvektor addiert.

Die Methode display() zeichnet einfach nur einen Kreis an der location, dessen Durchsichtigkeit bei jedem Durchlauf zunimmt.

Dann ist da noch die Methode isDead(), die nachprüft, ob die Lebensspanne überschritten ist. Und weil ich das Hauptprogramm möglichst übersichtlich halten wollte, gibt es noch die Methode run(), die einfach nur die beiden Methoden update() und display() hintereinander aufruft.

Das Hauptprogramm sieht dann wie folgt aus:

from particles import Particle

loc = PVector(250, 70)
particles = []

def setup():
    size(500, 500)
    this.surface.setTitle("Partikelsysteme 1")

def draw():
    background(235)
    particles.append(Particle(loc))
    for i in range(len(particles) - 1, -1, -1):
        particles[i].run()
        if particles[i].isDead():
            particles.pop(i)

Es ist weitestgehend selbsterklärend: Bei jedem Durchlauf in draw() wird ein neues Partikel erzeugt und in die Welt hinausgestoßen, das heißt, an die Liste der Partikel angehängt. Und wenn ein Partikel gestorben ist, wird es aus der Liste der Partikel wieder entfernt – dies ist der Grund, warum die Schleife rückwärts durchlaufen wird.

image

Das ist ja schon ganz schön, aber ich wollte mehr: Ich wollte nicht nur Kreise, sondern auch rechteckige Partikel, die sich zusätzlich auch noch um die eigene Achse drehen. In Python muß man dafür nicht die ganze Klasse neu programmieren, sondern man kann eine Unterklasse bauen (die ich RectParticle genannt habe) und die erst einmal alle Eigenschaften von Particle erbt, bis auf die, die überschrieben werden.

# coding=utf-8

from particles import Particle

class RectParticle(Particle):
    
    def __init__(self, l):
        Particle.__init__(self, l)
        # super(RectParticle, self).__init__(l)
        rectMode(CENTER)
        self.rota = PI/3
    
    def display(self):
        stroke(0, self.lifespan)
        fill(204, 53, 100, self.lifespan)
        with pushMatrix():
            translate(self.location.x, self.location.y)
            rotate(self.rota)
            rect(0, 0, 20, 20)
        self.rota += random(0.02, .10)

Dadurch ist sie recht klein geraten. Der Konstruktor erbt erst einmal alles von der Oberklasse Particle mit dem Aufruf Particle.__init__(self, l) (die auskommentierte Zeile super(RectParticle, self).__init__(l) hat den gleichen Effekt, ich weiß aber bis heute nicht, welche dieser beiden Zeilen die kanonische Form des Oberklassenaufrufes ist).

Da die Rechtecke (genauer die Quadrate) sich um ihre eigene Achse drehen sollen, habe ich den rectMode auf CENTER gesetzt und die Rotation (rota) auf PI/3 initialisiert.

Nur die Methode display() mußte jetzt noch den Quadraten angepaßt werden: Mit pushMatrix() wird jeder Partikel auf seinen eigenen Ursprung gesetzt, rotiert und gezeichnet. Zum Schluß wird noch für den darauffolgenden Partikel eine neue Rotation festgelegt.

Im Hauptprogramm hat sich nur die draw()-Funktion geändert, denn hier wird nun mit Hilfe der Zufallsfunktion etwa die Hälfte der Partikel zu Kreisen und die andere Hälfte zu Quadraten erklärt:

def draw():
    background(235)
    ch = random(10)
    if ch <= 5:
        particles.append(Particle(loc))
    else:
        particles.append(RectParticle(loc))
    for i in range(len(particles) - 1, -1, -1):
        particles[i].run()
        if particles[i].isDead():
            particles.pop(i)

Das ergibt den Screenshot oben. Aber ich wollte noch mehr: Ich wollte eine Klasse ParticleSystem schaffen, die die Steuerung über die Partikel übernimmt und das Hauptprogramm von allen Einzelheiten über diese Steuerung befreit. Außerdem sollte bei jedem Mausklick ein neues Partikelsystem geschaffen werden. Dazu erst einmal das Hauptprogramm, das durch diese Änderung wieder sehr kurz geraten ist:

from particlesystem import ParticleSystem

systems = []

def setup():
    size(640, 480)
    this.surface.setTitle("Partikelsysteme 2")

def draw():
    background(235)
    for ps in systems:
        ps.addParticle()
        ps.run()

def mousePressed():
    systems.append(ParticleSystem(PVector(mouseX, mouseY)))

Mit jedem Mausklick wird ein neues Partikelsystem dort angelegt, wo mit der Maus geklickt wurde, und die gesamte Steuerung wurde einfach in die Klasse ParticleSystem ausgelagert:

# coding=utf-8

from particles import Particle
from rectparticles import RectParticle

class ParticleSystem(object):
    
    def __init__(self, l):
        self.location = l.get()
        self.particles = []
    
    def addParticle(self):
        ch = random(10)
        if ch <= 5:
            self.particles.append(Particle(self.location))
        else:
            self.particles.append(RectParticle(self.location))
        
    def run(self):
        for i in range(len(self.particles) - 1, -1, -1):
            self.particles[i].run()
            if self.particles[i].isDead():
                self.particles.pop(i)

Die beiden Aktionen aus der draw()-Funktion des vorherigen Hauptprogramms werden nun mit den Methoden addParticle() und run() ausgeführt. Neu ist hier eigentlich nichts. Das Schöne an dieser neuen Klasse ParticleSystem ist, daß die Klassen Particle und RectParticle unverändert übernommen werden können.

Und der Screenshot ganz oben zeigt, wie ein Lauf mit mehreren Partikelsystemen aussehen könnte.


(Kommentieren) 

image image



Über …

Der Schockwellenreiter ist seit dem 24. April 2000 das Weblog digitale Kritzelheft von Jörg Kantel (Neuköllner, EDV-Leiter, 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


Werbung


image  image  image
image  image  image


image