p5.js備忘録

p5.jsは、JavaScriptを使ってクリエイティブコーディングを実現するライブラリです
Processingという言語をベースにしていて、プログラミング初心者にも扱いやすい設計がされています

p5.jsはCanvas APIを抽象化していて便利な関数が多く、使いやすいAPIが提供されているので、ネイティブのCanvasAPIを使うよりかなり楽です
*p5.jsはcreateCanvas()関数にWEBGLオプションを指定することで、WebGLモードを有効にすることもできます

備考

  • Canvas APIはHTML5の一部として導入された技術で、2Dグラフィックを描画するためのものです。JavaScriptを用いて簡単に図形、テキスト、画像などを描画できます
  • WebGLはWeb上で3Dグラフィックを描画するための技術で、HTML5のCanvas要素上で動作、3Dグラフィックスや、より高度な2Dグラフィックスの描画に使用されます
目次
  1. 基本の使い方
  2. ベクトル
  3. Vertex(頂点)
  4. 桜の花びら

基本の使い方

/* CDN */
<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
<script>
function setup() {
  createCanvas(300, 100);
  frameRate(1); 
}

function draw() {
  console.log(frameCount) //frameCountは表示されたフレームの数
}
</script>
  • createCanvas()
    キャンバス要素を作成します
    setup() の開始時に一度だけ呼び出す
    createCanvas() を使用しない場合、100×100ピクセルです
  • frameRate(Number型)
    setup() 内でフレームレートを設定が推奨
    frameRateを書かない場合はほとんどのコンピューターでは毎秒60フレーム
    引数なしで frameRate() を呼び出すと現在のフレームレートが返されます
    *滑らかなアニメーションには、毎秒24フレーム以上

指定された親に要素をアタッチする場合

<div id="myContainer"></div>
<script>
function setup() {
 let cnv = createCanvas(100, 100);
 cnv.parent('myContainer');
}
</script>
  • preload()
    画像のロードなどで使用
    preload()が定義されている場合、setup() はその中のロード呼び出しが完了するまで待機します
    *loadImage、loadJSON、loadFont、loadStrings以外はpreload()に含めない
    *ちなみにすべてのロード関数はオプションの最後の引数としてコールバック関数を受け入れることもできます
  • setup()
    プログラムの開始時に1回呼び出されます
    画面サイズや背景色などの初期環境を定義します
  • draw()
    draw関数はプログラムが停止するか noLoop() が呼び出されるまでコードを継続的に実行します
    noLoop() loop() redraw()で制御します
    draw() の実行回数はframeRate() で制御できます
    setup()noLoop() が呼び出された場合でも、停止する前にdraw() が1回実行されます
  • noLoop()
    draw() 内のコードを継続的に実行するのを停止します
  • loop()
    停止を再開できます
    setup() 内から loop() を呼び出さない
  • redraw()
    draw() 内のコードを1回実行します
    たとえばmousePressed() など登録されたイベントが発生した場合のみウィンドウを更新できます
    描画の回数は引数に整数を追加します(デフォルトは1)
  • isLooping()
    isLooping() は現在の状態を返します
  • remove()
    p5.js によって作成されたキャンバスとすべての要素が削除されます
  • push()pop()
    push() 関数は現在の描画スタイルの設定と変換を保存し、pop() はこれらの設定を復元します
    *常に一緒に使用されます

*p5をページにロードするにはsetup() または draw() のいずれかは必須

1つのWebページに複数のp5スケッチを含める場合(インスタンス モード)
*デフォルトはp5.j 関数はグローバル名前空間にあります

const canvas1 = c1 => {
  c1.setup = ()=> {
   c1.createCanvas(100, 100).parent('canvas1');
   c1.textSize(30);
   c1.textAlign(c1.CENTER, c1.CENTER);
   c1.noLoop();
  };
  c1.draw = () =>{
   c1.background(200);
   c1.text(c1.frameCount, c1.width / 2, c1.height / 2);
  };
 c1.mousePressed=()=>{
  c1.redraw();
  return false;
  }
};
new p5(canvas1); 

クリックでframeCountを更新

<div id="canvas1"></div>
<script>
function setup() {
  createCanvas(100, 100).parent('canvas1');
  frameRate(1); 
  textSize(30);
  textAlign(CENTER, CENTER);
}
function draw() {
  background(200);
  text(frameCount, width / 2, height / 2);
}
function mousePressed() {
  redraw();
  return false;
}
/* 備考:touchStarted()やmousePressed()だとスマフォでスクロールしなくなるので
const el1 = document.getElementById('canvas1');
   el1.addEventListener('click', ()=>{
   redraw();
  })
にしている*/
</script>

