2011年11月

2011年11月30日

modo501 SDK いじってみた その65 modo501 SP4

COrbInstance::vitm_Draw ( ) メソッドの続きだ。

radiusチャンネルから変数radに得た値は次のコードで円筒を表現する時のメッシュの分割数の算出に使われている。

float scale = 1.0;
scale /= 40.0;
n = (int) (3.142 / sqrt (scale / rad));
if (n < 8)
  n = 8;
else if (n > 1200)
  n = 1200;

fig03
 

変数nに円筒の分割数が取得されるんだけど、その導出式が意味不明だ。特にscaleは1/40という定数をわざわざ計算している。これはもしかしたらチャンネルを作って設定が変えられるようにしようと思ってたのかもね。で、結局、式はどうなってるのかと言うと、最初に思いつくのがメッシュの幅を一定に保って分割する方法で、それには現在の円周の長さ(π×半径×2)をメッシュ1つぶんの幅である定数で割って、それを整数に丸めればいい。

n = (int)(3.142*rad*2/1メッシュ分の幅)

上のコード内の式とこの式を比べてみると、この式を半径の平方根で割ったものだとわかる。下のグラフは2つの式をグラフ化したもので、紺色の方が今回のコードで使われた式の結果だ。見てわかる通り、半径が増えても分割数の増加は適度に抑えられていい感じで増加していることがわかる。

fig01

じゃあってことで上の式を半径の平方根じゃなくて半径で割ったらどうなるかって言えば、

n = (int)(3.142*rad*2/1メッシュ分の幅/rad)
= (int)(3.142*2/1メッシュ分の幅)

ただの定数になっちゃうからね。こういう半径に対して分割数を決める時にこの式はなかなか使い勝手がいいかも知れないね。

結果の値nは8から1200までで制限されて、その範囲より多くも少なくもならないようになってるね。

これで円筒の分割数nが決まった。次にこの分割数を使って、円筒を構成するポイントの座標値が計算される。

fig02

それがこのコードで、 for文で変数 i が0〜分割数−1まで変化すると、変数angが0〜2πラジアンまで変化して、1周分の中心角が得られる。

for (i = 0; i < n; i++) {
  ang = 3.14159 * 2.0 * i / n;
  sn = sin (ang);
  cs = cos (ang);

  Point point;
  point.vec[0] = sn * rad;
  point.vec[1] = cs * rad;
  point.vec[2] = 0.5 * rad;
  points.push_back (point);

  point.vec[2] = -0.5 * rad;
  points.push_back (point);
}

得られた角度(コード上では変数angを使ってるけど図ではθであらわしているから読み替えてね)に対してsinとcosを求めると、半径1の円周上の中心角方向の点の座標値が得られる。これをrad倍すると、半径radの円周上の点の座標値が得られる。それを上のコードでは、cosの方をY座標値、sinの方をX座標値として使っている。

fig10

そしてZ座標値は-0.5*radと+0.5*radに固定しているので、描かれる円周はZ値が一定になる。

fig03

そして中心角が1ステップずつ進みながら2つのポイントの座標値が決定される。

fig04

座標値が格納されるPointは構造体で、以下のように定義されていて、

struct Point
{
  LXtVector vec;
};

内部にLXtVector型の変数vecを持っている。そしてこのLXtVectorは以下のように定義されていて、

typedef double LXtVector[LXdND];

double型の配列だ。 pointに設定した座標値はPointを格納するvectorコンテナ

std::vector<Point> points;

に収納される。Pointは構造体だからメソッドに渡す時にコピーが渡される。これが配列を構造体で包んである理由かなぁ。これをpush_back ( ) でvectorコンテナの末尾に追加する。

points.push_back (point);

コピーだからpush_back ( ) した後でpointを修正してもvectorに格納されたデータには影響が出ない。だから前に作ったコードはnewしたまま放置したのはまずかったな(修正しといたよ)。

