ウェブサイト作成用備忘録・１５号：three.jsの学習に挑戦【コピペでプレビュー】

今回はwebGLの学習に挑戦してみました！
…が、まだまだ継続中の為、今回は学習の過程をまとめてみようと思います。

####１・webGLの直書きはとても難しい…　→　ライブラリを探してみる！

最初はブラウザ上での3Dグラフィックの表現に挑戦してみたくてwebGLについて検索する所から始めてみました。

しかし、実際に色々調べてみるとwebGLの直書きはとても難しく、基本的には色々なライブラリを併用して扱う技術なのだと分かりました。

####２・three.jsを発見　→　学習の為のWEBサイトを漁る

オススメのライブラリを探す過程で、three.jsというライブラリが人気だという事が分かりました。
人気のライブラリは日本語で解説された参考サイトが多く、英語の公式ドキュメントで調べても分からない事を日本語で検索して解決できる所が強みだと思います。

#####参考サイト集

　↓　０から勉強し始めて、ある程度の基本を覚えるまでは下記のサイトの内容が分かりやすく、とても参考になりました。

https://ics.media/tutorial-three/

　↓　こちらのサイトは英語版の記事を日本語に翻訳したものなので多少読みにくさを感じる時もありますが、　↑　のサイトである程度three.jsに慣れてから読み始めると、より細かい設定をやライブラリの仕組みを日本語でより深く学習する事が出来るサイトです。

https://threejsfundamentals.org/threejs/lessons/ja/

　↓　こちらの方のブログでは、より具体的なテクニックやトラブルの解決方法などが掲載されており、先述したサイトである程度学習を進め、自主課題作成の時に分からないことや躓いている事を調べたい時に参考になりました。

https://gupuru.hatenablog.jp/

　↓　こちらが公式ドキュメントです。日本語で書かれたページもありますが、基本英語のドキュメントなので、英語に疎い自分の場合、日本語の参考サイトで自分なりに色々調べながら学習を進めていき、three.jsに慣れてきた頃にやっと翻訳しながら分からない事を調べたり、やりたい事を実現する為の方法をサンプル一覧を見ながら調べていく事が出来るようになりました。

https://threejs.org/docs/#manual/ja/introduction/Creating-a-scene

そして…

three.jsでアニメーションを作成出来るようになると、「じゃあ、webGLで具体的に何を作ってみよう？」という段階になり、色々試していたのですが、ここで問題が発生しました。

three.jsで作成した3Dオブジェクトをアニメーションさせる時に、基本的にはrequestAnimationFrameメソッドを使用して１フレーム毎に処理を加え続ける事でアニメーションされるのですが…

現時点の自分では永続的に回転し続ける様なループアニメーションなら何とか実装できるのですが、あるボタンを押したら処理１→２→３の様に順番に別々のアニメーションを行ったりするような、複雑な動作をthree.jsだけでは実現できませんでした。

######※その気になれば時間をかけて実現する事も出来るとは思うのですが、とにかくもっと楽して解決したかったのです！

####three.js単品だと難しい場面が出てくる　→　更に別のライブラリ、TweeenMaxを導入

今の所、TweeenMax自体の機能についてはまだまだ分からない事が多いのですが…

####分からないなりに必要な部分だけ調べて使っている状態でも苦戦していたアニメーション部分を楽に解決する事が出来たので、使えるライブラリやプラグインはバンバン使って試していくのが良いと思います！

最後にコピペでプレビュー枠ですが、実用性というよりも、何とか動かすので精一杯な作品が出来上がりました…

良くも悪くも誰かの参考になれば幸いなので、記念に載せておきます。

