3Dプログラミング講座・その4:基本総復習・実践



同次座標がw,x,y,z,...の順番で定義されたものの説明です。順番が違うだけで、機能に変わりはありませんが・・・


ここまで、ベクトル、行列、クォータニオンが理解できたら、復習も兼ねて実践へとうつります。



ここでは、NAS6LIBを用います。
関数が不明でしたら、NAS6LIB ヘルプ ドキュメントを参照してください


基本のワールド回転行列を宣言し、単位同次行列とします。
var wmat = new N6LMatrix(4).UnitMat();

オブジェクトの代入は、特に意図がなければコンストラクタ渡しで、ディープコピーするのが安全です。
var objA = new N6LType(objB);

ノーマライズをきちんとするのは基本として
また、各要素の値が±1.0を「僅かに」超えたときに、致命的エラーになることを防ぐには、こうすると安全です。(あくまでも、誤差の蓄積の場合ですが)
これが問題になるケースは、大抵、arcsinなど、逆三角関数の呼び出しで±1.0を超えてNANを返してしまう時です。
objA = objA.DivMax();


回転軸を宣言します。
var axis = new N6LVector([1, 1, 1, 1], true);

回転角を宣言します。(60°)
var th = 60 * (Math.PI / 180);

スケールベクトルを宣言します。
var svec = new N6LVector([1, 2, 2, 2], true);

平行移動ベクトルを宣言します。
var tvec = new N6LVector([1, 1, 2, 3], true);


以上のパラメータでワールド回転行列を変換します。

平行移動します。
wmat = wmat.TranslatedMat(tvec);

スケールをかけます。
wmat = wmat.ScaleMat(svec);

回転します。
wmat = wmat.RotAxis(axis, th);


以上を1つの関数で行うには、以上の設定の後、
axis.x[0] = th; //axisを回転ベクトルにする
wmat = wmat.AffineMat(svec, axis, tvec);
で出来ます。


wmat = wmat.TranslatedMat(tvec).RotAxis(axis, th);

wmat = wmat.RotAxis(axis, th).TranslatedMat(tvec);
は、平行移動した後、その場で回転。と
は、その場で回転の後、平行移動。との違いがあることに注意しましょう。


ワールド回転行列に回転がかかったので任意のベクトルPとの積は、然るべく回転されます
var P = new N6LVector([1, -1, -1, -1], true);
var PP = wmat.Mul(P);

また、x,y,z各軸単位ベクトルを宣言します。
var ax = new N6LVector(4, true).UnitVec(1);
var ay = new N6LVector(4, true).UnitVec(2);
var az = new N6LVector(4, true).UnitVec(3);

各軸回転角を宣言します。(10°,20°,30°)
var th = new N6LVector([
  1,
  10 * (Math.PI / 180),
  20 * (Math.PI / 180),
  30 * (Math.PI / 180)], true);

さらに回転します。
wmat = wmat.RotAxis(ax, th.x[1]).RotAxis(ay, th.x[2]).RotAxis(az, th.x[3]);



そして、回転などの変換をし終わったワールド回転行列の逆行列はカメラ(ローカル視点)行列です。
var dt = [];
var vmat = wmat.InverseMat(dt);

補足ですが、wmat.FrustumMat(left, right, top, bottom, near, far);で透視射影(パース)行列が取得できます。



また、x3domに適用するには、
var v = wmat.Vector();
var rot = v.ToX3DOM();
var elm = document.getElementById('ID名');
elm.setAttribute('rotation', rot.toString());

等々とします。
ここで、NAS6LIBは、w,x,y,zの順番に対して、x3domは、x,y,z,wの順番な事に注意しましょう。


N6LMatrix.LookAtMat2()、注視関数の使い方


//下の関数が呼ばれる前にこれをしておく
if(x3domRuntime == undefined) x3domRuntime = document.getElementById('x3dom_ID名').runtime;

//lookat//注視
function viewp() {
  if(!x3domRuntime) return;
  var elm = document.getElementById('viewp001');

  var SWM = x3domRuntime.viewMatrix().inverse(); //ワールド回転行列取得
  var WM = new N6LMatrix().FromX3DOM(SWM);
  var Seye = SWM.multMatrixPnt(new x3dom.fields.SFVec3f(0, 0, 0)); //視点位置取得
  var sp = bx.ToX3DOM(true); //注視目標
  var Sat = x3dom.fields.SFVec3f.copy(sp);
  var lookat = new N6LVector([1.0, Sat.x, Sat.y, Sat.z], true); //注視目標セット
  var LAM = WM.LookAtMat2(lookat); //目的の関数
  var Vec = LAM.Vector();
  var ori = Vec.ToX3DOM();

  elm.setAttribute('position', Seye.toString());
  elm.setAttribute('orientation', ori.toString());
  //elm.setAttribute('centerOfRotation', sp.toString());
}


このようにして使います。

また、N6LMatrix.MoveMat()、オブジェクトの主観的操作は
htmファイルの<body onload="initKeyBoard(TMan, function() { chkKeyBoard(); });enter();">
として、chkKeyBoard()を登録して
(enter()はエントリーポイントになりますので、然るべく定義して用意してください(空関数でもいいっちゃいい))
viewpointタグの下に<navigationInfo type='"none"' id="navType"></navigationInfo>
として、このようにします。


