28.1.10

PolygonIslandを使って、PolygonNormalを揃えてみた。

0 comment
法線についての質問が某掲示板にあったので、解決するすべを考えてみました。
一つのオブジェクトの中に、法線の向きが反転してしまっている場合にそれを揃える問題です。



上の絵は、2つの Sphere を Merge して1つにして、真ん中の Polygon のみを反転させた状態です。
Border Line が見えますね。
真ん中だけ反転しているというものです。

このように、簡単な形状なら、真ん中の Polygon を選んで、Invert Polygons を実行すれば修正出来たことになりますが、ぐちゃぐちゃの場合、労力と気力と根性が必要です。

先ほど、Border Line が見えますとのことですので、ここで効力を発揮するのは、Polygon Island フィルターです。
Polygon Islandは、まさにPolygonの島を選択するツールです。
これをセットして、F10を押し、Raycastモードにしてから、適当なPolygonをつつきます。
すると、下図のようになります。



Border のところで、選択が止まりました。
なので、これを Invert Polygons で反転してあげます。

というのを数回繰り返せば、向きを揃えることができますね。

数回繰り返すのもバカらしいという方は、スクリプトも作ってみました。
たぶん大丈夫だと思うのだけれど・・・。
今回は、Jscriptです。
バグあったら、教えてください。
あっ。ちなみに、オブジェクトの形状が複雑だと重いかも。
手動の方が良かったりするんですよね。

function GetIslands(in_obj)
{
  var arrIslands = new Array();
  var plyPolygons = in_obj.ActivePrimitive.Geometry.Polygons;
  var arrSeen = new Array(plyPolygons.Count);

  for ( var i=0; i<plyPolygons.Count; i++ ) {
    if(arrSeen[i]){continue;}
    
    var xsiCurrPolygons = new ActiveXObject("XSI.Collection");
    xsiCurrPolygons.Add(plyPolygons(i));

    var fltPolyIslandFilter = Filters("Polygon_Island");
    var xsiCurrIslands = fltPolyIslandFilter.Subset(xsiCurrPolygons);
    
    var plyCurrIslands = Dictionary.GetObject(xsiCurrIslands).SubComponent.ComponentCollection;
    
    for ( var j=0; j<plyCurrIslands.Count; j++ )
      arrSeen[plyCurrIslands(j).Index] = 1;
    
    arrIslands.push(plyCurrIslands);
  }
  
  for(var i=0;i<arrIslands.length;i++)
    AlignNormal(arrIslands[i]);
  
  return arrIslands;
}

function AlignNormal(pIslandCol)
{
  var oCurPoly = new ActiveXObject("XSI.Collection");
  oCurPoly.Add(pIslandCol(0));
  var xsiCurrIslands = Filters("Polygon_Island").Subset(oCurPoly);
  ApplyTopoOp("InvertPolygon", xsiCurrIslands);
}

for(var i=0;i<Selection.Count;i++)
  GetIslands(Selection(0))

24.12.09

Real Time Ambient Occlusion via ICE II

2 comment
メリークリスマスイブっ!

すごいひさしぶりにICEをいじってみました。

以前、WeightMapにしか適用でき無かったAOですが、VertexColorに再現出来る方法が分かったのでやってみました。

こちらの方のサイトを参考にしました。
http://coralocean.sakura.ne.jp/2008/09/20080901020653757

こちらがICE Tree。



では、つたない解説。



まず、初めに、ポイントごとに必要なランダムベクトルを作るために配列を用意します。
0~1までの、配列を作れるとわかりやすくていいかもです。
5個の配列なら

[ 0, 0.25, 0.5, 0.75, 1 ]

って感じ。
一番左端のIntegerノードがランダムベクトルの数になります。
この数字を増やせば増やすほど高精細な結果になるけど、重くなるってところです。



次は、キレイにならんでいる配列にランダム感を与えます。

ランダムノードは、一律に同じ値が入ってしまうので、使えません。
なので、Modulo(剰余)を使ってちょっと細工をします。
入ってきた配列に対して、適当な値を掛け算します。この値は、整数では駄目です。

