LoginSignup
4
2

More than 3 years have passed since last update.

Three.jsで3D ラーメンタイマー作ってみた。

Last updated at Posted at 2020-02-24

どうもこんにちは、ウマシバ(@UMASHIBA)と言います。

今回こちらの記事に触発されてThree.jsでラーメンタイマーを作成してみました。

その中でいくつかの機能をどのように実装したかを紹介してみようと思います。

作ったモノ🍜

3Dラーメンタイマー

アプリURL
https://3d-ramen-timer.umashiba.dev/
GitHubコード
https://github.com/UMASHIBA1/3DRamenTimer

使用した技術

  • Three.js
  • Typescript
  • webpack
  • PWA

赤いリングの作成方法

この赤い円をどうやって作成したか説明します。
赤い円

考え方

Three.jsにはTorusGeometryという下の写真のようなチューブ状の円を表現するジオメトリがありました。
チューブの円

そのTorusGeometryは下の写真のようにチューブ状であった円を弧にすることができます。
image.png

その弧をいくつか組み合わせて下の写真のような小さな一重の円を作成し、
image.png

その円を重ねて、下の写真のような大きい円を作成します。
赤い円

コード

コードは以下のような感じです。

弧のコード

ここでもともとチューブの円形であったTorusGeometryを弧の形にしています。
またここで色なども付けています。

弧のコード

import * as THREE from "three";

class RingFragment extends THREE.Mesh {
  constructor(
    fragmentIndex: number,
    radius: number,
    tube: number = 3,
    arc: number = 0.63
  ) {
    const thisAngleDiff = (2 / 9) * Math.PI * fragmentIndex;
    const circleDiameter = 10;
    const geometry = new THREE.TorusGeometry(radius, tube, 2, 100, arc);
    const material = new THREE.MeshToonMaterial({ color: "#FF0000" });
    super(geometry, material);
    this.position.x = Math.sin(thisAngleDiff) * circleDiameter;
    this.position.y = Math.cos(thisAngleDiff) * circleDiameter;
    this.rotation.y = 0;
    this.rotation.x = 0;
    // 1.23は初期でのangleの傾きを修正するための値
    this.rotation.z = -thisAngleDiff + 1.23;
  }
}

export default RingFragment;

一重の円のコード

  1. 弧を作っているRingFragmentというクラスのインスタンスをいくつか作りThree.jsのGroup機能でまとめます。
  2. Groupのrotation.zをフレームごとに少し進めて円を回転させます(tickというメソッドが毎フレームごとに呼ばれるようになってます。)
一重の円のコード
import * as THREE from "three";
import RingFragment from "../component/RingFragment";

type RotateDirectionType = "right" | "left";

class OneRing {
  private _group: THREE.Group;
  private _rotateDirection: RotateDirectionType;
  constructor(
    rotateDirection: RotateDirectionType,
    radius: number,
    tube?: number,
    arc?: number
  ) {
    this._group = new THREE.Group();
    this._rotateDirection = rotateDirection;
    for (let i = 0; i < 9; i++) {
      const ringFrag = new RingFragment(i, radius, tube, arc);
      this._group.add(ringFrag);
    }
    this._group.rotation.z = Math.random();
  }

  public get ring() {
    return this._group;
  }

  public tick() {
    const rotateSpeed = 0.005;
    if (this._rotateDirection === "left") {
      this._group.rotation.z += rotateSpeed;
    } else {
      this._group.rotation.z -= rotateSpeed;
    }
  }
}

export default OneRing;

複数重ねの円

  1. 複数の大きさの一重の円のインスタンスを作成して、これまたThree.jsのGroupでまとめます。
  2. 1重の円のtickメソッドをこちらのtickメソッドに組み込んで回るようにして完成です。
複数重ねの円

import * as THREE from "three";
import OneRing from "./OneRing";

class MultiRing {
  private _group: THREE.Group;
  private _oneRings: OneRing[];
  constructor(
    outlineRadius: number = 50,
    ringNum: number = 3,
    ringSizeDiff: number = 10,
    tube?: number,
    arc?: number
  ) {
    this._group = new THREE.Group();
    this._oneRings = [];

    for (
      let [count, nowRadius] = [0, outlineRadius];
      count < ringNum;
      count++, nowRadius -= ringSizeDiff
    ) {
      const rotateDirection = count % 2 ? "left" : "right";
      const ring = new OneRing(rotateDirection, nowRadius, tube, arc);
      this._oneRings.push(ring);
    }

    for (let i of this._oneRings) {
      this._group.add(i.ring);
    }
  }

  public addToScene(scene: THREE.Scene) {
    scene.add(this._group);
  }

  public tick() {
    for (let i of this._oneRings) {
      i.tick();
    }
  }
}

export default MultiRing;

以上が赤いリングの作り方です。

タイマーの作り方

次はタイマーの実装方法について説明します。
タイマーは普通にsetIntervalで時間を計測しました。

手順としては
1. 1秒経った際にoneSecondFlagといったような「一秒経ったよ」フラグをtrueにする
2. 毎フレーム呼ばれるメソッドであるtickでoneSecondFlagがtrueの際のみ一秒をカウントする処理を実行

このような流れになります。

基本的に他のアニメーションなどもフラグを作成してtickで処理をするという流れで作りました。

コードにすると以下のようなイメージです。

class Second {
  private _oneSecondFlag: boolean;
  constructor() {
    this._oneSecondFlag = false;
  }

  public startCount() {
    setInterval(() => {
      this._oneSecondFlag = true;
    }, 1000);
  }

  public tick() {
    if (this._oneSecondFlag) {
      // 一秒経った際にする処理
      this._oneSecondFlag = false;
    }
  }
}

今回私が作ったラーメンタイマーでは「一秒経った際にする処理」としてタイマーを回転させる処理を挟みました。

以上がタイマーの作り方です。

イージングの作成

今回作ったラーメンタイマーではほとんどのアニメーションにイージングを付けました。

そのコードは以下のようなものになりました。

const easing = (
  nowLocation: number,
  targetLocation: number,
  easingSpeed: number = 0.09
) => {
  return (targetLocation - nowLocation) * easingSpeed;
};

この関数を毎フレームごとにnowLocationを変えて呼びだす事でイージングを実装しました。

終わりに

以上が3Dラーメンタイマーの技術紹介になります。

Three.jsは今まで触ったことがなかったのですがこのラーメンタイマーを作ったら何となくわかるようになりました。

やっぱりどんなフレームワークや言語でもラーメンタイマーは良い題材なんですね!

最後まで読んでいただきありがとうございました。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2