4.2 三角関数

さて、プログラムによって図形を描く場合に避けて通れないのが三角関数(サイン、コサイン)です。円などに代表される曲線を描く際によく使われます。タンジェントは使用頻度が低いので、ここでは省略します。

多少公式的なものが出てくるがゆえに苦手意識が生じる人もいるかもしれませんが、冷静にゆっくり実習してみましょう。公式は覚えなくても使う時に参考書を確認すればいいので、安心してください。
それでは、サイン(sin)、コサイン(cos)を使って曲線を描いてみましょう。

4.2.1 円運動(極座標)

まずは円を描いてみます。もちろんellipse(x, y, width, height)でも円が描けますが、このサイン、コサインは円だけでなく様々な曲線運動に使えます。

【図4.2-a】を見てください。円を描く際には、図のθ(シータ)の角度からx, yの座標を求める必要があります。

sin_cos_01.gif

図4.2-a

R –> 円の半径
x, y –> 円上の点
θ –> 角度

ここで、
x = R * cos(θ)
y = R * sin(θ)
という式になります。
【リスト4.2-a】が、円運動のサンプルになります。

2016_09_25_2_40

図4.2-b
リスト4.2-a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
float x, y;  //中心点のx, y座標
float R = 100;  //軌跡を描く円の半径
int angle = 0;  //角度
 
void setup() {
  size(400, 400);  
  noStroke();
  fill(0);
}
 
void draw() {
  background(255);
 
  //静止画の場合////////////////////////////////
  for (int i = 0; i < 360; i ++) {
 
    //thetaは角度(i)をラジアンの単位に変換したもの
    float theta = radians(i);
    x = R*cos(theta);
    y = -R*sin(theta);    //数学の座標とはy方向が反対になる
    //画面中央を中心にして円上に点を描画
    ellipse(x + width/2, y + height/2, 1, 1);
  }
 
  //アニメーションの場合//////////////////////////
  float theta = radians(angle);
  x = R*cos(theta);
  y = -R*sin(theta);
  ellipse(x + width/2, y + height/2, 10, 10);
 
  angle ++;
  //もしangleが360以上になったら0にする。
  if (angle > = 360) angle = 0;
}

数学における座標とは違い、processingの場合はy軸の方向が上から下になっている点に気をつけましょう。
この場合、円運動が0度から始まった時に上方向に移動させるため、y座標の値をマイナスにしています。

4.2.2 ラジアンを直接使う

【リスト4.2-a】のサンプルは、度数法から弧度法に変換するradians()を使いましたが、実は直接弧度法で書くこともできます。コードとしてはその方が簡潔です。まず、ラジアンを理解しておきましょう。
ここで、円周率(π)が出てきます。このπは、直径に対する円周の比ですね。3.14159265…です。
そして、360° = 2πラジアン(rad)になります。

度数法と弧度法の関係は次のとおりです【図4.2-c】。

%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab_000-2

図4.2-c
リスト4.2-b
度数法
90°
180°
270°
360°
弧度法
0π rad
0.5π rad
1π rad
1.5π rad
2π rad
数値
0
1.5707…
3.1415…
4.7123…
6.2831…

まずは、360° = 2π radだけでも覚えてください。
前述のサンプルを、ラジアンを直接使って書き直すと次のサンプルになります。4行目のTWO_PIは2πの事で、数値だと6.2831…です。

2016_09_26_2_31

図4.2-d
リスト4.2-c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
float x, y;  //中心点のx, y座標
float R = 100;  //軌跡を描く円の半径
float theta = 0.0;  //角度
 
//10度ずつ回転する様に、2πを36で割っている。
float rad = TWO_PI/36.0;
 
void setup() {
  size(400, 400); 
  noStroke();
  fill(0);  
}
 
void draw() {
  background(255);
 
  //静止画の場合////////////////////////////////
  for (float i = 0.0; i < TWO_PI; i += rad) {
 
    x = R*cos(i);
    y = -R*sin(i);    //数学の座標とはy方向が反対
    ellipse(x + width/2, y + height/2, 3, 3);
  }
 
  //アニメーションの場合//////////////////////////
  x = R*cos(theta);
  y = -R*sin(theta);
  ellipse(x + width/2, y + height/2, 10, 10);
 
  theta += rad;
 
  //もしthetaが2π以上になったら0.0にする。
  if (theta >= TWO_PI) theta = 0.0;
}

どうでしょうか。少しスッキリしました。
ここでひとつポイントがあります。6行目に注目してください。

