Digitales Prototyping: p5.js: Push & Pop Rotation / 2020-04-16 / Matthias Edler-Golla, CC BY-SA 4.0


|



 



Hallo!

Das gerade geöffnete Script mit meinen Anmerkungen dazu als Film…


Themen heute

  • Externe Funktionen (incl. Return)
  • Verschieben von Elementen mit push & pop
  • Drehen von Elementen mit push & pop
  • Rotieren von Elementen mit push & pop
  • Animiertes Rotieren von Elementen mit push & pop
  • Analoge Sekunden-Anzeige
  • Übungsaufgabe: Minuten- und Stundenanzeige

Externe Funktionen

Externe Funktionen helfen Euch, Euren Code übersichtlicher und einfacher lesbar zu machen:

Demo

Schaut Euch die Demo an – oder ladet das komplette Paket runter!

p5.js

function setup() {
  // …

  // ================================
  // einfaches Auslagern von Aufgaben
  // gut für die Lesbarkeit!
  // ================================

  weisserKreis();

  rotesQuadrat();

  willkommen();
}

// ============= externe Funktionen =============

function weisserKreis() {
  strokeWeight(5);
  stroke("blue");
  fill("white");
  ellipse(50, 60, 80);
}

function rotesQuadrat() {
  noStroke();
  fill("red");
  rect(120, 20, 120);
}

function willkommen() {
  textFont("Roboto Slab");
  textSize(24);
  noStroke();
  fill("black");
  text("Willkommen!", 280, 70);
}

Externe Funktion mit Werteübergabe

DRY (Don’t repeat yourself): Redundant vorhandene Informationen (z. B. Code-Duplikate im Quelltext) sind aufwändig zu pflegen. Bei Systemen, die dem DRY-Prinzip treu bleiben, brauchen Änderungen nur an einer Stelle vorgenommen zu werden

Externe Funktionen haben auch die tolle Eigenschaft, immer wieder verwendbar zu sein, wenn man ihnen Werte übergibt. Damit muss man deutlich weniger Code schreiben und wiederholt sich nicht!

Demo

Schaut Euch die Demo an – oder ladet das komplette Paket runter!

Einfache Beispiel mit der Übergabe von zwei Wert

function setup() { 
  // roter Kreis
  kreisZeichnen("red", 50);

  // grüner kreis
  kreisZeichnen("rgb(0,185,100)", 150);
}

// externe Funktion mit 2 übergebenen Werten (fuellFarbe, xPos)
function kreisZeichnen(fuellFarbe, xPos) {
  strokeWeight(5);
  noStroke();
  fill(fuellFarbe);
  ellipse(xPos, 60, 80);
}

Komplexeres Beispiel mit Übergabe von mehreren Werten

function setup() {
  // ACHTUNG: Die Reihenfolge muss EXAKT eingehalten werden!
  // Angaben: strichStaerke, strichFarbe, fuellFarbe, xPos, yPos, durchMesser
  kreisZeichnenKomplex(3, "blue", "orange", 340, 60, 60);

  kreisZeichnenKomplex(8, "hsl(200,50%,50%)", "#000", 430, 60, 90);

  // eine Loop-Funktion zeichnet so viele Kreise wie hier angegeben
  vieleKreiseZeichnen(18);
}

function kreisZeichnenKomplex(strichStaerke, strichFarbe, fuellFarbe, xPos, yPos, durchMesser){
  strokeWeight(strichStaerke);
  stroke(strichFarbe);
  fill(fuellFarbe);
  ellipse(xPos, yPos, durchMesser);
}

function vieleKreiseZeichnen(anzahl){
  // anzahl Kreise zeichnen
  // "20 + 30 * i" sorgt dafür, dass die Kreise waagrecht nebeneinander stehen
  for (var i = 0; i <= anzahl; i++) {
    kreisZeichnenKomplex(6, 120, "rgb(0,255,255)", 20 + 30 * i, 170, 20);
  }
}

