3Dモデルを動かせるアプリをPWAで作る

子(1歳5ヶ月)が最近すごく消防車とか救急車に興味を持っているようで、またiPadを人差し指で操作することを覚えてきているので、じゃあ好きな車を表示してグリグリ動かせるのを作ってあげよう、と思った。

Web系エンジニアとしてはやはりブラウザで動くようなものが作りやすいな、と思い Web技術を軸に実装してみた。

モデリングデータ

まずは3Dモデルを探してみた。海外の救急車のものなどはよくヒットしたが、国内のものでそれっぽく良いものはなかなか見つからなかった。 最終的にこれを購入。

booth.pm

消防車も欲しいけどここには無さそう… どこかに良いの無いかな……

Three.jsで表示

データが手に入れば、あとは Three.js のような優れたライブラリを使えば簡単に読み込んで表示して動かしたりできる。マトモに使ったことは無かったけど、exampleなど見ながらちょっと書いてみたらスッと動くものが出来上がった。

import {
  Scene,
  PerspectiveCamera,
  WebGLRenderer,
  AmbientLight,
  DirectionalLight,
} from "three";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
import { MTLLoader } from "three/examples/jsm/loaders/MTLLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";

document.addEventListener("DOMContentLoaded", async () => {
  const renderer = new WebGLRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  const camera = new PerspectiveCamera();
  camera.fov = 40;
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  camera.position.y = 3;
  camera.position.z = 8;

  const scene = new Scene();

  const aLight = new AmbientLight(0xffffff, 0.75);
  scene.add(aLight);
  const dLight = new DirectionalLight(0xffffff, 0.25);
  scene.add(dLight);

  const mtlLoader = new MTLLoader();
  const mtl = await mtlLoader.loadAsync("/data/ambulance_ob.mtl");
  const objLoader = new OBJLoader();
  objLoader.setMaterials(mtl);
  const ambulance = await objLoader.loadAsync("/data/ambulance_ob.obj");
  ambulance.position.y = -1;
  scene.add(ambulance);

  const controls = new OrbitControls(camera, renderer.domElement);
  const animate = function () {
    controls.update();
    renderer.render(scene, camera);
    requestAnimationFrame(animate);
  };
  animate();
});

PWA化

手元のブラウザでは動いても、これを子が遊ぶためのiPadで表示するにはどこかweb上にdeployしなければならない。しかし使用条件として「再配布禁止」というのがあり、web上にdeployするとそこから幾らでもダウンロードできるようになってしまう。

ので、作ったものをPWA (Progressive web apps) として配布できるように準備した。

web.dev

これも今までまともにやったことなくて苦労したが… manifest ファイルやアイコン画像やService Workerを準備し、レスポンスをキャッシュしてオフラインでも動作するように設定。 結局 iOS のアプリアイコンにするには manifesticons 指定ではダメで apple-touch-icon で指定する必要がある、とかハマりどころは多かった。

Install

PWAとして動くよう準備できたら、ngrok を使って一時的に手元のアプリをtunnelさせる。そこで発行されたURLにiPadから繋ぎ、「ホーム画面に追加」でアプリとしてインストール。 一度だけ開いてService Workerでレスポンスをキャッシュしてしまえば、あとはオフラインでも動作するアプリになる。 終わったらngrokを切断。

これで出来上がり。いつでもiPadから起動して触って動かすことができるようになる。

結果

OrbitControls での操作はまだ慣れないのか、結局あまり触ってくれず。泣

もう少し背景とか道路とか動きがあった方が興味持ってくれるかもしれないのでアップデートは必要そう