2012年8月7日火曜日

3D Translate実装例(2) Diceについて



3D 四面、八面ではY軸中心の回転だけでしたが、DiceではX、Yの2軸で回転させ、さらに落下を出すためにZ軸方向に移動しています。
加えてTransform後のどの面が正面を向いているかを判断しています。

HTMLでのDIVの配置、CSS設定は3D 四面の応用です。

5つの立方体のDiceを配置する。
<div id="view">
  <div id="cube1" onclick="selCube(this, false);">
    <div class="face f1">1</div>
    <div class="face f2">2</div>
    <div class="face f3">3</div>
    <div class="face f4">4</div>
    <div class="face f5">5</div>
    <div class="face f6">6</div>
  </div>
  <div id="cube2" onclick="selCube(this, false);">
    <div class="face f1">1</div>
    <div class="face f2">2</div>
    <div class="face f3">3</div>
    <div class="face f4">4</div>
    <div class="face f5">5</div>
    <div class="face f6">6</div>
  </div>
以下同様にcube3~cube5を配置する。
</div>

(selCubeはtapされたときにそのDiceを選択状態にするfunction)

上記DIVを配置するCSS
全体の位置
#view {
  width:640px;
  height:600px;
  margin:0px auto 0px auto;
  -webkit-transform:translateX(1px);
  -webkit-perspective: 1600;
  -webkit-perspective-origin-x: 550px;
  -webkit-perspective-origin-y: 600px;
}

各cube共通設定
#cube1, #cube2, #cube3, #cube4, #cube5 {
  position:absolute;
  display:none;
  height:200px;
  width:200px;
  -webkit-transform-style: preserve-3d;
  -webkit-transform:translateZ(-3000px);
}

上列3個のDiceのtop位置
#cube1, #cube2, #cube3 {
  margin-top: -240px;
}

下列3個のDiceのY位置
#cube4, #cube5 {
  margin-top: 60px;
}
各Diceの横位置。cube2は中央なので設定不要。
#cube1 { margin-left:320px; }
#cube3 { margin-left:-320px; }
#cube4 { margin-left:160px; }
#cube5 { margin-left:-160px; }

Dice各面の共通設定
.face {
  position: absolute;
  height: 160px;
  width: 160px;
  padding: 20px;
  font-size: 150px;
  text-align;center;
  vertical-align;center;
  color: transparent;
  border:solid 4px darkgray;
  background-color: rgba(50, 50, 50, 0.5);
  -webkit-border-radius: 40px;
}

6面各々の位置決め、およびイメージ設定。
.f1 {
  -webkit-transform: rotateX(90deg) translateZ(100px);
  background-image:url("../Images/1.png");
}

.f2 {
  -webkit-transform: translateZ(100px);
  background-image:url("../Images/2.png");
}

.f3 {
  -webkit-transform: rotateY(90deg) translateZ(100px);
  background-image:url("../Images/3.png");
}

.f4 {
  -webkit-transform: rotateX(180deg) translateZ(100px);
  background-image:url("../Images/4.png");
}

.f5 {
  -webkit-transform: rotateY(-90deg) translateZ(100px);
  background-image:url("../Images/5.png");
}

.f6 {
  -webkit-transform: rotateX(-90deg) translateZ(100px) ;
  background-image:url("../Images/6.png");
}

JavaScript


//Diceコンストラクタ
Dice = function(cube) {
  this.cube = cube;
  this.x = Dice.rand() * 360;
  this.y = Dice.rand() * 360;
  this.z = Dice.rand() * 360;

  this.dx = this.dy = this.dz = 0;
  this.dist = -200;  //traslateZ
  this.sec = 0.1;
  this.interval = 100; //msec;
  this.timer = null;
  //同一DiceでTimerイベントを複数実行しないようにするためのフラグ。
  this.busy = false;
  //trueの間、落下・回転を続ける。
  this.flag = true;
  //何故かiOSでは2,3が入れ替わるので、OSに合わせてfacesのindexを設定
  this.fArr = isIOS ? new Array(1,3,2,4,5,6) : new Array(1,2,3,4,5,6);
}

//Dice初期化. 5個のDiceに対応するインスタンスを作り、cubes配列にセット.
//選択されていないDiceは0とする.
//spotsArrは正面を向いている面が何かを判断するための配列。
Dice.init = function() {
  Dice.spotsArr = new Array(
    (cubes[0].getAttribute("selected") != "true" ? Dice.spotsArr[0] : 0),
    (cubes[1].getAttribute("selected") != "true" ? Dice.spotsArr[1] : 0),
    (cubes[2].getAttribute("selected") != "true" ? Dice.spotsArr[2] : 0),
    (cubes[3].getAttribute("selected") != "true" ? Dice.spotsArr[3] : 0),
    (cubes[4].getAttribute("selected") != "true" ? Dice.spotsArr[4] : 0));
  Dice._seed = Math.random(new Date().getTime());
}

