« R11SDKのtag()関数を使用して、選択された(ポイント/ポリゴン)選択範囲タグで選択されたエレメントにヌルオブジェクトを配置するスクリプトです。 | トップページ | C4D テキストダイアログ »

C4D C.O.F.F.E.E.でポリゴンの座標を算出する関数について、考えてみる

任意のオブジェクトにヌルオブジェクトを配置するスクリプト関連の記事を読んでいただいたC.O.F.F.E.E.の学習を始めたばかりの方へ、ありがとうございます。
スクリプト自体は実用的ではないのですが、複数選択されたオブジェクトの簡単な処理がわかっていただけたと思います。

ただし、関数を作らずmain()関数のみで組んであります。

なぜ村人とC4Dでは、関数をあまり使わないのかは、後ほど…
関数を作る能力が無いから…が第一の理由。ま、もっと重要な事があるんですけどね。

では、このスクリプトの中で、ポリゴンの座標を算出する部分があります。
ここを関数にすると、他のスクリプトに関数をコピペすると簡単に使うことができますね。

では、ポリゴンの座標を算出する関数を考えてみよう。

ポリゴンの座標は、ポリゴンを構成するポイント座標の平均と仮定します。

関数にするのですから、関数に渡すパラメータが必要ですね。
何を渡しますか?

ポリゴンの4点を渡しますか。
それとも、ポリゴンですか?ポリゴンだと、ポイント番号のみしか記憶していないから、却下ですね。

ポリゴン配列とポリゴン番号を渡しますか?
これも却下。ポリゴン配列にはポイント番号しか記憶されていません。
そうすると、ポイント配列も渡す必要があります。

ポリゴンオブジェクトとポリゴン番号だろうか?

一度作った関数を使う場合を考えると、一番簡単に使えるのは、ポリゴンオブジェクトと番号でしょうね。

では、考えられる関数を作ってみましょう。

まずは、4点を渡してみましょう。

/*
4点を渡し平均を返す関数。
2009.6.1
*/


GetPolygonPosition(pa , pb , pc , pd)
{
  var pos;

  if(pc == pd){//三角ポリゴン
    pos = (pa + pb + pc) / 3.0;
  }else{       //四角ポリゴン
    pos = (pa + pb + pc + pd) / 4.0;
  }

  return pos;
}

この関数は、ポリゴンの座標を算出するために作ったのですが…
4つの変数を渡し3個目と4個目を比べて同じだったら1,2,3の平均。
3番目と4番目が違うと1,2,3,4の全ての平均。

でも4つの変数はベクトルに限定していないので、整数、実数、ベクトル、テキスト、それとnil、が混在しても渡せてしまいます。そうするとエラーが出る事があります。

使う時には注意が必要です。

もし変数がベクトル型かチェックするのであれば…

/*
4点を渡し平均を返す関数。
2009.6.1
*/


GetPolygonPosition(pa , pb , pc , pd)
{
  if(typeof(pa) != DT_VECTOR
    || typeof(pb) != DT_VECTOR
      || typeof(pc) != DT_VECTOR
        || typeof(pd) != DT_VECTOR)return;

  var pos;

  if(pc == pd){//三角ポリゴン
    pos = (pa + pb + pc) / 3.0;
  }else{       //四角ポリゴン
    pos = (pa + pb + pc + pd) / 4.0;
  }

  return pos;
}

渡された変数が、ベクトル型でなければ処理をする事なく関数から出ます。

この関数を使うときは、使う前にポリゴンから4つのポイント番号を取得し、その4つのポイント番号からポイントの座標を取得し関数へ渡します。
関数から返ってきた値をチェックし、nilだと渡した値に問題があると言う事になります。

ポリゴンとポイント配列では。

/*
ポリゴンとポイント配列を渡してポリゴンの座標を取得する
2009.6.1
*/


GetPolygonPosition(poly , pts)
{
  if(!instanceof(poly , Polygon)
    || typeof(pts) != DT_ARRAY)return;

  var pos;

  if(poly->c == poly->d){
    pos = (pts[poly->a]
          + pts[poly->b]
             + pts[poly->c]) / 3.0;
  }else{
    pos = (pts[poly->a]
          + pts[poly->b]
             + pts[poly->c]
              + pts[poly->d]) / 4.0;
  }

  return pos;
}