1
2
//6行目。10度ずつ回転する様に、2πを36で割っている。
float rad = TWO_PI / 36.0;

rad = TWO_PI / 36.0は、1フレームごとに進む角度を計算しているものです。度数法だと360° / 36 = 10°です。

4.2.3 波としての三角関数

実は、曲線運動の記述にはもうひとつ代表的なアルゴリズムがあります。前述のx = R * cos(θ), y = R * sin(θ)と考え方は基本的に同じなのですが、波として記述する方法です。サイン波(正弦波)、コサイン波(余弦波)と呼ばれます。
それでは、まずサイン波を描いてみましょう。サイン波とは【図4.2-e】のような波です。このような性質の波で一番代表的なものは「音」でしょう。現実の音は、色々なサイン波が合成されていますが、分解するとこのような単純な波になります。

11_14_15__4_08_PM

図4.2-e

また、サイン波をコードで描くことができると様々な応用が可能になります。特に、直線運動のみに比べると飛躍的に自由度が高まります。サイン波をコードで書く際には少し記述方法が変わりますが、前述の円の描画と基本的に考え方は一緒です。
x座標は一定の速度で変化しますが、y座標は次の式によって求められます。

y = A*sin(w*t + p)

A = 振幅(上下の波の大きさ)
w = 角周波数(1秒当たりの周期数。数値が高いと狭くなる)
p = 初期位相(開始地点。+の値で左に移動、-の値で右に移動)
t = 経過時間

variable110414_ai____150___RGB_プレビュー_

図4.2-f

それでは、実際にコードを書いてみましょう。

図4.2-g
リスト4.2-d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
float x, y;  //x, y座標
float A;  //振幅
float w;  //角周波数(周期)
float p1;  //静止画用初期位相
float p2;  //動画用初期位相
float t2;  //アニメーション用経過時間(X座標)
 
void setup() {
  size(720, 400);
 
  A = 100.0;    //振幅を設定
  w = 0.02;    //角周波数を設定
  p1 = 0.0;    //初期位相を設定
  p2 = 0.0;    //初期位相を設定
  t2 = 0.0;    //経過時間を初期化
}
 
void draw() {
  background(255);
 
  noStroke();
  fill(0);
 
  //サイン波を点で静止画として描画///////////////////////////
  for (float t1 = 0; t1 < width; t1 += 2) {
    x = t1;
    y = -A*sin(w*t1 + p1);
    ellipse(x, y + height/2, 1, 1);
  }
 
  //点のアニメーションを描画////////////////////////////////  
  x = t2;
  y = -A*sin(w*t2 + p2);
  ellipse(x, y + height/2, 10, 10);
 
  t2 += 1.0;    //時間を進める
  if (t2 > width) t2 = 0;    //1周期分終わったら原点に戻る
}

A, w, p1, p2, t1, t2の数値を変えて、どのようにサイン波が変化するかを確認してみましょう。

また、この波の動きは円の拡大縮小に使うこともできます。【リスト4.2-e】は、円が拡大縮小するサンプルです。
円の直径が最大に近くなるときと、最小に近くなるときに、動きが緩やかになります。通常の加算や減算によるプログラムと動きが違うことが分かります。

11_12_15__01_43

図4.2-h
リスト4.2-e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
float x, y;  //x, y座標
float A;  //振幅
float w;  //角周波数
float p;  //初期位相
float t;  //経過時間
float eSize = 100;  //直線運動する円のサイズ(最大200)
 
float rad = TWO_PI/36.0;
int speed = 5;    //直線運動する円のスピード
 
void setup() {
  size(600, 300); 
 
  A = 100.0;    //振幅を設定
  w = 1.0;    //角周波数を設定
  p = 0.0;    //初期位相を設定
  t = 0.0;    //経過時間を初期化
}
 
void draw() {  
  background(255);  
 
  //sine, cosineを使った曲線的な拡大縮小///////////////////  
  //この場合、-A*sin(w*t - p)の計算結果は100.0~-100.0なので
  //100を足すことによって、0.0~200.0にしている。
  y = A*sin(w*t + p) + 100;
  ellipse(150, height/2, y, y);  //円を描く  
  t += rad;    //時間を進める
  if (t > TWO_PI) t = 0.0;
 
  //sine, cosineを使わない直線的な拡大縮小//////////////////
  eSize += speed;
  if ((eSize > 200) || (eSize < 0)) speed = -speed;
  ellipse(450, height/2, eSize, eSize);  //円を描く
}

