ウェブサイト作成用備忘録・１８号：three.jsで似たようなオブジェクトをまとめて生成し、後で個別にアニメーションさせたい【コピペでプレビュー】

タイトル通りの目標を実現させようとした結果、最終的にこのようなサンプルが完成しました。

学習が進むたびにプレビューのコードがどんどん長くなってそろそろ邪魔になってきたので、今回は折りたたみ機能を活用してみました！

<details>
<summary>__【コピペでプレビュー】__</summary><div>

```HTML

<!DOCTYPE html>

<html>

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <!-- jquery -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <!-- TweenMax -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
  <!-- three.js -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r127/three.min.js"></script>
  <!-- OrbitControls.js -->
  <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r109/examples/js/controls/OrbitControls.js"></script>
</head>

<body style="margin: 0;">

  <!-- 3Dオブジェクト -->
  <div id="3dCanvas" style="position: fixed; z-index: 0;"></div>
  <!-- /3Dオブジェクト -->

  <!-- HTMLパート -->
  <div
    style="position: fixed; bottom: 0; right: 50%; transform: translateX(50%); height: 20%; display: flex; justify-content: center; z-index: 1;">
    <div
      style="border-radius: 25px; box-sizing: border-box; padding: 1em 0; min-width: 350px; width: 99%; max-width: 450px; height: 99%; background-color: rgba(0,0,0,.7); display: flex; flex-flow: wrap; align-items: center; text-align: center; font-size: calc(.5rem + 2vmin); color: #ffffff;">
      <!-- 入力パート -->
      <button class="" id="up" style="margin: 0 auto; padding: 5px 0; width: 10em;" type="button">↑</button>
      <div style="width: 100%;">
        <button class="" id="left" style="margin: auto; padding: 5px 0; width: 10em; vertical-align: middle;"
          type="button">←</button>
        <button class="" id="right" style="margin: auto; padding: 5px 0; width: 10em; vertical-align: middle;"
          type="button">→</button>
      </div>
      <button class="" id="down" style="margin: 0 auto; padding: 5px 0; width: 10em;" type="button">↓</button>
      <!-- /入力パート -->
    </div>
  </div>
  <!-- /HTMLパート -->

  <script>
    jQuery(document).ready(function () {

      // カメラ用変数
      let controls;
      let camera;
      let near = 0.1;
      let fov = 30;
      let far = 100;
      let target = new THREE.Vector3(0, 1, 0);
      let cameraX = 0;
      let cameraY = 10;
      let cameraZ = 5;

      // ページの読み込みを待つ
      $(window).on("load", function () {

        // レンダラーを作成
        const canvas = document.getElementById("3dCanvas");
        const renderer = new THREE.WebGLRenderer({
          alpha: true,
        });
        $("#3dCanvas").append(renderer.domElement);

        function cameraResize() {
          // カメラを作成
          camera = new THREE.PerspectiveCamera();
          // カメラコントローラーを作成
          controls = new THREE.OrbitControls(camera, canvas);
          // レンダラーのサイズ設定（高画質対応）
          renderer.setSize(window.innerWidth, window.innerHeight);
          renderer.setPixelRatio(window.devicePixelRatio);
          // カメラ設定
          camera.near = near;
          camera.fov = fov;
          camera.aspect = window.innerWidth / window.innerHeight;
          camera.far = far;
          // カメラポジション
          camera.position.set(cameraX, cameraY, cameraZ);
          // 原点方向を見つめる
          camera.lookAt(target);
          // カメラコントローラー制御
          // 自動回転
          controls.autoRotate = true;
          // 自動回転速度
          controls.autoRotateSpeed = 1;
          // 垂直回転制限
          controls.maxPolarAngle = 1.55;
          controls.minPolarAngle = 0;
          // パン移動無効化
          controls.enablePan = false;
          // カメラを登録
          camera.updateProjectionMatrix();
        }
        // レンダラー・カメラの初期設定
        cameraResize();

        // リサイズ時にレンダラー・カメラの再設定
        $(window).resize(function () {
          fov = camera.fov;
          cameraX = camera.position.x;
          cameraY = camera.position.y;
          cameraZ = camera.position.z;
          cameraResize();
        });

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

        // 床を作成
        const planeSize = 5;
        const planeGeometry = new THREE.PlaneGeometry(planeSize, planeSize);
        const normalMaterial = new THREE.MeshLambertMaterial({
          color: 0x333333,
        });
        const planeMesh = new THREE.Mesh(planeGeometry, normalMaterial);
        planeMesh.position.set(0, -10, 0);
        planeMesh.rotation.x = Math.PI * -.5;
        scene.add(planeMesh);

        // 柱を9本作成
        const pillarSize = 0.5;
        const pillarGeometry = new THREE.BoxGeometry(pillarSize, 0.1, pillarSize);
        const pillarMaterial = new Array(9);
        for (var n = 0; n < 9; n++) {
          pillarMaterial[n] =  new THREE.MeshLambertMaterial({
            color: 0x333333,
            transparent: true,
            opacity: 1,
          });
        }
        let pillarMesh = new Array(9);
        let pillar_n = 0;
        for (var z = 0; z < 3; z++) {
          for (var x = 0; x < 3; x++) {
            pillarMesh[pillar_n] = new THREE.Mesh(pillarGeometry, pillarMaterial[pillar_n]);
            pillarMesh[pillar_n].position.set((x - 1) * 1, -10, (z - 1) * 1);
            scene.add(pillarMesh[pillar_n]);
            TweenMax.to(pillarMesh[pillar_n].scale, 1, {
              delay: pillar_n / 3,
              y: 100,
            });
            TweenMax.to(pillarMesh[pillar_n].position, 1, {
              delay: pillar_n / 3,
              y: -5,
            });
            pillar_n++;
          }
        }
        function pillar_loop() {
          TweenMax.to(pillarMaterial, .1, {
            opacity: 0.5,
          });
          if (pillar_n > 8) {
            pillar_n = 0;
          } else if (pillar_n < 0) {
            pillar_n = 8;
          }
          TweenMax.to(pillarMaterial[pillar_n], .5, {
            opacity: 1,
          });
        }
        pillar_loop();

        // 柱を個別にアニメーション
        $("#up").click(function () {
          TweenMax.to(pillarMesh[pillar_n].scale, .5, {
            y: "+=10",
          });
          TweenMax.to(pillarMesh[pillar_n].position, .5, {
            y: "+=0.5",
          });
        });
        $("#right").click(function () {
          pillar_n++;
          pillar_loop();
        });
        $("#left").click(function () {
          pillar_n--;
          pillar_loop();
        });
        $("#down").click(function () {
          TweenMax.to(pillarMesh[pillar_n].scale, .5, {
            y: "-=10",
          });
          TweenMax.to(pillarMesh[pillar_n].position, .5, {
            y: "-=0.5",
          });
        });

        // 環境光源を作成
        const ambientlight = new THREE.AmbientLight(0xFFFFFF, 0.5);
        scene.add(ambientlight);
        // 平行光源を作成
        const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 0.5);
        directionalLight.position.set(0, 1, 0.5);
        scene.add(directionalLight);

        // ループアニメーション設定
        function tick() {
          requestAnimationFrame(tick);

          // カメラコントローラーを更新
          controls.update();

          // レンダリングを更新
          renderer.render(scene, camera);
        }
        tick();
      });
    });
  </script>
</body>

</html>

```