123.4818

などのような値が好ましいようです。1でModuloするので少数が入ってりゃいいです。
欲しいのは、半球のランダムベクトルなので、XとZだけ、レンジを-1~1にします。
Yは、0~1までです。

最後に、Point毎にNormalの方向にProject Vectorを使ってランダムベクトルを向けます。



Scalar to 3D Vector をプレビューすると、こんなベクトルの花が咲きました。



次は、このベクトルフラワーをPointごとに配置して、このベクトルが、自分自身に突き刺さるかを判定するのに、Raycastを使います。遮蔽率を求めるためです。
PointPositionに、PointNormalをちょっと加えているのは、フラワーをサーフェイスから少し浮かすためです。
でないと、まったいらな平面でも遮蔽してしまうことになってしまうからです。
Dirtmapは実はそこが弱点だったりしますが・・・。



ベクトルがヒットしたか否かの配列の平均を取ることで、そのPointの遮蔽率を出します。
Gradientノードに一度さして、PointColorに焼き込みます。
その後、改めてVertexColorにさします。
直接させないのが、なんで?って感じなんですけど・・・。
教えて欲しいものです。

以上でざーっとですが、ICE Treeの解説でした。

もっといろいろやりたいんですが、いかんせんアルゴリズムが分かりません。
数学Iしかやってないのが、ここに来て響いたなぁ(笑)

ま、自業自得ですねー。もっと勉強します。

そして、ワタクシ、来年より無職になります。
半年くらい仕事しない予定。
だけど、ブログはちまちまやっていこうかなと思ってます。

では、早いですが良いお年をお過ごしください。

来年も良い年でありますように。

19.9.09

ヘアースタイリング - Hair2Curve

4 comment
ヘアースタイリングツールを作ってみました。
初めに断っておきますが、このツールは、スクリプテッドオペレータを使用しています。
で、そのスクリプテッドオペレータが存在している状態で、保存すると二度とデータは復帰出来ません
という超危険な仕様なので、使い方は気をつけてくださいw
保存するときは、ヘアについてる、Hair2Curveをすべて消してからにしましょう。

使い方は、下記画像さんしょ。



ヘアを選択して実行するやつ
Hair2Curve.pys

#---------------------------------------------------
app=Application;log=app.LogMessage;sel=app.Selection
import win32com.client
#---------------------------------------------------
if sel.Count==0 or sel(0).type!="hair":
 raise

sCode = """from win32com.client.dynamic import Dispatch as d
def Hair2Curve_Update(ctx,OutPrim,InPrim):
 oCrvCol = InPrim.Value.Geometry.Curves
 lHairPos = [[],[],[]]
 for oCrv in oCrvCol:
  for fHairPer in [f/13.000*99.999+0.001 for f in range(14)]:
   vPos = d(oCrv.EvaluatePositionFromPercentage(fHairPer)[0])
   lHairPos[0].append(vPos(0))
   lHairPos[1].append(vPos(1))
   lHairPos[2].append(vPos(2))
 OutPrim.Value.GetGeometry2(ctx.CurrentFrame).Points.PositionArray = lHairPos
"""

oCrvCol = win32com.client.Dispatch("XSI.Collection")
for oHair in sel:
 oHair.AllowStretch.Value = True
 lPos=[[f for f in l] for l in oHair.ActivePrimitive.Geometry.Points.PositionArray]
 iStrNmb=len(lPos[0])/14
 lPos.append(list([1])*len(lPos[0]))
 oCrv = oHair.Parent.AddNurbsCurveList()
 for i in range(iStrNmb):
  oCrv.ActivePrimitive.Geometry.AddCurve(
   [ lPos[0][i*14:i*14+14],
    lPos[1][i*14:i*14+14],
    lPos[2][i*14:i*14+14],
    lPos[3][i*14:i*14+14] ],
   range(14),
   False,
   1,
   1
  )
 oFitCrvOp = app.ApplyGenOp("CrvFit", "", oCrv, 3, "siPersistentOperation", "siKeepGenOpInputs", "")(0)
 oFitCrvOp.points.Value = 3
 log(oFitCrvOp.Parent3DObject)
 oFitCrv = oFitCrvOp.Parent3DObject
 oHair.Parent.AddChild(oFitCrv)
 
 oTransfo = oHair.Kinematics.Global.Transform
 oFitCrv.Kinematics.Global.Transform = oTransfo
 app.FreezeModeling(oFitCrv)
 app.DeleteObj(oCrv)
 #app.ToggleVisibility(oFitCrv)
 oCrvCol.Add(oFitCrv)
 app.Refresh()
 
 oOpr = oHair.ActivePrimitive.AddScriptedOp(sCode,oFitCrv.ActivePrimitive,"Hair2Curve","Python",1)
  
