2011年07月

2011年07月29日

modo501 SDK いじってみた その30 modo501 SP2

小松左京氏のご冥福をお祈りします。昭和がだんだん遠のいて行くなぁ。

さて、mandelbrotのソースコードの残りだ。CMandlebrotWork::share_Evaluate ( )から呼び出されて1つのピクセルに対するマンデルブロー集合の計算をしているのがCMandlebrotWork::Pixel ( )メソッドだ。

  float
CMandlebrotWork::Pixel (
  double x,
  double y)
{
  std::complex<double> z (0.0, 0.0);
  std::complex<double> c (src_filt->x_0 + src_filt->x_s * x,
  src_filt->y_0 + src_filt->y_s * y);
  unsigned i;

  for (i = 1; i <= src_filt->i_max; i++) {
    z = z * z + c;
    if (abs (z) > 2.0)
      return log ((double) i) / src_filt->max_log;
  }

  return -1.0;
}

何度も書くけどマンデルブロー集合は下の数列の漸化式のCを出発点とした収束発散の状態を集めたものだ。

n+1=Z+C

=0

Pixel ( )に与えられる引数x,yはそれぞれ0≦x≦1、0≦y≦1で表わされる矩形の中の1点で、これをまずマンデルブロー計算の出発点となる定数Cの虚数空間座標に直す必要がある。C++には虚数を扱えるcomplexというクラスがあるので、最初にまずZとCをそのcomplexクラスのオブジェクトとして宣言している。
Zは上の定義から実数も虚数も0だ。
Cは1X1の矩形範囲をアイテムのcenter.X、center.Y、radiusチャンネルが示す虚数空間内の矩形範囲(中心点が(center.X , center.Y i)で縦横の幅が2×radiusの矩形範囲)にマッピングして得られる座標値になる。これらのチャンネルの値はCMandelbrotInstance::vclip_AllocFilter ( )で

cx = attr.Float (gd->cx);
cy = attr.Float (gd->cy);
rad = attr.Float (gd->rad);

filt->x_0 = cx - rad;
filt->y_0 = cy - rad;
filt->x_s = rad * 2.0;
filt->y_s = rad * 2.0;

と変換され、虚数空間の矩形の最小の頂点の座標が(x_0 , y_0)で、矩形の横、縦の長さが(x_s , y_s)になっているので、

src_filt->x_0 + src_filt->x_s * x
src_filt- >y_0 + src_filt->y_s * y

としてやれば、x_0の点からx_0+x_sまでの範囲をx(値が0〜1の間)で按分した点とy_0の点からy_0+y_sまでの範囲をy(値が0〜1の間)で按分した点が得られる。これがCの虚数座標になるので、complexクラスのインスタンスCのコンストラクタに引数で渡して初期化している。

あとは漸化式をi_max回繰り返して計算し、Zの絶対値が2を超えたところでこの数列が発散すると見て繰り返しをやめている。その時までに繰り返した回数は変数iに入っていて、その対数をとって、

log ((double) i) / src_filt->max_log;

CMandelbrotFilter::imf_Generate ( )の中で

max_log = log ((double) i_max);

と求めている値で割った値をこのメソッドの戻り値として返している。この結果得られるのはi_maxを底とした繰り返し回数の指数値だ。

Logi_max i

要するにi_maxを何乗するとiになるかって値ね。こういう対数値にすることでグラディエントに入力される値の範囲を0〜1の値にマッピングしているわけだね。そして、もしi_max回繰り返しても発散しない場合は−1を戻り値にしている。これで1つのピクセルに対するマンデルブロー集合の計算がなされ、その値が0〜1の範囲か−1になることがわかった。それを1次元配列f_buf[ ] に全てのピクセルぶん蓄積してからCMandelbrotFilter::imf_Generate ( ) の中でグラディエントを使って色に変換して画像にしているのがこのプラグインなわけだ。

