Hand Tracking¶

The Hand component offers a way to stream the current pose of the hand to the server. To use this mixed reality (XR) feature, you need to setup vuer behind a SSL proxy. We usually do so with ngrok, which is a paid service, or we can do so with local tunnel, which is free.

Warning

Please go through the relevant documentation of either ngrok or localtunnel before preceding.

Here is the what it looks like with the Vision Pro

Hand API¶

You can get the full pose of the hands by listening to the HAND_MOVE event. You can add flags left and right to specify which hand you want to track.

Warning

Make sure that you set the stream option to True to start streaming the hand movement! Otherwise the event will not be triggered. This is to avoid unnecessary clogging up the uplink from the client.

Warning

The WebXR Hand Input API is currently unsupported in the Pico browser. For more information, please refer to the official Pico website. We will monitor updates and integrate support as soon as it becomes available.

from vuer import Vuer, VuerSession
from vuer.schemas import Hands
from asyncio import sleep

app = Vuer()

@app.add_handler("HAND_MOVE")
async def handler(event, session):
    print(f"Movement Event: key-{event.key}", event.value)

@app.spawn(start=True)
async def main(session: VuerSession):
    # Important: You need to set the `stream` option to `True` to start
    # streaming the hand movement.

    session.upsert(
        Hands(
            stream=True,
            key="hands",
            # hideLeft=False,       # hides the hand, but still streams the data.
            # hideRight=False,      # hides the hand, but still streams the data.
            # disableLeft=False,    # disables the left data stream, also hides the hand.
            # disableRight=False,   # disables the right data stream, also hides the hand.
        ),
        to="bgChildren",
    )

    while True:
        await sleep(1)

The returned data looks like the following:

/**
 * Left and right pose are relative to the wrist transformations.
 */
export type HandsData = {
  left?: Float32Array;       // 25 * 16 values.
  right?: Float32Array;      // 25 * 16 values.
  leftState: HandState;
  rightState: HandState;
};

export type HandState = {
  pinch: boolean;
  squeeze: boolean;
  tap: boolean;

  pinchValue: number;
  squeezeValue: number;
  tapValue: number;
}

Matrix format¶

All 4x4 transform matrices used in WebGL are stored in 16-element Float32Arrays. The values are stored in the array in column-major order; that is, each column is written into the array top-down before moving to the next column to the right and writing it into the array. Therefore, for the array [a0, a1, a2, …, a13, a14, a15], the matrix looks like this:

                                  ⌈  a0 a4 a8 a12  ⌉
                                  |  a1 a5 a9 a13  |
                                  |  a2 a6 a10 a14 |
                                  ⌊  a3 a7 a11 a15 ⌋

For details, refer to the MDN documentation on XR Rigid Body Transformation

Hand Landmarks¶

We follow the XR Hand | Hand Joints conventions, and return the landmarks in a single array of 25 * 16 values in the following order:

Hand joint

Index

Hand joint (continue)

Index

wrist

0

middle-finger-phalanx-distal

13

thumb-metacarpal

1

middle-finger-tip

14

thumb-phalanx-proximal

2

ring-finger-metacarpal

15

thumb-phalanx-distal

3

ring-finger-phalanx-proximal

16

thumb-tip

4

ring-finger-phalanx-intermediate

17

index-finger-metacarpal

5

ring-finger-phalanx-distal

18

index-finger-phalanx-proximal

6

ring-finger-tip

19

index-finger-phalanx-intermediate

7

pinky-finger-metacarpal

20

index-finger-phalanx-distal

8

pinky-finger-phalanx-proximal

21

index-finger-tip

9

pinky-finger-phalanx-intermediate

22

middle-finger-metacarpal

10

pinky-finger-phalanx-distal

23

middle-finger-phalanx-proximal

11

pinky-finger-tip

24

middle-finger-phalanx-intermediate

12

-

-