様々な関数の使い方をおさらいし、作例コードを書くことで、自分の知識の定着を図っています。
今回は、beginContour() 関数の解説を行い、サンプルコードを書くことで、その使い方を学びます。誤りや、勘違いがあるかもしれません。何かあれば、ご指摘をいただけると嬉しいです。
p5.js は、バージョン 1.6.0 を使用しています。
beginContour() 関数の説明と使い方
beginContour() | p5.js 公式リファレンス
説明
beginContour() を使うと、自由な形状でくり抜きを行うことができます。
切り抜きは 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() の描画が重なったところがくり抜かれるという感じでしょうか。
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() 同士が重なるところではくり抜きは行われない、ということのようです。
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);
}