@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,301 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Segmentation Overlay (2D)"
|
|
3
|
+
description: "Draw semantic segmentation regions (lips, eyes, brows, face skin, mouth, person, background, or any label an external model emits) as tinted 2D layers over the camera, without canonical 3D face geometry."
|
|
4
|
+
sidebarTitle: "Segmentation Overlay"
|
|
5
|
+
icon: "layer-group"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
`SegmentationOverlay` renders one or more regions of a **semantic mask** as a
|
|
9
|
+
screen-aligned 2D layer composited over the camera feed. Masks come from the
|
|
10
|
+
SDK's own providers or from any external segmentation model (face parsing,
|
|
11
|
+
matting), as plain data.
|
|
12
|
+
|
|
13
|
+
Import doors: the component, both providers, settings and the `SemanticMask`
|
|
14
|
+
type are in `@vincentt-xr/sdk/face-effects`; `SegmentationOverlay` and
|
|
15
|
+
`usePersonMask` are also in `@vincentt-xr/sdk/tracking` for person /
|
|
16
|
+
background work; the material object API, shaders, mask textures,
|
|
17
|
+
rasterizer primitives and `computeMaskUvTransform` are
|
|
18
|
+
`@vincentt-xr/sdk/low-level`.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import {
|
|
22
|
+
SegmentationOverlay,
|
|
23
|
+
useFaceRegionMask,
|
|
24
|
+
usePersonMask,
|
|
25
|
+
} from "@vincentt-xr/sdk/face-effects";
|
|
26
|
+
|
|
27
|
+
const Lips = () => {
|
|
28
|
+
const faces = useFaceRegionMask(); // lips, mouth, eyes, brows, face_skin
|
|
29
|
+
return <SegmentationOverlay mask={faces} label="lips" color="#c0304f" blendMode="multiply" />;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const DimBackground = () => {
|
|
33
|
+
const person = usePersonMask(); // bg, person
|
|
34
|
+
return (
|
|
35
|
+
<SegmentationOverlay
|
|
36
|
+
mask={person}
|
|
37
|
+
label="bg"
|
|
38
|
+
color="#000000"
|
|
39
|
+
opacity={0.6}
|
|
40
|
+
blendMode="normal"
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## When to use 2D segmentation versus canonical 3D geometry
|
|
47
|
+
|
|
48
|
+
| Need | Use |
|
|
49
|
+
| -------------------------------------------------------------------------------------------------------- | ------------------------------------- |
|
|
50
|
+
| A region tint, cut-out, or image stamped **in screen space** | `SegmentationOverlay` (this page) |
|
|
51
|
+
| Regions with **no landmarks**: background and person (built in); hair, beard, ears with an external mask | `SegmentationOverlay` + a mask source |
|
|
52
|
+
| Lipstick with highlights, gradients, UV-mapped designs that **follow the lip surface** | `LipEffect` (3D, canonical UVs) |
|
|
53
|
+
| Textures that must stay attached under head rotation, depth, occlusion | `FaceMeshRenderer` / `LipEffect` |
|
|
54
|
+
|
|
55
|
+
The 2D path has no UV atlas and no per-vertex normals: a design texture is
|
|
56
|
+
sampled in **screen** space (it does not rotate with the head), and lighting
|
|
57
|
+
comes only from the camera pixels via the blend mode. It is cheaper (one
|
|
58
|
+
full-screen quad, one small texture) and works for any label a mask carries.
|
|
59
|
+
The 3D path needs the canonical mesh and landmark depth but gives stable
|
|
60
|
+
texture mapping and real shading. Both can run at once.
|
|
61
|
+
|
|
62
|
+
## The mask data model
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
type SemanticMask = {
|
|
66
|
+
width: number;
|
|
67
|
+
height: number;
|
|
68
|
+
indices: Uint8Array; // one class index per pixel, row 0 = top
|
|
69
|
+
labels: readonly string[]; // labels[index]; index 0 is conventionally "bg"
|
|
70
|
+
space?: "feed" | "source";
|
|
71
|
+
version: number; // bump on every change; consumers upload lazily
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Any model can feed this: write its per-pixel class ids into `indices` and
|
|
76
|
+
name them in `labels`. `SEMANTIC_MASK_LABELS` lists what the SDK's own
|
|
77
|
+
providers produce (`bg`, `brows`, `eyes`, `face_skin`, `lips`, `mouth`,
|
|
78
|
+
`person`); any other string works, so a face-parsing model's `beard`, `ears`
|
|
79
|
+
or `hair` render the same way. The SDK does not ship a model for those labels:
|
|
80
|
+
landmarks carry no ear points and cannot tell whether facial hair is present,
|
|
81
|
+
and the portrait segmenter only separates person from background. Helpers: `createSemanticMask`, `clearSemanticMask`, `fillPolygon`
|
|
82
|
+
(normalized points, y down), `fillLandmarkPolygon`, `semanticMaskLabelIndex`.
|
|
83
|
+
The mask object is meant to be mutated in place; bump `version` after writing.
|
|
84
|
+
|
|
85
|
+
## Built-in providers
|
|
86
|
+
|
|
87
|
+
- `useFaceRegionMask({ faceIndex = "all", regions, resolution = 256, maxUpdateFps, holdMs = 250, predictMs = 0 })`
|
|
88
|
+
rasterizes the face tracker's landmarks into `face_skin`, `brows`, `eyes`,
|
|
89
|
+
`lips` and `mouth` every frame (`FACE_REGION_POLYGONS` lists the landmark
|
|
90
|
+
loops; `rasterizeFaceRegions` is the pure rasterizer). `faceIndex: "all"`
|
|
91
|
+
stacks every tracked face into one mask; a number isolates one face.
|
|
92
|
+
Small or fast faces make the tracker drop out for a frame or two at a
|
|
93
|
+
time; `holdMs` keeps the last mask alive across such gaps (the same bridge
|
|
94
|
+
`useFaceInfos` applies), and `predictMs` moves the held landmarks along
|
|
95
|
+
their last smoothed velocity for up to that long so the mask keeps pace
|
|
96
|
+
with a moving subject instead of lagging behind it. Set `holdMs: 0` to
|
|
97
|
+
show raw tracker state. The bridge is `FaceTrackMemory`, usable with any
|
|
98
|
+
landmark set.
|
|
99
|
+
- `usePersonMask({ maxUpdateFps = 15 })` exposes the portrait segmentation as
|
|
100
|
+
`bg` / `person`. It acquires the segmentation model itself. Each update is
|
|
101
|
+
a GPU→CPU readback, hence the default cap. The model downloads on first
|
|
102
|
+
use, so the mask stays at `version` 0 for a few seconds; an overlay draws
|
|
103
|
+
nothing until the first real mask arrives. The selfie segmenter is a
|
|
104
|
+
person-class model: every person in frame is `person`, so multi-person
|
|
105
|
+
frames work without configuration.
|
|
106
|
+
- For `bg` and `person` prefer `blendMode="normal"` (a flat tint) or
|
|
107
|
+
`screen`; `multiply` darkens, which is right for lips on skin but nearly
|
|
108
|
+
invisible on a dark background.
|
|
109
|
+
- `useHairMask({ modelUrl, quality, active })` runs MediaPipe's **Hair
|
|
110
|
+
Segmenter** and returns `{ mask, status, error, labels }` with labels
|
|
111
|
+
`bg` / `hair`. See [Hair segmentation](#hair-segmentation) below.
|
|
112
|
+
- `useSemanticSegmentationMask({ preset, modelUrl, quality, active })` is
|
|
113
|
+
the generic form behind it; `preset: "multiclass"` runs the Selfie
|
|
114
|
+
Multiclass model (`bg`, `hair`, `body_skin`, `face_skin`, `clothes`,
|
|
115
|
+
`others`). One preset per session: the first acquirer decides.
|
|
116
|
+
|
|
117
|
+
## Hair segmentation
|
|
118
|
+
|
|
119
|
+
Hair is a dedicated region with its own model, because neither landmarks
|
|
120
|
+
(no hair points) nor the portrait segmenter (person as one class) can
|
|
121
|
+
produce it.
|
|
122
|
+
|
|
123
|
+
**Model and runtime contract.** `XRModel.SEMANTIC_SEGMENTATION` runs a
|
|
124
|
+
MediaPipe `ImageSegmenter` in `VIDEO` mode with `outputCategoryMask` on the
|
|
125
|
+
GPU delegate. The `hair` preset is MediaPipe's Hair Segmenter
|
|
126
|
+
(`hair_segmenter.tflite`, 512×512 input, classes `background` / `hair`). Its
|
|
127
|
+
category mask carries the class index per pixel (0 background, 1 hair; the
|
|
128
|
+
multiclass model likewise emits 0–5; both measured against the live models); the SDK's portrait selfie segmenter is
|
|
129
|
+
different and emits 255 / 0, which `usePersonMask` normalises separately.
|
|
130
|
+
By default the model loads from MediaPipe's published file, whose bytes are
|
|
131
|
+
pinned in the SDK's integrity manifest, so the default is verified: a
|
|
132
|
+
republished file fails loudly with an SRI mismatch instead of silently
|
|
133
|
+
changing behaviour. The same hash is pinned under the SDK CDN path
|
|
134
|
+
`/assets/xr/models/hair_segmenter.tflite`; once that file is uploaded, pass
|
|
135
|
+
it as `modelUrl` (or the SDK will flip the default). `useHairMask()` with no
|
|
136
|
+
options works:
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
import { SegmentationOverlay, useHairMask } from "@vincentt-xr/sdk/face-effects";
|
|
140
|
+
|
|
141
|
+
const HairTint = () => {
|
|
142
|
+
const hair = useHairMask();
|
|
143
|
+
if (hair.status === "error") console.warn(hair.error);
|
|
144
|
+
return (
|
|
145
|
+
<SegmentationOverlay
|
|
146
|
+
mask={hair.mask}
|
|
147
|
+
label="hair"
|
|
148
|
+
color="#7c3aed"
|
|
149
|
+
blendMode="overlay"
|
|
150
|
+
feather={2}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Lifecycle.** `status` is `loading` while the WASM and model download and
|
|
157
|
+
the segmenter initialises, `ready` once inference runs, `error` if the asset
|
|
158
|
+
fails verification or the segmenter throws (`error` carries the reason). An
|
|
159
|
+
inference error is transient: the next successful frame returns the status
|
|
160
|
+
to `ready`, so consumers always see the current state. `labels` is the
|
|
161
|
+
preset's label list from the first render, before any data.
|
|
162
|
+
The mask stays at `version` 0 until the first inference, so the overlay
|
|
163
|
+
draws nothing during loading. The model is refcounted like every tracker:
|
|
164
|
+
mount it once above scene switches, pause it with `active: false`, and it
|
|
165
|
+
is disposed when the last consumer unmounts.
|
|
166
|
+
|
|
167
|
+
**Coordinates and resolution.** The feed is stretched into a process canvas
|
|
168
|
+
at `quality` × feed size (low 0.5, medium 0.75, high 1), so the mask is in
|
|
169
|
+
feed space with the feed's aspect ratio and mirroring, exactly like the
|
|
170
|
+
other providers; the overlay needs no coordinate setup. The mask is
|
|
171
|
+
nearest-filtered at the process resolution; use `feather` for soft hair
|
|
172
|
+
edges and `quality: "high"` when flyaway strands matter.
|
|
173
|
+
|
|
174
|
+
**Performance.** Inference runs at the model's `targetFps` (15 by default,
|
|
175
|
+
overridable through the session `perf` / pipeline options). Each inference
|
|
176
|
+
reads the category mask back to the CPU, which is why the rate is capped;
|
|
177
|
+
the hair model is heavier than the portrait segmenter, so prefer `medium`
|
|
178
|
+
quality on phones. Only one semantic segmenter runs per session; use the
|
|
179
|
+
`multiclass` preset when you need hair and clothes together.
|
|
180
|
+
|
|
181
|
+
**Hair versus the face mesh and the portrait segmenter.** The face mesh is
|
|
182
|
+
geometry: it follows landmarks, supports UV-mapped textures and lighting,
|
|
183
|
+
and knows nothing about hair. The portrait segmenter answers "is this pixel
|
|
184
|
+
a person", which includes hair, clothes and skin together. The hair
|
|
185
|
+
segmenter answers "is this pixel hair", so it is the right source for hair
|
|
186
|
+
tints, recolours, highlights, and for occluding or cutting out hair in a
|
|
187
|
+
composite, all as 2D screen-space effects.
|
|
188
|
+
|
|
189
|
+
Both return a stable object in **feed space**.
|
|
190
|
+
|
|
191
|
+
## Coordinates, mirroring, aspect and resolution
|
|
192
|
+
|
|
193
|
+
- **Feed space** is the session camera texture: what trackers see, already
|
|
194
|
+
cover-cropped to the viewport and mirrored for webcams. The overlay quad
|
|
195
|
+
covers the viewport exactly like `VideoBackground`, so a feed-space mask
|
|
196
|
+
needs no coordinate work. Row 0 of the mask is the top of the screen.
|
|
197
|
+
- **Source space** (`space: "source"`) is the raw media frame. The overlay
|
|
198
|
+
cover-fits it to the feed (centred crop) using the mask's own aspect ratio
|
|
199
|
+
and the camera texture size, via `computeMaskUvTransform`. Set `mirrored`
|
|
200
|
+
when the feed is mirrored relative to that source (a webcam selfie view;
|
|
201
|
+
image and video sources are not mirrored).
|
|
202
|
+
- **Resolution** is independent of the viewport: masks are nearest-filtered
|
|
203
|
+
class indices, so edges are as sharp as the mask. Raise `resolution` (long
|
|
204
|
+
edge) for crisper edges or use `feather` (in mask texels) to soften them.
|
|
205
|
+
- Landmarks are normalized to the feed, so the face providers are resolution
|
|
206
|
+
and orientation agnostic; rotating the device re-crops the feed and the
|
|
207
|
+
mask follows.
|
|
208
|
+
|
|
209
|
+
## Rendering
|
|
210
|
+
|
|
211
|
+
Props are `Partial<SegmentationOverlaySettings>` plus `mask`, `texture`,
|
|
212
|
+
`material`, `renderOrder` (default just above the video background) and
|
|
213
|
+
`visible`.
|
|
214
|
+
|
|
215
|
+
| Setting | Meaning |
|
|
216
|
+
| ----------- | --------------------------------------------------------------------------- |
|
|
217
|
+
| `label` | Region(s) to show; comma-separate up to four (`"lips,mouth"`) |
|
|
218
|
+
| `invert` | Show everything except the selected labels |
|
|
219
|
+
| `color` | Tint; with `texture`, multiplied into the image |
|
|
220
|
+
| `opacity` | Layer opacity |
|
|
221
|
+
| `blendMode` | `normal` (flat paint), `multiply` (keeps skin shading), `screen`, `overlay` |
|
|
222
|
+
| `feather` | Edge softening radius in mask texels (0 = hard class edges) |
|
|
223
|
+
| `mirrored` | Flip the mask horizontally |
|
|
224
|
+
|
|
225
|
+
Output is a transparent layer, so other content composites normally above
|
|
226
|
+
and below it. `texture` is sampled in screen UV (cover of the viewport);
|
|
227
|
+
`resolveSegmentationOverlaySettings` / `patchSegmentationOverlaySettings`
|
|
228
|
+
resolve partial settings, and `SegmentationOverlaySettingsPanel` from
|
|
229
|
+
`@vincentt-xr/sdk/debug-ui` edits them live.
|
|
230
|
+
|
|
231
|
+
### Custom materials
|
|
232
|
+
|
|
233
|
+
`material` accepts a node, a `THREE.Material` instance (shareable), or a
|
|
234
|
+
factory receiving:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
type SegmentationOverlayMaterialContext = {
|
|
238
|
+
cameraTexture: THREE.Texture | null;
|
|
239
|
+
maskTexture: THREE.Texture; // red channel = class index / 255, nearest-filtered
|
|
240
|
+
maskTransform: THREE.Matrix3; // feed UV → mask UV
|
|
241
|
+
labelIndices: number[]; // the selected classes
|
|
242
|
+
mask: SemanticMask | null;
|
|
243
|
+
settings: SegmentationOverlaySettings;
|
|
244
|
+
};
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Sample membership as `abs(floor(texture2D(maskTexture, (maskTransform *
|
|
248
|
+
vec3(uv, 1.0)).xy).r * 255.0 + 0.5) - index) < 0.5`. `labelIndices` is empty
|
|
249
|
+
until the mask has data, and `maskTexture` is re-allocated when the mask
|
|
250
|
+
changes size (the factory is re-run with the new one), so read both from the
|
|
251
|
+
context on every call rather than caching them. The built-in shader is
|
|
252
|
+
exported as `SEGMENTATION_OVERLAY_VERTEX_SHADER` /
|
|
253
|
+
`SEGMENTATION_OVERLAY_FRAGMENT_SHADER`, and the material object API
|
|
254
|
+
(`createSegmentationOverlayMaterial`, `updateSegmentationOverlayMaterial`,
|
|
255
|
+
`useSegmentationOverlayMaterial`) lets you share one material.
|
|
256
|
+
|
|
257
|
+
## Multiple faces
|
|
258
|
+
|
|
259
|
+
`useFaceRegionMask({ faceIndex: "all" })` merges every face into one mask, so
|
|
260
|
+
one overlay paints all lips. For per-face looks, create one provider per face
|
|
261
|
+
(`faceIndex: 0`, `faceIndex: 1`) and one overlay each. Match the tracker's
|
|
262
|
+
`numFaces`. `usePersonMask` is per scene, not per face.
|
|
263
|
+
|
|
264
|
+
## Performance
|
|
265
|
+
|
|
266
|
+
- One overlay is one full-screen quad and one small `R8` texture upload per
|
|
267
|
+
mask version. Combine labels (`label="lips,mouth"`) rather than stacking
|
|
268
|
+
overlays when they share a look.
|
|
269
|
+
- Face masks default to a 256 px long edge; rasterizing a few landmark
|
|
270
|
+
polygons at that size costs well under a millisecond. Lower `resolution`
|
|
271
|
+
or set `maxUpdateFps` on the provider to trade sharpness for CPU.
|
|
272
|
+
- `usePersonMask` reads the segmentation mask back from the GPU; keep its
|
|
273
|
+
`maxUpdateFps` low (15 by default) and share one provider between overlays.
|
|
274
|
+
- The session's `perf={{ targetFps, camRes }}` still governs tracking cost.
|
|
275
|
+
|
|
276
|
+
## Manual visual test plan
|
|
277
|
+
|
|
278
|
+
Run `/segmentation-overlay` in the playground and check each row with the
|
|
279
|
+
region picker, on a webcam and on an image/video source:
|
|
280
|
+
|
|
281
|
+
| Mask / region | Source | Expect |
|
|
282
|
+
| ----------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
283
|
+
| `lips` (face provider) | webcam, video | Tint hugs the outer lip line; mouth interior untinted; follows talking |
|
|
284
|
+
| `mouth` | video, mouth open | Only the opening is painted |
|
|
285
|
+
| `eyes`, `brows` | video | Both eyes / both brows painted; nothing on the lids' skin |
|
|
286
|
+
| `face_skin` | video | Oval painted; eyes, brows, lips and mouth cut out |
|
|
287
|
+
| `bg` (person provider) | video | Everything but the person painted; silhouette edge follows the mask |
|
|
288
|
+
| `person`, `invert` on | video | Same as `bg` |
|
|
289
|
+
| `mirrored` toggle | webcam | Feed-space masks drift off the face when mirrored (expected); off again aligns |
|
|
290
|
+
| portrait vs landscape pane | resize the pane | Regions stay on the face after the feed re-crops |
|
|
291
|
+
| `feather` 0 → 4 | any | Class edges go from stepped to soft |
|
|
292
|
+
| `hold` 0 vs 250, `predict` 0 vs 150 | "Person Fullbody", 2–8 s | At 0 the regions blink while the far-away face is lost and re-found; with hold they stay on, and with predict they follow the walk |
|
|
293
|
+
| `texture` on | any | Image appears in screen space, clipped to the region |
|
|
294
|
+
| custom stripes material | any | Stripes clipped to the region, still following the face |
|
|
295
|
+
| two faces | "Two People Smiling" | `All faces`: both painted; `Face 0 only`: one |
|
|
296
|
+
| `hair`, short dark hair | "Person ThumbUp" | Hair painted to the hairline; no bleed onto forehead or background |
|
|
297
|
+
| `hair`, long hair, tied | "Person Fullbody", "Person Shaking Head" | Painted mass follows head turns and shakes; strands at the edges soften with `feather` |
|
|
298
|
+
| `hair`, two people | "Two People Smiling" | Both heads painted; the mask is per pixel, not per face |
|
|
299
|
+
| `hair`, lighting | webcam, bright vs dim room | Mask holds under both; dark hair on a dark background is the hard case |
|
|
300
|
+
| `hair`, orientation | webcam, rotate device | Mask re-crops with the feed and stays on the hair |
|
|
301
|
+
| `hair`, `status` | load with wrong `modelUrl` | Status line reads `error` with the reason; nothing is painted |
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Capabilities & Session Lifecycle"
|
|
3
|
+
description: "Check what the device can do before a session starts, read structured fallback reasons, and drive a hardened start / pause / resume / stop / reset lifecycle that survives camera loss and tab switches."
|
|
4
|
+
sidebarTitle: "Capabilities & Lifecycle"
|
|
5
|
+
icon: "heart-pulse"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Two things decide whether an AR experience works on a given phone: whether the
|
|
9
|
+
platform can run it at all, and whether the session stays healthy once it
|
|
10
|
+
runs. The SDK answers the first with a capability check that never opens the
|
|
11
|
+
camera, and the second with a session state machine that releases resources
|
|
12
|
+
exactly once and recovers from a lost camera.
|
|
13
|
+
|
|
14
|
+
## Capability detection
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { useXRCapabilities, resolveTrackerSupport, XRModel } from "@vincentt-xr/sdk";
|
|
18
|
+
|
|
19
|
+
const { capabilities, status, refresh } = useXRCapabilities({
|
|
20
|
+
models: [XRModel.FACE_TRACKER], // optional: HEAD-probe each tracker's asset
|
|
21
|
+
});
|
|
22
|
+
if (status === "ready" && capabilities) {
|
|
23
|
+
const face = resolveTrackerSupport(capabilities, XRModel.FACE_TRACKER);
|
|
24
|
+
if (!face.supported) showFallback(face.reason); // { code, message, recoverable }
|
|
25
|
+
if (face.needsPermission) showCameraPrimer(); // the first getUserMedia will prompt
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`useXRCapabilities` needs no `<XRProvider>`, so it runs on a landing screen.
|
|
30
|
+
`detectXRCapabilities(options)` is the same check as a plain promise.
|
|
31
|
+
|
|
32
|
+
The report (`XRCapabilities`):
|
|
33
|
+
|
|
34
|
+
| Field | What it checks |
|
|
35
|
+
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| `camera` | Secure context, `getUserMedia` presence, number of video inputs, Permissions API state. `status` is `supported`, `permission-prompt`, `permission-denied`, `unsupported` or `unknown`. |
|
|
37
|
+
| `rendering` | WebGL version (`0`/`1`/`2`), OffscreenCanvas, WebAssembly, WebGPU presence (informational). |
|
|
38
|
+
| `models` | For each `XRModel` in `models`: a `HEAD` request to its asset (status, reason, byte size). Unlisted models report `unknown`. |
|
|
39
|
+
| `canRunTracking` | Rendering is supported and the camera is not unsupported. |
|
|
40
|
+
|
|
41
|
+
`permission-denied` and `unsupported` are distinct: the first is fixable by the
|
|
42
|
+
viewer (recoverable), the second is not. The Permissions API is consulted
|
|
43
|
+
before the device list, so a browser that hides devices until permission is
|
|
44
|
+
granted still reads as denied, not as "no camera".
|
|
45
|
+
|
|
46
|
+
### Fallback reasons
|
|
47
|
+
|
|
48
|
+
Every degraded state in the SDK is described by one `XRFallbackReason`:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
type XRFallbackReason = { code: XRFallbackCode; message: string; recoverable: boolean };
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
| Code | Source |
|
|
55
|
+
| ----------------------------------------------------------------------- | -------------------------- |
|
|
56
|
+
| `camera-permission-denied`, `camera-in-use`, `no-camera` | camera check / session |
|
|
57
|
+
| `camera-insecure-context`, `camera-unsupported` | camera check |
|
|
58
|
+
| `camera-lost` | session (track ended) |
|
|
59
|
+
| `webgl-unavailable`, `offscreen-canvas-unavailable`, `wasm-unavailable` | rendering check |
|
|
60
|
+
| `model-unreachable`, `model-integrity`, `model-init-failed` | model probe / `useXRModel` |
|
|
61
|
+
|
|
62
|
+
Branch on `code`; `message` is display prose. `resolveTrackerSupport` returns
|
|
63
|
+
the most fundamental blocking reason first (rendering, then camera, then the
|
|
64
|
+
model).
|
|
65
|
+
|
|
66
|
+
### Tracker consumers
|
|
67
|
+
|
|
68
|
+
Inside a session, components that do not own a tracker read its status:
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { useXRTrackerStatus } from "@vincentt-xr/sdk/low-level";
|
|
72
|
+
|
|
73
|
+
const { status, fallback } = useXRTrackerStatus(XRModel.FACE_TRACKER);
|
|
74
|
+
// status: "unavailable" | "loading" | "ready" | "blocked" | "error"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`blocked` means the camera failed (the session error maps to `fallback`);
|
|
78
|
+
`error` means the model's own initialize failed (`useXRModelErrors()` lists
|
|
79
|
+
them; `useXRModel().fallback` carries the same reason for the owner).
|
|
80
|
+
|
|
81
|
+
## Session lifecycle
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
idle ──setMediaSource──> media-bound ──start──> running ──stop──> stopped
|
|
85
|
+
↑ │ ↑ │
|
|
86
|
+
│ pause │ │ resume │
|
|
87
|
+
│ ▼ │ │
|
|
88
|
+
│ paused ─┘ │
|
|
89
|
+
└────────────reset──────────────────────┘
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| Call | Effect |
|
|
93
|
+
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
94
|
+
| `session.pause()` | `running` → `paused`: feed loop stops, every model pauses, the video element pauses. The stream stays open. No-op elsewhere. |
|
|
95
|
+
| `session.resume()` | `paused` → `running` with no rebind. `start()` from `paused` does the same. |
|
|
96
|
+
| `session.stop()` | Releases the stream and tracks, keeps the models initialized. `start()` afterwards needs a `setMediaSource()` first; binding the same source again re-opens it. |
|
|
97
|
+
| `session.reset()` | `stop()` + release every model; back to `idle`. Clears errors. |
|
|
98
|
+
| provider unmount | The facade is disposed: `reset()` plus listener removal. Every later call throws. Models and tracks are released exactly once. |
|
|
99
|
+
|
|
100
|
+
`useXRLifecycle()` (from `/low-level`) subscribes to the state; the example's
|
|
101
|
+
log is built from it.
|
|
102
|
+
|
|
103
|
+
### Camera loss and permission changes
|
|
104
|
+
|
|
105
|
+
When a live video track ends (device unplugged, permission revoked, another
|
|
106
|
+
app took it) or the camera permission flips to `denied`, the session releases
|
|
107
|
+
the media once, suspends every model, publishes a session error with
|
|
108
|
+
`reason: "camera-lost"` (or `"permission-denied"`) and parks in `stopped`.
|
|
109
|
+
Recovery is the normal path: `setMediaSource()` then `start()`. A track that
|
|
110
|
+
the session itself replaced during a source swap never triggers this.
|
|
111
|
+
|
|
112
|
+
### Visibility
|
|
113
|
+
|
|
114
|
+
A hidden tab throttles `requestAnimationFrame` and iOS Safari pauses the camera
|
|
115
|
+
track. The session always retries `video.play()` when the tab returns. With
|
|
116
|
+
`<XRProvider lifecycle={{ pauseWhenHidden: true }}>` it also pauses the whole
|
|
117
|
+
session while hidden and resumes on return. A session the app paused itself
|
|
118
|
+
is left paused.
|
|
119
|
+
|
|
120
|
+
### Source swaps while paused
|
|
121
|
+
|
|
122
|
+
`setMediaSource()` on a paused session binds the new media but stays paused;
|
|
123
|
+
`resume()` starts the loop against it.
|
|
124
|
+
|
|
125
|
+
## Manual validation
|
|
126
|
+
|
|
127
|
+
Run `/session-health`:
|
|
128
|
+
|
|
129
|
+
| Case | Steps | Expect |
|
|
130
|
+
| ----------------- | ------------------------- | ------------------------------------------------------------------- |
|
|
131
|
+
| Capability report | page load, "re-check" | camera / rendering / model rows with reasons; `canRunTracking` |
|
|
132
|
+
| Pause / resume | buttons | lifecycle log `running → paused → running`; feed fps 0 while paused |
|
|
133
|
+
| Stop / start | stop, start | `start` throws (needs a source); pick a media source to recover |
|
|
134
|
+
| Camera loss | "simulate camera loss" | `error camera-lost`, lifecycle `stopped`, trackers `blocked` |
|
|
135
|
+
| Recovery | pick another media source | lifecycle `running`, error cleared |
|
|
136
|
+
| Route change | navigate away and back | no leaked stream; the page starts clean |
|
package/docs/api/trackers.mdx
CHANGED
|
@@ -28,13 +28,31 @@ import {
|
|
|
28
28
|
`scaleWithFace`, and `followFaceRoll`
|
|
29
29
|
- `HandTracker`: 21-joint skeleton; use `hand` or `handIndex` for selection
|
|
30
30
|
- `GestureTracker`: named gesture recognition
|
|
31
|
+
- `HolisticTracker`: one inference for face + hands + pose with
|
|
32
|
+
`HolisticTracker.Face` / `.Hand` / `.Body` sub-trackers; see
|
|
33
|
+
[Holistic Tracker](/api/holistic-tracker)
|
|
31
34
|
- `BodyTracker`: 33 pose landmarks
|
|
32
35
|
- `Segmentation`: portrait mask for background replacement
|
|
36
|
+
- `useHairMask` / `useSemanticSegmentationMask` (hair or multiclass category
|
|
37
|
+
masks for 2D overlays; see [Segmentation Overlay](/api/segmentation-overlay))
|
|
33
38
|
|
|
34
39
|
The example app route `/body` shows the recommended body-tracking pattern:
|
|
35
40
|
mount `BodyTracker` once, then attach visible content with `TrackingAnchor`
|
|
36
41
|
targets such as `body.head`, `body.leftShoulder`, and `body.rightWrist`.
|
|
37
42
|
|
|
43
|
+
For a low-level hand, gesture, or body landmark, SDK tracker coordinates have a
|
|
44
|
+
center origin, X right, Y up, and positive Z toward the viewer. X/Y occupy the
|
|
45
|
+
usual `[-1, 1]` tracker range; Z is tracker-relative depth and is not a
|
|
46
|
+
normalized screen axis. Convert a point to `ScreenImage`, `ScreenText`, or
|
|
47
|
+
`ScreenShape` design pixels with `trackerPointToScreenPixels` from
|
|
48
|
+
`@vincentt-xr/sdk`. Do not mirror, cover-map, or origin-flip it again.
|
|
49
|
+
|
|
50
|
+
This is distinct from a raw MediaPipe face landmark: face landmarks use a
|
|
51
|
+
top-left normalized image plane and are consumed by `FaceTracker`,
|
|
52
|
+
`FaceMesh`, and `FaceMeshRenderer` internally. Attach 3D face content with
|
|
53
|
+
`TrackingAnchor`; do not pass a raw face landmark to
|
|
54
|
+
`trackerPointToScreenPixels`.
|
|
55
|
+
|
|
38
56
|
Gesture names are exactly `closed_fist`, `open_palm`, `pointing_up`,
|
|
39
57
|
`thumb_down`, `thumb_up`, `victory`, `i_love_you`, and `none`. Use
|
|
40
58
|
`GestureTrigger` for one-shot callbacks and `useXRModelNode` from
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Persistent Tracking Configuration"
|
|
3
|
+
description: "A serializable, validated, versioned document for tracker selection and tuning (fps, camera, smoothing, prediction, assets, fallbacks), projected onto the provider, trackers and model options."
|
|
4
|
+
sidebarTitle: "Tracking Config"
|
|
5
|
+
icon: "sliders"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
import { XRProvider, useXRTrackingConfig } from "@vincentt-xr/sdk";
|
|
10
|
+
import { FaceTracker } from "@vincentt-xr/sdk/tracking";
|
|
11
|
+
|
|
12
|
+
const App = () => {
|
|
13
|
+
// Above the provider: perf / lifecycle / quality come from the document.
|
|
14
|
+
const tracking = useXRTrackingConfig({
|
|
15
|
+
storageKey: "myapp.tracking",
|
|
16
|
+
initial: { trackers: { hand: { enabled: true } } },
|
|
17
|
+
});
|
|
18
|
+
const face = tracking.trackerProps("face");
|
|
19
|
+
// Wait for the persisted document before mounting the provider: a different
|
|
20
|
+
// perf / camera after hydration would re-initialize it (camera restart).
|
|
21
|
+
if (!tracking.hydrated) return null;
|
|
22
|
+
return (
|
|
23
|
+
<XRProvider {...tracking.providerProps}>
|
|
24
|
+
<XRScene>
|
|
25
|
+
{face.active && <FaceTracker numFaces={face.numFaces} targetFps={face.targetFps} />}
|
|
26
|
+
</XRScene>
|
|
27
|
+
</XRProvider>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
tracking.update({ perf: { targetFps: 20 }, camera: { facingMode: "environment" } });
|
|
32
|
+
tracking.reset();
|
|
33
|
+
tracking.json; // the serialized document
|
|
34
|
+
tracking.importJson(text);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## The document (`XRTrackingConfig`, version 1)
|
|
38
|
+
|
|
39
|
+
| Section | Fields |
|
|
40
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| `trackers` | `face { enabled, numFaces 1..10, targetFps \| null, holdMs, predictMs, smoothing }`, `hand / gesture / body { enabled, targetFps \| null }`, `holistic { enabled, targetFps, hands, holdMs, predictMs, analysisMaxDimension, outputFaceBlendshapes, confidence 0..1, delegate }`, `segmentation { enabled, quality }`, `semanticSegmentation { enabled, preset, quality }` |
|
|
42
|
+
| `perf` | `targetFps` (0 = uncapped), `camRes`, `cameraZoom`, `analysisScale` |
|
|
43
|
+
| `lifecycle` | `pauseWhenHidden` |
|
|
44
|
+
| `quality` | an adaptive-quality partial config, or `false` |
|
|
45
|
+
| `camera` | `facingMode`, `deviceId \| null`, `mirrored: boolean \| "auto"` |
|
|
46
|
+
| `fallbacks` | `onUnsupported: "block" \| "degrade"`, `delegate: "auto" \| "GPU" \| "CPU"` |
|
|
47
|
+
| `assets` | `holisticModelUrl`, `semanticSegmentationModelUrl` (http(s) or `null` for the SDK default) |
|
|
48
|
+
| `updatedAt` | wall-clock ms of the last change |
|
|
49
|
+
|
|
50
|
+
`targetFps: null` on a tracker means "inherit `perf.targetFps`".
|
|
51
|
+
|
|
52
|
+
## What is excluded, on purpose
|
|
53
|
+
|
|
54
|
+
Runtime resources never enter the document: media streams, textures, model
|
|
55
|
+
instances, the session state, calibration measurements (they have their own
|
|
56
|
+
store, `useXRCalibration`), quality decisions, and permission state.
|
|
57
|
+
`serializeTrackingConfig` re-resolves the object, so anything attached at
|
|
58
|
+
runtime is dropped on the way out, and `parseTrackingConfig` drops unknown
|
|
59
|
+
fields on the way in.
|
|
60
|
+
|
|
61
|
+
## Validation and defaults
|
|
62
|
+
|
|
63
|
+
`resolveTrackingConfig(input, base?)` fills every field from `base`
|
|
64
|
+
(`DEFAULT_TRACKING_CONFIG` unless given), type-checks and clamps each leaf,
|
|
65
|
+
and reports dropped or clamped values as `warnings` instead of throwing.
|
|
66
|
+
`DEFAULT_TRACKING_CONFIG` is the template's shipping default: face only,
|
|
67
|
+
`perf { targetFps 24, camRes 720, cameraZoom 1.2 }`, adaptive quality on,
|
|
68
|
+
front camera, `degrade` on unsupported devices.
|
|
69
|
+
|
|
70
|
+
## Migration
|
|
71
|
+
|
|
72
|
+
`parseTrackingConfig(json)` accepts any version: a document from an older
|
|
73
|
+
(or missing) `version` is migrated field-by-field onto the current
|
|
74
|
+
defaults, keeping the fields that still exist, with `migrated: true` and a
|
|
75
|
+
warning. Corrupt JSON yields the defaults. `useXRTrackingConfig` re-saves a
|
|
76
|
+
migrated document immediately, so the next load is clean. Bump
|
|
77
|
+
`TRACKING_CONFIG_VERSION` and extend the parse path when the shape changes.
|
|
78
|
+
|
|
79
|
+
## Projections
|
|
80
|
+
|
|
81
|
+
- `toProviderProps(config)` → `{ perf, lifecycle, quality }` for `<XRProvider>`.
|
|
82
|
+
- `toTrackerProps(config, kind)` → props for `<FaceTracker>` (`numFaces`, `targetFps`, `holdMs`, `predictMs`, `active`), `<HandTracker>`, `<BodyTracker>`, `<GestureTracker>`, `<HolisticTracker>` (incl. `modelUrl`, resolved `delegate`), `<Segmentation>`.
|
|
83
|
+
- `toCameraRequest(config)` → `{ facingMode, deviceId?, mirrored? }` for `setMediaSource` / `switchCamera`.
|
|
84
|
+
- `toModelOptions(config, model)` → acquire options for `useXRModel` / `registry.acquire`.
|
|
85
|
+
- `enabledModels(config)` → the `XRModel[]` to probe with `useXRCapabilities` or preload.
|
|
86
|
+
|
|
87
|
+
`fallbacks.delegate` replaces a tracker's `"auto"` delegate; `fallbacks.onUnsupported`
|
|
88
|
+
is advisory for the app's own gate (see Capabilities).
|
|
89
|
+
|
|
90
|
+
## Storage
|
|
91
|
+
|
|
92
|
+
`loadTrackingConfig` / `saveTrackingConfig` / `clearTrackingConfig` use
|
|
93
|
+
`localStorage` under `vincentt.xr.tracking-config` by default, with an
|
|
94
|
+
injectable storage for tests or a server-backed store. The hook persists on
|
|
95
|
+
every `update`, clears on `reset`, and exposes `hydrated`.
|
|
96
|
+
|
|
97
|
+
## Manual validation
|
|
98
|
+
|
|
99
|
+
Run `/tracking-config`:
|
|
100
|
+
|
|
101
|
+
| Case | Steps | Expect |
|
|
102
|
+
| -------- | -------------------------------------------------------------------- | ------------------------------------------------------- |
|
|
103
|
+
| Edit | toggle hand, set face.numFaces 2 | diagnostics shows hand ready; JSON updates |
|
|
104
|
+
| Persist | "reload page" | the same values come back; `persisted` |
|
|
105
|
+
| Validate | numFaces 40 via "edit JSON" + import | clamped to 10 with a warning |
|
|
106
|
+
| Migrate | import `{ "version": 0, "trackers": { "face": { "numFaces": 3 } } }` | numFaces 3, warning "migrated from version 0" |
|
|
107
|
+
| Camera | facingMode environment (phone) | rear camera opens straight; mirrored override respected |
|
|
108
|
+
| Reset | "reset" | defaults, storage cleared |
|
package/docs/docs.json
CHANGED
|
@@ -29,18 +29,35 @@
|
|
|
29
29
|
"pages": [
|
|
30
30
|
"api/runtime-3d",
|
|
31
31
|
"api/screen-space-coordinates",
|
|
32
|
+
"api/coordinate-spaces",
|
|
32
33
|
"api/screen-image",
|
|
33
34
|
"api/screen-text",
|
|
34
35
|
"api/screen-shape",
|
|
35
36
|
"api/trackers",
|
|
37
|
+
"api/holistic-tracker",
|
|
36
38
|
"api/face-info",
|
|
39
|
+
"api/iris-tracking",
|
|
37
40
|
"api/face-actions",
|
|
41
|
+
"api/gestures",
|
|
42
|
+
"api/session-lifecycle",
|
|
43
|
+
"api/product-ar",
|
|
44
|
+
"api/product-details",
|
|
45
|
+
"api/tracking-config",
|
|
46
|
+
"compatibility",
|
|
47
|
+
"api/camera-permission",
|
|
48
|
+
"api/cross-camera",
|
|
49
|
+
"api/adaptive-quality",
|
|
50
|
+
"api/multi-subject",
|
|
51
|
+
"api/asset-lifecycle",
|
|
52
|
+
"api/diagnostics-calibration",
|
|
38
53
|
"api/face-attribute-expression",
|
|
39
54
|
"api/face-binding-anchors",
|
|
40
55
|
"api/face-effects",
|
|
41
56
|
"api/face-mesh",
|
|
42
57
|
"api/face-painting",
|
|
43
58
|
"api/face-retouching",
|
|
59
|
+
"api/segmentation-overlay",
|
|
60
|
+
"api/person-cutout",
|
|
44
61
|
"api/capture-recording",
|
|
45
62
|
"api/audio",
|
|
46
63
|
"api/texture-sources",
|
|
@@ -60,10 +77,15 @@
|
|
|
60
77
|
"icon": "plug",
|
|
61
78
|
"pages": ["guides/harness-debug-ui"]
|
|
62
79
|
},
|
|
80
|
+
{
|
|
81
|
+
"group": "Creator Guides",
|
|
82
|
+
"icon": "palette",
|
|
83
|
+
"pages": ["guides/lip-effect-textures"]
|
|
84
|
+
},
|
|
63
85
|
{
|
|
64
86
|
"group": "Examples",
|
|
65
87
|
"icon": "code",
|
|
66
|
-
"pages": ["examples/scene-object-recipes"]
|
|
88
|
+
"pages": ["examples/scene-object-recipes", "examples/lip-shaders"]
|
|
67
89
|
}
|
|
68
90
|
]
|
|
69
91
|
}
|