ちょっと難しいかなと思っていたが、同時描画をやってみました。
カオスは微分方程式の解を描画するのはよくありますが、これはUnity内での運動をリアルタイムで取得して、描画するものです。
実験室内で本物を動画にとって、後で記述するのは比較的可能だけど、ある意味その代替になる手法として教育(高校生あたり)にも有効だと思います。
VRでも見えるので。。。
やったこと
(1)アイディア
(2)実際のコード
(3)動かしてみると。。ちょっと考察
(1)アイディア
①一番難易度高いかなと思ったのは、やはり軌跡を描かせるところ。
しかし、ググってみると、Unityでのグラフ描画ありました。やはりLineRendererで描くんですね。
【参考】
・unity > グラフ描画 > LineRendererで折れ線を引く
このコードは、最初に乱数でx、yを計算しまとめて描画する。
つまり、これを、リアルタイムに位置を取得して、描画するように変更すれば出来そう♪
②次に、リアルタイムに振り子の位置を取得する
これは、以前物体の運動やったので、あれと同じようにやればできそう。
つまり、以下の応用!
z = obj.transform.GetComponent<Rigidbody>().position.z;
③最後はやはり振り子
結局、一番ポピュラーな単独の二重振り子のカオスが見えるかやってみました。
(2)実際のコード
今回は振り子の説明は割愛します。前の記事を読めば想像できると思います。
ということで、まず肝心の描画部分は以下のとおり
この部分の完成版はおまけに置きました。
まずグローバル変数を定義
public class graph_renderer : MonoBehaviour
{
[SerializeField]
private GameObject obj;
float rx,ry;
int idx_max=10;
public List<Vector2> my2DPoint_xy = new List<Vector2>();
次が描画の関数です。
void DrawLine(List<Vector2> my2DVec, int startPos)
{
List<Vector3> myPoint = new List<Vector3>();
for (int idx = 0; idx < 2; idx++)
{
myPoint.Add(new Vector3(my2DVec[startPos + idx].x, my2DVec[startPos + idx].y, 0.0f));
}
GameObject newLine = new GameObject("Line");
LineRenderer lRend = newLine.AddComponent<LineRenderer>();
lRend.SetVertexCount(2);
lRend.SetWidth(0.1f, 0.1f);
Vector3 startVec = myPoint[0];
Vector3 endVec = myPoint[1];
lRend.SetPosition(0, startVec);
lRend.SetPosition(1, endVec);
}
以下が二段目の振り子の位置情報(x、y)を取得して描画させているコードです。
rigidbodyは二段目の振り子に張り付け、指定しています。
void Update()
{
rx = GetComponent<Rigidbody>().position.x;
ry = GetComponent<Rigidbody>().position.y;
idx_max += 1;
my2DPoint_xy.Add(new Vector2(1f * rx, 1f * ry));
int idx_min = (int)(0.9 * idx_max);
for (int idx = idx_min; idx < idx_max - 1; idx++)
{
DrawLine(my2DPoint_xy, /* startPos=*/idx);
}
}
}
あと、二段目の振り子の軌跡に合わせて球体が動くようにしました。
そのコードが以下のものです。
obj.transform.GetComponent<Rigidbody>().position = new Vector3(1f*rx, 1f*ry, 10f);
(3)動かしてみると。。ちょっと考察
【Unity】二重振り子のカオスな振舞をリアルタイム描画してみた♬
※画像をクリックするとYouTube動画につながります
画像の右が位相空間
(x、v_x)
の図で、振り子の真ん中あたりが描いているのがその位置の軌跡です。
(x、y)
コードの工夫という意味では、適当にListで描画しているけど、これがだんだん時間経過とともに重くなってくる。それは全部描画させていると大変なことになったので、90%は捨ててって、そりゃそこまでで描画しているので必要ない。全部捨てて一個だけ描こうとしたら失敗した。そこで以下の90%で手をうったという状況です。ちなみに、99%でやると出だしの部分が描画できないのでダメですが、95%程度だとまともな絵になるのでこの辺りが最適なのかもしれません。
int idx_min = (int)(0.9 * idx_max);
for (int idx = idx_min; idx < idx_max - 1; idx++)
{
DrawLine(my2DPoint_xy, /* startPos=*/idx);
}
また、二重振り子の第二段の振り子の軌跡を描画しましたが、同様に多段な振り子の振る舞いを追えるのでいろいろやってみると面白いと思います。
パラメーターも特に調整しなったので、調整するとさらに面白い絵が得られると思います。
おまけ
using UnityEngine;
using System.Collections;
using System.Collections.Generic; // for List<>
using UnityEngine.UI;
using System.IO;
public class graph_renderer : MonoBehaviour
{
[SerializeField]
private GameObject obj;
float rx,ry,vx,vy;
Vector2 my2Dpoint_rb;
int idx_max=10;
public List<Vector2> my2DPoint_xy = new List<Vector2>();
public List<Vector2> my2DPoint_xv = new List<Vector2>();
void DrawLine(List<Vector2> my2DVec, int startPos)
{
List<Vector3> myPoint = new List<Vector3>();
for (int idx = 0; idx < 2; idx++)
{
myPoint.Add(new Vector3(my2DVec[startPos + idx].x, my2DVec[startPos + idx].y, 0.0f));
}
GameObject newLine = new GameObject("Line");
LineRenderer lRend = newLine.AddComponent<LineRenderer>();
lRend.SetVertexCount(2);
lRend.SetWidth(0.1f, 0.1f);
Vector3 startVec = myPoint[0];
Vector3 endVec = myPoint[1];
lRend.SetPosition(0, startVec);
lRend.SetPosition(1, endVec);
}
void Start()
{
List<Vector2> my2DPoint = new List<Vector2>();
for (int idx = 0; idx < 10; idx++)
{
my2DPoint.Add(new Vector2(-15 + 0.2f * idx, Random.Range(0.0f, 5.0f)));
}
float x = 0f;
float y = 0f;
float z = 40f;
obj.transform.GetComponent<Rigidbody>().position = new Vector3(x, y, z);
}
void Update()
{
rx = GetComponent<Rigidbody>().position.x;
ry = GetComponent<Rigidbody>().position.y;
vx = GetComponent<Rigidbody>().velocity.x;
idx_max += 1;
obj.transform.GetComponent<Rigidbody>().position = new Vector3(1f*rx, 1f*ry, 10f);
my2DPoint_xv.Add(new Vector2(30+0.5f*rx, 0.25f*vx));
my2DPoint_xy.Add(new Vector2(1f * rx, 1f * ry));
int idx_min = (int)(0.9 * idx_max);
for (int idx = idx_min; idx < idx_max - 1; idx++)
{
DrawLine(my2DPoint_xv, /* startPos=*/idx);
DrawLine(my2DPoint_xy, /* startPos=*/idx);
}
}
}