ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
空間コンピューティング向けのARKitについて
ARKitのトラッキングとシーン認識機能を活用して、まったく新しいイマーシブなアプリやゲームの世界を創り出す方法について解説します。visionOSとARKitの連携により、プライバシーを守りながらユーザーの周囲を認識するアプリを作成するのにどのように役立つかについてご確認ください。ARKit APIの最新情報を入手し、アプリのハンドトラッキングとシーンジオメトリを活用する方法についてのデモンストレーションを行います。
リソース
関連ビデオ
WWDC23
-
ダウンロード
♪ ♪ ♪ こんにちは Ryanです 私はConnerです Ryan: このセッションでは 空間コンピューティング向けの ARKitについてご紹介します これが新しいプラットフォームで いかに重要な役割を果たすか そしてこれを活用して 次世代のアプリを構築する方法を紹介します ARKitは洗練されたコンピューター ビジョンアルゴリズムを使い ユーザーの周りの世界や動きについての 理解を生み出します この技術は デベロッパが手元で体験できる すばらしい拡張現実体験を 作成するための方法として iOS 11で公開されました このプラットフォームでは ARKitは 新しいリアルタイムな基盤で 一から再構築され 完全なサービスシステムとなりました ARKitはオペレーティングシステムの ファブリックに 深く織り込まれており ウインドウの操作から イマーシブなゲームプレイまで すべてのものを強化します この強化の一部として APIも 完全な見直しを行いました 新しいデザインは 私たちがiOSで学んだ すべてのことに加え 空間コンピューティング独特の ニーズを考慮して生まれたものです みなさんもきっと気に入るはずです ARKitはテーブルに バーチャルコンテンツを配置するなど すばらしいことを行うための 強力なさまざまな機能を提供します コンテンツが実際にそこにあるように 手を伸ばして触ることができ そのコンテンツが 実際の世界でインタラクションするのを 見ることができます 実に魔法のような体験です この新しいプラットフォームで ARKitを使用して 達成できることを 少しお見せしたところで 本日の内容をお伝えします 最初にAPIを構成する基礎的な概念と 基本要素の概要を説明し 次に実世界に 関連するバーチャルコンテンツを 配置するのに不可欠な ワールドトラッキングを 紹介します その後 環境に関する便利な情報を提供する シーン認識機能を説明してから バーチャルコンテンツを手に配置したり その他のオーダーメイドの操作を 構築したりするために活用できる エキサイティングな最新機能 ハンドトラッキングを紹介します 最後は最初に戻って 先程お見せした動画からの コードを調査して これらの機能の実用的な適用方法を 説明します では始めましょう 新しいAPIは モダンなSwiftとクラシックなCの 2つの爽快なフレーバーで 最新の注意を払って作成されました すべてのARKit機能は 個別に提供されるようになりました デベロッパが体験を構築するのに 必要なものだけを選択できるように 可能な限りの柔軟性を提供します ARKitデータへのアクセスは プライバシーファーストな方法で デザインされています ユーザーのプライバシーを 保護手段を適用しながら シンプルさをデベロッパに提供します APIは3つの基本要素から構成されます これらは セッション データプロバイダ アンカーです アンカーから始めて セッションに戻ります アンカーは実世界の位置と方向を 表します すべてのアンカーには一意な識別子と トランスフォームが含まれます 一部のアンカーは トラッキングすることもできます トラッキング可能なアンカーが トラッキングされていない場合 アンカーされている バーチャルコンテンツは 非表示にしてください データプロバイダは 個々のARKit機能を表します データプロバイダは アンカーの変更など データの更新のポーリングや 観察を可能にします 異なるタイプのデータプロバイダが 異なるタイプのデータを提供します セッションは特定の体験を作成するために 一緒に使用したい ARKit機能の組み合わせを表します セッションはデータプロバイダのセットを 提供することで実行します セッションの実行中 データプロバイダは データを受信し始めます 更新はデータタイプによって 非同期かつ異なる頻度で到着します ではプライバシーと アプリがARKitデータに アクセスする方法を説明します プライバシーは基本的人権です Appleのコアバリューでもあります ARKitのアーキテクチャとAPIは ユーザーのプライバシーの 保護を考慮してデザインされています ARKitがユーザーの周辺の世界の 理解を生み出すために デバイスには さまざまなカメラや 異なるタイプのセンサーが備わっています カメラフレームなどの これらのセンサーからのデータは クライアントスペースには送信されません その代わりにセンサーデータは アルゴリズムによって 安全に処理されるように ARKitのデーモンに送信されます 結果としてこれらのアルゴリズムにより 生成されるデータは データを要求するみなさんのアプリなどの クライアントに転送される前に慎重に 収集されます ARKitデータにアクセスするための 前提条件があります アプリはフルスペースに 入る必要があります ARKitは共有スペースにいる アプリにデータを送信しません 次に 一部のARKitデータのタイプには アクセス権限が必要です 権限を付与されていない人には 私どもはそのタイプのデータを アプリ側に送信しません これを促進するために ARKitは 権限を処理するために便利な 認証APIを提供します セッションを使用することで アクセスしたいデータのタイプの アクセス権を要求できます これを行わない場合 必要に応じて ARKitにより セッションを実行する場合に 自動的に 権限をリクエストするプロンプトが 表示されます ここではハンドトラッキングデータへの アクセスを要求しています 単一のリクエストで 必要なすべての認証タイプを 一括にまとめることができます 認証結果を得たら 同じ操作を繰り返して各認証タイプの ステータスを確認します 権限が付与されている場合 ステータスは「allowed」になります アクセスが拒否されたデータを提供して データプロバイダでセッションを 実行しようとすると セッションの失敗につながります では このプラットフォームで ARKitがサポートする それぞれの機能の詳細を見てみましょう まずはワールドトラッキングです ワールドトラッキングを使うと バーチャルコンテンツを実世界で アンカーできます ARKitは6DoFでデバイスの動きを トラッキングし アンカーが環境に関連づけられた 同じ場所から動かないように 各アンカーを更新します ワールドトラッキングが使用する DataProviderは WorldTrackingProviderと呼ばれ いくつかの重要な機能を提供します WorldAnchorを追加できるようになり ARKitはデバイスが移動した場合 ユーザーの環境に関連付けられた 場所に固定されます WorldAnchorはバーチャルコンテンツの配置に 不可欠なツールです 追加したWorldAnchorは アプリの起動と再起動で 自動的に永続化されます この動作が構築する体験に 望ましくない場合は 使い終わったときに アンカーを削除すれば 永続化されなくなります 一部の場合では 永続化を利用できないことにご留意ください また WorldTrackingProviderを使用して アプリの原点に基づいた デバイスのポーズを取得できます これはMetalを使用して 独自のレンダリングを行う場合に 必要です では詳細にWorldAnchorが何かを調べ これを使用すべき理由を説明します WorldAnchorは トランスフォームを受け取る イニシャライザを持つTrackableAnchorで トランスフォームとは アンカーを配置したい アプリの原点に関連付けられた 位置や方向のことを指します アンカーされていないバーチャルコンテンツと アンカーされているバーチャルコンテンツの 違いを 視覚化するための例を見てみましょう ここに2つの立方体があります 左の青い立方体はWorldAnchorによって 更新されていません 右の赤い立方体は WorldAnchorで更新されています どちらの立方体もアプリが起動したときに アプリの原点に応じて配置されています デバイスが動いても どちらも配置された場所から動きません クラウンを押し続けるとアプリを もう一度中心に置くことができます 再び中央に置かれるとき アプリの原点は現在の位置に移動します アンカーされていない青い立方体は アプリの原点に関連する場所を維持するため 再配置されています アンカーされている赤い立方体は 実世界に関連した場所に固定されたままです WorldAnchorの永続化の機能を 説明しましょう デバイスが動くと ARKitは環境をマッピングします WorldAnchorを追加する際に これをマップに挿入すると 自動的に永続化されます WorldAnchorの識別子と トランスフォームのみが永続化されます バーチャルコンテンツなどのその他のデータは 含まれません WorldAnchor識別子を 関連付けるバーチャルコンテンツに 維持するのはみなさん次第です マップは場所ベースであるため 例えば自宅からオフィスへと デバイスを新しい場所に運ぶと 自宅のマップはアンロードされ 異なるマップがオフィス用に ローカライズされます この新しい場所に追加したアンカーは そのマップに取り込まれます 1日の終わりにオフィスを去り 自宅に向かった場合 ARKitがオフィスで 構築したマップと そこに配置したアンカーは アンロードされます 繰り返しになりますが マップとアンカーは 自動的に永続化されています 自宅に着くと ARKitは場所が変わったことに気づき この場所の既存のマップを確認して 再ローカライズの処理を始めます 見つかった場合は そのマップと 自宅に以前に追加したすべてのアンカーは 再びトラッキングされるようになります では デバイスのポーズに移りましょう WorldAnchorの追加と削除に加えて WorldTrackingProviderを使用して デバイスのポーズを取得できます ポーズはアプリの原点に応じた デバイスの位置と方向です フルイマーシブ型の体験向けに MetalとCompositorServicesで 独自のレンダリングをする場合 ポーズのクエリを行う必要があります このクエリは比較的高価なものとなります コンテンツ配置などの その他のタイプのアプリのロジック用に デバイスのクエリを実行する際は 慎重に行いましょう 簡素化されたレンダリングの例で デバイスポーズをARKitから CompositorServicesに 提供する方法をお見せします セッション ワールドトラッキングプロバイダ 最新のポーズを保有する Renderer構造体があります Rendererを初期化するには まずセッションを作成します 次に各フレームをレンダリングする際の デバイスポーズのクエリに使用する ワールドトラッキングプロバイダを 作成します 必要なデータプロバイダで セッションを実行します この例では ワールドトラッキング プロバイダのみを使用します また レンダー関数の割当を開始するために ポーズを作成します フレームレートで呼び出す レンダー関数に移ります CompositorServicesからの drawableを使用して ターゲットレンダータイムを取得します 次にそのターゲットレンダータイムを使い デバイスのポーズを照会します 成功すると アプリの原点に関連する ポーズのトランスフォームを抽出できます このトランスフォームを コンテンツのレンダリングに使用します 最後に 合成のフレームを送信する前に drawableでポーズを設定し フレームのコンテンツを レンダリングするのに どのポーズを使用したかを コンポジタに伝達します 独自のレンダリングの詳細については Metalを使用したイマーシブアプリの作成に 焦点を当てたセッションをご確認ください さらに 空間コンピューティングの パフォーマンスについての 考慮事項を説明する セッションもおすすめします 次はシーン認識を見てみましょう シーン認識は機能の一部で 異なる方法で環境についての 情報を提供してくれます まず「平面検出」を見てみましょう 平面検出はARKitが実世界で検出する 水平面と垂直面上のアンカーを提供します 平面検出が使用する DataProviderのタイプは PlaneDetectionProviderと呼ばれます 環境で平面が検出されると PlaneAnchorの形で提供されます PlaneAnchorはテーブルに バーチャルオブジェクトを配置するなど コンテンツの配置を促進するために 使用されます また 床や壁など基本的で 十分に平面な配置の 物理シミュレーションにも 使用できます 各PlaneAnchorには水平と垂直方向の 調整や平面のジオメトリ 分類結果が含まれています 平面は床やテーブルなど 異なるタイプの表面の 種類として分類されます 特定の表面を識別できない場合は 提供された分類は状況に応じて 「未知」「不確定」 「利用不可」として示されます ではシーンジオメトリに移りましょう シーンジオメトリは実世界の形状を推定する ポリゴンメッシュを含有する アンカーを提供します シーンジオメトリが使用する DataProviderタイプは SceneReconstructionProviderと 呼ばれます ARKitが周りの世界をスキャンするともに 環境は細分化されたメッシュとして 再構築されます MeshAnchorの形で提供されます PlaneAnchorのように コンテンツ配置の促進に MeshAnchorを使用できます また バーチャルコンテンツを 表面が平面でない物体と インタラクションさせる必要がある場合に 忠実度のより高い物理シミュレーションを 実現することもできます 各MeshAnchorには メッシュのジオメトリが含まれます このジオメトリには頂点 法線 面と 面ごとの分類結果が含まれています メッシュ面は異なるタイプの物体の 種類として分類できます 特定の物体を識別できない場合 提供される分類は「なし」になります 最後に画像トラッキングを見てみましょう 画像トラッキングは 実世界の2D画像を 検出するのに使用できます 画像トラッキングが使用する DataProviderのタイプは ImageTrackingProviderと呼ばれます ImageTrackingProviderは検出したい 一連のReferenceImageで構成できます これらのReferenceImageは 異なる方法で作成できます プロジェクトのアセットカタログの ARリソースグループから ロードするオプションがあり また CVPixelBuffer またはCGImageを提供して 自身でReferenceImageを 初期化することもできます 画像が検出されると ARKitはImageAnchorを提供します ImageAnchorsはコンテンツを 既知で静的に配置された画像に 配置するのに使用します たとえば映画のポスターの横に 映画についての説明を配置できます ImageAnchorsはTrackableAnchorで 推定のスケール要因が含まれ 検出された画像のサイズが 指定した物理的サイズや アンカーが対応するReferenceImageと 比較される方法を示します 次は新機能ハンドトラッキングについてです Connerがこの機能の 例をご紹介します Conner: どうも ハンドトラッキングを見てみましょう ARKitに追加された最新機能です ハンドトラッキングはそれぞれの手の 骨格データを含むアンカーを提供します ハンドトラッキングが使用する DataProviderのタイプは HandTrackingProviderと呼ばれます 手が検出されるとHandAnchorの形で これが提供されます HandAnchorはTrackableAnchorです HandAnchorには骨格と キラリティーが含まれています キラリティーは 左手であるか右手であるかを伝えます HandAnchorのトランスフォームは アプリの原点に関連する 手首のトランスフォームです 骨格は関節から構成され 名前で照会できます 関節には親関節 その名前 親関節に関連するlocalTransform ルート関節に関連する rootTransformが含まれ 各関節にはブール型が含まれており この関節がトラッキングされているか どうかを示します ここでは手の骨格で利用可能な すべての関節を列挙しています 関節階層のサブセットを見てみましょう 手首は手のルート関節です 各指の最初の関節は手首に属しています たとえば 1は0に属しています 後続の指の関節は その前の関節に属しています たとえば 2は1に属しており 他も同じです HandAnchorは手に関連する コンテンツの配置や カスタムジェスチャの検出に使用します HandAnchorを受信するための オプションは2つあります 更新のポーリングを行うか 利用可能なときに 非同期でアンカーを受信します Swiftの非同期の更新の例は 後でお見せしますが 今はハンドアンカーのポーリングを 先程のレンダラに追加しましょう これは更新された構造体の定義です ハンドトラッキングプロバイダと 左と右のハンドアンカーを追加しました 更新されたinit関数で 新しい ハンドトラッキングプロバイダを作成し 実行するプロバイダの リストに追加してから ポーリングする際に必要となる 左右のハンドアンカーを作成します レンダーループの割当を回避するために これを先に作成してください 構造体が更新され初期化されたら レンダー関数で get_latest_anchorsを呼び出します プロバイダと 事前割当された ハンドアンカーをパスします 利用可能な最新のデータで アンカーが生成されます 最新のアンカーが生成されると 体験でデータを使用できるようになります すばらしいですね では先程お見せした例に戻りましょう ARKitとRealityKitの 機能の組み合わせを使い この体験を構築しました シーンジオメトリは物理的なコライダと ジェスチャとして使用され ハンドトラッキングは 立方体エンティティを直接操作するのに 使用されます この例が構築された方法を見てみましょう まずアプリの構造体とビューモデルを 確認します 次に ARKitセッションを初期化します そして 指先用のコライダと シーン再構築からのコライダを追加します 最後に ジェスチャで立方体を 追加する方法を見てみましょう さっそく始めましょう こちらがTimeForCubeアプリです 比較的標準的なSwiftUIアプリと シーンが設定されています シーン内で ImmersiveSpaceを宣言します IimmersiveSpaceは ARKitデータにアクセスするために フルスペースに移動するために必要です ImmersiveSpace内で RealityViewを定義すると ビューモデルからコンテンツが提示されます アプリのほとんどのロジックは ビューモデルに存在しています それを見てみましょう ビューモデルは ARKitにくっついています 使用するデータプロバイダは 作成するその他すべての エンティティを含む コンテンツエンティティ シーンとハンドコライダマップです また ビューモデルはアプリから呼び出す さまざまな機能を提供します これらをアプリのコンテキストから 説明しましょう 最初に呼び出す関数は contentEntityを設定するための RealityView内のmakeクロージャです ビューモデルがビューのコンテンツに エンティティを追加できるように このエンティティをRealityViewの コンテンツに追加します setupContentEntityは すべての指エンティティを contentEntityの子としてマップに追加し 返します いいですね セッションの初期化に移りましょう セッションの初期化は 3つのタスクの1つを実行します 最初のタスクは runSession関数を呼び出します この関数は2つのプロバイダで セッションを実行します セッションの実行中 アンカーの更新情報を 受信することができます 立方体の操作にに使用する 指先コライダを作成し更新しましょう こちらは 手の更新を処理するタスクです この関数はプロバイダのアンカー更新の 非同期シーケンスを反復します ハンドアンカーがトラッキングされ 人差し指の指先の関節を取得し 関節自体がトラッキングされることを確認します 次に アプリの原点に関連する 人差し指の指先の トランスフォームを計算します 最後に どの指エンティティを更新し トランスフォームを 設定すべきかを検索します
指エンティティの マップをもう一度見てみましょう ModelEntityの拡張子を介して 各手のエンティティを作成します この拡張子は衝突シェイプの 5ミリメートルの球体を作成します キネマティック物理 ボディコンポーネントを追加して 不透明コンポーネントを追加して このエンティティを非表示にします このユースケースでは非表示にしますが すべてが期待どおりに機能していることを 確認するために 指先エンティティを表示することが 好ましいでしょう 一時的に不透明度を1に設定し エンティティが適切な場所に 配置されているか確認しましょう すばらしい 指先に球体が配置されています 手が球体の位置を覆っています これはハンドオクルージョンという システム機能で バーチャルコンテンツ上で手を確認できるように するためのものです これはデフォルトで有効になっていますが 球体をより明確に表示したい場合は シーンの upperLimbVisibilityセッターで ハンドオクルージョンの可視性を 構成できます 手の可視性を非表示に設定すると 手がどこにあっても 全体の球体を確認できます この例では上肢の可視性を デフォルトの値のままにし 不透明度をゼロに戻します すばらしい ではシーンコライダを 追加しましょう これはフィジックスと ジェスチャターゲットに使用します こちらはモデルの関数を呼び出すタスクです プロバイダのアンカー更新の 非同期シーケンスで 反復して MeshAnchorから ShapeResourceを生成することを 試みてから アンカー更新のイベントに切り替えます アンカーを追加する場合は 新しいエンティティを作成して そのトランスフォームを設定し コリジョンと 物理ボディコンポーネントを追加し このコライダがジェスチャの ターゲットとなるように 入力ターゲットコンポーネントを追加します 最後に 新しいエンティティを コンテンツエンティティの子として 追加します エンティティを更新するには それをマップから取得して トランスフォームとコリジョン コンポーネント形状を更新します 削除するには 親とマップに対応する エンティティを削除します 手とシーンコライダができたので 立方体にジェスチャを追加します エンティティにターゲットされた SpatialTapGestureを追加します これは誰かがRealityViewのコンテンツの エンティティをタップした場合に 通知を送信します タップが完了したら グローバルからシーン座標に 変換する3D位置を受信します この位置を視覚化しましょう タップの場所に球体を 追加するとこうなります ビューモデルにこの場所に関連する場所に 立方体を追加するよう伝えます 立方体を追加するには まず タップされる場所の 20センチ上にある配置場所を計算します 次に立方体を作成し計算された 配置場所に設定します エンティティが反応するジェスチャの種類を 設定するための InputTargetComponentを追加します このユースケースでは 指先コライダが直接的な操作を 提供するため 立方体に対し間接的な 入力タイプのみを許可します 物理的な操作が向上するように カスタムパラメータで PhysicsBodyComponentを追加します 最後に 立方体を コンテンツエンティティに追加します これで立方体の準備ができました この例をエンドツーエンドで もう一度見てみましょう シーンコライダか立方体を タップするたびに 新しい立方体がタップ場所の 上に追加されます 物理システムにより 立方体は シーンコライダに落下します ハンドコライダで立方体を操作できます RealityKitについての詳細は 入門的な空間コンピューティングでの RealityKitの使用に関する セッションをご覧ください このプラットフォームに 取り入れたいと思う 既存のARKit体験をすでに お持ちの場合は このトピックについての 専門的なセッションを参照してください みなさんが新しいARKitを使用することを チーム全員が楽しみにしています このすばらしい新しいプラットフォームで みなさんが構築する画期的な アプリを見るのが楽しみです Ryan: ご視聴ありがとうございます ♪
-
-
5:20 - Authorisation API
// Privacy // Authorization session = ARKitSession() Task { let authorizationResult = await session.requestAuthorization(for: [.handTracking]) for (authorizationType, authorizationStatus) in authorizationResult { print("Authorization status for \(authorizationType): \(authorizationStatus)") switch authorizationStatus { case .allowed: // All good! break case .denied: // Need to handle this. break ... } } }
-
10:20 - World Tracking Device Pose Render Struct
// World tracking // Device pose #include <ARKit/ARKit.h> #include <CompositorServices/CompositorServices.h> struct Renderer { ar_session_t session; ar_world_tracking_provider_t world_tracking; ar_pose_t pose; ... }; void renderer_init(struct Renderer *renderer) { renderer->session = ar_session_create(); ar_world_tracking_configuration_t config = ar_world_tracking_configuration_create(); renderer->world_tracking = ar_world_tracking_provider_create(config); ar_data_providers_t providers = ar_data_providers_create(); ar_data_providers_add_data_provider(providers, renderer->world_tracking); ar_session_run(renderer->session, providers); renderer->pose = ar_pose_create(); ... }
-
10:21 - World Tracking Device Pose Render function
// World tracking // Device pose void render(struct Renderer *renderer, cp_layer_t layer, cp_frame_t frame_encoder, cp_drawable_t drawable) { const cp_frame_timing_t timing_info = cp_drawable_get_frame_timing(drawable); const cp_time_t presentation_time = cp_frame_timing_get_presentation_time(timing_info); const CFTimeInterval target_render_time = cp_time_to_cf_time_interval(presentation_time); simd_float4x4 pose = matrix_identity_float4x4; const ar_pose_status_t status = ar_world_tracking_provider_query_pose_at_timestamp(renderer->world_tracking, target_render_time, renderer->pose); if (status == ar_pose_status_success) { pose = ar_pose_get_origin_from_device_transform(renderer->pose); } ... cp_drawable_set_ar_pose(drawable, renderer->pose); ... }
-
16:00 - Hand tracking joints
/ Hand tracking @available(xrOS 1.0, *) public struct Skeleton : @unchecked Sendable, CustomStringConvertible { public func joint(named: SkeletonDefinition.JointName) -> Skeleton.Joint public struct Joint : CustomStringConvertible, @unchecked Sendable { public var parentJoint: Skeleton.Joint? { get } public var name: String { get } public var localTransform: simd_float4x4 { get } public var rootTransform: simd_float4x4 { get } public var isTracked: Bool { get } } }
-
17:00 - Hand tracking with Render struct
// Hand tracking // Polling for hands struct Renderer { ar_hand_tracking_provider_t hand_tracking; struct { ar_hand_anchor_t left; ar_hand_anchor_t right; } hands; ... }; void renderer_init(struct Renderer *renderer) { ... ar_hand_tracking_configuration_t hand_config = ar_hand_tracking_configuration_create(); renderer->hand_tracking = ar_hand_tracking_provider_create(hand_config); ar_data_providers_t providers = ar_data_providers_create(); ar_data_providers_add_data_provider(providers, renderer->world_tracking); ar_data_providers_add_data_provider(providers, renderer->hand_tracking); ar_session_run(renderer->session, providers); renderer->hands.left = ar_hand_anchor_create(); renderer->hands.right = ar_hand_anchor_create(); ... }
-
17:25 - hand tracking call in render function
// Hand tracking // Polling for hands void render(struct Renderer *renderer, ... ) { ... ar_hand_tracking_provider_get_latest_anchors(renderer->hand_tracking, renderer->hands.left, renderer->hands.right); if (ar_trackable_anchor_is_tracked(renderer->hands.left)) { const simd_float4x4 origin_from_wrist = ar_anchor_get_origin_from_anchor_transform(renderer->hands.left); ... } ... }
-
18:00 - Demo app TimeForCube
// App @main struct TimeForCube: App { @StateObject var model = TimeForCubeViewModel() var body: some SwiftUI.Scene { ImmersiveSpace { RealityView { content in content.add(model.setupContentEntity()) } .task { await model.runSession() } .task { await model.processHandUpdates() } .task { await model.processReconstructionUpdates() } .gesture(SpatialTapGesture().targetedToAnyEntity().onEnded({ value in let location3D = value.convert(value.location3D, from: .global, to: .scene) model.addCube(tapLocation: location3D) })) } } }
-
18:50 - Demo app View Model
// View model @MainActor class TimeForCubeViewModel: ObservableObject { private let session = ARKitSession() private let handTracking = HandTrackingProvider() private let sceneReconstruction = SceneReconstructionProvider() private var contentEntity = Entity() private var meshEntities = [UUID: ModelEntity]() private let fingerEntities: [HandAnchor.Chirality: ModelEntity] = [ .left: .createFingertip(), .right: .createFingertip() ] func setupContentEntity() { ... } func runSession() async { ... } func processHandUpdates() async { ... } func processReconstructionUpdates() async { ... } func addCube(tapLocation: SIMD3<Float>) { ... } }
-
20:00 - function HandTrackingProvider
class TimeForCubeViewModel: ObservableObject { ... private let fingerEntities: [HandAnchor.Chirality: ModelEntity] = [ .left: .createFingertip(), .right: .createFingertip() ] ... func processHandUpdates() async { for await update in handTracking.anchorUpdates { let handAnchor = update.anchor guard handAnchor.isTracked else { continue } let fingertip = handAnchor.skeleton.joint(named: .handIndexFingerTip) guard fingertip.isTracked else { continue } let originFromWrist = handAnchor.transform let wristFromIndex = fingertip.rootTransform let originFromIndex = originFromWrist * wristFromIndex fingerEntities[handAnchor.chirality]?.setTransformMatrix(originFromIndex, relativeTo: nil) }
-
21:20 - function SceneReconstruction
func processReconstructionUpdates() async { for await update in sceneReconstruction.anchorUpdates { let meshAnchor = update.anchor guard let shape = try? await ShapeResource.generateStaticMesh(from: meshAnchor) else { continue } switch update.event { case .added: let entity = ModelEntity() entity.transform = Transform(matrix: meshAnchor.transform) entity.collision = CollisionComponent(shapes: [shape], isStatic: true) entity.physicsBody = PhysicsBodyComponent() entity.components.set(InputTargetComponent()) meshEntities[meshAnchor.id] = entity contentEntity.addChild(entity) case .updated: guard let entity = meshEntities[meshAnchor.id] else { fatalError("...") } entity.transform = Transform(matrix: meshAnchor.transform) entity.collision?.shapes = [shape] case .removed: meshEntities[meshAnchor.id]?.removeFromParent() meshEntities.removeValue(forKey: meshAnchor.id) @unknown default: fatalError("Unsupported anchor event") } } }
-
22:20 - add cube at tap location
class TimeForCubeViewModel: ObservableObject { func addCube(tapLocation: SIMD3<Float>) { let placementLocation = tapLocation + SIMD3<Float>(0, 0.2, 0) let entity = ModelEntity( mesh: .generateBox(size: 0.1, cornerRadius: 0.0), materials: [SimpleMaterial(color: .systemPink, isMetallic: false)], collisionShape: .generateBox(size: SIMD3<Float>(repeating: 0.1)), mass: 1.0) entity.setPosition(placementLocation, relativeTo: nil) entity.components.set(InputTargetComponent(allowedInputTypes: .indirect)) let material = PhysicsMaterialResource.generate(friction: 0.8, restitution: 0.0) entity.components.set(PhysicsBodyComponent(shapes: entity.collision!.shapes, mass: 1.0, material: material, mode: .dynamic)) contentEntity.addChild(entity) } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。