クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【p5.js】Generative Design with p5.js「色 P_1_0_03」のコードを読み解く

p5.jsの続きです。前回は「Generative Design with p5.js」の色編の第二回でした。今回は3回目です。今回は円形に配置した色のスペクトル。three.jsの記事でやった三角関数が出てきます。それではいきましょう!

【目次】

※参考:【p5.js】Generative Design with p5.js「色 P_1_0_02」のコードを読み解く - クモのようにコツコツと

p5.jsを習得するためにやったことまとめ
qiita.com

完成品

書籍「Generative Design with p5.js」より

こちらのソースコード
※参考:Generative Design with p5.js[p5.js版ジェネラティブデザイン] ―ウェブでのクリエイティブ・コーディング

「P.1.1.2 円形に配置した色のスペクトル」のソースコード

※参考:p5.js Web Editor

初期表示が漆黒の円で面食らうw

円の中がスペクトルになっていてカーソルを動かすと色合いが変わる。上下が明度で左右が彩度。

あとキーで1〜5を打つとスペクトルの円を作っている三角形の数が変わる!

完成品のコード(全体)

コードの全体がこちら

// P_1_1_2_01
//
// Generative Gestaltung – Creative Coding im Web
// ISBN: 978-3-87439-902-9, First Edition, Hermann Schmidt, Mainz, 2018
// Benedikt Groß, Hartmut Bohnacker, Julia Laub, Claudius Lazzeroni
// with contributions by Joey Lee and Niels Poldervaart
// Copyright 2018
//
// http://www.generative-gestaltung.de
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * changing the color circle by moving the mouse.
 *
 * MOUSE
 * position x          : saturation
 * position y          : brighness
 *
 * KEYS
 * 1-5                 : number of segments
 * s                   : save png
 */
'use strict';

var segmentCount = 360;
var radius = 300;

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

function draw() {
  colorMode(HSB, 360, width, height);
  background(360, 0, height);

  var angleStep = 360 / segmentCount;

  beginShape(TRIANGLE_FAN);
  vertex(width / 2, height / 2);

  for (var angle = 0; angle <= 360; angle += angleStep) {
    var vx = width / 2 + cos(radians(angle)) * radius;
    var vy = height / 2 + sin(radians(angle)) * radius;
    vertex(vx, vy);
    fill(angle, mouseX, mouseY);
  }

  endShape();
}

function keyPressed() {
  if (key == 's' || key == 'S') saveCanvas(gd.timestamp(), 'png');

  switch (key) {
  case '1':
    segmentCount = 360;
    break;
  case '2':
    segmentCount = 45;
    break;
  case '3':
    segmentCount = 24;
    break;
  case '4':
    segmentCount = 12;
    break;
  case '5':
    segmentCount = 6;
    break;
  }
}

冒頭がクレジットでその次が概要。概要を訳すとこうある。

/ **
  *マウスを動かしてカラーサークルを変更する。
  *
  *マウス
  *位置x:彩度
  *位置y:明るさ
  *
  *キー
  * 1-5:セグメント数
  * s:pngを保存
  * /

全体構成

まずは全体構成

冒頭にグローバルに使われる変数が設定されている

var segmentCount = 360;
var radius = 300;
  • 変数segmentCountの初期値は360
  • 変数radiusの初期値は300

セグメント数とはなんぞ?

英語で、断片、部分、切れ目、分割(されたもの)などの意味。

※参考:セグメント - Wikipedia

radiusはCSSの角丸border-radiusでお馴染みの単語だが「半径」という意味のようだ。

その後の3つの関数はいつもと同じ。

function setup() {
  // ページ読み込み時の処理
}

function draw() {
  // 一定時間ごとに繰り返し実行
}

function keyPressed() {
  // キーを押した時に実行
}

では関数の中を見ていく。

setup()の中身

最初にsetup()メソッドの中身

function setup() {
  createCanvas(800, 800);
  noStroke();
}
  • createCanvas()でキャンバズを作成。引数はサイズで幅も高さも800
  • noStroke()でキャンバズに線は無し

今回のsetup()はキャンバス設定のみ。あっさり。

draw()の中身

draw()メソッド定義(全体)

次はdraw()メソッド。ここが今回の本丸ぽい。