vectorはpush_backされた順にデータが入っていくので、結果的にポイントは以下のような通し番号の位置に格納される。

fig05

最後はこれを呼び出して線を引いて行くコードだ。ループの部分で変数kが2つ跳びに増加し、0〜(分割数n−1)×2の間で変化する。

k = 0;
for (i = 0; i < n; i++) {

      :

  k += 2;
}

こうすることでkの値は常に上の画像の左側の円周上のポイントを指すようになる。

次に

LXx_VCPY (vert, points[k].vec);
strokeDraw.Vertex (vert, strokeFlags);
LXx_VCPY (vert, points[(k + 2) % (n * 2)].vec);
strokeDraw.Vertex (vert, strokeFlags);

とすることで、kがさすポイントと、k+2がさすポイントがvertにコピーされて、それがVertex ( ) メソッドに渡されて、BeginW ( )メソッドのタイプがLXiSTROKE_LINESだったので、2つのポイントが渡されるたびにその2点間に線が引かれることになる。

strokeDraw.BeginW (LXiSTROKE_LINES , itemColor, 1.0, lineWidth);

fig06

pointの添字がk+2じゃなくて(k + 2) % (n * 2)になっているのはkが(分割数n−1)×2になった時にk+2だとpointsにない番号になっちゃうのを防ぐためだ。こうしておくと、k+2の値は0になって、ポイントの番号がループするようになる。%は割り算の余りを求める演算子で、たとえばn%3をnを0から順に計算すると、

n%3
0 0
1 1
2 2
3 0
4 1
5 2
6 0

となって、0〜2の間で数値がループするのがわかる。これと同じ仕組みだね。

ところで何で一度、変数vertにコピーするんだろう? Vertex ( ) メソッドの宣言で、posがconstになっていないから、

void Vertex (LXtVector pos, int flags)

このメソッドに渡した時に中身が書き換えられてしまう恐れがある(書き換わらない事が保証されてない)って事だろうね。一応

strokeDraw.Vertex (points[k].vec, strokeFlags);
strokeDraw.Vertex (points[(k + 2) % (n * 2)].vec, strokeFlags);

strokeDraw.Vertex (points[(k + 2) % (n * 2)].vec, strokeFlags);
strokeDraw.Vertex (points[(k + 3) % (n * 2)].vec, strokeFlags);

strokeDraw.Vertex (points[k].vec, strokeFlags);
strokeDraw.Vertex (points[(k + 3) % (n * 2)].vec, strokeFlags);

strokeDraw.Vertex (points[(k + 3) % (n * 2)].vec, strokeFlags);
strokeDraw.Vertex (points[(k + 1) % (n * 2)].vec, strokeFlags);

としてみたけど動作は変わらなかったよ。

同様にして前のLXx_VCPYでvertにはpoints[(k + 2) % (n * 2)].vecが残っているので、それとk+3番のポイントをVertex ( ) メソッドで送って、2点間に線を引く。

strokeDraw.Vertex (vert, strokeFlags);
LXx_VCPY (vert, points[(k + 3) % (n * 2)].vec);
strokeDraw.Vertex (vert, strokeFlags);

fig07

以後そのくりかえしだね。これはkとk+3

LXx_VCPY (vert, points[k].vec);
strokeDraw.Vertex (vert, strokeFlags);
LXx_VCPY (vert, points[(k + 3) % (n * 2)].vec);
strokeDraw.Vertex (vert, strokeFlags);

fig08

最後はvertにk+3のポイントが残っているからk+3とk+1の間で線が引かれる。

strokeDraw.Vertex (vert, strokeFlags);
LXx_VCPY (vert, points[(k + 1) % (n * 2)].vec);
strokeDraw.Vertex (vert, strokeFlags);

fig09

これを円筒1周ぶんやれば円筒の形状のメッシュが描画されるわけだ。

それではまた次回。

カテゴリー別ページ



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

2011年11月29日

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

