🌿
🌿 MidoriPhotoArt.
HTMLコンテンツを3D空間に表示。CSS&HTMLタグのWEBGL空間内での表示。HTMLコンテンツの3d空間内でのアニメーション。バラバラにして空間を浮遊させる。

ContentDisplay UI システム仕様書

1. システム概要

本システムは、Three.js と OffscreenCanvas を活用して、WEBGL空間にHTML/CSSを動的にレンダリングするコンポーネント「ContentDisplay」を提供します。 このクラスは、SVG_TEMPLATE を基盤に、OffscreenCanvas 上でHTML構文を描画し、その描画結果を THREE.js の Mesh として3Dシーンに配置します。 これは、WEBGL空間にCSSやHTMLを表示するクラス定義のひな型です。このコードに追加したいHTMLを記載していくことで、 WEBGL上に自由なHTML構文を表示できるようになっています。

2. コアコンポーネントの設計

ContentDisplay クラスは、HTMLコンテンツを3D空間に表示するためのクラスです。 指定されたサイズのHTMLコンテンツを、OffscreenCanvasを用いて描画し、それをテクスチャとしてThree.jsのMeshオブジェクトに適用します。 また、ユーザーが定義したインタラクティブな領域(InteractiveElement)に対するクリックイベントを検出し、対応するコールバック関数を実行する機能を提供します。

InteractiveElement クラスは、ContentDisplay クラスで表示されるHTMLコンテンツ内のインタラクティブな要素を表すクラスです。 要素の座標、サイズ、およびクリック時に実行されるコールバック関数を保持します。

これらのクラスは、以下の主要な技術的要素から構成されています。

  • Three.js: WebGLを簡単に扱うための3Dグラフィックスライブラリです。3Dモデルの作成、シーンへの追加、カメラや光源の設定、アニメーションの実行など、3Dコンテンツをウェブブラウザで表示・操作するための包括的な機能を提供します。
  • OffscreenCanvas: メインスレッドとは別のスレッドでCanvasの描画処理を行うことができるAPIです。これにより、描画処理が重い場合でも、メインスレッドの処理をブロックすることなく、スムーズなユーザーインターフェースを維持することができます。
  • SVG (Scalable Vector Graphics): XMLベースのベクター画像フォーマットです。解像度に依存しないため、拡大縮小しても画質が劣化しないという特徴があります。このコードでは、HTMLコンテンツをSVG内に埋め込み、それをテクスチャとして3Dオブジェクトに適用しています。
  • Data URL: データを直接URLに埋め込むための仕組みです。このコードでは、生成したSVGをData URLとしてエンコードし、それを`Image`オブジェクトの`src`に設定することで、SVGを画像として読み込んでいます。
  • 表示オプション (displayOptions) によるジオメトリサイズとメッシュ拡大率の調整
  • THREE.js の CanvasTexture と Mesh を用いた3Dシーンでの表示
  • 非同期更新処理 (_updateClass()update()) による最新内容の反映
  • DOMクリックイベントの検出 (attachInteraction()) とインタラクティブ要素のコールバック処理
  • メッシュのスケール変更 (setMeshScale()) 機能

主なメソッド一覧


// ContentDisplay クラスのコンストラクタ
// width: 表示するコンテンツの幅
// height: 表示するコンテンツの高さ
// displayOptions: 3Dオブジェクトのサイズや拡大率を指定するオプション
constructor(width = 1920, height = 1080, displayOptions = { planeWidth: 150, scale: 2 })

// SVG テンプレートの生成
// 指定された幅と高さを持つSVG文字列を生成します。
_generateRawSVG()

// OffscreenCanvas 上の更新処理
// SVGを生成し、それをData URLに変換してImageオブジェクトとして読み込みます。
// 読み込みが完了したら、OffscreenCanvasに描画し、Meshのテクスチャを更新します。
_updateClass()