ポリゴンを構成しているポイント座標の平均なので、コード構成の違いは大差がありません。

一応、ポイント配列の型チェックはしているものの、配列かどうかチェックしているだけなので、配列の中に記憶されている数値の型が違えばエラーが出ることでしょう。

間違えてポリゴン配列を渡さないように。

ポリゴン番号とポリゴン配列、ポイント配列を渡す場合は…あまり大差がないので必要はないですし、気が進まないのですが…

/*
ポリゴン番号とポリゴン配列とポイント配列を渡してポリゴンの座標を取得する
2009.6.1
*/


GetPolygonPosition(pl_num , pls , pts)
{
  //変数の型チェック
  if(typeof(pl_num) != DT_LONG
    || typeof(pls) != DT_ARRAY
      || typeof(pts) != DT_ARRAY)return;

  var pos;
  var pl_idx = pl_num * 4;

  //ポリゴン番号の範囲内チェック
  if(pl_idx < 0 || pl_idx >= sizeof(pls))return;

  if((pls[pl_idx + 2]) == (pls[pl_idx + 3])){
    pos = (pts[pls[pl_idx]]
          + pts[pls[pl_idx + 1]]
             + pts[pls[pl_idx + 2]]) / 3.0;
  }else{
    pos = (pts[pls[pl_idx]]
          + pts[pls[pl_idx + 1]]
             + pts[pls[pl_idx + 2]]
              + pts[pls[pl_idx + 3]]) / 4.0;
  }

  return pos;
}

この場合の変数の型チェックも配列かどうかなので、ポリゴン配列とポイント配列を入れ違うとエラーが出ますね。

ポリゴン番号がポリゴン配列のインデックス数の範囲内かチェックする必要があります。
ただし、ポリゴン配列はシーケンシャルでポイント番号を記憶しているので、ポリゴン番号を4倍にしなくてはならない事を忘れないように。

同様にポリゴン配列からポイント番号を取得する場合も、ポリゴンを形成するa,b,c,dは、ポリゴン番号×4、ポリゴン番号×4+1、ポリゴン番号×4+2、ポリゴン番号×4+3です。

では、最後にポリゴンオブジェクトとポリゴン番号を渡してみましょう。

/*
ポリゴンオブジェクトとポリゴン番号を渡してポリゴン座標を取得
2009.6.1
*/


GetPolygonPosition(ply_obj , ply_num)
{
  //変数型チェック
  if(!instanceof(ply_obj , PolygonObject))return;
  if(typeof(ply_num) != DT_LONG)return;

  //ポリゴン番号の範囲内チェック
  var ply_c = ply_obj->GetPolygonCount();//ポリゴン数の取得
  if(ply_num < 0 || ply_num >= ply_c)return;

  var pls = ply_obj->GetPolygons();//ポリゴン配列の取得
  var pts = ply_obj->GetPoints();  //ポイント配列の取得

  var pos;
  var pl_idx = ply_num * 4;

  //ポリゴン座標の算出
  if((pls[pl_idx + 2]) == (pls[pl_idx + 3])){
    pos = (pts[pls[pl_idx]]
          + pts[pls[pl_idx + 1]]
             + pts[pls[pl_idx + 2]]) / 3.0;
  }else{
    pos = (pts[pls[pl_idx]]
          + pts[pls[pl_idx + 1]]
             + pts[pls[pl_idx + 2]]
              + pts[pls[pl_idx + 3]]) / 4.0;
  }

  return pos;
}

同じ目的の4個の関数ができました。
実際に使うとすると、どれを使いますか?

使い勝手が良いのは、最後のポリゴンオブジェクトとポリゴン番号を渡す関数でしょう。

それでは、最後に大切な関数の処理時間です。
3DCGなので大量のポリゴンやポイントを処理します。
C.O.F.F.E.E.は実行速度が速くないので、必要以上に遅くならないように、気を配らなければなりません。

/*
ポリゴンの座標を返す4種類の関数を試す
2009.6.1
*/


