コーディング・チャレンジ AltEdu2022 で制作したコードの数々

2022年2月5日土曜日

p5.js Processing 作例

t f B! P L

日本の Processing コミュニティ "Processing Community Japan" のコーディング・チャレンジ・イベント「AltEdu2022」が 2022年 2月の一ヶ月間行われていました。

毎日のお題など詳しくはこちらからご覧いただけます。

AltEdu2022_v1 Google スプレッドシート

多くの参加で盛り上がり、いろんな方のアイディアあふれる作品を沢山見ることが出来て、とても楽しいイベントでした。

この AltEdu2022 向けに書いた私のコードを掲載します。それぞれのライセンスは以下のとおりです。ライセンス条項にのっとってご自由にお使いください。


目次

 

Day 1. 画面の中央に円を描くところから

画面の中央に円を描くコードを三つ紹介。


1.
2. circle(w * 0.5, h * 0.5, r);
3. ranslate(w / 2, h / 2); circle(0, 0, r);

「1.」は、直径=sqrt(w * w + h * h)の円を描いたときと結果同じなので、【書かずして描く】というクリエイティグ・コーディングの極意、「孫子コーディング」の実例となっています。


/* AltEdu2022 Day 1. 画面の中央に円を描くところから
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.01
 * license CC0
 */

const w = 540;
const h = 320;

function setup() {
    createCanvas(w, h);
    background(240);
    stroke(64);
    strokeWeight(5);

    // 1.
    fill('#fc9');
    let r = sqrt(w * w + h * h);
    circle(w * 0.5, h * 0.5, r);

    // 2.
    fill('#9cf');
    r = min(w, h) * 0.8;
    circle(w * 0.5, h * 0.5, r);

    // 3.
    fill('#ff9');
    r = min(w, h) * 0.5;
    translate(w / 2.0, h / 2.0);
    circle(0, 0, r);

}


 

Day 2. 30分コードを書いて

30分「コード」を書いた結果です。近頃は文字を書く機会がめっきり減り、30分手の力が持ちませんでした。


/* AltEdu2022 Day 2. 30分コードを書いて
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.02
 * license CC0
 */

const w = 600;
const h = 850;
const divX = w / 15;
const divY = h / 25;

function setup() {
    createCanvas(w, h);
    background(240);
    stroke(0);
    noFill();

    translate(divX * 0.5, divY * 0.5);

    let num = 0;
    for (let y = 0; y < h; y += divY) {
	for (let x = 0; x < w; x += divX) {
	    push();
	    translate(x, y);
	    rotate(random(-1, 1) * PI * pow((x + y * 2) * 0.005, 3) * 0.0003);
	    strokeWeight(noise(x * 0.01, y * 0.01) * 2 + 0.5);
	    writeCode(num);
	    pop();
	    num++;
	}
    }
}

function writeCode(_n) {
    push()
    translate(-19, -10);
    fatigue(_n)
    line(0, 0, 10, 0);
    fatigue(_n)
    line(10, 0, 10, 9);
    fatigue(_n)
    line(0, 10, 10, 10);

    fatigue(_n)
    line(15, 5, 25, 5);

    fatigue(_n)
    line(30, -2, 30, 11);
    fatigue(_n)
    line(30, 2, 35, 6);
    fatigue(_n)
    line(33, 0, 34, 1);
    fatigue(_n)
    line(36, -1, 37, 0);
    pop();
}

function fatigue(_n) {
    if (random(1.0) < 0.5) {
	rotate(random(-1.5, 1) * PI * pow(_n * 0.1, 2) * 0.00007);
    }
}


 

Day 3. 住んでいる国や地域の文化的な風習をモチーフに

コード中に 2 14 が入っているところが作品です。描いたハートはおまけです。


/* AltEdu2022 Day 3. 住んでいる国や地域の文化的な風習をモチーフに
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.03
 * license CC0
 * reference https://mathworld.wolfram.com/HeartCurve.html
 */
					 
        function          setup() {					 
     const w = 640;     const h = 640;					 
   createCanvas(w, h);  background(240);
  noStroke();                fill('#f36');					 
 translate(w * 0.5, h * 0.5); beginShape();
for (let t = 0; t < TWO_PI; t += PI * .01)
  {         let x = pow(sin(t), 3) * 105;
   let y = cos(t * 4) + cos(t * 3) * 3
     + cos(t * 2) * 6 - cos(t) * 12;
        vertex(x * 2, y * 14);}
                endShape();
                     }


 

Day 4. 文字を使ったグラフィック

はかない恋の物語。

p5.Font の textToPoints() を使えば簡単にできるはずの文字の形の取得を、わざわざ自前関数 text2vs を書いてやっているのは、私が textToPoints() を知らなかったからです。

このように、いくら p5.js のことを好きでも、向こうから「これ使ったら」と声をかけてもらえることなどありません。 いつも想いは一方通行、永遠の片思いなんだわ。


/* AltEdu2022 Day 4. 文字を使ったグラフィック
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.04
 * license GPL3
 */
 
const w = 720;
const h = 480;
const tSize = 120;
const frmRate = 30;
const waitSec = 2;
const waveSec = 5;
const ochiSec = 3;
const wholeSec = waitSec + waveSec + ochiSec;
let beforeL, afterL;

function setup() {
    createCanvas(w, h);
    colorMode(HSB, 360, 100, 100, 100);
    frameRate(frmRate);

    beforeL = getLetter(['砂に書いた', 'ラヴ♡レター']);
    afterL = getLetter(['波に', ' 流サレター']);
}

function draw() {

    background(50, 15, 90, 100);
    const frmCnt = (frameCount - 1) % (frmRate * wholeSec);

    // letter
    if (frmCnt < frmRate * (waitSec + waveSec * 0.5)) {
	image(beforeL, 0, 0);
    } else {
	image(afterL, 0, 0);
    }

    //wave
    if (frmCnt > frmRate * waitSec && frmCnt < frmRate * (waitSec + waveSec)) {
	const waveRate = easeInOutSine(map(frmCnt, frmRate * waitSec, frmRate * (waitSec + waveSec), 0.0, 1.0));
	drawWave(waveRate);
    }

}


/* drawWave : 波描画
 * _t   : 寄せては返す時間
 */
function drawWave(_t) {
    const maxR = PI * 3;
    const by = h * 0.75 * pow(sin(PI * _t), 2);

    noStroke();
    fill(50, 15, 90, 100);
    rect(0, 0, w, by);

    fill(220, 90, 80, 100);
    textSize(20);
    for (let r = 0; r < maxR; r += PI * 0.1) {
	let wX = w * r / maxR;
	let maxY = (0.5 + sin(PI * _t)) * by + abs(sin(r + maxR * _t)) * h * 0.3 * cos(PI * _t);
	for (let wY = 0; wY < maxY; wY += 30) {
	    text('波', wX, wY);
	}
    }
}


/* getLetter : 文字列をその文字を使って描画した PGraphics を返す
 * _ltr    : 描画する文字列の入った配列、長さ制限ありまくり
 */
function getLetter(_ltr) {
    const pg = createGraphics(w, h);
    pg.clear();
    pg.push();
    pg.translate(tSize * 0.5, h * 0.5 - tSize);
    pg.noStroke();
    pg.fill(80);
    pg.textAlign(CENTER, CENTER);
    pg.textSize(10);
    for (let txt of _ltr) {
	pg.push();
	for (let i = 0; i < txt.length; i++) {
	    const vs = text2vs(txt.charAt(i), 100, 0.25, tSize, tSize);
	    for (let v of vs) {
		pg.text(txt.charAt(i), v.x, v.y);
	    }
	    pg.translate(tSize, 0);
	}
	pg.pop();
	pg.translate(-tSize * 0.5, tSize + 20);
    }
    pg.pop();

    return pg;
}