Externe Funktion mit Return

Mit return könnt Ihr eine externe Funktion „arbeiten lassen“ –
und sie schickt Euch das Resultat ihrer Arbeit zurück („returniert diese“)…

Demo

Schaut Euch die Demo an – oder ladet das komplette Paket runter!

p5.js

function setup() {
  // …
}

function draw() {
  // …

  // Ausgabe der Flächengröße oberhalb des Rechtecks
  groessenAngabe();
}

function groessenAngabe() {
  // …

  // "flaechenBerechnung()" ist eine externe Funktion, die die Fläche ausrechnet
  // und den Wert zurückgibt
  text("Fläche des Rechtecks in Quadrat-px: " + flaechenBerechnung(), 10, 20);
}

function flaechenBerechnung() {
  let breite = mouseX - x;
  let hoehe = mouseY - y;
  let flaeche = Math.round(breite * hoehe);

  // nächste Zeile "schickt" den Wert zurück zur Ausgangsfunktion
  return flaeche;
}

Übungsaufgabe zu den externen Funktionen

Baut den oben gezeigten Sketch nach und verwendet dazu eine externe Furnktion, die alle 3 Kreise zeichnet!

Ladet Euch dazu das Übungspaket runter – dort befinden sich schon die Anfänge des erforderlichen Codes:

function setup() {
  const meinCanvas = createCanvas(600, 200);
  meinCanvas.parent("canvasContainer");
  background("#ddd");

  // Füllfarbe, X-Position, Y-Position, Durchmesser übergeben!
  kreisZeichnen();
  kreisZeichnen();
  kreisZeichnen();
}

// externe Funktion mit übergebenen Werten
function kreisZeichnen() {
  noStroke();
  // was muss hier rein?
}

Was müsst Ihr einfügen, damit die externe Function "kreisZeichnen()" die 3 Kreise zeichnet?


Push & Pop Grundlagen

Code zwischen push() und pop() muss man sich wie eine eigene Photoshop-Ebene vorstellen, die mit dem Code außenrum nichts zu tun hat…

Demo

Schaut Euch die Demo an – oder ladet das komplette Paket runter!

p5.js

function setup() {
  // …

  const start = 100;
  const d = 80;

  // Ellipse mit Default-Settings
  strokeWeight(4);
  stroke("red");
  ellipse(start, 50, d, d);

  // mit "push()" aus normalem "Code-Flow" rausnehmen
  push(); 
  strokeWeight(12);
  stroke("black");
  fill(204, 153, 0);
  ellipse(start + 2 * d, 50, d, d); 

  push();
  stroke(0, 102, 153);
  ellipse(start + 3 * d, 50, d, d); 
  pop(); 

  // Ende des speziellen "Codes"
  pop(); 

  // ab hier wieder normaler "Code-Flow"
  // deswegen Ellipse wieder mit Einstellungen, die vor push & pop festgelegt wurden
  ellipse(start + 5 * d, 50, d, d);
}

Elemente verschieben mit Push & Pop

Oft ist es einfacher, Elemente ganz "normal" am oberen Rand des Canvas zu konstruieren und diese dann mit push() und pop() an die Stelle zu schieben, wo man diese braucht. Im nächsten Beispiel seht Ihr, dass man diese mit push() und pop() auch drehen kann…

Demo

Schaut Euch die Demo an – oder ladet das komplette Paket runter!

p5.js

function setup() {
  // …

  // 3 ganz normale Elemente, die am oberen Rand kleben
  fill("blue");
  stroke("blue");
  ellipse(0, 0, 10);
  line(0, 0, 0 + 200, 0);
  ellipse(0 + 200, 0, 10);

  // 3 Elemente, die mit "translate()" verschoben werden
  push();
  translate(20, 20);

  stroke("orange");
  fill("orange");

  // Beachtet, das die x- und y-Werte identisch wie bei den oberen Elementen sind!
  // nur durch "translate(20,20)" werden sie gemeinsam verschoben
  // das ist oft vom Kopf einfacher als immer alles umrechnen zu müssen…
  ellipse(0, 0, 10);
  line(0, 0, 0 + 200, 0);
  ellipse(0 + 200, 0, 10);
  pop();

  // …
}