//4点を渡す------------------------------------------------
GetPolygonPositionTypeA(pa , pb , pc , pd)
{
  if(typeof(pa) != DT_VECTOR
    || typeof(pb) != DT_VECTOR
      || typeof(pc) != DT_VECTOR
        || typeof(pd) != DT_VECTOR)return;

  var pos;

  if(pc == pd){//三角ポリゴン
    pos = (pa + pb + pc) / 3.0;
  }else{       //四角ポリゴン
    pos = (pa + pb + pc + pd) / 4.0;
  }

  return pos;
}


//ポリゴンとポイント配列を渡す---------------------------------
GetPolygonPositionTypeB(poly , pts)
{
  if(!instanceof(poly , Polygon)
    || typeof(pts) != DT_ARRAY)return;

  var pos;

  if(poly->c == poly->d){
    pos = (pts[poly->a]
          + pts[poly->b]
             + pts[poly->c]) / 3.0;
  }else{
    pos = (pts[poly->a]
          + pts[poly->b]
             + pts[poly->c]
             
+ pts[poly->d]) / 4.0;
  }

  return pos;
}


//ポリゴン番号とポリゴン配列、ポイント配列を渡す-------------------
GetPolygonPositionTypeC(pl_num , pls , pts)
{
  //変数の型チェック
  if(typeof(pl_num) != DT_LONG
    || typeof(pls) != DT_ARRAY
      || typeof(pts) != DT_ARRAY)return;

  var pos;
  var pl_idx = pl_num * 4;

  //ポリゴン番号の範囲内チェック
  if(pl_idx < 0 || pl_idx >= sizeof(pls))return;

  if((pls[pl_idx + 2]) == (pls[pl_idx + 3])){
    pos = (pts[pls[pl_idx]]
          + pts[pls[pl_idx + 1]]
             + pts[pls[pl_idx + 2]]) / 3.0;
  }else{
    pos = (pts[pls[pl_idx]]
          + pts[pls[pl_idx + 1]]
             + pts[pls[pl_idx + 2]]
              + pts[pls[pl_idx + 3]]) / 4.0;
  }

  return pos;
}


//ポリゴンオブジェクトとポリゴン番号を渡す------------------------
GetPolygonPositionTypeD(ply_obj , ply_num)
{
  //変数型チェック
  if(!instanceof(ply_obj , PolygonObject))return;
  if(typeof(ply_num) != DT_LONG)return;

  //ポリゴン番号の範囲内チェック
  var ply_c = ply_obj->GetPolygonCount();//ポリゴン数の取得
  if(ply_num < 0 || ply_num >= ply_c)return;

  var pls = ply_obj->GetPolygons();//ポリゴン配列の取得
  var pts = ply_obj->GetPoints();  //ポイント配列の取得

  var pos;
  var pl_idx = ply_num * 4;

  //ポリゴン座標の算出
  if((pls[pl_idx + 2]) == (pls[pl_idx + 3])){
    pos = (pts[pls[pl_idx]]
          + pts[pls[pl_idx + 1]]
             + pts[pls[pl_idx + 2]]) / 3.0;
  }else{
    pos = (pts[pls[pl_idx]]
          + pts[pls[pl_idx + 1]]
             + pts[pls[pl_idx + 2]]
              + pts[pls[pl_idx + 3]]) / 4.0;
  }

  return pos;
}


