Three.jsで筒を作ってみる


## はじめに

Three.jsで筒を作りたいなと思い、挑戦しました。
結果、作ったのは↓の2種類

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/c7e997b0-6474-7823-0d6c-6e87faaff461.png)

## 1.CylinderGeometryで筒を作ってみた

#### 1-1.参考

- [CylinderGeometry](https://threejs.org/docs/#api/en/geometries/CylinderGeometry)
- [Three.js　マテリアルのプロパティ（高度な質感表現）](http://gupuru.hatenablog.jp/entry/2013/12/08/215106)

#### 1-2.スクリプト


```html:CylinderGeometryで筒.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <style>
      body {
          margin: 0;
      }
  </style>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>

  <script>
    // ページの読み込みを待つ
    window.addEventListener('load', init);

    function init() {

      // サイズを指定
      const width = 960;
      const height = 540;

      // レンダラーを作成
      const renderer = new THREE.WebGLRenderer(
        {
          canvas: document.querySelector('#myCanvas'),
          alpha: true
        }
      );
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(width, height);
      renderer.setClearColor( 0x000000, 0 ); // クリアカラーで消す、２つめの引数が0

      // シーンを作成
      const scene = new THREE.Scene();

      // カメラを作成
      const camera = new THREE.PerspectiveCamera(45, width / height);
      camera.position.set(0, 0, +1000);


      // ↓ 筒 -----------------------------------------------------------
      const loader = new THREE.TextureLoader();
      loader.load(
        './tex001.png',
        function(texture){
          // 円柱
          const cylinderMaterial01 = new THREE.MeshBasicMaterial(
            {
              map:texture,
              side: THREE.DoubleSide
            }
          );
          // ↑ side について
          // THREE.FrontSide ：表面だけ描画
          // THREE.BackSide  ：裏面だけを描画
          // THREE.DoubleSide：両面を描画
          
          let radiusTop = 250; //上面の半径です。0にすると円錐になります。
          let radiusBottom = 250; //底面の半径です。
          let objHeight = 400; //高さです。
          let radiusSegments = 32; //円周の分割数です。
          let heightSegments = 32; //高さの分割数です。
          //let openEnded = false; //true：フタをしない,false：フタをする
          let openEnded = true; //true：フタをしない,false：フタをする

          const cylinder01 = new THREE.Mesh( 
              //円柱のジオメトリー(上面半径,下面半径,高さ,円周分割数)
              new THREE.CylinderGeometry(radiusTop, radiusBottom, objHeight, radiusSegments, heightSegments, openEnded),
              cylinderMaterial01
          );

          cylinder01.position.set(2, 0, 0); //(x,y,z)
          scene.add(cylinder01);

          tick();

          // 毎フレーム時に実行されるループイベントです
          function tick() {
            cylinder01.rotation.y += 0.01;
            cylinder01.rotation.x += 0.015;
            cylinder01.rotation.z += 0.02;
            renderer.render(scene, camera); // レンダリング
            requestAnimationFrame(tick);
          }
        }
      );
      // ↑ 筒 -----------------------------------------------------------

      //環境光源(アンビエントライト)：すべてを均等に照らす、影のない、全体を明るくするライト
      const ambient = new THREE.AmbientLight(0xFFFFFF, 0.9);
      scene.add(ambient); //シーンにアンビエントライトを追加

      //平行光
      const light = new THREE.DirectionalLight(0xFFFFFF, 1);
      light.position.set(200,200,300);
      scene.add(light);
    }
  </script>
</head>
<body>
  <canvas id="myCanvas"></canvas>
</body>
</html>
```

#### 1-3.ポイント

ポイントは2つ

- [ポイント１] CylinderGeometryのフタを外す

```javascript
//let openEnded = false; //true：フタをしない,false：フタをする
let openEnded = true; //true：フタをしない,false：フタをする

//円柱のジオメトリー(上面半径,下面半径,高さ,円周分割数)
new THREE.CylinderGeometry(radiusTop, radiusBottom, objHeight, radiusSegments, heightSegments, openEnded)
```

- [ポイント２] 両面にテクスチャを適用する

```javascript
const cylinderMaterial01 = new THREE.MeshBasicMaterial(
{
    map:texture,
    side: THREE.DoubleSide
}
// ↑ side について
// THREE.FrontSide ：表面だけ描画
// THREE.BackSide  ：裏面だけを描画
// THREE.DoubleSide：両面を描画
```

## 2.Geometryクラスで筒を作ってみた

#### 2-1.参考

【three.js】Geometryクラスで正三角柱をつくる
https://qiita.com/baikichiz/items/4d8cf1a4f0f58d986152

#### 2-2.各点の番号

配列 `vertices` に格納した順番で番号が振られる。（面を作成するときに使用）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/cb6cb8a3-7968-76ee-a5aa-ce65ee94d668.png" width=400>


#### 2-3.スクリプト

```html:Geometryクラスで筒.html

<!DOCTYPE html>

<html>
<head>
    <meta charset=utf-8>
    <title>My first three.js app</title>
    <style>
        body { margin: 0; }
        canvas { width: 100%; height: 100% }
    </style>
</head>
<body>

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/105/three.min.js"></script>
<script>
// 三角柱をつくる。原点は底面正三角形の重心、柱の高さの半分の位置とする。
// length:
// height:
// depth:
function createTriangle(argWidth, argHeight, argDepth)
{

    var faceColor = 0x0000FF;
    var hW = argWidth / 2.0;
    var hH = argHeight/ 2.0;
    var hD = argDepth;

    var rate = 0.1
    var sW = argWidth / 2.0 - argWidth * rate;
    var sH = argHeight/ 2.0 - argHeight * rate;


    var vertices = [
        new THREE.Vector3(-hW,     0, -hH), // 0
        new THREE.Vector3( hW,     0, -hH), // 1
        new THREE.Vector3( hW,     0,  hH), // 2
        new THREE.Vector3(-hW,     0,  hH), // 3
        new THREE.Vector3(-hW,   -hD, -hH), // 4
        new THREE.Vector3( hW,   -hD, -hH), // 5
        new THREE.Vector3( hW,   -hD,  hH), // 6
        new THREE.Vector3(-hW,   -hD,  hH), // 7

        new THREE.Vector3(-sW,     0, -sH), // 8
        new THREE.Vector3( sW,     0, -sH), // 9
        new THREE.Vector3( sW,     0,  sH), // 10
        new THREE.Vector3(-sW,     0,  sH), // 11

        new THREE.Vector3(-sW,   -hD, -sH), // 11
        new THREE.Vector3( sW,   -hD, -sH), // 12
        new THREE.Vector3( sW,   -hD,  sH), // 13
        new THREE.Vector3(-sW,   -hD,  sH), // 14
    ];
    var faces = [
        // ↓ 上部 ↓ ----
        new THREE.Face3(0, 3, 8),
        new THREE.Face3(3, 11, 8),
        new THREE.Face3(3, 2, 11),
        new THREE.Face3(2, 10, 11),
        new THREE.Face3(2, 1, 10),
        new THREE.Face3(10, 1, 9),
        new THREE.Face3(1, 0, 9),
        new THREE.Face3(9, 0, 8),

        // ↑ 上部 ↑ ----
        new THREE.Face3(1, 2, 5),
        new THREE.Face3(6, 5, 2),
        new THREE.Face3(6, 2, 3),
        new THREE.Face3(7, 6, 3),
        new THREE.Face3(7, 3, 0),
        new THREE.Face3(4, 7, 0),
        new THREE.Face3(0, 1, 5),
        new THREE.Face3(0, 5, 4),
        // ↓ 下部 ↓ ----
        new THREE.Face3(4, 12, 7),
        new THREE.Face3(12,15, 7),
        new THREE.Face3(7, 15, 14),
        new THREE.Face3(7, 14, 6),
        new THREE.Face3(14, 5, 6),
        new THREE.Face3(14, 13, 5),
        new THREE.Face3(13, 4, 5),
        new THREE.Face3(13, 12, 4),
        // ↑ 下部 ↑ ----
    // 内側
        new THREE.Face3(13, 9, 8),
        new THREE.Face3(12, 13, 8),
        new THREE.Face3(8, 11, 12),
        new THREE.Face3(15, 12, 11),
        new THREE.Face3(11, 10, 14),
        new THREE.Face3(11, 14, 15),
        new THREE.Face3(13, 10, 9),
        new THREE.Face3(13, 14, 10),
        new THREE.Face3(8, 11, 12),
        new THREE.Face3(15, 12, 11),
    ];

    var geometry = new THREE.Geometry();
    var i = 0;
    for (i = 0; i < vertices.length; i++) {
        geometry.vertices.push(vertices[i]);
    }
    for (i = 0; i < faces.length; i++) {
        geometry.faces.push(faces[i]);
    }

    var material = new THREE.MeshBasicMaterial({ color: faceColor });

    // 三角柱のワイヤーフレームを描く
    var wireframeGeometry = new THREE.EdgesGeometry(geometry);
    var wireframeMaterial = new THREE.LineBasicMaterial({ color: 0xFFFFFF, linewidth: 4 });

    var triangleMesh = new THREE.Mesh(geometry, material);
    var wireframe = new THREE.LineSegments(wireframeGeometry, wireframeMaterial);

    triangleMesh.add(wireframe);
    return triangleMesh;
}

// x, y, z軸を赤、緑、青で描く
// length: 軸の長さ
function createAxes(length)
{
    var createOneAxis = function (color, vertex) {
        var material = new THREE.LineBasicMaterial({
            color: color
        });
        var vertices = [
            new THREE.Vector3(10, -10, -10),
            vertex
        ];
        var geometry = new THREE.Geometry();
        geometry.vertices = vertices;

        var line = new THREE.Line(geometry, material);
        return line;
    };

    return [
        createOneAxis(0x770000, new THREE.Vector3(length, 0, 0)),
        createOneAxis(0x007700, new THREE.Vector3(0, length, 0)),
        createOneAxis(0x000077, new THREE.Vector3(0, 0, length))
    ];
}


   window.onload = function() {

    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    var mesh = createTriangle(20, 20, 20);  scene.add(mesh);
    var axes = createAxes(100); axes.forEach(function (a) { scene.add(a);})
    //----------------------------//
    var hW = 10;
    var hH = 10;
    var hD = 20;

        var rate = 0.1
        var sW = hW - hW * 2 * rate;
        var sH = hH - hW * 2 * rate;

    //----------------------------//

    camera.position.z = 30;
    camera.position.y = 30;
    camera.position.x = 15;
    camera.lookAt(0, 0, 0);

    function animate() {
        mesh.rotation.x += 0.01;
        mesh.rotation.y += 0.01;
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
    }

    /////-- ↓ マウスイベント ↓ --//////
    var mousedown = false;
    renderer.domElement.addEventListener('mousedown', function(e) {
            mousedown = true;
    }, false);

    renderer.domElement.addEventListener('mousemove', function(e) {
        if (!mousedown) return;
        mesh.rotation.x += 0.1;
        mesh.rotation.y += 0.1;
        render();
    }, false);

    renderer.domElement.addEventListener('mouseup', function(e) {
            mousedown = false;
    }, false);

    function render(){
            renderer.render(scene, camera);
    }
    /////-- ↑ マウスイベント ↑ --//////
    animate();
   }
</script>
</body>
</html>
```

## おわりに

個人開発のWebARコンテンツ `ウソ穴` に必要な部品作りとして調査しました。
Geometryクラスで筒を作るのは、面倒な上に筒っぽさの完成度が低い。