//オブジェクト位置情報
//位置4*4マトリクス(継続パラメータ)
var A = false;

//速度(継続パラメータ)
var V = 0.1;
var a = 0;
var pyr = new N6LVector([1, 0, 0, 0], true); 

//以上のように初期化してから、下の関数を呼び続ける

//加速度a,(スカラー量)(新規パラメータ) 
//ピッチヨーロール(1, θp, θy, θr)(新規パラメータ) 
function moveobj(wa, wpyr) {
  if(!A) {
    if(x3domRuntime) {
      var vm = x3domRuntime.viewMatrix().inverse(); //ワールド回転行列取得
      A = new N6LMatrix().FromX3DOM(vm);
    }
    else return;
  }

  var outmat = [];
  var outv = [];
  var WA = A.MoveMat(outmat, outv, new N6LVector(4, true).ZeroVec(), wpyr, V, wa, 0, 5); //目的の関数

  //値を適用
  V = outv[0].Abs();
  A = new N6LMatrix(WA);
  pyr = new N6LVector([1, 0, 0, 0], true);

  //x3domに適用
  var Vec = A.Vector();
  var pos = A.Pos();
  var eye = pos.ToX3DOM(true);
  var ori = Vec.ToX3DOM();

  var elm = document.getElementById('viewp001');
  elm.setAttribute('position', eye.toString());
  elm.setAttribute('orientation', ori.toString());
}

//キー入力
var KBLock7 = 0;
var KBLock9 = 0;
var KBIntvl = 5;
function chkKeyBoard(){
  if(KeyB.keystate[KeyB.indexof(KeyB.ToReal('VK_N1'))]) {//N1Key
    pyr.x[3] -= 1 * (Math.PI / 180);
  }
  if(KeyB.keystate[KeyB.indexof(KeyB.ToReal('VK_N2'))]) {//N2Key
    pyr.x[1] -= 1 * (Math.PI / 180);
  }
  if(KeyB.keystate[KeyB.indexof(KeyB.ToReal('VK_N3'))]) {//N3Key
    pyr.x[3] += 1 * (Math.PI / 180);
  }
  if(KeyB.keystate[KeyB.indexof(KeyB.ToReal('VK_N4'))]) {//N4Key
    pyr.x[2] -= 1 * (Math.PI / 180);
  }
  if(KeyB.keystate[KeyB.indexof(KeyB.ToReal('VK_N5'))]) {//N5Key
    a = 0;
  }
  if(KeyB.keystate[KeyB.indexof(KeyB.ToReal('VK_N6'))]) {//N6Key
    pyr.x[2] += 1 * (Math.PI / 180);
  }
  if(KeyB.keystate[KeyB.indexof(KeyB.ToReal('VK_N7'))]) {//N7Key
    if(KBIntvl < KBLock7) KBLock7 = 0;
    if(KBLock7 == 0) {
      a -= 0.005;
      if(a < -0.5) a = -0.5;
    }
    KBLock7++;
  }
  else KBLock7 = 0;
  if(KeyB.keystate[KeyB.indexof(KeyB.ToReal('VK_N8'))]) {//N8Key
    pyr.x[1] += 1 * (Math.PI / 180);
  }
  if(KeyB.keystate[KeyB.indexof(KeyB.ToReal('VK_N9'))]) {//N9Key
    if(KBIntvl < KBLock9) KBLock9 = 0;
    if(KBLock9 == 0) {
      a += 0.005;
      if(0.5 < a) a = 0.5;
    }
    KBLock9++;
  }
  else KBLock9 = 0;
};



バグりそうな目星(うっかりするところ):
・ゼロ除算、-の根、undefined
・値や絶対値の1.0オーバー(ノーマライズされていない、逆三角関数の範囲外、など)
・objA = new N6LType(objB);とするところを、objA = objB;としてしまって、ディープコピーのし忘れ
・bHomoフラグ関連(bHomoが同次座標行列N6LMatrixの各行のN6LVectorはfalseなのに対して
それを軸として取り出そうとするときは、.SetHomo(true)をして
その後、元の行列を使うときは、.SetHomo(false)をしなければならない、など)
*SetHomo()がバグの元なので、その実行でthisの変更するのをやめて、戻り値を返す方式にした
*行や列の操作に、より安全なGet/Set-Col/Row()関数を用意した


姿勢制御の概算値の目安はここ、回転テストrottest.htmを参考にしてください






3Dプログラミング講座
NAS6LIB
ポリゴンテスト解説・x3dom使い方
その1:ベクトル
その2:行列
その3:クォータニオン
その4:基本総復習・実践
その5:応用その1・メッシュアニメーション、動的テクスチャ
その6:応用その2・GLSL、カスタムシェーダー、キーボード、ファイル
その7:応用その3・ゲームプログラミング、タグの動的追加
その8:応用その4・GLSL、シェーダー、その2

<<prev クォータニオン : メッシュアニメーション、動的テクスチャ next>>





戻る