2016年12月02日

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

前回に引き続き「TD SDK」の「Channel」クラスについて調べてみたい。

前回出てきたアイテムの「worldMatrix」チャンネルのマトリクスを使えば多重にペアレントしているアイテム上の座標系をいつでも簡単にワールド座標系に変換したり、ローカル座標系に変換したり出来る。

例えば下のようにアイテムがペアレントされた状態で、

fig02

「Cone」の288番の頂点が「Cube」のローカル座標系からどう見えるのかを知りたければ、頂点のローカル座標値を取得して、それを「Cone」の「worldMatrix」でワールド座標系に変換し、それを「Cube」の「worldMatrix」の逆変換行列で変換すればいい。

fig01

下のプログラムは上記の方法で「Cube」のローカル座標系での「Cone」の288番の頂点座標値を求めて、「Cube」の7番の頂点をその座標に動かすものだ。期待通りに動けば2つの頂点の位置が一致するはずだ。

import modo
cone = modo.Mesh('Cone')
cube = modo.Mesh('Cube')
cone_world = modo.Matrix4(cone.channel('worldMatrix').get())
cube_world = modo.Matrix4(cube.channel('worldMatrix').get())
coneP288Vec3 = modo.Vector3(cone.geometry.vertices[288].position)
coneP288Vec3.mulByMatrixAsPoint(cone_world * cube_world.inverted())
cubeP7 = cube.geometry.vertices[7]
cubeP7.position = coneP288Vec3
cube.geometry.setMeshEdits()

変換行列は変換の順に掛け合わせて行けばその変換を一挙にやってくれる変換行列が出来上がる。「Cone」のローカル座標からワールド座標に変換する変換マトリクスとワールド座標系から「Cube」のローカル座標系に変換する変換マトリクスはそれぞれ「cone_world」「cube_world.inverted()」だから、これを一気に行う変換マトリクスは以下のようになる。

cone_world * cube_world.inverted()

前回も出てきたように「Vector3」クラスには「Matrix4」を使って自らの座標値を変換する「mulByMatrixAsPoint()」メソッドがあるので、「Cone」の288番目の頂点のローカル座標値を「Vector3」オブジェクトに変換し、そのメソッドを使って上記変換マトリクスを適用している。

coneP288Vec3 = modo.Vector3(cone.geometry.vertices[288].position)
coneP288Vec3.mulByMatrixAsPoint(cone_world * cube_world.inverted())

そして得られた「Vector3」値を「Cube」の7番目の頂点に設定して、その変更を「setMeshEdit()」で適用している。

cubeP7 = cube.geometry.vertices[7]
cubeP7.position = coneP288Vec3
cube.geometry.setMeshEdits()

そしてこれが結果。頂点の位置が一致しているのがわかる。

fig03

続きはまた来週。

modo10ブログ目次



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

2016年12月01日

ビットマップペイントツールのチュートリアルをやってみた その4 3dsmax 2017

引き続きMAXScriptマニュアルに載っている「チュートリアル-ビットマップペイントツールを9つの簡単なステップで作成する」について見て行きたい。

「ステップ2」ではマウスのストロークが線で繋がるようにペイントのためのファンクションが改良された。

「ステップ3」は「ブラシのサイズとカラー」がテーマで、1ピクセルの太さの黒い線しか引けなかったブラシのサイズと色を変更出来るように修正される。

コードを見て行くと、まずユーザーインターフェースとしてロールアウトに新たに2つのコントロールが追加されて、色とブラシサイズが指定できるようになっている。

  colorpicker inkColor height:16 modal:false color:black across:2
  spinner BrushSize "Size"range:[1,50,10] type:#integer fieldwidth:40

fig01

カラーピッカーはクリックすると下の「カラーセレクタ」が表示されて、ここからカラーを選択出来るようになっている。「colorpicker」の定義をよく見ると「modal」オプションがfalseになっていて、この「カラーセレクタ」を出したままでもペイントが可能になっている。もちろん色の選択は反映される。「modal」がtrueの場合は「OK」を押してこのダイアログを閉じるまでペイントする事が出来なくなる。

fig02

さらに「colorpicker」の定義にはレイアウトパラメータの「across」に2が設定されている。これはこのコントロールを含む2つのコントロールを1行の中に配置する事を意味している。これが無ければ次の「spinner」は次の行に配置される事になる。上の「MicroPaint」ダイアログの画像を見ると、「colorpicker」と「spinner」が同じ行に並んでいるのが確認できる。また、これら2つのコントロールを配置するためにダイアログボックスのサイズを30だけ縦に広げている。

createDialog MicroPaint_CanvasRollout bitmapX (bitmapY+30)

「colorpicker」のオブジェクト名は「inkColor」で定義されていて、そこで設定した色はその「color」プロパティから得る事が出来るので、ビットマップに点を描く「aintBrush」ファンクションが下のように変更された。

fn paintBrush pos = (setPixels theCanvasBitmap pos #(inkColor.color))

ブラシの幅を設定する方は「spinner」コントロールが使われていて、「range」パラメータで、値の範囲が1〜50で初期値10に設定され、「type」パラメータで値の型が整数に設定されている。

spinner BrushSize "Size"range:[1,50,10] type:#integer fieldwidth:40

この値もオブジェクト名 「BrushSize」と「value」プロパティから取得できる。そしてこれを使って、今まで1ドットの点を打っていたところに線幅で指定したサイズの正方形を描くことで太い線を表現している。前回「ステップ2」のコードでは以下のようになっていたのを、

for i = 0 to maxSteps do
(
  paintBrush currentPos
  currentPos += [deltaStepX, deltaStepY]
) 
theCanvas.bitmap = theCanvasBitmap

今回のコードは以下のように置き換えられている。

for i = 0 to maxSteps do
(
   for b = -BrushSize.value/2 to BrushSize.value/2 do
     for c = -BrushSize.value/2 to BrushSize.value/2 do
       paintBrush (currentPos + [c,b])
   currentPos += [deltaStepX, deltaStepY]
) 
theCanvas.bitmap = theCanvasBitmap

2つの「for」文はブラシサイズの半分の値のマイナス値からプラス値までループしていて、これに前回点を打っていた位置「currentPos」を加えると、点を中心とした縦横「BrushSize.value」の幅の正方形の範囲の全ピクセル座標値になり、そこに「paintBrush」ファンクションで点を打つから、その正方形が塗りつぶされる。

fig04

前回の変更でストロークの点は繋がって描かれるようになったので、点のかわりに大きさ「BrushSize.value」の正方形を描いても太い線として繋がるわけだ。

以上がこのステップの変更点だ。

下は実行結果。

fig03

続きはまた次回。

maxまとめページ



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

2016年11月30日

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

前回に引き続き「TD SDK」の「Channel」クラスについて調べてみたい。

チャンネルタイプが「storage」の場合はチャンネルを「get()」で取得するとUnknownのCOMオブジェクトを返してくる。当然得体の知れないまま使う事は出来ないので、「TD SDK」ではストレージのタイプにあわせたラッパークラスを使ってオブジェクト化して使うようになっている。

マニュアルに書いてある例だと、「matrix4」タイプのものは「modo.Matrix4」クラスで扱うことが出来る。

import modo
mesh = modo.Mesh('Mesh')
matrixObject = mesh.channel('worldMatrix').get()
matrix = modo.Matrix4(matrixObject)
print matrix

例えばこんな状態の「Mesh」アイテムに対してこのプログラムを実行すると、

fig01

結果は下のように4X4の行列を扱うオブジェクトになる。

Matrix4([[0.843493268656316, 0.49240387650610395, -0.21461017714275646, 0.0], [-0.4184120444167326, 0.8528685319524432, 0.31232455601872633, 0.0], [0.33682408883346515, -0.1736481776669303, 0.9254165783983234, 0.0], [3.0, 2.0, 1.0, 1.0]])

「Matrix4」はコンストラクタの引数として他の「Matrix4」オブジェクトや、3次元座標値の「position」パラメータを与える事が出来る。

class Matrix4(Matrix3)
 |  Matrix class
 |  
 |  Method resolution order:
 |      Matrix4
 |      Matrix3
 |      __builtin__.object
 |  
 |  Methods defined here:
 |  
 |  __init__(self, other=None, position=None)
 |      Initializes a Matrix object
 |      
 |      :param other: Copies from other Matrix4 (optional)
 |      :type other: Matrix4
 |      :param position: Translation value to set (optional)
 |      :type position: iterable
 |      returns: Matrix4

最初の例ではチャンネルから得られたオブジェクトから「Matrix4」オブジェクトを生成したけど、下のようにいろいろなパターンで「Matrix4」オブジェクトを生成できる。

import modo
m1 = modo.Matrix4()
m2 = modo.Matrix4(((1,2,3,0),(4,5,6,0),(7,8,9,0),(0,0,0,1)))
m3 = modo.Matrix4(position=(1,2,3))
m4 = modo.Matrix4(((1,2,3,0),(4,5,6,0),(7,8,9,0),(0,0,0,1)),(1,2,3))
m5 = modo.Matrix4(m2,(1,2,3))

print m1
print m2
print m3
print m4
print m5

これが実行結果。

# Result: 
Matrix4([[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]])
Matrix4([[1, 2, 3, 0], [4, 5, 6, 0], [7, 8, 9, 0], [0, 0, 0, 1]])
Matrix4([[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [1, 2, 3, 1.0]])
Matrix4([[1, 2, 3, 0], [4, 5, 6, 0], [7, 8, 9, 0], [1, 2, 3, 1]])
Matrix4([[1, 2, 3, 0], [4, 5, 6, 0], [7, 8, 9, 0], [1, 2, 3, 1]])

この「Matrix4」の行列は3次元オブジェクトの「移動」「回転」「スケール」の変換を表現出来るって話は随分前にもやったね。これにローカルの座標値(x,y,z,1)をかけてやれば親座標系での座標値が求められたり、逆行列を使えば逆変換も出来るわけだ。「Vector3」と「Matrix4」の乗算は「Vector3」クラスの「mulByMatrixAsPoint()」メソッドで出来る。

mulByMatrixAsPoint(self, other)
    Transform this point's position by a Matrix4 in place.
    
    :param Matrix4 other: Other Matrix

例えば1mX1mX1mの立方体をこのように移動、回転、スケールして、

fig04

1つの頂点のローカル座標値(0.5,0.5,0.5)をこのアイテムの変換マトリクスで変換するプログラムは

fig02

以下のようになる。このプログラムではさらに変換マトリクスの逆行列を「inverted()」メソッドで得て、変換された座標値にかけてさらに変換してみている。

import modo

cube = modo.Mesh('Cube')
matrix = modo.Matrix4(cube.channel('worldMatrix').get())
p6 = modo.Vector3(0.5,0.5,0.5)
p6.mulByMatrixAsPoint(matrix)
print p6
invmatrix = matrix.inverted()
p6.mulByMatrixAsPoint(invmatrix)
print p6

これが実行結果。変換された座標値に逆変換のマトリクスをかけると元の座標値に戻る事が確認できる。

# Result: 
Vector3(1.338491, 2.375887, 4.027926)
Vector3(0.500000, 0.500000, 0.500000)

そしてこれがこのポイントのワールド座標値。変換された座標値と一致しているのがわかる。

fig03

続きはまた次回。

modo10ブログ目次



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

2016年11月29日

ビットマップペイントツールのチュートリアルをやってみた その3 3dsmax 2017

引き続きMAXScriptマニュアルに載っている「チュートリアル-ビットマップペイントツールを9つの簡単なステップで作成する」について見て行きたい。

「ステップ1」ではビットマップコントロールを1つ持つダイアログを作り、ダイアログのマウスイベントを使ってビットマップにドットを描き、それをビットマップコントロールに表示する「macroScript」を定義していた。

ただビットマップへのポイントのプロットはマウスの移動イベントが発生した時にマウスポインタがある場所に限られていたので、マウスが素早く動くとポイントの間隔が広がって点描になってしまう欠点があった。

fig01

そこで「ステップ2」ではこの点と点の間を線で繋ぐ仕組みを導入している。

具体的には2つの点の間のX軸方向距離、Y軸方向距離を求めて、それを2点間に描く点の数で割って、1点ごとのXY方向の移動距離を出し、それを加算しながら座標値を出して、そこに点を描いている。

2点の間に何個の点があれば点が繋がって線に見えるかは、2点間のX方向距離(ピクセル数)とY方向距離(ピクセル数)の大きい方を選ぶことで決める事が出来る。

fig02

まずダイアログの3つのマウスイベントハンドラから見て行くと、ボタンを押した時(lbuttondown)にその位置を線の始点にするために「lastPos」に現在のマウスカーソルの位置をコピーし、前回出てきた「isDrawing」をtrueにしてドラッグ中である事を記録し、線を描くファンクション(drawStroke)を呼び出している。

マウスの移動中(mousemove)はそれがドラッグ中なら線を描くファンクションを呼び出して、以前に記録した線の始点である「lastPos」から現在のマウスカーソルの位置「pos」まで線を引く。現在の位置を「lastPos」に記録して、次回線を描く時の始点としている。

  on MicroPaint_CanvasRollout lbuttondown pos do
  (
    lastPos = pos
    isDrawing = true
    drawStroke lastPos pos
  )
  on MicroPaint_CanvasRollout lbuttonup pos do isDrawing = false
  on MicroPaint_CanvasRollout mousemove pos do
  (
    if isDrawing do drawStroke lastPos pos
    lastPos = pos
  )

一方呼び出される方の「drawStroke」は以下のようになっている。

  fn drawStroke lastPos pos =
  (
    currentPos = lastPos
    deltaX = pos.x - lastPos.x
    deltaY = pos.y - lastPos.y
    maxSteps = amax #(abs(deltaX),abs(deltaY))
    deltaStepX = deltaX / maxSteps
    deltaStepY = deltaY / maxSteps
    for i = 0 to maxSteps do
    (
      paintBrush currentPos
      currentPos += [deltaStepX, deltaStepY]
    ) 
    theCanvas.bitmap = theCanvasBitmap
  )

まず線の始点の位置「lastPos」を「currentPos」にコピーして、これを最初に点を打つ位置にして、「deltaX」と「deltaY」に線の始点と終点のXY差分を代入している。ここでこの「deltaX」と「deltaY」を「currentPos」のX座標値とY座標値にそれぞれ足せば、posの座標値と等しくなるわけだ。

    currentPos = lastPos
    deltaX = pos.x - lastPos.x
    deltaY = pos.y - lastPos.y

次にこの差分の数値が大きい方を求める。差分は2点の位置関係によってプラスになることもマイナスになる事もあるので、「abs()」関数を使って絶対値に変換し、「#()」で配列にしてから「amax」関数に渡して、配列の中の最大値を求めて「maxSteps」に代入している。この関数「amax」は引数が配列1つで、配列には無数に値が格納出来るから、多くの値の中から一番大きな値を得たい時にとても使い易いようだ。

    maxSteps = amax #(abs(deltaX),abs(deltaY))

これで2点間を何個の点で描けばいいかが決定出来たので、その値で差分を割って1ステップでの座標値の増分を求める。

    deltaStepX = deltaX / maxSteps
deltaStepY = deltaY / maxSteps

この「deltaStepX」「deltaStepY」を「currentPos」に「maxSteps」回足せば、「pos」の値と等しくなるわけだ。そこで「currentPos」に点を打っては「currentPos」に「deltaStepX」「deltaStepY」を加えて更新するを繰り返す。これで「lastPos」から「pos」までビットマップ上に点が並ぶ。

    for i = 0 to maxSteps do
    (
      paintBrush currentPos
      currentPos += [deltaStepX, deltaStepY]
    ) 

線が引けたらビットマップをダイアログのビットマップコントロールにセットして表示を更新する。

    theCanvas.bitmap = theCanvasBitmap

これでマウスでドラッグした軌跡が連続した線になって現れるわけだ。

下がその実行結果。

fig03

続きはまた次回。

maxまとめページ



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

2016年11月28日

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

「TD SDK」を調べていたら「Vector3」クラスにおかしな事を見つけたので書いておくね。

下のプログラムは「Vector3」クラスのオブジェクトを作って乗算してみたものだ。

from modo import Vector3
v1 = Vector3(1,2,3)
v2 = Vector3(4,5,6)
print v1 * v2

これを実行すると以下のようになる。

# Result: 
Traceback (most recent call last):
  File "", line 5, in 
  File "C:\Program Files\Luxology\modo\10.0v1j\extra\Python\modules\modo\mathutils.py", line 565, in __mul__
    return Vector3(list(vector.dot(self.values, other.values)))
TypeError: 'int' object is not iterable

実際「*」の演算子を使ってベクトルとベクトルを掛け合わせるってのが何を意味しているのかは微妙なところで、内積なのか外積なのかをはっきりさせた方がいいわけで、下の例のように内積なら「dot()」、外積なら「cross()」というメソッドが用意されている。

from modo import Vector3
from lxu import vector
v1 = Vector3(1,2,3)
v2 = Vector3(4,5,6)
print vector.dot(v1,v2)
print vector.cross(v1,v2)

これがその実行結果。内積ならスカラーになり、外積ならベクトルが値になる。

# Result: 
32
Vector3(-3.000000, 6.000000, -3.000000)

で、問題の「Vector3」の「*」演算なんだけど、以下のようになっている。

def __mul__(self, other):
    """Vector multiplication

    :param other: Can be another Vector3, a Matrix4 or a float value.
    """

    # Scale this vector by scalar value
    if isinstance(other, int) or isinstance(other, float):
        return Vector3(vector.scale(self.values, other))

    # Return dot value of this and other vector
    elif isinstance(other, Vector3):
        return Vector3(list(vector.dot(self.values, other.values)))

    elif isinstance(other, Matrix4):
        raise NotImplementedError("Please use Vector3.mulByMatrixAsPoint instead")
    return self

掛け合わせる相手が「Vector3」の時は下のコードが実行されるけど、これがエラーの原因だ。よく見ると判る通り乗算は内積でやってるから得られる値はスカラー値だけだ。しかし「list()」は引数として「iterable」なオブジェクトを必要としている。「iterable」とはリストや辞書みたいにいくつかの要素を持ったオブジェクトで要素をひとつずつ返せるものだ。しかし内積の結果は単なる数値なのでエラーになったわけだ。

return Vector3(list(vector.dot(self.values, other.values)))

「*」を内積とするなら

return vector.dot(self.values, other.values)

でいいし、外積なら

return Vector3(vector.cross(self.values, other.values))

でいいわけだ。さて、どっちでしょう?

それではまた次回。

modo10ブログ目次



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