</div></details>

####今回工夫した箇所

#####１・配列の中にメッシュやマテリアルを格納する事で、for文でまとめて生成した後に、配列の中で変更したい部分だけを後から個別に操作できるようになりました。（例えばこの部分とか）

```javascript

        // 柱を9本作成
        const pillarSize = 0.5;
        const pillarGeometry = new THREE.BoxGeometry(pillarSize, 0.1, pillarSize);
        const pillarMaterial = new Array(9);
        for (var n = 0; n < 9; n++) {
          pillarMaterial[n] =  new THREE.MeshLambertMaterial({
            color: 0x333333,
            transparent: true,
            opacity: 1,
          });
        }
        let pillarMesh = new Array(9);
        let pillar_n = 0;
        for (var z = 0; z < 3; z++) {
          for (var x = 0; x < 3; x++) {
            pillarMesh[pillar_n] = new THREE.Mesh(pillarGeometry, pillarMaterial[pillar_n]);
            pillarMesh[pillar_n].position.set((x - 1) * 1, -10, (z - 1) * 1);
            scene.add(pillarMesh[pillar_n]);
            TweenMax.to(pillarMesh[pillar_n].scale, 1, {
              delay: pillar_n / 3,
              y: 100,
            });
            TweenMax.to(pillarMesh[pillar_n].position, 1, {
              delay: pillar_n / 3,
              y: -5,
            });
            pillar_n++;
          }
        }

```

#####注意点・このプレビューを作った時点では、配列の全ての要素に順番に処理を行うforEach文の方が良いかと思ったのですが、メッシュを格納する前の空配列の段階だとforEach文が機能しなかった為、まとめて生成する時はfor文の繰り返し処理で配列にメッシュやマテリアルを格納して、一度格納した後に配列全ての要素を同時に操作する場合はforEach文を使用すると楽だと思います。

#####２・ボックスジオメトリを地面から伸ばして柱の様な外観にしようとした際、大きさだけを変更するとオブジェクト中央を基準に上下に向かって伸びていく挙動だった為、大きさの変更と同時にY軸の高さを調整しています（大きさと高さの比率については…目分量で頑張りました）

…今回はこれだけですね。
手隙の時間で色々勉強してはいるのですが、一定の期間毎にアウトプット出来る範囲をまとめていくと、単体では薄味な内容になってしまいますね。

自分用の備忘録ですが、誰かの参考になれば幸いです。