スタジオブロス TECH BLOG

スタジオブロススタッフからの最新ツール情報やチュートリアル・TIPSなどを紹介します

基礎から始める3dsmax/Python(MaxPlus)プログラミング⑦

今回はちょっと不思議なプログラムを作ってみます。 以下の短いものを実行してみます。

def factorial(i):
 if i>1:
  return i * factorial(i-1)
 else:
  return i
N = 3
print(factorial(N))

これはNの階乗を求めるプログラムです。N=3なので3x2x1=6という結果を出します。N=4なら4x3x2x1=24になります。

仕組みを説明します。 1行目で関数factorialを定義して、iを引数で受け取ります。 2行目で「if」で、iが1より大きいならば、「i * factorial(i-1)」の計算結果を返します。 4行目で「if」でiが1より小さい場合は、iの値を返します。

これでなぜ正しく計算できるのでしょう。 N=3なら、i * factorial(i-1)は、3factorial(3-1)=3factorial(2)となりますね? factorial(2)で関数の処理しますから、2factorial(2-1)=2factorial(1)です。 factorial(1)でまた関数の処理をしますが、「else:」に該当するので1を返します。 つまり、factorial(2)=2、factorial(1)=1です。 結果3x2x1=6となります。

ちょっと解りにくいのですが、これを「再帰(ネスト)」と呼びます。 関数が自分の中でグルグル回っている、という感じです。 これは同じ計算を繰り返す場合、複雑になるのを防ぐことができます。 「フラクタル図形」というのを聞いたり見たことがあると思いますが、それが再帰です。

www.gaia.h.kyoto-u.ac.jp

今回は3dsmaxでそれをやってみたいと思います。

import MaxPlus as mp
def createTeapot():
 obj = mp.Factory.CreateGeomObject(mp.ClassIds.Teapot)
 obj.ParameterBlock.Radius.Value = 5.0
 return mp.Factory.CreateNode(obj)

def treeOfSpheres(parent, width, xinc, depth, maxdepth):
 if depth == maxdepth:
  return
 for i in range(width):
  n = createTeapot()
  n.Parent = parent
  n.SetLocalPosition(mp.Point3(0, i * xinc, 15))
  treeOfSpheres(n, width, xinc * width, depth + 1, maxdepth)

mp.FileManager.Reset(True)
treeOfSpheres(createTeapot(), 3, 10, 0, 2)

これを実行すると、こんな感じです。

階層を見ると右列がNo.1,2,3です。この3つが最初にできます。

2段目が親になっています。

その下の階層に3段目が並んでいます。

プログラムでパーツの名前を設定ないので、生成した順に番号が付いています。 つまり、2段目の1つを作った後に3段目を3つ作ってるのを繰り返してます。

ちょっと良く解らないかもしれませんので、いろいろ実験してみましょう。 最後の行を変えて実行してみます。赤い文字が変更点です。

treeOfSpheres(createTeapot(), 3, 10, 0, 1)

2段で3個だけできました。2回だけ再帰したことになります。 また違う実験をしてみます。

treeOfSpheres(createTeapot(), 2, 10, 0, 2)

3段で2個づつ複製して再帰しています。最後にmaxdepthを増やしてみます。

treeOfSpheres(createTeapot(), 2, 10, 0, 4)

つまり、widthの値が1回分のコピー数でfor文で繰り返していて、depthとmaxdepthの差が高さの段数を決めています。 maxdepthはもっと大きくすると凄い数のティーポットが自動作成できます。

なぜこのような結果になるのでしょうか?不思議ですね・・・

では、説明していきます。1と16行目は毎度お馴染みなので省略します。 2~5行目もお馴染みのティーポットを作成して、大きさ5にするだけですので、これも省略です。

7行目で関数treeOfSpheresを定義しています。

treeOfSpheres(createTeapot(), 3, 10, 0, 2)

17行目で実行していて、この関数は引数が5つあります。 width,xinc,depth,maxdepth←,3, 10, 0, 2 と代入して関数を実行しています。 最初の引数のcreateTeapot()が特別で、これだけでTeapotが作成され、それがparentになり関数に行きます。これは原点に生成されます。

8行目でif depth == maxdepth:で2つの値を比較して同じなら何もしません。 今回は0と2なのでOKです。 10行目で、forでwidth=3なので3回繰り返します。 n = createTeapot()でティーポットを作成する関数をnに入れます。この命令だけでティーポットが生成されます。

n.Parent = parentで親子関係を作ります。 引数で、parent=createTeapot()なので、ティーポットを作成する度に最初のティーポットの子供になります。 n.SetLocalPosition(mp.Point3(0, i * xinc, 15)) これは前々回にやった、移動の命令を1行で設定してます。 X=0、Z=15で、Yはforでi * xincづつ増加します。今回は10です。ティポットが半径5なのでピッタリ並びます。

14行がtreeOfSpheres関数があり再帰して繰り返し処理をします。 treeOfSpheres(n, width, xinc * width, depth + 1, maxdepth) これでn=createTeapot()、widthはそのまま、Y方向は width倍で、depthは1増加で、maxdepthがそのままです。

つまり、2段目の3つのティーポットが一番下のティーポットの立場になり、子供を3つを3段目にそれぞれ生成します。よって、1→3→9個となります。 depthが1づつ増えるので、0→1→2で9行目のReturenで終了します。

再帰はちゃんとやめる仕組みを作らないと無限に終わらない、という問題を起こしますので注意が必要です。