【Three.js】文字が球体の周りをグルグル回るヤツ

2021年4月18日JavaScript,Three.js

USJ行きたいね…マリオのヤツ…

はじめに

USJの地球儀のモニュメント?みたいなのを見て「コレだ!」と思いました。

できた

デモサイトはこちら

これを見出しの代わりとして画面の奥の方でクルクル回してあげれば、おしゃれになるんじゃないでしょうか。

とりあえずJavaScriptの全文です。

<script type="text/javascript">

  window.onload = function() {

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

    // アスペクト比
    var aspRatio = window.innerWidth / window.innerHeight;

    // 視野角
    var fov;
    if (aspRatio > 1) {
      fov = 35;
    } else if (aspRatio > 0.9) {
      fov = 45
    } else if (aspRatio > 0.8) {
      fov = 50;
    } else if (aspRatio > 0.6) {
      fov = 60;
    } else if (aspRatio > 0.5) {
      fov = 70;
    } else {
      fov = 80;
    }

    // カメラ
    const camera = new THREE.PerspectiveCamera(fov, aspRatio, 0.1, 1000);
    camera.position.x = 0;
    camera.position.y = 0;
    camera.position.z = 100;
    camera.lookAt(scene.position);

    // レンダラー
    const webGLRenderer = new THREE.WebGLRenderer();
    webGLRenderer.setSize(window.innerWidth, window.innerHeight);
    webGLRenderer.shadowMap.enabled = true;
    document.getElementById("screen").appendChild(webGLRenderer.domElement);

    // ライト
    const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
    dirLight.position.set(-100,100,100);
    scene.add(dirLight);

    // テキストと球体を入れるコンテナ
    const container = new THREE.Object3D();
    scene.add(container);

    // テキスト
    let textMesh;
    const textSize = 8;
    const fontLoader = new THREE.FontLoader();
    fontLoader.load("../assets/fonts/helvetiker_regular.typeface.json", function(font) {
      createTextGeometry("A", font, 310);
      createTextGeometry("B", font, 335);
      createTextGeometry("O", font, 0);
      createTextGeometry("U", font, 25);
      createTextGeometry("T", font, 50);
    });
    
    // 球体
    const sphereGeometry = new THREE.SphereGeometry(15, 20, 20);
    const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true });
    const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
    container.add(sphere); 
    
    // カメラのコントロール
    const trackballControls = new THREE.TrackballControls(camera);
    trackballControls.panSpeed = 0.2;
    trackballControls.rotateSpeed = 3.0;
    trackballControls.maxDistance = 1000;
    
    /***** 3Dテキスト作成関数 *****/
    function createTextGeometry(text, font, deg) {
      const textGeometry = new THREE.TextGeometry(text, {
        font: font,
        size: textSize,
        height: 1,
        curveSegment: 1
      });
      textGeometry.center();

      const material = new THREE.MeshPhongMaterial({ color: 0xffffff });

      const r = 17
      const phi = 90 * (Math.PI / 180);
      const theta = deg * (Math.PI / 180);
      const sphericalPos = new THREE.Spherical(r, phi, theta);

      textMesh = new THREE.Mesh(textGeometry, material);
      textMesh.position.setFromSpherical(sphericalPos);

      const vector = new THREE.Vector3();
      vector.copy(textMesh.position).multiplyScalar(2);

      textMesh.lookAt(vector);
      container.add(textMesh);
    }

    /***** 描画関数 *****/
    const clock = new THREE.Clock();
    function renderScene() {
      // コンテナを回す(コンテナ中身のテキストと球体も回る)
      container.rotation.y -= 0.03;
      const delta = clock.getDelta();
      trackballControls.update(delta);
      requestAnimationFrame(renderScene);
      webGLRenderer.render(scene, camera);
    }

    /***** ウィンドウサイズ変更 *****/
    window.addEventListener("resize", function(){

      // アスペクト比
    var aspRatio = window.innerWidth / window.innerHeight;

      // 視野角
      var fov;
      if (aspRatio > 1) {
        fov = 35;
      } else if (aspRatio > 0.9) {
        fov = 45
      } else if (aspRatio > 0.8) {
        fov = 50;
      } else if (aspRatio > 0.6) {
        fov = 60;
      } else if (aspRatio > 0.5) {
        fov = 70;
      } else {
        fov = 80;
      }

      camera.aspect = aspRatio;
      camera.fov = fov;
      camera.updateProjectionMatrix();
      webGLRenderer.setSize(window.innerWidth, window.innerHeight);
      render();

    });

    // 描画
    renderScene();
  }
</script>