そして矩形画像の横1列ぶんの計算を受け持つのが前回やったSharedWorkインターフェースを実装したワーククラスCMandlebrotWorkのshare_Evaluate ()メソッドだ。そしてそのワークオブジェクトを計算に必要なぶんだけ生成して複数のスレッドに割り振って処理しているのが、CMandelbrotFilter::imf_Generate ( ) の真ん中あたりのコードで、まず1つのワークオブジェクトを作り、それをCLxUser_ThreadServiceのProcessShared( )に渡すことでそのワークオブジェクトの複製をどんどん作らせて、個別にスレッドに割り当てて、share_Share ( )メソッドでそれらに順にアクセスして計算させる行を割り振って行き、share_Share ( )メソッドがLXe_FALSEが返して、もう割り振る仕事が無くなったら、割り振った仕事が無くなるまで待機してもどるわけだ。その途中で生成されたワークオブジェクトは処理が終了すると自動的に開放されるけど、最初に作ったものは自動的に開放されないので最後に手動で開放する。

  LxResult
CMandelbrotFilter::imf_Generate (
  int width,
  int height,
  ILxUnknownID monitor,
  void **ppvObj)
{

         :

  /*
  * Unless we have a valid buffer we compute it in threads. We allocate a
  * work object, initialize it to the full image and process it.
  */
  if (gen_buffer) {
    CLxUser_ThreadService mt;
    CMandlebrotWork *work;
    LXtObjectID workObj;

    work = pF->work.Alloc (&workObj);
    work->src_filt = this;
    work->i_y = 0;
    work->n_y = i_h;

    max_log = log ((double) i_max);

    mt.ProcessShared ((ILxUnknownID) workObj);
    lx::ObjRelease (workObj);
  }

         :

}

以上がマンデルブロープラグインのコードの自分の解釈だ。

暑くなって来たので今年も来週から2週間は夏休みにするよ。期間中は不定期更新だ。

それではまた、夏休み明けに。

カテゴリー別ページ




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

2011年07月28日

max script 勉強してみた その4 3dsmax 2012

前回はパス名リテラルによってオブジェクトを指し示したり、ワイルドカードを使ってオブジェクトの集合であるオブジェクトセットを示したりする方法について調べてみた。今回はその続き。

オブジェクトはリンクすることで階層構造に出来る。パス名リテラルではこの階層構造を使ってオブジェクトを示すことも出来る。

例えば下のように15個のBOXを作って名前がつけてある。

fig01

そしてこれらは下のようにRootからリンクされた構造にしてある。

fig02

こんな場合でも単純にオブジェクトの名前を指定すれば、マッチするオブジェクトをパス名リテラルで指定する事ができる。例えばオブジェクト「b」はユニークな名前なのでselectメソッドで選択可能だ。

select $b

オブジェクト「b」が選択された。

fig03

同じ名前のオブジェクトがある場合は最初にマッチしたオブジェクトが指定されるのも一緒。

select $child02

一番左のオブジェクト「child02」1つが選択された。

fig04

ワイルドカードを使うとマッチしたもの全てを含むオブジェクトセットを指定できる。

select $grandchild01*

オブジェクト「grandchild01」が全て選択された。

fig05

このように階層とは無関係に名前を指定すればダイレクトにマッチング検索がなされるわけだけど、階層を使うことで絞り込む事が出来る。例えばオブジェクト「a」にリンクされたオブジェクト「child02」はおおもとの「root」から辿って、

$root/a/child02

としてやれば指定する事が出来る。selectで選択すると、

select $root/a/child02

を実行した結果。

fig06

単純に考えて「$a」とすればオブジェクト「a」が指定できるんだから、「$a/child02」で行けそうな感じなんだけどうまく行かない。

$a/child02
undefined

となってしまう。しかしワイルドカードが入ると階層の部分的なマッチングで検索されるので、

$a/child02*
$$objects/.../a/child02*

のようにオブジェクトが指定できる。選択してみると、

select $a/child02*

aにリンクされたchild02が選択される。

fig06

この場合、aはユニークな名前だったので、その下の1つしかないchild02が選択されたけど、child02自体は複数あるので、これでマッチングすれば、

select $child02/grandchild01*

複数箇所のオブジェクトが指定されることになる。

fig07

もちろんマッチングの範囲を広げれば、指定範囲を狭める事が出来るし、

select $a/child02/grandchild01*

fig08

