@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.
Files changed (287) hide show
  1. package/GROUNDING.md +1188 -14
  2. package/dist/{DebuggerUI-C_rbLdkh.js → DebuggerUI-C-ldiozL.js} +1 -1
  3. package/dist/{DebuggerUI-DYT_dk1E.cjs → DebuggerUI-CWX5EtQv.cjs} +1 -1
  4. package/dist/FaceMeshRenderer-BEEXZwS6.cjs +158 -0
  5. package/dist/FaceMeshRenderer-UuH5sTXG.js +1322 -0
  6. package/dist/FaceRetouch-C7h7x9kd.js +683 -0
  7. package/dist/FaceRetouch-DqftT26Y.cjs +134 -0
  8. package/dist/GLTFLoader-C5q63sKO.js +1633 -0
  9. package/dist/GLTFLoader-Cb_CRwmG.cjs +1 -0
  10. package/dist/SegmentationOverlay-DQuW3dsc.js +133 -0
  11. package/dist/SegmentationOverlay-aWVod8Kl.cjs +1 -0
  12. package/dist/assets/cache.d.ts +99 -0
  13. package/dist/assets/estimate.d.ts +11 -0
  14. package/dist/assets/index.d.ts +7 -0
  15. package/dist/assets/loaders.d.ts +28 -0
  16. package/dist/assets/react.d.ts +33 -0
  17. package/dist/calibration/calibration.d.ts +70 -0
  18. package/dist/calibration/index.d.ts +6 -0
  19. package/dist/calibration/storage.d.ts +13 -0
  20. package/dist/calibration/types.d.ts +53 -0
  21. package/dist/calibration/useXRCalibration.d.ts +46 -0
  22. package/dist/camera/CameraPermissionGate.d.ts +26 -0
  23. package/dist/camera/devices.d.ts +26 -0
  24. package/dist/camera/index.d.ts +8 -0
  25. package/dist/camera/permission.d.ts +86 -0
  26. package/dist/camera/useCameraPermission.d.ts +35 -0
  27. package/dist/capabilities/detect.d.ts +45 -0
  28. package/dist/capabilities/index.d.ts +5 -0
  29. package/dist/capabilities/types.d.ts +120 -0
  30. package/dist/capabilities/useXRCapabilities.d.ts +18 -0
  31. package/dist/compat/environment.d.ts +5 -0
  32. package/dist/compat/index.d.ts +4 -0
  33. package/dist/compat/run.d.ts +18 -0
  34. package/dist/compat/scenarios.d.ts +10 -0
  35. package/dist/compat/types.d.ts +101 -0
  36. package/dist/components/BodyTracker/index.d.ts +9 -0
  37. package/dist/components/FaceMesh/FaceMeshRenderer.d.ts +11 -3
  38. package/dist/components/FaceMesh/canonical/CanonicalFaceMeshRenderer.d.ts +41 -5
  39. package/dist/components/FaceMesh/canonical/parseCanonicalFaceModel.d.ts +12 -0
  40. package/dist/components/FaceMesh/canonical/shaders.d.ts +1 -1
  41. package/dist/components/FaceMesh/index.d.ts +2 -2
  42. package/dist/components/GestureListener/index.d.ts +11 -0
  43. package/dist/components/HandTracker/index.d.ts +10 -0
  44. package/dist/components/HolisticTracker/index.d.ts +69 -0
  45. package/dist/components/HolisticTracker/settings.d.ts +58 -0
  46. package/dist/components/HolisticTracker/signals.d.ts +49 -0
  47. package/dist/components/IrisEffect/IrisMaterial.d.ts +73 -0
  48. package/dist/components/IrisEffect/index.d.ts +51 -0
  49. package/dist/components/IrisEffect/settings.d.ts +117 -0
  50. package/dist/components/LipEffect/LipEffect.d.ts +7 -1
  51. package/dist/components/LipEffect/LipEffectMaterial.d.ts +43 -7
  52. package/dist/components/LipEffect/LipEffects.d.ts +9 -0
  53. package/dist/components/LipEffect/defaults.d.ts +27 -4
  54. package/dist/components/LipEffect/index.d.ts +11 -3
  55. package/dist/components/LipEffect/settings.d.ts +271 -0
  56. package/dist/components/LipEffect/types.d.ts +56 -9
  57. package/dist/components/LipEffect/useLipDesignTextures.d.ts +11 -0
  58. package/dist/components/LipEffect/uvGuide.d.ts +59 -0
  59. package/dist/components/PersonCutout/index.d.ts +50 -0
  60. package/dist/components/PersonCutout/personCutout.d.ts +16 -0
  61. package/dist/components/SceneObjectRenderer/index.d.ts +9 -2
  62. package/dist/components/ScreenImage/components/ScreenImageBody.d.ts +1 -1
  63. package/dist/components/ScreenImage/index.d.ts +3 -1
  64. package/dist/components/ScreenImage/materials/ScreenImageBlendMaterial.d.ts +1 -1
  65. package/dist/components/ScreenImage/materials/ScreenImageMaskMaterial.d.ts +11 -0
  66. package/dist/components/ScreenImage/source.d.ts +6 -0
  67. package/dist/components/ScreenImage/types.d.ts +18 -0
  68. package/dist/components/SegmentationOverlay/SegmentationOverlay.d.ts +37 -0
  69. package/dist/components/SegmentationOverlay/SegmentationOverlayMaterial.d.ts +31 -0
  70. package/dist/components/SegmentationOverlay/index.d.ts +9 -0
  71. package/dist/components/SegmentationOverlay/maskTexture.d.ts +23 -0
  72. package/dist/components/SegmentationOverlay/providers.d.ts +72 -0
  73. package/dist/components/SegmentationOverlay/settings.d.ts +65 -0
  74. package/dist/components/XRMediaSurface/index.d.ts +2 -1
  75. package/dist/components/XRProvider/index.d.ts +16 -2
  76. package/dist/components/XRViewport/hooks/useViewportExternalFaces.d.ts +1 -1
  77. package/dist/components/XRViewport/hooks/useViewportMediaSource.d.ts +1 -1
  78. package/dist/components/XRViewport/hooks/useViewportPipeline.d.ts +1 -1
  79. package/dist/components/index.d.ts +3 -0
  80. package/dist/constants/index.d.ts +4 -1
  81. package/dist/contexts-CO0sd4JQ.cjs +1 -0
  82. package/dist/{contexts-DvQI4PqI.js → contexts-FzlnnMYD.js} +252 -246
  83. package/dist/coordinates/feed.d.ts +52 -0
  84. package/dist/coordinates/index.d.ts +5 -0
  85. package/dist/coordinates/spaces.d.ts +49 -0
  86. package/dist/coordinates/viewport.d.ts +22 -0
  87. package/dist/coordinates/world.d.ts +15 -0
  88. package/dist/debug-ui/gesture-settings/index.d.ts +14 -0
  89. package/dist/debug-ui/holistic-tracker-settings/index.d.ts +16 -0
  90. package/dist/debug-ui/index.d.ts +14 -0
  91. package/dist/debug-ui/iris-effect-settings/index.d.ts +12 -0
  92. package/dist/debug-ui/lip-effect-settings/index.d.ts +15 -0
  93. package/dist/debug-ui/media-source.cjs +1 -1
  94. package/dist/debug-ui/media-source.js +1 -1
  95. package/dist/debug-ui/product-hotspots/index.d.ts +24 -0
  96. package/dist/debug-ui/segmentation-overlay-settings/index.d.ts +12 -0
  97. package/dist/debug-ui/xr-diagnostics/index.d.ts +25 -0
  98. package/dist/debug-ui.cjs +3 -3
  99. package/dist/debug-ui.js +2057 -1687
  100. package/dist/detector-K8aej4MB.js +59 -0
  101. package/dist/detector-ijypGtse.cjs +1 -0
  102. package/dist/entries/core.d.ts +21 -3
  103. package/dist/entries/face-effects.d.ts +12 -4
  104. package/dist/entries/low-level.d.ts +22 -3
  105. package/dist/entries/product.d.ts +11 -0
  106. package/dist/entries/tracking.d.ts +26 -1
  107. package/dist/face-effects.cjs +155 -1
  108. package/dist/face-effects.js +1134 -2196
  109. package/dist/gestures/definitions.d.ts +85 -0
  110. package/dist/gestures/detector.d.ts +87 -0
  111. package/dist/gestures/index.d.ts +8 -0
  112. package/dist/gestures/measures.d.ts +101 -0
  113. package/dist/gestures/settings.d.ts +49 -0
  114. package/dist/{getFaceInfo-B43Cwq4n.js → getFaceInfo-BkZfaQs-.js} +5 -5
  115. package/dist/{getFaceInfo-DEzcL_Ub.cjs → getFaceInfo-ahPxQFGu.cjs} +1 -1
  116. package/dist/hooks/selectors.d.ts +68 -1
  117. package/dist/hooks/useCalibrationFaceTracker.d.ts +1 -1
  118. package/dist/hooks/useFaceInfo.d.ts +13 -1
  119. package/dist/hooks/useFaceInfos.d.ts +1 -1
  120. package/dist/hooks/useGesture.d.ts +32 -0
  121. package/dist/hooks/useIrisTracking.d.ts +45 -0
  122. package/dist/hooks/useXRDiagnostics.d.ts +81 -0
  123. package/dist/hooks/useXRModel.d.ts +7 -0
  124. package/dist/hooks/useXRSession/facade.d.ts +44 -2
  125. package/dist/hooks/useXRSession/index.d.ts +2 -2
  126. package/dist/hooks/useXRSession/reason.d.ts +3 -1
  127. package/dist/hooks/useXRSession/registry.d.ts +14 -0
  128. package/dist/hooks/useXRSession/store.d.ts +52 -3
  129. package/dist/index--RgGzZ6X.cjs +47 -0
  130. package/dist/{index-uE3sKczj.js → index-CHVbH0hz.js} +4 -4
  131. package/dist/index-CeVkgsag.cjs +1 -0
  132. package/dist/index-CnGox6Xr.js +3054 -0
  133. package/dist/{index-B-s4YbWu.js → index-CsUdQ-pn.js} +113 -111
  134. package/dist/{index-CY7Z8cHV.cjs → index-DKBAh6hV.cjs} +1 -1
  135. package/dist/index-DsZwTfG0.cjs +1 -0
  136. package/dist/index-Dw6tj2Mi.js +144 -0
  137. package/dist/index-OCPjOaeP.cjs +1 -0
  138. package/dist/{index-G_5h_FJQ.js → index-s9pF4zkG.js} +999 -1014
  139. package/dist/internal-BYza1h8_.cjs +1 -0
  140. package/dist/{internal-ldPYiUF0.js → internal-vsfNQycj.js} +15 -16
  141. package/dist/internal.cjs +1 -1
  142. package/dist/internal.js +2 -2
  143. package/dist/iris/compute.d.ts +25 -0
  144. package/dist/iris/index.d.ts +4 -0
  145. package/dist/iris/lead.d.ts +19 -0
  146. package/dist/iris/types.d.ts +87 -0
  147. package/dist/landmarkMask-CbTFzBYG.js +224 -0
  148. package/dist/landmarkMask-u7es2k4X.cjs +1 -0
  149. package/dist/low-level.cjs +1 -1
  150. package/dist/low-level.js +1104 -639
  151. package/dist/main.cjs +1 -1
  152. package/dist/main.js +1519 -462
  153. package/dist/measures-9dQPdw96.js +154 -0
  154. package/dist/measures-KfJzpvod.cjs +1 -0
  155. package/dist/models/Base/index.d.ts +49 -0
  156. package/dist/models/Body/index.d.ts +1 -1
  157. package/dist/models/Face/index.d.ts +1 -1
  158. package/dist/models/Hand/GestureTracking.d.ts +1 -1
  159. package/dist/models/Hand/HandTracking.d.ts +1 -1
  160. package/dist/models/Holistic/index.d.ts +49 -0
  161. package/dist/models/Holistic/result.d.ts +68 -0
  162. package/dist/models/SemanticSegmentation/index.d.ts +31 -0
  163. package/dist/models/SemanticSegmentation/presets.d.ts +28 -0
  164. package/dist/models/index.d.ts +2 -0
  165. package/dist/product/ProductDimensions.d.ts +16 -0
  166. package/dist/product/ProductHotspot.d.ts +16 -0
  167. package/dist/product/ProductViewer.d.ts +9 -0
  168. package/dist/product/details.d.ts +53 -0
  169. package/dist/product/index.d.ts +14 -0
  170. package/dist/product/loadModelViewer.d.ts +4 -0
  171. package/dist/product/types.d.ts +71 -0
  172. package/dist/product/useProductAr.d.ts +37 -0
  173. package/dist/product/useProductDetails.d.ts +60 -0
  174. package/dist/product.cjs +1 -0
  175. package/dist/product.js +414 -0
  176. package/dist/providers-C0Dlhh9Z.js +365 -0
  177. package/dist/providers-YNwkmNeK.cjs +62 -0
  178. package/dist/quality/XRQualityController.d.ts +25 -0
  179. package/dist/quality/controller.d.ts +33 -0
  180. package/dist/quality/index.d.ts +6 -0
  181. package/dist/quality/types.d.ts +100 -0
  182. package/dist/quality/useXRQuality.d.ts +28 -0
  183. package/dist/{renderGroups-CFt1yYLB.js → renderGroups-CN1cGFUo.js} +1 -1
  184. package/dist/{renderGroups-BJsx32q5.cjs → renderGroups-O4KmaMl9.cjs} +1 -1
  185. package/dist/renderPasses-Cg-zqmEz.cjs +21 -0
  186. package/dist/renderPasses-NnKZVfBy.js +1460 -0
  187. package/dist/scene-object.cjs +1 -1
  188. package/dist/scene-object.js +2 -2
  189. package/dist/screen2d/coordinates.d.ts +20 -0
  190. package/dist/screenTransform-BenfCtZt.js +44 -0
  191. package/dist/screenTransform-BiXdpVf0.cjs +1 -0
  192. package/dist/segmentation-9PhSvc-R.cjs +1 -0
  193. package/dist/segmentation-CeOTKemr.js +5 -0
  194. package/dist/segmentation2d/faceMemory.d.ts +28 -0
  195. package/dist/segmentation2d/faceRegions.d.ts +24 -0
  196. package/dist/segmentation2d/index.d.ts +9 -0
  197. package/dist/segmentation2d/maskUv.d.ts +23 -0
  198. package/dist/segmentation2d/rasterize.d.ts +14 -0
  199. package/dist/segmentation2d/types.d.ts +46 -0
  200. package/dist/selectors-C5G1PMYL.js +195 -0
  201. package/dist/selectors-Du_PsVnW.cjs +1 -0
  202. package/dist/{session-CANvoLD6.cjs → session-BBr0yN8h.cjs} +7 -7
  203. package/dist/{session-Dhi0OqZo.js → session-Cy0m7l7-.js} +76 -76
  204. package/dist/settings-DRR7-snV.cjs +1 -0
  205. package/dist/settings-aQk845NJ.js +73 -0
  206. package/dist/subjects/index.d.ts +2 -0
  207. package/dist/subjects/select.d.ts +62 -0
  208. package/dist/subjects/types.d.ts +55 -0
  209. package/dist/tracking/coordinates.d.ts +5 -0
  210. package/dist/tracking-config/apply.d.ts +57 -0
  211. package/dist/tracking-config/index.d.ts +7 -0
  212. package/dist/tracking-config/resolve.d.ts +25 -0
  213. package/dist/tracking-config/storage.d.ts +8 -0
  214. package/dist/tracking-config/types.d.ts +106 -0
  215. package/dist/tracking-config/useXRTrackingConfig.d.ts +38 -0
  216. package/dist/tracking.cjs +1 -1
  217. package/dist/tracking.js +1056 -380
  218. package/dist/types/common.d.ts +11 -1
  219. package/dist/types/compositor.d.ts +21 -0
  220. package/dist/types/gesture-tracking.d.ts +2 -2
  221. package/dist/types/hand-tracking.d.ts +3 -3
  222. package/dist/types/holistic.d.ts +121 -0
  223. package/dist/types/index.d.ts +1 -0
  224. package/dist/types/model.d.ts +22 -2
  225. package/dist/types/nodes.d.ts +29 -4
  226. package/dist/types/segmentation.d.ts +19 -0
  227. package/dist/types/session.d.ts +48 -4
  228. package/dist/useFaceInfo-Blf7FCu3.js +303 -0
  229. package/dist/useFaceInfo-C5mO8NQY.cjs +1 -0
  230. package/dist/useXRCapabilities-B1R2ghy-.cjs +1 -0
  231. package/dist/useXRCapabilities-C68v__wB.js +30 -0
  232. package/dist/utils/fns.d.ts +6 -3
  233. package/dist/utils/integrity.d.ts +4 -1
  234. package/dist/utils/updateThrottle.d.ts +2 -0
  235. package/dist/utils/useStableUniforms.d.ts +18 -0
  236. package/docs/SDK_DOCUMENTATION.md +3 -0
  237. package/docs/api/adaptive-quality.mdx +104 -0
  238. package/docs/api/asset-lifecycle.mdx +78 -0
  239. package/docs/api/camera-permission.mdx +142 -0
  240. package/docs/api/capture-recording.mdx +9 -1
  241. package/docs/api/coordinate-spaces.mdx +165 -0
  242. package/docs/api/cross-camera.mdx +88 -0
  243. package/docs/api/diagnostics-calibration.mdx +97 -0
  244. package/docs/api/face-effects.mdx +6 -0
  245. package/docs/api/face-mesh.mdx +35 -0
  246. package/docs/api/gestures.mdx +173 -0
  247. package/docs/api/holistic-tracker.mdx +159 -0
  248. package/docs/api/iris-tracking.mdx +242 -0
  249. package/docs/api/multi-subject.mdx +95 -0
  250. package/docs/api/person-cutout.mdx +136 -0
  251. package/docs/api/product-ar.mdx +111 -0
  252. package/docs/api/product-details.mdx +78 -0
  253. package/docs/api/runtime-3d.mdx +5 -0
  254. package/docs/api/screen-image.mdx +3 -1
  255. package/docs/api/screen-space-coordinates.mdx +35 -1
  256. package/docs/api/segmentation-overlay.mdx +301 -0
  257. package/docs/api/session-lifecycle.mdx +136 -0
  258. package/docs/api/trackers.mdx +18 -0
  259. package/docs/api/tracking-config.mdx +108 -0
  260. package/docs/docs.json +23 -1
  261. package/docs/examples/lip-shaders.mdx +224 -0
  262. package/docs/guides/lip-effect-textures.mdx +292 -0
  263. package/docs/guides/lip-uv-template.png +0 -0
  264. package/docs/guides/lip-uv-template.svg +1 -0
  265. package/package.json +16 -1
  266. package/dist/FaceMeshRenderer-D1WsrTLA.cjs +0 -155
  267. package/dist/FaceMeshRenderer-DSCd1H55.js +0 -1264
  268. package/dist/FaceRetouch-CMJbGThL.js +0 -551
  269. package/dist/FaceRetouch-DsE0Dbw-.cjs +0 -134
  270. package/dist/contexts-h-9TRH0r.cjs +0 -1
  271. package/dist/index-C0-fg1Hk.cjs +0 -1
  272. package/dist/index-CxMEeCRP.cjs +0 -1
  273. package/dist/index-D67hHm44.cjs +0 -1
  274. package/dist/index-DPPpU5By.cjs +0 -17
  275. package/dist/index-DYfY9ljy.js +0 -111
  276. package/dist/index-I4eF5TKT.js +0 -1457
  277. package/dist/internal-MvKrz0Nt.cjs +0 -1
  278. package/dist/renderLayers-lDtL0_43.cjs +0 -1
  279. package/dist/renderLayers-zrK2pv5O.js +0 -13
  280. package/dist/renderPasses-C72MbZ-a.js +0 -1500
  281. package/dist/renderPasses-D-Krl6D0.cjs +0 -21
  282. package/dist/selectors-ByKNTXeA.js +0 -59
  283. package/dist/selectors-CAwmL9VW.cjs +0 -1
  284. package/dist/useFaceInfo-BSZq7AO1.cjs +0 -1
  285. package/dist/useFaceInfo-FVQBBNdY.js +0 -62
  286. package/dist/useFaceResults-C3oZkn2Z.cjs +0 -1
  287. package/dist/useFaceResults-D62STzT1.js +0 -19
