LoginSignup
8
4

More than 5 years have passed since last update.

きのこの山を襲撃するたけのこの里(ScriptSOPを用いて)

Last updated at Posted at 2018-12-07

こんにちは、TouchDesigner Advent Calendar 8日目担当のなるみんです。

今年のアドベントカレンダーではScriptSOPBoidアルゴリズムを使って「きのこの山を襲撃するたけのこの里」を実装してみました。

▼デモ動画
takenoko.gif
参考にしたプログラムはこちらです。
サンプルプログラムはこちらです。

今回の記事ではScriptSOPの使い方を中心に紹介したかったので、実装に使用したオペレータ全ての解説はしていません。サンプルプログラムを見てみて下さい。

ScriptSOP

ScriptSOPは、cookされるたびにcallback関数のpythonスクリプトを実行します。ポイント、プリミティブ、その頂点を作成、削除、変更することができます。カスタム属性やuvやN(法線)などの組み込み属性を作成できます。
ScriptSOPのcallback関数にはデフォルトで以下の3つのメソッドが用意されています。

メソッド 機能
setupParameters() ScriptSOPのパラメータウィンドウの[Script]ページ->[Setup Parameters]をクリックすると実行されるメソッド。
onPulse() カスタムのパラメータが押された時に実行されるメソッド。
onCook() ScriptSOPがcookされた時に実行されるメソッド。

setupParameters(scriptOp)

ScriptSOPで扱うパラメータをカスタマイズするための関数です。

メソッド 機能
appendCustomPage(name) カスタムパラメータの新しいページを追加します。
appendXYZ(name, label=None) XYZ位置タイプのパラメータを作成します。 size = 3のfloatパラメータを作成するのと同様ですが、より適切なデフォルト命名が使用されます。
appendInt(name, label=None) Int型のパラメータを作成します。
appendFloat(name, label=None) Float型のパラメータを作成します。
appendPulse(name, label=None) パルスボタンタイプのパラメータを作成します。

カスタムページで扱える関数はまだまだたくさんあります!
詳しくはこちらを読んで下さい。

setupParameters(scriptOp)
def setupParameters(scriptOp):
    page = scriptOp.appendCustomPage('Boids')
    target_pos = page.appendXYZ('Targetpos', label='Target Position')
    cohesion_scale = page.appendInt('Cohesion', label='Cohesion Scale')
    cohesion_scale[0].normMin = 1
    cohesion_scale[0].normMax = 300
    cohesion_scale[0].default = 150
    cohesion_scale[0].min = 1
    cohesion_scale[0].clampMin = True

    separation_dist = page.appendFloat('Separation', label='Separation Distance')
    separation_dist[0].default = 0.2

    alignment_level = page.appendInt('Alignment', label='Alignment Level')
    alignment_level[0].normMin = 1
    alignment_level[0].normMax = 100
    alignment_level[0].default = 30
    alignment_level[0].min = 1
    alignment_level[0].clampMin = True

    target_scale = page.appendInt('Targetscale', label='Target Scale')
    target_scale[0].normMin = 1
    target_scale[0].normMax = 500
    target_scale[0].default = 200
    target_scale[0].min = 1
    target_scale[0].clampMin = True

    boundary_scale = page.appendFloat('Boundary', label='Boundary Scale')
    boundary_scale[0].default = 0.05

    boid_minimum_distance = page.appendFloat('Distance', label='Boid Minimum Distance')
    boid_minimum_distance[0].default = 0.8

    boid_speed_limit = page.appendFloat('Speed', label='Boid Speed Limit')
    boid_speed_limit[0].default = 0.5

    boundary_size = page.appendFloat('Boundarysize', label='Boundary Size')
    boundary_size[0].normMin = 1
    boundary_size[0].normMax = 100
    boundary_size[0].default = 10
    boundary_size[0].min = 1
    boundary_size[0].clampMin = True

    restart = page.appendPulse('Restart', label='Restart')
    reset_parameter = page.appendPulse('Resetparameter', label='Reset Parameter')
    return
onPulse(par)
    if (par.name == "Restart"):
        par.owner.clear()
    elif par.name == 'Resetparameter':
        par.owner.par.Cohesion = 150
        par.owner.par.Separation = 0.2
        par.owner.par.Alignment = 30
        par.owner.par.Targetscale = 200
        par.owner.par.Boundary = 0.05
        par.owner.par.Distance = 0.8
        par.owner.par.Speed = 0.5
        par.owner.par.Boundarysize = 10

Boidアルゴリズムのルール

ボイドアルゴリズムは Craig Reynolds によって提案された,群れを形成するためのアルゴリズムである.このアルゴリズムは“整列”,“結合”,“分離”という3 つの単純なルールを与えることによって,各ルールが個体間で相互作用することにより,群れを形成するアルゴリズムである.
引用元: 階層的ボイドアルゴリズムによる大規模な魚群のシミュレーション

ルール1「個体が群れの中心方向へ向かうように方向を変える」(結合)
ルール2「個体が他の個体とぶつからないように距離をとる」(分離)
ルール3「個体が他の個体と概ね同じ方向に飛ぶように合わせる」(整列)
これに、ルール4「TouchDesignerの描画領域から出ていかないようにする」と、ルール5「指定のターゲットに群れが向かっていく」を追加しています。