// コンポーネントの開始処理
// アニメーションを開始します。
// _createMeshを呼び出してMeshオブジェクトを作成します。
// updateを呼び出してコンテンツを更新します。
// cameraとdomElementが指定されている場合は、attachInteractionを呼び出してインタラクションを設定します。
// アニメーションコールバックが登録されていない場合は、window.animationCallbacksにupdateメソッドを登録します。
start(camera, domElement)

// THREE.js Mesh の作成
// OffscreenCanvasをテクスチャとして使用するMeshオブジェクトを作成します。
// テクスチャのフィルタリング設定を行います。
// displayOptionsで指定されたplaneWidthとアスペクト比から、PlaneGeometryのサイズを決定します。
// MeshBasicMaterialを使用して、テクスチャ、両面表示、透明度、不透明度を設定します。
// Meshの位置を原点に設定します。
// displayOptionsで指定されたscaleを適用して、Meshのサイズを調整します。
_createMesh()

// Mesh の取得
// 作成したMeshオブジェクトを返します。
getMesh()

// 更新処理
// _updateClassを呼び出してコンテンツを更新します。
update()

// インタラクションの設定
// cameraとdomElementを設定します。
// interactiveConfig配列を空に初期化します(現状、この配列は使用されていません)。
// updateInteractiveElementsを呼び出して、インタラクティブな要素を更新します。
// 既存のクリックイベントハンドラがあれば削除します。
// 新しいクリックイベントハンドラをdomElementにアタッチします。
//   クリックされた位置を3D空間内の座標に変換します。
//   Raycasterを使用して、クリックされた位置にあるMeshとの交差を判定します。
//   交差がある場合は、交差した点のUV座標からSVG内の座標を計算します。
//   interactiveElements配列内の各要素について、クリックされた座標が要素の範囲内にあるかどうかを判定し、範囲内にある場合は要素のコールバック関数を実行します。
attachInteraction(camera, domElement)

// インタラクティブ要素の更新
// _generateRawSVGを呼び出してSVG文字列を生成します。
// 画面外に一時的なdiv要素を作成し、そこにSVGを挿入します。
// interactiveConfig配列内の各要素について、対応するDOM要素をSVG内から検索します。
//   要素が見つかった場合は、その要素の位置とサイズを取得し、InteractiveElementオブジェクトを作成します。
//   要素が見つからない場合は、config.defaultで指定された位置とサイズを使用してInteractiveElementオブジェクトを作成します。
// 作成したInteractiveElementオブジェクトをinteractiveElements配列に追加します。
// 一時的なdiv要素を削除します。
updateInteractiveElements()

// メッシュのスケール変更
// Meshオブジェクトのスケールを設定します。
setMeshScale(x, y, z)
                    

InteractiveElement クラス


// InteractiveElement クラスのコンストラクタ
// x: 要素の左上の座標
// y: 要素の右上の座標
// width: 要素の幅
// height: 要素の高さ
// callback: 要素がクリックされたときに実行されるコールバック関数
constructor(x, y, width, height, callback)

// 指定座標が領域内に含まれるか判定
// 指定された座標が要素の範囲内にあるかどうかを判定します。
contains(pointX, pointY): boolean
                    

3. インタラクティブ機能と更新処理

ContentDisplay は、非同期処理によって OffscreenCanvas 上に描かれたHTMLコンテンツを定期的に更新します。 具体的には、`update()`メソッドが定期的に呼び出され、`_updateClass()`メソッドによって最新のHTMLコンテンツが再描画されます。 `_updateClass()`メソッドは、`_generateRawSVG()`メソッドで生成されたSVGをData URLに変換し、それを`Image`オブジェクトとして読み込みます。 読み込みが完了したら、`OffscreenCanvas`に描画し、`Mesh`のテクスチャを更新します。

