1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Stripe AppsにWebGLを載せてみた

Last updated at Posted at 2022-12-17

こちらは JP_Stripes Advent Calendar 2022 の12月18日分のエントリーです。

はじめに

まずはぜひデモ動画からご覧ください。
stripe-webgl-demo.gif
キレイなキューブが60fpsでヌルヌル動きます。マウスでも操作が可能です。ソースコードは GitHub に公開しています。

Stripe Appsとは

Stripeダッシュボードの中で表示されるカスタムUIの拡張機能がStripeアプリです。Stripe APIとWebhookを使用することで、データを可視化したり、独自のサービスと連携したりすることが可能です。

アプリはReactで作られているので、TypeScriptでコードを書きます。

アプリの作り方についてもっと詳しく知りたい方はぜひ @hideokamoto さんの Stripe Apps を25日間紹介し続ける Advent Calendar 2022 を読んでみてください。

WebGLとの連携・仕組み

Stripeアプリはセキュリティのため、サンドボックスされた iframe で表示されます。DOMには直接アクセスできず、WebGLのコンテキストでGPUからの出力は表示できません。

ただ、GPUの出力をバッファーにキャプチャーすることは可能(リアルタイムでテクスチャ生成など)なので、キャプチャーされた画像データをCPU側のメモリに戻し、以下のdataURLの様にエンコードすることで静止画像として表示することは可能です。

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASAAAAEgCAYAAAAUg66AAAAAAX...

そしてみんな大好きThree.jsがこのレンダリング出力からdataURLのエンコードまでの流れを既に対応しています。とてもありがたいことです。

Stripeアプリの Img の画像表示するコンポーネントは src 属性にdataURLをそのまま渡せるので、Reactのstateでレンダリング出力のdataURL文字列を保存することで、基本のレンダリングループが簡単に出来上がります。

const ThreeJS = () => {
	const [renderOutput, setRenderOutput] = useState('');

	useEffect(() => {
		renderer.setAnimationLoop((time) => {
			// ...update scene here...
			renderer.render(scene, camera);
			setRenderOutput(renderer.domElement.toDataURL());
		});
	}, []);

	return (
		<Img width={width} height={height} src={renderOutput} />
	);
}

マウスからの入力

DOMに直接アクセスがないなので、MouseEvent にローカル座標の offsetX / offsetY が無く、clientX / clientY の絶対座標をコンポーネントのローカル座標にどう変換すればいいかという情報すら取得ができないらしい。

(※ React用の ref を使ったり、 getBoundingClientRect() などで結構色々と苦労したのですが、もし誰かが解決方法がご存じでしたら、ぜひ教えてください!)

一応仮の解決方法として、画像のサイズ ( widthheight は指定してる)は分かるし、MouseEvent を取得する際はマウスが Img コンポーネントに確実に入ってる時のみになるので、マウス座標を取得する際はコンポーネントのエリアを自動的に調整し、ユーザーがマウスを動かすことで正確なバウンディングボックスが認識されるようになります。

const mx = evt.clientX;
const my = evt.clientY;

// update sliding bounds window based on input coordinates
if ((mx < bound.x1) || (my < bound.y1)) {
	bound.x1 = Math.min(mx, bound.x1);
	bound.y1 = Math.min(my, bound.y1);
	bound.x2 = bound.x1 + width;
	bound.y2 = bound.y1 + height;
} else if ((mx > bound.x2) || (my > bound.y2)) {
	bound.x2 = Math.max(mx, bound.x2);
	bound.y2 = Math.max(my, bound.y2);
	bound.x1 = bound.x2 - width;
	bound.y1 = bound.y2 - height;
}

あとはマウスの座標を正規化することで、X/Yの位置はコンポーネントの中で [0.0 .. 1.0] に変換されます。

// convert mouse coords to local normalized space
const mouseState = {
    pos: {
        x: (mx - bounds.x1) / width,
        y: (my - bounds.y1) / height
    },
    buttons: evt.buttons
};

props.onMouseEvent(mouseState);

なお、絶対座標が正確ではなくても、今回のデモのように相対座標を利用すれば、正常の動きは取れます。

今後の進展

3Dデータの可視化はWebGLがかなり良いなので、Stripe Appsには結構使えそうですね。

現状は本当にシンプルなプロトタイプなもんですが、あとはキーボード入力とか、もっとクオリティが高いのシーンを表示したりしたいと思います!

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?