/* text2vs : 元の文字から、その文字を構成する点の座標(vector)の配列を返す
 * _text    : 元の文字
 * _size    : 文字を描画するサイズ
 * _density : 文字を構成する点を取得する割合。1.0 なら全部取得、0.1 なら 10% 取得。取得はランダムなので均一になるとは限らないし、正確にその割合で取得するわけでもない。
 * _width   : 文字を描画するキャンバスサイズの幅
 * _height  : 文字を描画するキャンバスサイズの高さ
 */
function text2vs(_text, _size, _density, _width, _height) {

    const pg = createGraphics(_width, _height);
    pg.clear();
    pg.noFill();
    pg.noStroke();
    pg.fill(0);
    pg.textSize(_size);
    pg.textAlign(CENTER, CENTER);
    pg.text(_text, _width * 0.5, _height * 0.5);
    pg.loadPixels();

    const d = pg.pixelDensity();
    const vectors = new Array();
    for (let w = 0; w < _width; w += 2) {
	for (let h = 0; h < _height; h += 2) {
	    let pIndex = (_width * h * d * d + w * d) * 4;
	    // brightness は RGB の最大値になるそうなので
	    if (
		max(pg.pixels[pIndex], max(pg.pixels[pIndex + 1], pg.pixels[pIndex + 2])) < 250 &&
		    pg.pixels[pIndex + 3] > 0
	    ) {
		if (random(1.0) < _density) {
		    vectors.push(createVector(w, h));
		}
	    }
	}
    }

    return vectors;
}


/* easeInOutSine : イージング
 * _t    : イージング元
 */
function easeInOutSine(_t) {
    return -(cos(PI * _t) - 1.0) / 2.0;
}


 

Day 5. 線を使わないで線を書いて

人とは、とかく境界線を引きたがるもの。面と面を向かい合わせれば、ほら、もうそこに境界線を引かずにはいられない。


/* AltEdu2022 Day 5. 線を使わないで線を書いて
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.01
 * license CC0
 */

const w = 640;
const h = 480;
const d = 2;
const r = 60; // 60, 10

function setup() {
    createCanvas(w, h);
    noStroke();
    fill(240);
    background(0);

    let sign = 1;
    for (let cY = r; cY < h; cY += r * 2) {
	for (let cX = r; cX < w; cX += r) {
	    sign *= -1;
	    for (let t = 0; t < PI; t += PI * 0.05) {
		let x = r * cos(t * sign);
		let y = r * sin(t * sign);
		circle(cX + x, cY + y, r - d);
	    }
	}
    }
}


 

Day 6. 誰かのコードを書き換える

Lisa Sekaida 世界田りさ(@lisasekaida)さんのコード

Lisa Sekaida 世界田りさ(@lisasekaida)さんの Day 2 のコードを参照させていただきました。


ライセンスは CreativeCommons Attribution NonCommercial ShareAlike ですので、それに従います。

元コードでは、透明度をつけた背景で塗りつぶすことで残像のような効果が出ています。面白いですね。 これを活かして、残像で形が変わって見えるアニメーションを作りました。


/* AltEdu2022 Day 6. 誰かのコードを書き換える
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.05
 * license CC BY-NC-SA 3.0
 */

const w = 640;
const h = w;
const step = 0.003;
let a = 0;

function setup() {

    createCanvas(w, h);
    frameRate(30);
    strokeWeight(10);
    stroke(255);
    noFill();

}

function draw() {

    a += frameCount * step;
    a %= TWO_PI;
    
    background(0, 20);
    translate(w / 2, h / 2); 
    rotate(a);
    rect(0, 0, w * 0.5, w * 0.3);
    ellipse(0, 0, w * 0.5, w * 0.3);

}


 

TAKAWO Shunsuke(@takawo)さんのコード

TAKAWO(@takawo)さんのコードは、「どうやってるんだろう?」と興味を惹かれ、動作を理解するためにいじってみることが多いです。


元コードは OpenProcessing で公開されているので、それを fork することで、

  • 元コードからのフォークであることが明示される。
  • 元コードのライセンスがデフォルトで設定される。

が自動で行われます。参照元の表示を忘れたりが無くなるので、OpenProcessing とても良いですね。

 

Day 7. 全画面でアニメーションするコード


マス目のサイズをパーリンノイズで変化させるものです。色に透明度を付加することで、Day 6. で憶えた残像効果が付きました。


/* AltEdu2022 Day 7. 全画面でアニメーションするコード
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.05
 * license CC0
 */

const divX = 10;
const divY = 8;
const wAry = Array(divX);
const hAry = Array(divY);

function setup() {

    createCanvas(windowWidth,windowHeight);
    colorMode(HSB, 360, 100, 100, 100);
    frameRate(30);
    strokeWeight(5);
    stroke(0, 0, 90, 100);    
    background(0, 0, 0, 100);
    noFill();

}

function draw() {

    // animation parameter
    let t = frameCount * 0.01;

    // size calculation
    let wSum = 0;
    for (let i = 0; i < divX; i++) {
	let nVal = noise(i, t);
	wAry[i] = nVal;
	wSum += nVal;
    }

    let hSum = 0;
    for (let i = 0; i < divY; i++) {
	let nVal = noise(i, t);
	hAry[i] = nVal;
	hSum += nVal;
    }

    // draw
    let x = 0;
    for (let ix = 0; ix < divX; ix++) {
	let w = wAry[ix] * width / wSum;
	let y = 0;
	for (let iy = 0; iy < divY; iy++) {
	    let h = hAry[iy] * height / hSum;
	    fill(noise(ix, iy) * 360, 60, 80, 10);
	    rect(x, y, w, h);
	    y += h;
	}
	x += w;
    }

}


 

Day 8. お気に入りの曲を聞きながら

名曲、竹村健一さんの「MOU CORI GORI DA」を聴きながら。


/* AltEdu2022 Day 8. お気に入りの曲を聞きながら
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.05
 * license CC0
 */

const w = 720;
const h = 480;
const num = 10;
const frmRate = 30;
const pulseSec = 6;
const pulseFrm = frmRate * pulseSec;

let numX, numY;

function setup() {

    createCanvas(w, h);
    colorMode(HSB, 360, 100, 100, 100);
    rectMode(CENTER);
    noStroke();
    frameRate(frmRate);

    numX = floor(num * w / (w + h));
    numY = floor(num * h / (w + h));

}

function draw() {

    let frmRatio = map(frameCount % pulseFrm, 0, pulseFrm, 0, 1);

    blendMode(BLEND);
    background(240, 100, 30, 100);

    translate(0.5 * w / numX, 0.5 * h / numY);
    for (let x = 0; x < numX; x++) {
	for (let y = 0; y < numY; y++) {
	    let sTime = sin(PI * ((frmRatio + noise(10, x, y)) % 1));
	    let sHue  = ((frmRatio + noise(20, x, y)) * 720) % 360;
	    star(x / numX, y / numY, sTime, sHue);
	}
    }

}

function star(_x, _y, _t, _hue) {
    
    blendMode(ADD);
    for (let r = 0.0; r < 1.0; r += 0.1) {
	let x = w * _x;
	let y = h * _y;
	let s = w * r / num;
	fill(_hue, _t * 100, (2.0 - r) * 10, 100);
	circle(x, y, s * 2);
	fill(_hue, 100, r * 5, 100);
	rect(x, y, s * 4, s * 4);
    }

}


 

Day 9. においや手触りのような感覚をコードで

「感覚をコードで表現」するってすごく難しいと思います。

こちらは Processing のコード。間隔を調整しながら多数の線を引くことで、なめらかなタイルの立体感を出せないかとやってみました。線と線の間隔はイージング関数を使って計算しています。