sel.SetAsText(oCrvCol.GetAsText())



そんでもって、Hair2CurveOpを消すスクリプトは、これです。

FreezeHair2Curve.pys

#---------------------------------------------------
app=Application;log=app.LogMessage;sel=app.Selection
import win32com.client
from win32com.client import constants as c
from win32com.client.dynamic import Dispatch as d
#---------------------------------------------------
app.ActivateObjectSelTool()
oHairPrimCol = app.FindObjects("","{D5C3CDBA-B361-4A11-9582-91DBA0ECBB49}")
for oPrim in oHairPrimCol:
 oOp = oPrim.NestedObjects("Hair2Curve")
 if oOp:
  log(oOp)
  oCrv = d(oOp).InputPorts(0).target2.Parent3DObject
  app.FreezeObj(oPrim.Parent3DObject)
  app.DeleteObj(oCrv)




いやいや、おっかねぇスクリプトですね。
会社でなんて使わせられません。

そもそも、Hairにスクリプテッドオペレータが適用出来ないXSI様が悪いんですけどね。
HairにICE適用出来ないのも悪いですね。

そいや、Softimage2010を使い始めましたわ。
2010になって、HairにICE適用出来るようになったのかしら。
後でやってみよーっと。

FaceRobotは、なかなか素晴らしいものがありますね。
顎とか、口まわりとか、目まわりとか。
顔は、モーションキャプチャの場合、Envelopeのほうが断然楽ですな。
ただし、後付け修正したいので、Shapeで加えるとかですかね。
もちろん、移動差分の骨のクリップを並べて、MixerのNormalizeをオフって加算でやるのもいいですね。
どっちも得手不得手があるので、それぞれ選ぶべきですなー。

データは軽く作れよっ。

26.7.09

Sukio Sukio Sukio Python X

0 comment
遂に10回目になりました。
しかし、かなり時間を空けてしまいましたね・・・。
サボタージュ期間が長かったかもです。
でも、海の日にヴぁかんスに行ってきたのでちょっと回復。
さぁ、行きましょうか。

では、今回は、よく使うPythonの関数を紹介しましょう。
いろいろ便利なものが揃っています。
取りあえずは、こんなのあるなーくらいに思うくらいにしておいて、確かこんなのあったな。
って時に使うのがベストですね。


map(関数,シーケンス)

これは、シーケンスのひとつずつのアイテムを関数にかまします。
戻り値は、関数のreturn値を介してリストで戻します。

mapを使ったもの

def fct(p):
    return p
lRtn = map(fct,range(10000000))

for文を使ったもの

lRtn = list()
for i in range(10000000):
    lRtn.append(i)

内容はほぼ一緒です。(あんまり意味の無いサンプルのサンプルですが・・・)
ただmapを使ったほうがちょびっとだけ早く動作するみたいですね。
XSIでの使い方は、大量のオブジェクトがあったとして、そのひとつひとつに同じ関数をかましたいときに使います。
例えば、null群があって、そのsizeを0.1にしてさらに形状をBoxにしたいときは

def SetDefaultNull(pObj):
    pObj.size.Value = 0.1
    pObj.primary_icon.Value = 4
map(SetDefaultNull,Application.Selection)