Elemente drehen mit Push & Pop

"push()" nimmt alles, was bis "pop()" hier steht, aus dem Zusammenhang und verhindert so, dass die "rotate()" Angabe auch auf die 3. Linie und das 3. Rechteck angewandt werden

Deaktiviert mal push() und pop() und seht, was dann passiert!

Demo

Schaut Euch die Demo an – oder ladet das komplette Paket runter!

p5.js

function setup() {
  // …

  stroke("red");
  line(5, 2, 95, 2);

  rectMode(CENTER);
  stroke("#ddd");

  fill("black");
  rect(50, 50, 90);

  // "push()" nimmt alles, was bis "pop()" hier steht,
  // aus dem Zusammenhang und verhindert so,
  // dass die "rotate()" Angabe auch auf die 3. Linie und
  // das 3. Rechteck angewandt werden

  // Deaktiviert mal push() und pop() und seht, was dann passiert!

  push();

  // Drehpunkt ist die obere, linke Ecke des Canvas
  // wenn man es nicht mit "translate()" definiert
  // Drehung um -35° Grad
  rotate(radians(-35));

  stroke("red");
  line(5, 120, 95, 120);

  stroke("#ddd");
  rectMode(CENTER);
  rect(50, 170, 90);

  pop();

  stroke("red");
  line(205, 120, 295, 120);

  noStroke();
  rectMode(CENTER);
  rect(250, 170, 90);
}

Elemente rotieren mit Push & Pop

Im unten gezeigten for-loop wird in jeder Schleife das Koordinaten-System um den vorgegebenen Wert "winkel" um den Mittelpunkt des Canvas gedreht.

Demo

Schaut Euch die Demo an – oder ladet das komplette Paket runter!

p5.js

let winkel;

function setup() {
  // …

  const xMitte = width / 2;
  const yMitte = height / 2;

  const laenge = 140; // Länge der Linie

  // 3 ganz normale Elemente, die am oberen Rand kleben
  fill("blue");
  stroke("blue");
  ellipse(0, 0, 10);
  line(0, 0, 0 + laenge, 0);
  ellipse(0 + laenge, 0, 10);

  // gleiche Elemente, aber mit push & pop verschoben und gedreht
  push();

  fill("orange");
  stroke("orange");

  // verschieben um jeweils 20px
  translate(20, 20);

  // drehen des Koordinaten-Systems um 20°
  // der Drehpunkt liegt hier bei (20,20);
  // der Drehpunkt liegt hier bei (20,20);
  rotate(radians(20));

  ellipse(0, 0, 10);
  line(0, 0, 0 + laenge, 0);
  ellipse(0 + laenge, 0, 10);

  pop();

  // mit for-Loop viele Drehungen erzeugt
  // 36 * 10° = 360° (komplette Drehung)
  for (var i = 0; i < 36; i++) {
    push();

    translate(width / 2, height / 2);

    // der Drehpunkt liegt hier bei (width / 2, height / 2);
    rotate(winkel);

    fill(255, 0, 0);
    stroke(255, 0, 0);

    ellipse(0, 0, 10);
    line(0, 0, 0 + laenge, 0);

    // Sonderbehandlung der äußeren Kreise
    // ebenfalls durch push & pop „isoliert
    push();
    fill("white");
    ellipse(0 + laenge + 10, 0, 10);
    pop();

    pop();

    winkel = winkel + radians(10);
  }
}

Elemente animiert rotieren

Innerhalb der Funktion "draw()" wird bei jedem Durchlauf der Winkel um einen festgelegten Wert geändert und entsprechend das Koordinaten-System gedreht. Entsprechend ändert sich die Darstellung von Linien und Kreise.

