3Dプログラミング入門講座・その7:応用その3・ゲームプログラミング、タグの動的追加



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


3Dのスネークゲームを作ります。
サンプルはこちら
ファイルはこちら



コードはこちら。
3dsnake.js

...省略...

var win = 0;
var clr = 600;
var numoni = 2;
var posme;
var orime;
var posoni = ["true,4,1,-15,0,-25", "true,4,1,15,0,-25"];
var orioni = ["true,4,3.14,0,1,0", "true,4,3.14,0,1,0"];
var live = [true, false, false, false]

var thnk;

...省略...

function moveobj(wa, wpyr) { //メインループ

...省略...

  //敵行動
  for(thnk = 0; thnk < numoni; thnk++) {
    if(!live[thnk]) continue;
    var tgt = thinkOni();
    moveOni(tgt, true, 0);
    oldtgt[thnk] = tgt;
  }

  //体つくり
  genBody(0);
  for(thnk = 0; thnk < numoni; thnk++) {
    if(!live[thnk]) continue;
    genBody(thnk + 1);
  }

  viewp();

  //終了条件
  if(clr <= num) gameclear();
  else if(bound(0)) gameover();
  else if(numoni){
    for(thnk = 0; thnk < numoni; thnk++){
      if(!live[thnk]) continue;
      if(bound(thnk + 1)) gamewin(thnk);
    }
  }
}

...省略...

//オブジェクト位置情報
//位置4*4マトリクス(継続パラメータ)
var VONI = 0.3;
var TONI = new N6LMatrix(4).UnitMat();
var ONI = [new N6LMatrix(4).UnitMat()];
var oldpos = [];
var oldpos2 = [];

var trate = 20;
var trateW = 20;
var oldtgt = [4];
var depth = 5;
var depthW = 5;
var cont = 10;
var count = [0, 0, 0, 0];
var rotoni = 2.75;
var rotation = rotoni * (Math.PI / 180);
var notVec = [0,0,0];
var dang = 30;

var skp = 8;

//敵思考
function thinkOni(){
//if(!thnk)return 1;
//else return 7;

  count[thnk]++;
  chknotVec(); //近くの壁検知
  var ttt = [0, 0, 0, 0, 0, 0, 0, 0, 0];
  var uuu = [false, false, false, false, true, false, false, false, false];
  var bnd;
  //一定行動&危険察知
  if(count[thnk] < cont) {
    bnd = moveOni(oldtgt[thnk], false, 0);
    ONI[thnk] = new N6LMatrix(TONI);
    if(bnd < 0) return oldtgt[thnk];
    ttt[oldtgt[thnk]] = bnd;
  }
  count[thnk] = 0;
  var i;
  var j;
  var k;
  var c;
  var h;
  var rnd;
  var b = false;
  //危険察知
  for(i = 0; i < 3; i++) {
    for(j = 0; j < 3; j++) {
      k = i * 3 + j;
      if(count[thnk] < cont && k == oldtgt[thnk]) continue;
      ttt[k] = moveOni(k, false, 0);
      ONI[thnk] = new N6LMatrix(TONI);
    }
  }
  //前進と前の行動優先
  if(ttt[4] == -1 && Math.floor(Rand(0, 100)) < trate) return 4;
  if(oldtgt[thnk] != 4 && ttt[oldtgt[thnk]] == -1 && Math.floor(Rand(0, 100)) < trateW) return oldtgt[thnk];
  uuu[oldtgt[thnk]] = true;
  h = 7;
  if(oldtgt[thnk] == 4) h++;
  c = 0;
  //安全な回転
  while(c < h) {
    rnd = Math.floor(Rand(0, 9));
    if(uuu[rnd]) continue;
    uuu[rnd] = true;
    c++;
    if(ttt[rnd]  == -1) return rnd;
  }
  //危険な中でも一番安全な選択
  for(i = 1, j = ttt[0], k = 0; i < 9; i++) {
    if(j < ttt[i]) { j = ttt[i]; k = i; }
  }
  while(true) {
    rnd = Math.floor(Rand(0, 9));
    if(ttt[rnd] != j) continue;
    return rnd;
  }
}

