L-system(6日目)
ここまでで、L-systemをコーディングして、2次元の木っぽい形を作成できました。L-systemの要点は次の2つだと思います。
- ある離散的な時間が経過したときの発展を予測する
- 同じ要素なら同じルールに従ってパラレルに成長する
私は植物がなめらかに成長していくのを再現したいと思っているので、離散的な発展方程式では上手く行かなそうです。なので、連続的な発展を考えていく必要があります。2つ目の要点については、自然界では同じ要素でも発展のルールは違ってきそうです。たとえば、日照や養分量をパラメータに加える必要があります。
下のリンク先では、養分量を調整してシミュレーションを行っていて、連続的な発展をモデル化できているところが面白そうです。
【プロシージャル生成】植物生長シミュレーション | 株式会社ヘキサドライブ | HEXADRIVE | ゲーム制作を中心としたコンテンツクリエイト会社
L-system(5日目)
枝分かれした線を描くためにOpenGL APIをUnityから呼びます。LineRendererを複数回使って線を描く方法を考えましたが、1つのオブジェクトに含まれるLineRendererコンポーネントは1つだけに制限されているので、LineRendererを使おうとすると、オブジェクトをたくさん(枝分かれの分だけ)作成しなくてはならず煩雑になってしまいます。LineRendererのほうが、線の太さや色を変えるのはやりやすいんですが、ここではOpenGLを使うことにします。まずはOpenGLで線分を1本描画します。
スクリプトの中にMaterialを定義するとGUIに反映されるため、GUIでMaterialを割り当てます。OnRenderObjectメソッドはカメラがレンダリングを終了した後に行う処理を指定します。ここにOpenGLを使ったスクリプトを書きます。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Example2 : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } public Material lineMaterial; void OnRenderObject() { lineMaterial.SetPass(0); GL.Begin(GL.LINES); GL.Vertex3(0f, 0f, 0f); // Set the first vertex GL.Vertex3(1f, 1f, 0f); // Set the second vertex GL.End(); } }
もう少し複雑な例
線分を繰り返し描画して、コッホ曲線を作ります。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Example3 : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } public Material lineMaterial; void OnRenderObject() { // Make string representation of tree int n = 3; string s = "F"; for (int i = 0; i < n; i++) { string ss = s.Replace("F", "F+F--F+F"); s = ss; } // Interplate node positions from the string Vector3 p0 = new Vector3(-7.5f, 0f, 0f); float d = 5.0f / (float)Math.Pow(3f, (double)(n - 1)); double delta = 60.0 / 180.0 * Math.PI; double theta = 0.0 / 180.0 * Math.PI; lineMaterial.SetPass(0); GL.Begin(GL.LINES); for (int i = 0; i < s.Length; i++) { if (s.Substring(i, 1) == "F") { Vector3 p1 = p0 + d * new Vector3 ((float)Math.Cos(theta), (float)Math.Sin(theta), 0f); GL.Vertex(p0); // First line GL.Vertex(p1); p0 = p1; } else if (s.Substring(i, 1) == "-") { theta -= delta; // Rotate turtle -delta } else if (s.Substring(i, 1) == "+") { theta += delta; // Rotate turtle +delta } } GL.End(); } }
さらに複雑な例
次に、枝分かれが含まれる線を描画してみます。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Example4 : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } public Material lineMaterial; struct State { public double theta; public Vector3 p0; }; void OnRenderObject() { // Make string representation of tree int n = 3; string s = "F"; for (int i = 0; i < n; i++) { string ss = s.Replace("F", "F[+F]F[-F]F"); s = ss; } // Interplate node positions from the string Vector3 p0 = new Vector3(0f, -2f, 0f); float d = 2.0f / (float)Math.Pow(3f, (double)(n - 1)); double delta = 60.0 / 180.0 * Math.PI; double theta = 90.0 / 180.0 * Math.PI; lineMaterial.SetPass(0); GL.Begin(GL.LINES); Stack<State> stack = new Stack<State>(); for (int i = 0; i < s.Length; i++) { if (s.Substring(i, 1) == "F") { Vector3 p1 = p0 + d * new Vector3 ((float)Math.Cos(theta), (float)Math.Sin(theta), 0f); GL.Vertex(p0); // First line GL.Vertex(p1); p0 = p1; } else if (s.Substring(i, 1) == "-") { theta -= delta; // Rotate turtle -delta } else if (s.Substring(i, 1) == "+") { theta += delta; // Rotate turtle +delta } else if (s.Substring(i, 1) == "[") { State state; state.theta = theta; state.p0 = p0; stack.Push(state); } else if (s.Substring(i, 1) == "]") { State state = stack.Pop(); theta = state.theta; p0 = state.p0; } } GL.End(); } }
L-system(4日目)
前回はUnityで線分を描画しましたが、今回はL-systemで作成した曲線が描画できることを確認します。LineRendererコンポーネントは一筆書きできる線分にしか対応していないため、コッホ曲線を描画します。枝分かれした線の描画は次にやることにします。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Example : MonoBehaviour { // Start is called before the first frame update void Start() { // Make string representation of tree int n = 3; string s = "F"; for (int i = 0; i < n; i++) { string ss = s.Replace("F", "F+F--F+F"); s = ss; } LineRenderer renderer = gameObject.GetComponent<LineRenderer>(); // Number of nodes int nn = (int)Math.Pow(4.0, (double)n) + 1; renderer.positionCount = nn; // Interplate node positions from the string Vector3 p = new Vector3(-7.5f, 0f, 0f); float d = 5.0f / (float)Math.Pow(3f, (double)(n-1)); double delta = 60.0 / 180.0 * Math.PI; double theta = 0.0 / 180.0 * Math.PI; int count = 0; renderer.SetPosition(0, p); // First position for (int i = 0; i < s.Length; i++) { if (s.Substring(i,1) == "F") { count++; p = p + d * new Vector3 ((float)Math.Cos(theta), (float)Math.Sin(theta), 0f); renderer.SetPosition(count, p); } else if (s.Substring(i,1) == "-") { theta -= delta; // Rotate turtle -delta } else if (s.Substring(i,1) == "+") { theta += delta; // Rotate turtle +delta } } // Line width renderer.startWidth = 0.1f; renderer.endWidth = 0.1f; } // Update is called once per frame void Update() { }
L-system(3日目)
Unityを使って2日目までに作った植物っぽい線分を可視化します。まずはUnityで2Dの線分を書きます。
- 2Dで新規プロジェクトを作って Empty オブジェクト(ここではSampleTreeとする)を追加する
- SampleTreeの中に LineRenderer コンポーネント、C#スクリプト(ここではExample)を作る
- Exampleの中でLineRendererを呼び出して線分の情報をセットする
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Example : MonoBehaviour { // Start is called before the first frame update void Start() { LineRenderer renderer = gameObject.GetComponent<LineRenderer>(); // Line width renderer.SetWidth(0.1f, 0.1f); // Number of vertices renderer.SetVertexCount(2); // Set vertices renderer.SetPosition(0, Vector3.zero); renderer.SetPosition(1, new Vector3(0f, 3f, 0f)); } // Update is called once per frame void Update() { } }
参考文献
https://qiita.com/kwst/items/ad61e72562a8bcd9a9f7 ― kwstさんのQiitaの投稿
L-system(2日目)
L-systemで帰納的に作った文字列を図形として解釈してプロットします。解釈の方法をturtle interpretationと言います。
コッホ曲線
植物を作るときに使う記号は6種類あります:
- F:線を描きながら単位長さ進む
- f:線を描かずに単位長さ進む
- +:亀の向きを回転させる
- -:亀の向きを回転させる
- [:亀の位置と向きをスタックにプッシュする
- ]:亀の位置と向きをスタックからポップする
まずは、使っている記号が少ないものを試すため、置換規則「F → F+F--F+F」(は60°)を使います。これはコッホ曲線の置換規則で、置換前後でFの数が1つから4つになっているので、1つの線分が4つの線分になっていると分かります。また、使っている文字は、F+-だけなので一筆書きできる経路が生成されます。
from math import sin, cos, pi import matplotlib.pyplot as plt import matplotlib.cm as cm import matplotlib.collections as mc n = 3 s = 'F' for i in range(0,n): ss = s.translate(s.maketrans({'F' : 'F+F--F+F'})) s = ss print(s) # Make lines from string delta = 60 / 180 * pi # Angle for +/- symbol p0 = (0., 0.) # Initial point theta = 0 / 180 * pi # Initial direction of a turtle lines = [] for i in range(1,len(s)): if s[i] == 'F': p1 = tuple([x + y for (x, y) in zip(p0, (cos(theta), sin(theta)))]) line = [p0, p1] lines.append(line) p0 = p1 elif s[i] == '-': theta -= delta elif s[i] == '+': theta += delta print(lines) # Visualize the result colors = [cm.viridis(i/len(lines)) for i in range(len(lines))] lc = mc.LineCollection(lines, colors=colors) fig, ax = plt.subplots() ax.add_collection(lc) ax.autoscale() ax.set_aspect('equal') plt.show() print(s)
植物らしい形
次に、ブラケットを使ってもっと植物らしい形を作ります。上のコードの中の文字列を線分に解釈する部分に、現在の状態をスタックに保存するコードと、スタックに保存した状態を呼び出すコードを追加します。置換規則は「F → F[+F]F[-F]F」とします。
stack = [] for i in range(1,len(s)): if s[i] == 'F': p1 = tuple([x + y for (x, y) in zip(p0, (cos(theta), sin(theta)))]) line = [p0, p1] lines.append(line) p0 = p1 elif s[i] == '-': theta -= delta elif s[i] == '+': theta += delta elif s[i] == '[': state = (theta, p0) stack.append(state) elif s[i] == ']': state = stack.pop() theta = state[0] p0 = state[1]
参考文献
https://aidiary.hatenablog.com/entry/20131125/1385385271 ー aidiaryさんのブログ http://www.kk62526.server-shared.com/Lsys/index.html ー KK62526さんのホームページ https://repository.kulib.kyoto-u.ac.jp/dspace/bitstream/2433/95243/1/KJ00004760964.pdf ー 京大の発表レポジトリ
L-system
植物の生成シミュレーションをやろうと思います。L-systemというのが有名らしいので、とりあえずそれを実装してみることにします。
植物の形が文字列で表現されていて、文字列を置換することが、時間が経過して植物が成長することに対応しています。
n = 3 s = 'F' for i in range(0,n): ss = s.translate(s.maketrans({'F' : 'F[+F-F-F]F[--F+F+F]'})) s = ss print(s)