Durch den Einsatz von random ändern sich Winkel, Durchmesser und Länge des Striches.

Demo

Schaut Euch die Demo an – oder ladet das komplette Paket runter!

p5.js

let xMitte, yMitte;
let laenge = 140;
let step = 5;
let winkel = 0;
let d = 10;

function setup() {
  // …

  // Gradangabe, um wieviel gedreht wird
  winkel = radians(0);

  xMitte = width / 2;
  yMitte = height / 2;

  frameRate(30);
}

function draw() {

  push();

  // Verschieben von Kreisen und Linien in die Mitte
  translate(xMitte, yMitte);

  // animiert rotieren durch "winkel = winkel + step;"
  rotate(radians(winkel));

  // die schwarze Linie
  stroke(0);
  line(0 - laenge, 0, 0 + laenge, 0);

  // die Kreise an den Enden der Linie
  fill("white");
  ellipse(0 - laenge - d, 0, d);
  ellipse(0 + laenge + d, 0, d);

  // roter Kreis in der Mitte
  strokeWeight(3);
  stroke("white");
  fill("red");
  ellipse(0, 0, d * 4);

  pop();

  winkel = winkel + step;

  if (winkel > 180) {
    background("#fff");
    winkel = 0;

    // zufällige Werte generieren
    step = random(0.5, 10);
    laenge = random(90, height / 2 - 10);
    d = random(5, 20);
  }
}

Element animiert rotieren incl. Random

Weiterentwicklung des vorherigem Beispiels – nur mit noch mehr Einsatz von random()

Demo

Schaut Euch die Demo an – oder ladet das komplette Paket runter!

p5.js

let xMitte, yMitte;
let laenge = 140;
let step = 5;
let winkel = 0;
let d = 10;

function setup() {
  const meinCanvas = createCanvas(600, 350);
    meinCanvas.parent("canvasContainer");

  // Gradangabe, um wieviel gedreht wird
  winkel = radians(0);

  xMitte = width / 2;
  yMitte = height / 2;

  frameRate(30);

  background("black");
}

function draw() {
  push();

  // Verschieben von Kreisen und Linien in die Mitte
  translate(xMitte, yMitte);

  // animiert rotieren durch "winkel = winkel + step;"
  rotate(radians(winkel));

  // die schwarze Linie
  stroke(80);
  line(0 - laenge, 0, 0 + laenge, 0);

  // die Kreise an den Enden der Linie
  // noStroke();
  fill(random(255), random(255), random(255), random(200, 255));
  ellipse(0 - laenge, 0, d);
  ellipse(0 + laenge, 0, d);

  pop();

  fill(255, 100);
  noStroke();
  // ellipse(xMitte, yMitte, 40);

  winkel = winkel + step;

  d = random(10, 40);
  laenge = random(50, width / 2);
  step = random(0.5, 15);

  // Abdeckfläche, die Sachen langsam verblassen lässt
  // geht durch "fill(0, 10);" 10 bedeutet dabei weitgehend transparent
  fill(0, 10);
  rect(0, 0, width, height);
}

Sekundenzeiger mit Push & Pop

Der Sketch holt sich die Sekunden, Minuten und Stunden aus dem System des Computers und nützt diese Zahlen, um das Koordinaten-System des jeweiligen Zeigers zu drehen. So ist es möglich, die Uhrzeit analog darzustellen.

Im hier gezeigten Beispiel wird nur der Sekundenzeiger dargestellt.

Demo

Schaut Euch die Demo an – oder ladet das komplette Paket runter!

p5.js

let winkel;
let xMitte, yMitte;
const laenge = 130;

