Python
Cinema4D

[Cinema 4D] ワールド座標と平行なバウンディングボックスサイズを取得したい

CGソフトを使っているとバウンディングボックスサイズを取得したいということがあります.Cinema 4Dにもバウンディングボックスサイズを取得する便利な関数があるのですが,一つ欠点があり,ローカル座標と平行なバウンディングボックスサイズしか取得できないのです.

次の画像のような四角推を例にとってみます.高さ5,底面は5*5で,底面中心を軸に持ち,そこを基点にワールド座標に対してH.Bに30度傾いているとします.GetMp()はオブジェクトの全頂点の中心座標を返します.GetRad()はGetMp()を基点としたローカル軸と平行な各軸の半径を返します.この時,GetMp()で取得した座標値にGetRad()の数値をプラスとマイナスすればローカルのバウンディングボックスサイズが分かります.しかし,ワールド座標から見たときのそのオブジェクトの最大サイズと最小サイズ(赤い枠)が欲しいときには困ります.

20190227-1.jpg

GetRad()とGetMp()を使う場合は,このオブジェクトをH.Bに-30度してワールド座標と平行な向きに戻します.これは逆行列を乗算すれば簡単です.この時,頂点も-30度されてしまうので,各頂点にはH.Bに30度傾けてポイント位置だけを最初の位置に戻します.これでワールド座標と軸は並行でポイント位置は最初の状態に戻ります.Pythonの中で軸モードを使って軸の向きだけワールドと平行にしたいところですが,どうやらそれはPythonでは不可能なようで,この手の質問がCinema 4DのPlugincafeでもしばしばトピックに上がっているみたいです.

この状態でGetMp()とGetRad()すれば,赤い枠の中心座標と上下前後のサイズが得られます.

それでは実際にこの作業をスクリプトで行い,得られたサイズからバウンディングボックスと同じサイズのポリゴンを作成してみます.

注意点として最初に選択オブジェクトのクローンを取得して,キャッシュを取得します.opを操作すると選択オブジェクトを操作することになってしまいます.また,プリミティブの場合はキャッシュをとります.他,デフォーマーを使っていたり,オブジェクタイプによっては最終ポリゴンを取得する追加処理が必要になります.また,途中でクローンに対してMessage(c4d.MSG_UPDATE)を実行してEvetnAdd()して姿勢を確定させる必要があります.MSGはCinema 4D側にある操作をしたから,情報を更新してくださいというメッセージを渡します.これは実は様々な個所で使いますし,メッセージタイプも実にたくさんあります.

def main():

doc = c4d.documents.GetActiveDocument()
op = doc.GetActiveObject()
if op is None:
return
doc.StartUndo()

# 選択オブジェクトからキャッシュを取得
# デフォーマなどを使っている場合は,別途GetDeformeCache()などを用いて取得
if op.GetCache():
clone = op.GetCache().GetClone()
else:
clone = op.GetClone()

# 自身の逆行列を乗算して姿勢をワールドと平行にする
clone.SetMg(~op.GetMg() * op.GetMg())

# 頂点を元の位置に戻す
pcount = clone.GetPointCount()
points = clone.GetAllPoints()
for p in xrange(pcount):
clone.SetPoint(p, op.GetMg() * points[p])

# クローンをアップデートしてEventAdd()すること
clone.Message(c4d.MSG_UPDATE)
c4d.EventAdd()

# センター位置からXYZの最大,最小を取得
center = clone.GetMp()
xMin = center.x + clone.GetRad().x * -1
xMax = center.x + clone.GetRad().x
yMin = center.y + clone.GetRad().y * -1
yMax = center.y + clone.GetRad().y
zMin = center.z + clone.GetRad().z * -1
zMax = center.z + clone.GetRad().z
print xMin
print xMax
print yMin
print yMax
print zMin
print zMax

# 新規バウンディングボックス用のポリゴンオブジェクトを作成して,座標をセットしていく
box = c4d.BaseObject(c4d.Opolygon)
box.ResizeObject(8,6)
box.SetPoint(0,c4d.Vector(xMin, yMin, zMin))
box.SetPoint(1,c4d.Vector(xMin, yMax, zMin))
box.SetPoint(2,c4d.Vector(xMax, yMin, zMin))
box.SetPoint(3,c4d.Vector(xMax, yMax, zMin))
box.SetPoint(4,c4d.Vector(xMax, yMin, zMax))
box.SetPoint(5,c4d.Vector(xMax, yMax, zMax))
box.SetPoint(6,c4d.Vector(xMin, yMin, zMax))
box.SetPoint(7,c4d.Vector(xMin, yMax, zMax))
box.SetPolygon(0, c4d.CPolygon(0,1,3,2))
box.SetPolygon(1, c4d.CPolygon(2,3,5,4))
box.SetPolygon(2, c4d.CPolygon(4,5,7,6))
box.SetPolygon(3, c4d.CPolygon(6,7,1,0))
box.SetPolygon(4, c4d.CPolygon(1,7,5,3))
box.SetPolygon(5, c4d.CPolygon(6,0,2,4))
doc.InsertObject(box)

# 表示タグで線表示にする
tag = c4d.BaseTag(c4d.Tdisplay)
tag[c4d.DISPLAYTAG_AFFECT_DISPLAYMODE] = True
tag[c4d.DISPLAYTAG_SDISPLAYMODE] = 6
tag[c4d.DISPLAYTAG_WDISPLAYMODE] = 0
box.InsertTag(tag)

box.Message(c4d.MSG_UPDATE)

doc.AddUndo(c4d.UNDOTYPE_NEW,box)
doc.EndUndo()
c4d.EventAdd()

このようにすれば,色々な姿勢で存在するオブジェクトから,ワールド座標に対して平行なバウンディングボックスを取得できます.

何に使うかというと,例えば,ワールドに平行なバウンディングボックスの端にオブジェクトを配置させたりしたい場合に,内部でこのようなバウンディングボックスを生成すれば座標を得られます.ちなみに,box = c4d.BaseObject(c4d.Opolygon)の行で空のポリゴンオブジェクトを生成していますが,後の行でInsertObjectする,しないに関わらずメモリ上にはこのオブジェクトが存在しつづけるので注意が必要です.Insertしない場合は,Remove()で削除しておきましょう.

できました.

20190227-4.jpg

20190227-2.jpg

20190227-3.jpg

ポリゴン数が多いと頂点を元に戻す処理で時間がかかるので,八分木などを用いて最大,最小座標を割り出すのが良いと思います.