フォントを読み込む

まずはTHREE.FontLoaderでフォントのデータを読み込みます。読み込むデータはJSON形式でないといけないようです。JSONがよく分からないけど、なんとかして入手します。僕はなんとかして入手できました。

const fontLoader = new THREE.FontLoader();
fontLoader.load("../assets/fonts/helvetiker_regular.typeface.json", function(font) {
  createTextGeometry("A", font, 310);
  createTextGeometry("B", font, 335);
  createTextGeometry("O", font, 0);
  createTextGeometry("U", font, 25);
  createTextGeometry("T", font, 50);
});

THREE.FontLoaderload関数でフォントデータを読み込みます。今回は「ヘルベチカ」というフォントを使います。

load関数の第1引数にフォントデータを、第2引数にコールバック関数を指定します。

コールバック関数はフォントの読み込みが終了したら呼び出されるので、その関数の中に3Dテキストを作る処理を書いてあげれば、「まだフォントデータの読み込み終わってないからテキスト作れないよ!」みたいなエラーが出ることがなくなります。

文字を一文字ずつ作る理由

createTextGeometry("ABOUT", font, 310);

こうやって書いてあげれば一回で済みますが、コレだとこうなります↓。

まっすぐな板みたいになっちゃう。今回は球体の周りにまとわりついてるような感じにしたいので、一文字ずつ分けました。

3Dテキストを作る

const textGeometry = new THREE.TextGeometry(text, {
  font: font,
  size: textSize,
  height: 1,
  curveSegment: 1
});
textGeometry.center();

3Dのテキストを作る時はTHREE.TextGeometryを使います。第1引数で表示させたい文字、第2引数でプロパティ群の設定をします。

注意点ですが、この3Dテキストの原点は左下になります。でも球体は中心が原点になるので、この2つを一緒に表示するとズレてしまいます↓。

そんな時はTHREE.TextGeometrycenterで解決。テキストの中心が原点になります↓。

textGeometry.center();

3Dテキストの位置を決める

3Dテキストの位置は、球体の表面上の位置情報を持つTHREE.Sphericalを使って設定しています。

const r = 17
const phi = 90 * (Math.PI / 180);
const theta = deg * (Math.PI / 180);
const sphericalPos = new THREE.Spherical(r, phi, theta);

textMesh = new THREE.Mesh(textGeometry, material);
textMesh.position.setFromSpherical(sphericalPos);

rは半径、phiは経度、thetaは緯度です。この位置情報をもつTHREE.Sphericalを作成して、THREE.MeshsetFromSpherical関数に渡してあげます。たぶん座標をコピーしているんじゃないでしょうか。

3Dテキストの向きを決める

これで悩みましたねぇ。

const vector = new THREE.Vector3();
vector.copy(textMesh.position).multiplyScalar(2);
textMesh.lookAt(vector);

実は、前にもこの方法で作ったものがあるんですけど、その時は分からないまま「ま、いっか」って使ってました。今回はちゃんと考えました。

vector.copy(textMesh.position)で原点から文字までのベクトルを作り、multiplyScalar(2)でそのベクトルに2を掛けてるんですね。そしてtextMesh.lookAt(vector)で文字をそのベクトルの方向に向かせてます。

で、「なんでベクトルに2を掛けるのか」なんですけど、別に2じゃなくてもいいんですね。1じゃなきゃいいんです。

1にするとこうなります↓。

みんな正面向いちゃった。

1だとベクトルの先端が文字の位置になってtextMesh.lookAt(vector)が「自分自身の方向に向け」ってなるワケです。「自分を見ろ」って言われてもムリだから初期値である正面を向いちゃうんだと思います。

だから1以上の値にしてあげれば、ベクトルの先端が文字の位置より遠くになって、文字もそっちを向くんですね。ちなみに1より小さくすると原点方向に向きます↓。

あー。スッキリ。

3Dテキストと球体を一緒に回す方法

やり方はいろいろあると思いますが、一番簡単そうなのにしました。

const container = new THREE.Object3D();
scene.add(container);

THREE.Object3Dで3Dテキストと球体を入れるコンテナを作って、それをシーンに追加します。

container.add(sphere); 

↑そのコンテナに球体を追加します。

container.add(textMesh);

↑3Dテキストも追加します。

container.rotation.y -= 0.03;

↑描画関数の中でコンテナを回します。「入れ物が回るので中身も回る」というワケですね。

あとがき

はい。というワケでいいものができました。新しいパソコンになったので色々とはかどります。ありがとうございました。

おしゃれ度

★★★☆☆

Posted by ナカタ