I tried to apply for the "Sketch viewing gathering" on an online event of creative coding "Processing Community Hangout Japan vol.2" that held on Sunday, July 5, 2020 (Japan Standard Time).

The theme of this "Sketch viewing gathering" is "Tanabata".

By ★Kumiko★ from Tokyo, Japan - 七夕, CC BY-SA 2.0, Link

## A challenge.

I challenged two thingｓ this time.

### 1. Real-time animation with multiple scenes.

I used to make loop animation with one scene. And I don't like global variables, so I did not write real-time playing animation sketches.

A loop animation example.

### 2. Poetic expression.

I tried to make images in poetic expression, not storytelling.
When I write code, I always start with the idea of an interesting algorithm or mathematical formula, not final images. I tried to start with the final images and moving this time.

## Points that I worked out.

After seeing Naoto Hieda's presentation at "Creative Coding Online Meetup 1" in April, I learned how to combine multiple scenes using PGraphics, and I used it to layer scenes.

PGraphics bg; // Background starry sky
PGraphics tz; // Tanzaku

~

image(bg, 0.0, 0.0);
image(tz, 0.0, 0.0);

I used OOP Polymorphism to draw a wish on Tanzaku (strip of paper).
fds[0] = new RandomWalker(ngs[0], 3);
fds[1] = new RandomFlower(ngs[1]);

~

for (int i = 0; i < toIndex; i++) {
fds[i].move();
image(ngs[i], width * i / ngNum, 0.0);
}

I cut Tanzaku drawn image in pieces and scattered these pieces.
In p5.js, you can do it with 'to_layer.copy(from_layer)'. But in Processing, to_layer is fixed to the main canvas. So I used to_layer.image(get(main_canvas)).
kfs[i][j].beginDraw();
kfs[i][j].image(
get(
kfW * i, kfH * j,
kfW, kfH
),
0.0, 0.0);
kfs[i][j].endDraw();

I drew Milky Way with recursion formula and Vector Field.
for (int j = 0; j < 200; j++) {
float plotRatio = map(j, 0, 200, 0.0, 1.0);
ex += 0.2 * cos(TWO_PI * (sin(TWO_PI * paramA * py) - cos(TWO_PI * paramB * px)));
ey += 0.2 * sin(TWO_PI * (sin(TWO_PI * paramC * px) - cos(TWO_PI * paramD * py)));
es = sin(PI * plotRatio) * 1.0;
_p.ellipse(ex, ey, es, es);
px = ex;
py = ey;
}

An inspiration for this is Korin Ogata: "Kohakubai-zu byobu".

## Reviewing.

### Well composed? or not?

I felt hard to determine the composition of this image. I must study design.

### Witless code design.

I wrote a class to draw something, and also I wrote a function to draw. And there was no reason to use these properly.
If I read this code 6 months later, I should be confused and have problems...

### Not multi canvas size ready.

At first, I tried to handle multi canvas size to calculate object location with the ratio of width-height. But it was too hard to calculate every object size, line thickness, etc. So I gave up.

## The code.

I used Japanese font in this code. And text drawing (Waka Poetry) in Japanese.

/*** WARNING! environment-dependent ***/
textFont(createFont("KouzanBrushFontSousyo", 58, true));