ワイルドカードを使わなければ完全に一致するオブジェクト1つだけが指定されることになる。

select $root/a/child02/grandchild01


fig09

ワイルドカードは階層の途中で使うことも出来て、

select $b/*/grandchild02

root/b/child01/grand02とroot/b/child02/grand02にマッチングしている。

fig10

また、階層が何段階もある場合に、リンクの任意の階層に対してマッチングさせたい場合は「...」を使う事が出来る。

$root/.../child01

または

$root...child01

こうすればrootから繋がるリンク上のchild01と名前のつくオブジェクトを全て指定できる。例えばgrandchild01のひとつをchild01に変更して以下の選択をしてみると、

select $a...child01

aにリンクする下の階層全体の中からchild01にマッチする名前が全て選択される。

fig11

これをもし、「/*/」でやったら、

select $root/*/child01

fig12

select $root/*/*/child01

fig13

となって、階層が何段階であるかのマッチングもなされてしまうので、上のようにはならない。

この階層による指定をする場合、オブジェクトセットを検索の出発点にする事も出来る。オブジェクトセット名としては以下のものがある。

  • objects -- すべてのオブジェクト
  • geometry -- 標準の 3ds Max カテゴリ
  • lights
  • cameras
  • helpers
  • shapes
  • systems
  • spacewarps
  • selection -- 現在の選択

それぞれのオブジェクトセットから目的のオブジェクトまでの階層はまちまちなので、「...」やワイルドカードを使うとうまく指定できる。

例えばライトで名前が「Omni001」を指定したければ、

$lights...Omni001

としてやればいい。現在選択しているオブジェクトの中からgrandchild01を選択したいなんて時は、

select $selection...grandchild01

でOKだ。例えばこのように3つのオブジェクトが選択されている状態で、

fig15

select $selection...grandchild01

とすると、選択されていたオブジェクトの中から該当するものが選択された。もし該当するものが無ければ選択は変わらない。

fig16

下のように名前だけ指定した場合は最初にマッチしたオブジェクトが選択される。これが上の結果と異なる事からも偶然選択内のオブジェクトが最初に見つかって選ばれたわけじゃなくて、ちゃんと選択セット内から検査されているのがわかる。

select $grandchild01 

fig14

それではまた次回。

maxまとめページ



take_z_ultima at 12:13|この記事のURLComments(2)TrackBack(0)3ds Max | CG

2011年07月27日

modo501 SDK いじってみた その29 modo501 SP2

さて、今回は最後に残ったマンデルブロー集合を計算してる部分だ。この集合は以前に書いた通り横軸を実数縦軸を虚数とした平面空間のある点Cに対して次の漸化式がとる収束値の集合で、Cの座標ごとに計算が独立している。

n+1=Z+C

=0

こういう全て独立した計算はマルチコアCPUを使って並列計算するのに適している。今回取り上げているmandelbrotプラグインでもその性質をうまく利用して計算効率を上げるために集合の計算はスレッドに分割して並列処理するようになっている。詳しくはlx_thread.htmにいろいろ書いてあるみたいだけど、今回はこのコードで使われているマルチスレッド実現のための手順を追って見たい。

まず最初に必要なのはSharedWorkインターフェースを実装したオブジェクトを生み出すファクトリーだ。これは何度も出てきたファクトリーを集めたCFactoriesクラスにworkという名前で宣言されている。

class CFactories {
  public:

        :

    CLxPolymorph<CMandlebrotWork> work;

    CFactories ()
    {

        :

      work.AddInterface (
        new CLxIfc_SharedWork
            <CMandlebrotWork>);
    }

} *pF;

ファクトリーの方でCLxIfc_SharedWorkインターフェースを追加したので、テンプレートに割り当てるCMandlebrotWorkクラスにはその実装クラスであるCLxImpl_SharedWorkを継承する。

class CMandlebrotWork :
  public CLxImpl_SharedWork
{
  public:
    CMandelbrotFilter *src_filt;
    int i_y, n_y;

    LxResult share_Evaluate ();
    LxResult share_Spawn (void **ppvObj);
    LxResult share_Share (ILxUnknownID other,
                    unsigned int split);

    float Pixel (double x, double y);
};