今回も<Quat>の続きだ。前回は演算子が意外な実装をされていてびっくりした。MAXScriptの<Quat>は3Dプログラミング用に特化していて数学のクオータニオン数とはちょっと異なるものだと思っておかないとね。

今回はメソッドについて。


copy <quat>

copyは同じ内容の<Quat>オブジェクトを生成するメソッドだ。変数に代入された<Quat>値をほかの変数に代入しても、参照がコピーされるだけで2つの変数は同じオブジェクトをさすことになるけど、copyを使えば複製したものをほかの変数にわたせるわけだ。下の例ではaからbへ代入した場合とaのcopyをbへ代入した場合を比較したものだ。a.xの内容を書き換えたときに、単純に代入しただけのものはbが示すオブジェクトとaが示すオブジェクトが同じだから、bからアクセスしてみると、aから行った変更がbから確認できる。コピーした方はa,bともに独立したオブジェクトを参照しているので、aからの変更がbに影響しない。

a = quat 1 2 3 4
(quat 1 2 3 4)
b = a
(quat 1 2 3 4)
a.x = 5
5
a
(quat 5 2 3 4)
b
(quat 5 2 3 4)

b = copy a
(quat 5 2 3 4)
a.x = 1
1
a
(quat 1 2 3 4)
b
(quat 5 2 3 4)


isIdentity <quat>

isIdentityは<Quat>値がx=y=z=0.0; w=1.0であるかどうかをチェックして、trueかfalseを返す。

q = quat 0 0 0 1
(quat 0 0 0 1)
q = quat 1
(quat 0 0 0 1)
isIdentity q
true
q.x=1
1
q
(quat 1 0 0 1)
isIdentity q
false


normalize <quat>

normalizeは回転軸ベクトルの方向、回転角度を変えずに<Quat>値の大きさが1になるように調整する。

q = quat 1 2 3 4
(quat 1 2 3 4)
qn = normalize q
(quat 0.182574 0.365148 0.547723 0.730297)
q.x^2 + q.y^2 + q.z^2 + q.w^2
1.0
q.angle
86.1774
qn.angle
86.1774
q.axis
[0.267261,0.534522,0.801784]
qn.axis
[0.267261,0.534522,0.801784]


inverse <quat>

<Quat>値の逆数を求めるメソッド。変換前の値と掛け合わせると単位クォータニオン(x=y=z=0.0; w=1.0)になる。

q
(quat 1 2 3 4)
(inverse q) * q
(quat 0 0 0 1)
q * (inverse q)
(quat 0 0 0 1)


conjugate <quat>

<Quat>値の複素共役値を求めるメソッド。 複素数部分の符号が反転する。クオータニオンを使って3次元座標値を回転させるには、座標値をクォータニオンに置き換えて、共益クォータニオンとクォータニオンにはさんで掛け算する。

たとえば回転変換に使うクォータニオンqと変換したい座標値[x,y,z]があったら、

p = quat x y z 0
pd = (conjugate q) * p * q

としてやれば、変換後の座標値は、[pd.x, pd.y, pd.z]となる。


logN <quat>

<Quat>値を対数空間にマッピングするメソッド。対数空間にクォータニオン値を投影すると、掛け算が足し算になって計算が単純になるメリットがある。

ちょっと数学的な事も触れておくと、オイラーの公式

e = cosθ+ i sinθ

というのがあって、これをクォータニオンに拡張すると、

e = cosθ+ v sinθ = q

ただし v = x i + y j + z k

となるので、

ln q = ln ( e) = vθ

となる。2つのクォータニオンの掛け算は、

q1 * q2 = ev1θ1 * ev2θ2 = e(v1θ1 + v2θ2)

となり、指数どうしの足し算になり、それは対数クォータニオン値である。だから、クォータニオンの対数をとって、それを足し合わせた値を指数とするeの値を求めれば、クォータニオンの掛け算と等しくなる(近似値らしいけど・・・)。これを式で書くと以下のようになる。