/* AltEdu2022 Day 9. においや手触りのような感覚をコードで表現
 * Processing 3.5.3
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.07
 * license CC0
 */

void setup() {

  size(640, 480);
  smooth();
  noFill();
  background(240);
  noLoop();

  int   divX  = 5;
  int   divY  = divX;
  float cellW = width / divX;
  float cellH = height / divY;

  // アイテムを格子上に描画
  for (int dX = 0; dX < divX; dX++) {
    for (int dY = 0; dY < divY; dY++) {
      pushMatrix();
      translate(dX * cellW, dY * cellH);
      stroke(10.0, 5.0, 30.0, random(10.0, 50.0));
      drawCell(cellW, cellH);
      popMatrix();
    }
  }

}

// イージング関数で線の間隔を決定
void drawCell(float _w, float _h) {
  for (float t = 0.0; t < 1.0; t += 0.0005) {
    float x = _w * ease(t);
    float y = _h * ease(t);
    line(x, 0, x, _h);
    line(0, y, _w, y);
  }
}

// イージング関数`
float ease(float _t) {
  // easeOutQuart
  return 1.0 - pow(1.0 - _t, 4);
}



同じ考え方で、イージング関数を変えて、より立体感を出すよう工夫してみました。こちらは p5.js のコードです。


/* AltEdu2022 Day 9. においや手触りのような感覚をコードで表現
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.09
 * license CC0
 */

function setup() {

    createCanvas(640, 640);
    colorMode(HSB, 360, 100, 100, 100);
    smooth();
    noFill();
    background(0, 0, 100, 100);
    noLoop();

    const divX = 3;
    const divY = divX;
    const cellW = width / divX;
    const cellH = height / divY;
    const mRate = 0.025;
    const mWidth = max(cellW, cellH) * mRate;
    const baseHue = random(360);

    // 開始位置調整
    translate(cellW * mRate, cellH * mRate);

    // アイテムを格子上に描画
    for (let dX = 0; dX < divX; dX++) {
	for (let dY = 0; dY < divY; dY++) {
	    push();
	    translate(dX * cellW, dY * cellH);
	    stroke((baseHue + dX * 60 + dY * 30) % 360, 80, 30, random(10.0, 30.0));
	    drawCell((1 - mRate * 2) * cellW, (1 - mRate * 2) * cellH);

	    // 隙間を埋める
	    push();
	    translate(-mRate * cellW * 0.5, -mRate * cellH * 0.5);
	    stroke(0, 0, 80, 100);
	    strokeWeight(mWidth);
	    rect(0, 0, (1 - mRate) * cellW, (1 - mRate) * cellH);
	    // アイテムの角に丸みをもたせる
	    strokeWeight(mWidth * 2);
	    rect(0, 0, (1 - mRate) * cellW, (1 - mRate) * cellH, mWidth * 3);
	    pop();
	    pop();
	}
    }

}

// イージング関数で線の間隔を決定
function drawCell(_w, _h) {
    for (let t = 0.0; t < 1.0; t += 0.001) {
	strokeWeight(t);
	let x = _w * ease(t);
	let y = _h * ease(t);
	line(x, 0, x, _h);
	line(0, y, _w, y);
    }
}

// イージング関数`
function ease(_t) {
    // easeInOutQuint
    return _t < 0.5 ? 16 * pow(_t, 5) : 1 - pow(-2 * _t + 2, 5) / 2;
}


 

Day 10. 身の回りのモノの色を使ったコードを書く

彩度と明度はそのままに、色相だけをソートしたらどうなるか?

やってみたら、全然おもしろくなくて、ヤケになって色相ランダムにしたらそっちのほうが面白かったというコードです。


「身の回りのモノ」の写真を撮って、色もコンピュータに取得してもらおうという考えで作っています。お手持ちの写真を ./data ディレクトリに入れて、 "your_photo.jpg" をその写真のファイル名にすると、ご自分の写真で試すことができます。


/* AltEdu2022 Day 10. 身の回りにあるモノをいくつか集めて、その色を使ったコードを書いてください。
 * Processing 3.5.3
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.10
 * license GPL3
 */

import java.util.Arrays;

void setup() {
  size(640, 640);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noLoop();

  int gridDiv = 32;
  PImage img = loadImage("your_photo.jpg");
  float rateSize = width * 1.0 / max(img.width, img.height);
  img.resize(floor(img.width * rateSize), floor(img.height * rateSize));

  // nodes on the grid
  int gridStep = floor(max(img.width, img.height) / gridDiv);
  ArrayList<Node> nodes = getGridNodes(img, gridStep);

  // draw
  translate((width - img.width) / 2, (height - img.height) / 2);
  background(0.0, 0.0, 90.0, 100.0);
  //  drawSortedHue(nodes, gridStep);
  drawRndHue(nodes, gridStep);
  
}

/**
 * draw with sorted hue
 */
void drawSortedHue(ArrayList<Node> _nodes, int _step) {

  float[] hueVal = new float[_nodes.size()];
  for (int i = 0; i < _nodes.size(); i++) {
    hueVal[i] = _nodes.get(i).hueVal;
  }
  Arrays.sort(hueVal);
  
  for (int i = 0; i < _nodes.size(); i++) {
    Node n = _nodes.get(i);
    fill(hueVal[i] % 360.0, n.satVal, n.briVal, 100.0);
    circle(n.x, n.y, _step);
  }
}

/**
 * draw with random selected hue
 */
void drawRndHue(ArrayList<Node> _nodes, int _step) {

  for (Node n : _nodes) {
    float hueVal = _nodes.get(floor(random(_nodes.size()))).hueVal;    
    fill(hueVal % 360.0, n.satVal, n.briVal, 100.0);
    circle(n.x, n.y, _step);
  }
}

/**
 * getGridNodes : returns node on the grid as Node array
 */
ArrayList<Node> getGridNodes(PImage _img, int _step) {

  ArrayList<Node> nodes = new ArrayList<Node>();
  _img.loadPixels();

  // centering the grid
  int iXMax = ceil(_img.width * 1.0 / _step);
  int iYMax = ceil(_img.height * 1.0 / _step);
  int rXDiv = floor((_img.width - iXMax * _step + _step) * 0.5);
  int rYDiv = floor((_img.height - iYMax * _step + _step) * 0.5);
  for (int iY = 0; iY < iYMax; iY++) {
    for (int iX = 0; iX < iXMax; iX++) {
      int rX = iX * _step + rXDiv;
      int rY = iY * _step + rYDiv;
      if (rX < _img.width && rY < _img.height) {
        int pixIndex = floor(rY * _img.width + rX);
        nodes.add(new Node(
                           rX,
                           rY,
                           hue(_img.pixels[pixIndex]),
                           saturation(_img.pixels[pixIndex]),
                           brightness(_img.pixels[pixIndex])
                           ));
      }
    }
  }
  return nodes;
}

/**
 * Node : draw and hold location and color.
 */
public class Node {

  public  int   x, y;   // coordinate of node
  private float hueVal; // hue value of node
  private float satVal; // saturation value of node
  private float briVal; // brightness value of node

  Node(int _x, int _y, float _c, float _s, float _b) {
    x = _x;
    y = _y;
    hueVal = _c;
    satVal = _s;
    briVal = _b;
  }

}


 

Day 11. 「デジタル」を感じるようなコード

ええか?ええか?ええのんか?

「デジタル」を感じるコード(敏感ちゃん)です。


/**
 * AltEdu2022 11. 「デジタル」を感じるようなコード
 * Processing 3.5.3
 * @author @deconbatch
 * @version 0.1
 * created 0.1 2022.02.11
 * license CC0
 */