mousePressed()
タッチが登録されるたびに呼び出されます
touchStarted()が定義されていない場合代わりに mousePressed() が定義されていれば呼び出されます
デフォルトの動作を防ぐには、メソッドの最後にreturn falseを追加します
*その他MouseやKeyboardのイベントを処理する仕組みがあります

備考:mousePressed()などのイベントを使うと、なぜかスマフォでスクロールしなくなる😂

定数と便利な変数

  • QUARTER_PI:円の円周と直径の比率の 4 分の 1 です
  • HALF_PI:円の円周と直径の比率の半分
  • PI:円の直径に対する円周の比率
  • TWO_PI:円の円周と直径の比率の 2 倍
  • windowWidth:ウィンドウの幅
  • windowHeight:ウィンドウの高さ
  • width:キャンバスの幅
  • height:キャンバスの高さ
  • mouseX:キャンバスの (0, 0) に対するマウスの現在の水平位置
  • mouseY:キャンバスの (0, 0) に対するマウスの現在の垂直位置

windowResized()はウィンドウのサイズが変更されるたびに1回呼び出されます
resizeCanvas()は指定された幅と高さにキャンバスのサイズを変更・クリア・再レンダリングをします

function windowResized() {
 resizeCanvas(windowWidth, windowHeight);
}

setup内で設定するモードの例

  • colorMode()
    デフォルトはcolorMode(R​​GB)
    colorMode(HSB) を設定するとHSBを使用できます
    *同系色や明暗をランダムに作りたいときはH(色相)を使います
    *例:fill(360, 100, 100, 1)
  • angleMode()
    RADIANS or DEGREESを設定します
    デフォルトはRADIANS です

備考

  • RADIANS(弧度法)は円の半径と同じ長さの孤を切り取るときの角の大きさを「1 rad」と規定して、DEGREESより正確です
  • DEGREES(度数法)は、円周を360等分したときの角の大きさを「1 deg」と規定

*「度数法」を使用するにはradians()を使用して度をラジアンに変換するかangleMode(DEGREES)を使用します

ベクトル

ベクトル(数字のリスト)で2次元「x,y」や3次元「x,y,z」を管理すると便利
createVector()
ベクトルを格納するためのデータ型を作成します

let v1;
function setup() {
  createCanvas(100, 100);
  stroke(255, 0, 255);
  v1 = createVector(width / 2, height / 2);
}
function draw() {
  background(255);
  line(v1.x, v1.y, mouseX, mouseY); //v1.xとv1.yで取得
}

主なベクトル計算のメソッド

  • add()
    ベクトルを加算
  • sub()
    ベクトルを減算
  • mult()
    スカラーを乗算
  • div()
    ベクトルをスカラーで除算
  • mag()
    ベクトルの長さを計算し、結果をfloatとして返します
    *これは単に方程式 sqrt(xx + yy + z*z) と同じ
  • normalize()
    ベクトルを長さ「1」に正規化します
    正規化はベクトルの方向は維持しつつ長さ「1」にすることです
    *長さ「1」のベクトルのことを単位ベクトルと呼びます

クリックでスタートとストップ

<div id="canvas2"></div>
<script>
const NUM = 30 
let deg = 360/ NUM
let balls = [];
function setup() {
  createCanvas(300, 300).parent('canvas2');
  colorMode(HSB);
  angleMode(DEGREES)
  for (let i = 0; i < NUM; i++) {
   balls.push(new Ball(360/ NUM * i, cos(deg * i), sin(deg * i)))
  }
  noLoop();
}
function draw() {
  background(255);
  balls.forEach(ball=>{
    ball.move();
    ball.display();
  })
}
//クリックで停止と再開 
function mousePressed() {
  isLooping()? noLoop():loop()
  return false;
}

