gotutiyan’s blog

競技プログラミングをやったりopenframeworksでお絵かきをしたりしています。

Processingで始める周期運動

こんにちは。KSWLのごつちやんです。
ここでは「三角関数を使って何かする」ことを「周期運動」と銘打って、processingを使ってやっていこうと思います。

processingのインストール

processingのインストールについては他にもたくさん記事があると思うので、そちらを参照してください。(以下のサイトなどが参考になるかも)
【Processing入門】インストールと使い方(Windows編) | アルゴリズム雑記
processing入門:Processing 2のダウンロードとインストール 梶山 喜一郎

processingの基本的なメソッド

以下に自分の記事があるので参考にしてください。この記事の中でも、「基本的な構造」「円と楕円」「三角関数」「ある範囲の変数を別の範囲に移す」の項を特に読んでおくと良いです。
processingとopenframeworksを比較してみる - gotutiyan’s blog

三角関数について

最初に周期運動を三角関数を使って表現するものと説明しました。一般に三角関数は以下のような形をしています。
f:id:gotutiyan:20190212141243p:plain
ここで見て欲しいのは、三角関数というグラフは、xがとる値を増やすに連れてその値は大きくなる、小さくなるを繰り返して動くものだということです。

でも、プログラムの中でこのxの値をどのように変えれば良いのかという問題があります。そこで、xを時間に設定するという考えのもと実装すれば、綺麗なものになります。

また、三角関数は-1~1の範囲しか取りません。この値をプログラムで利用すれば、たった1ピクセル単位でしか振動せず、変化が分かりません。このため、これから行う実例では、三角関数の値に100などの大きな数をかけることで、-100~100まで範囲を拡張して利用することになります。この点も重要なので、覚えておきましょう。

三角関数の利用

では、いくつかの例をもとにして、実際に三角関数を利用してみます。

円の直径に利用する

三角関数の値を円の直径に利用することで、円が周期的に大きくなったり小さくなったりします。
ここでは、xに時間としてframeCountを50で割ったものを利用しています。なぜ50で割るのかといったことは、こうすれば程よい速さで周期が回るので良いから、という説明をしておきます。
また、三角関数の値を動かしたい半径に対応させるため、[-1~1]の範囲を[50~450]に移します。

void setup(){
  size(500,500);
}

void draw(){
  background(255);
  float radian=frameCount/50.0;
  float r=map(sin(radian),-1,1,50,450);
  ellipse(width/2,height/2,r,r);
}
円の位置に利用する

円のx座標に三角関数を適用すれば、円が左右に動きます。
以下の例では、三角関数をx座標に利用し、先ほどの例と同様に[-1~1]の範囲を[50~450]に移します。

void setup(){
  size(500,500);
}

void draw(){
  background(255);
  float radian=frameCount/50.0;
  float x=map(sin(radian),-1,1,50,450);
  ellipse(x,height/2,50,50);
}
円の色に利用する

色をHSBで使用する時、Hの濃さを三角関数で操作すれば、虹色に変化します。
色は通常0~255で指定するので、[-1~1]の範囲を[0~255]に移します。
また、frameCount/100.0とすることで、frameCount/50.0よりも少し遅い変化を実現します。

void setup(){
  size(500,500);
}

void draw(){
  background(255);
  colorMode(HSB);
  float radian=frameCount/100.0;
  float c=map(sin(radian),-1,1,0,255);
  fill(c,255,255);
  ellipse(width/2,height/2,450,450);
}

もう少し三角関数を使う

円挙動

これまでの例では、三角関数は1つしか使われていませんでした。ここからはさらに使う量を増やして見ましょう。
x座標にcos、y座標にsinを利用してみると、円の挙動は円になります。(少し変な表現ですが笑)

今回もmap()を使っていますが、このような変換であれば200*sin(radian)と同じなので、より表記が簡単なこちらを利用することもできます。が、混乱を避けるため全てmapで行います)