```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>
  <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r109/examples/js/controls/OrbitControls.js"></script>
</head>
<body style="margin: 0;">
<div id="myCanvas"></div>
</body>
<script>
  jQuery(document).ready(function () {
    // ページの読み込みを待つ
    $(window).on("load", function () {

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

      // カメラを作成
      const camera = new THREE.PerspectiveCamera();
      // カメラコントローラーを作成
      const controls = new THREE.OrbitControls(camera, canvas);

      function onResize() {
        // レンダラーのサイズを設定する（高画質対応）
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(window.devicePixelRatio);
        // カメラを設定する
        camera.fov = 50;
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.position.set(0, 3, 1.5);
        // 原点方向を見つめる
        camera.lookAt(new THREE.Vector3(0, 0, 0));

        // 滑らかにカメラコントローラーを制御する
        controls.enableDamping = true;
        controls.dampingFactor = 0.2;

        camera.updateProjectionMatrix();
      }
      // レンダラー・カメラの初期設定
      onResize();

      // リサイズ時にレンダラー・カメラの再設定
      $(window).resize(function () {
        onResize();
      });

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

      // シーン→グループ
      const group = new THREE.Group();
      scene.add(group);

      // グループ→正四面体
      const Tetrahedron_size = 1;
      const Tetrahedron = new THREE.TetrahedronGeometry(Tetrahedron_size);

      const Tetrahedron_m = new THREE.MeshNormalMaterial({
        side: THREE.DoubleSide,
        transparent: true,
        blending: THREE.NormalBlending,
      });
      Tetrahedron_m.opacity = .5;
      const mesh_Tetrahedron = new THREE.Mesh(Tetrahedron, Tetrahedron_m);
      mesh_Tetrahedron.position.set(0, Tetrahedron_size / 3, 0);
      const Tetrahedron_y = (Math.PI * 2) / 8;
      const Tetrahedron_x = 2.186275;
      const Tetrahedron_A = 1;

      mesh_Tetrahedron.rotation.set(Tetrahedron_x * Tetrahedron_A, Tetrahedron_y, 0);

      group.add(mesh_Tetrahedron);

      // グループ→正四面体外縁線
      //ジオメトリーの作成
      const points = [];
      points.push(new THREE.Vector3(0, Tetrahedron_size * 1.5, 0)); //1
      points.push(new THREE.Vector3(Tetrahedron_size * .95, -.1, .55)); //2
      points.push(new THREE.Vector3(Tetrahedron_size * -.95, -.1, .55)); //3
      points.push(new THREE.Vector3(0, -.1, -1.1)); //3.5
      points.push(new THREE.Vector3(Tetrahedron_size * .95, -.1, .55)); //4
      points.push(new THREE.Vector3(Tetrahedron_size * -.95, -.1, .55)); //5
      points.push(new THREE.Vector3(0, Tetrahedron_size * 1.5, 0)); //6
      points.push(new THREE.Vector3(Tetrahedron_size * .95, -.1, .55)); //7
      points.push(new THREE.Vector3(Tetrahedron_size * -.95, -.1, .55)); //8
      points.push(new THREE.Vector3(0, -.1, -1.1)); //9
      points.push(new THREE.Vector3(0, Tetrahedron_size * 1.5, 0)); //10
      const line = new THREE.BufferGeometry().setFromPoints(points);

      const line_m = new THREE.LineBasicMaterial({
        color: 0xff0000,
        linewidth: 1,
        side: THREE.DoubleSide,
      })

      const mesh_line = new THREE.Line(line, line_m);
      group.add(mesh_line);

      // グループ→正八面体
      const Octahedron = new THREE.OctahedronGeometry(.1);
      const Octahedron_m = new THREE.MeshNormalMaterial({
        side: THREE.DoubleSide,
      });
      const mesh_sprite = new THREE.Mesh(Octahedron, Octahedron_m);
      mesh_sprite.position.set(0, Tetrahedron_size * 1.5, 0);
      group.add(mesh_sprite);

      // 正八面体を外縁線沿いにアニメーション
      function path1_2() {
        TweenMax.to(mesh_sprite.position, 1.5, {
          x: Tetrahedron_size * .95,
          y: -.1,
          z: .55,
          onComplete: path2_3,
        });
      }
      path1_2();

      function path2_3() {
        TweenMax.to(mesh_sprite.position, 1.5, {
          x: Tetrahedron_size * -.95,
          y: -.1,
          z: .55,
          onComplete: path3_11,
        });
      }

      function path3_11() {
        TweenMax.to(mesh_sprite.position, 1.5, {
          x: 0,
          y: -.1,
          z: -1.1,
          onComplete: path11_4,
        });
      }

      function path11_4() {
        TweenMax.to(mesh_sprite.position, 1.5, {
          x: Tetrahedron_size * .95,
          y: -.1,
          z: .55,
          onComplete: path4_5,
        });
      }

      function path4_5() {
        TweenMax.to(mesh_sprite.position, 1.5, {
          x: Tetrahedron_size * -.95,
          y: -.1,
          z: .55,
          onComplete: path5_6,
        });
      }

      function path5_6() {
        TweenMax.to(mesh_sprite.position, 1.5, {
          x: 0,
          y: Tetrahedron_size * 1.5,
          z: 0,
          onComplete: path6_7,
        });
      }

      function path6_7() {
        TweenMax.to(mesh_sprite.position, 1.5, {
          x: Tetrahedron_size * .95,
          y: -.1,
          z: .55,
          onComplete: path7_8,
        });
      }

      function path7_8() {
        TweenMax.to(mesh_sprite.position, 1.5, {
          x: Tetrahedron_size * -.95,
          y: -.1,
          z: .55,
          onComplete: path8_9,
        });
      }

      function path8_9() {
        TweenMax.to(mesh_sprite.position, 1.5, {
          x: 0,
          y: -.1,
          z: -1.1,
          onComplete: path9_1,
        });
      }

      function path9_1() {
        TweenMax.to(mesh_sprite.position, 1.5, {
          x: 0,
          y: Tetrahedron_size * 1.5,
          z: 0,
          onComplete: path1_2,
        });
      }

      // シーン→トップライト
      const directionalLight = new THREE.AmbientLight(0xFFFFFF, 1);
      directionalLight.position.set(0, 1, -1);
      scene.add(directionalLight);

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

        mesh_sprite.rotation.y += 0.25;

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

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

</html>

```