class Ball {
  constructor(color, vX, vY) {
    this.vPos = createVector(width/2, height/2);
    this.radius = 15;
    this.color = color;
    this.vVelocity = createVector(vX, vY);
  }
  move() {
    //ベクトルの加算するだけで、それぞれのXとYを加算する必要がない
    this.vPos.add(this.vVelocity) 
    if(this.vPos.x  > width || this.vPos.x < 0){
     this.vVelocity.x = -this.vVelocity.x
    }
    if(this.vPos.y  > height || this.vPos.y < 0){
     this.vVelocity.y = -this.vVelocity.y
   }
  }
  display() {
    ellipse(this.vPos.x, this.vPos.y, this.radius, this.radius);
    fill(this.color, 100, 100, 1);
    noStroke()
  }
}
</script>
  • cos()
    コサインを計算(値は -1〜1の範囲で返されます)
  • sin()
    サインを計算(値は -1〜1の範囲で返されます)
  • random()
    • ランダムな浮動小数点数を返します
    • 引数が指定されていない場合は、0から1までの乱数 (1 を含まない)
    • 引数が1つの場合は、0からその数値までの乱数(数値は含まない)
    • 引数が2つの場合は、最初の引数から2 番目の引数までの乱数(2 番目の引数は含まない)
    • 配列の場合、その配列からランダムな要素を返します
  • noise()
    指定された座標でのパーリンノイズ値を返します
    0.005 ~ 0.03 のステップが最適です
  • atan2(y, x)
    *第一引数がy座標
    正のx軸から測定した、指定した点から座標原点までの角度 を計算します
    angleMode() が RADIANS の場合は値は PI から -PIの範囲のfloat
    angleMode() が DEGREES の場合は180から-180の範囲で返されます
<div id="canvas3"></div>
<script>
let eyes=[];
function setup() {
  createCanvas(300, 300).parent('canvas3');
  noStroke();
  eyes.push(new Eye(c3.width*0.25, c3.height*0.25, 100));
  eyes.push(new Eye(c3.width*0.25, c3.height*0.75, 100));
  eyes.push(new Eye(c3.width*0.75, c3.height*0.75, 100));
  eyes.push(new Eye(c3.width*0.75, c3.height*0.25, 100));
}
function draw() {
  background(102);
  eyes.forEach(eye=>{
    eye.update(mouseX, mouseY);
    eye.display();
  })
}
class Eye{
constructor(x, y, size){
  this.x = x;
  this.y = y;
  this.size = size;
  this.angle = 0;
}
update(mx, my){
    this.angle = atan2(my - this.y, mx - this.x);
  }
display() {
    push();
    translate(this.x, this.y);
    fill(255);
    ellipse(0, 0, this.size, this.size);
    rotate(this.angle);
    fill(153, 204, 0);
    ellipse(this.size / 4, 0, this.size / 2, this.size / 2);
    pop();
  };
}
</script>

*translate()は後で呼び出されるdraw関数に常に適用されます

Vertex(頂点)

  • vertex()
    beginShape() および endShape() 関数内で点・線・多角形などの頂点座標を指定するために使用します
  • beginShape()
    beginShape() はシェイプの頂点の記録を開始し、endShape() は記録を停止します
    * translate()、rotate()、scale() などの変換は、beginShape() 内では機能しません
    *beginShape() 内で ellipse() や rect() などの他の形状を使用できません
    パラメータの値
    • なし:形状は任意の不規則な多角形にすることができます
    • POINTS: 一連の点を描く
    • LINES: 一連の接続されていない線分 (個々の線))を描画します
    • TRIANGLES :一連の個別の三角形を描画します
    • TRIANGLE_FAN :最初の頂点を共有する一連の接続された三角形を扇状に描画します
    • TRIANGLE_STRIP :一連の接続された三角形をストリップ形式で描画します
    • QUADS: 一連の個別のクワッドを描画します
    • QUAD_STRIP :隣接するエッジを使用してクワッド ストリップを描画し、次のクワッドを形成します
  • endShape()
    beginShape() の後にのみ呼び出すことができます
    前回の beginShape() の呼び出し以降に定義されたすべての画像データが画像バッファーに書き込まれます
    パラメータの値
    • CLOSE:形状を閉じる (開始と終了を接続する)

備考
極座標(距離,角度)と直交座標(x,y)
距離と角度からXとYを計算する
x = 距離 * cos(角度)
y = 距離 * sin(角度)