Utayomi(int _start, int _stop) {
ku[0] = "ひさかたの";     //  5  5
ku[1] = "天つしるしと";   //  7  6
ku[2] = "水無し川";       //  5  4
ku[3] = "隔てて置きし";   //  7  6
ku[4] = "神代し恨めし";   //  7  6

``````
/**
* Tanab@ta
* for Processing Community Hangout Japan #02
*
* Processing 3.5.3
* @author @deconbatch
* @version 0.1
* created 0.1 2020.07.04
*/

int ngNum    = 5;  // Negaigoto = Tanzaku number
int kfwNum   = 10; // Kami Fubuki column number
int kfhNum   = 16; // Kami Fubuki row number
int frmRate  = 30; // frameRate
int[] timing = {   // switching timing
frmRate * 3,
frmRate * 8,
frmRate * 26,
frmRate * 40,
frmRate * 60
};

// layer
PGraphics bg; // Background starry sky
PGraphics tz; // Tanzaku
PGraphics sz; // Seiza
PGraphics ngs[]   = new PGraphics[ngNum];          // Negaigoto

// drawing objects
PGraphics kfs[][] = new PGraphics[kfwNum][kfhNum]; // Kami Fubuki
PVector   kfl[][] = new PVector[kfwNum][kfhNum];   // Kami Fubuki Location
Fude      fds[]   = new Fude[ngNum];               // Negaigoto no Fude
Constellation syokujyo, kengyuu; // Seiza
Utayomi       uta;               // Waka

/**
* Fude
* drawing methods of Negaigoto.
*/
public abstract class Fude {
PGraphics p;
Fude(PGraphics _p) {
p = _p;
}
public abstract void move();
}

public class Ripples extends Fude {
int rNum = 20;
ArrayList<PVector> ripples = new ArrayList<PVector>();
Ripples(PGraphics _p) {
super(_p);
}

ripples.add(new PVector(random(0.25, 0.75) * p.width, random(0.2, 0.8) * p.height, 0.0));
}

public void move() {
if (ripples.size() < rNum && random(1.0) < 0.2) {
}
p.beginDraw();
p.background(0.0, 0.0, 0.0, 0.0);
p.noFill();
p.strokeWeight(1.0);
for (int i = 0; i < ripples.size(); i++) {
PVector r = ripples.get(i);
r.z += 1.5;
if (r.z > 100.0) {
ripples.remove(i);
} else {
p.stroke(0.0, 0.0, 20.0, 100.0 - r.z);
for (float eR = r.z; eR > 2; eR -= 15) {
p.ellipse(r.x, r.y, eR, eR);
}
}
}
p.endDraw();
}
}

public class RandomFlower extends Fude {
int fNum = 15;
ArrayList<PVector> flows = new ArrayList<PVector>();
RandomFlower(PGraphics _p) {
super(_p);
}

flows.add(new PVector(random(0.2, 0.8) * p.width, random(p.height), random(40.0, 70.0)));
}

public void move() {
if (flows.size() < fNum && random(1.0) < 0.2) {
}
p.beginDraw();
p.background(0.0, 0.0, 0.0, 0.0);
p.rectMode(CENTER);
p.noFill();
p.strokeWeight(1.0);
for (int i = 0; i < flows.size(); i++) {
PVector r = flows.get(i);
r.x += sin(i * TWO_PI / flows.size());
r.y += 0.5;
r.z -= 1.0;
if (r.z <= 0.0) {
flows.remove(i);
} else {
for (float eR = r.z; eR > r.z - 15.0; eR -= 5.0) {
p.stroke(0.0, 0.0, 0.0, map(eR, 70.0, 0.0, 60.0, 0.0));
p.pushMatrix();
p.translate(r.x, r.y);// - eR * eR * 0.025);
p.rotate(eR * 0.2);
p.ellipse(0.0, 0.0, eR, eR * 0.25);
p.ellipse(0.0, 0.0, eR * 0.25, eR);
p.popMatrix();
}
}
}
p.endDraw();
}
}