こんな風に使います。
やりたいことをファンクションにしておいて、シーケンスをかます。
とてもシンプルに書けます。


enumerate(シーケンス)

enumerateは、for文と共に使います。
インデックスとアイテムを同時に発生させることが出来ます。

for i,o in enumerate(list):

iにインデックスが入って、oにリストの中身が入ります。
for文だけでリストなどを回すとリストに入っているアイテムしか取得出来ず、それが何番目のオブジェクトなのかは分かりません。
使いどころは例えば、envelopeのweightsとdeformersをシンクロしたいときに効果を発揮します。weightsは、デフォーマの順番に各ポイントが割り振られている2次元配列で戻って来ます。

((0.0,50.0,100.0),(100.0,50.0,0.0))←こんな感じに。

つまり、weightsの0番目に処理しているときに、deformersの0番目を取得したいときなどに使えますね。

import win32com.client
app=Application;log=app.LogMessage;sel=app.Selection
oDfm = sel(0).Envelopes(0).Deformers
tWgt = sel(0).Envelopes(0).Weights.Array
oBoneCol = win32com.client.Dispatch("XSI.Collection")
for i,w in enumerate(tWgt):
    if max(w):oBoneCol.add(oDfm(i))
log(oBoneCol.GetAsText())

このサンプルは、エンベロープのウェイトが0のオブジェクトを弾くXSICollectionを作成します。
max()も使っていますが後述します。
maxの値が0ってことは、weightがすべて割り振られてない0 weiht bone なので、スルーです。


sum(シーケンス)

リスト、タプルの足し算を返します。
sum(range(10))は、0から9を足しているので45を返します。
ただし、数字しか受け付けません。文字列の場合は、joinを使いましょう。

",".join(string_list)

よく使うとか言っておきながらサンプルを思いつけませんでした(ギャボンヌ)
うーん。使わないかなw


max(シーケンス),min(シーケンス)

maxは、リストなどの最大値を取得。minは、同様に最小値取得です。
FCurveの最大値とか取れそうですね(取ったこと無いけど・・・)

app=Application;log=app.LogMessage;sel=app.Selection
log(max([k.Value for k in sel(0).posy.Source.Keys]))


open(ファイルパス,モード(読み取りとか書き込みとか))

結構良く使います。
weight情報を吐き出したり、ログファイルを生成したいときとか、テキスト情報を読みに行ったりとか。
読み込みたいときはコレです。stripは、末尾の改行を示す"\n"を取り除いてくれます。
pathになんか入れてください。

app=Application;log=app.LogMessage
f=open(path,"r")
while 1:
    s = f.readline()
    if not s:break
    log(s.strip())
f.close()


逆に書き込みたいときは

app=Application;log=app.LogMessage;sel=app.Selection
f=open(path,"w")
f.write(sel.GetAsText())
f.close()

pathになんかパス。選択したもののリストを書き出します。
と、Openには、いろいろあるんですが、使うのはこれくらいで十分じゃないでしょうかね。


今回は、こんな感じです。
だいぶ、Pythonの海泳げるようになって来たんじゃないでしょうか?
そいや、初心者の講座って始めたんですが、全然初心者向けにならなくなってきた感じですよね。
一度初心者の道を通り終わると、初心にかえるのが難しいことを意味しているんですが、ということは、ワタクシもまだまだひよっこなんだなぁと思い知らされます。

絶えずもどかしい。ああもどかしい。もどかしい。

でも、ライブラリとして、とって置けたのでなんとなくはいいかな。と。
完全なる自己満足。

ってなわけで、次回は、HairをCurveに変換するPythonでも公開しましょうかね。
Curveに変換して、Scripted Operatorにつなぎます。
すると、Curveで、Hairをスタイリング出来ます。

Softimageは、Hairスタイリングツールは、開発者のためにあるようなものですから、ちょっとは楽にしてほしいところなんですけどねぇ・・・。
とはいえ、この辺は難しいところですから、デザイナーがどう使うかにかかっているのですが。

では、近々公開しますね。