<div id="canvas4"></div>
<script>
function setup() {
 createCanvas(300, 300).parent('canvas4');
 push();
 translate(width * 0.25, height * 0.25)
 star(50, 20, 5);
 pop();
 push();
 translate(width * 0.75, height * 0.25);
 star(50, 20, 10);
 pop();
 push();
 translate(width * 0.25, height * 0.75);
 star(50, 50, 4);
 pop();
 push();
 translate(width * 0.75, height * 0.75);
 star(50, 40, 8);
 pop();
}
function star(radius1, radius2, points) {
  let angle = TWO_PI / points; // TWO_PIは360
  let halfAngle = angle / 2.0;
  beginShape();
  for (let a = 0; a < TWO_PI; a += angle) {
    let sx = cos(a) * radius2;
    let sy = sin(a) * radius2;
    vertex(sx, sy);
    sx = cos(a + halfAngle) * radius1;
    sy = sin(a + halfAngle) * radius1;
    vertex(sx, sy);
  }
  endShape(CLOSE);
}
</script>

極座標で r(原点からの距離)= sin(n*角度) の「nを分数」にするとバラ曲線が描けます
バラ曲線 の表(nが分子dが分母

上の表8/5

<div id="canvas5"></div>
<script>
function setup(){
  createCanvas(300, 300).parent('canvas5');
  translate(width / 2, height / 2);
  roseCurve(8,5);
}
function roseCurve(n,d) {
 beginShape();
 for(let angle = 0; angle < TWO_PI * d; angle += 0.01){
    let r = 100 * sin(angle * (n / d));
  //直座標(x,y)に変換
    let x = r * cos(angle);  
    let y = r * sin(angle);
    vertex(x, y);
  }
 endShape(CLOSE);
}
</script>

桜の花びら

<div id="canvas6"></div>
<script>
function setup(){
  createCanvas(300, 300).parent('canvas6');
  translate(width / 2, height / 2);
  sakura(4)//花びらの枚数
}
function sakura(n) {
  beginShape();
  for(let angle = 0; angle < 360 ; angle++){
    let A = n/ PI * radians(angle);
    let mod = floor(A) % 2; 
    let r0 = pow(-1, mod) * (A - floor(A)) + mod;
    let h = r0 < 0.8 ? 0 : 0.8 - r0
    let r = r0 + 2 * h;
    let x = 100 * r * cos(radians(angle)); 
    let y = 100 * r * sin(radians(angle)); 
    vertex(x, y);
  }
  endShape(CLOSE);
}
</script>

クリックすると花びらが散る

<div id="canvas7"></div>
<script>
let petals = [];
const NUM = 100
function setup() {
  createCanvas(300, 300).parent('canvas7');
  frameRate(30);
  colorMode(HSB);
  noStroke();
  for (let i = 0; i < NUM; i++) {
   petals.push(new Sakura())
  }
  noLoop();
}
function draw() {
  background(255);
  petals.forEach(petal=>{
    petal.display();
    petal.move();
  })
}
//クリックで停止と再開
function mousePressed() {
  isLooping()? noLoop():loop()
  return false;
}
class Sakura {
  constructor() {
   this.hue = random(320, 350);
   this.alpha = random(0.6, 1);
   this.size = random(20, 40);
   this.xRadius = random(10, 50);
   this.base = random(width)
   this.vPos = createVector(this.base, random(height));
   this.vAngle = createVector(random(360), random(360))
   this.vVelocity = createVector(1, this.size / 20)
   //花びらの形を変える
   this.yScale = 1;
  }
  move() {
    this.vAngle.add(this.vVelocity) 
    this.yScale = abs(sin(radians(this.vAngle.x)));
    // X軸の動き 基準値 + 半径 * -1から1
    this.vPos.x = this.base + this.xRadius * sin(radians(this.vAngle.y));
    // Y軸の動き
    this.vPos.y += this.vVelocity.y;
    if(this.vPos.y > height + this.size){
      this.vPos.y = -this.size;
    }
  }
 display() {
  fill(this.hue, 30, 100, this.alpha);
  push();
  translate(this.vPos.x, this.vPos.y);
  rotate(radians(this.vAngle.x));
  beginShape();
   for(let angle = 0; angle < 360 /4 ; angle++){
    let A = 4/ PI * radians(angle);
    let mod = floor(A) % 2; 
    let r0 = pow(-1, mod) * (A - floor(A)) + mod;
    let h = r0 < 0.8 ? 0 : 0.8 - r0
    let r = r0 + 2 * h;
    let x = this.size * r * cos(radians(angle)); 
    let y = this.yScale * this.size * r * sin(radians(angle)); 
    vertex(x, y);
  }
  endShape(CLOSE);
  pop();
  }
}
</script>