int frmRate = 30;
int cycleSec = 2;
int cycleFrm = frmRate * cycleSec;
Face face = new Face();
ArrayList<Heart> hearts = new ArrayList();
ArrayList<PVector> digitals = new ArrayList();

void setup() {
  size(720, 720);
  colorMode(HSB, 360, 100, 100, 100);
  frameRate(frmRate);
  smooth();
  rectMode(CENTER);
  textAlign(CENTER, CENTER);
  textFont(createFont("SansSerif.bold",240,true));
}

void draw() {

  background(0.0, 0.0, 90.0, 100.0);

  // face
  face.display();

  // digitals
  if (frameCount % cycleFrm == 0) {
    digitals.add(new PVector(random(0.1, 0.9) * width, -100.0));
  }
  for (PVector d : digitals) {
    d.y += 5;
    noStroke();
    fill(d.x % 360.0, 90.0, 30.0, 100.0);
    rect(d.x, d.y, 125.0, 50.0, 25.0);
    fill(0.0, 0.0, 100.0, 100.0);
    textSize(20);
    text("デジタル", d.x, d.y);
    // sensation
    if (
        abs(d.x - width * 0.5) < width * 0.25 &&
        abs(d.y - height * 0.5) < height * 0.1
        ) {
      hearts.add(new Heart(d.x + random(-50, 50), d.y));
      face.sensation();
    }
  }
  for (int i = 0; i < digitals.size(); i++) {
    if(digitals.get(i).y > height) {
      digitals.remove(i);
    }
  }

  // hearts
  for (Heart h : hearts) {
    h.move();
    h.display();
  }
  for (int i = 0; i < hearts.size(); i++) {
    if(hearts.get(i).isOut()) {
      hearts.remove(i);
    }
  }

}

/**
 * Heart
 */
public class Heart {
  private float cx, x, y;
  private float r, t;
  
  Heart(float _x, float _y) {
    cx = _x;
    x = _x;
    y = _y;
    r = 0.0;
    t = 0.0;
  }

  public void move() {
    r += 0.1;
    t += 0.1;
    t %= TWO_PI;

    x = cx + r * sin(t);
    y -= 2;
  }

  public void display() {
    noStroke();
    fill(340.0, 60.0, 80.0, 100.0);
    textSize(30);
    text("❤", x, y);
  }

  public boolean isOut() {
    if (y < 0) {
      return true;
    }
    return false;
  }
}

/**
 * Face
 */
public class Face {
  private int raptureCnt;
  private int raptureMax;
  private boolean raptured;
  private String[] look = {"😐", "🤪", "😳", "😊", "😍", "😚"};
  private String[] sigh = {"", "あっはん", "うっふん", "あぁ〜ん", "いやぁ〜ん"};
  private int idxLook, idxSigh;
  private float sighX, sighY;

  Face() {
    reset();
    sighX = 0.0;
    sighY = 0.0;
    
  }

  public void reset() {
    raptureCnt = 0;
    raptureMax = 60; // raputuring frames
    idxLook = 0;
    idxSigh = 0;
    raptured = false;
  }

  public void sensation() {
    if (!raptured) {
      idxLook = floor(random(1, look.length));
      idxSigh = floor(random(1, sigh.length));
      sighX = width * 0.5 + random(-100.0, 100.0);
      sighY = height * (0.5 + ((random(1.0) < 0.5) ? 0.25 : -0.2));
      raptured = true;
    }
  }

  public void display() {
    noStroke();
    fill(0.0, 0.0, 20.0, 100.0);
    textSize(300);
    text(look[idxLook], width * 0.5, height * 0.5);
    fill(340.0, 90.0, 60.0, 100.0);
    textSize(80);
    text(sigh[idxSigh], sighX, sighY);

    raptureCnt++;
    if (raptureCnt > raptureMax) {
      reset();
    }
  }
}

 

Generativemasks を使って

TAKAWO(@takawo)さんGenerativemasks に、格子状のチラチラする背景を付けてみました。 格子がチラチラすれば、それだけで「デジタル」って感じしますよね? ね? ね? ねぇってば!

前景の画像の生成には Generativemasks の派生物を作るのに便利は JavaScript のモジュール「Garg」に手を入れたものを使いました。 「Garg」を入れると長くなるので、コード上は省略しています。「Garg」のコードについてはこちらをご覧ください。

Generativemasks を生成する JavaScript モジュールを作る

/* AltEdu2022 Day 11. 「デジタル」を感じるようなコード
 * p5.js + Garg
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.10
 * license CC BY-NC-SA 3.0
 */

const w = 640;
const h = w;
const step = 20;
const frmRate = 6;
const change = frmRate * 3;
let gg, mask;

function setup() {

  createCanvas(w, h);
  ellipseMode(CORNER);
  frameRate(frmRate);
  gg = new Garg(false, false, false);
  mask = gg.createMask(floor(random(10000, 11000)), w);

}

function draw() {

  if ((frameCount % change) == 0) {
    mask.remove();
    mask = gg.createMask(floor(random(10000, 11000)), w);
  }

  // 透明度で残像効果
  blendMode(BLEND);
  background(0, 64);

  // 光を重ねて色を複数に見せる
  blendMode(ADD);
  noStroke();
  fill(gg.palette[0]);
  for (let y = 0; y < h; y += step) {
    for (let x = 0; x < w; x += step) {
      if (random(1) < 0.1) {
        rect(x, y, step);
      }
    }
  }

  // 格子を描けばそれはもうデジタル
  blendMode(BLEND);
  stroke(64);
  strokeWeight(1);
  noFill();
  for (let i = 0; i < w; i += step) {
    line(i, 0, i, h);
    line(0, i, w, i);
  }

  // マスクを描画
  image(mask, 0, 0);

}


/* 
 * Garg : Generates A Resembling Generativemasks.
 * 以下、省略
 */


 

ぴらりんいこう さんのコードを参照して

ぴらりんいこう(@pirarinpic)さんの Day 7. のコードを参照させていただきました。

元のコードは OpenProcessing の「チュートリアルモード」で作られており、どのようにコードを書いていったかがわかるようになっています。面白いですね。


/* AltEdu2022 Day 11. 「デジタル」を感じるようなコード
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.10
 * license CC BY-NC-SA 3.0
 * reference 
 * Class_tutorial by pirarin https://openprocessing.org/sketch/1471197
 */

const w = 720;
const h = 480;
const num = 50;
const wireStep = 12;
const holeSize = w * 0.4;
const items = new Array();
let wire;

function setup() {
  createCanvas(w, h);
  frameRate(24);
  rectMode(CENTER);

  // items define
  for (let i = 0; i < num; i++) {
    items.push(new Item());
  }

  // wire define
  wire = createGraphics(w, h);
  wire.background(0);
  wire.noFill();
  wire.stroke(0);
  wire.strokeWeight(3);
  wire.erase();
  for (let x = 0; x < w; x += wireStep) {
    wire.line(x, 0, x, h);
  }
  for (let y = 0; y < h; y += wireStep) {
    wire.line(0, y, w, y);
  }
  wire.fill(0);
  wire.circle(w / 2, h / 2, holeSize);
  wire.noErase();

}

function draw() {
  // 背景
  blendMode(BLEND);
  background(0, 0, 96, 32);

  // items 描画
  blendMode(ADD);
  noStroke();
  for (let i of items) {
    if (dist(i.x, i.y, w / 2, h / 2) < holeSize / 2) {
      i.change();
    } else {
      i.setNormalSize();
    }
    i.move();
    i.display();
  }

  // wire 描画
  blendMode(BLEND);
  image(wire, 0, 0);

}

// item 
class Item {