また、`attachInteraction()` メソッドを通じ、DOM上のクリックイベントを Three.js の Raycaster で検出することで、 定義されたインタラクティブ領域内で各コールバックが実行される仕組みを提供します。 `attachInteraction()`メソッドは、`updateInteractiveElements()`メソッドを呼び出して、インタラクティブな要素を更新します。 `updateInteractiveElements()`メソッドは、`_generateRawSVG()`メソッドで生成されたSVGを一時的な`div`要素に挿入し、 `interactiveConfig`配列内の各要素について、対応するDOM要素をSVG内から検索します。 要素が見つかった場合は、その要素の位置とサイズを取得し、`InteractiveElement`オブジェクトを作成します。 要素が見つからない場合は、`config.default`で指定された位置とサイズを使用して`InteractiveElement`オブジェクトを作成します。 作成した`InteractiveElement`オブジェクトを`interactiveElements`配列に追加します。

クリックイベントが発生すると、`attachInteraction()`メソッドで設定されたクリックイベントハンドラが呼び出されます。 クリックイベントハンドラは、クリックされた位置を3D空間内の座標に変換し、`Raycaster`を使用して、クリックされた位置にある`Mesh`との交差を判定します。 交差がある場合は、交差した点のUV座標からSVG内の座標を計算します。 `interactiveElements`配列内の各要素について、クリックされた座標が要素の範囲内にあるかどうかを判定し、範囲内にある場合は要素のコールバック関数を実行します。

4. UML クラス図

以下は、ContentDisplay および InteractiveElement クラスの概要を示す UML クラス図です。

ContentDisplay - width: number - height: number - displayOptions: Object - canvas: OffscreenCanvas - context: CanvasRenderingContext2D - mesh: THREE.Mesh - isRunning: boolean - _isRegistered: boolean - _clickHandler: function - camera: any - domElement: any + constructor(width, height, displayOptions) - _generateRawSVG(): string - _updateClass(): Promise<void> + start(camera, domElement): Promise<void> - _createMesh(): void + getMesh(): THREE.Mesh + update(): Promise<void> + attachInteraction(camera, domElement): void + updateInteractiveElements(): void + setMeshScale(x, y, z): void
InteractiveElement - x: number - y: number - width: number - height: number - callback: function + constructor(x, y, width, height, callback) + contains(pointX, pointY): boolean

5. 動作フローと実装上の留意点

  • start() により、ContentDisplay が初期化され、OffscreenCanvas 上の描画内容が THREE.CanvasTexture として Mesh に変換され、3Dシーンに配置されます。
  • update() メソッドが定期的に呼ばれ、最新のHTMLコンテンツが再描画されます。
  • attachInteraction() により、DOM のクリックイベントが Three.js の Raycaster で検出され、インタラクティブ領域のコールバックが実行されます。
  • setMeshScale() を使用して、生成済みメッシュのスケールを動的に変更可能です。
  • OffscreenCanvas の再利用とテクスチャ更新により、効率的な描画とリソース管理が実現されています。
  • 現状、`interactiveConfig`配列は初期化されているものの、実際には使用されていません。この配列を活用して、インタラクティブな要素の設定を外部から行えるようにすると、より柔軟な使い方が可能になります。例えば、`start`メソッドの引数として`interactiveConfig`を受け取るようにし、それを元に`updateInteractiveElements`メソッドで`interactiveElements`配列を生成するように変更することが考えられます。
  • 現状では、`updateInteractiveElements`メソッドが呼び出されるたびに、一時的な`div`要素を作成してSVGを挿入し、要素の位置とサイズを取得しています。この処理はコストが高いため、`interactiveConfig`が変更された場合にのみ実行するように変更すると、パフォーマンスが向上します。
  • 現状では、`InteractiveElement`クラスは単純な矩形領域のみをサポートしています。これを拡張して、円形や多角形などのより複雑な形状をサポートできるようにすると、より多様なインタラクションを実現できます。
  • 現状では、クリックイベントのみを処理していますが、マウスオーバーやマウスアウトなどの他のイベントも処理できるようにすると、よりリッチなインタラクションを実現できます。
  • 現状では、`_updateClass`メソッドでエラーが発生した場合、単に`reject`を呼び出していますが、エラーの内容をユーザーに通知したり、ログに出力したりするなど、より詳細なエラーハンドリングを行うと、デバッグが容易になります。