4.2.4 様々な曲線

サイン波の他にも様々な曲線があります。奥の深い世界ですが、ここでは代表的な曲線を紹介します。

●八の字の曲線

次のサンプルは、点が八の字に描画されます。

2016_09_27_16_15

図4.2-i
リスト4.2-f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
float x, y;  //x, y座標
float A;  //振幅
float w;  //角周波数
float p;  //初期位相
float t;  //経過時間
 
float rad = TWO_PI/180.0;
 
void setup() {
  size(400, 400);
  background(255);  
  noStroke();
  fill(0);
 
  A = 100.0;  //振幅を設定
  w = 1.0;    //角周波数を設定
  p = 0.0;    //初期位相を設定
  t = 0.0;    //経過時間を初期化
}
 
void draw() {
  x = A*sin(w*t - p);
  y = -A*sin(w*t*2 - p);
  ellipse(x + width/2, y + height/2, 2, 2);  //円を描く
 
  t += rad;    //時間を進める
}

このような形で、A, w, pのそれぞれ数値を変えただけで、様々な運動のバリエーションを生み出すことができます。

●アステロイド

アステロイドは星のような形です。非常にシンプルな式です。しかし、w1とw2の値をそれぞれ変えてみると、思いもかけない形になります。

11_12_15__20_06

図4.2-j
リスト4.2-g
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
float x, y;  //x, y座標
float A;  //振幅
float w1, w2;  //角周波数
float p;  //初期位相
float t;  //経過時間
 
float rad = TWO_PI/180.0;
 
void setup() {
  size(400, 400);
  background(255);
  noStroke();
 
  A = 100.0;   //振幅を設定
  w1 = 1.0;    //角周波数。w1とw2の値が違うと大きく変形する
  w2 = 1.0;
  p = 0.0;     //初期位相を設定
  t = 0.0;     //経過時間を初期化
}
 
void draw() {  
  //アステロイド  
  x = A*pow(cos(w1*t), 3);
  y = A*pow(sin(w2*t), 3);
 
  fill(0);
  ellipse(x + width/2, y + height/2, 2, 2);  
 
  t += rad;    //時間を進める
}

●サイクロイド、トロコイド

サイクロイド (cycloid) は、円が回転するときの円上の定点の軌跡です。ここでは、同じ曲線のバリエーションであるトロコイドも作成してみます。

2016_09_27_2_05

図4.2-k
リスト4.2-h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
float x, y;  //x, y座標
float A1, A2;  //振幅
float w;  //角周波数(周期)
float t;  //アニメーション用経過時間(X座標)
 
float rad = TWO_PI/180.0;
 
void setup() {
  size(720, 400);
  background(255);
  noStroke();
  fill(0);
 
  A1 = 30.0;    //振幅を設定
  A2 = 60.0;
  w = 1.0;    //角周波数を設定
  t = 0.0;    //経過時間を初期化
}
 
void draw() {
  //点のアニメーションを描画///////////////////////
  //サイクロイド
  x = A1*(t - sin(w*t)); 
  y = -A1*(1 - cos(w*t));
 
  //トロコイド(A1 = A2の時にはサイクロイドになる)
  //x = A1*(t) - A2*sin(t);
  //y = -(A1 - A2*cos(t));
 
  ellipse(x, y + height/2, 3, 3);  //円を描く
 
  t += rad;    //時間を進める
 
  if (x > width) {
    x = 0.0;  //点が右端まで行ったらになったら原点に戻る
    t = 0.0;  //経過時間もリセット
  }
}

●内/外サイクロイド、内/外トロコイド

内/外サイクロイドは、大きい円上を小さい円が回っている時の円上の定点の軌跡です。内/外トロコイドは小さい円の半径に定点がない場合の軌跡になります。特にw1, w2の数値を変えると大きく図形が変化します。

内サイクロイド
11_12_15__19_43

図4.2-l

内トロコイド
11_12_15__19_44

図4.2-m

外サイクロイド
11_12_15__19_38

図4.2-n

外トロコイド
11_12_15__19_41

図4.2-o
リスト4.2-i
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
float x, y;
float A1, A2, A3;  //振幅
float w1, w2;      //角周波数(周期)
float t;           //経過時間
 
float rad = TWO_PI/180.0;
 
void setup() {
  size(400, 400);  
  background(255); 
  noStroke();
  fill(0);
 
  A1 = 100.0;
  A2 = 10.0;
  A3 = 20.0;
  w1 = 1.0;  
  w2 = 1.0;
  t = 0.0;
}
 