q1 * q2 = exp( (logN q1) + (logN q2) )

ただしなぜかMAXScriptの<Quat>どうしの足し算は掛け算になってしまい、要素どうしの足し算はできないから、上の式は実際には成り立たない。MAXScriptの場合は、

lq1 = logN q1
lq2 = logN q2
q1q2 = exp( quat (q1.x + q2.x) (q1.y + q2.y) (q1.z + q2.z) (q1.w + q2.w) )

と、足し算は要素ごとにやってやる必要がある。


exp <quat>

対数空間にマッピングされたクォータニオン値を元に戻す。


slerp <start_quat> <end_quat> <number>

2つの<Quat>値を補間する<Quat>値を求めるメソッド。 <number>の値を0〜1に変化させるとメソッドの戻り値が<start_quat>から<end_quat>へ変化する。

たとえば下のコードは選択オブジェクトの0フレーム目と30フレーム目の回転方向をrotationプロパティ(値が<Quat>)から取得して、1〜29フレームの補間回転キーを設定するコードだ(コンテキスト式便利だなぁ)。

at time 0 q1 = $.rotation
at time 30 q2 = $.rotation
with animate on (
 for i = 1 to 29 do (
  sq = slerp q1 q2 (i/30.0)
  at time i $.rotation = sq
 )
)

たとえば0フレーム目でこんな角度にしてキーを打っておいて、

fig01

30フレーム目でこんな角度でキーを打っておいて、

fig02

オブジェクトを選択して上のコードを実行してみた。これがその結果。2つのフレーム間に1フレームずつキーが打たれてスムーズに回転しているのがわかる。

fig04

下の画像は0と30フレームにのみキーを打ったものと、slerp補間したものをグラフで比較してみたものだ。slerp補間は曲線的に変化していることがわかる。

fig03

2つの回転方向の間を結ぶ経路は球面上の2点を結ぶ線が無限にあるのと同じでいくらでもあるし、回転によっては球面上の2点間の最短距離を結ぶ円周じゃなくてその反対側の円弧の軌道をたどりたいこともあるからなかなか難しい。slerpを使うと等速で最短経路に沿った補間ができるらしい。仕組みとしてはこんな感じらしい。

slerp(q1,q2,t) = (q2 * q1-1)t * q1

tが0ならq2 * q1-1の項が単位クォータニオンになってqだけが残り、tが1ならq2*q1-1*q1となってq1-1*q1の項が単位クォータニオンになってq2の項だけが残る。うまいことできてるね。

それではまた次回。

maxまとめページ



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

2011年11月28日

modo501 SDK いじってみた その64 modo501 SP4

ようやく歯医者に行けたああああああ!

医療費3割負担になってた・・・ぐふぇっ・・・。

って、どんだけ医者行ってないんだよw

地デジテレビが安くなってきたし、そろそろ初めての地デジテレビ購入を考えていたのになぁ(今頃かよw)。

さて、前回ちょっと手をつけたCOrbInstance::vitm_Draw ( ) メソッド。この中に出てくる

BeginW (int type,LXtVector color,double alpha,double width)

のtypeパラメータ部分について調べてみた。メソッドを以下のように書き換えてビルドしてからアイテムをシーンに挿入してみた。

  LxResult
