@vincentt-xr/sdk 2.0.0-alpha.6 → 2.0.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GROUNDING.md +1188 -14
- package/dist/{DebuggerUI-C_rbLdkh.js → DebuggerUI-C-ldiozL.js} +1 -1
- package/dist/{DebuggerUI-DYT_dk1E.cjs → DebuggerUI-CWX5EtQv.cjs} +1 -1
- package/dist/FaceMeshRenderer-BEEXZwS6.cjs +158 -0
- package/dist/FaceMeshRenderer-UuH5sTXG.js +1322 -0
- package/dist/FaceRetouch-C7h7x9kd.js +683 -0
- package/dist/FaceRetouch-DqftT26Y.cjs +134 -0
- package/dist/GLTFLoader-C5q63sKO.js +1633 -0
- package/dist/GLTFLoader-Cb_CRwmG.cjs +1 -0
- package/dist/SegmentationOverlay-DQuW3dsc.js +133 -0
- package/dist/SegmentationOverlay-aWVod8Kl.cjs +1 -0
- package/dist/assets/cache.d.ts +99 -0
- package/dist/assets/estimate.d.ts +11 -0
- package/dist/assets/index.d.ts +7 -0
- package/dist/assets/loaders.d.ts +28 -0
- package/dist/assets/react.d.ts +33 -0
- package/dist/calibration/calibration.d.ts +70 -0
- package/dist/calibration/index.d.ts +6 -0
- package/dist/calibration/storage.d.ts +13 -0
- package/dist/calibration/types.d.ts +53 -0
- package/dist/calibration/useXRCalibration.d.ts +46 -0
- package/dist/camera/CameraPermissionGate.d.ts +26 -0
- package/dist/camera/devices.d.ts +26 -0
- package/dist/camera/index.d.ts +8 -0
- package/dist/camera/permission.d.ts +86 -0
- package/dist/camera/useCameraPermission.d.ts +35 -0
- package/dist/capabilities/detect.d.ts +45 -0
- package/dist/capabilities/index.d.ts +5 -0
- package/dist/capabilities/types.d.ts +120 -0
- package/dist/capabilities/useXRCapabilities.d.ts +18 -0
- package/dist/compat/environment.d.ts +5 -0
- package/dist/compat/index.d.ts +4 -0
- package/dist/compat/run.d.ts +18 -0
- package/dist/compat/scenarios.d.ts +10 -0
- package/dist/compat/types.d.ts +101 -0
- package/dist/components/BodyTracker/index.d.ts +9 -0
- package/dist/components/FaceMesh/FaceMeshRenderer.d.ts +11 -3
- package/dist/components/FaceMesh/canonical/CanonicalFaceMeshRenderer.d.ts +41 -5
- package/dist/components/FaceMesh/canonical/parseCanonicalFaceModel.d.ts +12 -0
- package/dist/components/FaceMesh/canonical/shaders.d.ts +1 -1
- package/dist/components/FaceMesh/index.d.ts +2 -2
- package/dist/components/GestureListener/index.d.ts +11 -0
- package/dist/components/HandTracker/index.d.ts +10 -0
- package/dist/components/HolisticTracker/index.d.ts +69 -0
- package/dist/components/HolisticTracker/settings.d.ts +58 -0
- package/dist/components/HolisticTracker/signals.d.ts +49 -0
- package/dist/components/IrisEffect/IrisMaterial.d.ts +73 -0
- package/dist/components/IrisEffect/index.d.ts +51 -0
- package/dist/components/IrisEffect/settings.d.ts +117 -0
- package/dist/components/LipEffect/LipEffect.d.ts +7 -1
- package/dist/components/LipEffect/LipEffectMaterial.d.ts +43 -7
- package/dist/components/LipEffect/LipEffects.d.ts +9 -0
- package/dist/components/LipEffect/defaults.d.ts +27 -4
- package/dist/components/LipEffect/index.d.ts +11 -3
- package/dist/components/LipEffect/settings.d.ts +271 -0
- package/dist/components/LipEffect/types.d.ts +56 -9
- package/dist/components/LipEffect/useLipDesignTextures.d.ts +11 -0
- package/dist/components/LipEffect/uvGuide.d.ts +59 -0
- package/dist/components/PersonCutout/index.d.ts +50 -0
- package/dist/components/PersonCutout/personCutout.d.ts +16 -0
- package/dist/components/SceneObjectRenderer/index.d.ts +9 -2
- package/dist/components/ScreenImage/components/ScreenImageBody.d.ts +1 -1
- package/dist/components/ScreenImage/index.d.ts +3 -1
- package/dist/components/ScreenImage/materials/ScreenImageBlendMaterial.d.ts +1 -1
- package/dist/components/ScreenImage/materials/ScreenImageMaskMaterial.d.ts +11 -0
- package/dist/components/ScreenImage/source.d.ts +6 -0
- package/dist/components/ScreenImage/types.d.ts +18 -0
- package/dist/components/SegmentationOverlay/SegmentationOverlay.d.ts +37 -0
- package/dist/components/SegmentationOverlay/SegmentationOverlayMaterial.d.ts +31 -0
- package/dist/components/SegmentationOverlay/index.d.ts +9 -0
- package/dist/components/SegmentationOverlay/maskTexture.d.ts +23 -0
- package/dist/components/SegmentationOverlay/providers.d.ts +72 -0
- package/dist/components/SegmentationOverlay/settings.d.ts +65 -0
- package/dist/components/XRMediaSurface/index.d.ts +2 -1
- package/dist/components/XRProvider/index.d.ts +16 -2
- package/dist/components/XRViewport/hooks/useViewportExternalFaces.d.ts +1 -1
- package/dist/components/XRViewport/hooks/useViewportMediaSource.d.ts +1 -1
- package/dist/components/XRViewport/hooks/useViewportPipeline.d.ts +1 -1
- package/dist/components/index.d.ts +3 -0
- package/dist/constants/index.d.ts +4 -1
- package/dist/contexts-CO0sd4JQ.cjs +1 -0
- package/dist/{contexts-DvQI4PqI.js → contexts-FzlnnMYD.js} +252 -246
- package/dist/coordinates/feed.d.ts +52 -0
- package/dist/coordinates/index.d.ts +5 -0
- package/dist/coordinates/spaces.d.ts +49 -0
- package/dist/coordinates/viewport.d.ts +22 -0
- package/dist/coordinates/world.d.ts +15 -0
- package/dist/debug-ui/gesture-settings/index.d.ts +14 -0
- package/dist/debug-ui/holistic-tracker-settings/index.d.ts +16 -0
- package/dist/debug-ui/index.d.ts +14 -0
- package/dist/debug-ui/iris-effect-settings/index.d.ts +12 -0
- package/dist/debug-ui/lip-effect-settings/index.d.ts +15 -0
- package/dist/debug-ui/media-source.cjs +1 -1
- package/dist/debug-ui/media-source.js +1 -1
- package/dist/debug-ui/product-hotspots/index.d.ts +24 -0
- package/dist/debug-ui/segmentation-overlay-settings/index.d.ts +12 -0
- package/dist/debug-ui/xr-diagnostics/index.d.ts +25 -0
- package/dist/debug-ui.cjs +3 -3
- package/dist/debug-ui.js +2057 -1687
- package/dist/detector-K8aej4MB.js +59 -0
- package/dist/detector-ijypGtse.cjs +1 -0
- package/dist/entries/core.d.ts +21 -3
- package/dist/entries/face-effects.d.ts +12 -4
- package/dist/entries/low-level.d.ts +22 -3
- package/dist/entries/product.d.ts +11 -0
- package/dist/entries/tracking.d.ts +26 -1
- package/dist/face-effects.cjs +155 -1
- package/dist/face-effects.js +1134 -2196
- package/dist/gestures/definitions.d.ts +85 -0
- package/dist/gestures/detector.d.ts +87 -0
- package/dist/gestures/index.d.ts +8 -0
- package/dist/gestures/measures.d.ts +101 -0
- package/dist/gestures/settings.d.ts +49 -0
- package/dist/{getFaceInfo-B43Cwq4n.js → getFaceInfo-BkZfaQs-.js} +5 -5
- package/dist/{getFaceInfo-DEzcL_Ub.cjs → getFaceInfo-ahPxQFGu.cjs} +1 -1
- package/dist/hooks/selectors.d.ts +68 -1
- package/dist/hooks/useCalibrationFaceTracker.d.ts +1 -1
- package/dist/hooks/useFaceInfo.d.ts +13 -1
- package/dist/hooks/useFaceInfos.d.ts +1 -1
- package/dist/hooks/useGesture.d.ts +32 -0
- package/dist/hooks/useIrisTracking.d.ts +45 -0
- package/dist/hooks/useXRDiagnostics.d.ts +81 -0
- package/dist/hooks/useXRModel.d.ts +7 -0
- package/dist/hooks/useXRSession/facade.d.ts +44 -2
- package/dist/hooks/useXRSession/index.d.ts +2 -2
- package/dist/hooks/useXRSession/reason.d.ts +3 -1
- package/dist/hooks/useXRSession/registry.d.ts +14 -0
- package/dist/hooks/useXRSession/store.d.ts +52 -3
- package/dist/index--RgGzZ6X.cjs +47 -0
- package/dist/{index-uE3sKczj.js → index-CHVbH0hz.js} +4 -4
- package/dist/index-CeVkgsag.cjs +1 -0
- package/dist/index-CnGox6Xr.js +3054 -0
- package/dist/{index-B-s4YbWu.js → index-CsUdQ-pn.js} +113 -111
- package/dist/{index-CY7Z8cHV.cjs → index-DKBAh6hV.cjs} +1 -1
- package/dist/index-DsZwTfG0.cjs +1 -0
- package/dist/index-Dw6tj2Mi.js +144 -0
- package/dist/index-OCPjOaeP.cjs +1 -0
- package/dist/{index-G_5h_FJQ.js → index-s9pF4zkG.js} +999 -1014
- package/dist/internal-BYza1h8_.cjs +1 -0
- package/dist/{internal-ldPYiUF0.js → internal-vsfNQycj.js} +15 -16
- package/dist/internal.cjs +1 -1
- package/dist/internal.js +2 -2
- package/dist/iris/compute.d.ts +25 -0
- package/dist/iris/index.d.ts +4 -0
- package/dist/iris/lead.d.ts +19 -0
- package/dist/iris/types.d.ts +87 -0
- package/dist/landmarkMask-CbTFzBYG.js +224 -0
- package/dist/landmarkMask-u7es2k4X.cjs +1 -0
- package/dist/low-level.cjs +1 -1
- package/dist/low-level.js +1104 -639
- package/dist/main.cjs +1 -1
- package/dist/main.js +1519 -462
- package/dist/measures-9dQPdw96.js +154 -0
- package/dist/measures-KfJzpvod.cjs +1 -0
- package/dist/models/Base/index.d.ts +49 -0
- package/dist/models/Body/index.d.ts +1 -1
- package/dist/models/Face/index.d.ts +1 -1
- package/dist/models/Hand/GestureTracking.d.ts +1 -1
- package/dist/models/Hand/HandTracking.d.ts +1 -1
- package/dist/models/Holistic/index.d.ts +49 -0
- package/dist/models/Holistic/result.d.ts +68 -0
- package/dist/models/SemanticSegmentation/index.d.ts +31 -0
- package/dist/models/SemanticSegmentation/presets.d.ts +28 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/product/ProductDimensions.d.ts +16 -0
- package/dist/product/ProductHotspot.d.ts +16 -0
- package/dist/product/ProductViewer.d.ts +9 -0
- package/dist/product/details.d.ts +53 -0
- package/dist/product/index.d.ts +14 -0
- package/dist/product/loadModelViewer.d.ts +4 -0
- package/dist/product/types.d.ts +71 -0
- package/dist/product/useProductAr.d.ts +37 -0
- package/dist/product/useProductDetails.d.ts +60 -0
- package/dist/product.cjs +1 -0
- package/dist/product.js +414 -0
- package/dist/providers-C0Dlhh9Z.js +365 -0
- package/dist/providers-YNwkmNeK.cjs +62 -0
- package/dist/quality/XRQualityController.d.ts +25 -0
- package/dist/quality/controller.d.ts +33 -0
- package/dist/quality/index.d.ts +6 -0
- package/dist/quality/types.d.ts +100 -0
- package/dist/quality/useXRQuality.d.ts +28 -0
- package/dist/{renderGroups-CFt1yYLB.js → renderGroups-CN1cGFUo.js} +1 -1
- package/dist/{renderGroups-BJsx32q5.cjs → renderGroups-O4KmaMl9.cjs} +1 -1
- package/dist/renderPasses-Cg-zqmEz.cjs +21 -0
- package/dist/renderPasses-NnKZVfBy.js +1460 -0
- package/dist/scene-object.cjs +1 -1
- package/dist/scene-object.js +2 -2
- package/dist/screen2d/coordinates.d.ts +20 -0
- package/dist/screenTransform-BenfCtZt.js +44 -0
- package/dist/screenTransform-BiXdpVf0.cjs +1 -0
- package/dist/segmentation-9PhSvc-R.cjs +1 -0
- package/dist/segmentation-CeOTKemr.js +5 -0
- package/dist/segmentation2d/faceMemory.d.ts +28 -0
- package/dist/segmentation2d/faceRegions.d.ts +24 -0
- package/dist/segmentation2d/index.d.ts +9 -0
- package/dist/segmentation2d/maskUv.d.ts +23 -0
- package/dist/segmentation2d/rasterize.d.ts +14 -0
- package/dist/segmentation2d/types.d.ts +46 -0
- package/dist/selectors-C5G1PMYL.js +195 -0
- package/dist/selectors-Du_PsVnW.cjs +1 -0
- package/dist/{session-CANvoLD6.cjs → session-BBr0yN8h.cjs} +7 -7
- package/dist/{session-Dhi0OqZo.js → session-Cy0m7l7-.js} +76 -76
- package/dist/settings-DRR7-snV.cjs +1 -0
- package/dist/settings-aQk845NJ.js +73 -0
- package/dist/subjects/index.d.ts +2 -0
- package/dist/subjects/select.d.ts +62 -0
- package/dist/subjects/types.d.ts +55 -0
- package/dist/tracking/coordinates.d.ts +5 -0
- package/dist/tracking-config/apply.d.ts +57 -0
- package/dist/tracking-config/index.d.ts +7 -0
- package/dist/tracking-config/resolve.d.ts +25 -0
- package/dist/tracking-config/storage.d.ts +8 -0
- package/dist/tracking-config/types.d.ts +106 -0
- package/dist/tracking-config/useXRTrackingConfig.d.ts +38 -0
- package/dist/tracking.cjs +1 -1
- package/dist/tracking.js +1056 -380
- package/dist/types/common.d.ts +11 -1
- package/dist/types/compositor.d.ts +21 -0
- package/dist/types/gesture-tracking.d.ts +2 -2
- package/dist/types/hand-tracking.d.ts +3 -3
- package/dist/types/holistic.d.ts +121 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/model.d.ts +22 -2
- package/dist/types/nodes.d.ts +29 -4
- package/dist/types/segmentation.d.ts +19 -0
- package/dist/types/session.d.ts +48 -4
- package/dist/useFaceInfo-Blf7FCu3.js +303 -0
- package/dist/useFaceInfo-C5mO8NQY.cjs +1 -0
- package/dist/useXRCapabilities-B1R2ghy-.cjs +1 -0
- package/dist/useXRCapabilities-C68v__wB.js +30 -0
- package/dist/utils/fns.d.ts +6 -3
- package/dist/utils/integrity.d.ts +4 -1
- package/dist/utils/updateThrottle.d.ts +2 -0
- package/dist/utils/useStableUniforms.d.ts +18 -0
- package/docs/SDK_DOCUMENTATION.md +3 -0
- package/docs/api/adaptive-quality.mdx +104 -0
- package/docs/api/asset-lifecycle.mdx +78 -0
- package/docs/api/camera-permission.mdx +142 -0
- package/docs/api/capture-recording.mdx +9 -1
- package/docs/api/coordinate-spaces.mdx +165 -0
- package/docs/api/cross-camera.mdx +88 -0
- package/docs/api/diagnostics-calibration.mdx +97 -0
- package/docs/api/face-effects.mdx +6 -0
- package/docs/api/face-mesh.mdx +35 -0
- package/docs/api/gestures.mdx +173 -0
- package/docs/api/holistic-tracker.mdx +159 -0
- package/docs/api/iris-tracking.mdx +242 -0
- package/docs/api/multi-subject.mdx +95 -0
- package/docs/api/person-cutout.mdx +136 -0
- package/docs/api/product-ar.mdx +111 -0
- package/docs/api/product-details.mdx +78 -0
- package/docs/api/runtime-3d.mdx +5 -0
- package/docs/api/screen-image.mdx +3 -1
- package/docs/api/screen-space-coordinates.mdx +35 -1
- package/docs/api/segmentation-overlay.mdx +301 -0
- package/docs/api/session-lifecycle.mdx +136 -0
- package/docs/api/trackers.mdx +18 -0
- package/docs/api/tracking-config.mdx +108 -0
- package/docs/docs.json +23 -1
- package/docs/examples/lip-shaders.mdx +224 -0
- package/docs/guides/lip-effect-textures.mdx +292 -0
- package/docs/guides/lip-uv-template.png +0 -0
- package/docs/guides/lip-uv-template.svg +1 -0
- package/package.json +16 -1
- package/dist/FaceMeshRenderer-D1WsrTLA.cjs +0 -155
- package/dist/FaceMeshRenderer-DSCd1H55.js +0 -1264
- package/dist/FaceRetouch-CMJbGThL.js +0 -551
- package/dist/FaceRetouch-DsE0Dbw-.cjs +0 -134
- package/dist/contexts-h-9TRH0r.cjs +0 -1
- package/dist/index-C0-fg1Hk.cjs +0 -1
- package/dist/index-CxMEeCRP.cjs +0 -1
- package/dist/index-D67hHm44.cjs +0 -1
- package/dist/index-DPPpU5By.cjs +0 -17
- package/dist/index-DYfY9ljy.js +0 -111
- package/dist/index-I4eF5TKT.js +0 -1457
- package/dist/internal-MvKrz0Nt.cjs +0 -1
- package/dist/renderLayers-lDtL0_43.cjs +0 -1
- package/dist/renderLayers-zrK2pv5O.js +0 -13
- package/dist/renderPasses-C72MbZ-a.js +0 -1500
- package/dist/renderPasses-D-Krl6D0.cjs +0 -21
- package/dist/selectors-ByKNTXeA.js +0 -59
- package/dist/selectors-CAwmL9VW.cjs +0 -1
- package/dist/useFaceInfo-BSZq7AO1.cjs +0 -1
- package/dist/useFaceInfo-FVQBBNdY.js +0 -62
- package/dist/useFaceResults-C3oZkn2Z.cjs +0 -1
- package/dist/useFaceResults-D62STzT1.js +0 -19
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Gestures"
|
|
3
|
+
description: "Reusable face, hand and body gesture detection with thresholds, hysteresis, confidence, cooldown and lifecycle events."
|
|
4
|
+
sidebarTitle: "Gestures"
|
|
5
|
+
icon: "hand-sparkles"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
`useGesture` turns raw tracking into stable gesture events: pinch, grab,
|
|
9
|
+
open palm, wave, hand raise, nod, head shake, mouth open, smile, blink,
|
|
10
|
+
brow raise, arm raise and T-pose, plus your own. One engine handles the
|
|
11
|
+
thresholds, hysteresis, hold, cooldown, confidence and tracking loss, so
|
|
12
|
+
scenes stop re-implementing them.
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { GestureListener, useGesture } from "@vincentt-xr/sdk/tracking";
|
|
16
|
+
|
|
17
|
+
// Hook: React state that changes only when the gesture's state changes.
|
|
18
|
+
const pinch = useGesture("pinch", { hand: "right", onBegin: () => grab(), onEnd: () => drop() });
|
|
19
|
+
if (pinch.active) {
|
|
20
|
+
/* … */
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Declarative: mount and act on events.
|
|
24
|
+
<GestureListener gesture="headNod" onBegin={() => confirm()} />;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Each gesture acquires the shared face, hand or body model it needs; no
|
|
28
|
+
second detector starts. Mount gesture hooks once above scene switches, like
|
|
29
|
+
any tracker.
|
|
30
|
+
|
|
31
|
+
## Built-in gestures
|
|
32
|
+
|
|
33
|
+
| Name | Kind | Measures | Default enter / exit / hold / cooldown |
|
|
34
|
+
| ---------------------------------- | ---- | ------------------------------------------------------------ | -------------------------------------- |
|
|
35
|
+
| `mouthOpen` | face | `jawOpen` blendshape | 0.5 / 0.35 / 0 / 0 |
|
|
36
|
+
| `smile` | face | mean of `mouthSmileLeft` / `mouthSmileRight` | 0.4 / 0.25 / 0 / 0 |
|
|
37
|
+
| `blink` | face | both eyes closed (min of the `eyeBlink` blendshapes) | 0.55 / 0.4 / 50 ms / 0 |
|
|
38
|
+
| `browRaise` | face | max of the brow-up blendshapes | 0.45 / 0.3 / 0 / 0 |
|
|
39
|
+
| `headNod` | face | pitch oscillation: 3 turning points ≥ 0.12 rad within 1.5 s | 0.99 / 0.3 / 0 / 600 ms |
|
|
40
|
+
| `headShake` | face | yaw oscillation: 3 turning points ≥ 0.15 rad within 1.5 s | 0.99 / 0.3 / 0 / 600 ms |
|
|
41
|
+
| `pinch` | hand | thumb–index tip gap relative to palm size (1 = touching) | 0.75 / 0.5 / 0 / 0 |
|
|
42
|
+
| `grab` | hand | mean curl of index..pinky (1 = fist) | 0.7 / 0.45 / 0 / 0 |
|
|
43
|
+
| `openPalm` | hand | mean extension of index..pinky | 0.7 / 0.45 / 0 / 0 |
|
|
44
|
+
| `handRaised` | hand | wrist height in the frame (0 bottom, 1 top) | 0.7 / 0.6 / 0 / 0 |
|
|
45
|
+
| `oneFinger` | hand | index extended, middle..pinky curled (pointing) | 0.65 / 0.45 / 0 / 0 |
|
|
46
|
+
| `peaceSign` | hand | index + middle extended, ring + pinky curled | 0.65 / 0.45 / 0 / 0 |
|
|
47
|
+
| `threeFingers` | hand | index + middle + ring extended, pinky curled | 0.65 / 0.45 / 0 / 0 |
|
|
48
|
+
| `fourFingers` | hand | index..pinky extended, thumb tucked | 0.65 / 0.45 / 0 / 0 |
|
|
49
|
+
| `fist` | hand | all four curled and the thumb tucked | 0.65 / 0.45 / 0 / 0 |
|
|
50
|
+
| `palm` | hand | all four extended and the thumb out | 0.65 / 0.45 / 0 / 0 |
|
|
51
|
+
| `thumbUp` / `thumbDown` | hand | thumb out and pointing up / down, fingers curled | 0.6 / 0.4 / 0 / 0 |
|
|
52
|
+
| `wave` | hand | wrist x oscillation: 3 turning points ≥ 0.08 within 1.2 s | 0.99 / 0.3 / 0 / 600 ms |
|
|
53
|
+
| `armRaisedLeft` / `armRaisedRight` | body | wrist above its shoulder, in shoulder widths (person's side) | 0.3 / 0.15 / 0 / 0 |
|
|
54
|
+
| `armRaised` / `bothArmsRaised` | body | either / both wrists above their shoulders | 0.3 / 0.15 / 0 / 0 |
|
|
55
|
+
| `tPose` | body | both arms level and outstretched by a shoulder width | 0.6 / 0.4 / 150 ms / 0 |
|
|
56
|
+
|
|
57
|
+
Hand measures are scale-free (divided by the palm size) and body measures
|
|
58
|
+
are in shoulder widths, so distance from the camera does not change the
|
|
59
|
+
thresholds. `GESTURES[name].description` carries each line of the table.
|
|
60
|
+
|
|
61
|
+
The finger-count signs score the _weakest_ required finger, so a pattern is
|
|
62
|
+
1 only when every finger agrees: `peaceSign` needs index and middle extended
|
|
63
|
+
_and_ ring and pinky curled. `grab` and `openPalm` are the softer, mean-curl
|
|
64
|
+
versions of `fist` and `palm` that ignore the thumb; `measureFingerPattern`
|
|
65
|
+
builds any other pattern (`{ fingers: [true, null, null, false], thumb: true }`
|
|
66
|
+
uses `null` for "either").
|
|
67
|
+
|
|
68
|
+
## The engine contract
|
|
69
|
+
|
|
70
|
+
Every frame the definition measures a `{ value, confidence }` sample (or
|
|
71
|
+
`null` when the tracker has no subject). The detector then applies, in order:
|
|
72
|
+
|
|
73
|
+
1. **Confidence gate**: a sample below `minConfidence` (default 0.5), or no
|
|
74
|
+
sample, is tracking loss.
|
|
75
|
+
2. **Loss grace**: an active gesture survives loss for `lostGraceMs`
|
|
76
|
+
(default 250, the SDK's hold window) with its value frozen, then ends
|
|
77
|
+
with `endReason: "lost"`. An inactive gesture just resets its hold timer.
|
|
78
|
+
3. **Hysteresis**: inactive → `begin` when `value >= enterThreshold`;
|
|
79
|
+
active → `end` (`endReason: "released"`) when `value <= exitThreshold`.
|
|
80
|
+
The band between them is where the gesture is stable; the default exit is
|
|
81
|
+
0.15 below enter.
|
|
82
|
+
4. **Hold**: the value must stay at or above enter for `holdMs` before
|
|
83
|
+
`begin`; a dip resets the clock.
|
|
84
|
+
5. **Cooldown**: after `end`, no `begin` for `cooldownMs`.
|
|
85
|
+
|
|
86
|
+
A `begin` is always followed by at least one `stay` frame, so a one-frame
|
|
87
|
+
gesture still surfaces both edges to an effect that watches `justBegan` and
|
|
88
|
+
`justEnded`.
|
|
89
|
+
|
|
90
|
+
`GestureState`: `active`, `value`, `confidence`, `tracked`, `phase`
|
|
91
|
+
(`begin` | `stay` | `end` | `none`), `justBegan`, `justEnded`, `endReason`
|
|
92
|
+
(`released` | `lost` | null), `startedAtMs`, `durationMs`, `count`. The hook
|
|
93
|
+
publishes a new state only when one of these changes, and `durationMs` is
|
|
94
|
+
excluded from that comparison so an active gesture does not re-render every
|
|
95
|
+
frame: it reads as of the last published change (an ending gesture carries
|
|
96
|
+
its full length). Derive a live duration from `startedAtMs`.
|
|
97
|
+
|
|
98
|
+
Confidence is presence-based for the face, hand and body trackers, which
|
|
99
|
+
report no per-landmark score: 1 while the subject is tracked. A custom
|
|
100
|
+
definition can supply a real score (the holistic result's `confidence`, for
|
|
101
|
+
example).
|
|
102
|
+
|
|
103
|
+
## Options
|
|
104
|
+
|
|
105
|
+
`useGesture(gesture, options)` takes the detector options above plus
|
|
106
|
+
`active`, `faceIndex` / `numFaces` (face), `hand` / `handIndex` (hand),
|
|
107
|
+
`bodyIndex` (body), `targetFps`, and `onBegin` / `onEnd` / `onChange`
|
|
108
|
+
callbacks, which fire from the frame loop with that frame's state.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
const nod = useGesture("headNod", { faceIndex: 1, numFaces: 2, cooldownMs: 1000 });
|
|
112
|
+
const wave = useGesture("wave", { hand: "left", lostGraceMs: 400 });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Per-gesture defaults are the ones in the table; `resolveGestureDetectorOptions(overrides, defaults)`
|
|
116
|
+
is what the hook applies.
|
|
117
|
+
|
|
118
|
+
## Custom gestures
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { defineGesture, HAND_JOINTS } from "@vincentt-xr/sdk/tracking";
|
|
122
|
+
|
|
123
|
+
// A "thumbs up": thumb tip well above the wrist while the other fingers curl.
|
|
124
|
+
const thumbsUp = defineGesture({
|
|
125
|
+
name: "thumbsUp",
|
|
126
|
+
kind: "hand",
|
|
127
|
+
description: "Thumb up, fingers curled.",
|
|
128
|
+
defaults: { enterThreshold: 0.7, exitThreshold: 0.5 },
|
|
129
|
+
measure: (hand) => {
|
|
130
|
+
if (!hand || hand.length < 21) return null;
|
|
131
|
+
const lift = hand[HAND_JOINTS.thumbTip].y - hand[HAND_JOINTS.wrist].y;
|
|
132
|
+
return { value: Math.min(1, Math.max(0, lift / 0.3)), confidence: 1 };
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const up = useGesture(thumbsUp);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Temporal gestures add `createMemory` and read it in `measure`; the built-in
|
|
140
|
+
`OscillationTracker` (on `/low-level`) counts direction reversals for nods,
|
|
141
|
+
shakes and waves.
|
|
142
|
+
|
|
143
|
+
## Relation to the other gesture APIs
|
|
144
|
+
|
|
145
|
+
- `<GestureTrigger>` fires once on a **named static hand pose** from the
|
|
146
|
+
gesture-recognizer model (`victory`, `thumb_up`, …). Use it for "show ✌️ to
|
|
147
|
+
start". `useGesture` measures **continuous and temporal** gestures from the
|
|
148
|
+
landmark trackers with thresholds you can tune.
|
|
149
|
+
- `useFaceAction` is the face-only predecessor with the same hysteresis idea;
|
|
150
|
+
the face gestures here reuse its measurements, and add cooldown,
|
|
151
|
+
confidence, loss grace and the temporal nod / shake.
|
|
152
|
+
|
|
153
|
+
## Tracking loss and confidence
|
|
154
|
+
|
|
155
|
+
Losing the subject never leaves a gesture stuck: within `lostGraceMs` the
|
|
156
|
+
state holds (value frozen, `tracked: false`), after it the gesture ends with
|
|
157
|
+
`endReason: "lost"` so an effect can clean up differently from a deliberate
|
|
158
|
+
release. A subject that reappears starts cold: hold timers restart and
|
|
159
|
+
temporal memories reset, so a nod half-done before the dropout does not
|
|
160
|
+
complete on return.
|
|
161
|
+
|
|
162
|
+
## Manual validation
|
|
163
|
+
|
|
164
|
+
Run `/gestures`:
|
|
165
|
+
|
|
166
|
+
| Case | Steps | Expect |
|
|
167
|
+
| ------------- | --------------------------------------- | --------------------------------------------------------------- |
|
|
168
|
+
| Hysteresis | hold a half pinch around the threshold | Row stays active until the value drops below the exit threshold |
|
|
169
|
+
| Hold | set hold 300 ms on `pinch`, tap quickly | No begin for taps shorter than 300 ms |
|
|
170
|
+
| Cooldown | `headNod`, nod twice quickly | One begin; the second counts after 600 ms |
|
|
171
|
+
| Tracking loss | cover the hand mid-pinch | Row shows "lost" then ends with `lost` after the grace window |
|
|
172
|
+
| Confidence | set min confidence above 1 | Gesture never begins |
|
|
173
|
+
| Body | "Person Fullbody" clip, raise an arm | `armRaised` / side rows activate |
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Holistic Tracker"
|
|
3
|
+
description: "One MediaPipe inference for face, hands and pose, with unified results, dropout bridging and the same TrackingAnchor targets as the individual trackers."
|
|
4
|
+
sidebarTitle: "Holistic Tracker"
|
|
5
|
+
icon: "person-rays"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
`<HolisticTracker>` runs MediaPipe's Holistic Landmarker: a single graph that
|
|
9
|
+
returns face landmarks and blendshapes, left and right hand landmarks, and the
|
|
10
|
+
33-point pose for **one person**. It is additive and opt-in; `FaceTracker`,
|
|
11
|
+
`HandTracker` and `BodyTracker` are unchanged.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { HolisticTracker, TrackingAnchor } from "@vincentt-xr/sdk/tracking";
|
|
15
|
+
|
|
16
|
+
<HolisticTracker holdMs={250}>
|
|
17
|
+
<HolisticTracker.Face>
|
|
18
|
+
<TrackingAnchor target="face.noseTip">{/* … */}</TrackingAnchor>
|
|
19
|
+
</HolisticTracker.Face>
|
|
20
|
+
<HolisticTracker.Hand hand="right">
|
|
21
|
+
<TrackingAnchor target="hand.indexTip">{/* … */}</TrackingAnchor>
|
|
22
|
+
</HolisticTracker.Hand>
|
|
23
|
+
<HolisticTracker.Body>
|
|
24
|
+
<TrackingAnchor target="body.leftShoulder">{/* … */}</TrackingAnchor>
|
|
25
|
+
</HolisticTracker.Body>
|
|
26
|
+
</HolisticTracker>;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Composition
|
|
30
|
+
|
|
31
|
+
- `<HolisticTracker>` acquires `XRModel.HOLISTIC_TRACKER` through the shared,
|
|
32
|
+
refcounted registry. Every descendant and every other `<HolisticTracker>`
|
|
33
|
+
in the session shares that one model and one inference per frame.
|
|
34
|
+
- `<HolisticTracker.Face>`, `<HolisticTracker.Hand hand="left" | "right">` and
|
|
35
|
+
`<HolisticTracker.Body>` are the stream groups. Each provides the same
|
|
36
|
+
tracker context as `FaceTracker` / `HandTracker` / `BodyTracker`, so
|
|
37
|
+
`TrackingAnchor` targets (`face.*`, `hand.*`, `body.*`, including
|
|
38
|
+
`body.neck` and `face.custom`) and `smoothing` work unchanged. The group is
|
|
39
|
+
hidden while its stream is absent, and its origin is the nose tip, the
|
|
40
|
+
wrist, or the head.
|
|
41
|
+
- `useHolisticTracking()` returns `{ node, ready, status, error }`. Read
|
|
42
|
+
`node.getResult()` inside `useFrame`; the result object is stable and
|
|
43
|
+
mutated in place.
|
|
44
|
+
|
|
45
|
+
## The unified result
|
|
46
|
+
|
|
47
|
+
`HolisticResult` carries `version`, `timestampMs` and four streams that are
|
|
48
|
+
`null` when neither detected nor held:
|
|
49
|
+
|
|
50
|
+
| Stream | Coordinates | Extras |
|
|
51
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
52
|
+
| `face` | 478 `NormalizedLandmark`s in MediaPipe image space (0..1, top-left, Y down) — the same space as `FaceTracker`'s `detectionResults` | `blendshapes` name → score, `confidence`, `held` |
|
|
53
|
+
| `leftHand`, `rightHand` | 21 `TrackerPoint`s: center origin, X right, Y up, Z toward the viewer — the same as `HandTracker`'s `coordinates` | `handedness`, `worldLandmarks` (metres, MediaPipe world space), `confidence`, `held` |
|
|
54
|
+
| `pose` | 33 `TrackerPoint`s — the same as `BodyTracker`'s `getLandmarks2D()` | `visibility[]`, `worldLandmarks`, `confidence`, `held` |
|
|
55
|
+
|
|
56
|
+
Mirroring of coordinates follows the individual trackers: no source-dependent
|
|
57
|
+
flip is applied to any stream. Hand **labels** are the person's anatomical
|
|
58
|
+
side on every source: holistic assigns hands from the pose, whose left/right
|
|
59
|
+
follow the image, so the SDK swaps them on the mirrored webcam feed.
|
|
60
|
+
`leftHand` is the person's left hand (on the viewer's left in the selfie view),
|
|
61
|
+
the same hand `<HandTracker hand="left">` selects, and `hands="left"` keeps it.
|
|
62
|
+
The pose's left/right landmark indices are swapped the same way, so `pose`
|
|
63
|
+
wrist 15 and `leftHand` are always the same physical hand. This differs from
|
|
64
|
+
`BodyTracker`, whose indices follow the image.
|
|
65
|
+
|
|
66
|
+
**Pre-release note:** the default model is MediaPipe's published
|
|
67
|
+
`holistic_landmarker.task` on `storage.googleapis.com` (integrity pinned)
|
|
68
|
+
until the SDK CDN copy is published. Pass `modelUrl` to self-host it. The face has no transformation matrix in the holistic graph, so there is
|
|
69
|
+
no head rotation; use `<FaceTracker>` when children must roll with the head.
|
|
70
|
+
|
|
71
|
+
### Confidence contract
|
|
72
|
+
|
|
73
|
+
- Face and hands: `1` while detected (the holistic graph does not report
|
|
74
|
+
per-landmark visibility for them).
|
|
75
|
+
- Pose: the mean landmark `visibility`.
|
|
76
|
+
- While a stream is **held** after a dropout, `held` is `true` and
|
|
77
|
+
`confidence` decays linearly from the last live value to `0` at `holdMs`.
|
|
78
|
+
|
|
79
|
+
### Dropouts
|
|
80
|
+
|
|
81
|
+
Each stream runs through `FaceTrackMemory`, the SDK's one hold/predict bridge:
|
|
82
|
+
a missing stream is replayed for `holdMs` (default 250), extrapolated along its
|
|
83
|
+
last velocity for the first `predictMs` (default 0), then dropped. Set
|
|
84
|
+
`holdMs={0}` for immediate disappearance. Do not add app-side holds.
|
|
85
|
+
|
|
86
|
+
## Options
|
|
87
|
+
|
|
88
|
+
| Prop | Applies | Notes |
|
|
89
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------- |
|
|
90
|
+
| `modelUrl` | model creation | Defaults to MediaPipe's published `holistic_landmarker.task` (13.7 MB), SHA-384 pinned |
|
|
91
|
+
| `minFaceDetectionConfidence`, `minFacePresenceConfidence`, `minPoseDetectionConfidence`, `minPosePresenceConfidence`, `minHandLandmarksConfidence` | model creation | MediaPipe thresholds, default 0.5 |
|
|
92
|
+
| `outputFaceBlendshapes` | model creation | Default `true` |
|
|
93
|
+
| `delegate` | model creation | `"auto"` (default), `"GPU"`, `"CPU"`; see below |
|
|
94
|
+
| `targetFps` | run time | Overrides `perf.targetFps` for this model |
|
|
95
|
+
| `hands` | live | `"both"` (default), `"left"`, `"right"`, `"none"`; holistic tracks at most one of each |
|
|
96
|
+
| `analysisMaxDimension` | live | Longest side of the analysed frame, default 768 |
|
|
97
|
+
| `holdMs`, `predictMs` | live | Dropout bridge |
|
|
98
|
+
| `active` | live | Pause inference without unmounting |
|
|
99
|
+
| `onStatusChange(status, error)` | — | `loading` → `ready` / `error`; a transient inference error recovers on the next frame |
|
|
100
|
+
|
|
101
|
+
Model-creation options come from the first mounted consumer (like `numFaces`
|
|
102
|
+
on `FaceTracker`); changing them re-acquires the model. Live options apply
|
|
103
|
+
through `node.setOptions` and the last writer wins.
|
|
104
|
+
|
|
105
|
+
## Delegate: GPU with an automatic CPU fallback
|
|
106
|
+
|
|
107
|
+
MediaPipe's holistic face-blendshape stage has no WebGL implementation for
|
|
108
|
+
one of its ops, so with `outputFaceBlendshapes` the GPU graph fails on its
|
|
109
|
+
first frame (`UNIMPLEMENTED … No support of const`). With the default
|
|
110
|
+
`delegate="auto"` the tracker rebuilds the graph on the CPU once: one
|
|
111
|
+
`console.warn`, status dips to `loading` during the rebuild, no refetch, and
|
|
112
|
+
`node.getDelegate()` reports `"CPU"` afterwards. CPU inference is several
|
|
113
|
+
times slower (tens to hundreds of milliseconds per frame on a laptop). When
|
|
114
|
+
blendshapes are not needed, `outputFaceBlendshapes={false}` keeps the whole
|
|
115
|
+
graph on the GPU. `delegate="GPU"` reports the failure as `error` instead of
|
|
116
|
+
retrying; `delegate="CPU"` starts on the CPU.
|
|
117
|
+
|
|
118
|
+
## Coexistence with the individual trackers
|
|
119
|
+
|
|
120
|
+
- Holistic is a **separate model**. Mounting `<FaceTracker>` next to
|
|
121
|
+
`<HolisticTracker>` runs two inferences; pick one per scene.
|
|
122
|
+
- Inside a holistic scene, use the sub-trackers instead of `FaceTracker` /
|
|
123
|
+
`HandTracker` / `BodyTracker`. `GestureTracker` (named gestures) and
|
|
124
|
+
`Segmentation` are unrelated models and combine freely.
|
|
125
|
+
- `FaceMesh`, face readers (`useFaceInfo`, `useFaceResults`), face effects and
|
|
126
|
+
head binding read `XRModel.FACE_TRACKER`; they do not consume holistic
|
|
127
|
+
results. For those, keep `<FaceTracker>`.
|
|
128
|
+
|
|
129
|
+
## Combined signals
|
|
130
|
+
|
|
131
|
+
`getHolisticSignals(result)` derives presence, `smile` / `mouthOpen`
|
|
132
|
+
blendshape scores, raised hands (pose wrist above its shoulder), open palms
|
|
133
|
+
(three of four fingers extended) and `greeting`: a raised open hand while
|
|
134
|
+
smiling. `isHandRaised` and `isPalmOpen` are exported on their own.
|
|
135
|
+
|
|
136
|
+
## Migration and performance
|
|
137
|
+
|
|
138
|
+
- Replace `<FaceTracker>` + `<HandTracker>` + `<BodyTracker>` with one
|
|
139
|
+
`<HolisticTracker>` and three sub-trackers when a scene needs all three; the
|
|
140
|
+
anchors move over unchanged. Keep the individual trackers when only one
|
|
141
|
+
stream is needed: the holistic model is heavier than any single one.
|
|
142
|
+
- Default inference cap follows `perf.targetFps` (the template ships 24).
|
|
143
|
+
Lower `analysisMaxDimension` (512) when only the face and hands matter and
|
|
144
|
+
the subject is close.
|
|
145
|
+
- First load downloads 13.7 MB plus the shared MediaPipe engine.
|
|
146
|
+
|
|
147
|
+
## Manual validation
|
|
148
|
+
|
|
149
|
+
Run `/holistic-tracking` in the playground:
|
|
150
|
+
|
|
151
|
+
| Case | Source | Expect |
|
|
152
|
+
| ----------------- | ------------------------------------ | ----------------------------------------------------------------------------------------- |
|
|
153
|
+
| All streams | "Person Fullbody" | Face, both hands and pose dots follow; HUD shows every stream present |
|
|
154
|
+
| Hands option | panel `hands: left` | Right hand dots disappear immediately; left keeps tracking |
|
|
155
|
+
| Hold | panel `holdMs: 0` vs 250 | Dots vanish instantly on a dropout vs. hold for a quarter second with decaying confidence |
|
|
156
|
+
| Combined gesture | webcam, raise an open hand and smile | The raised hand turns yellow and the HUD reports the greeting |
|
|
157
|
+
| Status | reload | `loading` → `ready`; a bad `modelUrl` reports `error` with the message |
|
|
158
|
+
| Delegate fallback | blendshapes on, then off | HUD shows `delegate CPU` after one warning; with blendshapes off it stays on `GPU` |
|
|
159
|
+
| Coexistence | mount `<FaceTracker>` too | Two models in the debugger; no crash, no shared state |
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Iris Tracking"
|
|
3
|
+
description: "Stable left/right iris data from the shared face tracker, and an iris effect that draws a shader or image material over each iris."
|
|
4
|
+
sidebarTitle: "Iris Tracking"
|
|
5
|
+
icon: "eye"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Iris tracking reads the ten refined iris landmarks the face tracker already
|
|
9
|
+
produces. It starts no second detector: `useIrisTracking` acquires the same
|
|
10
|
+
shared `XRModel.FACE_TRACKER` as `FaceTracker` and `useFaceInfo`, and
|
|
11
|
+
`<IrisEffect>` renders over it.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { useIrisTracking } from "@vincentt-xr/sdk/tracking";
|
|
15
|
+
import { IrisEffect } from "@vincentt-xr/sdk/face-effects";
|
|
16
|
+
|
|
17
|
+
// Rendering: built-in procedural iris, or an image, or your own material.
|
|
18
|
+
<IrisEffect mode="shader" color="#4fa3ff" opacity={0.85} smoothing={15} />;
|
|
19
|
+
<IrisEffect mode="image" textureUrl="/iris.png" scale={1.1} />;
|
|
20
|
+
|
|
21
|
+
// Data: read inside useFrame.
|
|
22
|
+
const { result, status } = useIrisTracking({ faceIndex: 0, smoothing: 15 });
|
|
23
|
+
useFrame(() => {
|
|
24
|
+
if (!result.detected) return;
|
|
25
|
+
const { center, radius, offset, confidence } = result.left;
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Landmarks and sides
|
|
30
|
+
|
|
31
|
+
| Side (person's) | Center | Ring (outer, top, inner, bottom) | Inner / outer corner | Upper / lower lid |
|
|
32
|
+
| --------------- | ------ | -------------------------------- | -------------------- | ----------------- |
|
|
33
|
+
| `left` | 473 | 474, 475, 476, 477 | 362 / 263 | 386 / 374 |
|
|
34
|
+
| `right` | 468 | 469, 470, 471, 472 | 133 / 33 | 159 / 145 |
|
|
35
|
+
|
|
36
|
+
Sides follow the face-mesh topology, so they are the person's anatomical
|
|
37
|
+
sides on every source: mirrored webcam, straight video or image. The data
|
|
38
|
+
needs the refined mesh (478 points); with fewer landmarks each eye reports
|
|
39
|
+
`detected: false`. These indices are exported as `IRIS_LANDMARKS`. The
|
|
40
|
+
allocation-free helpers behind the hook (`createIrisResult`,
|
|
41
|
+
`updateIrisResult`, `computeIrisEye`, `IrisLead`) live on
|
|
42
|
+
`@vincentt-xr/sdk/low-level` for pipelines that already hold their own
|
|
43
|
+
landmarks.
|
|
44
|
+
|
|
45
|
+
## Coordinate spaces
|
|
46
|
+
|
|
47
|
+
- `center`, `ring`, `eyeCenter`: MediaPipe image space, 0..1 with a top-left
|
|
48
|
+
origin and Y down, the same space as `FaceInfo.landmarks` and
|
|
49
|
+
`detectionResults`. `z` is tracker-relative depth.
|
|
50
|
+
- `trackerCenter`: SDK tracker space (center origin, X right, Y up, Z toward
|
|
51
|
+
the viewer), the same as hand and body points.
|
|
52
|
+
- `radius`, `eyeWidth`: image-width units (half the horizontal ring diameter
|
|
53
|
+
and the corner-to-corner distance). To draw in world space, unproject the
|
|
54
|
+
center and a point `radius` to its right and take their distance, which is
|
|
55
|
+
what `<IrisEffect>` does.
|
|
56
|
+
- `offset`: the iris center relative to the eye center, each axis divided by
|
|
57
|
+
half the eye width and clamped to [-1, 1]; +x toward the person's left, +y
|
|
58
|
+
up, on every source (the horizontal sign is corrected on the mirrored
|
|
59
|
+
webcam feed, so it never flips with the camera). **This is the iris position inside the eyelid opening, not a gaze
|
|
60
|
+
direction**: it ignores head pose and eyeball geometry. Use it for
|
|
61
|
+
pupil-follow and shader inputs, not for "where is the person looking".
|
|
62
|
+
- `openness`: eyelid gap divided by eye width (about 0.25–0.35 open, 0 closed).
|
|
63
|
+
|
|
64
|
+
Mirroring: the session mirrors the webcam feed and no other source, and the
|
|
65
|
+
landmarks are already in that feed space, so positions need no flip. The
|
|
66
|
+
`mirrored` prop on `<IrisEffect>` flips the material horizontally (image
|
|
67
|
+
orientation, shader +x), nothing else.
|
|
68
|
+
|
|
69
|
+
## Confidence and lifecycle
|
|
70
|
+
|
|
71
|
+
MediaPipe reports no score for iris points and emits them even when the eye
|
|
72
|
+
is closed, so confidence is derived: `eye.confidence` is `openness / 0.2`
|
|
73
|
+
clamped to 0..1, scaled by the hold decay. `result.confidence` is 1 while the
|
|
74
|
+
face is live and decays linearly to 0 over `holdMs` while `held`. Gate
|
|
75
|
+
effects on these; the built-in materials fade with `irisConfidence`.
|
|
76
|
+
|
|
77
|
+
`useIrisTracking` returns `status` (`loading` → `ready` / `error`) for the
|
|
78
|
+
shared face model plus `result.detected` / `held` / `version` /
|
|
79
|
+
`timestampMs` / `faceIndex`. The `result` object is stable and mutated in
|
|
80
|
+
place; read it inside `useFrame`.
|
|
81
|
+
|
|
82
|
+
## Smoothing, hold and face selection
|
|
83
|
+
|
|
84
|
+
- Dropouts go through `FaceTrackMemory`, the SDK's one bridge: `holdMs` 250
|
|
85
|
+
and `predictMs` 0 by default, like every other face surface. A face that
|
|
86
|
+
stays missing longer than `holdMs` is dropped and `<IrisEffect>` hides at
|
|
87
|
+
once, so no stale eye effect lingers.
|
|
88
|
+
- `smoothing` runs the same One Euro filter as `TrackingAnchor` on the
|
|
89
|
+
centers and radius (default 0 = raw; ~10 tames jitter at rest). It is a
|
|
90
|
+
low-pass whose floor cutoff is `smoothing × 0.1` Hz, so 15 attenuates and
|
|
91
|
+
lags a 2–3 Hz head shake: keep it low when exact following matters. It
|
|
92
|
+
resets on re-acquire, so the iris never slides in from an old position.
|
|
93
|
+
- `targetFps` requests an inference cap for the shared face model (0 keeps
|
|
94
|
+
the session's `perf.targetFps`, 24 in the template). The first mounted face
|
|
95
|
+
consumer's value wins, so pass the same number everywhere the face model
|
|
96
|
+
is acquired, and note that a later change only takes effect once every
|
|
97
|
+
holder has released the model (a lone `<IrisEffect>` re-acquires and
|
|
98
|
+
re-initializes it; a session pipeline entry pins the first value for the
|
|
99
|
+
session). The example makes the effect the only holder so its panel value
|
|
100
|
+
is the real rate. **60 gives the best following, but use it with caution**: it runs
|
|
101
|
+
face inference 2.5× as often as the template default and will heat and
|
|
102
|
+
throttle phones. Ship it only when the eye effect is the point of the
|
|
103
|
+
experience and you have checked the target devices; otherwise keep the
|
|
104
|
+
session default (or 30) and let `leadMs` bridge the gap.
|
|
105
|
+
- `leadMs` (default 80, 0 disables) bridges throttled inference to the render
|
|
106
|
+
rate: between samples the centers advance along their last
|
|
107
|
+
sample-to-sample velocity for at most `leadMs`, so a 24 fps face model
|
|
108
|
+
reads continuous at 60+ fps instead of hold-and-jump. When the lag itself
|
|
109
|
+
is the problem, raise the face model's `targetFps` first (the example
|
|
110
|
+
requests 60).
|
|
111
|
+
- `faceIndex` / `numFaces` select the face exactly like `useFaceInfo`: the
|
|
112
|
+
index is the result-array position, not a persistent identity, and the
|
|
113
|
+
first mounted face consumer decides `numFaces`.
|
|
114
|
+
|
|
115
|
+
## `<IrisEffect>` props and materials
|
|
116
|
+
|
|
117
|
+
`<IrisEffect>` takes the flat `IrisEffectSettings` (`eye`: both | left |
|
|
118
|
+
right, `mode`: shader | image, `color`, `textureUrl`, `tintImage` (multiply the
|
|
119
|
+
image by `color`; off by default so images show as authored), `scale`, `opacity`,
|
|
120
|
+
`targetFps`, `smoothing`, `leadMs`, `mirrored`, `faceIndex`, `holdMs`) plus `numFaces`, `active`,
|
|
121
|
+
`predictMs`, `texture` (a pre-loaded three texture, beats `textureUrl`),
|
|
122
|
+
`material`, `renderOrder` and `onTracking(handle)`.
|
|
123
|
+
|
|
124
|
+
Each eye is a quad spanning the iris diameter, positioned on the camera's
|
|
125
|
+
z=0 plane. Every material receives the same uniforms (`IrisUniforms`):
|
|
126
|
+
|
|
127
|
+
| Uniform | Type | Meaning |
|
|
128
|
+
| ---------------- | ----------- | ---------------------------------- |
|
|
129
|
+
| `irisCenter` | `vec2` | Image-space center |
|
|
130
|
+
| `irisRadius` | `float` | Image-width radius |
|
|
131
|
+
| `irisOffset` | `vec2` | Position inside the eye, [-1, 1] |
|
|
132
|
+
| `irisConfidence` | `float` | 0..1, fade with it |
|
|
133
|
+
| `irisOpenness` | `float` | Eyelid gap / eye width |
|
|
134
|
+
| `time` | `float` | Seconds since mount |
|
|
135
|
+
| `color` | `vec3` | Tint |
|
|
136
|
+
| `opacity` | `float` | |
|
|
137
|
+
| `mirror` | `float` | 1 when mirrored |
|
|
138
|
+
| `tintImage` | `float` | 1 to multiply the image by `color` |
|
|
139
|
+
| `map` | `sampler2D` | The image in `image` mode |
|
|
140
|
+
|
|
141
|
+
A minimal custom material:
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { IrisEffect, type IrisMaterialContext } from "@vincentt-xr/sdk/face-effects";
|
|
145
|
+
|
|
146
|
+
const ring = ({ uniforms }: IrisMaterialContext) =>
|
|
147
|
+
new THREE.ShaderMaterial({
|
|
148
|
+
uniforms, // the shared, live uniforms
|
|
149
|
+
vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,
|
|
150
|
+
fragmentShader: `
|
|
151
|
+
uniform float irisConfidence; uniform vec3 color; uniform float opacity; varying vec2 vUv;
|
|
152
|
+
void main(){
|
|
153
|
+
float r = length(vUv - 0.5) * 2.0;
|
|
154
|
+
float ring = smoothstep(0.6, 0.7, r) * (1.0 - smoothstep(0.9, 1.0, r));
|
|
155
|
+
gl_FragColor = vec4(color, ring * opacity * irisConfidence);
|
|
156
|
+
#include <colorspace_fragment>
|
|
157
|
+
}`,
|
|
158
|
+
transparent: true,
|
|
159
|
+
depthTest: false,
|
|
160
|
+
depthWrite: false,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
<IrisEffect material={ring} color="#ffcc00" />;
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The factory runs once per eye for each `material` identity; the effect
|
|
167
|
+
disposes what a factory created when the factory changes or on unmount,
|
|
168
|
+
while an instance you pass stays yours. Read the live texture from
|
|
169
|
+
`uniforms.map`: `context.texture` is only the texture known when the factory
|
|
170
|
+
ran, often null. The built-in
|
|
171
|
+
image shader shows it as authored, multiplies it by `color` only when
|
|
172
|
+
`tintImage` is on, and fades with confidence.
|
|
173
|
+
|
|
174
|
+
## When to use what
|
|
175
|
+
|
|
176
|
+
- **Iris tracking**: eye decorations, pupil overlays, iris recolouring,
|
|
177
|
+
shader inputs tied to where the iris sits in the eye.
|
|
178
|
+
- **Blendshapes** (`useFaceInfo` / `useFaceAction`): blinks, winks, squints,
|
|
179
|
+
eye-look-left/right/up/down categories as discrete expressions.
|
|
180
|
+
- **Gaze estimation**: not provided. `offset` is not gaze; a real estimate
|
|
181
|
+
needs head pose and eye geometry.
|
|
182
|
+
- **PinToMesh / FaceMesh materials**: content that must deform with the skin
|
|
183
|
+
around the eye rather than sit on the iris.
|
|
184
|
+
|
|
185
|
+
## Hiding the iris behind the eyelids
|
|
186
|
+
|
|
187
|
+
The quads are flat and draw over the lids. To clip them to the eye openings
|
|
188
|
+
without a dedicated mask, draw the face mesh over them with the camera feed
|
|
189
|
+
and no eye-opening triangles (the example's "eyelid mask" toggle). The
|
|
190
|
+
material samples the feed through the mesh's live `faceUv` attribute with
|
|
191
|
+
depth testing off, so it always lands on top of the quads:
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
import { CanonicalFaceMeshRenderer, IrisEffect } from "@vincentt-xr/sdk/face-effects";
|
|
195
|
+
import { useXRCameraTexture } from "@vincentt-xr/sdk/low-level";
|
|
196
|
+
|
|
197
|
+
const feed = new THREE.ShaderMaterial({
|
|
198
|
+
uniforms: { cameraTexture: { value: useXRCameraTexture() } },
|
|
199
|
+
vertexShader: `attribute vec2 faceUv; varying vec2 vUv;
|
|
200
|
+
void main(){ vUv = faceUv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,
|
|
201
|
+
fragmentShader: `uniform sampler2D cameraTexture; varying vec2 vUv;
|
|
202
|
+
void main(){ gl_FragColor = texture2D(cameraTexture, vUv); #include <colorspace_fragment> }`,
|
|
203
|
+
transparent: true, // the quads are transparent; an opaque mask would draw before them
|
|
204
|
+
depthTest: false,
|
|
205
|
+
depthWrite: false,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// IrisEffect renders at order 100; the mask draws after it.
|
|
209
|
+
<IrisEffect />;
|
|
210
|
+
<CanonicalFaceMeshRenderer
|
|
211
|
+
showEyeGeometry={false}
|
|
212
|
+
showEyeMask={false}
|
|
213
|
+
renderOrder={101}
|
|
214
|
+
materialOverride={feed}
|
|
215
|
+
/>;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Memoize the material on the camera texture and dispose it on unmount, as
|
|
219
|
+
the example does.
|
|
220
|
+
|
|
221
|
+
## Known limitations
|
|
222
|
+
|
|
223
|
+
- Single-person per `faceIndex`; multi-face scenes mount one effect per face.
|
|
224
|
+
- Iris points are present even for closed eyes; `confidence` is the gate.
|
|
225
|
+
- The quads sit on the z=0 plane without eyeball curvature or head pose.
|
|
226
|
+
- Hold replays the last position; with `predictMs` > 0 a sharp reversal can
|
|
227
|
+
overshoot for up to that window.
|
|
228
|
+
|
|
229
|
+
## Manual validation
|
|
230
|
+
|
|
231
|
+
Run `/iris-tracking`:
|
|
232
|
+
|
|
233
|
+
| Case | Steps | Expect |
|
|
234
|
+
| -------------------- | --------------------------------------- | ------------------------------------------------------------------ |
|
|
235
|
+
| Shader material | default, change color / scale / opacity | Both irises recoloured live; pupil follows the iris offset |
|
|
236
|
+
| Image material | mode `image`, presets or a URL | Image covers each iris, tinted by color; mirror flips it |
|
|
237
|
+
| Custom material | "custom shader (ring)" | Pulsing ring from the shared uniforms |
|
|
238
|
+
| Tracking loss | cover the face / "Person Shaking Head" | Effect holds ~250 ms then disappears; no stale eyes |
|
|
239
|
+
| Multi-face | "Two People Smiling", face index 0 / 1 | Effect moves to the selected face |
|
|
240
|
+
| Mirrored camera | webcam | Left/right stay anatomical; the eye on the viewer's left is `left` |
|
|
241
|
+
| Portrait / landscape | resize the pane | Quads stay centred on the irises at the right size |
|
|
242
|
+
| Closed eyes | blink | Effect fades with confidence |
|