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();
    }
}

f:id:hsmtta:20201018165238p:plain
上のスクリプトで描画される線分

もう少し複雑な例

線分を繰り返し描画して、コッホ曲線を作ります。

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();
    }
}

f:id:hsmtta:20201018165820p:plain
上のスクリプトで描画されるコッホ曲線

さらに複雑な例

次に、枝分かれが含まれる線を描画してみます。

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();
    }
}

f:id:hsmtta:20201018173249p:plain
上のスクリプトで描画される曲線

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()
    {
    }

f:id:hsmtta:20201017214746p:plain
上のスクリプトで描画される曲線

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()
    {
        
    }
}

f:id:hsmtta:20201016225827p:plain
上のスクリプトで生成された2D線分

参考文献

https://qiita.com/kwst/items/ad61e72562a8bcd9a9f7 ― kwstさんのQiitaの投稿

L-system(2日目)

L-systemで帰納的に作った文字列を図形として解釈してプロットします。解釈の方法をturtle interpretationと言います。

コッホ曲線

植物を作るときに使う記号は6種類あります:

  • F:線を描きながら単位長さ進む
  • f:線を描かずに単位長さ進む
  • +:亀の向きを+\delta回転させる
  • -:亀の向きを-\delta回転させる
  • [:亀の位置と向きをスタックにプッシュする
  • ]:亀の位置と向きをスタックからポップする

まずは、使っている記号が少ないものを試すため、置換規則「F → F+F--F+F」(\deltaは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:id:hsmtta:20201011173655p:plain
3回の繰り返しで生成されたコッホ曲線

植物らしい形

次に、ブラケットを使ってもっと植物らしい形を作ります。上のコードの中の文字列を線分に解釈する部分に、現在の状態をスタックに保存するコードと、スタックに保存した状態を呼び出すコードを追加します。置換規則は「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]

f:id:hsmtta:20201011182103p:plain
繰り返し3回で生成された植物らしい形

参考文献

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)