  constructor() {
    // 位置
    this.x = floor(random(w) * wireStep) / wireStep;
    this.y = floor(random(h) * wireStep) / wireStep;

    // 速度
    if (random(1) < 0.5) {
      this.sx = floor(random(-3, 3)) * wireStep;
      this.sy = 0;
    } else {
      this.sx = 0;
      this.sy = floor(random(-2, 3)) * wireStep;
    }
    if (this.sx == 0 && this.sy == 0) {
      this.sx = 0;
      this.sy = wireStep;
    }

    // 色
    this.c = color(
      floor(random(128) / 6) * 6,
      floor(random(128) / 6) * 6,
      floor(random(128) / 6) * 6,
      255
    );

    // サイズ
    this.setNormalSize();
  }

  // item サイズと方向を変更
  change() {
    this.r = wireStep * 2;
    if (random(1) < 0.05) {
      let tempS = this.sx;
      this.sx = this.sy;
      this.sy = tempS;
    }
  }

  // item サイズを標準サイズに設定
  setNormalSize() {
    this.r = wireStep;
  }

  // item を移動
  move() {
    this.x = this.x + this.sx;
    this.y = this.y + this.sy;
    if (this.x < 0 || width < this.x) {
      this.sx = this.sx * -1;
    }
    if (this.y < 0 || height < this.y) {
      this.sy = this.sy * -1;
    }
    this.x = floor(this.x / wireStep) * wireStep;
    this.y = floor(this.y / wireStep) * wireStep;
  }

  // item 表示
  display() {
    fill(this.c);
    rect(
      this.x,
      this.y,
      this.r * (1 + abs(this.sx * 0.1)),
      this.r * (1 + abs(this.sy * 0.1)),
      this.r * 0.2
    );
  }

}


 

Day 14. #つぶやきProcessing に挑戦

コードゴルフは苦手なので、文字数を少なくするため、なるべく単純な仕組みにすることを目指して。


/* AltEdu2022 Day 14. #つぶやきProcessing に挑戦
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.11
 * license CC0
 */

const s = 740;
const d = 2

function setup() {
 createCanvas(s, s);
 noStroke();
}

function draw() {
 for (let x = 0; x < s; x += d) {
  for (let y = 0; y < s; y += d) {
   fill((((x & y) | frameCount) % 5) * 64);
   rect(x, y, d);
  }
 }
}


 

Day 15.「懐かしさ」を感じるコード

「懐かしさを感じるものをコードを使って作れ」ではなく、「懐かしさを感じるコードを書け」なので、まだまだ経験の浅い私には難しいお題です。

それでも思い出してみると、Processing やり始めのころに、整数 / 整数 の計算結果が整数になってしまうことを忘れることがよくありました。少し前なのに、もう懐かしい…

こうなるはずが、

こうなる。


/* AltEdu2022 Day 15. 「懐かしさ」を感じるコード
 * Processing
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.12
 * license CC0
 */

void setup() {
  size(640, 640);
  colorMode(HSB, 360, 100, 100, 100);
  noStroke();

  background(0, 0, 90, 100);
  translate(width * 0.1, height * 0.5);
  
  int num = 100;
  for (int i = 0; i < num; i++) {

    // こうなるはずが、
    // float ratio = i * 1.0 / num;

    // こうなる。
    float ratio = i / num;

    float x = width * 0.8 * ratio;
    float y = height * 0.25 * sin(TWO_PI * ratio);
    fill(360 * ratio, 60.0, 80.0, 100.0);
    circle(x, y, 30);
  }
}


 

Day 16. 身の回りにある模様を真似して

部屋に模様が無い。かといって、壁紙とかにありそうなパターンを作るセンスも無い。だったら、パターンをコンピュータに作ってもらっちゃお。


/**
 * AltEdu2022 Day 16. 身の回りにある模様を真似して
 * Processing
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.12
 * license GPL3
 */

void setup() {

  size(800, 800);
  colorMode(HSB, 360, 100, 100, 100);
  imageMode(CENTER);

  int div = 4; // 格子の数
  PImage cell = getCell(floor(width / div));
  
  background(0, 0, 90, 100);
  for (int i = 0; i < div; i++) {
    for (int j = 0; j < div; j++) {
      pushMatrix();
      translate((i + 0.5) * width / div, (j + 0.5) * height / div);
      // セルをランダムに回転させて格子にはめる
      rotate(floor(random(4.0)) * HALF_PI);
      image(cell, 0, 0);
      popMatrix();
    }
  }

}

/** 
 * getCell : セルにランダムなパターンを描画して返す
 */
PImage getCell(int _size) {
  // 外周の接続ポイント
  PVector[][] points = {
    {new PVector(0.25, 0.0), new PVector(0.5, 0.0)},
    {new PVector(0.75, 0.0), new PVector(1.0, 0.25)},
    {new PVector(1.0, 0.5), new PVector(1.0, 0.75)},
    {new PVector(0.75, 1.0), new PVector(0.5, 1.0)},
    {new PVector(0.25, 1.0), new PVector(0.0, 0.5)},
    {new PVector(0.0, 0.75), new PVector(0.0, 0.25)},
  };

  PGraphics g = createGraphics(_size, _size);
  g.beginDraw();
  g.colorMode(HSB, 360, 100, 100, 100);
  g.background(0.0, 0.0, 100.0, 100.0);
  g.blendMode(BLEND);
  g.noFill();
  g.strokeWeight(_size * 0.05);
  g.strokeJoin(ROUND);
  for (PVector[] p : points) {
    g.stroke(random(360), 40, 60, 100);
    g.beginShape();
    g.vertex(p[0].x * _size, p[0].y * _size);
    // ランダムな通過点
    g.vertex(
             random(0.2, 0.8) * _size,
             random(0.2, 0.8) * _size
             );
    g.vertex(p[1].x * _size, p[1].y * _size);
    g.endShape();
    // 接合部に飾りの円を
    g.circle(p[0].x * _size, p[0].y * _size, 0.1 * _size);
    g.circle(p[1].x * _size, p[1].y * _size, 0.1 * _size);
  }
  g.endDraw();

  return g;

}


 

Day 17. 3色の色、3種類の図形だけを使って

blendMode(ADD) で色を混ぜれば色んな色が色々出る!と思ったけど、細い線だと重なる部分が小さすぎてわかりませんね。


/**
 * AltEdu2022 Day 17. 3色の色、3種類の図形だけを使って
 * Processing
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.13
 * license CC0
 */

int num = 3;
int frmRate = 30;
int cycle = 12;
int frmCycle = frmRate * cycle;

void setup() {

  size(720, 720, P2D);
  frameRate(frmRate);
  colorMode(HSB, 360, 100, 100, 100);
  rectMode(CENTER);
  background(240, 100, 30, 5);
}

void draw() {

  float frmRatio = (frameCount % frmCycle) * 1.0 / frmCycle; // 0.0 から 1.0 の周期
  float r = min(width, height) * 0.7;
  
  translate(width * 0.5, height * 0.5);
  // 残像
  blendMode(BLEND);
  fill(240, 100, 30, 5);
  noStroke();
  rect(0, 0, width, height);

  // 長方形を回転
  blendMode(ADD);
  noFill();
  for (int i = 1; i <= num; i++) {
    float x = r * 0.2 * cos(TWO_PI * frmRatio + i);
    float y = r * 0.2 * sin(TWO_PI * frmRatio + i);

    strokeWeight(num * 2 / i);
    stroke((120.0 + i * 60) % 360.0, 90, 80, 100);
    //    rotate(PI * frmRatio * ((i % 2 == 0) ? -i : i));
    rotate(PI * frmRatio * i);
    rect(x, y, r, r / (i * i));
    rect(-x, -y, r, r / (i * i));
  }

}

 

