sine, cosine

1. 三角関数

さて、プログラムによって図形を描く場合に避けて通れないのがsine, cosine(三角関数)です。これは、プログラムで円などに代表される曲線を描く際によく使われます。
tangentはあまり使われないようですので、ここでは省略します(僕も殆ど使ったことがない)。

多少公式的なものが出てくるが故に、ここでつまづく人も多いかもしれません。
しかし、一度公式を覚えてしまえばそんなに難しいものではありません。
ここでは、なるべく丁寧にsine, cosineがどのように使われるのか解説していきたいと思います。

まずは円を描いてみましょう。通常のellipse(x, y, width, height)のように、現在のプログラミング言語は、sine, cosineを使わなくても円が描けてしまいますが、このsine, cosineは円だけでなく様々な曲線運動への応用が利きます。

円を描く際には、円の中心点からの角度が変化することによるx, yの座標を求める必要があります。以下の図を見て下さい。

sin_cos_01.gif

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

ここで、

1
2
x = R * cosθ
y = R * sinθ

という式が成り立ちます。
これは、覚えちゃった方がいいですね。

で、画面上で円が描かれる場合には中心点が任意の点になるでしょうから、図としては、以下のようになります。

sin_cos_02.gif

結果、式は

1
2
x1 =x + R * cosθ
y1 = y + R * sinθ

となります。

かなり分かりやすくグラフィカルに説明しているサイトがありました。これを見ると理解しやすいと思います。
http://www.procreo.jp/tutorial03.html

以下が、円運動のスクリプトになります。

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
int eSize = 10;    //円のサイズ
int x, y;  //中心点のx, y座標
float x1, y1;  //円上のx, y座標
float R = 100;  //軌跡を描く円の半径
int angle = 0;  //角度
 
void setup()
{
  //画面のサイズを決定
  size(400, 400);  
  smooth();
  noStroke();
  fill(0);
  x = width/2;  //中心点はウィンドウの1/2
  y = height/2;
}
 
void draw()
{
  background(255);
  //点のアニメーション
  float rad = radians(angle);  //radは角度(angle)をラディアン値に直したもの
  x1 = x + R*cos(rad);
  y1 = y + R*sin(rad);
  ellipse(x1, y1, eSize, eSize);  //x, yに点を描画
 
  angle ++;
  if(angle >= 360) angle = 0;  //もしangleが360以上になったら0にする。
}

ここで理解しておかなければならないのは、一般的な数学で習う三角関数とは違い、processingの場合はy軸の方向が上から下になっているということです。
coordinates.gif
ですから、上記のスクリプトを再生させると、初めは下の方向に向かって点が動きます。これはコードの間違いではなく、環境の違いによるものなのです。

2. 三角形の点を描く

上記の式が分かると、三角形の点の定義ができます。

ProcessingScreenSnapz002

以下の式を実行してみましょう。

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
int eSize = 10;    //円のサイズ
int cPoint;  //中心点
float radius = 100;  //軌跡を描く円の半径
 
//画面のサイズを決定
size(400, 400);  
smooth();
noFill();
cPoint = width/2;  //中心点はウィンドウの1/2
background(255);
 
ellipse(cPoint, cPoint, 200, 200);  //基準の円を描く
 
for(int i = 30; i < 360; i +=120){    //120度ずつ回転移動する
  float rad = radians(i);  //radは角度-countをラディアン値に直したもの
  ellipse(cPoint + radius*cos(rad), cPoint + radius*sin(rad), eSize, eSize);  //x, yに点を描画
 
}
 
//これは、三角形がどんどん肥大して数が増えてくるコード。//triangleという関数を使っている
//が、要は半径を増やしているにすぎない。
/*
for(int i = 10; i <= 200; i +=10){
  triangle(cPoint + cos(radians(30))*i, cPoint + sin(radians(30))*i, 
           cPoint + cos(radians(150))*i, cPoint + sin(radians(150))*i,  
           cPoint + cos(radians(270))*i, cPoint + sin(radians(270))*i);
}
*/

3. サイン波

それでは次に、サイン波を描いてみましょう。サイン波とは、以下のような波です。このような性質の波で一番代表的なものは「音」でしょう。現実の音は、色々なサイン波が合成されているものなのです。

sin

また、サイン波をプログラムで描けると様々な応用が可能になります。特に、今までの実習では直線運動のみだったのが、かなりの自由度で曲線運動が描けます。
ここで注意点が一つあります。上記のコードの中でradians( )という関数がありました。この関数は何をやっているかというと、私たちが慣れ親しんでいる度数法(360°など)の角度を、コンピュータが理解できるラジアンという単位に変換しているのです。
しかし、実はこんなに回りくどいことをしなくても、ラジアンで直接角度を指定することができます。

1ラジアンは度数でいうと約57.29578°になり、360°は6.283185307…..(2π)です。
ラジアンという単位では360°=2πと覚えましょう。
ラジアンがいったいどういった単位かという点についてはここでは詳しく述べません。もっと知りたい人はwikipediaを参照してください。

またもう一つ、サイン波をコードで書くときには表記が変わります。が、円を描くときと基本的に考え方は一緒です。


A = 振幅(上下の波の大きさ)
w = 角周波数(波の頻度。狭いと高い)
p = 初期位相(スタート地点)
t(θ)= 時間(角度)

下図にはp = 初期位相が書かれていませんが、波が始まる位置です。

プログラム上では、tは描画速度になるので、重要なパラメータはA, w, pになります。
以下の図は、それらの値を変えてみたものです。