COrbInstance::vitm_Draw (
  ILxUnknownID itemChanRead,
  ILxUnknownID viewStrokeDraw,
  int selectionFlags,
  LXtVector itemColor)
{
   CLxUser_ChannelRead chanRead(itemChanRead);
  CLxLoc_StrokeDraw strokeDraw(viewStrokeDraw);

  float lineWidth;
  int i, n, k;
  std::vector<Point> points;

/*
* Use a thicker line width when selected or rollover.
*/
  if (selectionFlags & LXiSELECTION_SELECTED ||
    selectionFlags & LXiSELECTION_ROLLOVER) {
      lineWidth = 2.0;
  }
  else {
    lineWidth = 1.0;
  }

/*
* The item color is automatically set according to the last hit test.
*/
  strokeDraw.BeginW (LXiSTROKE_LINES , itemColor, 1.0, lineWidth);

  LXtVector vert;
  int strokeFlags = LXiSTROKE_ABSOLUTE;

  strokeDraw.Text("Test Text",LXiTEXT_CENTER);

/*
* Build the vertex list.
*/
  Point p;
   p.vec[0] = -2.0;
   p.vec[1] = -0.5;
   p.vec[2] = 0.0;
  points.push_back (p);
   p.vec[0] = -1.0;
   p.vec[1] = 0.5;
   p.vec[2] = 0.0;
  points.push_back (p);
   p.vec[0] = 0.0;
   p.vec[1] = -0.5;
   p.vec[2] = 0.0;
  points.push_back (p);
   p.vec[0] = 1.0;
   p.vec[1] = 0.5;
   p.vec[2] = 0.0;
  points.push_back (p);
   p.vec[0] = 2.0;
   p.vec[1] = -0.5;
   p.vec[2] = 0.0;
  points.push_back (p);
   p.vec[0] = 3.0;
   p.vec[1] = 0.5;
   p.vec[2] = 0.0;
  points.push_back (p);

/*
* Draw the edges of each polygon.
*/
  for (i = 0; i < points.size(); i++) {
    LXx_VCPY (vert, points[i].vec);
    strokeDraw.Vertex (vert, strokeFlags);
  }

  return LXe_OK;
}

この状態でstrokeDraw.BeginWの第1パラメータtypeをいろいろ変えてみた。

LXiSTROKE_LINES
Vertex ( ) メソッドで追加される点が直線の始点、終点、始点、終点、・・・と解釈される

fig01

LXiSTROKE_POINTS
Vertex ( ) メソッドで追加される点がそのまま点として解釈される

fig02

LXiSTROKE_LINE_STRIP
Vertex ( ) メソッドで追加される点が連続する直線の通過点として解釈される

fig03

LXiSTROKE_LINE_LOOP
LXiSTROKE_FRONT_LINE_LOOP
Vertex ( ) メソッドで追加される点がループする直線の通過点として解釈される。FORNT_LINE_LOOPの場合は裏表が存在し、裏側は表示されない。

fig04

LXiSTROKE_TRIANGLES
Vertex ( ) メソッドで追加される点は3つずつ三角ポリゴンの頂点として解釈される

fig05

LXiSTROKE_QUADS
Vertex ( ) メソッドで追加される点は四角形ポリゴンの頂点として解釈される。下の画像はポイントの順番が悪くてポリゴンがねじれてしまっている。1,2,4,3の順で指定していれば四角形になる。また、5,6はポイントが2つしかないのでポリゴンが生成されない。

fig06

LXiSTROKE_BEZIERS
Vertex ( ) メソッドで追加される4つの点がベジェ曲線の始点、終点と2つの制御点として解釈される。5,6は点が2つしかないので曲線が生成されていない。

fig07

LXiSTROKE_CIRCLES
Vertex ( ) メソッドで追加される2つの点が円の中心点と法線方向、2点間の距離が円の半径として解釈される。

fig08

これは画面を回転してみたところ。わかりやすいようにLXiSTROKE_LINESで描いた直線を重ねてみた。

fig12

LXiSTROKE_BOXES
LXiSTROKE_FRONT_BOXES
Vertex ( ) メソッドで追加される2つの点が直方体の対角点として解釈される。

fig09

Z座標値を−0.5と0.5で交互に変えてパースで見てみた。

fig11

FRONT_BOXESはFRONT_LINE_LOOPのこともあって、直方体の裏側の面が見えなくなるかと思ったけど変化がなかった。なんか設定があるんだろうか?

LXiSTROKE_TEXT
Vertex ( ) メソッドで追加される点がテキストの挿入点として解釈される。

fig10

