Hand Gestures
TL;DR: GestureLeft and GestureRight are floats in the range -1..6, with 0 as idle. Eight discrete values plus an analog band have meaning. The branch that produces the analog band is device-specific — for the default scheme (Oculus/Touch and similar) the analog value comes from the grip axis, not the trigger. Index/XR-Hand and Vive use the trigger axis instead.
Gesture values
Section titled “Gesture values”| Value | Name | Visual |
|---|---|---|
-1 | Hand Open | Relaxed open palm |
0 | Idle / Neutral | Default fallback |
0..1 | Trigger or grip curl (analog, device-specific) | Continuous |
2 | Fist | All fingers closed |
3 | Thumbs Up | Thumb extended, others closed |
4 | Point | Index extended |
5 | Peace / Victory | Index + middle extended |
6 | Gun / Rock ‘n’ Roll | Index + thumb extended |
These are also the integer keys for the blocked-avatar hand mesh — see the blocked-avatar mapping below for the actual blendshape index it produces.
Per-device behaviour
Section titled “Per-device behaviour”CVRXRModule.Update_Gestures() dispatches based on Type:
case eXRControllerType.Index:case eXRControllerType.XRHand: Update_Gestures_Index(...); break;case eXRControllerType.Vive:case eXRControllerType.MixedReality: Update_Gestures_Vive(); break;default: Update_Gestures_Default(); break;Default (Oculus / Touch / others)
Section titled “Default (Oculus / Touch / others)”Update_Gestures_Default() checks three input categories — surface touch (any of Primary2DAxisTouch, PrimaryTouch, SecondaryTouch), trigger active (TriggerTouch || Trigger > 0), grip active (GripTouch || Grip > 0):
| Touch | Trigger | Grip | Gesture |
|---|---|---|---|
| no | no | no | -1 (Hand Open) |
| yes | yes | yes | analog grip value |
| no | yes | yes (>0.5) | 2 (Fist) |
| no | no | yes (>0.5) | 3 (Thumbs Up) |
| yes | no | yes (>0.5) | 4 (Point) |
| yes | no | no | 5 (Peace) |
| yes | yes (>0.5) | no | 6 (Gun) |
The 0..1 analog band on this scheme is the grip axis, not the trigger axis, and it requires touch + trigger + grip to all be simultaneously active.
Vive / Mixed Reality
Section titled “Vive / Mixed Reality”Update_Gestures_Vive() projects Primary2DAxis (or EmoteOverride when held) into a polar angle and chooses a gesture by sector. Grip > 0.5 (without trackpad click) overrides everything to -1. Inside VrViveGestureDeadZone, Gesture = Trigger (analog).
| Trackpad sector | Left hand | Right hand |
|---|---|---|
(252°, 324°] | 2 | 5 |
(180°, 252°] | 3 | 6 |
(324°, 360°] ∪ [0°, 36°] | 4 | 4 |
(36°, 108°] | 5 | 2 |
(108°, 180°] | 6 | 3 |
| inside dead zone, no click | Trigger | Trigger |
Grip > 0.5, no click | -1 | -1 |
Index / XR Hand
Section titled “Index / XR Hand”Update_Gestures_Index(bool isIndex) thresholds the five FingerFullCurlNormalized* values against CVRInputManager.VrIndexLowerBound and VrIndexUpperBound. “low” = below VrIndexLowerBound; “high” = at or above VrIndexUpperBound.
| Thumb | Index | Middle | Ring | Pinky | Gesture |
|---|---|---|---|---|---|
| low | low | low | low | low | -1 (Hand Open) |
| any high | high | high | high | high | Trigger (analog) |
| low | high | high | high | high | 2 (Fist) |
| low | low | high | high | high | 3 (Thumbs Up) |
| high | low | high | high | high | 4 (Point) |
| high | low | low | high | high | 5 (Peace) |
| high | high | low | high | high | 6 (Gun) |
The result lands in GestureRaw. Gesture only follows GestureRaw while SkeletalToggleValue is on (Index) or while SkeletalToggleValue && !IsInteractingWithButton (XR Hand).
GestureLeft/Right ↔ GestureLeftIdx/RightIdx
Section titled “GestureLeft/Right ↔ GestureLeftIdx/RightIdx”Writing GestureLeft or GestureRight on AvatarAnimatorManager automatically computes the integer index and writes it to GestureLeftIdx or GestureRightIdx:
public float GestureRight{ set { _coreSyncData.GestureRight = value; if (Parameters.TryGetValue("GestureRight", out var p)) SetParameter_Internal(p, value); if (Parameters.TryGetValue("GestureRightIdx", out var pIdx)) SetParameter_Internal(pIdx, GestureRightIdx = Mathf.RoundToInt(value)); }}You only need to declare GestureLeftIdx/GestureRightIdx on your animator if you want the integer form for state-machine transitions — the float GestureLeft/GestureRight is enough for most workflows.
Blocked-avatar blendshape mapping
Section titled “Blocked-avatar blendshape mapping”CVRBlockedAvatarController.LateUpdate reads GestureLeft/GestureRight and drives a 7-element blendshape array on the blocked-avatar hand mesh. The mapping is not positional — gesture values are not used as blendshape indices directly:
| Gesture | Blendshape index |
|---|---|
-1 (Hand Open) | 0 |
4 (Point) | 1 |
3 (Thumbs Up) | 2 |
5 (Peace) | 3 |
| anything else, non-zero (e.g. analog) | 4 |
2 (Fist) | 5 |
6 (Gun) | 6 |
Index 4 is the catch-all for any non-zero, non-handled value, so the analog 0..1 band drives blendshape 4 on blocked avatars regardless of which device produced it.