//メイン---------------------------------------------------
main(doc , op)
{
  var tm;
  var pg;
  var pg_c;
  var pt;
  var i;
  var a , b , c , d;
  var pgn;
  var pos;

//関数を使わない
  tm = time();
  pg = op->GetPolygons();
  pt = op->GetPoints();
  pg_c = op->GetPolygonCount();
  for(i = 0 ; i < pg_c ; i++){
    if(pg[i * 4 + 2] == pg[i * 4 + 3]){
      pos = (pt[pg[i * 4]]
             + pt[pg[i * 4 + 1]]
              + pt[pg[i * 4 + 2]]) / 3.0;
    }else{
      pos = (pt[pg[i * 4]]
             + pt[pg[i * 4 + 1]]
               + pt[pg[i * 4 + 2]]
                 + pt[pg[i * 4 + 3]]) / 4.0;
    }
  }
  println("non-function : " , time() - tm);



//ポイント4点を渡す
  tm = time();
  pg = op->GetPolygons();
  pt = op->GetPoints();
  pg_c = op->GetPolygonCount();
  for(i = 0 ; i < pg_c ; i++){
    a = pt[pg[i * 4]];
    b = pt[pg[i * 4 + 1]];
    c = pt[pg[i * 4 + 2]];
    d = pt[pg[i * 4 + 3]];
    pos = GetPolygonPositionTypeA(a , b , c , d);
  }
  println("4-point : " , time() - tm);



//ポリゴンとポイント配列を渡す
  tm = time();
  pg_c = op->GetPolygonCount();
  for(i = 0 ; i < pg_c ; i++){
    pgn = op->GetPolygon(i);
    pos = GetPolygonPositionTypeB(pgn , pt);
  }
  println("polygon point-array : " , time() - tm);



//ポリゴン番号とポリゴン配列、ポイント配列を渡す
  tm = time();
  pg = op->GetPolygons();
  pt = op->GetPoints();
  pg_c = op->GetPolygonCount();
  for(i = 0 ; i < pg_c ; i++){
    pos = GetPolygonPositionTypeC(i , pg , pt);
  }
  println("polygon-number polygon-array point-array : " , time() - tm);



//ポリゴンオブジェクトとポリゴン番号を渡す
  tm = time();
  pg_c = op->GetPolygonCount();
  for(i = 0 ; i < pg_c ; i++){
    pos = GetPolygonPositionTypeD(op , i);
  }
  println("polygon-object polygon-number : " , time() - tm);

}

なるべく不公平感が無いように、最低限必要な数値は再取得しました。

立方体6面を処理するなら、全く大差はありません。

では、平面オブジェクトを「横方向の分割」「高さ方向の分割」共に100で編集可能にした、ポリゴン数10000のポリゴンオブジェクトで試してみます。

劇的な数値が…フリーズしたのかと思った…

単位:ミリ秒 1 2 3 4 5 6 7 8 9 10 平均
関数なし 11 12 12 12 12 12 11 10 11 11 11.4
4点 21 20 21 19 19 21 20 20 21 19 20.1
ポリゴンとポイント配列 25 26 26 27 31 26 26 25 26 25 26.3
ポリゴン番号とポリゴン配列、ポイント配列 20 22 23 21 21 24 21 20 20 20 21.2
ポリゴンオブジェクトとポリゴン番号 14288 14354 14349 14351 14260 14325 14304 14325 14400 14445 14340.1

これを見てしまうと…
使い勝手が良いからと言って安易に関数を組み立てると…

さすがに関数を使わないで直接組み込まれていると、速いですね。
ポリゴンオブジェクトを渡す方法は、オブジェクトを渡したから遅いのではないですよ。
1枚のポリゴンの座標を取得する度、ポリゴン配列やポイント配列を再取得するのですからですね…それにしても。

何でもかんでも、関数にすれば良いと言う事では無いと言う事ですね。
何処が遅いのか。何故遅いのか。関数にするならどのレベルまでで関数にするのか…を考えなければいけませんね。

実行処理の事は、極最近取り上げたネタのGetPoint()等やfor()のスピードチェックと、今回の関数のスピードチェックで、なんとなく分かって頂けたと思います。

1回のみ実行型のメニュープラグイン等なら、少々遅くても問題は少ないけど、C.O.F.F.E.E.エクスプレッションやXPressoのC.O.F.F.E.E.ノードはイベントが起こるたび実行されるので、気を使わなければいけないね…

努力と工夫の限界がきたら、あとはC++へ移行しよう。

でも、Win/Mac両方動作可能なC.O.F.F.E.E.を捨ててしまうのは、惜しい気がします…

|

« R11SDKのtag()関数を使用して、選択された(ポイント/ポリゴン)選択範囲タグで選択されたエレメントにヌルオブジェクトを配置するスクリプトです。 | トップページ | C4D テキストダイアログ »