Day 18. 音を使ったスケッチ

音を感じるようなスケッチは難しかったので、ランダム・ウォークに合わせて音を鳴らす、音を使う方のスケッチを作ってみました。

ルンルンな鼻歌ウォーキングです。


 /* AltEdu2022 Day 18. 音を使ったスケッチ
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.13
 * license CC0
 */

const w = 720;
const h = 480;
const pStep = 40;
let pX = 0;
let pY = 0;
let stepO, stepA;
let xyO, xyA;

function setup() {
  createCanvas(w, h);
  rectMode(CENTER);
  background(255);
  frameRate(5);

  // sound setting
  stepO = new p5.Oscillator('sine');
  stepA = 0.2;
  xyO = new p5.Oscillator('triangle');
  xyA = 0.1;
}

function draw() {

  let stepFBase = 0;
  let xyFBase = 0;
  
  translate(w * 0.5, h * 0.5);

  // afterimage effect
  fill(255, 32);
  stroke(0);
  rect(0, 0, w, h);

  // matrix
  noFill();
  stroke(160);
  for (let x = -w * 0.5; x <= w * 0.5; x += pStep) {
    for (let y = -h * 0.5; y <= h * 0.5; y += pStep) {
      rect(x, y, pStep, pStep);
    }
  }

  // random walk
  let direction = random(-1.0, 1.0);
  direction = direction == 0 ? 1.0 : direction / abs(direction);
  if (random(1.0) < 0.6) {  // consider aspect ratio
    pX += direction * pStep;
    fill("#ffb703");
    stepFBase = 220;
    xyFBase = 440;
  } else {
    pY += direction * pStep;
    fill("#219ebc");
    stepFBase = 262;
    xyFBase = 523;
  }

  // out of border
  if (abs(pX) > w * 0.5) {
    pX -= direction * pStep;
  }
  if (abs(pY) > h * 0.5) {
    pY -= direction * pStep;
  }

  // draw walker
  noStroke();
  circle(pX, pY, pStep);

  // play sound
  const stepF = stepFBase + (frameCount % 3) * 131;
  if (frameCount % 2 == 0) {
    xyO.stop();

    stepO.freq(stepF, 0.1);
    stepO.amp(stepA, 0.1);
    stepO.start();
  } else {
    stepO.stop();

    xyO.freq(xyFBase, 0.1);
    xyO.amp(xyA, 0.1);
    xyO.start();
  }

}

 

Day 21. 今日の天気を眺めて


TAKAWO(@takawo)さんのライブ・コーディング配信 Daily Coding LIve Sessions #05 [ARTSCLOUD] を参考に作りました。こちらの配信、作画のコツやアイディアがいろいろと聞けて、とても勉強になります。今回の実体を描かずに影だけ描くテクニックには腰を抜かしました。



/*
 * AltEdu2022 Day 21. 今日の天気を眺めて
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.19
 * license CC0
 */

// キャンバスサイズを写真サイズに
const w = 640;
const h = 427;
const pSiz = 6;
let bg;

function preload() {
  bg = loadImage("winterSky.jpg");
}

function setup() {
  createCanvas(w, h);
  colorMode(HSB, 360, 100, 100, 100);
  rectMode(CENTER);

  background(0, 80, 80, 100);
  image(bg, 0, 0);

  tx = getCover(pSiz);
  image(tx, 0, 0);
  image(tx, pSiz * 0.25, pSiz * 0.5);
}

function getCover(_baseSize) {
  const nStep = 0.005;

  g = createGraphics(w, h);
  g.background(240);
  g.noStroke();
  for (let x = 0; x < w; x += _baseSize) {
    for (let y = 0; y < h; y += _baseSize) {
      let eAlp = 100 + noise(10, x * nStep, y * nStep) * 155;
      let eRot = noise(20, x * nStep, y * nStep) * TWO_PI;
      let eSizA = noise(30, x * nStep, y * nStep) * _baseSize;
      let eSizB = random(0.5, 2.0) * _baseSize;
      let eDiv = random(-0.5, 0.5) * _baseSize;
      g.erase();
      g.fill(0, eAlp - 100);
      g.rect(x, y, _baseSize, _baseSize);
      g.fill(0, eAlp);
      g.push();
      g.translate(x, y);
      g.rotate(eRot);
      g.rect(eDiv, eDiv, eSizA, eSizB, _baseSize);
      g.pop();
      g.noErase();
    }
  }
  return g;
}

 

Day 22. これまで使ったことがない関数を用いて

Processing で初めて bezier() を使ってみました。

「Day 16. 身の回りにある模様を真似して」のコードで、 curveVertex() で描画していたところを bezier() に変更、これ便利かも! 気付かせてくれた AltEdu2022 に感謝です。


/**
 * AltEdu2022 Day 22. これまで使ったことがない関数を用いて
 * Processing
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.19
 * license GPL3
 */

void setup() {

  size(800, 800);
  colorMode(HSB, 360, 100, 100, 100);
  imageMode(CENTER);

  int div = 10; // 格子の数
  PImage cell = getCell(floor(width / div));
  
  background(0, 0, 90, 100);
  for (int i = 0; i < div; i++) {
    for (int j = 0; j < div; j++) {
      pushMatrix();
      translate((i + 0.5) * width / div, (j + 0.5) * height / div);
      // セルをランダムに回転させて格子にはめる
      rotate(floor(random(4.0)) * HALF_PI);
      image(cell, 0, 0);
      popMatrix();
    }
  }

}

/** 
 * getCell : セルにランダムなパターンを描画して返す
 */
PImage getCell(int _size) {
  // 外周の接続ポイント
  PVector[][] points = {
    {new PVector(0.0, 0.5), new PVector(0.5, 1.0)},
    {new PVector(0.5, 0.0), new PVector(1.0, 0.5)},
  };

  PGraphics g = createGraphics(_size, _size);
  g.beginDraw();
  g.colorMode(HSB, 360, 100, 100, 100);
  g.background(0.0, 0.0, 100.0, 100.0);
  g.blendMode(BLEND);
  g.noStroke();
  for (PVector[] p : points) {
    g.fill(random(360), 40, 60, 100);
    g.bezier(
             p[0].x * _size, p[0].y * _size,
             random(0.1, 0.9) * _size, random(0.1, 0.9) * _size,
             random(0.1, 0.9) * _size, random(0.1, 0.9) * _size,
             p[1].x * _size, p[1].y * _size
             );
  }
  g.endDraw();

  return g;

}

 

Day 23. お茶の味をコードで表現

しっぶい紅茶を飲みました。

透明度高めの色を塗り重ねると、いい感じになると信じています。


/*
 * AltEdu2022 Day 23. お茶の味をコードで表現
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.20
 * license CC0
 */

const w = 640;
const h = 480;
const margin = 0.2;

function setup() {
  createCanvas(w, h, WEBGL);
  colorMode(HSB, 360, 100, 100, 100);
  rectMode(CENTER);

  blendMode(BLEND);
  background(40, 30, 90, 100);

  translate(w * (margin - 0.5), h * (margin - 0.5)); // WEBGL調整
  blendMode(SUBTRACT);
  noStroke();

  for (let i = 0; i < 10; i++) {
    let divX = random(5, 50);
    let divY = random(5, 50);
    let stepX = w * (1.0 - margin * 2) / divX;
    let stepY = h * (1.0 - margin * 2) / divY;

    for (let x = 0; x < divX; x++) {
      for (let y = 0; y < divY; y++) {
        if (random(1.0) < 0.5) {
          fill(180 + random(-45, 45), 50, 50, 5);
          rect(x * stepX, y * stepY, stepX * 3, stepY * 3);
        }
      }
    }
  }

}

 