public class RandomWalker extends Fude {
int corners;
int   walkerMax;
int   tailMax;
float sideLen;
ArrayList walkers = new ArrayList();

RandomWalker(PGraphics _p, int _corners) {
super(_p);
corners = _corners;
walkerMax = 8; //21 - corners * 2;
tailMax   = 30; //corners * 3; // walker's tail
sideLen   = 8.0 - corners * 1.0;

for (int i = 0; i < walkerMax; i++) {
float wC = (50.0 * i) % 360.0;
float wD = TWO_PI / corners;
int   wR = 0; //floor(random(corners));
float wX = 0.0; //sideLen * corners * sin(wD) * i;
float wY = sideLen * corners * sin(wD) * 2.0 * (i - walkerMax * 0.5); //sideLen * corners * i - (sideLen * corners * walkerMax) * 0.5;
ArrayList<Walker> tails = new ArrayList<Walker>();
for (int j = 0; j < tailMax; j++) {
tails.add(new Walker(wX, wY, wC, wD, wR));
}
}

}

public void move() {
p.beginDraw();
p.background(0.0, 0.0, 0.0, 0.0);
p.translate(p.width * 0.5, p.height * 0.5);
p.strokeWeight(4 - corners % 4);
p.strokeJoin(ROUND);
p.strokeCap(SQUARE);
p.noFill();

for (int i = 0; i < walkers.size(); i++) {
float wX = 0.0;
float wY = 0.0;
float wC = 0.0;
float wD = 0.0;
int   wR = 0;
float wA = 0.0;

ArrayList<Walker> tails = (ArrayList)walkers.get(i);
for (Walker walker : tails) {
wX = walker.x;
wY = walker.y;
wC = walker.colour;
wR = walker.rotateCnt;
wA += 2.0;

p.stroke(0.0, 0.0, 100.0, wA);
p.beginShape();
p.vertex(wX, wY);
for (int j = 0; j < corners; j++) {
// it makes a straight line between corners
wX += sideLen * cos(wR * wD);
wY += sideLen * sin(wR * wD);
p.vertex(wX, wY);
}
p.endShape();
}

if (abs(wX) < p.width * 0.35 && abs(wY) < p.height * 0.35) {
if (random(1.0) < 0.5) {
--wR; // not turn
} else {
if (random(1.0) < 0.1) {
wD *= -1.0; // turn
}
}
}

++wR;
wR %= corners;
// tail shift
tails.add(new Walker(wX, wY, wC, wD, wR));
tails.remove(0);
}
p.endDraw();
}

private class Walker {
public float x, y;
public float colour;
public int   rotateCnt;

Walker(float _x, float _y, float _c, float _d, int _r) {
x = _x;
y = _y;
colour = _c;
rotateCnt = _r;
}
}
}

/**
* Constellation
* drawing constellation of the Lyra and the Aquila.
*/
public abstract class Constellation {
PGraphics p;
ArrayList<PVector> stars = new ArrayList<PVector>();
ArrayList<Integer> lines = new ArrayList<Integer>();
float hueVal;
float initX, initY;
float sizeMult;
int beat;

Constellation(PGraphics _p, float _c, float _x, float _y, float _s, int _b) {
p = _p;
hueVal = _c;
initX = _x;
initY = _y;
sizeMult = _s;
beat = _b;
setStars();
setLines();
}

abstract public void setStars();
abstract public void setLines();

public void draw() {

p.beginDraw();
p.pushMatrix();

p.blendMode(SCREEN);
p.rectMode(CENTER);
p.translate(initX, initY);

p.noStroke();
for (PVector s : stars) {
for (int i = 0; i < 4; i++) {
p.fill(0.0, 0.0, 50.0 - i * 10.0, 100.0);
p.pushMatrix();
p.translate(s.x * sizeMult, s.y * sizeMult);
p.rotate(PI * 0.25);
p.rect(0.0, 0.0, i * sizeMult * 0.75, i * sizeMult * 0.75);
p.popMatrix();
}
}
p.noFill();
p.strokeWeight(1.0);
p.strokeJoin(ROUND);
p.strokeCap(SQUARE);
p.stroke(0.0, 0.0, 50.0, 100.0);
p.beginShape();
for (int l : lines) {
p.vertex(stars.get(l).x * sizeMult, stars.get(l).y * sizeMult);
}
p.endShape();

p.popMatrix();
p.endDraw();

}

public void grow() {
float rate = (frameCount % beat) * 1.0 / beat;
float bri = 20.0 + 80.0 * sin(PI * rate);
float siz = sizeMult * (1.0 - sin(PI * rate)) * 5.0;

p.beginDraw();
p.blendMode(BLEND);
p.translate(initX, initY);
p.noStroke();
p.fill(hueVal, 60.0, bri, 100.0);
p.ellipse(
stars.get(0).x * sizeMult,
stars.get(0).y * sizeMult,
siz,
siz
);
p.endDraw();
}
}

public class Syokujyo extends Constellation {
Syokujyo(PGraphics _p, float _c, float _x, float _y, float _s, int _b) {
super(_p, _c, _x, _y, _s, _b);
}
public void setStars() {
}
public void setLines() {
}
}

