マス・スプリング・ダンパーのアニメーションをProcessingで作る

Processing

みなさん,こんにちは
おかしょです.

以前,マス・スプリング・ダンパーの運動方程式を導出しました.
さらに,その運動方程式を使って数値シミュレーションもやってみました.

この記事では,この数値シミュレーション結果を使ってアニメーションを作成したので,アニメーションをするプログラムを紹介します.

この記事を読むと以下のようなことがわかる・できるようになります.

  • Processingのアニメーションの作り方
  • マス・スプリング・ダンパーのアニメーション作成方法

 

この記事を読む前に

この記事では以下の記事で紹介している数値シミュレーションの結果を使用してアニメーションを作成しています.

以下の記事をまだ読んでいない方はそちらを先に読んでおくことをおすすめします.

 

アニメーション結果

まずは完成したアニメーションをご紹介します.

アニメーションの全体の画面はこのようになります.

左下にマス・スプリング・ダンパーのアニメーションが描かれています.

スプリングをProcessingで書くのは難しかったので,長方形を塗りつぶしたものを使用しています.

ダンパーは注射器のようなもので表現をしています.

画面左上には,状態量の数値を表示しています.

これによって,台車の位置や速度が目に見えます.
数値は時間の経過とともに更新されていくので,50Hzで更新されるようになっています.なので,目では追えないスピードで数値が更新されます.

右側には,各状態量の時系列グラフを表示しています.

上から,台車の速度・位置・入力の順で表示されています.

このグラフは左下のアニメーションと同じように変化するので,マス・スプリング・ダンパーの状態をアニメーションで感覚的に捉え,時系列グラフで数値的に捉えることができます.

 

アニメーションで使用するデータ

アニメーションを行うには,数値シミュレーション結果のデータが必要です.

今回使用したデータはcsvファイルを使用しました.

csvファイルとはカンマで区切られたデータのことで,Processingでcsvファイルを取り込む方法は以下の記事で解説しています.
Processingでcsvファイルを読み込む方法

アニメーションで使用するcsvファイルは,左から経過時間,台車の位置,速度,入力の順で格納されています.
実際に見た方がわかりやすいと思うので,使用しているデータを貼っておくので確認してみてください.

アニメーションを行うプログラム

それでは,アニメーションを行うプログラムを公開します.

String Data[];
String dataLine;
int L = 1;
String [] data;
float sample;
float dData1 = 0.5;  // グラフを描画する際sの縦軸の刻み幅の設定
float dData2 = 0.5;
float dData3 = 0.5;
float Time = 0;
float Velocity = 0;
float Position = 0;
float Input = 0;
float LastTime;
float MaxVelocity = 0;
float MaxPosition = 0;
float MaxInput = 0;

