はじめに

Homeデザイン > Design by Boxes

Design by Boxes(積層による造形)

補足

Pythonはオープンソースの(ソースコードが公開された)プログラミング言語です。インターネット上,あるいは,書籍に多くの情報があります。

参考ホームページ:

  • 公式サイト(英語):http://www.python.org/
  • 日本Pythonユーザ会(PyJUG):http://www.python.jp/

Pythonの文法についても,インターネット上,あるいは,書籍に多くの解説があります。以下のホームページに掲載された講義テキスト:「Python によるプログラミング入門(第4版)」がとてもわかりやすいと思います。

1. Design by Boxes(箱による造形)

ここでは,「ボックス(箱)を積む」というアルゴリズムを用いた建築形態のデザインに取り組みましょう。

図1 Design by Boxes

 

図1は,図2に示した,幅:奥行き:高さが1:2:1のボックスを積み上げてつくったボリュームから,図3に示すように,先に学んだ「Tower of Lights」で生成した窓を引き算した(削り取った)ものです。

図2 ヴォリューム

 

図3 窓

 

やや単純化しすぎる言い方になりますが,建築の形態は,全体のカタチとなるボリュームと,窓や階段などのエレメントといえると思います。先に学んだ「Twisting Tower」はボリュームの造形に関するスタディ,「Tower of Lights」はエレメントの造形に関するスタディだったと思います。

続いて,ここでは,全体のカタチとなるボリュームをエレメントの積層によってデザインするというハイブリッドな造形を考えてみましょう。その一つが「Design by Boxes(箱による造形)」です。

なお,「Design by Boxes」では,アルゴリズムを記述するスクリプトがやや複雑です(たいして複雑ではないとも言えるのですが…)。そこで,「Design by Boxes」のスクリプトについては詳細な解説を省略しています。その代わりに,ここでは,「1. Design by Boxes」に続いて,「4. Design by Sheres(球による造形)」を取り上げ,そのスクリプトについては,詳しく述べています。

2. 単純なアルゴリズム

単純なアルゴリズムを考えるために,ここではボックスをグリッド状に配置することを考えます(図4)。グリッドの単位形状が正方形である必要はないのですが,とりあえずは正方形で考えましょう。また,高さに変化を与えることも考えられるとは思いますが,とりあえずは高さの単位もグリッドの単位と揃えましょう。すなわち,ボックスの幅:奥行き:高さの比を1:2:1と仮定することにします。

図4 ボックスとグリッド

 

最初に,図5のように,グリッドの一層だけにボックスを数個,ランダムに配置することを考えましょう。ここで「ランダムに」というのは「位置と方向をランダムに」ということです。また,グリッドの範囲を指定し(図4のグリッドは12×12),「ボックスはグリッドからはみ出さない」ということもルールにしましょう。


図5 ボックスとグリッド

 

ボックスを配置(スクリプト1)

上記のアルゴリズムを記述するスクリプトは以下のようになります。このスクリプトでは,グリッド単位を3メートル立方(3000×3000×3000)とし,12×12のグリッドに4つのボックスを配置しています。

なお,18行目の「nz = 12」,23行目の「isFill2 = [[False for i in range(nx+2)] for j in range(ny+2)]」は,次項以降でボックスを積層させるためのコードです。平面へボックスを配置する段階では不要なコードです。

