1. はじめに
このAPIはユーザーの両手のスケルトンジョイントごとの姿勢を公開します。これを利用してジェスチャー検出や、VRシナリオでの手モデルの描画が可能になります。
2. 初期化
アプリケーションがセッション中に手の関節姿勢情報を取得したい場合は、該当するフィーチャディスクリプタでセッションをリクエストする必要があります。文字列"hand-tracking"は、本モジュールにより導入される、新たな有効な手関節トラッキング用フィーチャディスクリプタです。
"hand-tracking" フィーチャディスクリプタは、XRSessionのXRデバイスが物理ハンド入力ソースを持ち、ハンドトラッキングに対応している場合のみに許可されるべきです。
ユーザーエージェントは、このフィーチャディスクリプタに基づき、手ベースのXRInputSourcesサポートを制御することができます。
注: つまり、XRSessionが"hand-tracking"フィーチャディスクリプタをリクエストしない場合、ユーザーエージェントは手ベースの入力コントローラーをサポートしないことがあります。
3. 物理ハンド入力ソース
XRInputSource
は、物理的な手をトラッキングする場合、物理ハンド入力ソースです。物理ハンド入力ソースがハンドトラッキングに対応するのは、本仕様で定義されるスケルトンジョイントの1つ以上の姿勢報告をサポートしている場合です。
物理ハンド入力ソースには、入力プロファイル名として"generic-hand-select"をprofilesに必ず含めなければなりません。
多くの物理ハンド入力ソースの場合、プライマリアクションとスクイーズアクションに使われるジェスチャーが重複することがあります。例えば、つまみ(ピンチ)ジェスチャーは、近くや遠くのオブジェクトとの相互作用に応じて、「選択」イベントあるいは「スクイーズ」イベントの両方を示せます。コンテンツがこれらを独立したイベントとして扱う可能性があるため、ユーザーエージェントはスクイーズアクションをプライマリースクイーズアクションとして公開する代わりに、"generic-hand-select-grasp"プロファイル由来の入力プロファイルを用いて追加の「グラスプボタン」として公開することができます。
3.1. XRInputSource
partial interface XRInputSource { [SameObject ]readonly attribute XRHand ?hand ; };
ハンドトラッキングに対応するhand属性は、基盤となるハンドトラッキング機能にアクセスできるXRHandオブジェクトです。handは、その入力ソースがthisに設定されます。
XRInputSource
が、"hand-tracking"フィーチャディスクリプタをリクエストしていないXRSessionに属している場合、handはnullでなければなりません。
3.2. スケルトンジョイント
物理ハンド入力ソースは多数のスケルトンジョイントで構成されます。
手のスケルトンジョイントは、スケルトンジョイント名で一意に識別できます。これはXRHandJoint型の列挙値です。
スケルトンジョイントには、それにちなんだ名称で-Z軸の向きを決定するために使われる対応する骨がある場合があります。対応する骨とは、スケルトンジョイントから指先方向に進んだとき、ジョイントの次に現れる骨のことです。tipおよびwristジョイントには対応する骨はありません。
スケルトンジョイントには、その中心に球を配置して手の両側の皮膚に概ね接するようにした時の半径を持ちます。"tip" スケルトンジョイントは、指先との衝突判定のために適切な非ゼロ半径を持つべきです。実装では、tipジョイントの原点をずらして非ゼロ半径の球体形状にしても構いません。
このジョイントリストは、以下のスケルトンジョイントと順序を定めます:
| スケルトンジョイント | スケルトンジョイント名 | インデックス | |
|---|---|---|---|
| 手首 | wrist
| 0 | |
| 親指 | 中手骨 | thumb-metacarpal
| 1 |
| 近位指骨 | thumb-phalanx-proximal
| 2 | |
| 遠位指骨 | thumb-phalanx-distal
| 3 | |
| 先端 | thumb-tip
| 4 | |
| 人差し指 | 中手骨 | index-finger-metacarpal
| 5 |
| 近位指骨 | index-finger-phalanx-proximal
| 6 | |
| 中節骨 | index-finger-phalanx-intermediate
| 7 | |
| 遠位指骨 | index-finger-phalanx-distal
| 8 | |
| 先端 | index-finger-tip
| 9 | |
| 中指 | 中手骨 | middle-finger-metacarpal
| 10 |
| 近位指骨 | middle-finger-phalanx-proximal
| 11 | |
| 中節骨 | middle-finger-phalanx-intermediate
| 12 | |
| 遠位指骨 | middle-finger-phalanx-distal
| 13 | |
| 先端 | middle-finger-tip
| 14 | |
| 薬指 | 中手骨 | ring-finger-metacarpal
| 15 |
| 近位指骨 | ring-finger-phalanx-proximal
| 16 | |
| 中節骨 | ring-finger-phalanx-intermediate
| 17 | |
| 遠位指骨 | ring-finger-phalanx-distal
| 18 | |
| 先端 | ring-finger-tip
| 19 | |
| 小指 | 中手骨 | pinky-finger-metacarpal
| 20 |
| 近位指骨 | pinky-finger-phalanx-proximal
| 21 | |
| 中節骨 | pinky-finger-phalanx-intermediate
| 22 | |
| 遠位指骨 | pinky-finger-phalanx-distal
| 23 | |
| 先端 | pinky-finger-tip
| 24 | |
3.3. XRHand
enum {XRHandJoint ,"wrist" ,"thumb-metacarpal" ,"thumb-phalanx-proximal" ,"thumb-phalanx-distal" ,"thumb-tip" ,"index-finger-metacarpal" ,"index-finger-phalanx-proximal" ,"index-finger-phalanx-intermediate" ,"index-finger-phalanx-distal" ,"index-finger-tip" ,"middle-finger-metacarpal" ,"middle-finger-phalanx-proximal" ,"middle-finger-phalanx-intermediate" ,"middle-finger-phalanx-distal" ,"middle-finger-tip" ,"ring-finger-metacarpal" ,"ring-finger-phalanx-proximal" ,"ring-finger-phalanx-intermediate" ,"ring-finger-phalanx-distal" ,"ring-finger-tip" ,"pinky-finger-metacarpal" ,"pinky-finger-phalanx-proximal" ,"pinky-finger-phalanx-intermediate" ,"pinky-finger-phalanx-distal" }; ["pinky-finger-tip" Exposed =Window ]interface {XRHand iterable <XRHandJoint ,XRJointSpace >;readonly attribute unsigned long size ;XRJointSpace (get XRHandJoint ); };key
XRHandJoint列挙型は各XRHandが必ず備えるべきジョイント種別を定義します。
すべてのXRHandには、トラッキング対象の物理ハンド入力ソースである入力ソースが関連付けられています。
注: handednessプロパティは、XRInputSourceがどちらの手に対応するかを示します。
XRHandオブジェクトには[[joints]]内部スロットがあり、キーがXRHandJoint型、値がXRJointSpace型の順序付きマップです。
[[joints]]の順序はスケルトンジョイントの下のジョイントリストによります。
[[joints]]はセッション中に変化してはいけません。
個々のデバイスが本仕様で定めるジョイントをサポートしない場合、必ずエミュレートしなければなりません。
size属性は、必ず数値25を返さなければなりません。
get(jointName)メソッドをXRHandで呼び出すと、次の手順を実行します:
-
jointsを、thisの
[[joints]]内部スロット値とする。 -
joints[jointName]を返す。(未知のjointNameの場合、
undefinedとなることを意味します。)
3.4. XRJointSpace
[Exposed =Window ]interface :XRJointSpace XRSpace {readonly attribute XRHandJoint ; };jointName
XRJointSpaceのネイティブ原点は、基盤となる関節の位置と向きです。
XRJointSpaceのネイティブ原点は、同じ手上の他のすべてのXRJointSpaceのネイティブ原点が報告されている場合のみ報告されます。手が一部隠れている場合、ユーザーエージェントは隠れた関節をエミュレートするか、すべての関節の姿勢をnullで報告しなければなりません。
注: 姿勢の取得時には手全体か何も得られないかのいずれかとなります。
これにより、多指症や少指症の手の忠実な公開は現状できませんが、フィンガープリンティング懸念からも、いずれにせよ別途オプトインが必要となる可能性が高いです。詳細はIssue 11を参照してください。
ネイティブ原点の-Y方向は手のひらから外側に向けて皮膚と直交し、-Z方向は対応する骨に沿って、手首から指先方向を指します。
先端のスケルトンジョイントで
対応する骨が存在しない場合、-Z方向は対応する遠位ジョイントと同じ、すなわち一つ前の骨の方向になります。手首のスケルトンジョイントでは、-Z方向は手のひらの中心方向を大まかに指すべきです。
すべてのXRJointSpaceには、作成元であるXRHandである手が関連付けられています。
jointNameは、トラッキングしている関節の名称を返します。
すべてのXRJointSpaceには、スケルトンジョイントリスト内で対応するjointNameの関節が関連付けられています。
4. フレームループ
4.1. XRFrame
partial interface XRFrame {XRJointPose ?getJointPose (XRJointSpace ,joint XRSpace );baseSpace boolean fillJointRadii (sequence <XRJointSpace >,jointSpaces Float32Array );radii boolean fillPoses (sequence <XRSpace >,spaces XRSpace ,baseSpace Float32Array ); };transforms
getJointPose(XRJointSpace joint, XRSpace baseSpace)メソッドは、XRFrameのtime時点で、jointのbaseSpaceに対する姿勢をXRJointPoseで返します。
このメソッドが呼び出されると、ユーザーエージェントは以下の手順を実行します:
-
frameをthisとする。
-
sessionをframeの
sessionオブジェクトとする。 -
frameのactiveが
falseなら、InvalidStateErrorを投げる。 -
baseSpaceまたはjointのsessionが
sessionと異なれば、InvalidStateErrorを投げる。 -
poseを、sessionのrelevant realmで新しい
XRJointPoseとする。 -
jointのbaseSpaceでframe時点の姿勢情報を投入し、
force emulationはfalseにする。 -
poseが
nullなら、nullを返す。 -
poseを返す。
fillJointRadii(sequence<XRJointSpace> jointSpaces, Float32Array radii)メソッドはjointSpacesの半径値をradiiに格納し、すべてのspaceに有効な姿勢があるかどうかを示すブール値を返します。
このメソッドをXRFrameインスタンスframeで呼ぶと、ユーザーエージェントは以下を実行します:
-
frameをthisとする。
-
sessionをframeの
sessionオブジェクトとする。 -
frameのactiveが
falseであれば、InvalidStateErrorを投げる。 -
jointSpaces内の各jointについて:
-
jointのsessionとsessionが異なれば、
InvalidStateErrorを投げる。
-
-
jointSpacesの長さがradiiの要素数より大きければ、
TypeErrorを投げる。 -
offsetを
0とする。 -
allValidを
trueとする。 -
jointSpaces内の各jointについて:
-
allValidを返す。
注: UAが同じXRHandに属するspaceいずれの姿勢も取得できなければ、そのXRHandの全spaceの姿勢も取得できません。
fillPoses(sequence<XRSpace> spaces, XRSpace baseSpace, Float32Array transforms)メソッドは、spacesの各姿勢行列をbaseSpaceに対してtransformsに格納し、すべてのspaceに有効な姿勢があるかどうかのブール値を返します。
このメソッドをXRFrameインスタンスframeで呼ぶと、ユーザーエージェントは以下を実行:
-
frameをthisとする。
-
sessionをframeの
sessionオブジェクトとする。 -
frameのactiveが
falseであれば、InvalidStateErrorを投げる。 -
spaces内の各spaceについて:
-
spaceのsessionがsessionと異なれば、
InvalidStateErrorを投げる。
-
-
baseSpaceのsessionがsessionと異なれば、
InvalidStateErrorを投げる。 -
spacesの長さに
16かけた値がtransformsの要素数より大きければTypeErrorを投げる。 -
offsetを
0とする。 -
poseを次のように初期化:
-
fillPoses()が以前に呼ばれていれば、UAは: - poseを前回と同じオブジェクトとできる。
- それ以外
- poseをsessionのrelevant realm内の新しい
XRPoseとする。
-
-
allValidを
trueとする。 -
spaces内の各spaceについて:
-
allValidを返す。
注: 同じXRHandのspaceが姿勢情報を投入時にnullを返す場合、すべてのspaceも同様にnullを返さなければなりません。
4.2. XRJointPose
XRJointPoseはスケルトンジョイントの大きさ情報を持つXRPoseです。
[Exposed =Window ]interface :XRJointPose XRPose {readonly attribute float radius ; };
radius属性は、対応するスケルトンジョイントの半径(単位: m)を返します。
UAはradiusを、XRデバイスがこの値を判定できない場合(全体またはそのアニメーションフレームで 例:スケルトンジョイントが一部隠れている場合)、エミュレート値にしなければなりません。
5. プライバシーとセキュリティの考慮事項
WebXRハンド入力APIは強力な機能であり、重大なプライバシーリスクが伴います。この機能は新たなセンサーデータを返すため、ユーザーエージェントはセッション作成時にユーザーから明示的同意を必ず取得しなければなりません。
このAPIから返されるデータは、個人を特定できない程度に特異であってはなりません。ハードウェアが精度の高すぎるデータを返す場合、ユーザーエージェントはWebXRハンド入力API経由で公開する前にデータを匿名化しなければなりません。
このAPIは、"immersive-vr"または"immersive-ar"で作成されたXRSessionのみでサポートされるべきです。
"inline"
セッションでは本APIはサポートしてはなりません。
変更点
2020年10月22日初版ワーキングドラフトからの変更点
-
グラスププロファイルへの言及を追加 (GitHub #68)
-
定数からenum化・XRHandをmap化 (GitHub #71)
-
セキュリティセクションの明確化追記 (GitHub #87)
-
handにsameobject付加・補足注記追加 (GitHub #93)
-
tipの非ゼロ半径追加 (GitHub #111)