2016年10月

2016年10月31日

MCG_rayToSurfacePosition その13 3dsmax 2017

前回に引き続き「MCG_rayToSurfacePosition」コントローラを使った火花のシーンだ。

前回のままだとRayがオブジェクトに当っていない時もパーティクルが噴出してしまうのでこれを修正してみたい。

まずトラブルが起きやすいので「PFソース 001」という名前を半角で「PFS001」に変更する。一応スクリプト内では「$'PFソース 001'」でOKなんだけどうっかり保存する時にコードを間違えると動かなくなったりする(見た目にはわからないし)のでやめた方が安心だ。

次に「パーティクルビュー」を開いて「発生」オペレータの上に「発生スクリプト」をドラッグ&ドロップして置き換える。

fig01

そして「スクリプトを使用」ボタンをクリックして以下のコードを入力する。

on ChannelsUsed pCont do
(
	 pCont.useTime = true
	 pCont.useAge = true
)

on Proceed pCont do 
(
  cn = $Cone001
  ps = $PFS001
  perframe = 6
  pf = 160.0/perframe
	t1 = pCont.getTimeStart() as float
	t2 = pCont.getTimeEnd() as float
	if (t1 < 0) then (t1 = 0) 
	ss = ((at time (t1/160) cn.pos) !=  (at time (t1/160) ps.pos))
	es = ((at time (t2/160) cn.pos) !=  (at time (t2/160) ps.pos))

	if ((t2 < 100*160) and ss and es)  do -- 100 frames
	(
		for i in (t1/pf+1) to (t2/pf) do
		(
			curTime = pf*i as float
			pCont.AddParticle()
			pCont.particleIndex = pCont.NumParticles()
			pCont.particleTime = curTime/160
			pCont.particleAge = 0
		)
	)
)

このコードの「on Proceed pCont do」ハンドラはフレーム更新ごとに呼び出される。

上のコードでは引数の「cn」にはRayを発射する円錐オブジェクトのノードを、「ps」には「PFソース」のノードをセットしている。

「perframe」には1フレーム当りのパーティクル生成数をセットしている。

パーティクルを発生させたく無いのは「MCG_rayToSurfacePosition」がRayの先にターゲットを見つけられなかった時で、その時「PFソース」オブジェクトはRayを発射する円錐の位置に来る。このスクリプトでパーティクルを発生させてるのは「for〜」の部分のブロックなので2つのオブジェクトが一致している時にこの「for」文を実行させなければパーティクルは発生しないわけだ。

しかしパーティクルはフレームとフレームの間でも発生し続けるようにしてあるので、「PFソース」が円錐からターゲットに移動する時と戻ってくる時はフレーム間の開始時刻か終了時刻のどちらかの地点しか2つのオブジェクトの位置が一致しない。だから1つの時刻だけで一致するかどうかを判断すると、円錐とターゲットの間を移動するフレーム間で取りこぼしが出てしまい、円錐とターゲットの間の「PFソース」の移動した軌跡にパーティクルがばら撒かれる事になってしまう。

そこで開始時刻(t1)と終了時刻(t2)の2つの時刻での位置の比較をして、片方でも位置が一致する時はパーティクルを発生しないようにする事にした。「ss」は開始時刻、「es」は終了時刻で2つのオブジェクトの位置が一致しない時にTrueになる。「getTimeStart()」、「getTimeEnd()」で取得出来る時刻の単位はティックで、160ティックで1フレームなので「at time」に渡す前に160で割ってある。

ss = ((at time (t1/160) cn.pos) !=  (at time (t1/160) ps.pos))
es = ((at time (t2/160) cn.pos) !=  (at time (t2/160) ps.pos))

これと終了時刻が100フレーム(100X160ティック)未満の時に「if」文の条件がTrueになってパーティクルが発生するようになっている。

続きはまた次回。

maxまとめページ



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)3ds Max | CG

2016年10月28日

modo10のスクリプトについて調べてみた その27

前回は「Python API」を使ってUVマップの値を読むプログラムについて調べてみた。

今回は「Python API」をより使いやすくラッピングした「TD SDK」について調べてみたい。

「TD SDK」については「TD SDK ドキュメント」の「About」に以下のように紹介されている。

  • more pythonic and object oriented
  • Make common tasks easier & more intuitive.
  • Provide comprehensive documentation.
  • Hide the complexity of the core Python API.

最後の「Hide the complexity of the core Python API.」ってところがありがたいね。