cook(scriptOp)

cook(scriptOp)
import numpy as np

def cook(scriptOp):
    #カスタムパラメータで取得した値を変数に入れて取り出す。
    cohesionScale = scriptOp.par.Cohesion.eval()
    separationScale = scriptOp.par.Separation.eval()
    alignmentScale = scriptOp.par.Alignment.eval()
    targetScale = scriptOp.par.Targetscale.eval()
    boundaryScale = scriptOp.par.Boundary.eval()
    boidMinDist = scriptOp.par.Distance.eval()
    speedLimit = scriptOp.par.Speed.eval()
    boundarySize = scriptOp.par.Boundarysize.eval()
    target = np.array([scriptOp.par.Targetposx.eval(),scriptOp.par.Targetposy.eval(),scriptOp.par.Targetposz.eval()])

    myPoints = scriptOp.points
    if not myPoints:
        scriptOp.copy(scriptOp.inputs[0])
        myPoints = scriptOp.points

    #からの配列にポイントと速度を格納する
    oldPoints = np.empty([len(myPoints),3])
    oldVelocity = np.empty([len(myPoints),3])

    count = 0
    for i in myPoints:
        oldPoints[count]=[i.x,i.y,i.z]
        oldVelocity[count]=[i.vel[0],i.vel[1],i.vel[2]]
        count += 1

    for j in range(len(myPoints)):

        #ルール1: 個体が群れの中心方向へ向かうように方向を変える
        a = np.delete(oldPoints,j,0)
        mean = a.mean(0)
        rule1 = (mean - oldPoints[j])/cohesionScale

        #ルール2: 個体が他の個体とぶつからないように距離をとる
        myPos = np.array(oldPoints[j])
        distance = (np.sqrt((oldPoints-myPos)**2)).sum(axis=1)
        np.place(distance,distance>boidMinDist,0)
        np.place(distance,distance>0,separationScale)
        a = np.delete(distance,j,0)
        b = np.delete(oldPoints,j,0)
        rule2 = -((b-myPos)*(a[:,np.newaxis])).sum(axis=0)

        #ルール3: 個体が他の個体と概ね同じ方向に飛ぶように合わせる
        a = np.delete(oldVelocity,j,0)
        mean = a.mean(0)
        rule3 = (mean - oldVelocity[j])/alignmentScale

        #ルール4: TouchDesignerの描画領域から出ていかないようにする
        limits = np.array([[boundarySize,boundarySize,boundarySize],[-boundarySize,-boundarySize,-boundarySize]])
        rule4 = np.array([0.0,0.0,0.0])
        if oldPoints[j,0] > limits[0,0]:
            rule4[0] = -boundaryScale
        elif oldPoints[j,0] < limits[1,0]:
            rule4[0] = boundaryScale
        if oldPoints[j,1] > limits[0,1]:
            rule4[1] = -boundaryScale
        elif oldPoints[j,1] < limits[1,1]:
            rule4[1] = boundaryScale
        if oldPoints[j,2] > limits[0,2]:
            rule4[2] = -boundaryScale
        elif oldPoints[j,2] < limits[1,2]:
            rule4[2] = boundaryScale

        #ルール5: 指定のターゲットに群れが向かっていく
        rule5 = (target - oldPoints[j])/targetScale

        #全てのルールをまとめる
        rules = oldVelocity[j]+rule1+rule2+rule3+rule4+rule5

        #限界の速度を定義する
        ruleslimit = np.linalg.norm(rules)
        if ruleslimit > speedLimit:
            rules = (rules / ruleslimit) * speedLimit

        #ポイントの位置と速度を更新する

        p = myPoints[j]
        p.P += rules
        p.vel = rules
        p.N = rules
    return

もし指摘点や不足点がありましたらコメントいただけますと嬉しいです。

今回このようなくだらないコンテンツのために消費されてしまった由緒正しきボイドアルゴリズム、きのこの山とたけのこの里のオブジェクトを作ってくださった上に、Qiitaでの共有を許可していただいた津久井さん、TouchDesigner Advent Cakender2018をオープンしてくれたけど一つも記事を書いていない小原くんへ大いなる感謝を込めて。メリークリスマス。

TDSW 2018年お世話になりました。

私と小原くん、他学生数名で毎月2回東京を中心にTouchDesigner Study WeekendというTouchDesignerのワークショップを開催しています。
私たちが講師を務める初級コース、中級コース、Arduinoやオーディオリアクティブコースをはじめ2018年は川村さん、松岡さん、高尾さん、比嘉さん、Benさん、喜々津さんなどたくさんの講師の方をお招きして充実したワークショップを実現させることができました。講師の皆さん、参加者の皆さん、会場提供していただいた企業の皆さん、本当にありがとうございました。
来年もまた楽しい企画たくさんご用意する意気込みでいますので2019年もよろしくお願いいたします。

過去開催記録はこちらからご覧いただけます。
チュートリアル動画はこちらから購入できます。
アドベントカレンダーを見ている人限定でチュートリアル動画の30%OFFクーポンをプレゼントさせていただきます!クーポンはこちら

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4