function chknotVec() { //近くの壁察知
    var i;
    var j;
    var k;
    var n = [];
    var min;
    var mid;
    var max;
    var nmin;
    var nmid;
    var nmax;
    var dcp = ONI[thnk].Pos();
    var dc = dcp.Sub(oldpos2[thnk]).DirectionCosine().NormalVec();
    //各軸の壁の近さ
    for(i = 1; i < 4; i++) {
        if(dc.x[i] != 0.0) {
            k = -1;
            j = (-50.0 - dcp.x[i]) / dc.x[i];
            if(j < 0.0) {
              k = 1;
              j = (50.0 - dcp.x[i]) / dc.x[i];
            }
            n[i - 1] = k * j;
        }
        else n[i - 1] = 999999;
    }
    //順番
    min = n[0];
    nmin = 0;
    max = n[0];
    nmax = 0;
    if(Math.abs(n[1]) < Math.abs(min)) { min = n[1]; nmin = 1; }
    if(Math.abs(max) < Math.abs(n[1])) { max = n[1]; nmax = 1; }
    if(Math.abs(n[2]) < Math.abs(min)) { min = n[2]; nmin = 2; }
    if(Math.abs(max) < Math.abs(n[2])) { max = n[2]; nmax = 2; }
    for(i = 0; i< 3; i++) if(n[i] != min && n[i] != max) { mid = n[i]; nmid = i; }

    if(Math.abs(min) < dang)
      notVec[nmin] = new N6LMatrix().QSIGN(min) * 1;
    else { notVec = [0,0,0]; return;}
    if(Math.abs(mid) < dang)
       notVec[nmid] = new N6LMatrix().QSIGN(mid) * 2;
    else notVec[nmid] = 0;
    if(Math.abs(max) < dang)
       notVec[nmax] = new N6LMatrix().QSIGN(max) * 3;
    else notVec[nmax] = 0;
}

function moveOni(k, bbb, cnt){//移動&危険察知
  if(!x3domRuntime) return -1;

  var x = k % 3 - 1;
  var y = Math.floor(k / 3) - 1;
  //var wpyr = new N6LVector([1, x * rotation, y * rotation, x * rotation], true);
  var wpyr = new N6LVector([1, x * rotation, y * rotation, 0], true);
  var outmat = [];
  var outv = [];
  var WA;

  if(!bbb) { //危険察知
    WA = ONI[thnk].MoveMat(outmat, outv, new N6LVector(4, true).ZeroVec(), wpyr.Mul(skp), VONI * skp, 0, 0, 5); //目的の関数
    if(!cnt) TONI = new N6LMatrix(ONI[thnk]);
    var dep = depthW;
    if(k == 4) dep = depth; //直進はより深く

    //壁検知
    var max = outv[0].Max();
    var dir;
    for(x = 1; x < 4; x++) {
        if(outv[0].x[x] == max) { dir = x - 1; }
    }
    if(Math.abs(notVec[dir]) == 1){
      wpyr = wpyr.Mul(-1);
      var outmat2 = [];
      var outv2 = [];
      WA = WA.MoveMat(outmat2, outv2, new N6LVector(4, true).ZeroVec(), wpyr.Mul(skp), VONI * skp, 0, 0, 5); //目的の関数
      if(notVec[dir] < 0) {
          if(outv[0].x[dir] < outv2[0].x[dir])
              return cnt; //危険
      }
      else if(outv2[0].x[dir] < outv[0].x[dir])
          return cnt; //危険
    }
    ONI[thnk] = new N6LMatrix(WA);
    if(dep < cnt)
      return -1; //安全
    if(bound(thnk + 1))
      return cnt; //危険
    return moveOni(k, false, cnt + 1); //同じ方向のみさらに調べる
  }
  else WA = ONI[thnk].MoveMat(outmat, outv, new N6LVector(4, true).ZeroVec(), wpyr, VONI, 0, 0, 5); //目的の関数

  //値を適用
  ONI[thnk] = new N6LMatrix(WA);
  //x3domに適用
  var Vec = ONI[thnk].Vector();
  var pos = ONI[thnk].Pos();
  oldpos2[thnk] = new N6LVector(oldpos[thnk]);
  oldpos[thnk] = new N6LVector(pos);
  var oni = pos.ToX3DOM(true);
  var rot = Vec.ToX3DOM();

  var elm = document.getElementById('oniT' + String(thnk));
  elm.setAttribute('translation', oni.toString());
  elm = document.getElementById('oniR' + String(thnk));
  elm.setAttribute('rotation', rot.toString());
}