public class Kengyuu extends Constellation {
Kengyuu(PGraphics _p, float _c, float _x, float _y, float _s, int _b) {
super(_p, _c, _x, _y, _s, _b);
}
public void setStars() {
}
public void setLines() {
}
}

/**
* Utayomi
* drawing the Waka.
*/
public class Utayomi {
int     kuNum    = 5;                  // Ku number (it may be bad name)
String  ku[]     = new String[kuNum];  // Ku text
PVector locate[] = new PVector[kuNum]; // drawing location
int     yomi[]   = new int[kuNum];     // for drawing animation
int     timing[] = new int[kuNum * 2]; // switching timing
Utayomi(int _start, int _stop) {
ku[0] = "ひさかたの";     //  5  5
ku[1] = "天つしるしと";   //  7  6
ku[2] = "水無し川";       //  5  4
ku[3] = "隔てて置きし";   //  7  6
ku[4] = "神代し恨めし";   //  7  6

locate[0] = new PVector(width * 0.825, height * 0.6);
locate[1] = new PVector(width * 0.77,  height * 0.55);
locate[2] = new PVector(width * 0.715, height * 0.675);
locate[3] = new PVector(width * 0.3,   height * 0.775);
locate[4] = new PVector(width * 0.235, height * 0.825);

// 11.5 : frmRate * 2(yomihajime) + frmRate * 4.5(interval) + frmRate * 5(yoin)
// 7 : frmRate * 6.2(yomi)
int frmLen = _stop - _start;
if (frmLen - frmRate * 11.5 < frmRate * 7) { // enough time?
for (int i = 0; i < kuNum; i++) {
timing[i * 2] = 0;
yomi[i] = ku[i].length();
}
} else {
timing[0] = floor(_start + frmRate * 2);
timing[1] = floor(timing[0] + frmRate * 1.0);

timing[2] = floor(timing[1] + frmRate * 1.0);
timing[3] = floor(timing[2] + frmRate * 1.4);

timing[4] = floor(timing[3] + frmRate * 1.0);
timing[5] = floor(timing[4] + frmRate * 1.2);

timing[6] = floor(timing[5] + frmRate * 1.5);
timing[7] = floor(timing[6] + frmRate * 1.2);

timing[8] = floor(timing[7] + frmRate * 1.0);
timing[9] = floor(timing[8] + frmRate * 1.4);
for (int i = 0; i < kuNum; i++) {
yomi[i] = 0;
}
}
}

public void utau() {
fill(0.0, 0.0, 90.0, 100.0);
textAlign(CENTER, TOP);
for (int i = 0; i < kuNum; i++) {
int t = i * 2;
if (frameCount > timing[t] && frameCount < timing[t + 1]) {
yomi[i] = ceil(map(frameCount, timing[t], timing[t + 1], 0, ku[i].length()));
}
text(ku[i].substring(0, yomi[i]), locate[i].x, locate[i].y, 62.0, height);
}
}
}

/**
* world famous Setup and Draw
*
*/
public void setup() {
size(1280, 720);
colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
rectMode(CENTER);
smooth();
frameRate(frmRate);

/*** WARNING! environment-dependent ***/
textFont(createFont("KouzanBrushFontSousyo", 58, true));

// initialize layers
bg = pgInit();
tz = pgInit();
sz = pgInit();
for (int i = 0; i < ngNum; i++) {
ngs[i] = ngInit();
}

// initialize drawing objects
for (int i = 0; i < kfwNum; i++) {
for (int j = 0; j < kfhNum; j++) {
kfs[i][j] = kfInit();
}
}

fds[0] = new RandomWalker(ngs[0], 3);
fds[1] = new RandomFlower(ngs[1]);
fds[2] = new RandomWalker(ngs[2], 4);
fds[3] = new Ripples(ngs[3]);
fds[4] = new RandomWalker(ngs[4], 6);

syokujyo = new Syokujyo(sz, 350.0, width * 0.225, height * 0.165, 5.5, frmRate * 5);
kengyuu  = new Kengyuu(sz, 220.0, width * 0.825, height * 0.775, 5.5, frmRate * 4);

uta = new Utayomi(timing[3], timing[4]);

// initial draw
darkSky(bg);
syokujyo.draw();
kengyuu.draw();
amanogawa(sz);

}

