みかづきブログ・カスタム

基本的にはちょちょいのほいです。

AR.js + Three.js + React.js でWebARコンテンツをつくるときのためのカスタムフックをつくる 🔨

f:id:kimizuka:20220302114750j:plain

AR.js + Three.js + Next.jsReact.js) でWebARコンテンツを作る際、AR.jsの読み込みをどうするのがスマートなのか、ずーっと悩んでいたのですが、とりあえずカスタムフックを作ってみました。


ソースコード

useMakerAr.js

import { useEffect, useState } from 'react';

export default function useMakerAr({
  THREE,
  scriptUrl,
  cameraParametersUrl,
  patternUrl
}) {
  const [ THREEx, setTHREEx ] = useState(null);
  const [ markerRoot, setMarkerRoot ] = useState(null);
  const [ arToolkitSource, setArToolkitSource ] = useState(null);
  const [ arToolkitContext, setArToolkitContext ] = useState(null);
  const [ arMarkerControl, setArMarkerControl ] = useState(null);
  
  useEffect(() => {
    if (process.browser) {
      if (!window.THREE) {
        window.THREE = THREE;
      }

      if (!window.THREEx) {
        const script = document.createElement('script');

        script.onload = () => {
          setTHREEx(window.THREEx);
          document.body.removeChild(script);
        };
        script.src = scriptUrl;
        document.body.appendChild(script);
      }
    }
  }, []);

  useEffect(() => {
    if (!THREEx) {
      return;
    }

    const markerRoot = new THREE.Group();
    const arToolkitContext = new THREEx.ArToolkitContext({
      cameraParametersUrl,
      detectionMode: 'mono'
    });
    const arToolkitSource = new THREEx.ArToolkitSource({
      sourceType: 'webcam'
    });
    const arMarkerControl = new THREEx.ArMarkerControls(arToolkitContext, markerRoot, {
      type: 'pattern',
      patternUrl
    });

    setMarkerRoot(markerRoot);
    setArToolkitContext(arToolkitContext);
    setArToolkitSource(arToolkitSource);
    setArMarkerControl(arMarkerControl);
  }, [THREEx]);

  return {
    markerRoot,
    arToolkitSource,
    arToolkitContext,
    arMarkerControl
  }
}

AR.jsを読み込む際に、window.THREEがないとエラーになるので、

❶ moduleで読み込んだTHREEオブジェクトをwindow.THREEに代入
❷ scriptタグを動的につくって、AR.jsを読み込む
❸ AR.jsの読み込みが完了したらscriptタグを削除

という流れで実装しました。


つかいかた

index.jsx(抜粋)

import * as THREE from '~/scripts/three.module';
import useMarkerAr from '~/hooks/useMakerAr';

export default function IndexPage() {
  const { markerRoot, arToolkitSource, arToolkitContext } = useMarkerAr({
    THREE,
    scriptUrl: '/scripts/ar.js',
    cameraParametersUrl: '/ar/camera.dat',
    patternUrl: '/ar/pattern.patt'
  });
}

という感じで、THREEオブジェクト、AR.jsのURL、カメラパラメーターのURL、パターンのURLを渡すと、markerRoot、arToolkitSource、arToolkitContextが返ってくるようにつくりました。

arToolkitSource.init、arToolkitContext.initの処理もカスタムフック内に隠蔽しようと思ったのですが、addEventListenerやonのように複数回コールバックイベントを張る方法が見当たらなかったため諦めました。


DEMO用マーカー

f:id:kimizuka:20220301164230p:plain



以前の検証の通り、r128以降だとエラーが発生するので、r127で検証しています。

blog.kimizuka.org