var egg = [2.0, 2.0, 2.0, 2.0, 2.0];
var eggarea = [];
var oldegg = [new N6LVector([1, 0, 0, 25], true), new N6LVector([1, 0, 0, -25], true)];
var add = 0.02;
var num = 0;
var r = 4.0;
//体つくり
function genBody(id){

  egg[id] += add;
  if(1.0 < egg[id]) {
    egg[id] = 0.0;
    var mat;
    var col;
    if(!id) { mat = new N6LMatrix(A); col = '0.3 0.3 0.6'; }
    else { 
      mat = new N6LMatrix(ONI[id - 1]);
      switch(id) {
      case 1 : col = '0.6 0.15 0.3'; break;
      case 2 : col = '0.6 0.5 0.15'; break;
      case 3 : col = '0.23 0.6 0.15'; break;
      case 4 : col = '0.15 0.6 0.6'; break;
      default : col = '0 0 0'; break;
      }
    }
    var pos = mat.Pos();
    var trns = pos.x[1] + ' ' + pos.x[2] + ' ' + pos.x[3];
    var line = oldegg[id].x[1] + ' ' + oldegg[id].x[2] + ' ' + oldegg[id].x[3] + ' ' +
               pos.x[1] + ' ' + pos.x[2] + ' ' + pos.x[3];
    oldegg[id] = new N6LVector(pos);
    var area = Math.floor((pos.x[1] + 50) / 25) * 16 + Math.floor((pos.x[2] + 50) / 25) * 4 + Math.floor((pos.x[3] + 50) / 25);
    eggarea[num] = area;

    //タグの動的追加
    var material = document.createElement("material");
    material.setAttribute("diffuseColor", col);
    var appearance = document.createElement("appearance");
    var sphere = document.createElement("sphere");
    sphere.setAttribute("radius", String(r));
    var shape = document.createElement("shape");
    var transform = document.createElement("transform");
    transform.setAttribute("id", "sph_" + num);
    transform.setAttribute("translation", trns);

    appearance.appendChild(material);
    shape.appendChild(appearance);
    shape.appendChild(sphere);
    transform.appendChild(shape);

    var lmaterial = document.createElement("material");
    lmaterial.setAttribute("emissiveColor", col);
    var lcoordinate = document.createElement("coordinate");
    lcoordinate.setAttribute("point", line);
    var lindexedlineset = document.createElement("indexedlineset");
    lindexedlineset.setAttribute("coordIndex", "0 1 -1");
    var llineproperties = document.createElement("lineproperties");
    llineproperties.setAttribute("linetype", "1");
    //llineproperties.setAttribute("linewidthScaleFactor", "10");
    var lappearance = document.createElement("appearance");
    var lshape = document.createElement("shape");

    lappearance.appendChild(lmaterial);
    lappearance.appendChild(llineproperties);
    lshape.appendChild(lappearance);
    lindexedlineset.appendChild(lcoordinate);
    lshape.appendChild(lindexedlineset);

    var parent_object = document.getElementById("x3dsn");
    parent_object.appendChild(transform);
    parent_object.appendChild(lshape);

    num++;
    //debug//デバッグ用
    var elm = document.getElementById('debug');
    //elm.innerText = 'oldtgt = ' + String(oldtgt) + '\n' + String(num) + ' 個産まれた '; 
    elm.innerText = 'win : ' + String(win) + '\n' + String(num) + ' 個産まれた '; 
  }
}