Day 24. コンピュータアートの過去の作品を参照してコードを書く

1960年代からコンピュータを用いた作品を作ってらっしゃる Vera Molnár さんと、Georg Nees さんの作品を参照してコードを書きました。

Vera Molnár さんの作品は、以前 Processing を使って模写しています。

Vera Molnár さんの (Des)Ordres を Processing で模写

今回はこれを p5.js に移植し、Georg Nees さんの Schotter のエッセンスも加えてみました。作品名は「Homage to Vera Molnár (Des)Ordres and Georg Nees Schotter.」です。

模写してはみたものの、どうしても「コレジャナイ」感が拭えない。歪み具合や散らばり具合に魅力が乗らない。先人はやはり偉大なのです。


/*
 * AltEdu2022 Day 24. コンピュータアートの過去の作品を参照してコードを書く
 * p5.js
 * @title Homage to Vera Molnár (Des)Ordres and Georg Nees Schotter.
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.18
 * license CC0
 */

const w = 520;
const h = 920;
const cols = 12;
const rows = 22;

function setup() {
  createCanvas(w, h);
  rectMode(CENTER);
  background(240);
  noFill();

  // '2' means margin
  const cw = width / (cols + 2);
  const ch = cw;

  translate(cw * 1.5, ch * 1.5);
  for (r = 0; r < rows; r++) {
    let etrpR = map(r, 0, rows, 0.0, 1.0);
    for (c = 0; c < cols; c++) {
      let rndX = (random(-etrpR, etrpR) * 0.25 + c) * cw;
      let rndY = (random(-etrpR, etrpR) * 0.5 + r) * ch;

      push();
      translate(rndX, rndY);
      rotate(random(-etrpR, etrpR) * PI * 0.25);

      drawCell(0, 0, cw, ch);
      pop();
    }
  }
}

function drawCell(_cx, _cy, _cw, _ch) {
  const rectMax = 8;
  const distort = 0.08

  for (let rectCnt = 0; rectCnt < rectMax; rectCnt++) {

    // calculates corner points of the distorted rectangle
    const hW = _cw * rectCnt / rectMax / 2.4;
    const hH = _ch * rectCnt / rectMax / 2.4;
    const points = [{
        x: _cx - (1 + random(-1, 1) * distort) * hW,
        y: _cy - (1 + random(-1, 1) * distort) * hH
      },
      {
        x: _cx + (1 + random(-1, 1) * distort) * hW,
        y: _cy - (1 + random(-1, 1) * distort) * hH
      },
      {
        x: _cx + (1 + random(-1, 1) * distort) * hW,
        y: _cy + (1 + random(-1, 1) * distort) * hH
      },
      {
        x: _cx - (1 + random(-1, 1) * distort) * hW,
        y: _cy + (1 + random(-1, 1) * distort) * hH
      },
    ];

    stroke(30, random(255));
    strokeWeight(random(2));
    beginShape();
    // you need five points to draw a rectangle with vertex
    for (let corner = 0; corner < 5; corner++) {
      vertex(points[corner % 4].x, points[corner % 4].y);
    }
    endShape();
  }
}

 

Day 25. 新しい時計を作る

パーソナル・終末時計です。

明日どうなるかわからない人生。やりたい事があるならば、明日ではなく今日、今日と言わず今始めるべし、と諭してくれたりはしません。

背景は、回転錯視「ボート」を使って、時計が止まってるのに回転してるように見せようとしたものです。しかし、あまりうまく作れず効果が出ませんでした。

参考:回転錯視の作品集

それならと、長針をピクッと動かして、「はっ!もうすぐ私の○○が終わってしまう!」と思わせるようにしようとしたけど、これもイマイチでした。


/*
 * AltEdu2022 Day 25. 新しい時計を作る
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.17
 * license CC0
 */

const w = 720;
const h = w;
const frmRate = 30;
const cycleSec = 5;
const cycleFrm = frmRate * cycleSec;
const cR = w * 0.8;
let bg;

function setup() {
  createCanvas(w, h);
  frameRate(frmRate);

  bg = createGraphics(w, h);
  bg.translate(w * 0.5, h * 0.5);
  bg.background(16, 96, 192);
  bg.fill(16, 128, 224);
  bg.circle(0, 0, cR);

  for (let t = 0; t < TWO_PI * 10; t += PI * 0.12) {
    let r = map(t, 0, TWO_PI * 10, 0.01, 0.7) * w;
    let x = r * cos(t);
    let y = r * sin(t);
    illusion(bg, x, y, t, r * 0.1);
  }
  drawClock(bg, 0, 0, cR);

}

function draw() {

  image(bg, 0, 0);

  const frmRatio = map((frameCount % cycleFrm), 0, cycleFrm, 0.0, 1.0);
  const x = cR * 0.3 * cos(-HALF_PI - PI * (1.0 / 15.0 + frmRatio / 60.0));
  const y = cR * 0.3 * sin(-HALF_PI - PI * (1.0 / 15.0 + frmRatio / 60.0));

  // 長針
  translate(w * 0.5, h * 0.5);
  strokeWeight(cR * 0.05);
  line(0, 0, x, y);

}

// 時計の盤面と短針
function drawClock(_g, _x, _y, _r) {
  _g.push();
  _g.stroke(0);
  _g.strokeWeight(_r * 0.05);
  _g.noFill();
  _g.circle(_x, _y, _r);
  _g.line(_x, _y, _x, _y - _r * 0.25);
  //line(_x, _y, _x - _r * 0.06, _y - _r * 0.3);

  _g.noStroke();
  _g.fill(0);
  for (let t = PI; t <= PI + HALF_PI; t += PI / 6) {
    let x = _r * 0.4 * cos(t);
    let y = _r * 0.4 * sin(t);
    _g.circle(x, y, _r * 0.075);
  }
  _g.pop();
}

// 回転錯視のつもり
function illusion(_g, _x, _y, _t, _s) {
  _g.push();
  _g.translate(_x, _y);
  _g.rotate(_t + HALF_PI);
  _g.noStroke();
  for (let i = 0; i < _s; i++) {
    _g.fill(255, map(i, 0, _s, 50, 0));
    _g.circle(i * 2, 0, _s + i * 0.5);
    //    ellipse(i * 2, 0, _s * 0.5, _s + i);
  }
  _g.pop();
}

 

Day 27. 書いたコードを振り返って自慢する

振り返って、首の柔軟性を自慢する。


/*
 * AltEdu2022 Day 27. 書いたコードを振り返って自慢する
 * p5.js
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.19
 * license CC0
 */

const w = 720;
const h = w;
const frmRate = 24;
const saySec = 3;
let bg, txtr, say;

function setup() {
  createCanvas(w, h, WEBGL);
  noFill();
  noStroke();
  frameRate(frmRate);

  bg = createGraphics(w, h);
  bg.background(240);
  bg.noFill();
  bg.stroke(62);
  bg.strokeWeight(w * 0.1);
  bg.rect(0, 0, w, h);
  bg.fill(0);
  bg.noStroke();
  bg.textSize(20);
  bg.text("\
/* AltEdu2022 Day 3. 住んでいる国や地域の文化的な風習をモチーフに\n\
 * p5.js\n\
 * @author @deconbatch\n\
 * @version 0.1\n\
 * created 2022.02.03\n\
 * license CC0\n\
 * reference https://mathworld.wolfram.com/HeartCurve.html\n\
 */\n\
\n\
        function          setup() {\n\
     const w = 640;     const h = 640;\n\
   createCanvas(w, h);  background(240);\n\
  noStroke();                fill('#f36');\n\
 translate(w * 0.5, h * 0.5); beginShape();\n\
for (let t = 0; t < TWO_PI; t += PI * .01) \n\

 {         let x = pow(sin(t), 3) * 105;\n\
   let y = cos(t * 4) + cos(t * 3) * 3 \n \
     + cos(t * 2) * 6 - cos(t) * 12;\n\
        vertex(x * 2, y * 14);}\n\
                endShape();\n\
                     }\n\
          ", w * 0.15, h * 0.1, w * 0.8, h * 0.8);

  txtr = createGraphics(w * 0.5, h * 0.5);
  txtr.background("#ffcc4d");
  txtr.textSize(200);
  txtr.fill(0);
  txtr.text('😀', 0, h * 0.4);

  say = createGraphics(w * 0.5, h * 0.5);
  say.background(0, 0);
  say.textSize(300);
  say.fill(0);
  say.text('🗨️', 0, h * 0.4);
  say.textSize(32);
  say.text('首180度回るの!\nスゴイでしょ!', w * 0.05, h * 0.2);
}