@@ -0,0 +1,95 @@
1
+ ---
2
+ title: "Multi-Subject Tracking Policy"
3
+ description: "The identity and association contract for faces, hands, bodies and holistic results: what an index means, how many subjects each tracker reports, how to select one, what happens on loss and reappearance, and how subjects are matched across trackers."
4
+ sidebarTitle: "Multi-Subject Policy"
5
+ icon: "people-group"
6
+ ---
7
+
8
+ ## Identity contract
9
+
10
+ - **An index is a result-array position, not an identity.** `faceIndex`,
11
+ `handIndex`, `getFace(i)`, `getLandmarks2D(i)` read position `i` of the
12
+ current frame's result. The order is whatever MediaPipe emits; it can
13
+ change between frames when subjects move, overlap, or one is lost. Nothing
14
+ in the SDK promises that `faceIndex: 1` is the same person two frames
15
+ later.
16
+ - **No learned identity.** The SDK never recognises people. The only
17
+ continuity it offers is spatial: the `sticky` selection below follows the
18
+ subject nearest to the previously selected one.
19
+ - **Single-subject APIs are unchanged.** `useFaceInfo()`, `<FaceTracker>`,
20
+ `<HandTracker>`, `<TrackingAnchor>` keep reading index 0 by default; the
21
+ policy is an opt-in option on top.
22
+
23
+ ## Maximum subjects
24
+
25
+ | Tracker | Max | Default | Option |
26
+ | -------- | --- | ------- | --------------------------------------------------------------------------------------------- |
27
+ | face | 10 | 1 | `numFaces` on `<FaceTracker>` / `useFaceInfo(s)`; the first acquirer of the shared model wins |
28
+ | hand | 2 | 2 | none (two hands, `handedness` labels them) |
29
+ | gesture | 1 | 1 | none |
30
+ | body | 1 | 1 | none |
31
+ | holistic | 1 | 1 | none (single person by design) |
32
+
33
+ `XR_MAX_SUBJECTS` carries this table. Every consumer that needs more than one
34
+ face must ask for it; a consumer that asks for fewer than the first acquirer
35
+ gets the larger count.
36
+
37
+ ## Selecting one subject
38
+
39
+ ```tsx
40
+ import { useFaceInfo, selectSubject, faceCandidates } from "@vincentt-xr/sdk/tracking";
41
+
42
+ // Hook form (faces): pick by policy instead of a fixed index.
43
+ const face = useFaceInfo({ numFaces: 4, subject: { selection: "largest" } });
44
+ const follow = useFaceInfo({ numFaces: 4, subject: { selection: "sticky", stickyRadius: 0.2 } });
45
+
46
+ // Pure form (any tracker): build candidates, select, carry the memory.
47
+ const result = selectSubject(faceCandidates(results.faceLandmarks), policy, previousMemory);
48
+ ```
49
+
50
+ | `selection` | Picks |
51
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
+ | `index` | position `policy.index` (default 0) — the historical behaviour |
53
+ | `largest` | the biggest subject (closest to the camera) |
54
+ | `nearest-center` | closest to the feed centre |
55
+ | `leftmost` / `rightmost` | by feed x, as shown in the preview |
56
+ | `sticky` | nearest to the previously selected subject within `stickyRadius` (feed units, default 0.2); same `label` (handedness) preferred; otherwise `fallback` (default `largest`) |
57
+
58
+ `selectSubject` returns `{ index, reason, memory }`; `index` is `null` when
59
+ there is no candidate. `faceCandidates(landmarkArrays)` and
60
+ `jointCandidates(handsOrBodies)` build candidates (feed-space centre and
61
+ size) from face landmarks or tracker-space joints.
62
+
63
+ ## Loss and reappearance
64
+
65
+ - A subject that disappears for a few frames is bridged by the tracker's
66
+ hold (`holdMs`, 250 ms by default, through `FaceTrackMemory` / the readers'
67
+ hold): the last result is replayed, then dropped.
68
+ - `sticky` keeps its memory through a miss, so a subject that reappears
69
+ near where it was is re-selected; one that reappears elsewhere is a new
70
+ subject and the policy's `fallback` decides. When the selection switches
71
+ to the fallback, the memory moves with it (a deliberate switch, not a jump
72
+ back later).
73
+ - Reappearance never restores a previous index: read the policy's pick, not
74
+ a remembered position.
75
+
76
+ ## Association across trackers
77
+
78
+ `associateSubjects(primary, secondary, maxDistance = 0.35)` greedily matches
79
+ the nearest centres between two candidate lists (face ↔ body, hand ↔ body),
80
+ each subject at most once, ignoring pairs farther than `maxDistance` (feed
81
+ units). That proximity-in-the-same-frame rule is the whole association
82
+ contract; with the single-person body / holistic models it reduces to
83
+ "the body belongs to the nearest face".
84
+
85
+ ## Manual validation
86
+
87
+ Run `/multi-subject` (starts on the two-people image, `numFaces` 4):
88
+
89
+ | Case | Steps | Expect |
90
+ | ---------- | ----------------------------------------------- | --------------------------------------------------- |
91
+ | Candidates | two / three people images | one grey dot per face, count in the status line |
92
+ | Rules | largest / nearest-center / leftmost / rightmost | the cyan ring moves to the matching face |
93
+ | Index | `index` with 0..3 | ring on that array position, none when out of range |
94
+ | Sticky | `sticky` on "Two People Smiling" video | the ring stays on the same person as they move |
95
+ | Loss | cover a face (webcam) | ring holds ~250 ms, then follows the fallback |
@@ -0,0 +1,136 @@
1
+ ---
2
+ title: "Person Cutout"
3
+ description: "Background replacement as a two-layer ScreenImage composition: any background media below, the person-masked live camera above."
4
+ sidebarTitle: "Person Cutout"
5
+ icon: "user"
6
+ ---
7
+
8
+ `PersonCutout` is the live camera, masked to the person by portrait
9
+ segmentation, rendered as a full-screen `ScreenImage`. Put any background
10
+ surface behind it with a lower `renderOrder` and you have background
11
+ replacement without assembling masks, materials or coordinates yourself.
12
+
13
+ ```tsx
14
+ import { FULL_SCREEN_ANCHORS, PersonCutout, ScreenImage, ScreenSpaceUI } from "@vincentt-xr/sdk";
15
+
16
+ <ScreenSpaceUI>
17
+ {/* 1. Background: image, video texture or GIF — anything ScreenImage shows. */}
18
+ <ScreenImage
19
+ src="/backgrounds/beach.jpg"
20
+ fit="cover"
21
+ anchors={FULL_SCREEN_ANCHORS}
22
+ renderOrder={0}
23
+ />
24
+ {/* 2. Foreground: the camera, person only. */}
25
+ <PersonCutout renderOrder={100} />
26
+ </ScreenSpaceUI>;
27
+ ```
28
+
29
+ ## The composition contract
30
+
31
+ - **Background** is any full-screen surface drawn first: a `ScreenImage`
32
+ (`{ kind: "image" }`, `{ kind: "gif" }`, or `{ kind: "texture" }` with a
33
+ `THREE.VideoTexture`), or a custom shader plane inside
34
+ `<ScreenTransform anchors={FULL_SCREEN_ANCHORS}>`. Give it a `renderOrder`
35
+ below the cut-out's (default 100).
36
+ - **Foreground** is `PersonCutout`: a `ScreenImage` whose `source` is the
37
+ session camera texture and whose `maskTexture` is the portrait segmentation
38
+ mask. It accepts the usual `ScreenImage` props (`opacity`, `fit`, `anchors`,
39
+ `blendMode`, `flipX`, `renderOrder`, `visible`) except `source` and the mask
40
+ props, which it owns.
41
+ - **Ordering** is `renderOrder`; both layers are overlay-style screen planes
42
+ with depth testing off, so only the order matters.
43
+
44
+ ## Alignment: aspect ratio, mirroring, resolution
45
+
46
+ - The camera texture is already cover-cropped to the viewport and mirrored
47
+ for webcams by the session, and the segmentation mask is produced in that
48
+ same feed space. `PersonCutout` samples the mask through the image's own
49
+ fit transform (`fit="cover"` by default), so mask and camera stay aligned
50
+ under any aspect ratio and any further crop or flip you apply.
51
+ - Background media is not mirrored. If a background has to match a mirrored
52
+ webcam (a sign you are sitting in front of), set `flipX` on that
53
+ `ScreenImage`.
54
+ - The mask is a render-size canvas texture with a half-pixel blur, so edges
55
+ are soft at the display resolution. The segmentation `quality` option on
56
+ the model trades edge detail for cost.
57
+
58
+ ## Mask, opacity, invert
59
+
60
+ The SDK mask carries its signal in RGB: white (255) on the person, black on
61
+ the background, with alpha 255 everywhere (three's `alphaMap` reads the green
62
+ channel, which is how `VideoBackground` consumes the same texture). The
63
+ cut-out reads `maskChannel="green"`, keeping the person and dropping the
64
+ background. `invert` flips that to show only the background with the person
65
+ cut out.
66
+ `opacity` fades the whole person layer. `usePersonMaskTexture()` returns the
67
+ raw mask texture with its `status` for custom compositions, and `ScreenImage`
68
+ itself now accepts `maskTexture`, `maskChannel` (`alpha` | `red` | `green` |
69
+ `luminance`), `maskInvert`, `maskFeather` and `maskErode` for any image.
70
+
71
+ ## Edge quality: feather and erode
72
+
73
+ The segmenter's mask is hard-edged and sits a texel or two outside the person,
74
+ so the raw cut-out carries a fringe of the source background (green-screen
75
+ spill, a light halo on dark backgrounds). Two props clean it up:
76
+
77
+ - `maskFeather` — radius in mask texels of a 3×3 blur over the mask. `0` keeps
78
+ the hard edge. Texel units keep the softness the same on screen whatever
79
+ resolution the mask arrives at.
80
+ - `maskErode` — `0..1` of the feather width to shrink the kept region by. The
81
+ blurred edge is re-thresholded, so the silhouette moves inward and the fringe
82
+ falls outside it. It does nothing without feather.
83
+
84
+ `PersonCutout` defaults to `maskFeather={4}` and `maskErode={0.7}`, chosen on
85
+ the SDK's green-screen clips with the 720-px portrait mask. Raise erode when
86
+ spill remains, lower it when fingers or hair tips disappear. Neither prop fixes
87
+ the mask trailing a fast-moving arm; that is the segmenter's temporal lag.
88
+
89
+ ## Loading and fallback
90
+
91
+ The segmentation model downloads on first use. `PersonCutout` reports
92
+ `loading` → `ready` | `error` through `onStatusChange`. While not ready it
93
+ follows `fallback`: `"camera"` (default) draws the unmasked camera so the
94
+ experience degrades to a plain feed, `"hidden"` draws nothing so only the
95
+ background shows. On error the same fallback applies and `error` carries
96
+ the reason.
97
+
98
+ ## When to use this instead of 3D
99
+
100
+ - **Use this 2D path** for background replacement, virtual backgrounds,
101
+ person-in-a-scene stickers, and any effect that only needs "the person
102
+ versus the rest of the frame". It is one quad, one mask, no geometry.
103
+ - **Use the face mesh** when something must stick to the face surface with
104
+ correct perspective and shading (lipstick, face paint, UV-mapped textures).
105
+ - **Use the head occluder** when 3D objects must disappear behind the head
106
+ (glasses arms, hats seen from the side). A 2D mask cannot occlude in depth.
107
+ - The 2D mask also covers the body, hair and clothes, which neither the face
108
+ mesh nor the head occluder does.
109
+
110
+ ## Performance
111
+
112
+ One extra full-screen quad with a masked shader; the segmentation model
113
+ runs at its own `targetFps` (24 by default). Share the model: `VideoBackground`
114
+ with `segmentation="portrait"`, `usePersonMask` overlays and `PersonCutout`
115
+ all consume the same instance.
116
+
117
+ ## Manual visual test plan
118
+
119
+ Run `/person-cutout` in the playground:
120
+
121
+ | Case | Source / background | Expect |
122
+ | ------------------------ | --------------------------------------- | ------------------------------------------------------------------------------- |
123
+ | Image background | video source, Image | Person on the photo; no camera background visible around the edges |
124
+ | Video background | video source, Video | Background plays behind the person; both layers keep playing |
125
+ | GIF background | video source, GIF | GIF animates behind the person |
126
+ | Shader background | video source, Shader | Animated plane behind the person; person edges unaffected |
127
+ | Opacity | any, opacity 0.5 | Person fades over the background; background unchanged |
128
+ | Show background only | any, invert | Person region shows the background through the camera layer |
129
+ | Edge feather / erode | video source, feather 0 vs 4, erode 0.7 | Feather 0 shows a green fringe on sleeves; the defaults remove it |
130
+ | Aspect / orientation | resize the pane, rotate device | Person stays aligned with the camera; background re-covers the screen |
131
+ | Mirrored webcam | webcam | Person mirrored like the feed; text on a background not mirrored unless `flipX` |
132
+ | Movement | "Person Shaking Head", "Person ThumbUp" | Mask follows head and hands; thin fingers may soften |
133
+ | Lighting | webcam, bright vs dim | Mask holds under both; dark clothes on dark background is the hard case |
134
+ | Loading, fallback camera | reload | Plain camera until `ready`, then the background appears |
135
+ | Loading, fallback hidden | reload | Only the background until `ready` |
136
+ | Multi-person | "Two People Smiling" | Both people kept; the segmenter is person-class |
@@ -0,0 +1,111 @@
1
+ ---
2
+ title: "Product Placement AR"
3
+ description: "View-in-your-space AR for product pages on top of Google's model-viewer: capability detection, the ProductViewer wrapper, the useProductAr hook, and the camera handoff with the tracking session."
4
+ sidebarTitle: "Product AR"
5
+ icon: "cube"
6
+ ---
7
+
8
+ Placement AR puts a product on the floor or a wall through the device's
9
+ native path: WebXR (Android Chrome), Scene Viewer (Android) or Quick Look
10
+ (iOS, from a USDZ). It is a different mode from the SDK's camera session,
11
+ which has no world tracking. The SDK wraps Google's `<model-viewer>` for it
12
+ rather than rebuilding it; the rule for every page: **placement AR is
13
+ model-viewer, face/body AR is the session, never both at once.**
14
+
15
+ ```tsx
16
+ import { ProductViewer, useProductAr } from "@vincentt-xr/sdk/product";
17
+
18
+ const Product = () => {
19
+ const ar = useProductAr();
20
+ return (
21
+ <>
22
+ <ProductViewer
23
+ src="/hat.glb"
24
+ iosSrc="/hat.usdz"
25
+ poster="/hat.webp"
26
+ alt="Hat"
27
+ onReady={ar.bind}
28
+ />
29
+ {ar.supportedModes.length > 0 && (
30
+ <button onClick={() => ar.activate()} disabled={!ar.canActivate}>
31
+ View in your space
32
+ </button>
33
+ )}
34
+ </>
35
+ );
36
+ };
37
+ ```
38
+
39
+ ## Install
40
+
41
+ `@google/model-viewer` is an optional peer dependency: add it to the app
42
+ that renders products. `<ProductViewer>` imports it lazily on first render,
43
+ so other pages never pay for it. It bundles its own three.js (it cannot share
44
+ the SDK's), roughly 1 MB; keep it on product routes only.
45
+
46
+ ## Capability
47
+
48
+ `detectXRCapabilities` / `useXRCapabilities` report `placement`:
49
+
50
+ | Field | How it is detected |
51
+ | ------------- | ----------------------------------------------------------------------------------- |
52
+ | `webxr` | `navigator.xr.isSessionSupported("immersive-ar")` |
53
+ | `sceneViewer` | Android Chrome / Samsung Internet user agent |
54
+ | `quickLook` | `<a rel="ar">` support (iOS Safari and WebKit browsers) |
55
+ | `any` | one of the above; otherwise `reason.code` `placement-unsupported` (not recoverable) |
56
+
57
+ Offer "View in AR" only when `any` is true; desktop shows the viewer alone.
58
+
59
+ ## `<ProductViewer>`
60
+
61
+ | Prop | model-viewer attribute |
62
+ | -------------------------------------------------------------------------------------------- | ------------------------------------------------ |
63
+ | `src`, `iosSrc`, `poster`, `alt` | `src`, `ios-src`, `poster`, `alt` |
64
+ | `ar` (default true), `arModes` | `ar`, `ar-modes` (preference order) |
65
+ | `placement: "floor" \| "wall"` | `ar-placement` |
66
+ | `scale: "auto" \| "fixed"` | `ar-scale` (`fixed` = true-to-size, no pinch) |
67
+ | `cameraControls`, `autoRotate`, `loading`, `environmentImage`, `shadowIntensity`, `exposure` | same names, kebab-cased |
68
+ | `attributes` | anything else, passed straight through |
69
+ | `onLoad`, `onError`, `onArStatus`, `onReady(element)` | `load`, `error`, `ar-status` events; the element |
70
+
71
+ Children are slotted into model-viewer (`slot="ar-button"`, `slot="poster"`,
72
+ hotspots).
73
+
74
+ ## `useProductAr()`
75
+
76
+ Returns `{ status, supportedModes, canActivate, reason, loading, error, bind, activate, handleStatus }`.
77
+ `status` is model-viewer's `ar-status` (`not-presenting`, `session-started`,
78
+ `object-placed`, `failed`) or `unavailable` when no mode is supported.
79
+ `activate()` must run from a user gesture.
80
+
81
+ ### Camera handoff
82
+
83
+ Only WebXR runs in the page and needs the camera the tracking session
84
+ holds; Scene Viewer and Quick Look open a native activity and never touch
85
+ it. When the hook runs under an `<XRProvider>` whose session is `running` or
86
+ `paused` on the webcam and the mode about to be used is `webxr`,
87
+ `activate()` stops the session first and restores the same facing and
88
+ lifecycle (a paused session comes back paused) when `ar-status` returns to
89
+ `not-presenting` (or `failed`). `handoffCamera: false` opts out. Stream /
90
+ canvas sources are left alone. The pure form is
91
+ `releaseSessionForAr(session, mode)`. Keep `arModes` on the viewer and
92
+ `modes` on the hook identical; both default to `DEFAULT_PRODUCT_AR_MODES`.
93
+
94
+ ## Assets
95
+
96
+ - GLB for every platform; keep it under a few MB, Draco / KTX2 compressed.
97
+ - USDZ for iOS Quick Look via `iosSrc`; without it model-viewer exports one
98
+ on the fly (slower, lower fidelity). The GLB → USDZ step is a publishing
99
+ step (see the asset pipeline issue), never runtime code.
100
+ - Host both on the CDN with long cache headers.
101
+
102
+ ## Manual validation
103
+
104
+ Run `/product-ar`:
105
+
106
+ | Device | Expect |
107
+ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
108
+ | Desktop | viewer with orbit; `supported modes: none`, button disabled |
109
+ | Android Chrome | `scene-viewer` (and `webxr` on ARCore devices); tap → Scene Viewer / WebXR places the astronaut; status log `session-started` → `object-placed` → `not-presenting` |
110
+ | iPhone | `quick-look`; tap → Quick Look opens the USDZ |
111
+ | With a tracking session running | the session reads `stopped` during AR and `running` after |
@@ -0,0 +1,78 @@
1
+ ---
2
+ title: "Product Variants, Hotspots & Dimensions"
3
+ description: "Material variants (colour / size), surface-anchored hotspots with serializable authoring data, and true-size dimension labels on a ProductViewer."
4
+ sidebarTitle: "Product Details"
5
+ icon: "tags"
6
+ ---
7
+
8
+ Builds on `<ProductViewer>` (Product Placement AR). Three details a product
9
+ page needs on the same model: switching looks, pointing at parts, and
10
+ telling the real size.
11
+
12
+ ```tsx
13
+ import {
14
+ ProductViewer,
15
+ ProductHotspot,
16
+ ProductDimensions,
17
+ useProductVariants,
18
+ useProductHotspots,
19
+ useProductDimensions,
20
+ type ProductViewerElement,
21
+ } from "@vincentt-xr/sdk/product";
22
+
23
+ const [el, setEl] = useState<ProductViewerElement | null>(null);
24
+ const variants = useProductVariants(el); // { variants, variant, setVariant }
25
+ const dimensions = useProductDimensions(el); // { size: [w, h, d], center } in metres, null until loaded
26
+ const hotspots = useProductHotspots(el, { initial: fromCms, onChange: save });
27
+
28
+ <ProductViewer src="/shoe.glb" scale="fixed" onReady={(e) => setEl(e)}>
29
+ {hotspots.hotspots.map((h) => (
30
+ <ProductHotspot key={h.id} hotspot={h} onClick={open} />
31
+ ))}
32
+ <ProductDimensions dimensions={dimensions} unit="metric" />
33
+ </ProductViewer>;
34
+ ```
35
+
36
+ ## Variants
37
+
38
+ `useProductVariants(element)` reads the model's `KHR_materials_variants`
39
+ after load and sets `variantName` on the element. Variants change materials
40
+ only, so they carry into AR wherever the platform renders the glTF (WebXR,
41
+ Scene Viewer). Quick Look renders the USDZ as exported: ship one USDZ per
42
+ variant and switch `iosSrc` with the variant, or accept the default look.
43
+
44
+ ## Hotspots
45
+
46
+ `ProductHotspotData = { id, position: [x, y, z], normal: [x, y, z], label?, description? }`
47
+ in model metres; serializable and validated (`parseHotspots`,
48
+ `serializeHotspots`). `<ProductHotspot>` renders a slotted callout model-viewer
49
+ keeps over the surface as the camera orbits, fading it when the surface
50
+ faces away.
51
+
52
+ `useProductHotspots(element, { initial, onChange })` is the authoring and
53
+ runtime state: `placeFromPointer(clientX, clientY, label)` raycasts the
54
+ click onto the model (`positionAndNormalFromPoint`), `add` / `update` /
55
+ `remove` / `clear`, `json` / `importJson`. The debug-ui
56
+ `<ProductHotspotPanel>` wraps it with click-to-place, label editing and a
57
+ JSON copy, so authored data goes back into the app as `initial`.
58
+
59
+ ## Dimensions
60
+
61
+ `useProductDimensions(element)` returns the bounding box (`getDimensions`,
62
+ `getBoundingBoxCenter`) after load. `<ProductDimensions>` labels width,
63
+ height and depth on three box edges, anchored as hotspots (`dimensionLines`
64
+ computes the edges); `unit` is `metric` (cm under 1 m) or `imperial`.
65
+ Pair with `scale="fixed"` on the viewer so AR places the model at that
66
+ size with no pinch-to-resize.
67
+
68
+ ## Manual validation
69
+
70
+ Run `/product-details` (model-viewer's variants shoe):
71
+
72
+ | Case | Steps | Expect |
73
+ | ---------- | ----------------------------------- | -------------------------------------------------------------- |
74
+ | Variants | tap the colourway buttons | the shoe changes material; the panel select follows |
75
+ | Hotspots | "place hotspot", click the shoe | a labelled dot stays on that point while orbiting |
76
+ | Authoring | edit the label, "copy JSON", reload | label persists; JSON matches the panel |
77
+ | Dimensions | toggle, switch unit | W / H / D labels on the box edges, cm ↔ in |
78
+ | AR (phone) | "View in your space" | true-size placement; variant carried into Scene Viewer / WebXR |
@@ -270,6 +270,7 @@ type SceneObjectRendererProps = {
270
270
  renderLayer?: number;
271
271
  onComponentChange?: (component: SceneComponent) => void;
272
272
  fallback?: (message: string) => ReactNode;
273
+ faceMeshMaterialOverrides?: Record<string, FaceMeshMaterialOverride>;
273
274
  };
274
275
  ```
275
276
 
@@ -290,6 +291,10 @@ Nested scene objects are resolved from `SceneObjectStore` data. Root objects
290
291
  come from `createRenderPlan`; children are rendered recursively inside their
291
292
  parent runtime object, so child transforms inherit parent transforms.
292
293
 
294
+ `faceMeshMaterialOverrides` is runtime-only and keyed by a FaceMeshRenderer
295
+ component ID. Use it to provide a custom material node or factory without
296
+ putting a non-serializable Three.js object in `SceneObjectStore`.
297
+
293
298
  ## `ScreenImage` architecture and ownership
294
299
 
295
300
  `ScreenImage` is a thin screen-space orchestrator. Its implementation is split
@@ -45,7 +45,9 @@ GIF URLs are supported directly:
45
45
  Sources are `{ kind: "default" }`, `{ kind: "image", src }`,
46
46
  `{ kind: "gif", src }`, or an existing `{ kind: "texture", texture }`.
47
47
  `fit` supports `stretch`, `contain`, and `cover`. Canvas coordinates use center
48
- origin and positive Y upward.
48
+ origin and positive Y upward. The canvas stretches with the full rendered
49
+ viewport; use `fit="contain"` or `fit="cover"` when artwork itself needs a
50
+ preserved aspect ratio. When `size` is omitted, it fills the supplied canvas.
49
51
 
50
52
  `ScreenImage` owns GIF decoding. It preserves the GIF canvas size, frame delays,
51
53
  partial-frame compositing, and disposal rules before fitting the resulting
@@ -7,15 +7,46 @@ icon: "ruler"
7
7
 
8
8
  Import screen-coordinate helpers from `@vincentt-xr/sdk`. SDK screen pixels use
9
9
  a `720x1280` design canvas by default, center origin, and positive Y upward.
10
- They are not Three.js world units.
10
+ They are not Three.js world units. For the feed (landmark) space, the
11
+ source-media crop, mirroring and world projection, see
12
+ [Coordinate Spaces](/api/coordinate-spaces); this page covers the design-pixel
13
+ helpers.
14
+
15
+ ## Coordinate contract
16
+
17
+ Choose the helper from the coordinate system that produced the point. A helper
18
+ performs the origin and axis conversion once; do not chain another manual flip,
19
+ mirror, cover crop, or design-canvas conversion after it.
20
+
21
+ | Input | Origin and axes | Conversion to SDK design pixels |
22
+ | ---------------------- | ----------------------------- | ------------------------------------------------------------- |
23
+ | DOM client point | viewport top-left; Y down | `clientToScreenPixels` |
24
+ | normalized image point | top-left; X/Y in `[0, 1]` | `normalizedToScreenPixels` |
25
+ | media pixel | source-media top-left; Y down | `mediaToScreenPixels` with the active layout and mirror state |
26
+ | SDK tracker point | center; X right, Y up | `trackerPointToScreenPixels` |
27
+ | Three.js world point | world space | `worldToScreenPixels` |
28
+
29
+ Raw MediaPipe face landmarks are normalized image points, while low-level hand,
30
+ gesture, and body reads exposed by the SDK are tracker points. Face renderers
31
+ consume their raw landmarks internally; use `TrackingAnchor` for a 3D face
32
+ attachment instead of converting a face landmark into a screen point yourself.
33
+
34
+ The design canvas maps across the full rendered viewport on each axis for
35
+ screen-pixel helpers and canvas-based primitives. It does not letterbox itself
36
+ on landscape or square viewports. `ScreenTransform2DSettings`, used by serialized
37
+ editor layouts, is contain-fitted to preserve an authored text or shape aspect
38
+ ratio. Use content fitting inside a screen rect when artwork needs preserved
39
+ aspect ratio.
11
40
 
12
41
  ```ts
13
42
  import {
14
43
  clientToScreenPixels,
15
44
  normalizedToScreenPixels,
45
+ screenPixelsToScreenSpace,
16
46
  screenPixelsToUnits,
17
47
  screenPointToRay,
18
48
  screenUnitsToPixels,
49
+ trackerPointToScreenPixels,
19
50
  worldToScreenPixels,
20
51
  } from "@vincentt-xr/sdk";
21
52
 
@@ -30,6 +61,9 @@ const ray = screenPointToRay({ point: tapPoint, camera, viewportSize });
30
61
  - `mediaToScreenPixels` / `screenPixelsToMedia`: cover/contain layout and mirroring
31
62
  - `worldToScreenPixels`: labels and indicators over tracked 3D points
32
63
  - `screenPointToRay`: hit testing and 3D placement from design pixels
64
+ - `trackerPointToScreenPixels`: center-origin, Y-up hand/gesture/body tracker points
65
+ - `screenPixelsToScreenSpace` / `screenSpaceToScreenPixels`: map a design point to a
66
+ `ScreenSpaceUI` parent's non-uniform world units; use these for custom R3F meshes
33
67
  - `smoothNumber`, `smoothVector2`, `smoothVector3`: frame-rate-independent tracked motion
34
68
 
35
69
  Hide world projections whose `depth` is outside `[-1, 1]`. Reuse optional output