function bound(id){ //衝突検知
  var i;
  var j;
  var elm;
  var pos;
  var bpos;
  var dis;
  var str;
  var token;
  var mat;
  if(!id) { mat = new N6LMatrix(A); }
  else { mat = new N6LMatrix(ONI[id - 1]); bpos = A.Pos(); }

  pos = mat.Pos();
  //壁
  if((pos.x[1] < -50.0 + r || 50.0 - r < pos.x[1]) ||
     (pos.x[2] < -50.0 + r || 50.0 - r < pos.x[2]) ||
     (pos.x[3] < -50.0 + r || 50.0 - r < pos.x[3]))
    return true;

  //キャラ同士
  if(id) {
    dis = bpos.Sub(pos).Abs();
    if(dis < r * 1.5)
      return true;
  }
  for(i = 0; i < numoni; i++) {
    if(i == id - 1 || !live[i]) continue;
    bpos = ONI[i].Pos();
    dis = bpos.Sub(pos).Abs();
    if(dis < r * 1.5) {
      if(id) { 
        live[i] = false;
        elm = document.getElementById('oniS' + String(i));
        elm.setAttribute('render', 'false');
        win++;
      }
      return true;
    }
  }

  var area = [
      Math.floor((pos.x[1] + 50) / 25 + 0) * 16 + Math.floor((pos.x[2] + 50) / 25 + 0) * 4 + Math.floor((pos.x[3] + 50) / 25 + 0),
      Math.floor((pos.x[1] + 50) / 25 - 1) * 16 + Math.floor((pos.x[2] + 50) / 25 + 0) * 4 + Math.floor((pos.x[3] + 50) / 25 + 0),
      Math.floor((pos.x[1] + 50) / 25 + 1) * 16 + Math.floor((pos.x[2] + 50) / 25 + 0) * 4 + Math.floor((pos.x[3] + 50) / 25 + 0),
      Math.floor((pos.x[1] + 50) / 25 + 0) * 16 + Math.floor((pos.x[2] + 50) / 25 - 1) * 4 + Math.floor((pos.x[3] + 50) / 25 + 0),
      Math.floor((pos.x[1] + 50) / 25 + 0) * 16 + Math.floor((pos.x[2] + 50) / 25 + 1) * 4 + Math.floor((pos.x[3] + 50) / 25 + 0),
      Math.floor((pos.x[1] + 50) / 25 + 0) * 16 + Math.floor((pos.x[2] + 50) / 25 + 0) * 4 + Math.floor((pos.x[3] + 50) / 25 - 1),
      Math.floor((pos.x[1] + 50) / 25 + 0) * 16 + Math.floor((pos.x[2] + 50) / 25 + 0) * 4 + Math.floor((pos.x[3] + 50) / 25 + 1)];

  for(i = 0; i < num - (numoni + 1); i++) {
    //当該体が含まれる隣接エリア
    for(j = 0; j < area.length; j++)
      if(eggarea[i] != area[j])
        continue;
    //体
    elm = document.getElementById("sph_" + i);
    str = elm.translation;
    token = str.split(' ');
    bpos = new N6LVector([1, token[0], token[1], token[2]], true);
    dis = bpos.Sub(pos).Abs();
    if(dis < r * 1.5)
      return true;
  }
  return false; //安全
}

//終了処理
function gameclear(){
  V = 0.0;
  var elm = document.getElementById('debug');
  var str = 'ゲームクリア!!!\nScore : ' + String(num * 10 + win * 1500 + 1 * 5000) + '\nEgg : ' + String(num) + ' × 10 = ' + String(num * 10) + '\nWinBonus : 1500 × ' + String(win) + ' = ' + String(win * 1500) + '\nClearBonus : 5000 × 1 = 5000'; 
  elm.innerText = str; 
  window.alert(str);
  game = false;
}

function gameover(){
  V = 0.0;
  var elm = document.getElementById('debug');
  var str = 'ゲームオーバー!!!\nScore : ' + String(num * 10 + win * 1500 + 0 * 5000) + '\nEgg : ' + String(num) + ' × 10 = ' + String(num * 10) + '\nWinBonus : 1500 × ' + String(win) + ' = ' + String(win * 1500) + '\nClearBonus : 5000 × 0 = 0'; 
  elm.innerText = str; 
  window.alert(str);
  game = false;
}