function draw() {
  background(240);
  push();
  translate(-w * 0.5, -h * 0.5);
  image(bg, 0, 0);
  pop();

  fill("#dd2e44");
  push();
  rotateZ(PI);
  stroke(0);
  cone(50, 200, 8);
  pop();
  
  texture(txtr);
  push();
  translate(0, -100, 0);
  rotateY(constrain(pow(frameCount, 2) * 0.0005, 0.2, 1.4) * PI);
  noStroke();
  sphere(50, 200);
  pop();

  if (frameCount > frmRate * saySec) {
    image(say, -w * 0.5, -h * 0.5);
  }
}

 

Day 28. 100年後の誰かに向けてコードを書く

やあ、100年後のボク。機械の体の調子はどうだい? 機械の体になったからって、調子に乗って雪の平原で機械馬にまたがって人間狩りなんてしてないよね?

さて、このコードを送るから、参照して改変して、100年修行を続けたキミの腕前を見せてくれないかい?

もう忘れてしまっているかもしれないけど、この時代のアニメーションは、「空間」(キャンバス)に「物体」(四角とか丸とか)を置くところから始めて、その後に「時間」を導入して、物体が空間上をどう動くかという作り方をしているんだ。 そう、古典力学の考え方そのものさ。 皆が皆、世界は空間を占める物体からなっているという幻想の中に生きている時代なんだ。

キミの時代は、相対論や量子力学はもちろん、量子重力理論までもが既に一般常識として浸透してるんだろう? きっとクリエイティブ・コーディングにも、自然とその考えを取り入れているよね。 ボクには想像もつかない、時間と空間を同列に扱うコーディング。

…嗚呼、その域に達するまでに、ボクはあと何十年かかるのかな。いや、言わなくていいよ。 ボクはそれが、何十年もかかるであろうことが、そのこと自体が楽しみでしょうがないんだから。


/**
 * AltEdu2022 Day 28. 100年後の誰かに向けてコードを書く
 * Processing
 * @author @deconbatch
 * @version 0.1
 * created 2022.02.20
 * license GPL3
 */

void setup() {
  size(720, 720);
  smooth();
  noLoop();

  int nodeMax = 50;  // ノードの最大数
  int margin  = 160; // 描画マージン
  int divNum  = 10;  // ノードを並べる数
  int frmRate = 5;   // 再生時のframeRate
  int secPlay = 8;   // 再生秒数
  int frmMax  = frmRate * secPlay;
  float baseLen = (max(width, height) - margin * 2) * 1.0 / divNum;

  // ノードの初期値を取得
  ArrayList<PVector> nodes = killSolitude(getNodes(nodeMax, divNum, baseLen), baseLen);

  for (int frmCnt = 0; frmCnt < frmMax; frmCnt++) {

    // ノードをランダムウォークさせる
    randomWalk(nodes, baseLen);
    
    background(240);
    // ノードガーデン
    pushMatrix();
    rectMode(CENTER);
    translate(margin + baseLen * 0.5, margin + baseLen * 0.5);
    drawCircuit(nodes, baseLen, 240, 30);
    popMatrix();

    // 見栄えの外枠
    rectMode(CORNER);
    casing(80);
    saveFrame("frames/" + String.format("%04d", frmCnt) + ".png");
  }
  exit();
}


/**
 * randomWalk : ノードをある確率でランダムウォークさせる
 */
void randomWalk(ArrayList<PVector> _nodes, float _step) {
  for (PVector p : _nodes) {
    if (random(1.0) < 0.05) {
      int direction = floor(random(-1.0, 2.0));
      direction = (direction == 0) ? 1 : direction;
      if (random(1.0) < 0.5) {
        p.x += direction * _step;
      } else {
        p.y += direction * _step;
      }
    }
  }
}


/**
 * drawCircuit : ノードガーデンを描画
 */
void drawCircuit(ArrayList<PVector> _nodes, float _size, int _bgColor, int _fgColor) {

  noFill();
  stroke(_fgColor);
  strokeWeight(0.5);
  for (PVector p : _nodes) {
    for (PVector q : _nodes) {
      float d = dist(p.x, p.y, q.x, q.y);
      if (d > _size * 0.5 && d < _size * 1.5) {

        line(p.x, p.y, q.x, q.y);
        
        if (p.z > 0.5) {
          noFill();
        } else {
          if (random(1.0) < 0.5) {
            noFill();
          } else if (random(1.0) < 0.8) {
            fill(_bgColor);
          } else {
            fill(_fgColor);
          }
        }
        
        if (random(1.0) < 0.3) {
          rect(p.x, p.y, p.z * _size * 0.5, p.z * _size * 0.5, p.z * _size * 0.05);
        } else {
          circle(p.x, p.y, p.z * _size);
        }
      }
    }
  }
}


/**
 * getNodes : ランダムにノードを配置
 */
ArrayList<PVector> getNodes(int _num, int _divNum, float _len) {
  ArrayList<PVector> rnds = new ArrayList<PVector>();
  for (int i = 0; i < _num; i++) {
    float x = floor(random(_divNum)) * _len;
    float y = floor(random(_divNum)) * _len;
    float z = floor(random(1.0, 5.0)) / 5.0;

    boolean hit = false;
    for (PVector p : rnds) {
      if (p.x == x && p.y == y && p.z == z) {
        hit = true;
        break;
      }
    }
    if (!hit) {
      rnds.add(new PVector(x, y, z));
    }
  }
  return rnds;
}


/**
 * killSolitude : 一人ぼっちのノードを削除
 */
ArrayList<PVector> killSolitude(ArrayList<PVector> _nodes, float _size) {
  ArrayList<PVector> majority = new ArrayList<PVector>();
  for (PVector p : _nodes) {
    for (PVector q : _nodes) {
      float d = dist(p.x, p.y, q.x, q.y);
      if (d > _size * 0.5 && d < _size * 1.5) {
        majority.add(p);
        break;
      }
    }
  }
  return majority;
}


/**
 * casing : draw fancy casing
 */
public void casing(int _m) {
  fill(0.0, 0.0);

  strokeWeight(_m * 2 - 10.0);
  stroke(255.0);
  rect(0.0, 0.0, width, height);

  strokeWeight(_m * 2 - 32.0);
  stroke(230.0);
  rect(0.0, 0.0, width, height);

  strokeWeight(20.0);
  stroke(100.0);
  rect(0.0, 0.0, width, height);

  strokeWeight(16.0);
  stroke(255);
  rect(0.0, 0.0, width, height);
}

Processing でアニメーションを作る時、私はフレーム毎の静止画をファイルに落として、それを ffmpeg で動画に変換しています。 リアルタイムで動画を描画しないのであれば、draw() は要らないじゃんということで、このようなコードにしています。

 

 

付録:GPL v3


Copyright (C) 2022- deconbatch

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>


 

 

QooQ