そこでどれだけ複雑な「Python API」を隠してくれるのか、前回のプログラムと同じ結果を出力するプログラムを「TD SDK」で作ってみた。

import modo

mesh = modo.Mesh('Mesh')
uvm=mesh.geometry.vmaps['Texture'][0]
uvValues = []
polygon = mesh.geometry.polygons[0]
nVerts = polygon.numVertices
for eachVert in range(nVerts):
    uvValues.append(polygon.getUV(eachVert,uvm))
print(uvValues)

前回の「Python API」を使ったプログラムが悪夢だったみたいに簡単にUVマップの値が取得出来る。

「Mesh」オブジェクトは名前を引数にして呼び出せば見つけて生成してくれるし、ローカライズとか開放とかそんな事も考えなくていいようだ。

mesh = modo.Mesh('Mesh')

この「Mesh」オブジェクトは「Item」クラスのサブクラスなのでアイテムとして「選択されてるかどうか(selected)」とか「ペアレントしている親アイテム(parent)」とか聞けば教えてくれるし、ジオメトリについて聞きたければ「geometry」にアクセスすればいいって感じでとてもわかりやすい。

頂点マップも頂点にくっついているからジオメトリに聞けばいいわけで、

mesh.geometry.vmaps['Texture']

とやってやれば、引数「'Texture'」に名前がマッチする頂点マップを見つけ出してそれらを該当する「VertexMap」クラスを継承したクラスのオブジェクトに生成してリストで返してくれる。例えば下のようにあちこちに「Texture〜」って名前のマップがあった場合、

fig01

引数を「'Texture*'」にすると、下のようにマッチするマップが全て取得されてリストで返される。

print(modo.Mesh('Mesh').geometry.vmaps['Texture*'])
# Result: 
[UVMap('Texture'), UVMap('Texture (2)'), VertexMap('Texture')]

今回のプログラムでは「Texture」に完全にマッチするマップがUVマップ1つだけと言う条件で、実行しているので、得られたリストの0番目を「UVMap」オブジェクトとして取得している。

uvm=mesh.geometry.vmaps['Texture'][0]

同じような仕組みで「MeshPolygon」オブジェクトも「polygons」からポリゴンの番号で取得出来る。

polygon = mesh.geometry.polygons[0]

ポリゴンが取得できればその頂点もそのオブジェクトに聞けば教えてくれる。

nVerts = polygon.numVertices

そしてUVマップ値も頂点の番号とどのUVマップかをポリゴンオブジェクトに伝えて尋ねれば教えてくれる。それを配列に追加していけばポリゴン1枚分のUVマップ値が取得出来るわけだ。

polygon.getUV(eachVert,uvm)

そして「Python API」で必要だった「Apply()」とかもやる必要はない。

便利だなぁ。よっぽどの事が無ければ「Python API」まで降りて行ってプログラミングする必要は無さそうな感じだ。

続きはまた来週。

modo10ブログ目次



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)modo | CG

2016年10月27日

MCG_rayToSurfacePosition その12 3dsmax 2017

前回に引き続き「MCG_rayToSurfacePosition」コントローラを使ってみた。

下のようなシーン(ダウンロードページ)を用意して、左の円錐をRayの発射源として、円錐のZ軸が3つのターゲットに向くように下のように回転するアニメーションを付けた。

fig01

シーンにポイントを1つ追加して、ポイントの位置コントローラに「MCG_rayToSurfacePosition」をセットして、「Surfaces」にターゲットとなる球、BOX、ティーポットの3つのオブジェクトをセットし、「Ray Cast Object」として円錐をセットし、「Ray Axis」をZ方向にして「Flip」はOFFにし、「Use Surface Normal」をON、「Offset」を10.0にした。

fig02

これでタイムスライダを動かして円錐を回すと、ポイントがターゲットからちょっと浮いた位置を動くようになる。

fig03

次に「PFソース」をシーンの適当な位置に追加する。

fig04

fig05

そしてこの「PFソース」の位置コントローラにも「MCG_rayToSurfacePosition」を割り当てて下のようにPointとほぼ同じ設定をする。違っているのは「Offset」を0.0のままにする事だけだ。

fig06

回転コントローラに「ルックアットコンストレイント」を割り当てて、ターゲットをポイントにし、ルックアット軸をZにして「反転」をONにする。

fig07

これで「PFソース」がターゲット表面に沿って法線方向を向きながら動くようになった。

fig10

次に「ドラッグ」と「重力」をシーンの適当な位置に加える。

fig08

fig09

「ドラッグ」のパラメータは以下のように設定する。

fig18

できたら「PFソース」を選択して「修正」パネルから「パーティクルビュー」を開く。

fig01

「イベント001」に「削除」と「フォース」を加える。

fig12

「発生」を選択して以下のように設定を変更する。

fig13

「削除」

fig14

「位置」

fig15

「速度」

fig16

「フォース」はシーンに追加したスペースワープ2つを追加する。

fig17

これでとりあえずRayが当ったサーフェスからパーティクルが噴出すようになった。

続きはまた来週。

maxまとめページ



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)3ds Max | CG

2016年10月26日

modo10のスクリプトについて調べてみた その26

前回に引き続きPythonAPIでUVマップの値を読むプログラムだ。

import lx

layerService = lx.service.Layer()
layerScanObject = layerService.ScanAllocate(lx.symbol.f_LAYERSCAN_PRIMARY)
localizedMesh = layerScanObject.MeshBase(0)
 
storageBuffer = lx.object.storage('f',2)
polyAccessor = localizedMesh.PolygonAccessor()
mapAccessor = localizedMesh.MeshMapAccessor()
 
mapAccessor.SelectByName(lx.symbol.i_VMAP_TEXTUREUV,"Texture")
mapID = mapAccessor.ID()
 
uvValues = []
polyAccessor.SelectByIndex(0)
nVerts = polyAccessor.VertexCount()
for eachVert in range(nVerts):
    vertID = polyAccessor.VertexByIndex(eachVert)
    if polyAccessor.MapEvaluate(mapID, vertID, storageBuffer) == True:  
        values = storageBuffer.get()  
        uvValues.append(values)
layerScanObject.Apply()
print(uvValues)

前回までのところでローカライズされた「Mesh」オブジェクトが取得出来た。

このオブジェクトの頂点からUV値を取得する方法はポリゴンの「Accessor」オブジェクトが知っていて、

boolean = MapEvaluate(id map,id point,float[] value)

というメソッドだ。引数としてマップのID、頂点のID、データを受け取る「float」の配列を渡すようになっている。

そこでまずこのメソッドを使うために「PolygonAccessor」を取得しなくちゃならない。これはローカライズした「Mesh」オブジェクトから取得出来る。

polyAccessor = localizedMesh.PolygonAccessor()

UVマップ値を取得するのになんでポリゴンが必要かと言えば、UVマップがポリゴンごとに切り離されて不連続に出来るようにするためなわけで、ジオメトリでは1つの頂点でも、UVマップ上では分離して複数の頂点になっている事があるからだね。ところが上の「MapEvaluate()」の引数を見てもどのポリゴンのUV値を取得するかの指定がない。つまりそれはこのメソッドを持っている「polyAccessor」が知っていなくちゃならないって事だ。それを指定するのが以下のポリゴンアクセッサクラスのメソッドだ。

SelectByIndex(integer index)

このメソッドでポリゴンアクセッサにどのポリゴンを扱うかを番号で設定する。 今回のプログラムでは0番が設定されている。

polyAccessor.SelectByIndex(0)

次にどのUVマップを調べるかを決めるマップのIDの取得だ。これをやっているのが以下の部分だ。

mapAccessor = localizedMesh.MeshMapAccessor()
mapAccessor.SelectByName(lx.symbol.i_VMAP_TEXTUREUV,"Texture")
mapID = mapAccessor.ID()

これも、まず「Mesh」オブジェクトから「MapAccessor」オブジェクトを取得して、それにどのマップを扱わせるかを「SelectByName()」メソッド

SelectByName(integer type,string name)

で設定し、それからそのオブジェクトに扱っているマップのIDを聞くって形だ。マップの設定は、「MapAccessor」が扱える頂点マップがいろいろあるので、「type」で指定し、その名前で指定して選択する。ただその種類は番号で渡さなきゃならないけど、その番号は「lx.symbol」が知っているので、

lx.symbol.i_VMAP_TEXTUREUV

と書けばいいわけだ。ちなみに「lx.symbol」の「i_VMAP_〜」の部分は以下のように定義されている。UVマップは1415075158番だね。

fig01

次に頂点IDの取得だ。今回のプログラムでは0番のポリゴンに属する全ての頂点のUV値を取得しているので、その頂点のIDもポリゴンアクセッサから取得している。ここで指定する「index」はこのポリゴンを構成する頂点の0から始まる通し番号だ。

id point = VertexByIndex(integer index)