function gamewin(id){
  live[id] = false;
  var elm = document.getElementById('oniS' + String(id));
  elm.setAttribute('render', 'false');
  win++;

  var i;
  for(i = 0; i < numoni; i++)
    if(live[i]) return;

  V = 0.0;
  elm = document.getElementById('debug');
  var str = 'ゲームウィン!!!\nScore : ' + String(num * 10 + win * 1500 + 0 * 5000) + '\nEgg : ' + String(num) + ' × 10 = ' + String(num * 10) + '\nWinBonus : 1500 × ' + String(win) + ' = ' + String(win * 1500) + '\nClearBonus : 5000 × 0 = 0'; 
  elm.innerText = str; 
  window.alert(str);
  game = false;
}

//lookat//注視
function viewp() {
  if(!x3domRuntime || !numoni) return;
  var elm;
  var i;
  var j;
  var k;
  var dis = 999999;
  var WM = new N6LMatrix(A);
  var pos = WM.Pos();
  var lookat;
  for(i = 0; i < numoni; i++){
     if(!live[i]) continue;
     lookat = ONI[i].Pos(); //目標位置
     j = lookat.Sub(pos).Abs();
     if(j < dis) {dis = j; k = i;}
  }
  lookat = ONI[k].Pos(); //目標位置


/*
  //視垂台カリング失敗。むむむ。
  var TVM = new N6LMatrix(VM);
  var Offset = ONI.Pos();
  var Right = TVM.GetRow(1);
  var Up = TVM.GetRow(2);
  var Normal = TVM.GetRow(3);

  var radius = 0.001;
  var sH = 0.5;
  var near = Normal.Dot(Offset);
  var far = -(Normal.Dot(Offset)) + 120.0;
  var left = Right.Dot(Offset) - sH;
  var right = Right.Dot(Offset) + sH;
  var bottom = Up.Dot(Offset) - sH;
  var top = Up.Dot(Offset) + sH;
  if((-radius <= near) &&
    (-radius <= far) &&
    (-radius <= left) &&
    (-radius <= right) &&
    (-radius <= bottom) &&
    (-radius <= top)) { OutArrow(); return; }
*/

  var LAM = WM.LookAtMat2(lookat); //目的の関数

  //オイラー角を求める//ロールの処理が難しいから軸はこの順番
  var lamel = LAM.EulerAngle(2, 3, 1);
  var wmel = WM.EulerAngle(2, 3, 1);
  var p = lamel.x[1] - wmel.x[1];
  var y = lamel.x[2] - wmel.x[2];
  //if(wmel.x[2] < lamel.x[2]) y = wmel.x[2] - lamel.x[2];
  var r = 0.0 - wmel.x[3];
  //elm = document.getElementById('debug');
  //elm.innerText = String(Math.floor(r * (180.0 / Math.PI)));
  //ロールの処理が難しい
  //var Theta = Math.atan2(p, - WM.QSIGN(y) * Math.sqrt(y * y + r * r));
  //var Theta = Math.atan2(p, -y) + 2.0 * r;
  var Theta = Math.atan2(p, -y);

  //矢印セット
  var az = new N6LVector(4, true).UnitVec(3);
  var dir = new N6LVector([1.0, 0.75, 0.0, -2.0], true).RotAxis(az, Theta);
  var ppp = WM.GetCol(0);
  WM.x[1].x[0] = 0;
  WM.x[2].x[0] = 0;
  WM.x[3].x[0] = 0;
  dir = WM.Mul(dir).Add(ppp);
  WM = WM.TranslatedMat(dir);
  var pos = WM.Pos().ToX3DOM(true);
  var AM = new N6LMatrix(A);
  AM = AM.RotAxis(az, Theta - Math.PI / 2.0);
  var ori = AM.Vector().ToX3DOM();
  elm = document.getElementById('arrowT');
  elm.setAttribute('translation', pos.toString());
  elm = document.getElementById('arrowR');
  elm.setAttribute('rotation', ori.toString());

  //debug//デバッグ用
  //elm = document.getElementById('debug');
  //elm.innerText = 'th = ' + String(Math.floor(Theta * (180.0 / Math.PI)));
}