void setup()
{
  Data = loadStrings("data.csv");  // データ読み込み ファイル名 data.csvの読み込み
  background(0);  // 背景の色指定 黒に指定
  size(displayWidth,displayHeight);  // windowのサイズ指定 ディスプレイ全体に表示
  frameRate(50);  // 表示速度fps 50Hzで更新する
  // animation表示領域
  noStroke();  // 枠線なし
  fill(255);  // 白で塗りつぶし
  rect(width*0.05, height*0.37, width*0.43, height*0.53);  // 左上の点を指定 (左に5%,上部に37%余裕を空ける),幅(全体の43%),高さ(全体の53%) animationを表示する領域の全体を作成
  fill(100);  // 灰色で塗りつぶし
  rect(width*0.05, height*0.37, height*0.05, height*0.53);  // 壁作成 (左に5%,上部に37%余裕を空ける animation表示領域の左上隅に合わせる) 壁の幅を床の幅と合わせるために全体の高さの5%とする 壁の高さはaniation表示領域の下部に合わせる
  rect(width*0.05, height*0.85, width*0.43, height*0.05);  // 床作成
  // 時系列データ表示領域
  stroke(255);
  noFill();
  rect(width*0.58, height*0.09, width*0.37, height*0.24);  // グラフ1
  rect(width*0.58, height*0.38, width*0.37, height*0.24);  // グラフ2
  rect(width*0.58, height*0.67, width*0.37, height*0.24);  // グラフ3
  // シミュレーション終了時間を取得する
  float time = 0;
  float velocity = 0;
  float MinVelocity = 0;
  float position = 0;
  float MinPosition = 0;
  float input = 0;
  float MinInput = 0;
  for(L=1;L<Data.length;L++)
  {
    dataLine = Data[L];
    data = split(dataLine, ',');
    time = float(data[0]);
    velocity = float(data[2]);
    if(MaxVelocity<velocity)
    {
      MaxVelocity = velocity;
    }
    else if(MinVelocity>velocity)
    {
      MinVelocity = velocity;
    }
    position = float(data[1]);
    if(MaxPosition<position)
    {
      MaxPosition = position;
    }
    else if(MinPosition>position)
    {
      MinPosition = position;
    }
    input = float(data[3]);
    if(MaxInput<input)
    {
      MaxInput = input;
    }
    else if(MinInput>input)
    {
      MinInput = input;
    }
  }
  if(MaxVelocity<abs(MinVelocity))
  {
    MaxVelocity = abs(MinVelocity);
  }
  if(MaxPosition<abs(MinPosition))
  {
    MaxPosition = abs(MinPosition);
  }
  if(MaxInput<abs(MinInput))
  {
    MaxInput = abs(MinInput);
  }
  LastTime = time;
  L = 1;
  float dt = 0;
  if(LastTime<=10)
  {
    dt = 2;
  }
  else if(LastTime>10&&LastTime<=20)
  {
    dt = 5;
  }
  else if(LastTime>20&&LastTime<=50)
  {
    dt = 10;
  }
  else if(LastTime>50&&LastTime<=100)
  {
    dt = 20;
  }
  // 縦グリッドの描画
  stroke(100);
  for(int i =1;i<LastTime/dt;i++)
  {
    line(width*0.58+width*0.37*dt*i/LastTime, height*0.09, width*0.58+width*0.37*dt*i/LastTime, height*0.33);
    line(width*0.58+width*0.37*dt*i/LastTime, height*0.38, width*0.58+width*0.37*dt*i/LastTime, height*0.62);
    line(width*0.58+width*0.37*dt*i/LastTime, height*0.67, width*0.58+width*0.37*dt*i/LastTime, height*0.91);
  }
  fill(255);
  textSize(height*0.02);
  textAlign(CENTER);
  text("10", width*0.58+width*0.37*dt/LastTime, height*0.35);
  text("20", width*0.58+width*0.37*dt*2/LastTime, height*0.35);
  text("30", width*0.58+width*0.37*dt*3/LastTime, height*0.35);
  text("40", width*0.58+width*0.37*dt*4/LastTime, height*0.35);
  text("10", width*0.58+width*0.37*dt/LastTime, height*0.64);
  text("20", width*0.58+width*0.37*dt*2/LastTime, height*0.64);
  text("30", width*0.58+width*0.37*dt*3/LastTime, height*0.64);
  text("40", width*0.58+width*0.37*dt*4/LastTime, height*0.64);
  text("10", width*0.58+width*0.37*dt/LastTime, height*0.93);
  text("20", width*0.58+width*0.37*dt*2/LastTime, height*0.93);
  text("30", width*0.58+width*0.37*dt*3/LastTime, height*0.93);
  text("40", width*0.58+width*0.37*dt*4/LastTime, height*0.93);
  text("time [s]", width*0.765, height*0.96);
  // 横グリッドの描画
  stroke(100);
  for(int i =0;i<MaxVelocity/dData1;i++)
  {
    line(width*0.58, height*0.21-height*0.12*dData1*i/MaxVelocity, width*0.58+width*0.37, height*0.21-height*0.12*dData1*i/MaxVelocity);
    line(width*0.58, height*0.21+height*0.12*dData1*i/MaxVelocity, width*0.58+width*0.37, height*0.21+height*0.12*dData1*i/MaxVelocity);
  }
  fill(255);
  textSize(height*0.02);
  textAlign(CENTER);
  text("0", width*0.57, height*0.21);
  text("0.5", width*0.565, height*0.21-height*0.12*dData1*1/MaxVelocity);
  text("-0.5", width*0.56, height*0.21-height*0.12*dData1*(-1)/MaxVelocity);
  text("1.0", width*0.565, height*0.21-height*0.12*dData1*2/MaxVelocity);
  text("-1.0", width*0.56, height*0.21-height*0.12*dData1*(-2)/MaxVelocity);
  pushMatrix();
  rotate(radians(-90));
  text("velocity [m/s]", -height*0.21, width*0.54);
  popMatrix();
  for(int i =0;i<MaxPosition/dData2;i++)
  {
    line(width*0.58, height*0.50-height*0.12*dData2*i/MaxPosition, width*0.58+width*0.37, height*0.50-height*0.12*dData2*i/MaxPosition);
    line(width*0.58, height*0.50+height*0.12*dData2*i/MaxPosition, width*0.58+width*0.37, height*0.50+height*0.12*dData2*i/MaxPosition);
  }
  fill(255);
  textSize(height*0.02);
  textAlign(CENTER);
  text("0", width*0.57, height*0.50);
  text("0.5", width*0.565, height*0.50-height*0.12*dData2*1/MaxPosition);
  text("-0.5", width*0.56, height*0.50-height*0.12*dData2*(-1)/MaxPosition);
  text("1.0", width*0.565, height*0.50-height*0.12*dData2*2/MaxPosition);
  text("-1.0", width*0.56, height*0.50-height*0.12*dData2*(-2)/MaxPosition);
  pushMatrix();
  rotate(radians(-90));
  text("position [m]", -height*0.50, width*0.54);
  popMatrix();
  if(MaxInput==0)
  {
    MaxInput = 1;
  }
  for(int i =0;i<MaxInput/dData3;i++)
  {
    line(width*0.58, height*0.79-height*0.12*dData3*i/MaxInput, width*0.58+width*0.37, height*0.79-height*0.12*dData3*i/MaxInput);
    line(width*0.58, height*0.79+height*0.12*dData3*i/MaxInput, width*0.58+width*0.37, height*0.79+height*0.12*dData3*i/MaxInput);
  }
  fill(255);
  textSize(height*0.02);
  textAlign(CENTER);
  text("0", width*0.57, height*0.79);
  text("0.5", width*0.565, height*0.79-height*0.12*dData3*1/MaxVelocity);
  text("-0.5", width*0.56, height*0.79-height*0.12*dData3*(-1)/MaxVelocity);
  text("1.0", width*0.565, height*0.79-height*0.12*dData3*2/MaxVelocity);
  text("-1.0", width*0.56, height*0.79-height*0.12*dData3*(-2)/MaxVelocity);
  pushMatrix();
  rotate(radians(-90));
  text("input [N]", -height*0.79, width*0.54);
  popMatrix();

  // 状態表示領域
  fill(255);
  textSize(height*0.10);
  textAlign(LEFT);
  text('V', width*0.05, height*0.23);
  text('X', width*0.30, height*0.23);
  textSize(height*0.05);
  text("m/s", width*0.22, height*0.23);
  text('m', width*0.47, height*0.23);
}