//選択状態を表すために背景色を変更するとBackground-Imageが無効になるので再設定する。
Dice.prototype.setImageUrl = function() {
  this.cube.setAttribute("needsImage", false);
  for(var i=0; i<this.cube.childNodes.length; i++) {
    var face = this.cube.childNodes[i];
    if (face.nodeType == 1) {
      face.style.backgroundImage = face.getAttribute("url");
      face.style.color = "transparent";
    }
  }
}

//乱数生成
Dice.rand = function() {
  Dice._seed = Math.random(Dice._seed);
  return Math.random(new Date().getTime());
}

//タイマーイベントで呼び出される。
Dice.roll = function(o) {
  //selected属性をリセットする。
  o.cube.setAttribute("selected", "false");
  //引数のDiceインスタンスのrollを呼ぶ。
  o.roll();
}

//Diceを振る。WebKitの場合のみ実行。
Dice.prototype.roll = function() {
  if (!isWebKit || this.busy) return;
  this.busy = true;
  //画像がセットされていないときに画像をセット。
  if (this.cube.getAttribute("needsImage") == "true") this.setImageUrl();
  //this.cube.setAttribute("selected", "false");
  try {
    //視点の高さ(距離)設定
    var dist = (isIOS ? -1560 : -800);
    //高さによりロジックを変える。
    if (this.flag && this.dist > dist) {
      //まだdistまで落下していない場合。
      //乱数初期化
      Dice._seed = Math.random(new Date().getTime());
      //乱数を用いて回転角設定。
      var r1 = Dice.rand() * 40;
      var r2 = Dice.rand() * 40;
      var r3 = Dice.rand() * 40;
      //直前の回転角に加え、今回の回転をセット。
      this.x += r1;
      this.y += r2;
      this.z += r3;
      //落下速度調整
      this.dist -= 70;
      //次回の回転をTimerでセット。
      this.timer = setTimeout(Dice.roll, this.interval, this);
    } else {
      //distまで落下した一番正面を向いている面を真正面に向ける。
      var mX = this.x % 90; var amX = Math.abs(mX);
      var mY = this.y % 90; var amY = Math.abs(mY);
      var mZ = this.z % 90; var amZ = Math.abs(mZ);
      if (amX > 1 || amY > 1 || amZ > 1) {
        if (this.interval > 10) this.interval -= 1;
        this.x = (amX > 3 ?  this.x - 3 : this.x - mX);
        this.y = (amY > 3 ?  this.y - 3 : this.y - mY);
        this.z = (amZ > 3 ?  this.z - 3 : this.z - mZ);
        this.flag = false;
        this.timer = setTimeout(Dice.roll, this.interval, this);
        this.interval = 10;
      } else {
        //真正面を向いたら組み合わせ結果を表示。
        this.setSpots();
      }
    }
    //上の計算結果でTransformを実行。
    this.cube.style.webkitTransform = "translateZ(" + this.dist + "px) "
                                    + "rotateX("+ this.x + "deg) "
                                    + "rotateY("+ this.y + "deg) "
                                    + "rotateZ("+ this.z + "deg) ";
    this.cube.style.display = "inline";
  } catch(e) {
    alert("Dice.roll: " + e);
  } finally {
    this.busy = false;
  }
}

//正面を向いている目を判断する。
//X,Y,Z軸各々について回転角から面の位置を判断する。
//Timerイベントで実行されるときはthis=window。
Dice.prototype.setSpots = function() {
  //360°回転すると元に戻るので残りの角度で判断。
  //rotateの順に合わせ、Z、Y、Xの順で判断。順序は重要。
  var rz = this.z % 360;
  //90°毎に面をシフト。
  while(rz >= 90) {
    rz -= 90;
    var t = this.fArr[0];
    this.fArr[0] = this.fArr[4];
    this.fArr[4] = this.fArr[5];
    this.fArr[5] = this.fArr[1];
    this.fArr[1] = t;
  }
  var ry = this.y % 360;
  while(ry >= 90) {
    ry -= 90;
    var t = this.fArr[2];
    this.fArr[2] = this.fArr[4];
    this.fArr[4] = this.fArr[3];
    this.fArr[3] = this.fArr[1];
    this.fArr[1] = t;
  }
  var rx = this.x % 360;
  while(rx >= 90) {
    rx -= 90;
    var t = this.fArr[2];
    this.fArr[2] = this.fArr[5];
    this.fArr[5] = this.fArr[3];
    this.fArr[3] = this.fArr[0];
    this.fArr[0] = t;
  }
  //5つのcubeのどれかを判断。
  var n = new Number(this.cube.id.charAt(4));
  Dice.spotsArr[n-1] = this.fArr[2];
  //5つのDice全部の目がセットされたら役を表示。
  if (Dice.spotsArr[0]>0
   && Dice.spotsArr[1]>0
   && Dice.spotsArr[2]>0
   && Dice.spotsArr[3]>0
   && Dice.spotsArr[4]>0) {
    Dice.showSpots();
  }
}