function OutArrow(){
  var elm = document.getElementById('arrowT');
  var pos = new N6LVector([1, 1000, 0, 0], true).ToX3DOM(true);
  elm.setAttribute('translation', pos.toString());
}



体の球体を作るために(目的は)<sphere>タグを動的に追加します。

・タグの動的追加
var material = document.createElement("material");
material.setAttribute("diffuseColor", col);
var appearance = document.createElement("appearance");
...省略...
appearance.appendChild(material);
...省略...
var parent_object = document.getElementById("x3dsn"); //xhtml、シーンタグ
parent_object.appendChild(transform);

createElement()でタグエレメントを作り、setAttribute()で設定をして
appendChild()で入れ子構造を作り、htmlファイルのタグのIDを取得して
appendChild()で実際に挿入する。


・ゲームプログラミング
メインループに一連のゲームフローを引っ掛けます。
AIは

function thinkOni()

var trate = 20;
var trateW = 20;
var oldtgt = [4];
var depth = 5;
var depthW = 5;
var cont = 10;
var count = [0, 0, 0, 0];
var rotoni = 2.75;
var rotation = rotoni * (Math.PI / 180);
var notVec = [0,0,0];
var dang = 30;

var skp = 8;

//敵思考
function thinkOni(){
//if(!thnk)return 1;
//else return 7;

  count[thnk]++;
  chknotVec(); //近くの壁検知
  var ttt = [0, 0, 0, 0, 0, 0, 0, 0, 0];
  var uuu = [false, false, false, false, true, false, false, false, false];
  var bnd;
  //一定行動&危険察知
  if(count[thnk] < cont) {
    bnd = moveOni(oldtgt[thnk], false, 0);
    ONI[thnk] = new N6LMatrix(TONI);
    if(bnd < 0) return oldtgt[thnk];
    ttt[oldtgt[thnk]] = bnd;
  }
  count[thnk] = 0;
  var i;
  var j;
  var k;
  var c;
  var h;
  var rnd;
  var b = false;
  //危険察知
  for(i = 0; i < 3; i++) {
    for(j = 0; j < 3; j++) {
      k = i * 3 + j;
      if(count[thnk] < cont && k == oldtgt[thnk]) continue;
      ttt[k] = moveOni(k, false, 0);
      ONI[thnk] = new N6LMatrix(TONI);
    }
  }
  //前進と前の行動優先
  if(ttt[4] == -1 && Math.floor(Rand(0, 100)) < trate) return 4;
  if(oldtgt[thnk] != 4 && ttt[oldtgt[thnk]] == -1 && Math.floor(Rand(0, 100)) < trateW) return oldtgt[thnk];
  uuu[oldtgt[thnk]] = true;
  h = 7;
  if(oldtgt[thnk] == 4) h++;
  c = 0;
  //安全な回転
  while(c < h) {
    rnd = Math.floor(Rand(0, 9));
    if(uuu[rnd]) continue;
    uuu[rnd] = true;
    c++;
    if(ttt[rnd]  == -1) return rnd;
  }
  //危険な中でも一番安全な選択
  for(i = 1, j = ttt[0], k = 0; i < 9; i++) {
    if(j < ttt[i]) { j = ttt[i]; k = i; }
  }
  while(true) {
    rnd = Math.floor(Rand(0, 9));
    if(ttt[rnd] != j) continue;
    return rnd;
  }
}



//危険察知
for(i = 0; i < 3; i++) {
 for(j = 0; j < 3; j++) {
  k = i * 3 + j;
  if(count[thnk] < cont && k == oldtgt[thnk]) continue;
  ttt[k] = moveOni(k, false, 0);
  ONI[thnk] = new N6LMatrix(TONI);
 }
}

前方9方向に進むことを調べることとします。

