Pust-Effekt durchs Mikrofon p5.js
In diesem Tutorial wird die p5.js-Bibliothek verwendet, um eine interaktive Anwendung zu erstellen. Dabei wird das Mikrofon genutzt, um eine visuelle Darstellung ähnlich einer Pusteblume zu erzeugen. Die Animation von Pusteblumen wird dabei durch den Klangpegel gesteuert, wobei die Bewegung der Blüten auf Audioeingaben reagiert.
Editor
Um die Anwendung programmieren zu können, öffnen wir zuallererst den p5.js Editor unter folgendem Link: https://editor.p5js.org/.
Grundlegendes
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
}
Die setup()
-Funktion wird einmalig am Anfang des Programms aufgerufen. Die draw()
-Funktion hingegen wird in einer Schleife für jeden Frame ständig aktualisiert, um die Animationen zu rendern.
Variablen und Arrays definieren
"use strict";
Die Zeile "use strict";
ist eine sogenannte »Directive« in JavaScript. Sie weist den JavaScript-Interpreter an, den »Strict Mode« für den gesamten Code oder einen bestimmten Code-Block zu aktivieren.
Der Strict Mode ist eine Funktion von JavaScript, die dazu dient, Fehler im Code zu vermeiden und die Sicherheit zu verbessern, indem sie bestimmte problematische Verhaltensweisen unterbindet und strengere Regeln für die Vernwundung von JavaScript einführt.
Änderungen, die der Strict Mode bewirkt sind Folgende:
Fehler werden strenger behandelt: Bestimmte Aktionen, die im normalen Modus toleriert werden, lösen im Strict Mode Fehler aus
Verbesserte Fehlermeldungen: Fehlermeldungen enthalten oft detailliertere Informationen darüber, was falsch gelaufen ist
Verbesserte Sicherheit: Einige potenziell unsichere oder fehleranfällige Aktionen sind im Strict Mode nicht zulässig
In der Regel wird empfohlen, den Strict Mode zu verwenden, um sicherzustellen, dass der JavaScript-Code konsistenter, sicherer und weniger fehleranfällig ist.
let mic, gui, micLevel;
let controller, panel;
let dandelions = [];
In diesem Code-Bereich werden verschiedene Variablen und Arrays, die im weiteren Verlauf des Codes verwendet werden, definiert:
mic
,gui
,micLevel
: Diese Variablen werden verwendet, um das Mikrofon-Input-Objekt, das GUI-Objekt und das aktuelle Mikrofonlevel zu speicherncontroller
,panel
: Diese Variablen werden verwendet, um das GUI-Controller-Objekt und das Panel-Objekt zu speichern, die für die Steuerung der Benutzeroberfläche verwendet werdendandelions
: Dies ist ein Array, das verwendet wird, um alle Dandelion-Objekte (Pusteblumen) zu speichern, die auf dem Bildschirm gezeichnet werden. Jedes Element in diesem Array repräsentiert eine einzelne Pusteblume
Setup-Funktion
function setup() {
createCanvas(windowWidth, windowHeight);
dandelions.push(new Dandelion(width / 2, height / 2));
Nun kommen wir zu der setup()
-Funktion. Diese wird, wie vorher schon genannt, einmalig am Anfang des Programmes aufgerufen und ist dazu da, um Variablen zu initialisieren, den Canvas und andere Operationen einzurichten, die nur einmal ausgeführt werden müssen.
Am Anfang der setup()
-Funktion wird mit createCanvas(windowWidth, windowHeight);
ein Canvas-Element erstellt, das die gesamte Breite und Höhe des Browserfensters einnimmt. Die aktuelle Breite und Höhe des Browserfensters wird durch die Funktionen windowWidth
und windowHeight
geliefert.
In der nächsten Zeile wird ein neues Dandelion-Objekt erstellt und durch die Methode .push
dem Array dandelions
hinzugefügt. Um nun die horizontale und vertikale Mitte des Canvas zu finden, damit das Objekt in der Mitte des Canvas platziert werden kann, werden width / 2
und height / 2
verwendet.
GUI (Grafische Benutzeroberfläche)
// setup GUI
controller = function() {
this.debug = false;
this.strength = 0.2;
this.number = 3;
this.day = true;
}
Im nächsten Schritt wird eine GUI (Grafische Benutzeroberfläche) eingerichtet. In unserem Fall wird eine GUI-Bibliothek namens dat.GUI
verwendet, um eine Benutzeroberfläche zu erstellen, die dem Benutzer ermöglicht, Einstellungen interaktiv ändern zu können.
Der Code initialisiert eine Steuerungsstruktur (controller
), die die Einstellungen für die GUI definiert, und erstellt dann eine Instanz dieser Steuerungsstruktur (panel
). Anschließend wird die GUI erstellt und jedem Einstellungselement (wie Schaltern, Schiebereglern usw.) ein entsprechendes GUI-Element hinzugefügt.
Die controller
-Funktion dient als Konstruktor für ein Objekt, das die Steuerelemente der grafischen Benutzeroberfläche (GUI) enthält. Diese Steuerelemente werden später mit Hilfe der dat.GUI-Bibliothek erstellt und können später von jedem Benutzer individuell verändert werden.
panel = new controller();
gui = new dat.GUI();
gui.add(panel, 'debug');
gui.add(panel, 'strength', 0.2, 0.8);
gui.add(panel, 'number', 1, 7).step(1);
gui.add(panel, 'day');
Anschließend wird mit panel = new controller();
ein neues Objekt erstellt, das mit der controller
-Funktion initialisiert wird. Dieses Objekt wird verwendet, um die Eigenschaften der Steuerelemente zu speichern.
Als Nächstes wird mit gui = new dat.GUI();
eine Instanz der dat.GUI-Bibliothek erstellt, um die grafischen Benutzeroberflächen-Steuerelemente zu erstellen. Für dieses Tutorial ist diese Bibliothek essenziell. Diese kannst du dir aus der original Quelle dieses Tutorials holen, die unten bei den Quellen verlinkt ist.
Mit gui.add(panel, 'debug');
wird ein Schalter hinzugefügt, mit dem die Eigenschaft debug
des panel
-Objekts in der grafischen Benutzeroberfläche ein- und ausgeschaltet werden kann.
Der Debug bezieht sich auf den Prozess des Auffindens, Analysierens und Behebens von Fehlern oder Fehlfunktionen innerhalb eines Porgrammes. Mit dieser Funktion ist es daher möglich, während der Ausführung eines Programmes, zusätzliche Informationen zu sammeln. Dies hilft Fehler zu identifizieren und zu verstehen, was im Code passiert.
gui.add(panel, 'strength', 0.2, 0.8);
dient dazu, einen Schieberegler hinzuzufügen, der die Möglichkeit bietet, den Wert der strength
-Eigenschaft im Bereich von 0,2 bis 0,8 einzustellen.
Die strength
-Eigenschaft steuert die Stärke der Kräfte, die auf die Samen der Pusteblume wirken. Diese Kräfte werden durch den Mikrofonpegel (gemessen durch das Mikrofon des Geräts) und eine zufällige Komponente beeinflusst. Je höher der Wert von strength
, desto stärker sind die Kräfte, die auf die Samen wirken, was dazu führt, dass eine viel größere Kraft benötigt wird, damit die Samen wegfliegen können.
Außerdem wird noch ein weiterer Schieberegler hinzugefügt, um die Anzahl der Dandelion-Objekte im Bereich von eins bis sieben einzustellen. Das heißt, es können bis zu sieben Pusteblumen gleichzeitig angezeigt werden. Der step(1)
-Aufruf in gui.add(panel, 'number', 1, 7).step(1);
bedeutet, dass der Schieberegler nur Ganzzahlen annimmt.
Schließlich wird mit gui.add(panel, 'day');
ein Kippschalter hinzugefügt, um zwischen dem Tag- und Nachtmodus zu wechseln. Diese Steuerelemente werden später verwendet, um die Darstellung des Programms zu ändern.
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/addons/p5.dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/addons/p5.sound.min.js"></script>
<script src="dat.gui.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>
Damit die GUI-Datenbank auch funktioniert, muss das HTML etwas verändert werden. Zum einen muss die dat.gui.min.js
Datei verlinkt werden und zum anderen müssen drei Links hinzugefügt werden. Nachdem der HTML-Code geändert, eine neue .js
-Datei mit dem Namen dat.gui.min.js
erstellt und der entsprechende Code in die neue .js
-Datei eingefügt wurde, sollte die Benutzeroberfläche nun funktionieren und wie folgt aussehen:
Mikrofon
//Mic einrichten
mic = new p5.AudioIn()
mic.start();
In diesem Teil des Codes werden die Audiobibliothek p5.AudioIn
und das Mikrofon initialisiert. Das Mikrofon wird verwendet, um die Bewegung der Dandelions zu beeinflussen.
Mit Hilfe von mic = new p5.AudioIn()
wird ein neues p5.AudioIn
-Objekt namens mic
erstellt. Das p5.AudioIn
-Objekt ist Teil der p5.js-Bibliothek und wird verwendet, um Audioeingaben über das Mikrofon des Benutzers zu erfassen.
Durch mic.start();
wird das Mikrofon gestartet. Nach dem Aufruf dieser Methode beginnt das mic
-Objekt Audio vom Mikrofon zu erfassen, und stellt es für weitere Verarbeitungsschritte zur Verfügung, wie z. B. die Analyse des Audiopegels oder die Verarbeitung von Echtzeit-Audioeffekten.
Die Animation
function draw() {
background((panel.day)? 255 : 50);
// get mic
micLevel = mic.getLevel();
if (panel.debug) {
ellipse(width * 0.1, constrain(height - micLevel * height * 5, 0, height), 10, 10);
}
for (let i = 0; i < dandelions.length; i++) {
let d = dandelions[i];
d.applyForce(createVector(micLevel, random(-0.12,0.1)));
d.display();
}
if (dandelions.length > panel.number) {
dandelions.shift();
}
}
function doubleClicked() {
dandelions.push(new Dandelion(mouseX, mouseY));
}
Anschließend wird die draw()
-Funktion erstellt. In dieser wird der Hauptteil der Animation ausgeführt. Hier wird der Hintergrund gezeichnet, der Mikrofonpegel abgerufen und verwendet, um die Bewegung der Dandelions zu beeinflussen. Außerdem werden die Dandelions aktualisiert und dargestellt.
Die Zeile background((panel.day)? 255 : 50);
ist ein Ausdruck, der in der draw()
-Funktion verwendet wird, um den Hintergrund der Leinwand zu zeichnen. Hier wird ein sogenannter »ternärer Operator« verwendet, um zwischen zwei verschiedenen Farbwerten zu wählen, je nachdem, ob panel.day
wahr (true
) oder falsch (false
) ist. Diese Eigenschaft steuert den Tag- und Nachtmodus der Darstellung.
Der aktuelle Pegel wird durch micLevel = mic.getLevel();
abgerufen und in der Variable micLevel
gespeichert. Der Pegel wird später verwendet, um die Bewegung der Dandelions zu steuern.
Zudem wurde noch ein Debugger eingebaut. Dieser wird mit Hilfe von if (panel.debug) { ... }
überprüft, ob er aktiviert ist. Wenn dies der Fall ist, wird ein kleiner Kreis gezeichnet, der den aktuellen Mikrofonpegel visualisiert und sich entsprechend des Pegels bewegt.
Folgende Schleife for (let i = 0; i < dandelions.length; i++) { ... }
durchläuft alle vorhandenen Dandelions und aktualisiert ihre Positionen basierend auf dem Mikrofonpegel.
Mit if (dandelions.length > panel.number) { dandelions.shift(); }
wird überprüft, ob die Anzahl der Dandelions größer ist als die im GUI eingestellte maximale Anzahl. Wenn dies der Fall ist, wird das älteste Dandelion-Objekt aus dem Array entfernt.
Um ein weiteres Dandelion an einer beliebigen Stelle hinzuzufügen, muss man auf die Leinwand doppelklicken. Denn dadurch wird die Funktion function doubleClicked() { ... }
aufgerufen und dem Array dandelions
hinzugefügt.
Bisheriger Code
"use strict";
let mic, gui, micLevel;
let controller, panel;
let dandelions = [];
function setup() {
createCanvas(windowWidth, windowHeight);
dandelions.push(new Dandelion(width / 2, height / 2));
// setup GUI
controller = function() {
this.debug = false;
this.strength = 0.2;
this.number = 3;
this.day = true;
}
panel = new controller();
gui = new dat.GUI();
gui.add(panel, 'debug');
gui.add(panel, 'strength', 0.2, 0.8);
gui.add(panel, 'number', 1, 7).step(1);
gui.add(panel, 'day');
// setup mic
mic = new p5.AudioIn()
mic.start();
}
function draw() {
background((panel.day)? 255 : 50);
// get mic
micLevel = mic.getLevel();
if (panel.debug) {
ellipse(width * 0.1, constrain(height - micLevel * height * 5, 0, height), 10, 10);
}
for (let i = 0; i < dandelions.length; i++) {
let d = dandelions[i];
d.applyForce(createVector(micLevel, random(-0.12,0.1)));
d.display();
}
if (dandelions.length > panel.number) {
dandelions.shift();
}
}
function doubleClicked() {
dandelions.push(new Dandelion(mouseX, mouseY));
}
Class Dandelion
In der Klasse class Dandelion { ... }
werden die Eigenschaften und Methoden definiert, die ein einzelnes Dandelion-Objekt repräsentieren. Hier werden Kräfte auf die Samen angewendet und ihre Darstellung entsprechend angepasst.
class Dandelion {
constructor(x, y) {
this.pos = createVector(x, y);
this.rad = random(10, 20);
this.seeds = [];
this.seedN = this.rad + 8;
Der Konstruktor wird aufgerufen, wenn ein neues Dandelion
-Objekt erstellt wird. Er initialisiert die Eigenschaften des Dandelion-Objekts. Hier werden die Position (this.pos
), der Radius (this.rad
), das Array der Samen (this.seeds
) und die Anzahl der Samen (this.seedN
) festgelegt.
this.pos = createVector(x, y);
: Die Eigenschaftpos
speichert die Position des Dandelions im Koordinatensystemthis.rad = random(10, 20);
: Die Eigenschaftrad
speichert den Radius des Dandelions. Hier wird der Radius zufällig zwischen zehn und 20 Pixeln ausgewählt, um jedem Dandelion eine unterschiedliche Größe zu gebenthis.seeds = [];
: Die Eigenschaftseeds
ist ein leeres Array, das dazu dient, die Samen des Dandelions zu speichernthis.seedN = this.rad + 8;
: Die EigenschaftseedN
speichert die Anzahl der Samen, die das Dandelion haben wird. Sie wird berechnet, indem zum Radius des Dandelions (rad
) die Zahl acht addiert wird
for (let i = 0; i < this.seedN; i++) {
let ori = i * 2 * PI / this.seedN + PI / 2;
let vec = p5.Vector.fromAngle(ori);
vec.mult(this.rad);
let hue = random(360, 100);
this.seeds.push(new Seed(vec.x, vec.y, ori, this.seedN, hue));
}
}
In der darauffolgenden Schleife wird jedes Samenobjekt des Dandelions initialisiert und dem Array this.seeds
hinzugefügt. Für jeden Samen werden ein zufälliger Winkel (ori
), eine Position relativ zur Mitte des Dandelions (vec
), und eine zufällige Farbe (hue
) generiert. Diese Werte werden verwendet, um neue Seed
-Objekte zu erstellen und sie dem Array seeds
hinzuzufügen.
for (let i = 0; i < this.seedN; i++) { ... }
beginnt eine Schleife, diethis.seedN
-mal durchlaufen wird.this.seedN
ist die Anzahl der Samen, die im Dandelion erstellt werden sollenlet ori = i * 2 * PI / this.seedN + PI / 2;
hierbei wird der Winkel (ori
) für den aktuellen Samen berechnet. Der Winkel wird gleichmäßig um den Kreis verteilt, indem der Indexi
durch die Anzahl der Samenthis.seedN
geteilt und dann mit2 * PI
multipliziert wird. Durch die Hinzufügung vonPI / 2
wird der Startwinkel auf 90 Grad (im Uhrzeigersinn) verschoben.let vec = p5.Vector.fromAngle(ori);
: Ein Vektor (vec
) wird aus dem Winkel (ori
) erstellt. Dieser Vektor repräsentiert die Richtung, in die der Samen zeigen wird.vec.mult(this.rad);
: Der Vektor wird mit dem Radius des Dandelions (this.rad
) multipliziert, um die Position des Samens relativ zum Zentrum des Dandelions zu bestimmen. Dadurch wird der Samen vom Zentrum des Dandelions aus in die richtige Richtung verschoben.Bei
let hue = random(360, 100);
wird eine zufällige Farbe (hue
) generiert, die später für die Darstellung des Samens verwendet wird. Die Farbe wird zufällig zwischen 360 und 100 gewählt, wobei 100 die maximale Helligkeit ist.this.seeds.push(new Seed(vec.x, vec.y, ori, this.seedN, hue));
: Ein neuesSeed
-Objekt wird erstellt und demseeds
-Array des Dandelions hinzugefügt. Dabei werden die berechnete Position (vec.x
,vec.y
), der Winkel (ori
), die Anzahl der Samen insgesamt (this.seedN
) und die zufällige Farbe (hue
) übergeben.
Auf diese Weise wird für jeden Samen des Dandelions ein entsprechendes Seed
-Objekt initialisiert und dem seeds
-Array hinzugefügt.
Hier sind einige Beispiele dafür, wie sich das Ändern des Hue-Werts auswirken könnte:
Den Zustand der Samen verändern
applyForce(f) {
for (let i = 0; i < this.seedN; i++) {
this.seeds[i].applyForce(createVector(f.x * random(1), f.y + random(-0.1, 0.1)));
this.seeds[i].update();
}
}
Die applyForce(f) {...}
Methode wird verwendet, um eine Kraft (f
) auf alle Samen des Dandelions anzuwenden und anschließend den Zustand jedes Samens zu aktualisieren. Sie geht somit durch jedes Seed
-Objekt im seeds
-Array und wendet die Kraft darauf an, bevor sie die update()
-Methode jedes Samens aufruft.
Die
for (let i = 0; i < this.seedN; i++) { ... }
lässt jedesSeed
-Objekt imseeds
-Array des Dandelions durchlaufenthis.seeds[i].applyForce(createVector(f.x * random(1), f.y + random(-0.1, 0.1)));
: Für jeden Samen wird die MethodeapplyForce()
aufgerufen. Diese Methode erwartet einen Vektor als Argument, der die auf den Samen wirkende Kraft darstellt. Hier wird ein neuer Vektor erstellt, indem der Vektorf
skaliert wird. Der Wertf.x
wird mit einem zufälligen Wert zwischen Null und Eins multipliziert, um eine gewisse Variabilität einzuführen, undf.y
wird um einen zufälligen Wert zwischen -0.1 und 0.1 verändert. Dieser Vektor repräsentiert die angewendete Kraft auf den Samen.this.seeds[i].update();
: Nachdem die Kraft auf den Samen angewendet wurde, wird die Methodeupdate()
aufgerufen, um den Zustand des Samens zu aktualisieren. Diese Methode führt Bewegungsberechnungen durch, basierend auf den Kräften, die auf den Samen wirken. Dies ermöglicht es den Samen, sich entsprechend der Umgebung oder der Simulation zu bewegen oder zu verändern.
Indem diese Schleife durchlaufen wird, wird die auf jeden Samen wirkende Kraft angewendet und dann der Zustand jedes Samens aktualisiert. Dadurch können die Samen des Dandelions auf die Kraft reagieren und sich entsprechend bewegen oder verändern.
Darstellung auf dem Bildschirm
display() {
push();
translate(this.pos.x, this.pos.y);
if (panel.day){
fill(0, 0, 0, 200);
stroke(0, 0, 0, 100);}
else {
fill(255);
stroke(255);
}
ellipse(0,0,this.rad*2);
strokeWeight(5);
line(0, this.rad, 0, height);
for (let i = 0; i < this.seedN; i++) {
this.seeds[i].display();
}
pop();
}
}
Die display()
-Methode wird verwendet, um die Pusteblume (Dandelion) und seine Samen auf dem Bildschirm darzustellen. Sie übersetzt den Ursprung des Koordinatensystems zur Position des Dandelions und zeichnet dann einen Kreis für den Kopf des Dandelions und ebenso einen Stängel. Die Farben werden basierend auf der Tageszeit (panel.day
) ausgewählt. Dann wird eine Schleife verwendet, um jedes Seed
-Objekt im seeds
-Array zu durchlaufen. Für jedes dieser Objekte wird die Methode display()
aufgerufen, um jeden Samen grafisch darzustellen.
push();
speichert den aktuellen Zustand des Zeichenkontexts (z. B. Transformationen wie Verschiebungen oder Rotationen) im Stapel. Dies ermöglicht es, Änderungen vorzunehmen, die später wieder rückgängig gemacht werden könnentranslate(this.pos.x, this.pos.y);
: Hier wird der Ursprung des Koordinatensystems zum Mittelpunkt des Dandelions verschoben. Dadurch werden alle nachfolgenden Zeichnungen relativ zum Mittelpunkt des Dandelions durchgeführtMit
if (panel.day) { ... } else { ... }
wird überprüft, ob der Tag- oder Nachtmodus aktiv ist. Dies wird durch die Eigenschaftpanel.day
gesteuert. Abhängig vom Modus wird entweder eine dunkle Farbe für den Kreis am Stängel und den Stängel selbst festgelegt (fill(0, 0, 0, 200)
undstroke(0, 0, 0, 100)
) oder eine weiße Farbe für den Stängel (fill(255)
undstroke(255)
). Dunkel für den Tag-Modus, Hell für den Nacht-Modus.ellipse(0, 0, this.rad*2);
zeichnet einen Kreis mit einem Durchmesser, der dem doppelten Radius (this.rad
) des Dandelions entspricht. Dies repräsentiert den Kopf des Dandelions. MitstrokeWeight(5);
wird die Strichstärke auf fünf Pixel festgelegt.line(0, this.rad, 0, height);
zeichnet eine Linie vom Mittelpunkt des Dandelions bis zur unteren Grenze des Bildschirms und repräsentiert somit den Stängel des Dandelions.for (let i = 0; i < this.seedN; i++) { this.seeds[i].display(); }
: In dieser Schleife wird diedisplay()
-Methode jedes Samens des Dandelions aufgerufen, um sie auf dem Bildschirm darzustellen.
Am Ende der display()
-Methode wird pop()
verwendet, um das gespeicherte Koordinatensystem wiederherzustellen. Dadurch wird sichergestellt, dass nach dem Zeichnen des Dandelions alle Transformationen nur auf dieses Objekt angewendet wurden und nicht versehentlich auf nachfolgende Zeichnungen einfließen. Das bedeutet somit, dass alle Transformationen, die mit push()
angewendet wurden, dadurch rückgängig gemacht werden.
Class Seed
Die Seed
-Klasse ist dafür verantwortlich, die Eigenschaften und das Verhalten eines einzelnen Samens in der Pusteblume zu definieren. Jeder Samen hat eine Position auf dem Bildschirm, eine Geschwindigkeit und Beschleunigung, die seine Bewegung steuern, sowie eine Größe und Farbe. Die Klasse ermöglicht es auch, eine Kraft auf den Samen auszuüben, um ihn zu bewegen und seine Position und Ausrichtung mit der Zeit zu aktualisieren. Schließlich zeichnet die Klasse den Samen auf den Bildschirm, indem sie einen Kreis mit der entsprechenden Größe und Farbe an seiner Position zeichnet.
class Seed {
constructor(x, y, ori, size, hue) {
this.pos = createVector(x, y);
this.vel = createVector();
this.acc = createVector();
this.ori = ori;
this.oriOri = ori;
this.size = size;
this.mass = this.size / 4;
this.fly = false;
this.random = random(2);
this.dir = 3 * PI / 2;
this.hue = hue;
}
Der Konstruktor initialisiert eine Seed
-Instanz mit verschiedenen Eigenschaften, die für die Darstellung und Bewegung eines Samens erforderlich sind. Diese Eigenschaften bestehen aus der Anfangsposition (x
, y
), der Anfangsausrichtung (ori
), der Größe (size
) und dem Hue-Wert für die Farbe (hue
).
Durch die Koordinaten
x
undy
wird die Positionpos
des Samens festgelegt und an dieSeed
-Klasse übergebenDie Geschwindigkeit
vel
und Beschleunigungacc
des Samens werden als leere Vektoren initialisiert, da der Samen zu Beginn ruhtDie aktuelle Ausrichtung
ori
des Samens wird auf den übergebenen Wertori
gesetzt, der die Richtung angibt, in die der Samen anfangs fliegen wirdDie ursprüngliche Ausrichtung
oriOri
wird ebenfalls auf den übergebenen Wertori
gesetzt und später verwendet, um die Ausrichtung des Samens zu aktualisierenDie Größe des Samens
size
wird durch den übergebenen Parametersize
festgelegtDie Masse des Samens
mass
wird als ein Viertel seiner Größe festgelegt, was die Physiksimulation beeinflusstDer Flugstatus
fly
wird auffalse
gesetzt, da der Samen zu Beginn nicht fliegtEin zufälliger Faktor
random
wird generiert, der später für die Variation der Flugrichtung verwendet wirdDie Flugrichtung
dir
wird auf 3 * π / 2 gesetzt, was einer vertikalen Ausrichtung entsprichtDer Farbwert
hue
wird als Argument übergeben und später für die Farbe des Samens verwendet
Aktualisierung
update() {
this.vel.add(this.acc);
this.pos.add(this.vel);
this.acc.mult(0);
if (this.fly) {
this.ori = lerp(this.ori, this.dir, 0.1);
}
this.ori = (this.ori < PI / 2) ? this.ori + PI * 2 : this.ori;
this.ori = (this.ori > PI * 5 / 2) ? this.ori - PI * 2 : this.ori;
this.dir = 3 * PI / 2 + this.random;
}
Die update()
-Methode aktualisiert den Zustand des Samens basierend auf den Kräften, die auf ihn wirken und der Physiksimulation.
Mit this.vel.add(this.acc);
wird die Geschwindigkeit (vel
) durch die Beschleunigung (acc
) aktualisiert und dann mit this.pos.add(this.vel);
zur Position (pos
) addiert. Dies entspricht dem physikalischen Prinzip der Beschleunigung.
Die Beschleunigung wird mit this.acc.mult(0);
nach jedem erneuten klicken auf »Play« im Editor zurückgesetzt, also auf Null gesetzt, um sicherzustellen, dass keine dauerhafte Beschleunigung auf den Samen wirkt und somit der Samen nicht unkontrolliert beschleunigt wird.
In if (this.fly) { this.ori = lerp(this.ori, this.dir, 0.1); }
wird überprüft, ob der Samen fliegt. Wenn er fliegt, also fly
ist true
, wird seine Ausrichtung nach und nach in Richtung der aktuellen Flugrichtung (dir
) geändert.
this.ori = (this.ori < PI / 2) ? this.ori + PI * 2 : this.ori;
stellt sicher, dass die Orientierung des Samens im Bereich von 0 bis 2π bleibt, um eine kontinuierliche Rotation zu gewährleisten. Wenn die Orientierung kleiner als π/2 ist, wird sie um 2π erhöht, um sicherzustellen, dass sie im richtigen Bereich bleibt. In der darauffolgenden Zeile passiert ähnliches, nur dass der Wert nicht um 2π erhöht, sondern verringert wird.
In this.dir = 3 * PI / 2 + this.random;
wird die Zielorientierung des Samens neu festgelegt, basierend auf einem Ausgangswert von 3*π/2 (entsprechend einer vertikalen Ausrichtung nach oben) und einem zufälligen Offset (this.random
). Dies bestimmt, in welche Richtung sich der Samen bewegen soll.
Kraft anwenden
applyForce(f) {
if (f.mag() > panel.strength) {
this.fly = true;
let force = f.copy();
force.div(this.mass);
this.acc.add(force);
this.dir = f.heading() + PI * 2 + this.random;
}
Die applyForce(f)
-Methode wendet eine Kraft (f
) auf den Samen an (Instanzen der Seed
-Klasse), um seine Bewegung beeinflussen zu können. Diese Methode steuert also, wie sich die Samenobjekte unter dem Einfluss äußerer Kräfte verhalten und wie sie sich im Raum bewegen.
if (f.mag() > panel.strength) { ... }
überprüft, ob die Stärke der angewendeten Kraft größer ist als die in der vom Benutzer eingestellten Stärke (panel.strength
) in der GUI. Wenn der Flugstatus des Samens auf true
gesetzt wird, bedeutet dies, dass die Kraft stark genug ist, um den Samen fliegen zu lassen. Die angewendete Kraft wird entsprechend der Masse des Samens aufgeteilt, um eine realistische Beschleunigung zu erzielen.
this.fly = true;
setzt das fly
-Attribut des Samens auf true
, um anzuzeigen, dass der Samen fliegt.
let force = f.copy();
kopiert die übergebene Kraft f
in eine neue Variable namens force
.
force.div(this.mass);
teilt die Kraft durch die Masse des Samens, um die Beschleunigung zu berechnen.
this.acc.add(force);
addiert die berechnete Beschleunigung zur aktuellen Beschleunigung des Samens.
this.dir = f.heading() + PI * 2 + this.random;
setzt die Flugrichtung des Samens auf den Winkel der übergebenen Kraft plus einen zufälligen Wert.
if (f.mag() > 0.2) {
this.dir = lerp(this.ori, f.heading() + PI * 2, 0.5);
this.ori = lerp(this.ori, this.dir, 0.1);
} else {
this.ori = lerp(this.ori, this.oriOri, 0.05);
}
}
if (f.mag() > 0.2) { ... } else { ... }
: Wenn die übergebene Kraft f
kleiner oder gleich 0.2
ist, wird der Samen nicht fliegen gelassen.
Wenn die Kraft groß genug ist (f.mag() > 0.2
), wird der Samen in Richtung der Kraft ausgerichtet und seine Orientierung schrittweise geändert.
this.dir = lerp(this.ori, f.heading() + PI * 2, 0.5);
ermittelt die aktuelle Orientierung des Samens in Richtung der Kraftrichtung.
this.ori = lerp(this.ori, this.dir, 0.1);
ermittelt die aktuelle Orientierung des Samens in Richtung der neuen berechneten Richtung.
Wenn die Kraft nicht groß genug ist (f.mag() <= 0.2
), wird die Orientierung des Samens langsam auf seine ursprüngliche Orientierung zurückgeführt.
this.ori = lerp(this.ori, this.oriOri, 0.05);
ermittelt die aktuelle Orientierung des Samens in Richtung seiner ursprünglichen Orientierung mit einer geringeren Geschwindigkeit.
Zeichnung des Samens
display() {
push();
translate(this.pos.x, this.pos.y);
push();
rotate(this.ori + 0.05 * sin(this.random + frameCount * 0.1));
colorMode(HSB);
fill(this.hue, 100, 100, 100);
noStroke();
ellipse(this.size * 2, 0, this.size);
pop();
pop();
}
}
Die display()
-Methode ist dazu da, um den Samen auf dem Bildschirm zu zeichnen.
push()
speichert den aktuellen Zeichenstatus, sodass nachfolgende Transformationen und Zeichenoperationen isoliert sind und den ursprünglichen Status nicht beeinflussen.
translate(this.pos.x, this.pos.y)
verschiebt den Ursprung des Koordinatensystems an die Position des Samensobjekts (this.pos.x
, this.pos.y
). Dadurch wird der Samen an die richtige Position auf dem Bildschirm verschoben.
Der aktuelle Zeichenstatus wird mit push()
erneut gespeichert. Dies ist nützlich, um die Transformationen und Zeichenoperationen innerhalb des nachfolgenden pop()
-Aufrufs zu isolieren.
rotate(this.ori + 0.05 sin(this.random + frameCount 0.1))
rotiert den Samen um seinen eigenen Ursprung (translate()
-Position) basierend auf der aktuellen Orientierung (this.ori
) und einer dynamischen zusätzlichen Rotation, die sich mit der Zeit ändert (sin(this.random + frameCount * 0.1)
). Wodurch man einen schwankenden, organischen Bewegungseffekt erhält.
colorMode(HSB)
setzt den Farbmodus auf HSB (Farbton, Sättigung, Helligkeit), was es ermöglicht, die Farbe des Samens basierend auf einem Farbtonwert (this.hue
) festzulegen.
Mit fill(this.hue, 100, 100, 100)
wird die Füllfarbe für den Samen auf eine Farbe im HSB-Farbmodus gesetzt. this.hue
bestimmt den Farbtonwert, während die anderen Werte für die Sättigung (100), Helligkeit (100) und den Alpha-Kanal (100) festgelegt sind.
Durch noStroke()
wird erzielt, dass der Samen keine Umrandung erhält.
ellipse(this.size * 2, 0, this.size)
zeichnet eine Ellipse, also einen Kreis, am Ursprung des Koordinatensystems (0, 0
) des Samensobjekts. Die X-Koordinate (this.size * 2
) bestimmt den Abstand der Ellipse vom Ursprung in horizontaler Richtung, während die Y-Koordinate (0
) den Abstand in vertikaler Richtung angibt. Die Größe der Ellipse wird durch this.size
festgelegt.
pop()
stellt den Zeichenstatus vor dem letzten push()
-Aufruf wieder her, was bedeutet, dass alle Transformationen und Zeichenoperationen, die nach dem letzten push()
erfolgt sind, nun nicht mehr gelten.
pop()
stellt den ursprünglichen Zeichenstatus wieder her, der vor dem ersten push()
-Aufruf gespeichert wurde. Alle Transformationen und Zeichenoperationen, die nach dem ersten push()
erfolgt sind, werden jetzt rückgängig gemacht.
Vollständiger Code
"use strict";
let mic, gui, micLevel;
let controller, panel;
let dandelions = [];
function setup() {
createCanvas(windowWidth, windowHeight);
dandelions.push(new Dandelion(width / 2, height / 2));
// setup GUI
controller = function() {
this.debug = false;
this.strength = 0.2;
this.number = 3;
this.day = true;
}
panel = new controller();
gui = new dat.GUI();
gui.add(panel, 'debug');
gui.add(panel, 'strength', 0.2, 0.8);
gui.add(panel, 'number', 1, 7).step(1);
gui.add(panel, 'day');
// setup mic
mic = new p5.AudioIn()
mic.start();
}
function draw() {
background((panel.day)? 255 : 50);
// get mic
micLevel = mic.getLevel();
if (panel.debug) {
ellipse(width * 0.1, constrain(height - micLevel * height * 5, 0, height), 10, 10);
}
for (let i = 0; i < dandelions.length; i++) {
let d = dandelions[i];
d.applyForce(createVector(micLevel, random(-0.12,0.1)));
d.display();
}
if (dandelions.length > panel.number) {
dandelions.shift();
}
}
function doubleClicked() {
dandelions.push(new Dandelion(mouseX, mouseY));
}
class Dandelion {
constructor(x, y) {
this.pos = createVector(x, y);
this.rad = random(10, 20);
this.seeds = [];
this.seedN = this.rad + 8;
for (let i = 0; i < this.seedN; i++) {
let ori = i * 2 * PI / this.seedN + PI / 2;
let vec = p5.Vector.fromAngle(ori);
vec.mult(this.rad);
let hue = random(360, 100);
this.seeds.push(new Seed(vec.x, vec.y, ori, this.seedN, hue));
}
}
applyForce(f) {
for (let i = 0; i < this.seedN; i++) {
this.seeds[i].applyForce(createVector(f.x * random(1), f.y + random(-0.1, 0.1)));
this.seeds[i].update();
}
}
display() {
push();
translate(this.pos.x, this.pos.y);
if (panel.day){
fill(0, 0, 0, 200);
stroke(0, 0, 0, 100);}
else {
fill(255);
stroke(255);
}
ellipse(0,0,this.rad*2);
strokeWeight(5);
line(0, this.rad, 0, height);
for (let i = 0; i < this.seedN; i++) {
this.seeds[i].display();
}
pop();
}
}
class Seed {
constructor(x, y, ori, size, hue) {
this.pos = createVector(x, y);
this.vel = createVector();
this.acc = createVector();
this.ori = ori;
this.oriOri = ori;
this.size = size;
this.mass = this.size / 4;
this.fly = false;
this.random = random(2);
this.dir = 3 * PI / 2;
this.hue = hue;
}
update() {
this.vel.add(this.acc);
this.pos.add(this.vel);
this.acc.mult(0);
if (this.fly) {
this.ori = lerp(this.ori, this.dir, 0.1);
}
this.ori = (this.ori < PI / 2) ? this.ori + PI * 2 : this.ori;
this.ori = (this.ori > PI * 5 / 2) ? this.ori - PI * 2 : this.ori;
this.dir = 3 * PI / 2 + this.random;
}
applyForce(f) {
if (f.mag() > panel.strength) {
this.fly = true;
let force = f.copy();
force.div(this.mass);
this.acc.add(force);
this.dir = f.heading() + PI * 2 + this.random;
}
if (f.mag() > 0.2) {
this.dir = lerp(this.ori, f.heading() + PI * 2, 0.5);
this.ori = lerp(this.ori, this.dir, 0.1);
} else {
this.ori = lerp(this.ori, this.oriOri, 0.05);
}
}
display() {
push();
translate(this.pos.x, this.pos.y);
push();
rotate(this.ori + 0.05 * sin(this.random + frameCount * 0.1));
colorMode(HSB);
fill(this.hue, 100, 100, 100);
noStroke();
ellipse(this.size * 2, 0, this.size);
pop();
pop();
}
}
So sollte es nun aussehen
Quelle
Hier findest du die kompletten Code-Dateien zum herunterladen:
Wenn du sehen möchtest, welche Ursprungsdatei uns zu diesem Tutorial inspiriert hat, kannst Du sie dir gerne unter folgendem Link anschauen: https://wp.nyu.edu/shanghai-ima-documentation/electives/xx555/noc-week-7-midterm-dandelions-susan-xu/
Wir wünschen Dir viel Spaß beim ausprobieren!