実装するメソッドはshare_Evaluate ( )、share_Spawn ( )、share_Share ( )の3つだ。

share_Evaluate ( )メソッドには1つのスレッドに割り当てられて並列に処理されるプログラムを実装する。 今回のプログラムは矩形の画像を生成するのが目的で、そのサイズはsize.Xチャンネルとsize.Yチャンネルによって決まる。画像は縦横に並ぶピクセルの集まりで、マンデルブロー集合の計算はそのピクセルを虚数空間に割り当てて計算される。そこで、ピクセル座標から虚数空間座標への変換が必要となり、下のプログラムでは

v = i_y / (double) src_filt->i_h;
u = x / (double) src_filt->i_w;

の2つの式によってまずピクセル座標(x , i_y)から画像の矩形領域を2次元のUV座標の原点(0,0)から正の方向に1X1の正方形の形状にマッピングした座標系に変換している。だからに(u , v)の範囲はそれぞれ0〜1の間だ。この変換で使っている src_filt->i_w と src_filt->i_h にはsize.Xチャンネルとsize.Yチャンネルから読み込まれた値がセットされていて、変数xは0からsrc_filt->i_wまで変化するのでuは0〜1まで変化する。その時i_yは一定の値を保つので、(u,v)座標はvを固定値としてuが0〜1まで変化する横1列のピクセルを表わすことになり、i_yはそのピクセルの列が矩形の画像の何行目にあたるかを示すことになる。
以上のことから下のコードは生成する画像内のi_yが示す1行のピクセル列のマンデルブロー計算をするものだとわかる。1つのピクセルに対するマンデルブローの計算はPixel(u,v)で行われ、その結果がf_bufに格納される。f_bufは生成する画像のピクセル数と同じ数の配列要素数で、要素番号をi_y * src_filt->i_w + xとすることで、各ピクセルが1つの要素にマッピングされている。ちなみLXSDK_41321ではここがi_hになっていたけどi_wの間違いだ。にこれは例えば横5縦3のマス目を横に一行ごとに分割して5×1のマスを横に3つ並べて15×1マスにしたとき、元のマス目で横3、縦2のマスは、横に一行に並べたマスでは(2−1)×5+3=8で8マス目になるという計算と同じだ。ただし今回の計算の場合最初の行は0行目なので2−1のように1を引いていないよ。

  LxResult
CMandlebrotWork::share_Evaluate ()
{
  double u, v;

  if (n_y <= 0)
    return LXe_FALSE;

  v = i_y / (double) src_filt->i_h;
  for (int x = 0; x < src_filt->i_w; x++) {
    u = x / (double) src_filt->i_w;

    src_filt->f_buf[i_y * src_filt->i_w + x] = Pixel (u, v);
  }

  i_y ++;
  n_y --;
  return LXe_TRUE;
}

1行ぶんの処理が終わると、変数i_yは1増加され、変数n_yは1減少する。ここでやっかいなのは、i_yもn_yも複数生成されるワークオブジェクト個々に存在するってことだ。そしてこの増加と減少の処理はshare_Evaluate ( )とshare_Share ( )の両方に存在する。マニュアルに詳しい事は書かれていないし、確かめたわけじゃないけど、share_Spawn ( ) も share_Share ( )も最初に生成されてProcessShared ( )に引き渡されるワークオブジェクトのものが呼び出されるんじゃないかと思う。一応(getting more from the main shared work object)という記述はある。なので最初に生成されるワークオブジェクトと、その後に生成されて別のスレッドに割り当てられるワークオブジェクトでこれらの変数の役割が異なってくる。後から生成されるワークオブジェクトではn_yはshare_Spawn ( ) で0に設定され、share_Share ( )で1に、share_Evaluate ()の最後でまた0に戻る。そしてshare_Evaluate ()の最初でn_yが0以下なら処理をスキップしてLXe_FALSEを返すようになっている。要するにn_yは処理のためのスイッチとして機能している。それに対して最初に生成されるワークオブジェクトではn_yは初期値がsrc_filt->i_hでshare_Share ( )を呼び出すたびに1ずつ減少し、1以下になったらshare_Share ( )がLXe_FALSEを返して処理を終了する。これはn_yが現在残っている未処理の行数を表わしていて、残り1で終了って事は、自分自身もshare_Evaluate ()を実行するスレッドの1であるって事のようだ。変数i_yはワークオブジェクトごとにshare_Share ( )でどの行を処理するか割り当てていて、これも最初に生成したワークオブジェクトのメンバー変数がカウンターとして使われてshare_Share ( )が呼び出されるごとに1ずつ増加するようになっている。そしてその最初に生成されるワークオブジェクトのshare_Evaluate ()でも1増加させているので、なんとなくタイミングが微妙な気もするけど、最初のワークオブジェクトと次に生成されるワークオブジェクトで同じ行を処理しないようになっているようだ。

