AR.js + Three.js + Next.js(React.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のように複数回コールバックイベントを張る方法が見当たらなかったため諦めました。