void draw() {
  //内サイクロイド
  x = (A1 - A2)*cos(w1*t) + A2*cos(((A1 - A2)/A2)*t);
  y = (A1 - A2)*sin(w2*t) - A2*sin(((A1 - A2)/A2)*t);
 
  //内トロコイド(A2 = A3の時には内サイクロイドになる) 
  //x = (A1 - A2)*cos(w1*t) + A3*cos(((A1 - A2)/A2)*t);
  //y = (A1 - A2)*sin(w2*t) - A3*sin(((A1 - A2)/A2)*t);  
 
  //外サイクロイド
  //x = (A1 + A2)*cos(w1*t) - A2*cos(((A1 + A2)/A2)*t);
  //y = (A1 + A2)*sin(w2*t) - A2*sin(((A1 + A2)/A2)*t);
 
  //外トロコイド(A2 = A3の時には外サイクロイドになる)
  //x = (A1 + A2)*cos(w1*t) - A3*cos(((A1 + A2)/A2)*t);
  //y = (A1 + A2)*sin(w2*t) - A3*sin(((A1 + A2)/A2)*t);
 
  ellipse(x + width/2, y + height/2, 2, 2);  
 
  t += rad;    //時間を進める
}

●螺旋

螺旋は非常にシンプルな式で求めることができます。

11_13_15__01_29

図4.2-p
リスト4.2-j
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
float x, y;
float A = 5.0;  //振幅
float R;  //回転半径
float k = 0.11;  //拡大率
float t = 0.0;  //経過時間
float rad = 0.05;    //スピード
 
void setup() {
  size(400, 400);  
  background(255); 
  noStroke();
 
  println(exp(3));
}
 
void draw() { 
  //等角螺旋、対数螺旋。exp()は指数関数
  R = A*exp(k*t);
 
  x = R*cos(t);       // X座標の計算
  y = R*sin(t);       // Y座標の計算
 
  fill(0, 0, 0);
  ellipse(x + width/2, y + height/2, 2, 2);  
 
  t += rad;    //時間を進める  
}

●リサジュー

最後にリサジュー図形です。特にw1, w2, p1, p2の数値を変えると大きく図形が変化します。

11_13_15__01_52

図4.2-q
リスト4.2-k
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
float x, y;
float A1 = 100.0;  //振幅
float A2 = 100.0;
float w1 = 2.0;  //各周波数
float w2 = 3.0;
float p1 = 0.0;  //位相差
float p2 = 0.0;
float t = 0.0;
float rad = 0.01;  //スピード
 
void setup() {
  size(400, 400);  
  background(255); 
  noStroke();
}
 
void draw() {  
  float x = A1*sin(w1*t + p1);  //螺旋
  float y = A2*sin(w2*t + p2);
 
  fill(0, 0, 0);
  ellipse(x + width/2, y + height/2, 2, 2);  
 
  t += rad;    //時間を進める
}

参考:外トロコイドのコードでA2などの数値を適当に変えたらかなり不思議な画像になりました。

2016_09_27_2_34

図4.2-r
リスト4.2-l
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
float x, y;
float A1 = 15.0;  //振幅
float A2 = 5.0;
float A3 = 1.0;
float t = 0.0;  //経過時間
float rad1 = 0.02;  //スピード
float rad2 = 0.002;
float scale =1.0;
 
void setup() {
  size(400, 400);  
  background(255); 
  noStroke();
}
 
void draw() {
 
  //リサジュー
  x = (A1 + A2)*cos(t) - A3*cos(((A1 + A2)/A2)*t);
  y = (A1 + A2)*sin(t) - A3*sin(((A1 + A2)/A2)*t);
 
  fill(0, 0, 0);
  ellipse(x*scale + width/2, y*scale + height/2, 1, 1);  
 
  t += rad1;    //時間を進める
  scale += 0.0025;  //徐々に拡大
 
  //A2が0未満になったら増減を反転させる
  A2 -= rad2;
  if(A2 < 0) rad2 = -rad2;
}

どうでしたか?数式で様々な曲線を描くことが、面白いと感じてもらえれば嬉しいです。ここに掲載しているのは代表的な曲線ですが、あまり定石にこだわらずに、好きな場所にA1, A2, w1, w2, p1, p2などを入れて自由に数値を入れてみましょう。w3やw4を作ってもいいかもしれません。あえて間違った使い方をした方が面白い結果になるかもしれませんよ。