ついでに書いておくと、前回やった CMandelbrotFilter::imf_Generate ( )メソッドの最後の部分もLXSDK_41321では間違っているのでf_bufから計算結果を拾い上げる部分を修正する必要があるよ(太字のところ)。

/*
* Final pass converts the raw buffered values to colors. This could be
* done in the workers too, since setting pixels should be thread-safe.
*/
LXtImageFloat pixel[4];
unsigned x, y, k;
double d;

for (y = 0; y < i_h; y++) {
  for (x = 0; x < i_w; x++) {
    d = f_buf[y * i_w + x];
    if (d < 0.0)
      for (k = 0; k < 4; k++)
        pixel[k] = s_col[k];
    else
      for (k = 0; k < 4; k++)
        pixel[k] = g_filt[k].Evaluate (d);

    w_img.SetPixel (x, y, LXiIMP_RGBAFP, pixel);
  }
}

share_Spawn ( )はファクトリーを使ってワークオブジェクトを生成初期化するコードを実装する。work.Alloc (ppvObj)でppvObjに生成したオブジェクトを割り当て、*work = *thisでこのメソッドを呼び出しているワークオブジェクトのメンバーを新しく作ったワークオブジェクトにコピーする。このthisにあたるワークオブジェクトはおそらく後で出てくるProcessShared ( )に引き渡すオブジェクトであろう。だからこのメソッドを実行するたびにワークオブジェクトの複製が1つずつ生成されてppvObjで呼び出し元に戻されることになる。このワークオブジェクトがいくつ作られるのかはマニュアルに

This will spawn enough of the shared work objects to populate the available computing resources.

と書かれているだけなのでいくつになるかは不明だ。一応

getting more from the main shared work object when they are empty.

と書いてあるので足りなくなったら追加で作るって感じなのかな。

  LxResult
CMandlebrotWork::share_Spawn (
  void **ppvObj)
{
  CMandlebrotWork *work;

  work = pF->work.Alloc (ppvObj);
  *work = *this;

  work->i_y = 0;
  work->n_y = 0;
  return LXe_OK;
}

share_Share ( )は他のワークオブジェクトへ移動して値を設定したりといった作業をしていくためのメソッド。ここで他のワークオブジェクトのi_yをセットしてn_yを1にすることでそのワークオブジェクトのshare_Evaluate ()が処理をスキップしなくなって指定したi_y行の1回だけ処理することになる(処理の最後でi_yは0に戻る)。このshare_Share ( )を処理する行がなくなるまで繰り返し呼び出せば、マルチスレッドで動く複数のワークオブジェクトのshare_Evaluate ()に1行ごとの処理を割り振って行けて、最終的にLXe_FALSEを返せば、もう処理する行が残っていないことになり、各ワークオブジェクトのshare_Evaluate ()の終了待ちになるんだろうね。詳しい動作は書いて無いからよくわからないけど。

  LxResult
CMandlebrotWork::share_Share (
  ILxUnknownID other,
  unsigned int split)
{
  CMandlebrotWork *take = pF->work.Cast (other);

  if (n_y <= 1)
    return LXe_FALSE;

  take->i_y = i_y;
  take->n_y = 1;

  i_y ++;
  n_y --;
  return LXe_TRUE;
}

で、それらをコントロールしているのがCLxUser_ThreadServiceのProcessShared()メソッドだ。

