Processing
14. ベジエ曲線を利用する
ベジエ曲線は、直線や円弧とは異なる複雑な図形の描画も可能です。
指定する座標が多く、また描かれる曲線の姿が想像しにくい面もありますが、上手く利用すると多様な表情の図形を描くことができます。
制御点を規則的に変化させる
void setup() {
size( 500, 500 );
background( 255 );
smooth();
noLoop();
rectMode( CENTER );
}
void draw() {
int leftX = 50; //始点のx座標
int topY = 50; //始点のy座標
int rightX = 450; //終点のx座標
int bottomY = 450; //終点のy座標
int repeatNum = 50; //繰返しの回数
float controlPitch = ( rightX - leftX ) / repeatNum; //制御点を移動させる幅を、横幅を繰返しの回数で割って求める
int controlSpan = 100;
stroke( 204, 51, 0 );
noFill();
for( int i = 0; i < repeatNum; i++ ) {
float controlX = leftX + i * controlPitch;
float controlY = bottomY - i * controlPitch;
bezier( leftX, topY,
controlX, controlY - controlSpan,
controlX, controlY + controlSpan,
rightX, bottomY );
//rect( controlX, controlY - controlSpan, 4, 4 );
//rect( controlX, controlY + controlSpan, 4, 4 );
}
}
ベジエ曲線を練習する時、始点や終点、制御点を規則的に変化させると、どのような性質を持った曲線なのかが理解しやすくなります。
26行目〜29行目、長くなるので改行していますが、1行に書いても同じことです。
26行目、ベジエ曲線の始点は、x座標leftX、y座標topYに固定です。29行目、ベジエ曲線の描き終わりもx座標rightX、y座標bottomYに固定です。
24、25行目で、ベジエ曲線の2つの制御点の座標の基準を計算で求めていますが、画面左下から画面右上に等間隔で変化するようにしています。
27、28行目、21、22行目で求めた点から実際の制御点の位置を、y方向に-controlSpan、+ controlSpanしています。つまり上下に100ピクセルずらしています。
制御点の間隔は、描画全体の幅( rightX - leftX )を繰返しの回数で割って求めていますので、repeatNumの値さえ変えればベジエ曲線の本数を変えることができます。
30、31行目をコメントではなくすると、制御点の位置に小さな四角が描かれます。

ベジエ曲線は、始点、終点、制御点を変えると、ちょっとした変更でも大きくその表情を変えます。色々試してみて欲しいと思います。
void setup() {
size( 500, 500 );
background( 255 );
smooth();
noLoop();
rectMode( CENTER );
}
void draw() {
int leftX = 50; //始点のx座標
int topY = 50; //始点のy座標
int rightX = 450; //終点のx座標
int bottomY = 450; //終点のy座標
int repeatNum = 50; //繰返しの回数
float controlPitch = ( rightX - leftX ) / repeatNum; //制御点を移動させる幅を、横幅を繰返しの回数で割って求める
stroke( 204, 51, 0 );
noFill();
for( int i = 0; i < repeatNum; i++ ) {
float controlX1 = leftX + i * controlPitch;
float controlX2 = rightX - i * controlPitch;
bezier( controlX1, topY,
controlX1, 1000,
controlX2, -500,
controlX2, bottomY );
}
}

ベジエ曲線を描画単位に利用する
「13. 描画の単位を考える」で行ったような描画単位にベジエ曲線を利用してみます。
どのような描画単位しようか考える際には、いきなり数多くの繰返しを行うのではなく(例えば以下のrepeatNumを1にしておくなどして)描画単位がある程度作り、その上で繰返しを行った方が良いでしょう。
描画単位:異なる大きさの波形の線
void setup() {
size( 500, 500 );
background( 255 );
smooth();
noLoop();
colorMode( HSB, 360, 100, 100, 100 );
}
void draw() {
int repeatNum = 9;
int shapePitch = 50;
int shapeSize = 50;
int pitchWidth = shapePitch * ( repeatNum - 1 );
int leftX = ( width - pitchWidth ) / 2;
for( int i = 0; i < repeatNum; i++ ) {
for( int j = 0; j < repeatNum; j++ ) {
float drawPointX = leftX + j * shapePitch;
float drawPointY = leftX + i * shapePitch;
float lineAlpha = random( 50, 100 );
stroke( 240, 80, 70, lineAlpha );
strokeWeight( random( 1, 4 ) );
noFill();
drawShape( drawPointX, drawPointY, shapeSize );
}
}
}
void drawShape( float centerX, float centerY, int drawSize ) {
int repeatBezNum = 5;
float ctlPointPitch = drawSize / repeatBezNum;
float preX = centerX - drawSize / 2;
for( int i = 0; i < repeatBezNum; i++ ) {
float yuragiX = random( -ctlPointPitch/4, ctlPointPitch/4 );
float yuragiY1 = random( drawSize, drawSize*3 );
float yuragiY2 = random( drawSize, drawSize*3 );
float postX = preX + ctlPointPitch;
bezier( preX, centerY,
postX - ctlPointPitch/2 - yuragiX, centerY - yuragiY1,
postX - ctlPointPitch/2 + yuragiX, centerY + yuragiY2,
postX, centerY );
preX = postX;
}
}
23、24行目、繰返しの中で線の透明度に指定する変数の値をランダムに求め、色の透明度を変えています。同様に、25行目、線の太さもランダムにしています。
40〜50行目、drawShape()内で、さらに繰返しを記述し、その回数に応じて波の形になるように、ベジエ曲線に必要な座標を変化させています。
ランダムを多用すれば良いというものでもないのですが、プログラムで手描きらしさをどのように出すか、ということも考えてみる方向の1つとしては面白いかもしれません。
描画単位:ベジエ曲線でつくる形
void setup() {
size( 500, 500 );
background( 255 );
smooth();
noLoop();
colorMode( HSB, 360, 100, 100, 100 );
}
void draw() {
int repeatNum = 20;
int shapePitch = 20;
int shapeSize = 100;
int pitchWidth = shapePitch * ( repeatNum - 1 );
int leftX = ( width - pitchWidth ) / 2;
for( int i = 0; i < repeatNum; i++ ) {
for( int j = 0; j < repeatNum; j++ ) {
float drawPointX = leftX + j * shapePitch;
float drawPointY = leftX + i * shapePitch;
float colorHue = random( 360 );
fill( colorHue, 70, 100, 60 );
noStroke();
drawShape( drawPointX, drawPointY, shapeSize );
}
}
}
void drawShape( float centerX, float centerY, int drawSize ) {
float tmpX = random( -drawSize/4, drawSize/4 );
float yuragiX = random( 5 );
float topX = centerX + tmpX;
float bottomX = centerX - tmpX;
beginShape();
vertex( topX, centerY - drawSize/2 );
bezierVertex( topX + yuragiX, centerY - drawSize/4,
topX + yuragiX, centerY,
bottomX, centerY + drawSize/2 );
bezierVertex( topX - yuragiX, centerY,
topX - yuragiX, centerY - drawSize/4,
topX, centerY - drawSize/2 );
endShape();
}
ベジエ曲線で形を描く場合、40行目、beginShape();で始め、48行目、endShape();で終わります。
41行目、図形の始点へvertex()を使って移動し、その後はbezierVertex()で図形を描きます。
bezierVertex()は直前の描画の終点がそのまま始点になります。
つまり45行目の時点では、直前のベジエ曲線の終点の座標が、自動的に次の曲線の始点になります。したがって、bezierVertex()をつなげて書くことで、ベジエ曲線で囲まれた図形を描くことができます。