Das Abenteuer P5.js geht weiter: Ein kleines Planetensystem

P5.js
P5.js-Widget
Processing
Creative Coding
Simulation
Autor:in

Jörg Kantel

Veröffentlichungsdatum

30. Mai 2024

Als nächstes Projekt meiner kleinen Tutorialreihe zu P5.js und dem P5.js-Webeditor möchte ich eine kleine Animation eines Planetensystems aus Kreisen und Rechtecken entwickeln. Ich weiß, Planeten sind eher kugelförmig und keine Kisten, aber mein Freund und regelmäßiger Leser aus Bremen hatte mich gebeten, zu zeigen, wie man Shapes in P5.js rotieren kann – und das ist an Kreisen oder Kugeln nahezu unmöglich zu demonstrieren. Außerdem probiere ich auf diesen Seiten erstmalig aus, wie man das P5 Widget für solche Tutorials mehr oder weniger sinnvoll einsetzen kann1. Es ist also auch für mich ein völlig neues Experiment und ich bin gespannt, wie es ausfällt.

Das Tutorial soll also in der Hauptsache zeigen, wie man translate() und rotate() sinnvoll einsetzt, und dazu brauchte ich eben Rechtecke zu Verdeutlichung.

Ich beginne mit einem einfachem System eines Planeten, der seinen Fixstern umkreist, und dieser besitzt einen Trabanten, der wiederum den Planeten umrundet. Der Einfachheit halber habe ich die Akteure Sonne, Erde und Mond genannt.

Zu Beginn des Sketches lege ich erst einmal ein paar Zahlen fest:

const sunDiam = 40;
const earthDiam = 15;
const earthOrbitRadius = 65;

let earthAngle = 0;

Diese Zahlen sind durch keine physikalische Wirklichkeit gedeckt, sondern einfach so lange durch Experimente herausgesucht worden, bis sich eine ansprechende Animation ergab.

Die setup()-Funktion legt einfach nur die Größe des Ausgabefensters fest:

function setup() {
  createCanvas(300, 200);
}

In draw() setze ich den Hintergrund auf schwarz und dann zeichne ich die Sonne in die Mitte des Ausgabefensters:

function draw() {
  background(0, 0, 0);
  
  // Sonne im Zentrum
  translate(width/2, height/2);
  fill(255, 200, 64);
  circle(0, 0, sunDiam);

Die Zeile translate(width/2, height/2) sorgt dafür, daß der Nullpunkt des Koordinatensystem vom linken oberen Rand der Zeichenfläche in die Mitte des Ausgabefensters gelegt wird und so die Sonne mit circle(0, 0, sunDiam) auch genau dort gezeichnet wird. Probiert es aus, der Sketch ist so lauffähig:

Nun zur Erde, die die Sonne umkreist: Zuerst müssen dafür ein paar weitere Variablen im globalen Kontext definiert werden:

const earthDiam = 15;
const earthOrbitRadius = 65;

let earthAngle = 0;

Und dann müssen sie in der draw()-Funktion des Sripts mit Leben gefüllt werden:

// Erde dreht sich um die Sonne
  rotate(earthAngle);
  translate(earthOrbitRadius, 0);
  fill(64, 64, 255);
  circle(0, 0, earthDiam);
  rect(0, 0, earthDiam, earthDiam);

  earthAngle += 0.01;

Wenn Ihr diese Zeilen Code in die draw()-Funktion unterhalb der Sonne einfügt, bekommt Ihr eine blaue Erde, die sich langsam um die Sonne bewegt. Denn mit translate(earthOrbitRadius, 0) wurde das Koordinatensystem erneut verschoben, 180 Pixel von der Sonne entfernt, aber auf der gleichen y-Achse wie das Koordinatensystem der Sonne. Da rotate() vor der Koordinatentransformation aufgerufen wird, dreht sich die Erde noch um die Sonne und das Koordinatensystem der Sonne rotiert. Ein rotate() hinter der Koordinatentransformation würde bewirken, daß sich die Erde um sich selbst dreht – das heißt, daß das Koordinatensystem der Erde um sich selbst rotieren würde.

Das ergibt dann folgendes Bild:

Jetzt wird noch der Mond angehängt. Er braucht ebenfalls ein paar Konstanten und Variablen:

const moonDiam = 5;
const moonOrbitRadius = 25;

let moonAngle = 0;

Und dann kommt innerhalb der draw()-Funktion sein Auftritt:

// Mond dreht sich um die Erde
  rotate(moonAngle);
  translate(moonOrbitRadius, 0);
  fill(192, 192, 80);
  circle(0, 0, moonDiam);

Das Ergebnis könnt Ihr in diesem Skript-Fragment bewundern:

Durch diese Koordinatentransformation steht der Mond im gleichen Verhältnis zur Erde wie die Erde zur Sonne, der Ursprung des Koordinatensystems liegt nun 25 Pixel vom Erdmittelpunkt entfernt. Natürlich rotiert in diesen Zeilen das Koordinatensystem der Erde, damit der Eindruck entsteht, daß der Mond um die Erde kreist.

Das alles funktioniert natürlich nur, weil bei jedem erneuten Durchlauf der draw()-Funktion das Koordinatensystem jeweils neu zurückgesetzt wird, also alle bisherigen Transformationen »vergessen« (neu überschrieben) werden.

Nun kann man bei Kreisen schwer erkennen, ob sie wirklich rotieren, daher habe ich in einer zweiten Fassung, die Kreise von Erde und Mond durch Quadrate ersetzt (ich habe – damit Ihr die Position der Codezeilen findet, die ersetzte Kreisfunktion jeweils auskommentiert stehen lassen, die entsprechende Rechteckfunktion wird jeweils direkt unter der auskommentierten Zeile eingefügt):

// Erde dreht sich um die Sonne
  rotate(earthAngle);
  translate(earthOrbitRadius, 0);
  fill(64, 64, 255);
  // circle(0, 0, earthDiam);
  rect(0, 0, earthDiam, earthDiam);
  
  // Mond dreht sich um die Erde
  rotate(moonAngle);
  translate(moonOrbitRadius, 0);
  fill(192, 192, 80);
  // circle(0, 0, moonDiam);
  rect(0, 0, moonDiam, moonDiam);

Da ein Rechteck in P5.js seinen Ursprung in der linken. obenren Ecke besitzt, eine Rotation aber immer um den Urprung/Mittelpunkt des Shapes stattfindet, mußte in setup() mit

rectMode(CENTER);

der Ursprung eines jeden Rechtecks und/oder Quadrats auf seinen Mittelpunkt gesetzt werden.

Wenn Ihr das Programm jetzt startet, dreht sich eine große blaue Kiste um die Sonne mit einer kleinen grauen Kiste, die ebenfalls drehend um die Erde kreist und Ihr könnt die Rotation der beiden Kisten genau beobachten.

Doch was ist, wenn ein zweiter Mond – nennen wir ihn Nemesis – die Erde umkreisen soll? Das Koordinantensystem der Erde ist ja schon vom Koordinatensystem des Mondes ersetzt worden. Ihr braucht also eine Funktion, die das Koordinatensystem nur temporär verschiebt, so daß man auf das alte Koordinatensystem wieder zrückgreifen kann, wenn es benötigt wird. Genau dafür gibt es in P5.js das Funktionenpaar push() und pop(). Mit push() wird das bisherige Koordinatensystem auf einen Stack gelegt und mit pop() wird es wieder zurückgeholt.

Mit diesen Informationen im Hinterkopf braucht natürlich Nemesis ihren eigenen Satz Variablen

const nemDiam = 6;
const nemOrbitRadius = 19;

let nemAngle = 0;

und ich habe sie – damit Ihr seht, daß das alles nicht nur mit Quadraten funktioniert – als »echtes« Rechteck implementiert:

rect(0, 0, nemDiam, 1.5*nemDiam);

Und nun müssen Nemesis und der Mond jeweils eine eigene Koordinaten-Matrix spendiert bekommen:

// Mond dreht sich um die Erde
  push();
  rotate(moonAngle);
  translate(moonOrbitRadius, 0);
  fill(192, 192, 80);
  rect(0, 0, moonDiam, moonDiam);
  pop();
  
  // Nemesis dreht sich um die Erde
  push();
  rotate(nemAngle);
  translate(nemOrbitRadius, 0);
  fill(220, 75, 75);
  rect(0, 0, nemDiam, 1.5*nemDiam);
  pop();

Damit die Rotationen von Nemesis und Mond auch noch mit unterschiedlichen Geschwindigkeiten ablaufen, hat Nemesis mit

nemAngle += 0.015;

einen abweichenden Rotationswinkel verpasst bekommen.

Mein endgültiges Skript sieht daher so aus:

Natürlich hätte man die Nemesis betreffenden Zeilen nicht in eine eigene push()pop() Anweisung klammern müssen, aber so ist es sauberer und Ihr könnt der Erde noch einen dritten oder einen vierten Trabanten spendieren, ohne mit den Koordinatensystemen durcheinander zu geraten.

Wenn Ihr das Programm laufen lasst, werden Ihr sehen, warum ich für die Erde und ihre Trabanten Rechtecke gewählt habe. So ist zu erkennen, daß die Erde mit genau einer Seite immer zur Sonne zeigt und die beiden Trabanten ebenfalls mit genau einer Seite immer zur Erde. Das ist, weil sie sich jeweils in ihrem eigenen Koordinatensystem bewegen, dessen eine Achse immer das Zentrum des darüberliegenden Koordinatensystems schneidet.

Für die Monde ist das okay, wenn Ihr der Erde noch Tag und Nacht spendieren wollt, müßt Ihr ihr aber noch ein zweites rotate() nach der Koordinatentransformation spendieren.

Natürlich ist das Progrämmchen ausbaubar. Ihr könnt zum Beispiel mehrere Planeten jeweils mit ihren eigenen Koordinatensystemen um den Fixstern kreisen lassen. Alle diese Planeten könntet Ihr mit Monden umgeben, die wiederum ihr eigenes Koordinatensystem besitzen. Und wenn Ihr wirkliche Helden sein wollt: Schnappt Euch ein Buch mit den Keplerschen Gesetzen zur Planetenbewegung und simuliert damit ein realistischeres Planetensystem.

Das ist der sechste Beitrag meiner Reihe von P5.js-Tutorials. Bisher erschienen sind:

  1. Bouncing Faces: Drei Wege, mit P5.js zu spielen (GitHub)
  2. Luftballons im Wunderland: Erstes Abenteuer mit P5.js (GitHub)
  3. Der fliegende Dachs im Wunderland: Das Abenteuer P5.js geht weiter (GitHub)
  4. Abenteuer P5.js : Hallo Hörnchen! (GitHub)
  5. Abenteuer P5.js (Teil 5): Octopussy (GitHub)
  6. Das Abenteuer P5.js geht weiter: Ein kleines Planetensystem (GitHub)

Literatur

Die Idee zu diesem Sketch und einige der Parameter habe ich dem wunderbaren Buch »Processing for Visual Artists – How to Create Expressive Images and Interactive Art« von Andrew Glassner (Natick, MA, 2010), Seiten 192-200 entnommen und von Java nach JavaScript (alleine und ohne die Hilfe von ChatGPT 🤓) portiert.


Bild: Grandville: Un Autre Monde, 1844

Fußnoten

  1. Ich weiß, das Widget wird via eines CDN (toolness) eingebunden, aber da auch die P5.js-Bibliotheken im Webeditor über ein CDN (Cloudflare) eingebunden werden, nehme ich das erst einmal als gegeben hin. Glaubt mir, ich habe versucht eine selbstgehostete Version des Widgets zu implementieren, aber ich bin grandios gescheitert. Also, wenn jemand von Euch da draußen eine Alternative weiß … meine Kommentare stehen ihr oder ihm offen.↩︎