テキスト自体は、Text ( ) メソッドで基点と挿入内容を指定するようだ。

CLxLoc_StrokeDraw::Text(
  "表示したいテキスト",
  テキストの基点位置 );

#define LXiTEXT_LEFT 0
#define LXiTEXT_CENTER 1
#define LXiTEXT_RIGHT 2

大きさはどうやって変えるんだろう?

そしてLXiSTROKE_ARCSはどうやっても円弧が出てこなかった。おそらく中心角とか設定したりするんだと思うんだけどやり方がわからない。アイテムを挿入してビューポートをクリックすると、ある位置でアイテムを選択できるようなので、点くらいは出てるんだろうけど、それが円弧になるには何かが必要なようだ。ちなみにポイントを50個ランダムに作って試してみたが1つも円弧は出なかった。

まあとりあえずこれらを使えばレンダリングはされないけどビューポートに表示されるアイテムは生成できるようだ。

それではまた次回。これから抜歯だ・・・orz。

カテゴリー別ページ



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

2011年11月26日

高枝切りバサミで柿を採るテクニック

毎年この時期になると庭の柿を高枝切りばさみで採ってるんだけど、

fig01

結構な数を落としてしまい、割れた柿を食べ続ける日々が続くことになる。でもさすがに毎年やっていると多少はコツがわかってきて、最近ではミスの率がだいぶ無くなって来た。

そんな苦労をしているのは世の中で自分だけなのかも知れないが、ちょっとだけ手順を載せて見たい。

高枝切りばさみの構造は、下のようにハサミの刃の横に枝を挟むピンチが付いていて、枝が切れた瞬間にピンチがそれを掴むので、切った枝が落ちないというのがこの製品の売りだ。

fig02

そしてアームの反対側はハンドルが付いていて、これを握ると刃が閉じる仕組みになっていて、通常はバネで開いた状態を保っている。そしてこのハンドルの根元にはロックが付いていて、収納の時などにハサミが閉じた状態になるようになっている。

fig03

さて、いよいよ実技。柿を採る時はなるべく欲張らずに柿のなっている根元付近の枝を切る。付いている柿が重過ぎると落とす危険が増すのもあるけど、あまり大きく枝を落とすと翌年柿のなる枝まで落としちゃうからね。

で、この時注意しなくちゃならないのが、ハサミの入れ方。ピンチ部分が切り離された枝を掴むわけだから、刃の面が枝の根元側で、ピンチが枝の先側になって無いとダメなのは基本中の基本。でもこれで安心してちゃダメで、枝をハサミの根元に押し付けて、枝がなるべくハサミの根元で切れるように持って行く必要がある。これは根元と外側で枝が切れてからピンチが掴むまでの時間に差があるためで、根元の方がすばやくつかめるだけ掴み損ねるリスクが軽減される。それでも落としちゃう事があるので、切るときはとにかく瞬間に力を入れて勢い良く切るようにするといい。

fig04

柿が枝ごと掴めたら、そのままどこか地面にでも下ろして離してやればいいけど、

fig06

これが木の上での作業の場合、そうもいかない。採った柿を手元の袋に入れたいなんて場合、ハンドルを握ったままだと柿の方に手が届かない。

そこで登場するのがハンドルのロックだ。ハンドルを握ったままロックをすれば、柿の枝は掴まれたままはさみに固定される。

fig05

ハンドルを離しても大丈夫になったらハサミの方を手元まで持って来て、柿だけもぎってやればOKだ。

fig07

お試しあれ。


take_z_ultima at 12:53|この記事のURLComments(4)TrackBack(0)Goods | 園芸

2011年11月25日

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

今回も<quat>の続きだ。

<quat>のコンストラクタは前回登場した

quat <x_float> <y_float> <z_float> <w_float>

ただしq = w + xi + yj + zk

のほかに、回転角度と回転軸のベクトルで指定する方法や、<angleaxis>、<eulerAngles>、<Matrix3>から変換する方法もある。