長くなったので続きはまた次回。

カテゴリー別ページ



take_z_ultima at 12:53|この記事のURLComments(0)TrackBack(0)modo | CG

2011年07月26日

max script 勉強してみた その3 3dsmax 2012

前回に引き続きパス名リテラルの話だ。前回は文法的な面からパスリテラルの書き方を調べてみた。今回は機能面の話。

まずテストのためにこんなシーンを作ってみた。

fig01

オブジェクトどうしにペアレント関係は無い。この状態でBox001をパス名リテラルで表わすには

$Box001

と、「$」の後ろに続けてオブジェクト名を書いてやればいい。MAXScriptリスナーパネルで実行してみるとこのようになる(大文字小文字の区別はしないので小文字で入力してるよ)。

$box001
$Box:Box001 @ [-23.166307,17.274023,0.000000]

このオブジェクトを選択したいなら、BOXオブジェクトはノードクラスのサブクラスなのでノードクラスの「select」メソッドが使える。

select $box001

これを実行するとBox001が選択される。

fig02

ちなみにノードに対するメソッドのリファレンスは

MAXScript 言語リファレンス > 3ds Max オブジェクト > ノード : MAXWrapper > ノードの共通プロパティ、演算子、メソッド > ノードの共通プロパティ、演算子、メソッド

にあるよ。例えばそこにはこんなのが載っている。

move <node> <point3> -- mapped

これはノードを移動させるメソッド。<node>のところにノードクラスを継承したオブジェクトを指定して、<point3>のところにpoint3型の移動距離を表わすデータを指定すればその距離だけノードを移動させることが出来る。mappedはこのメソッドの効果をノードを集めたグループに対して一挙に適用させる事が出来る事を表わしている。

これを使ってBox002を上に10単位動かすにはこんなふうにすればいい。ちなみに[ X座標値,Y座標値,Z座標値 ]と書けばpoint3リテラルになる。

move $box002 [0,0,10]

これがその結果。Box002がZ方向に10移動した。Box001は直前の操作で選択したからBox002を移動しても選択されたままだ。

fig03

ここでちょっと試しにBox003を選択して、モディファイヤパネルに表示される名前をBox001に書き換えてみた。

fig04

Hキーを押して「シーンから選択」パネルを出してみると、Box001という名前が2つ並んでいるのがわかる。確認したらキャンセルを押してパネルを閉じる。

fig05

さてこの状態で$Box001を指定すると何が指定されるんだろうか?ビューポートの任意の位置をクリックして選択を解除しておいて、再びselectメソッドを実行してみた。

select $box001

結果はこのようになった。

fig03

Box001という名前のオブジェクトは2つあるけど、先に作成された方のオブジェクトが選択状態になった。とにかく先に見つかったオブジェクトが選ばれるそうだ。これを見ると名前でオブジェクトを選ぶのは危険だね。

パス名リテラルの<level_name>には、ワイルドカードと呼ばれる特殊な文字「*」と「?」が使える。ワイルドカードとは、名前のマッチングをあいまいにしてマッチングの幅を広げる役割をする文字だ。例えば今シーンに存在するBoxの名前は全て「Box〜」という書式になっている。この「〜」を文法的に表現したのが「*」で、

$Box*

と書けば、「*」の部分に任意の文字列(0文字でも良い)を含む名前がすべてマッチングすることになる。

例えば選択してみると、

select $box*

これがその結果。Box001、Box002、Box003全てが選択されている。

fig06

「?」は任意の1文字の代用で、「*」の場合はできない文字数の指定が可能になる。例えば頭文字が任意の3文字で、その後ろに002が付く名前は

$???002

となる。これで選択してみると、

select $???002

マッチするのはBox002だけなのでこうなる。

fig07

もし「?」のかわりに「*」を使ったら、

select $*002

Box002とSphere002のどちらも「〜002」のパターンにマッチしてしまうのでこのようになる。

fig14

任意の9文字なら?を9個並べればいい。

select $?????????

これにマッチするのはSphere001、Sphere002、Sphere003になる。

fig08

?と*の両方を使うと

select $??????*