では、ラジアンを使って実際にコードを書いてみましょう。

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;  //x, y座標
float A;  //振幅
float w;  //角周波数
float p;  //初期位相
float t;  //時間
float speed;    //アニメーションのスピード
float reSize = 35.0;  //画面の拡大率(数値を上げるとグラフック全体が大きくなる)
 
void setup()
{
  size(480, 300); 
  smooth();
  A = 1.0;    //振幅を設定
  w = 1.0;    //角周波数を設定
  p = 0.0;    //初期位相を設定
  t = 0.0;    //時間を初期化
  speed = 0.05;    //描画速度を設定
}
 
void draw()
{
  background(255);  
  noStroke();
 
  fill(0);
  //点のアニメーション
  y = A*sin(w*t - p);
  ellipse(t*reSize, -y*reSize+height/2, 10, 10);  //円を描く
  t += speed;    //時間を進める
  if(t*reSize > width) t = 0.0;    //点が右端まで行ったらになったら原点に戻る
}

また、この波の動きは円の拡大縮小に使うこともできます。
以下は、円が拡大縮小するサンプルです。
最大に近くなるときと、最小に近くなるときに、動きが緩やかになります。
通常の加算や減算によるプログラムと動きが違うことが分かると思います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
float eSize = 0.0;    //円のサイズ
int R = 200;  //円の半径
int angle = 0;  //角度
 
void setup(){
  size(400, 400);
  smooth();
}
 
void draw(){
  background(255);
  float rad = radians(angle);  //radは角度(angle)をラディアン値に直したもの
  //cod(radの値は1~-1なので、1を足すことによって、0~2にしている。
  eSize = R*(cos(rad)+1.0);
  ellipse(width/2, height/2, eSize, eSize);
 
  angle ++;
  if(angle >= 360) angle = 0;  //もしangleが360以上になったら0にする。
}

また、縦方向と横方向のスピードをかえることによって、描ける円運動のバリエーションはさらに広がります。
下のサンプルは八の字に運動するものです。

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
int eSize = 10;    //円のサイズ
int x, y;  //中心点のx, y座標
float x1, y1;  //円上のx, y座標
float R = 100;  //軌跡を描く円の半径
float angle1 = 0.0;  //横方向をコントロールする角度
float angle2 = 0.0;  //縦方向をコントロールする角度
 
void setup()
{
  //画面のサイズを決定
  size(400, 400);
  background(255);
  smooth();
  noStroke();
  fill(0);
  x = width/2;  //中心点はウィンドウの1/2
  y = height/2;
 
}
 
void draw()
{
  x1 = x + R*cos(radians(angle1));
  y1 = y + R*sin(radians(angle2));
  ellipse(x1, y1, eSize, eSize);  //x, yに点を描画
 
  angle1 += 1.0;    //angle2がangle1のスピードの2倍になったとき、動きが八の字になる。
  angle2 += 2.0;
 
  if(angle1 >= 360.0) angle1 = 0.0;  //もし360以上になったら0にする。
  if(angle2 >= 360.0) angle2 = 0.0;
}
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
int eSize = 10;
int x, y;
float x1, y1, x2, y2;
float R1 = 100.0;
float R2 = 40.0;
float degree = TWO_PI/360.0;
float rad = 0.0;
float s1 = 2.0;
float s2 = 5.0;
 
 
void setup(){
  size(400, 400);
  smooth();
  noStroke();
  fill(0);
 
  x = width/2;
  y = height/2;
 
}
 
void draw(){
  background(255);
 
  x1 = x + R1*cos(rad*s1);
  y1 = y + R1*sin(rad*s1);
 
  ellipse(x1, y1, eSize, eSize);
 
  x2 = x1 + R2*cos(rad*s2);
  y2 = y1 + R2*sin(rad*s2);
 
  ellipse(x2, y2, eSize, eSize);
 
  rad += degree;
  //if(rad >= TWO_PI) rad = 0.0;
}
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
int eSize = 2;  //円のサイズを指定
int cPoint;  //画面の中心点
float A1 = 120.0;  //A1, A2は上下の高さ
float A2 = 120.0;
float B1 = 30.0;
float B2 = 30.0;
float w1 = 1.0;  //w1, w2は幅
float w2 = 2.0;
float space = 0.01;  //カウンター
float count = 0.0;
float Scale = 100.0;  //基準となる高さ
float k = 50.0;
float[] x = new float[1000];
float[] y = new float[1000];
int num = 1000;
float speed;
 
void setup() {
  size(400, 400);  
  background(255); 
  cPoint =width/2;    ////中心点はウィンドウの1/2
  smooth();
  noStroke();
  fill(0);
  speed = PI/180.0;
}
 
void draw() {  
  //点のアニメーション
  background(255);
 
  for(int i = 0; i < num; i++){
 
    x[i] = (A1-B1)*cos(space*i + count) + k*cos(((A1-B1)/B1)*w1*count+((A1-B1)/B1)*w1*space*i);
    y[i] = (A2-B2)*sin(space*i + count) - k*sin(((A2-B2)/B2)*w2*count+((A2-B2)/B2)*w2*space*i);
 
 
    stroke(40, 40, 40, (float)(num-i)/(float)num*255.0);   
    line(cPoint + x[i], cPoint + y[i]-30*cos((float)i*0.01), (cPoint + x[i])+30*cos((float)i*0.01), (cPoint + y[i])+50*sin((float)i*0.05));  //2つのサイン波を足した値     
  }
  count += speed;
}