スクリプト 1(ダウンロード

  1. # Design by Boxes
  2. # 箱を積み上げる
  3. from random import randint
  4. import random
  5. scene = xshade.scene() #インスタンス
  6. ### パラメータ ###
  7. ox = 0   #原点(X座標)
  8. oy = 0   #原点(Y座標)
  9. oz = 0   #原点(Z座標)
  10. d = 3000   #ボックスの単位幅(幅:奥行=1:2)
  11. h = 3000   #ボックスの高さ
  12. nx = 12   #グリッドの幅
  13. ny = 12   #グリッドの奥行
  14. nz = 12   #グリッドの高さ
  15. nBox = 4   #最下層のボックス数
  16. ### 変数 ###
  17. isFill = [[False for i in range(nx+2)] for j in range(ny+2)]   #ボックス有無の記録(下層)(有:True,無:False)
  18. isFill2 = [[False for i in range(nx+2)] for j in range(ny+2)]   #ボックス有無の記録(上層)(有:True,無:False)
  19. maxLoop = 10000   #計算回数の上限
  20. nLoop = 0   #計算回数のカウント
  21. ### 関数 ###
  22. #createBox(ボックスを作成)(番号,基準点座標,グリッド位置,幅×高さ,方向)
  23. def createBox(n, x, y, z, ix, iy, d, h, t):
  24. → if t==0:
  25. → → x0 = x + (ix-1)*d; z0 = z + (iy-1)*d
  26. → → x1 = x + (ix+1)*d; z1 = z + (iy-1)*d
  27. → → x2 = x + (ix+1)*d; z2 = z + iy*d
  28. → → x3 = x + (ix-1)*d; z3 = z + iy*d
  29. → elif t==1:
  30. → → x0 = x + (ix-1)*d; z0 = z + (iy-1)*d
  31. → → x1 = x + ix*d; z1 = z + (iy-1)*d
  32. → → x2 = x + ix*d; z2 = z + (iy+1)*d
  33. → → x3 = x + (ix-1)*d; z3 = z + (iy+1)*d
  34. → elif t==2:
  35. → → x0 = x + (ix-2)*d; z0 = z + (iy-1)*d
  36. → → x1 = x + ix*d; z1 = z + (iy-1)*d
  37. → → x2 = x + ix*d; z2 = z + iy*d
  38. → → x3 = x + (ix-2)*d; z3 = z + iy*d
  39. → elif t==3:
  40. → → x0 = x + (ix-1)*d; z0 = z + (iy-2)*d
  41. → → x1 = x + ix*d; z1 = z + (iy-2)*d
  42. → → x2 = x + ix*d; z2 = z + iy*d
  43. → → x3 = x + (ix-1)*d; z3 = z + iy*d
  44. → scene.begin_creating()
  45. → scene.begin_line("Box"+str(n), 1)
  46. → scene.append_point([x0, y, z0], None, None, None, None)
  47. → scene.append_point([x1, y, z1], None, None, None, None)
  48. → scene.append_point([x2, y, z2], None, None, None, None)
  49. → scene.append_point([x3, y, z3], None, None, None, None)
  50. → scene.end_line()
  51. → scene.end_creating()
  52. → scene.solid_extrude([0, h, 0])
  53. → obj=scene.active_shape()
  54. → obj.has_surface_attributes = 1
  55. → obj.surface.has_diffuse = True
  56. → obj.surface.diffuse_color = (random.random(), random.random(), random.random())
  57. ### メイン ###
  58. n = 0
  59. #1層目
  60. while n<nBox:
  61. → nLoop = nLoop + 1
  62. → if nLoop > maxLoop:
  63. → → print("Loop Exceeded the Limit:", nLoop)
  64. → → break
  65. → ix = randint(1, nx)
  66. → iy = randint(1, ny)
  67. → t = randint(0, 3)
  68. → flag = True
  69. → if isFill[ix][iy]==True:
  70. → → flag = False
  71. → if t==0 and ix==nx:
  72. → → flag = False
  73. → if t==0 and isFill[ix+1][iy]==True:
  74. → → flag = False
  75. → if t==1 and iy==ny:
  76. → → flag = False
  77. → if t==1 and isFill[ix][iy+1]==True:
  78. → → flag = False
  79. → if t==2 and ix==1:
  80. → → flag = False
  81. → if t==2 and isFill[ix-1][iy]==True:
  82. → → flag = False
  83. → if t==3 and iy==1:
  84. → → flag = False
  85. → if t==3 and isFill[ix][iy-1]==True:
  86. → → flag = False
  87. →  if flag==True:
  88. → → n = n + 1
  89. → → isFill[ix][iy] = True
  90. → → if t==0:
  91. → → → isFill[ix+1][iy] = True
  92. → → if t==1:
  93. → → → isFill[ix][iy+1] = True
  94. → → if t==2:
  95. → → → isFill[ix-1][iy] = True
  96. → → if t==3:
  97. → → → isFill[ix][iy-1] = True
  98. → →  print(n, ox, oy, oz, ix, iy, d, h, t)
  99. → →  createBox(n, ox, oy, oz, ix, iy, d, h, t)
  100. → →  scene.update_figure_window()

Pythonの関数

スクリプト1では,27~64行目で関数「createBox」を定義しています。「def」を用いて関数を定義するのがPythonの文法です。

createBoxは「(n, x, y, z, ix, iy, d, h, t)」という引数(パラメーター)をもっています。これらのパラメーターの意味は以下の通りです。

  • n:ボックスの番号
  • x, y, z:グリッドの原点の座標
  • ix, iy:ボックスを配置するグリッドの位置
  • d:ボックスの幅
  • h:高さ
  • t:方向

関数はこれらの引数によって定義された手順を実行します。完成した関数はいわばブラックボックスのようなものです。関数を使用すると,プログラマーは関数を「どう使うか」ということだけを考えればよく,関数の「内容を覗く」作業から開放されます(というのは関数が完成していればの話ですが…)。プログラマーが常にプログラムの全体を把握するのは大変です。全体を細分化し,関数にできる部分は関数としてブラックボックス化すれば,眺めなければいけない全体をスケールダウンできます。それがプログラムの作法であるといえます。

方向

平面形が長方形(1:2)のボックスを配置するアルゴリズムではボックスの方向を考慮する必要があります。ボックスを配置する方向には以下の4パターンがあります。この4パターンのいずれかを指定するパラメーターが「t」です。

図6 ボックスの方向

 

ボックス配置のアルゴリズム

ボックスを配置するには,必要な数にいたるまで「配置する位置と方向を仮定し,仮定した位置に配置可能ならば配置する」という手順を繰り返せばいいと考えられますので,アルゴリズムは以下のように記述できます。

  1. 配置の位置と方向の仮定
    • 12×12のグリッド内における位置「ix,iy」を仮定(ixとiy:1~12)
    • ボックスの方向「t」を仮定(t:0~3)
  2. 上記「ix,iy,t」に基づくボックスに対して,以下をチェック
    • ボックスがグリッドからはみ出していないこと
    • すでに配置済みのボックスと重合しないこと

図7 ボックス配置のアルゴリズム

 

スクリプト1の67行目以降で上記のアルゴリズムを実行していますが,「すでに配置済みのボックス」をチェックするためにはボックスを配置を記録しなければなりません。そのための変数が「isFill」です。

「isFill」は二次元配列と呼ばれる変数です。すなわち,グリッドの数を定義する「nx,ny」に基づく「nx×ny」のマトリックスです。このマトリックス上にボックスがあれば「ON」,なければ「OFF」を記録すれば,「配置済みのボックス」の有無をチェックできることになります。

「isFill」は22行目で初期化をしています(isFill = [[False for i in range(nx+2)] for j in range(ny+2)])。この初期化の記述方法はPython独特のものでややわかりにくいのですが,これで初期化できます。初期化をすることで,「isFill[ix][iy](ix:1~nx,iy:1~ny)」というマトリックスが使用できるようになります。「ix=12,iy=12」ならば,12×12のマトリックスが設定されるわけです。なお,マトリックスの添字は[ ]で記述され,二次元配列の場合は[ ][ ]でマトリックスの要素を指定します。

「isFill[ ][ ]」は「有/無」を記録する変数です。「有/無」を表すには,たとえば「1/0」などが考えられますが,ここでは二値を表す変数「True/False」を使用しています。すなわち,ボックスが有れば「True」,なければ「False」ということです。

ボックスの位置と方向を指定して配置(スクリプト2)

さて,上記のアルゴリムを使用しないで,ボックスの位置と方向を指定して配置することを考えてみましょう。以下は「nx×ny」のマトリックスの4隅に4つのボックスを4つの方向に配置するスクリプトです。65行目を書き換えて実行してみてください。なお,73行目,77行目,81行目,85行目で「isFill[ ][ ]」にボックスの有無を記録しています。これは,後述の方法でボックスを積層させるためには「isFill[ ][ ]」による記録が必要だからです。

スクリプト 2(ダウンロード

  1. ### メイン ###
  2. n = 0
  3. #1層目
  4. n = n + 1
  5. ix = 1; iy = 1; t = 0; createBox(n, ox, oy, oz, ix, iy, d, h, t)
  6. isFill[ix][iy] = True; isFill[ix+1][iy] = True
  7. n = n + 1
  8. ix = nx; iy = 1; t = 1; createBox(n, ox, oy, oz, ix, iy, d, h, t)
  9. isFill[ix][iy] = True; isFill[ix][iy+1] = True
  10. n = n + 1
  11. ix = nx; iy = ny; t = 2; createBox(n, ox, oy, oz, ix, iy, d, h, t)
  12. isFill[ix][iy] = True; isFill[ix-1][iy] = True
  13. n = n + 1
  14. ix = 1; iy = ny; t = 3; createBox(n, ox, oy, oz, ix, iy, d, h, t)
  15. isFill[ix][iy] = True; isFill[ix][iy-1] = True
  16. print("Completed:", n)

スクリプト1および2を用いて,パラメーターを変化させながら,ボックスの位置と方向を指定して配置してみてください。

ボックスを配置しないアルゴリズム(スクリプト3)

ところで,以上とは逆に,ボックスをランダムに配置する場合に,指定した位置にボックスを配置しないようにすることを考えてみましょう。

スクリプト1では,ランダムにボックスが配置された後に「isFill[ ][ ]」にボックスの有無を記録し,し,すでにボックスが有る位置には新たにボックスを配置しないというアルゴリズムを記述していました。とすれば,意図的に「isFill[ ][ ]」を操作して,あらかじめ「isFill[ ][ ]」を「True」にしておけば,その位置にはボックスは配置されないことになります。ボックスの集合を建築と見なす時,階段や設備などのコアが決まった位置にあり,そこにボックスを置きたくない場合には,こんなアルゴリズムが使えると思います。

以下は,1層目にボックスを配置する際に,中央の「2×2」の部分にはボックスを配置しないスクリプトです。1層目にボックスを配置する67行目の前にこのスクリプトをおけば,たとえば「12×12」のグリッドの場合,「(6, 6),(7, 6),(7, 7),(6, 7)」の4つの位置にはボックスは配置されません。

スクリプト 3(ダインロード

  1. #コア(ボックスを配置しない部分)の設定
  2. if nx>2 and ny>2:
  3. → print(nx/2, nx/2+1, ny/2, ny/2+1)
  4. → isFill[nx/2][ny/2] = True
  5. → isFill[nx/2+1][ny/2] = True
  6. → isFill[nx/2+1][ny/2+1] = True
  7. → isFill[nx/2][ny/2+1] = True

なお,Pythonでは割り算をする場合には「整数で割った場合には答えは整数,実数で割った場合には答えは実数」となることに注意をする必要があります。たとえば「3/2=1」,「3/2.0=1.5」です。上記のスクリプトでは,nx(またはny)が奇数の場合でも,「nx/2」(または「ny/2」)は実数にはなりません。マトリックスであるisFill[ ][ ]の添字は整数でなければならないので,Pythonのこの仕様は便利です。

スクリプト1にスクリプト3を加え,指定した位置にボックスが配置されないことを確認してください。

3. ボックスの積層

次に,ボックスを積み上げることを考えます。ボックスは下にボックスがある場合にのみ積み上げることができるとしましょう。すなわち,ボックスを積層方法には下図の4つのパターンがあるとします。なお,積層されたボックスも,1層目のボックスと同様に以下の条件を満たさなければいけないとします。

  • ボックスがグリッドからはみ出していないこと
  • すでに配置済みのボックスと重合しないこと

図8 ボックスの積層

 

以上のアルゴリズムに基づき,ボックスを積層させるスクリプトは以下のように記述できます。以下のスクリプト4を,前述のスクリプト1に続ければ,nz層目までボックスが積み上がるはずです。

スクリプト4(ダウンロード

  1. #2層目以降
  2. for iz in range(2, nz+1):
  3. → for iy in range(1, ny+1):
  4. → → for ix in range(1, nx+1):
  5. → → → if isFill[ix][iy]==True:
  6. → → → t = randint(0, 3)
  7. → → → flag = True
  8. → → → if isFill2[ix][iy]==True:
  9. → → → → flag = False
  10. → → → → if t==0 and ix==nx:
  11. → → → → → flag = False
  12. → → → → if t==0 and isFill2[ix+1][iy]==True:
  13. → → → → → flag = False
  14. → → → → if t==1 and iy==ny:
  15. → → → → → flag = False
  16. → → → → if t==1 and isFill2[ix][iy+1]==True:
  17. → → → → → flag = False
  18. → → → → if t==2 and ix==1:
  19. → → → → → flag = False
  20. → → → → if t==2 and isFill2[ix-1][iy]==True:
  21. → → → → → flag = False
  22. → → → → if t==3 and iy==1:
  23. → → → → → flag = False
  24. → → → → if t==3 and isFill2[ix][iy-1]==True:
  25. → → → → → flag = False
  26. → → → →  if flag==True:
  27. → → → → → n = n + 1
  28. → → → → → isFill2[ix][iy] = True
  29. → → → → → if t==0:
  30. → → → → → → isFill2[ix+1][iy] = True
  31. → → → → → if t==1:
  32. → → → → → → isFill2[ix][iy+1] = True
  33. → → → → → if t==2:
  34. → → → → → → isFill2[ix-1][iy] = True
  35. → → → → → if t==3:
  36. → → → → → → isFill2[ix][iy-1] = True
  37. → → → → → print(n, ox, oy+(iz-1)*h, oz, ix, iy, d, h, t)
  38. → → → → → createBox(n, ox, oy+(iz-1)*h, oz, ix, iy, d, h, t)
  39. → isFill = isFill2
  40. → isFill2 = [[False for i in range(nx+2)] for j in range(ny+2)]

パラメーターを変化させながら,ボックスの積層による形態を生成してみてください。

 

集合住宅のデザイン

冒頭の図1のように,生成した積層するボックスに窓を空けると,形態にスケール感を加わって,集合住宅のように見せてくると思います。

でも,このままだと,外形に窓があるように見えるだけで,内部に部屋(室内空間)はありません。そこで,内部空間として部屋をモデリングした(ブーリアン演算で内部に空洞をつくった)のが図9です。

図9のスクリプトは以下よりダウンロードできます。なお,以下のスクリプトでは,外形「Box」の内部に少し小さな室内「Room」を生成しています。スクリプトではブーリアン演算を行っていないので,ブーリアン演算の定義は「ブラウザ」で行ってください。

ダウンロード

 

 

図9 断面図

 

フレームによる架構

もし「Design by Box」をフレーム構造(ラーメン構造)で架構するとすると,図10のようになるのかもしれません。この形態を生成したスクリプトは以下です。

ダウンロード

実際には,もしこの架構が鉄骨だったとすれば,ブレースが必要になるはずです(図11)。上記のスクリプトではブレースを省略しているので,残念ながら,手抜きです

図10 フレームによる架構

図11 フレームによる架構(ユニット)

 

4. Design by Spheres(球による造形)

さて,今度は,ボックス(箱)ではなく,球をモチーフとしたデザインに取り組んでみましょう(図12)。

図12 Design by Spheres


スクリプトの基本

ここで,Shade でスクリプトを使うための基本について,復習をしておきましょう。

スクリプトを使う時には,ウィンドウ[スクリプト]と ウィンドウ[メッセージ]を表示させましょう。

  • メニュー>表示>スクリプト>ON
  • メニュー>表示>メッセージ>ON

ウィンドウ「スクリプト」の「記録」をONにして,球を一つ作成してください。球を作成するスクリプトが以下のように記述されることがわかります。

スクリプト5

  1. xshade.scene().create_sphere(None, [3000.000000, 0.000000, -2500.000000], 4272.001953)

ここで,このスクリプトの文法は以下の通りです。

xshade.scene().create_sphere(名称, 中心座標, 半径)

名称は,「None」と記述すると,デフォルトの名称(この場合は「球」)が設定されます。

試しにスクリプトを以下のように修正して「実行」すると,座標(0,0,0)を中心とする「名称:Ball,半径:2000」の球が作成されます。

スクリプト6

  1. xshade.scene().create_sphere(“Ball”, [0, 0, 0], 2000)

球をグリッド状に配置

では,半径2メートルの球を縦・横・高さ方向の立体グリッド状に5×5×5=125個,5メートル間隔に,配置してみましょう(図13)。スクリプトは以下のようになります。

スクリプト7

  1. for x in range(5):
  2. → for y in range(5):
  3. → → for z in range(5):
  4. → → xshade.scene().create_sphere("Ball", [x*5000, y*5000, z*5000], 2000)

図13 グリッド状に配置

 

ここで,「for ~」は繰り返しの命令文です。「for (変数) in range(繰り返し回数)」が文法で,
変数は任意に設定できます。上記では,「x, y, z」の3つの変数が,それぞれ「0, 1, 2, 3, 4」と変化して行きます(「range(繰り返し回数)」は「0」から始まり,指定した整数より「1」小さい数値まで変化します)。

「for ~」文の次の行は,少なくとも1行は必ずインデント(行送り)されなければなりません。「for ~」文は,インデントされた行(1行以上)の繰り返しを定義するものだからです。

上記スクリプトでは,3つの「for ~」文がネスト(階層化)し,4行目の「xshade.scene().create_sphere(名称, 中心座標, 半径)」が5×5×5=125回,繰り返されます。1行目の「for ~」文で2~4行目が5回繰り返し,2行目の「for ~」文で3~4行目が5回繰り返し,3行目の「for ~」文で4行目が5回繰り返されるからです。

中心の座標は「0メートル,5メートル,10メートル,~」と変化していくわけですから,中心座標は[x*5000, y*5000, z*5000]で指定できます。

繰り返しの回数を多くすればより大量の球を生成できます。たった4行のスクリプトで大量のオブジェクトを配置できるのはコンピュータを用いたデザインの醍醐味だと思います。しかし,このままでは,いかにも均質に球が並んでいるだけです。均質さに変化を与え,躍動感のあるデザインを試みましょう。

躍動感を演出するもっとも単純な方法は乱数を使って,ランダムなゆらぎを与えることといえます。球の位置にゆらぎを与えてみましょう(図14)。

スクリプト8

  1. import random
  2. for x in range(5):
  3. → for y in range(5):
  4. → → for z in range(5):
  5. → → → xx = 5000 * x + 2000 * (random.random() - 0.5)
  6. → → → yy = 5000 * y + 2000 * (random.random() - 0.5)
  7. → → → zz = 5000 * z + 2000 * (random.random() - 0.5)
  8. → → → xshade.scene().create_sphere("Ball", [xx, yy, zz], 2000)

図14 グリッド状(位置にゆらぎ)

 

このスクリプトでは,5メートルの立体グリッドに対して,球の位置を±1000だけランダムに移動させています。

乱数を使うには,スクリプトの冒頭に(1行目)「import random」を記述します。この記述によって,0~1までの実数による乱数を発生する「random.random()」という数式が使えるようになります。「2000×(random.random()ー0.5)」という数式を書けば,2000に「-0.5~0.5」の実数を乗じた値,すなわち,「-1000~1000」の乱数を発生することができます。

6~8行目では,xx, yy, zzという変数を用いて球の中心座標を計算しています。6~8行目を省略して,9行目を以下のように書いてもいいのですが,9行目が長くなってしまうので,上記のスクリプトの方が見やすいと思います。

  1. xshade.scene().create_sphere("Ball", [5000 * x + 2000 * (random.random() - 0.5, 5000 * y
    + 2000 * (random.random() - 0.5, 5000 * z + 2000 * (random.random() - 0.5], 2000)

さらに,球の大きさ(半径)にもゆらぎを与えると図15のような図形が生成できます。以下のスクリプトでは,半径を「1000~3000」に変化させています。

スクリプト 9

  1. import random
  2. for x in range(5):
  3. → for y in range(5):
  4. → → for z in range(5):
  5. → → → xx = 5000 * x + 2000 * (random.random() - 0.5)
  6. → → → yy = 5000 * y + 2000 * (random.random() - 0.5)
  7. → → → zz = 5000 * z + 2000 * (random.random() - 0.5)
  8. → → → rr = 2000 + 2000 * (random.random() - 0.5)
  9. → → → xshade.scene().create_sphere("Ball", [xx, yy, zz], rr)

図15 グリッド状(位置と大きさにゆらぎ)

 

球をランダムに配置

次に,発想を変えて,球をグリッド状にではなく,完全にランダムに配置してみましょう(図16)。

20メートル立法の領域内に半径2メートルの球を50個,ランダムに配置するスクリプトは以下のように記述できます。

スクリプト10

  1. import random
  2. for n in range(50):
  3. → x = random.random() * 20000
  4. → y = random.random() * 20000
  5. → z = random.random() * 20000
  6. → xshade.scene().create_sphere("Ball", [x, y, z], 2000)

図16 ランダムに配置

 

上記スクリプトは以下のように書き換えることができます。

スクリプト11

  1. import random
  2. scene = xshade.scene()  #インスタンス
  3. d = 20000   #領域の幅
  4. h = 20000   #領域の奥行き
  5. w = 20000   #領域の高さ
  6. r = 2000    #半径
  7. nballs = 50   #球の数
  8. for n in range(nballs):
  9. → x = random.random() * d
  10. → y = random.random() * h
  11. → z = random.random() * w
  12. → scene.create_sphere("Ball", [x, y, z], r)

スクリプト10スクリプト11の内容はまったく同じですが,「領域の幅×奥行き×高さ」と「球の半径」と「球の個数」を変数で指定すると,スクリプトの可読性が高まり(見やすくなり),「領域,半径,個数」を後から変化させることが容易になります。

「#」はコメントを表します。すなわち,「#」を用いればスクリプトに自由にメモを記述できます。コンピュータは「#」以下を無視してくれるというわけです。

3行目には,「scene = xshade.scene()」という「インスタンス」(継承の意)を記述しています。最終行の「scene.create_sphere(名称,中心座標,半径)」に注目してください。スクリプト10では「xshade.scene().create_sphere(名称,中心座標,半径)」と記していたのですが,スクリプトの冒頭に「scene = xshade.scene()」と記すと,「xshade.scene()」を「scene」と省略することができるようになります。

次に,球にランダムに色を付けてみましょう(図17)。

スクリプト11に,以下のスクリプトの末尾の4行(16行目以降)を書き加えればOKです。

スクリプト12

  1. import random
  2. scene = xshade.scene()  #インスタンス
  3. d = 20000   #領域の幅
  4. h = 20000   #領域の奥行き
  5. w = 20000   #領域の高さ
  6. r = 2000    #半径
  7. nballs = 50   #球の数
  8. for n in range(nballs):
  9. → x = random.random() * d
  10. → y = random.random() * h
  11. → z = random.random() * w
  12. → scene.create_sphere("Ball", [x, y, z], r)
  13. → obj=scene.active_shape()
  14. → obj.has_surface_attributes = 1
  15. → obj.surface.has_diffuse = True
  16. → obj.surface.diffuse_color = (random.random(), random.random(), random.random())

図17 ランダムに配置(色を変化)

 

末尾の4行では,最初に作成した(作成直後の)オブジェクト(球)を選択し,次にそのオブジェクトに「attribute(表面材質)」と「diffuse(拡散反射)」を作成し,最後に「 diffuse_color(拡散反射の色)」をランダムに設定しています。色は「RGB(赤・緑・青)」の混合で表され,各RGBの値は「0~1」で表されるので,上記のように記述することができます。

球の大きさをランダムに

球の大きさ(半径)もランダムに変化させてみましょう(図18)。

たとえば,半径を2メートル±1メートルの間で変化させるには,以下のように記せばOKです。

スクリプト9

  1. import random
  2. scene = xshade.scene() #インスタンス
  3. d = 20000
  4. h = 20000
  5. w = 20000
  6. r0 = 2000
  7. nballs = 50
  8. for n in range(nballs):
  9. → x = random.random() * d
  10. → y = random.random() * h
  11. → z = random.random() * w
  12. → r = r0 + r0 * ( random.random() - 0.5)
  13. → scene.create_sphere("Ball", [x, y, z], r)
  14. → obj=scene.active_shape()
  15. → obj.has_surface_attributes = 1
  16. → obj.surface.has_diffuse = True
  17. → obj.surface.diffuse_color = (random.random(), random.random(), random.random())

図18 ランダムに配置(大きさを変化)

 

しかし,上記のスクリプトでは,小さな球が大きな球の内部に入り込んでしまうかもしれません。球をランダムに配置しつつ,しかし,球同士が重なり合わないようにするためにはどうすればいいでしょうか? 単純に数値をランダムに変化させることは簡単な数式で記述できるわけですが,条件を数式で表すのは単純とは限りません。「球同士の重なりを避ける」という条件を記述するスクリプトも,これまでのスクリプトと比較するとやや複雑になります。

球同士の重なりを避けるための一つの方法としては,「ある球を作成する時に,その球が他の球(作成済みの球)と重なっていないかどうかを判定し,重なっていない場合にだけOKとする」ということが考えられます。この方法を記述するためには,すべての作成済みの球の位置(中心座標)と半径を記録しておいて,ある球を作成する時にその記録を参照しなければならないことになります。

作成済みの球の位置と半径を記録するためには,「配列」という文法を使うことになります。以下が「配列」を用いて重ならないように球を配置するスクリプトです。

スクリプト10(ダウンロード

  1. # Design by Spheres
  2. # 立方体領域の中に球を配置
  3. import random
  4. scene = xshade.scene()   #インスタンス
  5. ### パラメータ ###
  6. w = 40000     #領域の幅
  7. d = 30000     #領域の奥行
  8. h = 20000     #領域の高さ
  9. rmin = 4000    #球の最小半径
  10. rr = 4000      #球の半径の増分(増分はランダムに変化)
  11. b = 1000      #球同士の重なりの許容値
  12. nballs = 40     #球の数
  13. maxloop=10000   #繰り返し計算の上限
  14. ### 初期値の設定 ###
  15. x = []
  16. y = []
  17. z = []
  18. r = []
  19. n = 0
  20. i = 0
  21. ### 最初の球 ###
  22. r0 = random.random()*rr + rmin
  23. x0 = random.random()*w
  24. y0 = random.random()*h
  25. z0 = random.random()*d
  26. x.append(x0)
  27. y.append(y0)
  28. z.append(z0)
  29. r.append(r0)
  30. scene.create_sphere(None, [x[n], y[n], z[n]], r[n])
  31. scene.update_figure_window()
  32. print(n, i)
  33. n = n + 1
  34. i = i + 1
  35. ### 2つ目以降の球 ###
  36. while n < nballs:
  37. → r0 = random.random()*rr + rmin
  38. → x0 = random.random()*w
  39. → y0 = random.random()*h
  40. → z0 = random.random()*d
  41. → flag = True
  42. → for m in range(n):
  43. → → if (x0-x[m])*(x0-x[m]) + (y0-y[m])*(y0-y[m]) + (z0-z[m])*(z0-z[m]) < (r0+r[m]-b)*(r0+r[m]-b):
  44. → → → flag = False
  45. → if flag == True:
  46. → → x.append(x0)
  47. → → y.append(y0)
  48. → → z.append(z0)
  49. → → r.append(r0)
  50. → → scene.create_sphere(None, [x[n], y[n], z[n]], r[n])
  51. → → scene.update_figure_window()
  52. → → print(n, i)
  53. → → n=n+1
  54. → i=i+1
  55. → if i>maxloop:
  56. → → break
  57. ### 球に着色 ###
  58. for i in range(2, n):
  59. → obj=xshade.scene().get_shape_by_ordinal(i)
  60. → obj.has_surface_attributes = 1
  61. → obj.surface.has_diffuse = True
  62. → obj.surface.diffuse_color = (random.random(), random.random(), random.random())

上記のスクリプトでは,「幅:40メートル,奥行:30メートル,高さ:20メートル」の範囲(球の中心が位置する範囲)に,「半径のゆらぎ:4メートル~8メートル」の球を,「個数:40個」配置しています。

球同士の重なりは,許容値を変数「b」で定義しています。上記スクリプトでは「b=1000」となっていますで,1メートル以内の重なりを許容します。もし「b=0」にすれば,重なりを許容しないことになります。

球が重なるかどうかの判定をしているのが52〜54行目です。54行目にある flag が球の重なりを判定する変数です。重ならない場合をTrue(真),重なる場合をFalse(偽)とし,51行目で球が重ならないと仮定して,「flag=True」としています。そして,52〜53行目で,「新たに作成する球:Sn」とすでに作成している「すべての球:Si(i=1~n-1)」の中心間の距離を計算し,その距離が「(Snの半径)+(Siの半径)ー(b)」より小さい時に「flag=False」としています。すなわち,新たに作成する球とすでに作成しているすべての球との距離が「(Snの半径)+(Siの半径)ー(b)」より大きい場合にだけ,「flag=True」となります。

以上のアルゴリズム(手順)によって,新たに作成する球の重なりを判定し,重ならない場合にだけ,56~61行目で球を作成しています。

ところで,このアルゴリズムには注意するべき点があります。このアルゴリズムは一定の空間を球で埋めていくものであるわけですが,空間に隙間がなくなってくると,新たに球を作成しようとしても配置する余地がなくなってくることになります。すると,何度判定を繰り返しても,新たな球が作成できないかもしれません。すなわち,「球の個数をn個」と指定しても,必ずn個の球が作成できるとは限らないわけです。

そこで,上記スクリプトでは,新たに球を作成する回数をカウントし,その回数に上限を設定しています。12行目の「maxloop=10000」がその上限回数です。上記スクリプトでは,45行目の「while n < nballs:」以降において,球の生成が「球の個数がn個」になるまで繰り返されますが,ただし,繰り返しの回数が「maxloop」を超えた場合は繰り返しを停止します。

繰り返し回数をカウントする変数は「i」です。25行目で変数「i」に初期値「0」を与え,42行目と66行目で,新たに球を作成しようとするごとに,「i=i+1」としてその回数をカウントしています。「i」が「maxloop」を超えると,68行目の「break」文によって,繰り返しが終了します。

図19は,スクリプト10によって生成した40個の球です。この40個の球を,ブーリアン演算により,「幅:40メートル,奥行:30メートル,高さ:20メートル」の領域で削り取ったのが図12です。


図19 ランダムに配置(重なりを判定)

 

面積計算

ここで,上記の形態が建築だと仮定して,その床面積を計算してみましょう。すなわち,個々の球を部屋だと考え,それぞれの部屋の面積をカウントし,最後にその合計値を計算してみましょう。なお,球状の部屋の面積は中心を含む円の面積であると仮定することにします(実際には球状の部屋はありえないかもしれないし,ありえたとしても中心を含む円がその部屋の床になるとは限らないわけですが,ここでは単純化して考えます)。また,球の重なりによる面積の増減も考慮しないことにします。

スクリプト10に以下の5行を加えてください。

  • 4.   import math
  • 26. amax = 3000000000  #面積の上限
  • 27. a = 0         #面積計算用変数
  • 36. a = a + math.pi*r0*r0  #面積の計算
  • 60. a = a + math.pi*r0*r0  #面積の計算

また,スクリプト10の44行目の「while n < nballs:」を以下のように書き換えてください。

  • 44. while a < amax:

上記のように変更したコードは以下よりダウンロードできます。

ダウンロード

 

4行目に加える「import math」は円周率=「pi」を使うのに必要となるおなじないです(「pi」という命令をつかわずに「3.14」などの数値を使うのならばこのおまじないは不要です)。

面積に上限値「areamax」を設定し,面積が上限を超えたら計算を打ち切るようにします。スクリプト10では,「areamax = 3000000000」,すなわち,面積の上限を3000㎡としています。

面積は,変数「a」によってカウントすることとし,27行目で初期値=「0」を与えています。そして,新たに円が生成された時に,「面積=半径×半径×円周率」を計算し,「a = a + math.pi*r1*r1」というコードで面積を加算していきます。

スクリプトの途中に「print(n, i, "Area=", a/1000/1000)」といったコードを追加すると,

最後に,69行目で「 print("Total Area (m2):", a/1000.0/1000.0)」といったコードを書いておくと,ミリ単位で計算される面積をメートル単位に変換した値が「メッセージ」ウィンドウに表示されます。

このように,数式を使った形態の生成には,乱数によってカタチにランダムなゆらぎを与えるだけではなく,重なりを判定したり,面積を計算したりすることが可能です。

美術館

図20は,半径が4000〜8000(直径が8〜16メートル)の球体を展示室に見立てた美術館のスケッチです。このままでは建築として機能するはずはないのですが,スケッチ(カタチのスタディ)は自由だと思います。

このスケッチは,平面:60×30m,高さ:8mの領域に,直径:8〜16メートルの球体を21個,配置した形態です。球体は200mmの厚さとし,内部を削り取っています。21個の球体の投影面積(中心を含む面の面積)の合計は2097.7㎡です。

このコードは以下よりダウンロードできます。

ダウンロード

 

図20 スケッチ(美術館)