public void draw() {

background(0.0, 0.0, 0.0, 100.0);

if (frameCount < timing[0]) {
image(bg, 0.0, 0.0);
} else if (frameCount < timing[1]) {
// Ki
insertTanzaku(tz, map(frameCount, timing[0], timing[1] - frmRate, 0.0, 1.0));
image(bg, 0.0, 0.0);
image(tz, 0.0, 0.0);
} else if (frameCount < timing[2]) {
// Syou
image(bg, 0.0, 0.0);
image(tz, 0.0, 0.0);
drawNegaigoto(timing[1], timing[2]);
if (frameCount == timing[2] - 1) {
cutTanzaku();
}
} else if (frameCount < timing[3]) {
// Ten
image(bg, 0.0, 0.0);
syokujyo.grow();
kengyuu.grow();
image(sz, 0.0, 0.0);
drawKamifubuki((frameCount - timing[2]) * 0.05);
} else if (frameCount < timing[4]) {
// Ketsu
image(bg, 0.0, 0.0);
syokujyo.grow();
kengyuu.grow();
image(sz, 0.0, 0.0);
uta.utau();
} else {
exit();
}

}

/**
* pgInit
* main canvas size layer initialization.
*/
public PGraphics pgInit() {
PGraphics p = createGraphics(width, height);
p.beginDraw();
p.colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
p.background(0.0, 0.0, 0.0, 0.0);
p.endDraw();
return p;
}

/**
* ngInit
* Negaigoto size layer initialization.
*/
public PGraphics ngInit() {
PGraphics p = createGraphics(width / ngNum, height);
p.beginDraw();
p.colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
p.background(0.0, 0.0, 0.0, 0.0);
p.endDraw();
return p;
}

/**
* kfInit
* Kami Fubuki size layer initialization.
*/
public PGraphics kfInit() {
PGraphics p = createGraphics(width / kfwNum, height / kfhNum);
p.beginDraw();
p.colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
p.background(0.0, 0.0, 0.0, 0.0);
p.endDraw();
return p;
}

/**
* darkSky
* draw dark starry sky.
*/
public void darkSky(PGraphics _p) {
_p.beginDraw();
_p.background(0.0, 0.0, 0.0, 100.0);
_p.noStroke();
for (int x = 0; x < width; x += 10) {
for (int y = 0; y < height; y += 10) {
_p.fill(
map(noise(y * 0.005, x * 0.005), 0, 1, 220.0, 260.0),
60.0,
noise(x * 0.002, y * 0.002) * 60.0,
100.0
);
_p.rect(x, y, 10.0, 10.0);
if (random(1.0) < 0.1) {
_p.fill(random(20.0, 60.0), random(80.0), random(60.0), 100.0);
_p.ellipse(x, y, 2.0, 2.0);
}
}
}
_p.endDraw();
}

/**
* amanogawa
* draw milky way with points and flowing lines.
*/
public void amanogawa(PGraphics _p) {

float paramA = random(0.5, 1.0) / height;
float paramB = paramA * 0.2;
float paramC = random(0.5, 1.0) / width;
float paramD = paramC * 0.2;

_p.beginDraw();
_p.translate(_p.width * 0.3, 0.0);
_p.blendMode(SCREEN);
_p.noStroke();
for (int y = 0; y < _p.height; y += 2) {
float wave = -cos(TWO_PI * (0.2 + y * 0.8 / _p.height)) * _p.width * 0.2;
float rW = _p.width * 0.55 * (0.1 + y * 0.9 / _p.height);
for (int i = 0; i < floor(rW * 0.02); i++) {
float ex = random(wave, wave + rW);
float ey = y;
float es = random(1.0, 3.0);
_p.fill(60.0, random(60.0), random(30.0, 90.0), 100.0);
_p.ellipse(ex, ey, es, es);

float px = ex;
float py = ey;
_p.fill(60.0, random(40.0), random(5.0, 20.0), 100.0);
for (int j = 0; j < 200; j++) {
float plotRatio = map(j, 0, 200, 0.0, 1.0);
ex += 0.2 * cos(TWO_PI * (sin(TWO_PI * paramA * py) - cos(TWO_PI * paramB * px)));
ey += 0.2 * sin(TWO_PI * (sin(TWO_PI * paramC * px) - cos(TWO_PI * paramD * py)));
es = sin(PI * plotRatio) * 1.0;
_p.ellipse(ex, ey, es, es);
px = ex;
py = ey;
}
}
}
_p.endDraw();
}

