３Dブロック作成ツールを作ってみる【Three.js】

３Dのブロック作成ツールを作成してみました。


#プログラムの動き
<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="naru0607" data-slug-hash="WNrMped" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Cube-20200701">
  <span>See the Pen <a href="https://codepen.io/naru0607/pen/WNrMped">
  Cube-20200701</a> by Naru0607 (<a href="https://codepen.io/naru0607">@naru0607</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>




##コード
```javascript
const boxes = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const size = 0.5;
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({
  antialias: true
});
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100);
const light = new THREE.DirectionalLight(0xFFFFFF);
const geometry = new THREE.BoxGeometry(size, size, size);
let material = new THREE.MeshLambertMaterial({
  color: 0xFF0000
});
const interval = 100;
const group = new THREE.Group();

camera.position.set(size / 2, 0, Math.cbrt(boxes.length) * 4);
light.position.set(0, 100, 100);
scene.add(light);

for (let i = 0; i < boxes.length; ++i) {
  ((i) => {
    if (boxes[i] === 0) {
      return;
    }

    const cube = new THREE.Mesh(geometry, material);
    const pos = getPosition(i);
    
    cube.position.set(
      pos.x * size,
      pos.y * size,
      pos.z * size
    );

    setTimeout(() => {
      group.add(cube);
    }, i * interval);
  })(i);
}

scene.add(group);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

render();

function getPosition(i) {
  const length = boxes.length;

  const x = i % Math.cbrt(length);
  const y = Math.floor(i / Math.pow(Math.cbrt(length), 2));
  const z = Math.floor(i / Math.cbrt(length)) % Math.cbrt(length);
  
  return {
  x: x,
  y: y,
  z: z
};
}

function render() {
   group.rotation.x += .01;
   group.rotation.y += .01;
   group.rotation.z += .01;
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}
```

##コードの解説
以下のように、Boxを置くか置かないかを"0","1"で保持しています。

```javascript
const boxes = [1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
```

配列に格納されている値を順に処理して"0"の場合には何もせずにループを抜けて、
"1"の場合にはBOXを置いています。


```javascript
for (let i = 0; i < boxes.length; ++i) {
  ((i) => {
    if (boxes[i] === 0) {
      return;
    }

    const cube = new THREE.Mesh(geometry, material);
    const pos = getPosition(i);

    cube.position.set(
      pos.x * size,
      pos.y * size,
      pos.z * size
    );
```

#どの順番でBOXを配置していくか
x軸、Y軸、Z軸を以下のようなイメージで定義しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612352/bca9ffb7-6b92-6054-9404-affbf5a7ebf2.png)

次に配列要素iの値に応じてどの順番でBOXを配置していくかを決定します。
今回はBOX配置順を次のように決めています。(コードでは３×３×３ですが、２×２×２で例をあげています)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612352/12c6b96a-3190-cd41-cf6d-e59cfd7d7d1d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612352/6ca4b2a7-c6c1-1d9e-82ed-0852820c018c.png)

getPosition関数のなかで配列要素iの値に応じてどこにBOXを配置していくかを決めているのですが、この際に重要なのは配列数と配列要素iの値の２つの情報から上記テーブルのX,Y,Z座標のそれぞれの値を表現することです。こうすることで２×２×２や３×３×３以外にも、６×６×６というようにボックス数を増やしていった場合でも対応ができるようになります。この点が難しいのですが、Excelを使用して以下に具体例を記載しています。


##マトリックスの数
マトリックスの数は、配列数の立方根(∛)で求めることができます。
例えば、
配列数が8個の場合には、２×２×２なので、２
配列数が27個の場合には、３×３×３なので、３
となります。

#X軸、Y軸、Z軸
X軸、Y軸、Z軸を順に説明します。
##X軸
Xは配列要素iの値をマトリックス数で割ったときの余りで求めることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612352/697ecac8-660c-43f2-e52c-90c6110d9b1a.png)
※ExcelのMOD関数(数値,除数)は、数値を除数で割ったときの余りを返します。

###該当コード
```javascript
const x = i % Math.cbrt(length);
```

##Y軸
Yは配列要素iの値をマトリックス数を2倍した値で割ったときの整数部で求めることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612352/19879878-79db-6803-14a3-7be078b24b0f.png)
※ExcelのQUOTIENT関数(数値,除数)は、数値を除数で割ったときの整数部を返します。余り部分(小数部) は切り捨てられます。 

###該当コード
```javascript
const y = Math.floor(i / Math.pow(Math.cbrt(length), 2));
```

##Z軸
Zは配列要素iの値をマトリックス数で割って求めた小数点以下切捨ての値にさらに、マトリックス数で割ったときの余りで求めることができます。
※Excelの関数では、小数点以下切捨てを求める際にはROUNDDOWN関数を使用します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612352/713ba2aa-8777-76f0-501b-610caf6f1a22.png)



###該当コード
```javascript
const z = Math.floor(i / Math.cbrt(length)) % Math.cbrt(length);
```

最終的には次のようになります。

```javascript
function getPosition(i) {
  const length = boxes.length;

  const x = i % Math.cbrt(length);
  const y = Math.floor(i / Math.pow(Math.cbrt(length), 2));
  const z = Math.floor(i / Math.cbrt(length)) % Math.cbrt(length);

  return {
  x: x,
  y: y,
  z: z
};
}
```

##プログラムの動き(回転アニメーションをOFF)
[rerun]ボタンをクリックしてください。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="naru0607" data-slug-hash="jOWZwqy" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Cube-20200701-1">
  <span>See the Pen <a href="https://codepen.io/naru0607/pen/jOWZwqy">
  Cube-20200701-1</a> by Naru0607 (<a href="https://codepen.io/naru0607">@naru0607</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>


#BOXに色付けをする
先ほどの例では、配列名Boxesには"0","1"のいずれかを保持していましたが、
少し発展させて配列の要素に、"red","yellow","green","blue"のように色情報を持たせた場合の動きです。これで任意の色でブロックの配置ができるようになりました。

##プログラムの動き
[rerun]ボタンをクリックしてください。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="naru0607" data-slug-hash="BajmraQ" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Cube-20200705-2">
  <span>See the Pen <a href="https://codepen.io/naru0607/pen/BajmraQ">
  Cube-20200705-2</a> by Naru0607 (<a href="https://codepen.io/naru0607">@naru0607</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>