なら6文字以上の名前が全てマッチするのでBoxもSphereも両方選択されるけど、

fig15

select $???????*

なら7文字以上の名前が全てマッチするのでSphereの方だけが選択されることになる。

fig16

ちなみにBox001が2つあるけど、

select $Box001*

とすると、*は0文字も含まれるので、2つのオブジェクトがマッチして選択される。

fig09

このようにワイルドカードを使うと名前にマッチする複数のオブジェクトを指定することが出来る。この複数のオブジェクトの集まりをオブジェクトセットと呼ぶ。

パス名リテラルはオブジェクトだけじゃなくてオブジェクトセットも表現出来るわけだ。

そしてmoveやselectのようにマニュアルにmappedという表記があるものは、このオブジェクトセットに対して一気に処理することが出来る。

move $sphere* [0,0,20]

としてやれば、3つのオブジェクトがそれぞれ上に20移動することになる。

fig10

オブジェクトセットについてはマニュアルの

MAXScript 言語リファレンス > コレクション > コレクションのタイプ > ObjectSet 値 

を見るといろいろ書いてあるけど、演算子の[ ]なんかは使えそうだな。

< objectset >[<integer>]

<integer>がオブジェクトセット内にある要素の1から始まる通し番号で、この演算子を使うと、オブジェクトセットの中の要素を1つ取り出せる。例えば、

$Box001*

とした場合、Box001が2つあったから2つのオブジェクトが指定されちゃったけど、

$Box001*[1] または $Box001*[2]

とすれば、2つのうちの1つが指定できる。

select $box001*[1] の結果。1つのBox001が選択された。

fig01

select $box001*[2] の結果。もう一方のBox001が選択された。

fig12

それではまた次回。

maxまとめページ



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

2011年07月25日

modo501 SDK いじってみた その28 modo501 SP2

前回後回しにした:imf_Generate( )メソッドから今回は見ていきたい。このメソッドはイメージクリップとしてmodoに渡す画像生成をする部分だ。だからここさえ書き換えれば、いろんな画像生成プラグインが作成可能になる。コードが長いので区切りながら見て行く。

まずは引数。見ての通り幅と高さを表わすwidthとheightが渡されている。しかし今回これは全く使用されていない。画像のピクセルサイズを決めているのはチャンネルのsize.xとsize.yで、それはフィルターをアロケートする時にi_wとi_hに読み込まれている。次のmonitorはプログレッシブバーを出して進捗状況を表示する目的かな?あちこちで散見するけどまだ具体的に使われている部分に遭遇していない。とりあえず今回はパス。次のppvObjは呼び出し側にオブジェクトを引き渡すものだね。今回は生成した画像を引き渡すことになる。

  LxResult