function draw() {
  colorMode(HSB, 360, width, height);
  background(360, 0, height);

  var angleStep = 360 / segmentCount;

  beginShape(TRIANGLE_FAN);
  vertex(width / 2, height / 2);

  for (var angle = 0; angle <= 360; angle += angleStep) {
    var vx = width / 2 + cos(radians(angle)) * radius;
    var vy = height / 2 + sin(radians(angle)) * radius;
    vertex(vx, vy);
    fill(angle, mouseX, mouseY);
  }

  endShape();
}

いくつかの塊があるので塊ごとに見ていく。

colorMode()でカラーモードをHSBに

  colorMode(HSB, 360, width, height);
  background(360, 0, height);
  • colorMode()でカラーモードを設定。第一引数でHSBモードに。次は値で色相(H)は360、彩度(S)は幅、明度(B)は高さ
  • background()で背景設定。色相(H)は360、彩度(S)は0、明度(B)は高さ

カラーモードがHSBなので背景色もHSBで設定されている。からモードの初期値は色相は360で固定。彩度と明度は幅、高さの値に。

背景色のサイドは0で固定。これで背景は真っ白になっている。

角度の段階数

var angleStep = 360 / segmentCount;
  • 変数angleStepは360をsegmentCountで割った数

アングルは角度なので、角度の段階数か。segmentCountの初期値は360なので360を360で割ると1になる。角度の段階が1度ずつということか。

beginShape()で形状を作成

  beginShape(TRIANGLE_FAN);
  // 中略
  endShape();
  • beginShape()で形状を作成。引数はモードでTRIANGLE_FAN
  • endShape()で計上作成を終了

beginShape()メソッドで形状を作成できる

※参考:reference | p5.js

Using the beginShape() and endShape() functions allow creating more complex forms. beginShape() begins recording vertices for a shape and endShape() stops recording. The value of the kind parameter tells it which types of shapes to create from the provided vertices. With no mode specified, the shape can be any irregular polygon.

beginShape()およびendShape()関数を使用すると、より複雑なフォームを作成できます。 beginShape()はシェイプの頂点の記録を開始し、endShape()は記録を停止します。 kindパラメーターの値は、提供された頂点から作成する形状のタイプを指示します。 モードを指定しない場合、形状は任意の不規則な多角形にすることができます。

beginShape()の引数の中でモードを設定しているが、TRIANGLE_FANとは「三角ファン」という意味でこんな形を作る。

f:id:idr_zz:20200421075433j:plain

beginShape()endShape()の間でvertex()で設定しているようだが、ここがまたキモっぽいので細かく見ていく。

vertex()で形状の最初の頂点(中心点)を設定

beginShape()endShape()の間のvertex()部分。

  vertex(width / 2, height / 2);
  • vertex()メソッドの第一引数は幅の半分、第二引数は高さの半分

vertex()メソッドとは?vertexは「頂点」という意味のようだ。

※参考:reference | p5.js

All shapes are constructed by connecting a series of vertices. vertex() is used to specify the vertex coordinates for points, lines, triangles, quads, and polygons. It is used exclusively within the beginShape() and endShape() functions.

すべての形状は、一連の頂点を接続することによって構築されます。 vertex()は、ポイント、ライン、三角形、四角形、およびポリゴンの頂点座標を指定するために使用されます。 beginShape()およびendShape()関数内でのみ使用されます。

頂点を設定しているんだな。

vertex(x, y)

vertex()の引数は横位置(x)、縦位置(y)の順番なので、これを幅の半分、高さの半分にすることで、キャンバスの中心が最初の頂点になる。

vertex()とfor文で2つ目以降の頂点を設定

その次はfor文でループしているが、中にはvertex()があるので2つ目以降の頂点設定だ。

  for (var angle = 0; angle <= 360; angle += angleStep) {
    var vx = width / 2 + cos(radians(angle)) * radius;
    var vy = height / 2 + sin(radians(angle)) * radius;
    vertex(vx, vy);
    fill(angle, mouseX, mouseY);
  }
  • for文で繰り返し。条件は変数angleの開始が0、360まで繰り返す、angleangleStepを加算する
  • 変数vxはキャンバス中心の横位置とcos(radians(angle)) * radius(後述)を足す
  • 変数vyはキャンバス中心の横位置とsin(radians(angle)) * radius(後述)を足す
  • vertex()の引数にvxvyを入れる
  • fill()で塗りぶり色を設定。引数は左からangle、マウスの横位置、マウスの縦位置