quat <degrees_float> <axis_point3>
<angleaxis> as quat
<eulerangle> as quat
<matrix3> as quat

また、単位クォータニオンを生成するコンストラクタや0クォータニオンを生成するコンストラクタもある。

quat 1
(quat 0 0 0 1)
quat 0
(quat 0 0 0 0)

MaxScriptの<quat>の演算子は以下のものが実装されている。

<quat> + <quat>
<quat> - <quat>
<quat> * <quat_or_number>
<quat> / <number>
-
<quat>

<quat> * <matrix3>

<quat> == <quat>
<quat> != <quat>
<quat> as <class>

数学のクォータニオンの加減算は各要素ごとの加算減算だけど、MaxScriptの<quat>どうしの加減算はそのまま*と/の演算子の置き換えになっている。だから<quat>+<quat>と書くのと<quat>*<quat>と書くのは同じ意味だ。図形的な意味で言えば、加算する<quat>それぞれが意味する回転変換を左から順番にして行く変換になる。

q1 = quat 30 [1,2,3]
(quat 0.258819 0.517638 0.776457 0.965926)
q2 = quat 50 [4,5,6]
(quat 1.69047 2.11309 2.53571 0.906308)
q1+q2
(quat 1.5393 3.16652 2.82487 -2.62479)
q1*q2
(quat 1.5393 3.16652 2.82487 -2.62479)
q1-q2
(quat -0.11846 -0.062828 -0.14229 0.300234)
q1/q2
(quat -0.11846 -0.062828 -0.14229 0.300234)

当然、加減算は順番によって計算結果が異なる。

q1+q2
(quat 1.5393 3.16652 2.82487 -2.62479)
q2+q1
(quat 2.19559 1.85394 3.48116 -2.62479)
q2*q1
(quat 2.19559 1.85394 3.48116 -2.62479)

掛け算は<quat>どうしのものは前回やった通りで、実数を掛ける場合は各要素ごとの掛け算になる。ただし掛ける順番は<quat>*<number>でしか出来ない。

q3 = quat 1 2 3 4
(quat 1 2 3 4)
q3 * 5
(quat 5 10 15 20)
5*q3
-- 互換性のないタイプ : 5、と (quat 1 2 3 4)

−演算子は<quat>の符号として使われた場合は各要素の符号を反転させ、2項演算子として使われた場合は逆クォータニオン(元のクォータニオンの逆変換のクォータニオン)を表わす。だから同じ値どうしを引き算すれば単位クォータニオンになるけど、単項演算子としての「−」をつけて足し算すると、逆クォータニオンではなく、符号が反転したクォータニオンとして計算される。

-q3
(quat -1 -2 -3 -4)
q3-q3
(quat 0 0 0 1)
q3 + -q3
(quat -8 -16 -24 -2)

<matrix3>との掛け算は<Matrix3>のrow4を除いた3X3の行列と<quat>のx,y,z部分との行列演算になる。<quat>の回転ベクトルを<Matrix3>で回転変換した<quat>値が得られる。

X軸中心に30度回転する<quat>
q = quat 30 [1,0,0]
(quat 0.258819 0 0 0.965926)

Z軸中心に90度回転する<Matrix3>
m = rotateZMatrix 90
(matrix3 [0,1,0] [-1,0,0] [0,0,1] [0,0,0])

qd =q * m
(quat 0 0.258819 0 0.965926)

計算結果はY軸中心に30度回転する<quat>になった。これは最初の回転軸であるX軸がZ軸中心に90度回転変換されてY軸になった事を意味する。
qd.axis
[0,1,0]
qd.angle
30.0

ここまで調べるのに結構時間がかかった。マニュアルにはこれらの計算がどういうものなのかまるで書いてないので、上記の結論はあくまで自分の試した結果でしかない事を断っておくよ。マニュアルはもうちょっと親切に書いていて欲しいよなぁ・・・。

それではまた来週。

maxまとめページ



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