Zeichnen mit Pottis und Partikel Effekt p5.js

In diesem Tutorial lernst du wie in p5.js mithilfe von Arduino und Potentiometer (kurz Poti) zeichnen kannst.
Inspiriert wurden wir von den Zaubertafel (Etch a Sketch) bei denen man zwei Knöpfe zum drehen hatte, wobei einer die X-Richtung und der andere die Y-Richtung gesteuert hat, um dann eine Zeichnung zu erschaffen.

Endergebnis

Aufbau

Um unseren Aufbau nachzubauen, braucht ihr den Arduino, ein Steckbrett, zwei Potis und verschieden lange Kabel. Bei beiden Potis werden auf der einen Seite mit plus und minus verbunden, welche in GND und 5V plaziert werden. Die andere Seite der Poti, der Mittelpin, wird einmal mit A0 und A1 verbunden.

Bild des Aufbaus mit Arduino, Steckbrett, Potis und Kabeln

Vorbereitungen

Wir haben unseren Code in p5.js Web Editor aufgebaut, damit wir gleich eine Zeichenfläche zum visualieren haben. Damit der Web Editor auch die Werte der Potis auslesen kann, müssen wir noch einen Zwischenschritt machen. Dafür brauchen wir erstmal das Programm Arduino IDE (https://www.arduino.cc/en/software/) in welches wir einen kleinen Code schreiben und hochladen. Den Code kannst du dir auch im Programm selber herholen über File -> Examples -> 01.Basics -> AnalogReadSerial, dieser gilt dann aber nur für beispielsweise einen Potti und nicht für zwei, deshalb haben wir den unteren Code für beide Pottis erstellt. Sobald wir den Code haben können wir diesen auf unseren Arduino hochladen.

//Setup Funktion läuft einmal, wenn man den Reset-Knopf drückt
void setup() {
  //Startet serielle Kommunikation
  Serial.begin(9600);
}

//Loop Funktion läuft un Endlosschleife
void loop() {
  int pot1 = analogRead(A0); //Wert von Poti 1, Variablen werden später noch gebraucht
  int pot2 = analogRead(A1); //Wert von Poti 2, Variablen werden später noch gebraucht

  //Werte Seriell senden
  Serial.print(pot1);
  Serial.print(",");
  Serial.println(pot2);

  delay(20); //kurze Pause in Milisekunden
}

Da aber dieses Programm keine Verbindung zu dem Web Editor p5.js aufbauen kann, müssen wir uns noch mit dem Programm SerialControl verbinden, welches man bei GitHub (https://github.com/p5-serial/p5.serialcontrol/releases )herunterladen kann. Zunächst benötigen wir unseren Port, dieser wird im Arduino IDE angezeigt, kann aber auch in SerialControl ausgewählt werden. Anschließend öffnen wir den Port und lassen und in der Serial Console die Werte ausgeben, dafür wählen wir "console enabled" und "read in ASCII" aus. Nun solltest du zwei Werte angezeigt bekommen, welche sich ändern sobald man die Potis dreht.

Wichtig ist das Programm im Hintergrund weiter laufen zu lassen.

Bild von SerialControl und den veränderbaren Werten

p5.js

Schlussendlich können wir auf die Editor Seite vom p5.js (https://editor.p5js.org) gehen. Dort sind im HTML Dokuement die nötigen Links bereits eingebunden um eine Verbindung zu Serial Control herzustellen. Es gibt auch die Möglichkeit diese lokas zu verlinken, da

Hier ist es noch wichtig das wir in die HTML Datei Links aus dem SerialControl einfügen, dabei ist die Reihenfolge wichtig. Man kann auch die Library lokal einbinden. In der sketch.js Datei schreiben wir jetzt unseren eigentlichen Code.

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.1/p5.js"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.1/addons/p5.sound.min.js"></script>

<script language="javascript" type="text/javascript"                 src="https://cdn.jsdelivr.net/npm/p5.serialserver@0.0.28/lib/p5.serialport.js"></script>

    <link rel="stylesheet" type="text/css" href="style.css">
    <meta charset="utf-8" />

  </head>
  <body>
    <main>
    </main>
    <script src="sketch.js"></script>
  </body>
</html>

Schritt 1: Variablen

let serial;
let pot1 = 0, pot2 = 0; //Speichert die Poti-Werte (A0 = X, A1 = Y)
let pottys = [];

Dabei ist serial für die Kommunikation mit dem Arduino zuständig. Die pot1, pot2 speichern die Werte von den Potis, welche wir zuvor in dem Programm Arduino IDE festgelegt haben. Dabei ist der Potti der an A0 angeschlossen ist für die X-Achsenrichtung und A1 für die Y-Achsenrichtung zuständig. Der Array pottys speichert alle Linien/Effekte die im aktuellen Frame gezeichnet werden sollen.

Schritt 2: Zeichenfläche

function setup() {
  createCanvas(windowWidth, windowHeight);
  colorMode(HSB, 360, 200, 100, 255);
  noStroke();
  
  // Serialport initialisieren
  serial = new p5.SerialPort();
  serial.on('data', serialEvent);
  serial.on('open', () => console.log("Port offen!"));
  serial.open("COM7");
}

Die function setup dient dazu die Voreinstellungen zu initialisieren. Hier bestimmen wir Grundlagen wie die Zeichenfläche und die Verbindung zum Arduino.

Mit createCanvas stellen wir die Größe der Zeichenfläche auf die Höhe und Breite des Browserfensters ein, der colorMode wird hier im HSB-Farbraum dargestellt, dabei stehen die 360 für den Farbton (Hue) schwarz, die 200 stehen für die Sättigung (Saturation), die 100 für die Helligkeit (Brightness) und die 255 für die Transparanz.

noStroke() entfernt die Umrisslinien der Zeichenfläche.

Anschliesßend wird der Arduino mit den verschiedenen Serial Komponenten mit eingebunden, hierbei ist wichtig das wir den Port in serial.open angeben. In unserem Fall ist das ("COM7") da wir mit einem Windows Pc gearbeitet haben, falls ihr mir einem Macbook arbeitet kann dieser Port beispielsweise so aussehen serial.open("/dev/tty.usbmodem13101").

Schritt 3: Zeichenschleife

function draw() {
  background(0, 0, 0, 80); // Trail-Effekt durch Transparenz

  let potX = map(pot1, 0, 1023, 0, width);
  let potY = map(pot2, 0, 1023, 0, height);

  pottys.push(new Potty(potX, potY, 'vertical'));
  pottys.push(new Potty(potX, potY, 'horizontal'));

  for (let i = pottys.length - 1; i >= 0; i--) {
    let p = pottys[i];
    p.update();
    p.display();
    if (p.isGone()) {
      pottys.splice(i, 1);
    }
  }
}

Die funtion draw() dient zur Aktualisierung des Bildschirms.

Der background wird auf halbtransparent schwarz gesetzt, um ein Nachleuchten des Cursers zu kreieren, dadruch entsteht ein Trail-Effekt bzw. Verblendungseffekt.

Durch das Mapping der zwei Potis potX und potY wird bestimmt das der Curser minimal bei Wert 0 und maximal bei Wert 1023 verlaufen kann. Der Wert 1023 kommt aus dem Programm SerialControl da das die Maximalen Werte von den bei uns verwendeten Potis sind.

Durch potty.push werden immer wieder neue Partikel hinzugefügt, damit es in einer durchgehenden zufälligen Bewegung bleibt und das in vertical Y-Achse und horitontal X-Achse.

Die in der for-Schleife enthaltenen p.update und p.display dienen dazu die Partikel des Cursers ständig zu aktualisieren und immer etwas auf der Zeichenfläche zu projezieren. Die folgenden Funktionen p.isGone und pottys.splice sorgen für Ordnung und überprüfen ob die Partikel noch gebraucht werden oder irrelevant geworden sind und hilft Speicher zu sparen.

Schritt 4: Daten Empfang

function serialEvent() {
  let data = serial.readLine();
  if (data) {
    let werte = split(data, ',');
    if (werte.length >= 2) {
      pot1 = int(werte[0]); //X-Richtung
      pot2 = int(werte[1]); //Y-Richtung
    }
  }
}

Die Funktion serialEvent() wird in p5.js verwendet, um die Daten automatisch von Serial Control oder direkt vom Arduino zu empfangen. Die let data = serial.readLine() liest die Daten dann ein, dabei ist data ein String der die Werte wie beispielsweise "512,300" annimmt, das sind die Werte die in dem Programm SerialControl angezeigt werden. Die angrenzende if - Schleife überprüft ob tatsächlich auch Daten empfangen werden. Die split(data, ',') teilt die ankommenden Werte dann bei jedem Komma auf, um die werte[0] und werte[1] abgrenzen zu können. Dabei überprüft die werte.length <= 2 das auch zwei Werte empfangen werden, anschließend werden in den Variablen pot1 und pot2 diese Ganzzahlwerte gespeichert.

Schritt 5: Partikel Klasse

class Potty {
  constructor(x, y, direction) {
    this.pos = createVector(x, y);  //Position wird mit Koordinaten x, y erstellt
    this.direction = direction;     //Richtung horizontal oder vertical 

    if (direction === 'vertical') { //wenn Vertical
      let dir = random([1, -1]);
      this.vel = createVector(0, dir * random(1, 2));
    } 

    else {                          //wenn nicht vertical
      let dir = random([1, -1]);
      this.vel = createVector(dir * random(1, 2), 0);
    }

    this.lifespan = 255;
    this.size = random(10, 30);
    this.hue = random(0, 360);
    this.rotationSpeed = random(-0.05, 0.05);
  }

  update() {
    this.vel.rotate(this.rotationSpeed);
    this.pos.add(this.vel);
    this.lifespan -= 10;
  }

  display() {
    fill(this.hue, 80, 100, this.lifespan);
    ellipse(this.pos.x, this.pos.y, this.size);
  }

  isGone() {
    return this.lifespan <= 0;
  }
}

Schlussendlich benötigen wir noch eine class Potty welche für die kleinen, graphischen Partikel zuständig sind welche sich bewegen, verändern und irgendwann verschwinden.

Dabei initialisiert constructor(x, y, direction) die Position (x oder y) und die Richtung (vertical oder horizontal) der Partikel. Die Methoden this.pos und this.direction speichern dann die Position und Richtung der Partikel.

In der if-else-Schleife wird die Geschwindigkeit und die Richtung der Partikel zufällig erzeugt.

Wenn die generelle Richtung vertikal ist, dient dir = random([1, -1]) zu zufällig zu entschieden ob sich die Partikel nach oben oder nach unten bewegen und createVector(0, dir * random(1, 2)) gibt die Geschwindigkeit auf der Y-Achse an. Wenn die allgemeine Richtung horizontal ist, benötigen wir die gleichen Funktionen um die Richtung (oben/unten) und Geschwindigkeit zufllig zu bestimmen.

this.lifespan = 255 entscheidet über die Lebensdauer der Partikel, dieser Wert wird mit der Zeit reduziert, deswegen verschwinden die Partikel dann.

this.size = random(10, 30) generiert eine zufällige Größe zwischen 10 und 30 Pixel.

this.hue = random(0, 360) entscheidet über den Farbtons des Partikels aus einem Farbspektrum wobei 0 für Rot und 360 wieder für Rot steht.

this.rotationSpeed = random(-0.05, 0.05) bestimmt die Rotationsgeschwindigkeit des Partikels.

Mithilfe von update() werden die Eigenschaften der Partikel gefestigt und aktualisiert also die Bewegung (this.vel.rotate(this.rotationSpeed)) , Drehung ( this.pos.add(this.vel) )und Lebenszeit( this.lifespan -= 10 ).

display() ist dazu da die Partikel zu zeichnen, wobei bei fill() mit this.hue der Farbton, 80 die Sättigung, 100 die Helligkeit und this.lifespan die Lebensdauer ist.

ellipse(this.pos.x, this.pos.y, this.size) zeichnet einen Kreis/Elipse an Postion this.pos mit dem Durchmesser this.size.

Zu letzt wird mit isGone() geprüft ob die Lebensdauer aufgebraucht ist und anschließend aus dem Array gelöscht wird.

Quellenangaben

Hier kommst du direkt zum Code im Web Editor p5.js.

Unsere Inspiration findet ihr hier und hier.

Zum einrichten von Serial Control hatten wir hilfe von hier und hier.

Viel Spaß beim Nachmachen und Ausprobieren.

Schlagworte


© 0t1

Cookies

0t1 mag keine Kekse (und kein Tracking). Wir verwenden lediglich notwendige Cookies für essentielle Funktionen.

Wir verwenden Schriftarten von Adobe Fonts. Dafür stellt dein Browser eine Verbindung zu den Servern von Adobe in den USA her. Wenn du unsere Seite nutzen möchtest, musst du dich damit einverstanden erklären.

Weitere Informationen in unserer Datenschutzerklärung.