//役判断用フラグ
Dice.spotsArr = new Array(0,0,0,0,0);
Dice.pair1 = null;
Dice.pair2 = null;
Dice.three = null;
Dice.four = null;
Dice.five = null;
Dice.fullHouse = null;

//回転終了時の正面の目の組み合わせから役を判断、表示する。
Dice.showSpots = function() {
  //役のフラグをクリア
  Dice.pair1 = Dice.pair2 = Dice.three = Dice.four = Dice.five = Dice.fullHouse = null;
  //正面を向いている目の配列をコピーし、ソートする。
  var arr = new Array(Dice.spotsArr[0],Dice.spotsArr[1],Dice.spotsArr[2],Dice.spotsArr[3],Dice.spotsArr[4]);
  arr.sort();
  //役の判断。
  Dice.findCombinations(arr);
  //役のフラグから役の名前を設定。
  var str = "";
  if (Dice.pair1 != null) {
    if (Dice.pair2 == null) str += "One Pair[" + Dice.pair1 +"]<br>";
    else str += "Tow Pairs[" + Dice.pair1 + "," + Dice.pair2 +"]<br>";
  } else if (Dice.three != null) {
     str += "Three Of A Kind[" + Dice.three +"]<br>";
  } else if (Dice.four != null) {
     str += "Four Of A Kind[" + Dice.four +"]<br>";
  } else if (Dice.five != null) {
     str += "Five[" + Dice.five +"]<br>";
  } else if (Dice.fullHouse != null) {
     str += "Full House[" + Dice.fullHouse +"]<br>";
  } else {
     str += "All Different[" + arr +"]<br>";
  }
  //役を表示。
  var div = document.getElementById("memo");
  div.innerHTML = str + "\n" + Dice.spotsArr;
}

//役を見つける。
Dice.findCombinations = function(arr) {
  var i=1, j=0, n=0, s=arr[0];
  for(; i<5; i++) {
    if (s == arr[i]) {
      n++;
    } else {
      Dice.setPairs(n+1, s);
      n = 0;
      s = arr[i];
    }
  }
  if (n > 0) Dice.setPairs(n+1, s);
  if (Dice.three && Dice.pair1) {
    Dice.fullHouse = Dice.three + "," + Dice.pair1;
    Dice.three = Dice.pair1 = null;
  }
}

//pair, three, four, fiveのフラグをセット。
Dice.setPairs = function(n, s) {
  if (n == 2) {
    if (Dice.pair1 == null) Dice.pair1 = s;
    else Dice.pair2 = s;
  } else if (n == 3) {
    Dice.three = s;
  } else if (n == 4) {
    Dice.four = s;
  } else if (n == 5) {
    Dice.five = s;
  }
}

//----- Diceを使用するfunction -----

var cubes = null;

//初期化(body.onloadなどで実行)
function init() {
  //cubeのDIVを配列にセット
  cubes = new Array(cube1, cube2, cube3, cube4, cube5);
  var divs = document.getElementsByTagName("DIV");
  //全Diceを選択状態にする。
  selAll();
  //Diceを振る。
  roll();
}

//選択状態のDiceを振る。
function roll() {
  Dice.init();
  for(var i=0; i<5; i++) {
    if (cubes[i].getAttribute("selected") == "true") new Dice(cubes[i]).roll();
  }
}

//Diceを選択状態にセット、または反転する。
function selCube(c, selAll) {
  if (!selAll && c.getAttribute("selected") == "true") {
//touchまたはclickされ、選択状態の場合は非選択状態にする。
    c.setAttribute("selected", "false");
    new Dice(c).setImageUrl();
  } else {
//sellAllから呼ばれた時、または非選択状態のときは選択状態にする。
    c.setAttribute("needsImage", "true");
    c.setAttribute("selected", true);
    var state = "true";
    //cの子要素の背景色、文字色を設定。
    var color = "rgba(130, 130, 255, 0.5)"; //半透明
    for(var i=0; i<c.childNodes.length; i++) {
      if (c.childNodes[i].nodeType == 1) { //ElementType
        if (state == "true") c.childNodes[i].style.background = color;
        if (state == "true") c.childNodes[i].style.color = "black";
      }
    }
  }
}

function selAll() {
  for(var i=0; i<5; i++) {
    selCube(cubes[i], true);
  }
}

3D Translate実装例(2) Diceについて