CMandelbrotFilter::imf_Generate (
  int width,
  int height,
  ILxUnknownID monitor,
  void **ppvObj)
{

次のブロックはイメージを生成するための領域確保をしている。まずはif文で領域確保の必要があるかどうかを調べている。条件はw_img.test()がfalseを返すか、use_imageがfalseの場合だ。w_imgはCLxUser_ImageWriteでImageWriteインターフェースのユーザーラッパークラスだ。この変数はCMandelbrotFilterのメンバー変数として宣言されているだけなので、フィルターが生成された時に引数なしのコンストラクタで生成される。CLxUser_ImageWriteのコンストラクタを辿って行くとこのインターフェースは操作対象となるImageオブジェクトからImageWriteインターフェースを取得して動作するものなので、それが割り当てられる前では動作できない。test()はその割り当てが済んでいるかどうかを調べるもので、引数なしのコンストラクタで生成されたばかりのオブジェクトは未割り当てなのでfalseを返す。falseなら!で論理が否定されているからifの条件はtrueになる。2つの条件は||で論理和がとられているので片方がtrueなら全体もtrueだ。use_imageはフィルターをアロケートする時にfalseがセットされている。そしてfilt_Convert( )内でtrueが割り当てられている。結局、このif文の条件がtrueにならないのは、w_imgがすでにimage_writeインターフェースを持つイメージオブジェクトをセット済みでかつfilt_Convert( )で変換されている時だけという事になる。

それ以外の時はイメージインターフェースのユーザーラッパークラスのCLxUser_Imageオブジェクトを作って、そこにCMandelbrotPackageのメンバーのCLxUser_ImageService img_svcのNew( )メソッドを使って横i_wピクセル、縦i_hピクセル、ピクセルタイプがLXiIMP_RGBA32のイメージオブジェクトを生成してimgに割り当てる。そしてそれをw_imgにset( )で割り当てて、このイメージオブジェクトに対して書き込む手段を手に入れる。

次にf_buf配列が割り当てられていたら開放してnullにする。この配列はマンデルブロー集合を計算した結果を格納するために後で使うものだ。

/*
* Allocate an image if we need one. We'll hold on to the writable one.
*/
  if (!w_img.test () || !use_image) {
    CLxUser_Image img;
    src_pkg->img_svc.New (img, i_w, i_h, LXiIMP_RGBA32);
    w_img.set (img);

    if (f_buf) {
      delete[] f_buf;
      f_buf = 0;
    }
  }

次のブロックはそのf_bufのメモリーの割り当てをしている。イメージオブジェクトのピクセルの個数と同じだけのfloatのメモリーを確保している。

/*
* Allocate the gradient input buffer.
*/
  if (!f_buf) {
    f_buf = new float[i_w * i_h];
    if (!f_buf)
      return LXe_OUTOFMEMORY;
  }

次のブロックはマルチスレッドの仕組みを使って1行ぶんのマンデルブローの計算を1つのスレッドに割り当てて行き、全ての行を異なるスレッドで並列に計算させている。詳しくは次回に譲るけど、SharedWorkインターフェースを実装したワークオブジェクトをThreadServiceのProcessShared( )メソッドに与えると、実装されたspawn( )が呼び出されてオブジェクトが複製され、それがShare( )メソッドに渡されてスレッドごとに異なる条件がセットされ、新しく生成されたスレッド上でEvaluate ( )が実行される。この繰り返しをShare( )メソッドがfalseを返すまで続ける。するとこの繰り返しの回数ぶんのスレッドが並列に走ることになる。スレッドは終了するごとに生成されたオブジェクトは開放され、最初に作ったオブジェクトだけが残るのでこれをリリースして開放する。

/*
* Unless we have a valid buffer we compute it in threads. We allocate a
* work object, initialize it to the full image and process it.
*/
  if (gen_buffer) {
    CLxUser_ThreadService mt;
    CMandlebrotWork *work;
    LXtObjectID workObj;

    work = pF->work.Alloc (&workObj);
    work->src_filt = this;
    work->i_y = 0;
    work->n_y = i_h;

    max_log = log ((double) i_max);

    mt.ProcessShared ((ILxUnknownID) workObj);
    lx::ObjRelease (workObj);
  }

最後のブロックは計算で得られた結果(f_bufに順番に入っている)を色に変換し、その色をSetPixel( )メソッドでイメージオブジェクトに書き出している。 色の変換方法は、計算結果がマイナスの場合はsetColorチャンネルのRGBA値を使い、計算結果がプラスの時はその値をグラディエントのRGBA各チャンネルの入力値としてEvaluate( )メソッドで出力値を算出してRGBA値としている。それを全てのピクセルに対して行ってるだけだ。最後に生成されたイメージオブジェクトをppvObjに割り当ててそれが成功したかどうかをメソッドの戻り値として返している。

/*
* Final pass converts the raw buffered values to colors. This could be
* done in the workers too, since setting pixels should be thread-safe.
*/
  LXtImageFloat pixel[4];
  unsigned x, y, k;
  double d;

  for (y = 0; y < i_h; y++) {
    for (x = 0; x < i_w; x++) {
      d = f_buf[y * i_h + x];
      if (d < 0.0)
        for (k = 0; k < 4; k++)
          pixel[k] = s_col[k];
      else
        for (k = 0; k < 4; k++)
          pixel[k] = g_filt[k].Evaluate (d);

      w_img.SetPixel (x, y, LXiIMP_RGBAFP, pixel);
    }
  }

  return w_img.get (ppvObj);
}

それではまた次回。

カテゴリー別ページ



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