void draw()
{
  // 前dataの格納
  float TimeP = Time;
  float VelocityP = Velocity;
  float PositionP = Position;
  float InputP = Input;
  // data格納
  dataLine = Data[L];
  data = split(dataLine, ',');
  Time = float(data[0]);
  Velocity = -float(data[2]);
  Position = -float(data[1]);
  Input = -float(data[3]);
  // データ表示
  textSize(height*0.05);
  fill(0);
  noStroke();
  rect(width*0.05+height*0.10-10, height*0.23, width*0.22-(width*0.05+height*0.10-10), -height*0.10);  // 文字を消す
  rect(width*0.30+height*0.10-10, height*0.23, width*0.47-(width*0.30+height*0.10-10), -height*0.10);
  fill(255);
  stroke(255);
  text(nf(Velocity, 2, 3), width*0.05+height*0.10-10, height*0.23);  // nf(float, int, int)でテキストに変換したい数値と整数部分の桁数,小数部分の桁数を指定
  text(nf(Position, 2, 3), width*0.30+height*0.10-10, height*0.23);
  // グラフ描画
  if(L>1)
  {
    stroke(0, 180, 255);
    line(width*0.58+width*0.37*TimeP/LastTime, height*0.21+height*0.12*VelocityP/MaxVelocity ,width*0.58+width*0.37*Time/LastTime, height*0.21+height*0.12*Velocity/MaxVelocity);
    stroke(0, 255, 180);
    line(width*0.58+width*0.37*TimeP/LastTime, height*0.50+height*0.12*PositionP/MaxPosition ,width*0.58+width*0.37*Time/LastTime, height*0.50+height*0.12*Position/MaxPosition);
    stroke(255, 180, 0);
    line(width*0.58+width*0.37*TimeP/LastTime, height*0.79+height*0.12*InputP/MaxInput ,width*0.58+width*0.37*Time/LastTime, height*0.79+height*0.12*Input/MaxInput);
  }
  // animation作成
  noStroke();
  fill(255);  // 白で塗りつぶし
  rect(width*0.05+height*0.05, height*0.37, width*0.43-height*0.05, height*0.48);
  pushMatrix();
  translate(-width*0.03*Position, 0);
  // 台車作成
  fill(100);
  rectMode(CENTER);
  rect(width*0.05+height*0.05+(width*0.43-height*0.05)/2, height*0.75, width*0.10, height*0.14);
  ellipse(width*0.05+height*0.05+(width*0.43-height*0.05)/2-width*0.03, height*0.835, height*0.03, height*0.03);
  ellipse(width*0.05+height*0.05+(width*0.43-height*0.05)/2+width*0.03, height*0.835, height*0.03, height*0.03);
  rectMode(CORNER);
  // dumper
  rect(height*0.05+(width*0.43-height*0.05)/2, height*0.78, -width*0.09, height*0.01);
  stroke(100);
  strokeWeight(5);
  line(height*0.05+(width*0.43-height*0.05)/2-width*0.09,height*0.775 ,height*0.05+(width*0.43-height*0.05)/2-width*0.09, height*0.795);
  //spring
  line(height*0.05+(width*0.43-height*0.05)/2,height*0.71 ,height*0.05+(width*0.43-height*0.05)/2-width*0.03, height*0.71);
  // iput arrow
  if(Input!=0)
  {
    stroke(0, 0, 255);
    fill(0, 0, 255);
    line(width*0.1+height*0.05+(width*0.43-height*0.05)/2,height*0.75 ,width*0.1+height*0.05+(width*0.43-height*0.05)/2+width*0.02*Input, height*0.75);
    triangle(width*0.11+height*0.05+(width*0.43-height*0.05)/2+width*0.02*Input, height*0.75, width*0.1+height*0.05+(width*0.43-height*0.05)/2+width*0.02*Input, height*0.76, width*0.1+height*0.05+(width*0.43-height*0.05)/2+width*0.02*Input, height*0.74);
  }
  popMatrix();
  // dumper作成
  noStroke();
  fill(100);
  rect(width*0.05+height*0.05, height*0.78, width*0.03, height*0.01);
  stroke(100);
  strokeWeight(3);
  line(width*0.08+height*0.05,height*0.77 ,width*0.08+height*0.05, height*0.8);
  line(width*0.08+height*0.05,height*0.77 ,width*0.08+height*0.05+width*0.06, height*0.77);
  line(width*0.08+height*0.05,height*0.8 ,width*0.08+height*0.05+width*0.06, height*0.8);
  // spring作成
  strokeWeight(5);
  line(width*0.05+height*0.05,height*0.71 ,width*0.08+height*0.05, height*0.71);
  fill(150);
  noStroke();
  rect(width*0.08+height*0.05, height*0.7, -width*0.11+(width*0.43-height*0.05)/2-width*0.03*Position, height*0.02);
  strokeWeight(1);  
  
  L++;  // dataの更新
  
  if(L==Data.length)  // dataが終了したらループを終了する
  {
    noLoop();
  }
}

このプログラムの解説は非常に大変なので,割愛させてもらいます.

各項目の説明は別の記事で書いています.

 

まとめ

この記事では,マス・スプリング・ダンパーのアニメーションを行うプログラムを紹介しました.

アニメーションの結果は,個人的には満足してはいません.

マス・スプリング・ダンパーのアニメーションがどうしてもかっこ悪いので,見栄えをよくするためには3D CADで作成する必要があると考えています.

 

続けて読む

この記事で使用している時系列グラフを描くプログラムは以下の記事で解説しています.時系列グラフの書き方を知りたい方は以下の記事を参考にしてください.

Twitterでは記事の更新情報や活動の進捗などをつぶやいているので気が向いたらフォローしてください.

それでは最後まで読んでいただきありがとうございました.

コメント

タイトルとURLをコピーしました