void setup(){
  size(500,500);
}

void draw(){
  background(255);
  float radian=frameCount/50.0;
  float x=map(cos(radian),-1,1,-200,200);
  float y=map(sin(radian),-1,1,-200,200);
  ellipse(x+width/2,y+height/2,50,50);
}
リサージュ曲線

先ほどの円運動は、x=cos(radian)、y=sin(radian)でした。実はこの表現方法は中々応用が利いて、この式を変えるだけで全く違う顔を見せます。
今回はx=sin(3*radian)、y=sin(4*radian)としてみましょう。
4文字付け加えるだけの簡単な変更ですが、全然違う動きをします。どのような軌跡を描くのかを見たければ、background()をコメントアウトします。
void setup(){
size(500,500);
}

void draw(){
background(255);
float radian=frameCount/100.0;
float x=map(sin(3*radian),-1,1,-200,200);
float y=map(sin(4*radian),-1,1,-200,200);
ellipse(x+width/2,y+height/2,50,50);
}

ステロイド曲線

x=cos(radian)*cos(radian)*cos(radian)
y=sin(radian)*sin(radian)*sin(radian)
と変更すれば、また新しい顔を見せます。

void setup(){
  size(500,500);
}

void draw(){
  background(255);
  float radian=frameCount/50.0;
  float x=map(cos(radian)*cos(radian)*cos(radian),-1,1,-200,200);
  float y=map(sin(radian)*sin(radian)*sin(radian),-1,1,-200,200);
  ellipse(x+width/2,y+height/2,50,50);
}

円の数を増やす

さて、ここまで周期運動を1つの円を動かすことで見てきましたが、まだまだ本領発揮とはいきません。円の数を増やすことで、さらなる感動を得られます。
今からやることは、円をいくつも用意してその周期の速さを変える(radianの進む速さを変える)ことです。

このために、配列を1つ用意します。
radianの配列です。以下はリサージュ曲線を例に、円をたくさん描画するものです。

ずっと見ていると複数の円が重なる場面が出てくるところが最高ですね。
PI/512*(i+1)/50; という部分にはそれなりの経験からくるものがあるのですが、説明は割愛します。PIというのは円周率3.1415..ですね。

float radian[]=new float[50];
void setup(){
  size(500,500);
}

void draw(){
  background(255);
  for(int i=0;i<50;i++){
   radian[i]+=PI/512*(i+1)/50.0;
   float x=map(cos(3*radian[i]),-1,1,-200,200);
   float y=map(sin(4*radian[i]),-1,1,-200,200);
   ellipse(x+width/2,y+height/2,10,10);
  }
}

今までと同様に、ここでのx,yに代入する式を変えることで全く違う顔を見せます。
以下はアステロイド曲線です。

float radian[]=new float[50];
void setup(){
  size(500,500);
}

void draw(){
  background(255);
  for(int i=0;i<50;i++){
   radian[i]+=PI/512*(i+1)/20.0;
   float x=map(cos(radian[i])*cos(radian[i])*cos(radian[i]),-1,1,-200,200);
   float y=map(sin(radian[i])*sin(radian[i])*sin(radian[i]),-1,1,-200,200);
   ellipse(x+width/2,y+height/2,10,10);
  }
}

色をグラデーションさせる

今、円は全て白塗りになっていて、少し味気ないです。色を加えることでもう少し綺麗に見せることができます。
具体的には、今ある50個の円を全てグラデーションさせます。

float radian[]=new float[50];
void setup(){
  size(500,500);
  colorMode(HSB);
}

void draw(){
  background(255);
  for(int i=0;i<50;i++){
   radian[i]+=PI/512*(i+1)/20.0;
   float c=map(i,0,49,0,255);
   float x=map(sin(3*radian[i]),-1,1,-200,200);
   float y=map(sin(4*radian[i]),-1,1,-200,200);
   fill(c,255,255);
   ellipse(x+width/2,y+height/2,10,10);
  }
}