function setup() {
  const meinCanvas = createCanvas(600, 350);
  meinCanvas.parent("canvasContainer");

  // Gradangabe, bei Sekunden ganz oben starten
  winkel = radians(270);

  // Angaben zur Schrift
  textFont("Roboto Slab"); // hier auch der Webfont von Google!

  xMitte = width / 2;
  yMitte = height / 2;

  background("#ddd");

  frameRate(1); // einmal pro Sekunde aktualisieren;
}

function draw() {
  background("#ddd");

  scalaZeichnen();

  digitalAusgabe();

  stundenZeiger(); // schon weiter unten vorbereitet! (schwierig!)
  minutenZeiger(); // schon weiter unten vorbereitet!

  sekundenZeiger();
}

function digitalAusgabe() {
  textAlign(CENTER);
  textSize(18);
  fill(255);
  noStroke();

  let sek = second();
  let min = minute();
  let stunde = hour();

  // bei einstelligen Werten noch eine "0" einfügen
  // weil sonst die Uhrzeit eigenartig aussieht
  if (sek < 10) {
    sek = "0" + sek;
  }

  if (min < 10) {
    min = "0" + min;
  }

  if (stunde < 10) {
    stunde = "0" + stunde;
  }

  text(stunde + ":" + min + ":" + sek, xMitte, yMitte - 17);
}

function scalaZeichnen() {
  // Anzeige der Mitte
  fill(60);
  noStroke();
  ellipse(xMitte, yMitte, 18);

  // fetter Dot bei den Viertelstunden
  for (var i = 0; i < 4; i++) {
    push();
    translate(width / 2, height / 2);
    rotate(winkel);
    noStroke();
    fill(60);
    ellipse(0 + laenge + 8, 0, 12);
    pop();
    winkel = winkel + radians(90);
  }

  // alle 5min ein Dot
  for (var i = 0; i < 12; i++) {
    push();
    translate(width / 2, height / 2);
    rotate(winkel);
    noStroke();
    fill(60);
    ellipse(0 + laenge + 8, 0, 8);
    pop();
    winkel = winkel + radians(30);
  }

  // alle 1min ein kleiner Dot
  for (var i = 0; i < 60; i++) {
    push();
    translate(width / 2, height / 2);
    rotate(winkel);
    noStroke();
    fill(255);
    ellipse(0 + laenge + 8, 0, 3);
    pop();
    winkel = winkel + radians(6);
  }
}

function stundenZeiger() {
  let stunde = hour();
  console.log(stunde); // Ausgabe in der Konsole

  let min = minute(); // wird hier auch gebraucht!

  // hier die Anzeige des jeweiligen Zeigers einbauen!
  // ACHTUNG: 12 Stunden-Anzeige -- Sonderfall!
}

function minutenZeiger() {
  let min = minute();
  console.log(min); // Ausgabe in der Konsole

  // hier die Anzeige des jeweiligen Zeigers einbauen!
}

function sekundenZeiger() {
  let sek = second();

  // Aussehen des Sekundenzeiger
  strokeWeight(4);
  stroke(255, 0, 0);
  fill(255, 0, 0);

  push();
  translate(xMitte, yMitte);
  // "sek * 6", weil 60sek pro Minute, entspricht 6° * 60 = 360°
  // "270" damit bei "12Uhr" angefangen wird
  rotate(radians(270 + sek * 6));
  ellipse(0, 0, 5);

  line(0, 0, laenge, 0);

  pop();
}

Übungsaufgabe zur Uhrzeit

Erweitert den vorherigen Sketch („Sekundenzeiger mit Push & Pop“) so, dass der Minuten-Zeiger und – als Königsaufgabe – auch der Stundenzeiger richtig dargestellt wird.

Der Stundenanzeiger ist schwieriger, weil dieser im Lauf der jeweiligen Stunde nicht fix an einer Stelle stehen bleibt, sondern sich langsam zwischen der aktuellen und der kommenden Stunden-Ziffer weiter bewegt…


Danke

Wir hören/sehen uns nächsten Donnerstag (23.4.2020)!

Bleibt gesund!

Weitere Vorträge: