2017年04月27日

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

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

今回は前回作ったプログラムについてだ。

「w2g()」、「inout()」、「isCross()」については以前にやったので割愛する。

このプログラムでは最終的に四角形と三角形の重なった部分の輪郭の頂点を接続順にリストで得るのが目的で、その成果を確認するためにポイントアイテムを頂点に順に並べてみたりしている。

その頂点の配列を格納するのが「linklist」で、その前段階で生成される輪郭頂点とその頂点に接続する辺の番号リストのリストを記録するのが「plist」で、冒頭で初期化している。そしてテスト用の四角形と三角形はシーンに配置された編集可能ポリゴン(Triangle、Rectangle)から頂点を取得するようにしていて、三角形の頂点のリストは「tvs」に、四角形は「rvs」に取得している。四角形の頂点は1→2→4→3の順に繋がっているので、その順番で配列に登録している。

plist = #()
tvs = #()
rvs = #()
theMesh = snapshotAsMesh $Triangle
append tvs (getVert theMesh 1)
append tvs (getVert theMesh 2)
append tvs (getVert theMesh 3)

theMesh = snapshotAsMesh $Rectangle
append rvs (getVert theMesh 1)
append rvs (getVert theMesh 2)
append rvs (getVert theMesh 4)
append rvs (getVert theMesh 3)

「t1」、「t2」は交点を計算した時に得られる媒介変数の値をファンクションから持ち帰るために用意した。

t1 = 0
t2 = 0

各頂点が互いの図形の内側にあるかどうかを判定するために、頂点を三角形の重心座標に変換する行列を「tm」「rm1」「rm2」に準備する。この変換は三角形に対してしか出来ないので、四角形は三角形2つに分けて処理することにして、2つの三角形のどちらの内部にも点が無ければ四角形の外と判定することにした。三角形に対する変換行列が「tm」で、四角形は「rm1」、「rm2」だ。

tm = w2g tvs[1] tvs[2] tvs[3]
rm1 = w2g rvs[1] rvs[2] rvs[3]
rm2 = w2g rvs[1] rvs[3] rvs[4]

次のループは四角形の1辺を順に取り出して、始点側の頂点が三角ポリゴンの内側にあるかどうかを判定して、中ならそのポイントを「plist」に追加する。次にその辺に対して三角形の3辺を1辺ずつ順に取り出して、その辺の始点が四角形の内側にあるかどうか判定して内側なら「plist」に追加する。三角形は四角形の1辺ごとに3辺を巡回処理しているからこのままだと各辺の始点の内外判定を4回やることになってしまうので、そうならないように四角形の最初の辺の時(r==0)だけ実行するようにしている。次に四角形と三角形の辺の交点を調べて、あれば「plist」に登録する。以上のループをすべて終了すると、全ての頂点の中で相手のポリゴンの内側にある頂点と各辺どうしのすべての交点が「plist」に登録される。

for r = 0 to 3 do (
	rp1 = rvs[r + 1]
	rp2 = rvs[int(mod (r + 1) 4) + 1]
	if (inout tm rp1) then append plist #(rp1,r,int(mod (r + 3) 4))
	for t = 0 to 2 do (
		tp1 = tvs[t + 1]
		tp2 = tvs[(mod (t + 1) 3) + 1]
		if r==0 then (
			if (inout rm1 tp1) then append plist #(tp1,t+4,int(mod (t + 2) 3)+4)
			else if (inout rm2 tp1) then append plist #(tp1,t+4,int(mod (t + 2) 3)+4)
		)
		if (isCross rp1 rp2 tp1 tp2 &t1 &t2) then (
			cp = [(rp2.x-rp1.x)*t1+rp1.x,(rp2.y-rp1.y)*t1+rp1.y]
			append plist #(cp,r,t+4)
		)
	)
)

「plist」に記録されるのは頂点と、その頂点に繋がっているエッジのリストだ。エッジは0〜6までの通し番号で、四角形のエッジは0〜3、三角形のエッジは4〜6の番号を振ってある。例えば0番の頂点に繋がっているエッジの番号は0と3、1番の頂点には0と1といった具合だ。

fig01

四角形の頂点がr番なら、それに接続しているエッジはr番とr−1番になるけど、rが0だとエッジの番号が−1になってしまう。四角形の頂点の番号は0〜3の番号を巡回していて、0の前は3になる。こういう巡回する番号は割り算の余りを計算するmodを使うと簡単に計算できる。

例えば下のようにしてrの値を0から1ずつ増やしていくと、rの値を4で割った余りなので計算結果は0、1、2、3、0、1、2、3、・・・と数値が0〜3の間で巡回する。

mod r 4

つまりrに4を加えた式も計算結果は同じだ。

mod (r+4) 4

ここでrに3を加えた式にすると、3、0、1、2、3、0、1、2、・・・となって、巡回する数値が1つ前にずれる。

mod (r+3) 4

よって四角形の頂点rの前後のエッジの番号はrとmod (r+3) 4と言う事になる。

頂点を追加するコードは以下のようになっている。

append plist #(rp1,r,int(mod (r + 3) 4))

三角形も同様に、tとmod (t + 2) 3だけどエッジの番号は4オフセットがかかっているので、

append plist #(tp1,t+4,int(mod (t + 2) 3)+4)

となる。

続きはまた次回。

4月28日〜5月7日まで定期更新はお休みします。

maxまとめページ



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

2017年04月26日

modo10.2v1のスクリプトについて調べてみた その80

引き続き「TD SDK」の「modo.mathutils」モジュールの「Matrix3」について調べてみたい。

「inverted()」「invert()」は逆行列を生成する。下がそのコードだ。「inverted()」は新しく生成した逆行列を返し、「invert()」はそのオブジェクト自身を逆行列で置き換える。

 def inverted(self):
     """

     :returns Matrix: An inverted copy of this Matrix
     """
     tmp = Matrix4(self)
     tmp.invert()
     return Matrix3(tmp)

 def invert(self):
     """Inverts this Matrix in place
     """
     self.m = self.inverted().m

また、どちらのメソッドも最終的には「Matrix4」の「inverted()」を呼び出して逆行列を生成しているようだ。「Matrix4」には「invert()」が無いので、「Matrix3」で「inverted()」が呼び出されると、「Matrix4()」オブジェクトを作成して、そのオブジェクトから呼び出される「invert()」は「Matrix4」クラスの継承元の「Matrix3」の「invert()」になり、そこから「Matrix4」の「inverted()」が呼び出されるというややこしい流れになっている。当然「Matrix3」の「invert()」を呼び出せば、「Matrix3.invert()」->「Matrix3.inverted()」->「Matrix3.invert()」->「Matrix4.inverted()」という呼び出し順になる。

「Matrix4」の「inverted()」は下のようになっている。4X4の行列の逆行列の計算式なので結構な量の数式だ。行列が逆行列を持つには行列式が0以外じゃないとダメなので、途中で行列式を計算して変数「det」にそれを得て、絶対値が0.0001以下なら0と見なして逆行列なしとして変換せずに行列の値を配列で返している。なんで配列を返してるのかは不明だけど、戻ってきた値が「Matrix4」クラスのオブジェクトじゃなくなってるので逆行列なのかどうかはわかる。

 def inverted(self):
     output = Matrix4._getIdentity(self.size)

     a0 = self.m[0][0] * self.m[1][1] - self.m[0][1] * self.m[1][0]
     a1 = self.m[0][0] * self.m[1][2] - self.m[0][2] * self.m[1][0]
     a2 = self.m[0][0] * self.m[1][3] - self.m[0][3] * self.m[1][0]
     a3 = self.m[0][1] * self.m[1][2] - self.m[0][2] * self.m[1][1]
     a4 = self.m[0][1] * self.m[1][3] - self.m[0][3] * self.m[1][1]
     a5 = self.m[0][2] * self.m[1][3] - self.m[0][3] * self.m[1][2]
     b0 = self.m[2][0] * self.m[3][1] - self.m[2][1] * self.m[3][0]
     b1 = self.m[2][0] * self.m[3][2] - self.m[2][2] * self.m[3][0]
     b2 = self.m[2][0] * self.m[3][3] - self.m[2][3] * self.m[3][0]
     b3 = self.m[2][1] * self.m[3][2] - self.m[2][2] * self.m[3][1]
     b4 = self.m[2][1] * self.m[3][3] - self.m[2][3] * self.m[3][1]
     b5 = self.m[2][2] * self.m[3][3] - self.m[2][3] * self.m[3][2]

     det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0

     if abs (det) <= 0.00001:
         return self.m

     output[0][0] = 0.0 + self.m[1][1] * b5 - self.m[1][2] * b4 + self.m[1][3] * b3
     output[1][0] = 0.0 - self.m[1][0] * b5 + self.m[1][2] * b2 - self.m[1][3] * b1
     output[2][0] = 0.0 + self.m[1][0] * b4 - self.m[1][1] * b2 + self.m[1][3] * b0
     output[3][0] = 0.0 - self.m[1][0] * b3 + self.m[1][1] * b1 - self.m[1][2] * b0
     output[0][1] = 0.0 - self.m[0][1] * b5 + self.m[0][2] * b4 - self.m[0][3] * b3
     output[1][1] = 0.0 + self.m[0][0] * b5 - self.m[0][2] * b2 + self.m[0][3] * b1
     output[2][1] = 0.0 - self.m[0][0] * b4 + self.m[0][1] * b2 - self.m[0][3] * b0
     output[3][1] = 0.0 + self.m[0][0] * b3 - self.m[0][1] * b1 + self.m[0][2] * b0
     output[0][2] = 0.0 + self.m[3][1] * a5 - self.m[3][2] * a4 + self.m[3][3] * a3
     output[1][2] = 0.0 - self.m[3][0] * a5 + self.m[3][2] * a2 - self.m[3][3] * a1
     output[2][2] = 0.0 + self.m[3][0] * a4 - self.m[3][1] * a2 + self.m[3][3] * a0
     output[3][2] = 0.0 - self.m[3][0] * a3 + self.m[3][1] * a1 - self.m[3][2] * a0
     output[0][3] = 0.0 - self.m[2][1] * a5 + self.m[2][2] * a4 - self.m[2][3] * a3
     output[1][3] = 0.0 + self.m[2][0] * a5 - self.m[2][2] * a2 + self.m[2][3] * a1
     output[2][3] = 0.0 - self.m[2][0] * a4 + self.m[2][1] * a2 - self.m[2][3] * a0
     output[3][3] = 0.0 + self.m[2][0] * a3 - self.m[2][1] * a1 + self.m[2][2] * a0

     inv_det = 1.0 / det

     for i in range (4):
         for j in range (4):
             output[i][j] = output[i][j] * inv_det

     return Matrix4(output)

行列式の値が0じゃなければ逆行列が存在するので計算して新規に作成した「Matrix4」オブジェクトに値をセットする。式の中に「0.0」が入ってるものがあるけど、これは暗黙の型変換を利用して配列の値が整数の場合でも値を実数に変換してから計算するようにしてるのかな?

逆行列を試してみた。下のシーンには1m角の立方体の中心にローカル座標原点を持つ「Mesh」アイテムを適当に移動、回転したものと、「Locator」アイテムが入ってる。

fig01

下のプログラムは、座標値[0.5,0.5,0.5]を「Mesh」の変換行列で変換し、「Locator」の位置として設定し、その座標値を変換行列の逆行列で変換して表示するものだ。

import modo

mesh = modo.Mesh('Mesh')
loc = modo.Locator('Locator')
m4 = modo.Matrix4(mesh.channel('worldMatrix').get())
v=modo.Vector3([0.5,0.5,0.5])
v.mulByMatrixAsPoint(m4)
print 'world position:%s' % v
loc.position.x.set(v.x)
loc.position.y.set(v.y)
loc.position.z.set(v.z)
m4inv=m4.inverted()
v.mulByMatrixAsPoint(m4inv)
print 'local position:%s' % v

実行すると下のように「Locator」アイテムは立方体のローカル座標の[0.5,0.5,0.5]の位置になる立方体の頂点に移動した。

fig02

そして出力は以下のようになった。

# Result: 
world position:Vector3(-0.453649, 2.086864, -1.766864)
local position:Vector3(0.500000, 0.500000, 0.500000)

変換行列がローカル座標値をワールド座標値に変換するのに対して、その逆行列がワールド座標値をローカル座標値に変換するのが確認できた。

続きはまた次回。

modo10ブログ目次



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

2017年04月25日

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

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

今までのファンクションを使って四角形と三角形の重なった部分の輪郭の頂点を接続順に配列化するプログラムを作ってみた。

threshold = 0.00000001

fn w2g p0 p1 p2 = (
		m= matrix3 [p0.x,p0.y,1.0]  [p1.x,p1.y,1.0]  [p2.x,p2.y,1.0] [0,0,0]
		return (inverse m)
)

fn inout m p = (
	g = [p.x,p.y,1.0] * m
	if g.x >=0.0 and g.y >=0.0 and g.z >=0.0 then return true else false 
)

fn isCross p1 p2 p3 p4 &t1 &t2= (
	det = (p2.x-p1.x)*(p3.y-p4.y)+(p3.x-p4.x)*(p1.y-p2.y)
	if abs(det) < threshold then return false
	t1 = ((p3.y-p4.y)*(p3.x-p1.x)+(p4.x-p3.x)*(p3.y-p1.y))/det
	t2 = ((p1.y-p2.y)*(p3.x-p1.x)+(p2.x-p1.x)*(p3.y-p1.y))/det
	if t1>=0.0 and t1<=1.0 and t2>=0.0 and t2<=1.0 then return true
	return false
)

plist = #()
tvs = #()
rvs = #()
theMesh = snapshotAsMesh $Triangle
append tvs (getVert theMesh 1)
append tvs (getVert theMesh 2)
append tvs (getVert theMesh 3)

theMesh = snapshotAsMesh $Rectangle
append rvs (getVert theMesh 1)
append rvs (getVert theMesh 2)
append rvs (getVert theMesh 4)
append rvs (getVert theMesh 3)

t1 = 0
t2 = 0
tm = w2g tvs[1] tvs[2] tvs[3]
rm1 = w2g rvs[1] rvs[2] rvs[3]
rm2 = w2g rvs[1] rvs[3] rvs[4]
for r = 0 to 3 do (
	rp1 = rvs[r + 1]
	rp2 = rvs[int(mod (r + 1) 4) + 1]
	if (inout tm rp1) then append plist #(rp1,r,int(mod (r + 3) 4))
	for t = 0 to 2 do (
		tp1 = tvs[t + 1]
		tp2 = tvs[(mod (t + 1) 3) + 1]
		if r==0 then (
			if (inout rm1 tp1) then append plist #(tp1,t+4,int(mod (t + 2) 3)+4)
			else if (inout rm2 tp1) then append plist #(tp1,t+4,int(mod (t + 2) 3)+4)
		)
		if (isCross rp1 rp2 tp1 tp2 &t1 &t2) then (
			cp = [(rp2.x-rp1.x)*t1+rp1.x,(rp2.y-rp1.y)*t1+rp1.y]
			append plist #(cp,r,t+4)
		)
	)
)
if plist.count == 0 then exit
next = plist[1][2]
without = 1
linklist=#(plist[1][1])
lp=0
while linklist.count!=plist.count do (
	lp = lp + 1
	if lp > 300 then exit
	find = false
	for i = 1 to plist.count do (
		for j =2 to 3 do (
			if i != without and plist[i][j] == next then (
				append linklist plist[i][1]
				if j == 2 then k = 3 else k = 2
				next = plist[i][k]
				without = i
				find = true
				exit
			)
		)
		if find then exit
	)
)
p=#($Point001,$Point002,$Point003,$Point004,$Point005,$Point006)
for i =1 to  linklist.count do (
	p[i].position =  [linklist[i].x,linklist[i].y,0]
)

下のようにXY平面に三角形と四角形の編集可能ポリゴン2枚とポイント6つの入ったシーンを用意して上のプログラムを実行すると、

fig01

下のように四角形と三角形が重なった多角形の頂点にポイントが順に配置されて、このプログラムの動作が確認できる。

fig02

四角形と三角形を変形して実行してみると、

fig03

このようになってやはり重なっている部分の輪郭の頂点に順にポイントが配置される。ただし上の結果と比較してみると、ポイントの並び順が上は反時計周りで下は時計回りで逆になっている。

fig04

プログラムの中身についてはまた次回。

maxまとめページ



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

2017年04月24日

modo10.2v1のスクリプトについて調べてみた その79

引き続き「TD SDK」の「modo.mathutils」モジュールの「Matrix3」について調べてみたい。

「setIdentity()」メソッドは「Matrix3」オブジェクトを単位行列にする。

 def setIdentity(self):
     """Set this matrix to it's identity """

     self.m = Matrix3._getIdentity(self.size)
     # self.m = Matrix3._getIdentity()

内部では「_getIdentity()」メソッドが呼びだされていて、内部の配列「self.m」の内容は新規に単位行列が生成されて置き換わる。

 @staticmethod
 def _getIdentity(size=None):

     mat = []
     size = 3 if size is None else size

     for row in range(size):
         r = [0.0 for i in range(size)]
         r[row] = 1.0
         mat.append(r)
     return mat

「transpose()」「transposed()」は行列の転置行列を得るためのメソッドだ。転置行列は行列の対角を挟んで値を入れ替えた行列だ。

fig01

「transposed()」は新規にオブジェクトを生成して、そこにオリジナルの行列の値を転置したものをコピーして返している。

 def transposed(self):
     """This function transposes a matrix, by flipping it across it's main
     diagonal. It returns a new transposed matrix. """

     # out_mat = Matrix3(self)
     out_mat = self.__class__(self)

     for i in range(self.size):
         for j in range(self.size):
             out_mat.m[i][j] = self.m[j][i]

     return out_mat

それに対して「transpose()」メソッドは内部で「transposed()」が呼び出されて新規に転置行列が生成された後でその転置行列内部の配列「m」をオリジナルの「m」に代入して置き換えることでオリジナルを転置している。

 def transpose(self):
     """This function transposes a matrix, by flipping it across it's main
     diagonal."""
     # self.m = Matrix3(self).transposed().m
     self.m = self.__class__(self).transposed().m

下のプログラムは「Matrix3」のオブジェクト「m」を作って「transposed()」、「transpose()」の前後の値を表示するプログラムだ。

import modo

def strm(m):
	buf = ''
	for i in range(3):
		buf += '|%8.3f %8.3f %8.3f|\n' % (m[i][0],m[i][1],m[i][2])
	return buf

m = modo.Matrix3([[1,2,3],[4,5,6],[7,8,9]])
print 'before m'
print strm(m)
m2 = m.transposed()
print 'after m'
print strm(m)
print 'after m2'
print strm(m2)
m.transpose()
print 'transpose m'
print strm(m)

「transposed()」後もオリジナルの「m」の値は変化せず、転置された行列が出力されているのがわかる。また、「transpose()」後はオリジナルの値が転置されているのがわかる。

# Result: 
before m
|   1.000    2.000    3.000|
|   4.000    5.000    6.000|
|   7.000    8.000    9.000|

after m
|   1.000    2.000    3.000|
|   4.000    5.000    6.000|
|   7.000    8.000    9.000|

after m2
|   1.000    4.000    7.000|
|   2.000    5.000    8.000|
|   3.000    6.000    9.000|

transpose m
|   1.000    4.000    7.000|
|   2.000    5.000    8.000|
|   3.000    6.000    9.000|

続きはまた次回。

modo10ブログ目次



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

2017年04月21日

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

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

今回は前回の方針に従ってプログラムを作ってみたい。

まずは2本のエッジが交差しているかどうかの判定だ。

これも媒介変数t1を使ってエッジを考えれば簡単だ。エッジの両端の点の座標を(x1,y1)、(x2,y2)とすると、エッジ上の点pの座標(px,py)は、

px =(x2−x1)t1+x1
py =(y2−y1)t1+y1

と書けて、媒介変数t1が0〜1の間であれば、点pはエッジ上に存在する。

このエッジと交差判定するエッジの両端の座標値を(x3,y3)、(x4,y4)とすると、

px =(x4−x3)t2+x3
py =(y4−y3)t2+y3

となって、2つのエッジの交点はこのpx,pxの値が同じになるところなので、

(x2−x1)t1+x1=(x4−x3)t2+x3
(y2−y1)t1+y1=(y4−y3)t2+y3

これを整理して行列の形に直すと、

fig01

となる。これを変形して、
fig02

ここで、2x2行列の逆行列が存在するためには、この行列の行列式

fig03

で、detが0でなければいい。もし0なら2つのエッジは交点を持たず平行と言う事になる。上の行列の式をdetを使った式に書き直すと以下のようになる。

fig04
これでt1とt2が求まるので、それぞれが0〜1の間にあるかどうかで交点が存在するかどうかが判定できる。

下が判定ファンクションだ。せっかく交点を計算しているから、実際使う時は算出できたt1、t2を返した方がいいだろうね。

threshold = 0.00000001

fn isCross p1 p2 p3 p4 = (
	det = (p2.x-p1.x)*(p3.y-p4.y)+(p3.x-p4.x)*(p1.y-p2.y)
	if abs(det) < threshold then return false
	t1 = ((p3.y-p4.y)*(p3.x-p1.x)+(p4.x-p3.x)*(p3.y-p1.y))/det
	t2 = ((p1.y-p2.y)*(p3.x-p1.x)+(p2.x-p1.x)*(p3.y-p1.y))/det
	if t1>=0.0 and t1<=1.0 and t2>=0.0 and t2<=1.0 then return true
	return false
)

それではまた次回。

maxまとめページ



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