今回のプログラムでは0番のポリゴンを構成する全ての頂点についてUV値を求めるので、ポリゴンアクセッサからポリゴンの頂点数を「VertexCount()」で取得して、0〜頂点数−1番までの頂点IDを取得して「MapEvaluate()」に渡している。

nVerts = polyAccessor.VertexCount()
for eachVert in range(nVerts):
    vertID = polyAccessor.VertexByIndex(eachVert)

最後の「float[] value」は「SDK」ではCの配列をポインタで渡す形になっている。

fig02

そういうパターンをPythonAPIで扱うためのクラスが「lx.object.storage」クラスと言うわけだ。UVマップはU値とV値の2つのfloat値で構成されているから「'f'」「2」でそのぶんのメモリーが確保される。つまり

float value[2]
って事だ。

型はfloat,double,int,byte,unsigned,pointerが指定できて、その頭文字だけでもOKのようだ。

storageBuffer = lx.object.storage('f',2)

以上全てのパラメーを揃えたら「MapEvaluate()」でUV値が「storageBuffer」に取得出来て、 そこから「get()」でPythonのListに取り込んでいるわけだ。

そして最後に「Apply()」でローカライズして使っていた「Mesh」オブジェクトを開放して編集モードを抜けるわけだ。

layerScanObject.Apply()

以上のように「Python API」を使えば「modo SDK」をPythonに置き換えて記述できるようになるけど、あくまで「modo SDK」の置き換えなのでいろいろお約束的な事が多くて「SDK」の智識がかなり必要になる。この問題を解決するためにもうちょっとPythonでくるんで使いやすくしたのが「TD SDK」という事らしい。

それではまた次回。

modo10ブログ目次



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)modo | CG

2016年10月25日

MCG_rayToSurfacePosition その11 3dsmax 2017

「MCG_rayToSurfacePosition」コントローラがMCGでどう作られているかわかったので、ちょっと使ってみた。

下のシーンにはディスプレイスで凹凸をつけた平面と車輪、ポイントが入っている。ポイントはX軸方向に直進するアニメーションがつけてある。この車輪を「MCG_rayToSurfacePosition」コントローラを使ってティスプレイスで凹凸が付いた地面に沿って動かしてみたい。

fig01

まず車輪を選択して、「モーション」パネルで「コントローラを割り当て」ロールアウトをあけて、リストから「位置:XYZ位置」を選択する。

「コントローラを割り当て」ボタンを押して「MCG_rayToSurfacePosition」を選択して「OK」を押す。

fig02

「Ray to Surface Position」ロールアウトで「Pick Surface」ボタンを押して、シーン内の平面を選択する。

fig03

同様に「Ray Cast Object」グループから「Ray Cast Object」ボタンを押して、シーン内のポイントをクリックする。選択がうまく行けばボタンのラベルがクリックしたポイントの名前に変更される。

fig04

「Ray Axis」はZがデフォルト状態で上の画像のようになっていて、「Ray Cast Object」のZ軸マイナス方向にレイが飛ぶようになっているのでこの状態でポイントの下にある平面の表面に車輪の中心が移動する。

fig05

ここで車輪の中心を平面の表面から車輪の半径3だけ浮き上がらせるため、「Offset Object」グループの「Offset」に3を入力する。

fig06

これで車輪が平面の表面に来た。シーンを再生するとポイントの移動に連れて車輪が平面の凹凸に沿って移動するようになる。

fig07

あとは車輪を回転させなきゃならないので、車輪の「Y回転:ベジェ実数」を選択して、「コントローラを割り当て」ボタンを押して、「実数スクリプト」を割り当てる。

fig08

次に「スクリプトコントローラ」ダイアログで、「変数を作成」グループの「名前」に「me」と入力して「作成」ボタンを押し、変数を作成する。下の「変数」リストに「me」が追加されて選択された状態になっているので、「ノード割り当て」ボタンを押して、「トラックビュー選択」ダイアログから「Cylinder001」のノードをクリックして割り当てる。

fig09

最後に「式」に以下の通り入力する。

l = 0.0
p = at time 0.0 me.pos
for fm = 1 to F do (
 pd = at time fm me.pos
 l += length (pd - p)
 p = pd
)
l/3.0

これで車輪の移動距離を大体測ってそれを車輪の半径3で割って計算した車輪の回転角度がY軸回転として出力されるようになる。

fig10

それではまた次回。

maxまとめページ



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)3ds Max | CG
Archives