p5.js 関数リファレンスと作例:beginContour()

2023年6月4日日曜日

p5.js リファレンス

t f B! P L
beginContour() タイトル画像

様々な関数の使い方をおさらいし、作例コードを書くことで、自分の知識の定着を図っています。

今回は、beginContour() 関数の解説を行い、サンプルコードを書くことで、その使い方を学びます。誤りや、勘違いがあるかもしれません。何かあれば、ご指摘をいただけると嬉しいです。

p5.js は、バージョン 1.6.0 を使用しています。

 

beginContour() 関数の説明と使い方

beginContour() | p5.js 公式リファレンス

説明

beginContour() を使うと、自由な形状でくり抜きを行うことができます。

beginContour() で描いた "D"

切り抜きは erase() でも可能ですが、erase() はキャンバスのくり抜きであるのに対し、beginContour() は図形のくり抜きになります。(erase() は背景ごとくり抜いてキャンバスが透明になります。beginContour() では背景が残ります。)

beginContour() は単独で使用するものではなく、endContour() とセットで使用します。また、くり抜き元となる図形は beginShape() と endShape() のセットで描きます。コード上の順番としては以下のようになります。


beginShape();
// 元となる図形の描画

beginContour();
// くり抜きの描画
endContour();

endShape();


beginShape()/endShape()、 beginContour()/endContour() では、vertex() により図形の頂点座標を指定することで描画を行います。ellipse() や rect() などを用いての描画は無効です。

vertex() での頂点座標の指定は、元の図形の描画が時計回りだとしたら、くり抜く図形の描画は半時計回りという具合に、逆方向で指定する必要があります。

beginContour() から endContour() の間では、translate()、rotate()、scale() は機能しません。

 

書き方

beginContour()

前述のとおり、beginShape()/endShape()、 beginContour()/endContour() のセットで使用する必要があります。

パラメータ

ありません。

返り値

ありません。

 

p5.js 公式リファレンスのコード例の補足


translate(50, 50);
stroke(255, 0, 0);
beginShape();
// Exterior part of shape, clockwise winding
vertex(-40, -40);
vertex(40, -40);
vertex(40, 40);
vertex(-40, 40);
// Interior part of shape, counter-clockwise winding
beginContour();
vertex(-20, -20);
vertex(-20, 20);
vertex(20, 20);
vertex(20, -20);
endContour();
endShape(CLOSE);

小さい四角形でくり抜かれた大きな四角形

beginShape() 直後の 4つの vertex() で外側の四角形を形づくり、beginContour() 後の vertex() で内側の四角形をくり抜いています。

重なったところがくり抜かれる?

beginShape() 直後と、beginContour() 後の 4つの vertex() を入れ替えると、こうなります。


beginShape();
vertex(-20, -20);
vertex(-20, 20);
vertex(20, 20);
vertex(20, -20);

beginContour();
vertex(-40, -40);
vertex(40, -40);
vertex(40, 40);
vertex(-40, 40);

小さい四角形でくり抜かれた大きな四角形

見た目、同じですね。

四角形のサイズを同じにして、beginShape() での四角形を左上に、beginContour() での四角形を右下にずらしてみるとこうなります。


beginShape();
vertex(-40, -40);
vertex(20, -40);
vertex(20, 20);
vertex(-40, 20);
beginContour();
vertex(-20, -20);
vertex(-20, 40);
vertex(40, 40);
vertex(40, -20);

beginShape() と beginContour() が重なった領域だけがくり抜かれている

beginShape() での描画と、beginContour() の描画が重なったところがくり抜かれるという感じでしょうか。

beginContour() を 2つ書くとどうなる?

beginShape() と beginContour() の四角形をずらした例にもう一つ beginContour() を加えてみると、こうなりました。


translate(50, 50);
stroke(255, 0, 0);
beginShape();
vertex(-40, -40);
vertex(20, -40);
vertex(20, 20);
vertex(-40, 20);