/**
* insertTanzaku
* Tanzaku inseting animation.
*/
public void insertTanzaku(PGraphics _p, float _ratio) {
// Tanzaku 5 colors. Blue, Red, Yellow, White, Black(Purple)
float[][] c =
{
new float[]{220.0, 60.0, 90.0},
new float[]{0.0, 40.0, 90.0},
new float[]{60.0, 50.0, 90.0},
new float[]{0.0, 0.0, 90.0},
new float[]{260.0, 60.0, 70.0}
};
float w = width / ngNum;

_p.beginDraw();
_p.background(0.0, 0.0, 0.0, 0.0);
_p.rectMode(CENTER);
_p.translate(w * 0.5, 0.0);
_p.noStroke();
for (int i = 0; i < ngNum; i++) {
float y = constrain(height * (1.0 - _ratio * map(i, 0, 4, 3.0, 1.0)), -10.0, height + 10.0);
_p.fill(c[i][0], c[i][1], c[i][2], 100.0);
_p.pushMatrix();
_p.translate(i * w, height * 0.5);
_p.rotate(PI * (i % 2));
_p.rect(0.0, y, w, height + 20.0);
_p.popMatrix();
}
_p.endDraw();
}

/**
* drawNegaigoto
* draw each Negaigoto.
*/
public void drawNegaigoto(int _start, int _stop) {
float div = (_stop - _start) * 1.0 / (ngNum + 2);
int toIndex = constrain(ceil((frameCount - _start) / div), 1, ngNum);
for (int i = 0; i < toIndex; i++) {
if (frameCount % 2 == 0) { // speed down
fds[i].move();
}
image(ngs[i], width * i / ngNum, 0.0);
}
}

/**
* cutTanzaku.
* cut the Tanzaku into Kami Fubuki.
*/
public void cutTanzaku() {
for (int i = 0; i < kfwNum; i++) {
for (int j = 0; j < kfhNum; j++) {
int kfW = floor(kfs[i][j].width);
int kfH = floor(kfs[i][j].height);
kfs[i][j].beginDraw();
kfs[i][j].image(
get(
kfW * i, kfH * j,
kfW, kfH
),
0.0, 0.0);
kfs[i][j].endDraw();
kfl[i][j] = new PVector(kfW * i, kfH * j);
}
}
}

/**
* drawKamifubuki
* draw Kami Fubuki animation.
*/
public void drawKamifubuki(float _time) {
for (int i = 0; i < kfwNum; i++) {
for (int j = 0; j < kfhNum; j++) {
float nFactor = i * 10.0 + j;
float kfW = kfs[i][j].width;
float kfH = kfs[i][j].height * cos(HALF_PI * noise(nFactor) * _time);

kfl[i][j].x += (0.5 + map(noise(nFactor, _time * 0.5, 100.0), 0.0, 1.0, -2.0, 2.0)) * _time;
kfl[i][j].y += (0.5 + map(noise(nFactor, _time * 0.5, 200.0), 0.0, 1.0, -2.0, 2.0)) * _time;
pushMatrix();
translate(kfl[i][j].x + kfW * 0.5, kfl[i][j].y + kfH * 0.5);
rotate(noise(nFactor, _time, 300.0) * _time * 0.3);
image(kfs[i][j], -kfW * 0.5, -kfH * 0.5, kfW, kfH);
popMatrix();
}
}
}

/*

This program is free software: you can redistribute it and/or modify