2011/01/20

非平面を平面に

ツイッター上で話題になっていた非平面ポリゴンを平面化する、というスクリプトです。
Softimage2011で確認。
単純に平面化するとほかのポリゴン面の形を変えてしまってせっかく平面だったものも非平面にしてしまうので
いろいろこねくり回して回避してます。
汚いコードですがとりあえずそれっぽく動くようにはなってます。
いくつか問題はありますが。。。

使い方は、平面化したいポリゴンを選択後実行。です。
使用前


使用後



おおまかな処理内容としては
・平面化したいポリゴン面に隣接するエッジ情報をとっておく
・平面化
・平面化したポリゴンととっておいたエッジの交差点にポイントを移動
てな感じです。

上にも書きましたが問題がありまして、
・ポリゴンの法泉がうまく取得できなくて失敗することがある。
PolygonNode.Normalから取得してるんですが値の意味を勘違いしてるのか使い方が悪いのか
けっこうな頻度でうまくいかない。
再現性があるのでなんかしらの理由があるのだろうけどわかりません。
情報求む。

ここに関しては横着せずに他の人がツイッターに書いていたように各ポイント法線から算出すれば解決するかもしれません。

・対象ポリゴンを構成するポイントが4つ以上のポリゴンに属しているとうまくいかない
最初にとっておいてるエッジ情報のインデックスと最後に移動させるポイントのインデックスが合わないのが一つ。
これはもうちょい手を入れればなんとかなりそう。
もうひとつがオレの陳腐な脳みそでは可決不能。
4つ以上の面に接したポイントをどう動かせばすべての面を平面に保てるかわからない。
誰か頭のいい人教えてください。

・なぜか1回目の実行は処理がされない
なんで???

とまぁこんな感じです。
そのうち直せたら直したい。

以下コード。

from win32com.client import *

xsi = Application

#サブコンポーネントからいろいろ取得
oSubCmp = xsi.Selection(0).SubComponent
oFace   = oSubCmp.ComponentCollection[0]
oPoly   = oFace.Nodes[0]
oParent = oSubCmp.Parent3DObject
oGeom   = oParent.ActivePrimitive.Geometry
xsi.FreezeModeling(oParent)

#隣接エッジのベクトルを保存しておく
#(レイキャスト時にちゃんとヒットするように拡大)
lPntIdxs = [oPnt.Index for oPnt in oFace.Points]
lEdgePos  = []
lEdgeAxis = []
lEdgePntIdx  = []
for oEdge in oFace.Edges.NeighborEdges():
    oEdgePnts = oEdge.Points
    if oEdgePnts[0].Index in lPntIdxs:
        oStartPnt = oEdgePnts[1]
        oEndPnt   = oEdgePnts[0]
    else:
        oStartPnt = oEdgePnts[0]
        oEndPnt   = oEdgePnts[1]
    vEdgeAxis = XSIMath.CreateVector3()
    vEdgeAxis.Sub(oEndPnt.Position, oStartPnt.Position)
    vEdgeAxis.ScaleInPlace(100)        #拡大
    lEdgeAxis.extend(vEdgeAxis.Get2())
    vPos = oStartPnt.Position
    vEdgeAxis.ScaleInPlace(0.5)        #逆側もレイキャストできるように後ろにずらす
    vPos.Sub(vPos, vEdgeAxis)
    lEdgePos.extend(vPos.Get2())
    lEdgePntIdx.append(oEndPnt.Index)
  

#ポリゴン面にそったトランスフォームを作成
vFacePos = XSIMath.CreateVector3()
oPnts = oFace.Points
for oPnt in oPnts:
    vFacePos.Add(vFacePos, oPnt.Position)
vFacePos.Scale(1.0/oPnts.Count, vFacePos)
 
oTrans = XSIMath.CreateTransform()
oTrans.Translation = vFacePos
vYAxis = XSIMath.CreateVector3(0, 1, 0)
vNrml = XSIMath.CreateVector3()
nAngle = vYAxis.Angle(oPoly.Normal) #radians
vCross = XSIMath.CreateVector3()
vCross.Cross(vYAxis, oPoly.Normal)
oTrans.SetRotationFromAxisAngle(vCross, nAngle)

#平面化
#(さっき作ったのトランスフォーム上でのY=0に移動)
#(レイキャスト時にちゃんとヒットするように拡大)
dPos    = {}
for oPnt in oPnts:
    vPntOnFace = XSIMath.MapWorldPositionToObjectSpace(oTrans, oPnt.Position)
    vPntOnFace.ScaleInPlace(5)
    vPntOnFace.Y = 0
    vPntOnFace = XSIMath.MapObjectPositionToWorldSpace(oTrans, vPntOnFace)
    dPos[oPnt.Index] = vPntOnFace
oPnts = oGeom.Points
lPosArr = [list(l) for l in oPnts.PositionArray]
for idx, v in dPos.iteritems():
    lPosArr[0][idx] = v.X
    lPosArr[1][idx] = v.Y
    lPosArr[2][idx] = v.Z
oPnts.PositionArray = lPosArr

#エッジベクトルから平面にレイキャスト
oGeom.SetupPointLocatorQueries(3, None, [oFace.Index], -1)
oPntLocations = oGeom.GetRaycastIntersections(lEdgePos, lEdgeAxis, 1)
lPos = oGeom.EvaluatePositions(oPntLocations)
for idx, allidx in enumerate(lEdgePntIdx):
    lPosArr[0][allidx] = lPos[0][idx]
    lPosArr[1][allidx] = lPos[1][idx]
    lPosArr[2][allidx] = lPos[2][idx]
oPnts.PositionArray = lPosArr


2 件のコメント:

  1. こんにちは。

    サブコンポーネントから色々取得する4行目、oParent = xoSubCmp.Parent3DObject ですが、

      xoSubCmp 

    余計な x が入っちゃってるようで、取っ払ったら動きました。 

    平面化する時の基準は何なのかがまだつかめていません。4角形ポリゴンの、全部のポイントを動かして平面化しているように見えますが、特定のポイントはそのまま保持したい場合がほとんどですので、「ここは動かさずにそれ以外の部分を動かして平面化する」だとか、何かしらそういう感じになると良いと思いました。 難しいですよね。 俺にはサパーリですw


    kissiy さんには、平面騎士の称号が授与されました。

    返信削除
  2. コメントありがとうございます!
    xoSubCmp
    コピペしたときにx押しちゃってたみたいです。
    すみません。

    今の平面化の基準は選択したポリゴンの位置とノーマルです。
    ポイント三点選んでそれを構成点にする面をリファレンスにすればできると思います。

    時間見つけてやってみますー。

    返信削除