beginContour();
vertex(-20, -20);
vertex(-20, 40);
vertex(40, 40);
vertex(40, -20);
endContour();

beginContour();
vertex(0, -50);
vertex(0, 0);
vertex(50, 0);
vertex(50, -50);
endContour();
endShape(CLOSE);

beginContour() が重なるところはくり抜かれていない

beginContour() 同士が重なるところではくり抜きは行われない、ということのようです。

curveVertex() ではどうなる?

元の公式リファレンスのコード例の vertex() をすべて curveVertex() に変えるとこうなります。

上部が凹んでいる

上部がおかしなことになっています。beginContour() 以下をコメントにして、beginShape() と curveVertex() だけにしてみるとこうなりますので、beginContour() に引っ張られて元の形が歪んでしまっているように見えます。

上部は凹んでいない

これ、beginShape() と beginContour() のどちらかを curveVertex()、もう片方を vertex() にしてもこの結果でした。

上部が凹んでいる

 

使いどころがわからない…

動作はわかったものの、これをどういう場面で使ったら良いのかが私にはピンときませんでした。

translate() や rotate() と併用できないのでは使いづらいし、くり抜くだけなら erase() の方が使い勝手が良さそうに思えます。curveVertex() で結果が思うようにならなかった先の例も、以下の例のように背景色と同じ色を使った beginShape() で思い通りに、しかも理解しやすいコードで実現できます。


background(200);
beginShape();
curveVertex(-40, -40);
curveVertex(40, -40);
curveVertex(40, 40);
curveVertex(-40, 40);
endShape(CLOSE);

fill(200);
beginShape();
curveVertex(-20, -20);
curveVertex(-20, 20);
curveVertex(20, 20);
curveVertex(20, -20);
endShape(CLOSE);

上部は凹んでおらず、きれいにくり抜かれているように見える

beginContour() を使わないと実現できないこと、beginContour() を使うと楽に実現できるこれといった表現を、今の私は思いつきません。

 

beginContour() を使った作例

渦状に色が変化するアニメーションを作りました。

渦状に色が変化するアニメーション

背景に変化する色の渦巻きを描き、beginContour() で同じく渦状に切り抜いて背景を見せています。

渦状に変化する色

くり抜きのための渦巻はわざとずらすことで、erase() では実現が難しいであろう変化をつけています。青が中心から外側へ、赤が外側から内側への渦巻です。

小さく青い渦と、大きく赤い渦


/** 
 * p5.js beginContour() 関数の作例
 * 渦巻状にくり抜く
 * 
 * @author @deconbatch
 * @version 0.1
 * @license CC0
 * p5.js 1.6.0
 * created 2023.06.04
 */

const half = 300;
const inner = 8;
const outer = 10;

function setup() {
  createCanvas(half * 2, half * 2);
  noStroke();
  frameRate(16);
}

function draw() {
  translate(half, half);
  
  // 背景
  noStroke();
  for (let t = 0; t < PI * 8; t += 0.2) {
    let nParam = frameCount * 0.1 + t;
    fill(
      224 + noise(100, nParam) * 32,
      32 + noise(200, nParam) * 128,
      32 + noise(300, nParam) * 64
    );
    circle(
      t * outer * cos(t),
      t * outer * sin(t),
      100
    );
  }

  fill(16, 96, 128);
  stroke(0);
  strokeWeight(5);

  beginShape();
  // キャンバス全体を塗る
  vertex(-half, -half);
  vertex(half, -half);
  vertex(half, half);
  vertex(-half, half);
  // 渦巻でくり抜き
  beginContour();
  // 順巻き
  for (let t = 0; t < PI * 6; t += 0.1) {
    vertex(
      t * inner * cos(t),
      t * inner * sin(t)
    )
  }
  // 逆巻き、わざと PI * 8
  for (let t = PI * 8; t > 0; t -= 0.1) {
    vertex(
      t * outer * cos(t),
      t * outer * sin(t)
    )
  }
  endContour();
  endShape(CLOSE);
}

QooQ