moveOni(k, false, 0)をdepthの深さだけ、再起呼び出しをして
一定動作を繰り返した時、安全かどうかを調べます。
moveOniは安全ならば、-1、危険ならば、安全だった時までのネストの深さを返します。
-1ならば問題なくランダムで進めますし、そうでない時は、一番深いネストまで到達したものを選ぶことにします。
直進および一定動作を繰り返すさじ加減がミソです。

ただ、体が多くなると衝突検知の回数が多くて、重くなるのは困ったことでした。


敵の方向矢印は、一番近い敵へのLookatMatrixを求めたのと、自機ローカル行列のオイラー角を求めて
↓のようにシータを求めて(ロールの処理がいまいち不明)

//オイラー角を求める//ロールの処理が難しいから軸はこの順番
var lamel = LAM.EulerAngle(2, 3, 1);
var wmel = WM.EulerAngle(2, 3, 1);
var p = lamel.x[1] - wmel.x[1];
var y = lamel.x[2] - wmel.x[2];
var r = 0.0 - wmel.x[3];
//elm = document.getElementById('debug');
//elm.innerText = String(Math.floor(r * (180.0 / Math.PI)));
//ロールの処理が難しい
//var Theta = Math.atan2(p, - WM.QSIGN(y) * Math.sqrt(y * y + r * r));
//var Theta = Math.atan2(p, -y) + 2.0 * r;
var Theta = Math.atan2(p, -y);

然るべく↓のように配置します

//矢印セット
var az = new N6LVector(4, true).UnitVec(3);
var dir = new N6LVector([1.0, 0.75, 0.0, -2.0], true).RotAxis(az, Theta);
var ppp = WM.GetCol(0);
WM.x[1].x[0] = 0;
WM.x[2].x[0] = 0;
WM.x[3].x[0] = 0;
dir = WM.Mul(dir).Add(ppp);
WM = WM.TranslatedMat(dir);
var pos = WM.Pos().ToX3DOM(true);
var AM = new N6LMatrix(A);
AM = AM.RotAxis(az, Theta - Math.PI / 2.0);
var ori = AM.Vector().ToX3DOM();
var elm = document.getElementById('arrowT');
elm.setAttribute('translation', pos.toString());
elm = document.getElementById('arrowR');
elm.setAttribute('rotation', ori.toString());







 ■■■ 3Dプログラミング入門講座 ■■■ 
NAS6LIB
ポリゴンテスト解説・x3dom使い方
その1:ベクトル
その2:行列
その3:クォータニオン
その4:基本総復習・実践
その5:応用その1・メッシュアニメーション、動的テクスチャ
その6:応用その2・GLSL、カスタムシェーダー、キーボード、ファイル
その7:応用その3・ゲームプログラミング、タグの動的追加
その8:応用その4・GLSL、シェーダー、その2
その9:物理演算その1・電卓で相対性理論を解く方法
その10:物理演算その2・相対性理論的ニュートン力学
その11:物理演算その3・ケプラー方程式で惑星軌道シミュレーターを作る

その12:物理演算その4・ルンゲクッタ法で作った 相対性理論的ニュートン力学物理エンジンで惑星軌道シミュレーターを作る

その13:経路探索(A*:A-STAR)&巡回セールスマン問題 : 巨大サイズ : くろにゃんこ

その14:プログラミングにおける配列テーブルテクニック
その15:javascriptのクラス活用法
その16:透視射影公式テスト

その17:ケプラー方程式カプセルライブラリ使用法
その18:CSVファイル処理
その19:物理演算その5・重力多体問題
その20:同次座標について(3D座標系の基本の基本)
その21:おさらいコモンクラスの宣言
その22:物理エンジンライブラリ解説(ケプラー方程式・ルンゲクッタ・相対論的万有引力)


 ■■■ THREE.JSプログラミング講座 ■■■ 
THREE.JSテスト解説・THREE.JS使い方
THREE.JS examplesをいじってみた(フレネル反射透過シェーダー)

THREE.JS (半透明シェーダー)

THREE.JS 3D演算で必要な計算(具体例)★とても重要★
THREE.JS THREE-VRM をいじってみた



<<prev GLSL、カスタムシェーダー、キーボード、ファイル : GLSL、シェーダー、その2 next>>





戻る