for文は360まで繰り返すが、その加算値はangleStep。初期値は1なので1ずつ加算して360回繰り返すと。

vertex()の引数にはvxvyを入れているが、この中のcos()やらsin()がパッと見では何をやっているかよくわからない、さらにキモな部分。

fill()は塗りつぶし設定。HSBモードで色相(H)はangleなので0〜360の間で段階的に繰り返す。彩度(S)はマウスの横位置、明度(B)はマウスの縦位置。これによってマウスの動きで彩度と明度が変わったわけだ!

cos()で三角関数のコサインを設定

今回の一番のキモ of キモっぽいcos()sin()部分を丁寧に見ていく。

まずはcos()が出てくる変数vxの内容

var vx = width / 2 + cos(radians(angle)) * radius;

これは一体何をされている方なの?

まず、width / 2はキャンバス幅の半分なのでキャンバスの横の中心になる。そしてradiusは最初のグローバル変数で半径だった。

置き換えるとこうなる。

var vx = キャンバス中心(横) + cos(radians(angle)) * 半径;

で、残るはここ!cos()メソッド!ユー・アー・キモ of kings!

cos(radians(angle)

cos()メソッドはコサインを計算するメソッドらしい。

※参考:reference | p5.js

Calculates the cosine of an angle. This function takes into account the current angleMode. Values are returned in the range -1 to 1.

角度の余弦を計算します。 この関数は、現在のangleModeを考慮に入れます。 値は-1から1の範囲で返されます。

「余弦」とは「コサイン」のこと。コサインは直角三角形における「鋭角」と「線の長さ」の関係を示す三角関数の中の一つ。

※参考:【Three.js】カメラ制御を実現している「三角関数」を理解する!(サイン、コサイン、タンジェント) - クモのようにコツコツと

radians()で角度をラジアンに変換

で、cos()メソッドの引数にあるradians()メソッドは角度をラジアンに変換するメソッド。

※参考:reference | p5.js

Converts a degree measurement to its corresponding value in radians. Radians and degrees are two ways of measuring the same thing. There are 360 degrees in a circle and 2*PI radians in a circle. For example, 90° = PI/2 = 1.5707964. This function does not take into account the current angleMode.

度の測定値をラジアン単位の対応する値に変換します。 ラジアンと度は、同じものを測定する2つの方法です。 円には360度、円には2 * PIラジアンがあります。 たとえば、90°= PI / 2 = 1.5707964です。 この関数は、現在のangleModeを考慮しません。

ラジアンも三角関数の記事で触れた。円の半径の長さに等しい弧に対する中心角の大きさのこと。

※参考:【Three.js】カメラ制御を実現している「三角関数」を理解する!(サイン、コサイン、タンジェント) - クモのようにコツコツと

p5.jsのアングルモード(angleMode)ってなんぞ?

※参考:reference | p5.js

Sets the current mode of p5 to given mode. Default mode is RADIANS.

p5の現在のモードを指定されたモードに設定します。 デフォルトモードはRADIANSです。

初期値はラジアン(RADIANS)のようだ。

Parameters
mode Constant: either RADIANS or DEGREES

パラメーター
モード定数:RADIANSまたはDEGREES

パラメータは定数が2つあり、 RADIANSはラジアンでDEGREESは度数か。

コサインから頂点の横位置を設定

改めてコードを見るとやっていることがわかる。

cos(radians(angle)
  • p5.jsのアングルモードはラジアンがデフォルトなのでradians()メソッドでangleの角度をラジアンに変換する
  • そのラジアンの値からcos()メソッドでコサインの値を割り出す

最初のコードを置き換えると

var vx = width / 2 + cos(radians(angle)) * radius;

こういう意味になる。

var vx = キャンバス中心(横) + コサイン * 半径;

おぉ、これって三角関数の記事でやった!「角度から座標(位置)を割り出す」だ!

  • 半径の長さrとコサインを掛けるとX(横)位置

※参考:【Three.js】カメラ制御を実現している「三角関数」を理解する!(サイン、コサイン、タンジェント) - クモのようにコツコツと

半径とコサインをかけてX(横)位置を割り出すが、さらにスタート地点をキャンバスの中心にするために「キャンバス中心(横)width / 2」も足しているということだ!

sin()でサインから頂点の縦位置を設定

そうすると、もう一つのvyもサインで同じことをやっているとわかる

var vy = height / 2 + sin(radians(angle)) * radius;

height / 2はキャンバス高さの半分なので「キャンバス中心(縦)」でradiusは半径。

sin()メソッドはサインの値を割り出す

Calculates the sine of an angle. This function takes into account the current angleMode. Values are returned in the range -1 to 1.

角度のサインを計算します。 この関数は、現在のangleModeを考慮に入れます。 値は-1から1の範囲で返されます。

※参考:reference | p5.js

でコサインと同じくradians()メソッドでangleの値をラジアンに変換している。

最初のコードを置き換えると

var vy = height / 2 + sin(radians(angle)) * radius;

こういう意味になる。

var vy = キャンバス中心(縦) + サイン * 半径;

これもまた三角関数の記事で触れた「角度から座標(位置)を割り出す」と同じ式だ!

半径の長さrとサインを掛けるとY(縦)位置

※参考:【Three.js】カメラ制御を実現している「三角関数」を理解する!(サイン、コサイン、タンジェント) - クモのようにコツコツと

半径とサインをかけてY(縦)位置を割り出すが、さらにスタート地点をキャンバスの中心にするために「キャンバス中心(縦)height / 2」も足しているということだ!

1つ目のvertex()と2つ目以降(for文内)のvertex()の比較

beginShape()の後、一番最初に出てきたvertex()とfor文の中の2つ目以降のvertex()を比較する。

最初のvertex()

vertex(width / 2, height / 2);

vertex()は頂点の設定だが、最初の頂点は第一引数がキャンバス幅の半分、第二引数はキャンバス高さの半分で、キャンバスの中心点になる。

for文の中のvertex()

vertex(vx, vy);

vxは角度とコサインから割り出した頂点の横位置、vyは角度とサインかr割り出した頂点の縦位置

f:id:idr_zz:20200413064010p:plain

※参考:三角関数で角度から座標を導くふたつの式の使い途 - Qiita

angleStep段階数を足していった角度angleからvxvyが都度計算されて、vertex()の引数に入る。

そうすると最初のbeginShape()TRIANGLE_FANモード=「三角ファン」の形になる!

f:id:idr_zz:20200421075433j:plain

この数値は確かに自分で手入力するのは困難で、for文で三角関数を繰り返し計算する方法はスマートに見える。(まだ自力でこの方法までたどり着けないなー…)

keyPressed()の中身

最後のkeyPressed()の中身を見ていく。前回までよりも内容が多い。

function keyPressed() {
  if (key == 's' || key == 'S') saveCanvas(gd.timestamp(), 'png');

  switch (key) {
  case '1':
    segmentCount = 360;
    break;
  case '2':
    segmentCount = 45;
    break;
  case '3':
    segmentCount = 24;
    break;
  case '4':
    segmentCount = 12;
    break;
  case '5':
    segmentCount = 6;
    break;
  }
  • keyPressed()メソッドを定義。引数は無し
  • もしsまたはSキーならcanvas要素をpng画像で保存
  • キーが1の場合segmentCount360
  • キーが2の場合segmentCount45
  • キーが3の場合segmentCount24
  • キーが4の場合segmentCount12
  • キーが5の場合segmentCount6

最初のif文のsまたはSから画像を保存するのは前回までと変わらない。

その下のswitch文でキーを15を押した時の設定がある。変数segmentCountの初期値が360だったがこの数字が変更される。

これによって1〜5を押すことで円を作っている三角形の段階数が変わる!

キー1(360段階)
f:id:idr_zz:20200422070623j:plain

キー4(12段階)
f:id:idr_zz:20200422070635j:plain

最後に(三角関数にまた遭遇)

今回、奇しくもですが少し前にthree.jsの時にみっちりやった三角関数にまた遭遇しました。三角関数がいろいろな場面で使われていることを実感しました。おかげで理解に助けにはなりましたが、同時に自分はまだパッと見で理解できるほどは三角関数が身についていないこともわかりました…。三角関数、これからも取り組んで行きたく思います。それではまた!


p5.jsを習得するためにやったことまとめ
qiita.com