@vincentt-xr/sdk 2.0.0-alpha.0

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 (159) hide show
  1. package/GROUNDING.md +537 -0
  2. package/LICENSE +7 -0
  3. package/README.md +85 -0
  4. package/dist/DebuggerUI-CEfdddkZ.cjs +1 -0
  5. package/dist/DebuggerUI-Dgop6Q6o.js +269 -0
  6. package/dist/audio/AudioEngine.d.ts +98 -0
  7. package/dist/audio/index.d.ts +34 -0
  8. package/dist/components/AspectRatioContainer/index.d.ts +10 -0
  9. package/dist/components/BodyTracker/index.d.ts +15 -0
  10. package/dist/components/Debugger/DebuggerProvider.d.ts +22 -0
  11. package/dist/components/Debugger/DebuggerUI.d.ts +13 -0
  12. package/dist/components/Debugger/index.d.ts +38 -0
  13. package/dist/components/Debugger/styles.d.ts +19 -0
  14. package/dist/components/FaceMesh/FaceMesh.d.ts +26 -0
  15. package/dist/components/FaceMesh/canonical/CanonicalFaceMeshRenderer.d.ts +34 -0
  16. package/dist/components/FaceMesh/canonical/parseCanonicalFaceModel.d.ts +16 -0
  17. package/dist/components/FaceMesh/canonical/useCanonicalFaceMesh.d.ts +15 -0
  18. package/dist/components/FaceMesh/deform.d.ts +36 -0
  19. package/dist/components/FaceMesh/index.d.ts +6 -0
  20. package/dist/components/FaceMesh/indices.d.ts +1 -0
  21. package/dist/components/FaceMesh/material/blendModes.d.ts +2 -0
  22. package/dist/components/FaceMesh/material/defaults.d.ts +3 -0
  23. package/dist/components/FaceMesh/material/index.d.ts +5 -0
  24. package/dist/components/FaceMesh/material/options.d.ts +25 -0
  25. package/dist/components/FaceMesh/material/support.d.ts +3 -0
  26. package/dist/components/FaceMesh/material/types.d.ts +23 -0
  27. package/dist/components/FaceMesh/uv.d.ts +1 -0
  28. package/dist/components/FaceRetouch/Background.d.ts +16 -0
  29. package/dist/components/FaceRetouch/index.d.ts +6 -0
  30. package/dist/components/FaceRetouch/landmarkMask.d.ts +23 -0
  31. package/dist/components/FaceRetouch/retouchRegions.d.ts +23 -0
  32. package/dist/components/FaceRetouch/useFaceRetouchMask.d.ts +14 -0
  33. package/dist/components/FaceRetouch/useFaceRetouchRegions.d.ts +12 -0
  34. package/dist/components/FaceTracker/BoundingBoxOverlay.d.ts +20 -0
  35. package/dist/components/FaceTracker/index.d.ts +54 -0
  36. package/dist/components/GestureTracker/index.d.ts +31 -0
  37. package/dist/components/GestureTrigger/index.d.ts +3 -0
  38. package/dist/components/HandTracker/index.d.ts +24 -0
  39. package/dist/components/HeadBinding/index.d.ts +31 -0
  40. package/dist/components/HeadBindingOccluder/index.d.ts +36 -0
  41. package/dist/components/HeadOccluder/index.d.ts +13 -0
  42. package/dist/components/Panel/index.d.ts +71 -0
  43. package/dist/components/PerspectiveCamera/index.d.ts +14 -0
  44. package/dist/components/ScreenSpaceUI/ScreenTransform.d.ts +54 -0
  45. package/dist/components/ScreenSpaceUI/context.d.ts +29 -0
  46. package/dist/components/ScreenSpaceUI/index.d.ts +40 -0
  47. package/dist/components/Segmentation/index.d.ts +44 -0
  48. package/dist/components/SpriteAnimation/index.d.ts +52 -0
  49. package/dist/components/TextLabel/index.d.ts +62 -0
  50. package/dist/components/TrackingAnchor/index.d.ts +74 -0
  51. package/dist/components/VideoBackground/index.d.ts +38 -0
  52. package/dist/components/XRCompositor/index.d.ts +5 -0
  53. package/dist/components/XRDebugView/index.d.ts +6 -0
  54. package/dist/components/XRLayer/index.d.ts +6 -0
  55. package/dist/components/XRMediaSurface/index.d.ts +27 -0
  56. package/dist/components/XRProvider/index.d.ts +13 -0
  57. package/dist/components/XRScene/defaults.d.ts +4 -0
  58. package/dist/components/XRScene/index.d.ts +15 -0
  59. package/dist/components/XRViewport/index.d.ts +101 -0
  60. package/dist/components/index.d.ts +26 -0
  61. package/dist/config/index.d.ts +8 -0
  62. package/dist/constants/index.d.ts +25 -0
  63. package/dist/constants/renderLayers.d.ts +25 -0
  64. package/dist/entries/all.d.ts +15 -0
  65. package/dist/entries/core.d.ts +21 -0
  66. package/dist/entries/face-effects.d.ts +19 -0
  67. package/dist/entries/internal.d.ts +16 -0
  68. package/dist/entries/low-level.d.ts +30 -0
  69. package/dist/entries/tracking.d.ts +15 -0
  70. package/dist/face-effects.cjs +252 -0
  71. package/dist/face-effects.js +3542 -0
  72. package/dist/hasFullUnitUVBounds-BGvcFsQM.js +26 -0
  73. package/dist/hasFullUnitUVBounds-p1Dl0ejX.cjs +1 -0
  74. package/dist/hooks/faceResults.d.ts +39 -0
  75. package/dist/hooks/selectors.d.ts +33 -0
  76. package/dist/hooks/useCalibrationFaceTracker.d.ts +23 -0
  77. package/dist/hooks/useDebugger/index.d.ts +25 -0
  78. package/dist/hooks/useDebugger/types.d.ts +20 -0
  79. package/dist/hooks/useFaceBoundingBox.d.ts +11 -0
  80. package/dist/hooks/useFaceResults.d.ts +31 -0
  81. package/dist/hooks/useGestureEdge.d.ts +53 -0
  82. package/dist/hooks/useMediaRecorder.d.ts +40 -0
  83. package/dist/hooks/useTrackerContext.d.ts +35 -0
  84. package/dist/hooks/useXRModel.d.ts +15 -0
  85. package/dist/hooks/useXRSession/constants.d.ts +4 -0
  86. package/dist/hooks/useXRSession/facade.d.ts +39 -0
  87. package/dist/hooks/useXRSession/fns.d.ts +5 -0
  88. package/dist/hooks/useXRSession/index.d.ts +4 -0
  89. package/dist/hooks/useXRSession/registry.d.ts +52 -0
  90. package/dist/hooks/useXRSession/store.d.ts +40 -0
  91. package/dist/index-B5J4gzn2.cjs +1 -0
  92. package/dist/index-BD-JWMjE.js +1991 -0
  93. package/dist/index-BVBVg96G.cjs +1 -0
  94. package/dist/index-CYMj26Ej.js +1331 -0
  95. package/dist/index-DtwHElmG.js +157 -0
  96. package/dist/index-PtFmD6B8.cjs +1 -0
  97. package/dist/internal-DH5d1Xnq.cjs +1 -0
  98. package/dist/internal-KOKJDxCG.js +519 -0
  99. package/dist/internal.cjs +1 -0
  100. package/dist/internal.js +14 -0
  101. package/dist/low-level.cjs +1 -0
  102. package/dist/low-level.js +941 -0
  103. package/dist/main.cjs +1 -0
  104. package/dist/main.js +612 -0
  105. package/dist/models/Base/index.d.ts +25 -0
  106. package/dist/models/Body/index.d.ts +22 -0
  107. package/dist/models/Face/constants.d.ts +2 -0
  108. package/dist/models/Face/index.d.ts +45 -0
  109. package/dist/models/Face/landmarksData.d.ts +2 -0
  110. package/dist/models/Hand/GestureTracking.d.ts +21 -0
  111. package/dist/models/Hand/HandTracking.d.ts +21 -0
  112. package/dist/models/Segmentation/index.d.ts +75 -0
  113. package/dist/models/index.d.ts +5 -0
  114. package/dist/modules/XRClient/index.d.ts +9 -0
  115. package/dist/modules/index.d.ts +1 -0
  116. package/dist/selectors-BHO6TnoC.js +59 -0
  117. package/dist/selectors-Bub0o3qy.cjs +1 -0
  118. package/dist/services/ApiService.d.ts +12 -0
  119. package/dist/services/MetricsCollector/CameraFPSTracker.d.ts +25 -0
  120. package/dist/services/MetricsCollector/MemoryTracker.d.ts +43 -0
  121. package/dist/services/MetricsCollector/RenderTimeTracker.d.ts +10 -0
  122. package/dist/services/MetricsCollector/ResolutionTracker.d.ts +26 -0
  123. package/dist/services/MetricsCollector/index.d.ts +59 -0
  124. package/dist/services/MetricsCollector/types.d.ts +47 -0
  125. package/dist/services/MetricsCollector/utils.d.ts +65 -0
  126. package/dist/texture/computeContainScale.d.ts +25 -0
  127. package/dist/texture/hasFullUnitUVBounds.d.ts +37 -0
  128. package/dist/texture/types.d.ts +20 -0
  129. package/dist/tracking.cjs +1 -0
  130. package/dist/tracking.js +344 -0
  131. package/dist/types/body.d.ts +3 -0
  132. package/dist/types/client.d.ts +3 -0
  133. package/dist/types/common.d.ts +9 -0
  134. package/dist/types/compositor.d.ts +23 -0
  135. package/dist/types/debugger.d.ts +83 -0
  136. package/dist/types/face.d.ts +67 -0
  137. package/dist/types/gesture-tracking.d.ts +15 -0
  138. package/dist/types/hand-tracking.d.ts +5 -0
  139. package/dist/types/headBinding.d.ts +67 -0
  140. package/dist/types/index.d.ts +12 -0
  141. package/dist/types/logger.d.ts +4 -0
  142. package/dist/types/model.d.ts +46 -0
  143. package/dist/types/nodes.d.ts +35 -0
  144. package/dist/types/segmentation.d.ts +31 -0
  145. package/dist/types/session.d.ts +102 -0
  146. package/dist/types/tracking.d.ts +30 -0
  147. package/dist/types/util-types.d.ts +1 -0
  148. package/dist/useFaceResults-cpsSmKKc.js +81 -0
  149. package/dist/useFaceResults-hVUJYSEY.cjs +1 -0
  150. package/dist/utils/calibrationDebug.d.ts +9 -0
  151. package/dist/utils/dataURL.d.ts +6 -0
  152. package/dist/utils/drawMediaCover.d.ts +18 -0
  153. package/dist/utils/errors.d.ts +3 -0
  154. package/dist/utils/fns.d.ts +48 -0
  155. package/dist/utils/headBinding.d.ts +85 -0
  156. package/dist/utils/integrity.d.ts +43 -0
  157. package/dist/utils/landmarkBounds.d.ts +11 -0
  158. package/dist/utils/logger.d.ts +7 -0
  159. package/package.json +113 -0
@@ -0,0 +1,35 @@
1
+ import { FaceLandmarkerResult } from '@mediapipe/tasks-vision';
2
+ import { Texture } from 'three';
3
+ import { Coordinate } from './common';
4
+ import { Face, FaceRawTrackingData } from './face';
5
+ import { GestureData } from './gesture-tracking';
6
+ import { HandData } from './hand-tracking';
7
+ import { SegmentationData } from './segmentation';
8
+ export type XRModelNodeSegmentation = {
9
+ backgroundSegmentationMask?: Texture;
10
+ getMaskCanvas: () => HTMLCanvasElement | null;
11
+ getRawSegmentationData: () => SegmentationData | null;
12
+ /**
13
+ * Supply a high-resolution input source (raw <video>/<img>) so segmentation
14
+ * runs on native-resolution, uncropped frames instead of the small display-fit
15
+ * session feed. Pass `null`/`undefined` to revert to the shared feed canvas.
16
+ */
17
+ setInputSource: (source: CanvasImageSource | null | undefined) => void;
18
+ };
19
+ export type XRModelNodeBodyTracking = {
20
+ getLandmarks2D: (bodyIndex?: number) => Coordinate[] | undefined;
21
+ };
22
+ export type XRModelNodeHandTracking = {
23
+ coordinates?: HandData["coordinates"];
24
+ gesture?: HandData["gesture"];
25
+ };
26
+ export type XRModelNodeGestureTracking = {
27
+ coordinates?: GestureData["coordinates"];
28
+ gesture?: GestureData["gesture"];
29
+ };
30
+ export type XRModelNodeFaceTracking = {
31
+ detectionResults?: FaceLandmarkerResult;
32
+ getDetectionResults: () => FaceLandmarkerResult | undefined;
33
+ getFace: (faceIndex?: number) => Face | undefined;
34
+ getRawFaceTrackingData: (faceIndex?: number) => FaceRawTrackingData | undefined;
35
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Supported segmentation pipeline types.
3
+ *
4
+ * Each value corresponds to a segmentation pipeline that can be enabled
5
+ * declaratively via the `<Segmentation type="…">` component. The runtime
6
+ * resolves the type to the corresponding xr-sdk segmentation pipeline.
7
+ *
8
+ * v1 supports `portrait` only. New types can be added without a breaking
9
+ * change as long as the existing values remain stable.
10
+ */
11
+ export type SegmentationType = "portrait";
12
+ /**
13
+ * Runtime list of supported segmentation types. Used by the editor UI's
14
+ * type picker and by the design agent's approved-types validator.
15
+ */
16
+ export declare const SEGMENTATION_TYPES: readonly ["portrait"];
17
+ export type SegmentationData = {
18
+ maskData: Uint8Array | null;
19
+ width: number;
20
+ height: number;
21
+ };
22
+ export type SegmentationQuality = "low" | "medium" | "high";
23
+ export type SegmentationModelOptions = {
24
+ /**
25
+ * Quality vs performance tradeoff for segmentation processing
26
+ * - 'low': 0.5x resolution (fast, lower quality) - default
27
+ * - 'medium': 0.75x resolution (balanced)
28
+ * - 'high': 1.0x resolution (best quality, slower)
29
+ */
30
+ quality?: SegmentationQuality;
31
+ };
@@ -0,0 +1,102 @@
1
+ import { XRMediaSource, XRModel } from '../constants';
2
+ import { XRMediaSourceKind } from './compositor';
3
+ import { BodyModelOptions } from './body';
4
+ import { FaceModelOptions } from './face';
5
+ import { SegmentationModelOptions } from './segmentation';
6
+ export declare enum SessionStatus {
7
+ ACTIVE = "ACTIVE",
8
+ DISABLED = "DISABLED"
9
+ }
10
+ export type Session = {
11
+ id: string;
12
+ status: SessionStatus;
13
+ networkId: string;
14
+ projectId: string;
15
+ createdAt: Date;
16
+ updatedAt: Date;
17
+ };
18
+ type SetMediaSourceWebcamParams = {
19
+ source: XRMediaSource.WEBCAM;
20
+ sourceKind?: "webcam";
21
+ };
22
+ type SetMediaSourceStreamParams = {
23
+ source: XRMediaSource.STREAM;
24
+ stream: MediaStream;
25
+ /**
26
+ * Mark the stream as static (e.g. a still image rendered to a captureStream).
27
+ * The session paints one frame, runs each model once to produce a detection
28
+ * snapshot, then pauses model RAF loops + the blit loop. Subsequent
29
+ * `setMediaSource` calls with a new static stream repeat the pulse.
30
+ */
31
+ static?: boolean;
32
+ sourceKind?: "webcam" | XRMediaSourceKind;
33
+ };
34
+ export type SetMediaSourceParams = SetMediaSourceWebcamParams | SetMediaSourceStreamParams;
35
+ export type SetFrameSourceParams = {
36
+ canvas: HTMLCanvasElement | OffscreenCanvas;
37
+ sourceKind?: "webcam" | XRMediaSourceKind;
38
+ };
39
+ export type RegisterXRPipelineParams = Array<{
40
+ model: XRModel;
41
+ } | {
42
+ model: XRModel.BODY_TRACKER;
43
+ options?: BodyModelOptions;
44
+ } | {
45
+ model: XRModel.FACE_TRACKER;
46
+ options?: FaceModelOptions;
47
+ } | {
48
+ model: XRModel.BACKGROUND_SEGMENTATION;
49
+ options?: SegmentationModelOptions;
50
+ }>;
51
+ /**
52
+ * Public XR session surface. Returned by `useXRContext().session` and used
53
+ * by tracker components, the editor preview, and example apps.
54
+ *
55
+ * The reference is STABLE for the entire lifetime of the enclosing
56
+ * `XRProvider` — internal state changes do NOT cause `session` to take a
57
+ * new identity. Mutable state is exposed via selector hooks
58
+ * (`useXRReady`, `useXRError`, `useXRCameraTexture`, `useXRModelNode`,
59
+ * `useXRPipelineSnapshot`) so consumers only re-render on the slice they
60
+ * care about.
61
+ *
62
+ * Lifecycle:
63
+ *
64
+ * idle ──setMediaSource──> media-bound ──start──> running ──stop──> stopped
65
+ * ↑ │
66
+ * └────────────reset──────────────────────┘
67
+ */
68
+ export type XRSession = {
69
+ setMediaSource: (params: SetMediaSourceParams) => Promise<void>;
70
+ setFrameSource: (params: SetFrameSourceParams) => Promise<void>;
71
+ /**
72
+ * Idempotent. Calling `start()` from the `running` state is a no-op.
73
+ * Throws if called from `idle` (no media source bound).
74
+ */
75
+ start: () => Promise<void>;
76
+ /**
77
+ * Halts the per-frame run loop and releases the input video / tracks.
78
+ * Preserves the model registry — a subsequent `setMediaSource` + `start`
79
+ * resumes with the same models.
80
+ */
81
+ stop: () => Promise<void>;
82
+ /**
83
+ * `stop()` + clear the model registry. Returns the session to `idle`.
84
+ */
85
+ reset: () => Promise<void>;
86
+ /**
87
+ * @deprecated Prefer `useXRModel(modelName, options)`. Kept as an
88
+ * additive shim for backwards compatibility — multiple calls are
89
+ * idempotent and never evict previously registered models.
90
+ */
91
+ registerXRPipeline: (params: RegisterXRPipelineParams) => Promise<void>;
92
+ getModelNode: <T extends Record<string, unknown>>(modelCode: XRModel) => T;
93
+ /**
94
+ * Synchronously render the current scene and read the canvas back as a PNG
95
+ * dataURL. Use from a gesture or state callback (e.g. on capture trigger)
96
+ * to grab the visible frame as a photo. Returns the dataURL string.
97
+ *
98
+ * Throws `XRError` if called before `<XRScene>` has mounted.
99
+ */
100
+ captureFrame: () => string;
101
+ };
102
+ export {};
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Tracking target enumerations.
3
+ *
4
+ * A `TrackingAnchor`'s `target` prop is a typed string in the form
5
+ * `<tracker>.<landmark>`. The namespace prefix (`face.`, `hand.`, `body.`)
6
+ * is used by the renderer and the editor to validate that an anchor's
7
+ * target matches the type of its nearest ancestor tracker.
8
+ *
9
+ * The lists below are the v1 enumeration. New entries can be added
10
+ * non-breakingly as additional landmarks/joints become supported.
11
+ */
12
+ export type FaceTarget = "face.forehead" | "face.noseTip" | "face.chin" | "face.leftEyeOuter" | "face.rightEyeOuter" | "face.leftCheek" | "face.rightCheek" | "face.mouthCenter";
13
+ export type HandTarget = "hand.wrist" | "hand.thumbTip" | "hand.indexTip" | "hand.middleTip" | "hand.ringTip" | "hand.pinkyTip" | "hand.indexBase" | "hand.middleBase" | "hand.ringBase" | "hand.pinkyBase";
14
+ export type BodyTarget = "body.head" | "body.neck" | "body.leftShoulder" | "body.rightShoulder" | "body.leftElbow" | "body.rightElbow" | "body.leftWrist" | "body.rightWrist" | "body.leftHip" | "body.rightHip" | "body.leftKnee" | "body.rightKnee" | "body.leftAnkle" | "body.rightAnkle";
15
+ /**
16
+ * Combined tracking target type used by the `TrackingAnchor` component.
17
+ */
18
+ export type TrackingTarget = FaceTarget | HandTarget | BodyTarget;
19
+ /**
20
+ * Tracker type identifier exposed via tracker context.
21
+ */
22
+ export type TrackerType = "face" | "hand" | "body";
23
+ export declare const FACE_TARGETS: readonly ["face.forehead", "face.noseTip", "face.chin", "face.leftEyeOuter", "face.rightEyeOuter", "face.leftCheek", "face.rightCheek", "face.mouthCenter"];
24
+ export declare const HAND_TARGETS: readonly ["hand.wrist", "hand.thumbTip", "hand.indexTip", "hand.middleTip", "hand.ringTip", "hand.pinkyTip", "hand.indexBase", "hand.middleBase", "hand.ringBase", "hand.pinkyBase"];
25
+ export declare const BODY_TARGETS: readonly ["body.head", "body.neck", "body.leftShoulder", "body.rightShoulder", "body.leftElbow", "body.rightElbow", "body.leftWrist", "body.rightWrist", "body.leftHip", "body.rightHip", "body.leftKnee", "body.rightKnee", "body.leftAnkle", "body.rightAnkle"];
26
+ /**
27
+ * Returns the tracker namespace for a given target string, or `null` if
28
+ * the target does not match any known prefix.
29
+ */
30
+ export declare const getTargetNamespace: (target: string) => TrackerType | null;
@@ -0,0 +1 @@
1
+ export type Class<T> = new (...args: any[]) => T;
@@ -0,0 +1,81 @@
1
+ import { useState as x, useEffect as g } from "react";
2
+ const E = (t) => {
3
+ if (t.length === 0)
4
+ return { minX: 0, minY: 0, maxX: 0, maxY: 0, width: 0, height: 0, centerX: 0, centerY: 0 };
5
+ let n = 1, e = 1, s = 0, a = 0;
6
+ for (let o = 0; o < t.length; o++) {
7
+ const { x: l, y: c } = t[o];
8
+ l < n && (n = l), c < e && (e = c), l > s && (s = l), c > a && (a = c);
9
+ }
10
+ const i = Math.max(0, s - n), h = Math.max(0, a - e);
11
+ return {
12
+ minX: n,
13
+ minY: e,
14
+ maxX: s,
15
+ maxY: a,
16
+ width: i,
17
+ height: h,
18
+ centerX: n + i * 0.5,
19
+ centerY: e + h * 0.5
20
+ };
21
+ }, N = (t, n) => t.map((e) => ({
22
+ landmarks: e,
23
+ bounds: E(e)
24
+ })), P = {
25
+ publishedCount: 0,
26
+ // -Infinity, not 0: the first non-empty frame must publish regardless of its
27
+ // timestamp (a frame at now=0 would otherwise be throttled against 0).
28
+ lastPublishAt: -1 / 0,
29
+ lastNonEmptyAt: -1
30
+ }, X = (t, n, e, s, a) => {
31
+ if (n > 0) {
32
+ const i = { ...t, lastNonEmptyAt: e };
33
+ return e - t.lastPublishAt >= s ? {
34
+ action: "publish",
35
+ state: { publishedCount: n, lastPublishAt: e, lastNonEmptyAt: e }
36
+ } : { action: "none", state: i };
37
+ }
38
+ return t.publishedCount === 0 ? { action: "none", state: t } : t.lastNonEmptyAt >= 0 && e - t.lastNonEmptyAt < a ? { action: "none", state: t } : {
39
+ action: "clear",
40
+ // Reset the throttle anchor so the next reacquired face publishes at once.
41
+ state: { publishedCount: 0, lastPublishAt: -1 / 0, lastNonEmptyAt: t.lastNonEmptyAt }
42
+ };
43
+ }, R = ({
44
+ node: t,
45
+ enabled: n,
46
+ selectedSource: e,
47
+ throttleMs: s = 16,
48
+ holdMs: a = 250,
49
+ onFrame: i
50
+ }) => {
51
+ const [h, o] = x([]);
52
+ return g(() => {
53
+ if (!n || !t) {
54
+ o((u) => u.length === 0 ? u : []);
55
+ return;
56
+ }
57
+ let l = 0, c, m = P;
58
+ const p = (u, r, f) => {
59
+ const { action: A, state: y } = X(m, u, r, s, a);
60
+ m = y, A === "publish" ? o(f) : A === "clear" && o((d) => d.length === 0 ? d : []);
61
+ }, b = () => {
62
+ l = requestAnimationFrame(b), i == null || i();
63
+ const u = performance.now(), r = t.getDetectionResults();
64
+ if (!r) {
65
+ p(0, u, []), c = r;
66
+ return;
67
+ }
68
+ if (r === c) return;
69
+ c = r;
70
+ const f = N(
71
+ r.faceLandmarks ?? []
72
+ );
73
+ p(f.length, u, f);
74
+ };
75
+ return l = requestAnimationFrame(b), () => cancelAnimationFrame(l);
76
+ }, [n, t, e, s, i]), h;
77
+ };
78
+ export {
79
+ E as c,
80
+ R as u
81
+ };
@@ -0,0 +1 @@
1
+ "use strict";const y=require("react"),x=t=>{if(t.length===0)return{minX:0,minY:0,maxX:0,maxY:0,width:0,height:0,centerX:0,centerY:0};let n=1,e=1,s=0,a=0;for(let u=0;u<t.length;u++){const{x:c,y:l}=t[u];c<n&&(n=c),l<e&&(e=l),c>s&&(s=c),l>a&&(a=l)}const i=Math.max(0,s-n),h=Math.max(0,a-e);return{minX:n,minY:e,maxX:s,maxY:a,width:i,height:h,centerX:n+i*.5,centerY:e+h*.5}},E=(t,n)=>t.map(e=>({landmarks:e,bounds:x(e)})),N={publishedCount:0,lastPublishAt:-1/0,lastNonEmptyAt:-1},R=(t,n,e,s,a)=>{if(n>0){const i={...t,lastNonEmptyAt:e};return e-t.lastPublishAt>=s?{action:"publish",state:{publishedCount:n,lastPublishAt:e,lastNonEmptyAt:e}}:{action:"none",state:i}}return t.publishedCount===0?{action:"none",state:t}:t.lastNonEmptyAt>=0&&e-t.lastNonEmptyAt<a?{action:"none",state:t}:{action:"clear",state:{publishedCount:0,lastPublishAt:-1/0,lastNonEmptyAt:t.lastNonEmptyAt}}},P=({node:t,enabled:n,selectedSource:e,throttleMs:s=16,holdMs:a=250,onFrame:i})=>{const[h,u]=y.useState([]);return y.useEffect(()=>{if(!n||!t){u(o=>o.length===0?o:[]);return}let c=0,l,f=N;const p=(o,r,m)=>{const{action:A,state:g}=R(f,o,r,s,a);f=g,A==="publish"?u(m):A==="clear"&&u(d=>d.length===0?d:[])},b=()=>{c=requestAnimationFrame(b),i==null||i();const o=performance.now(),r=t.getDetectionResults();if(!r){p(0,o,[]),l=r;return}if(r===l)return;l=r;const m=E(r.faceLandmarks??[]);p(m.length,o,m)};return c=requestAnimationFrame(b),()=>cancelAnimationFrame(c)},[n,t,e,s,i]),h};exports.computeLandmarkBounds=x;exports.useFaceResults=P;
@@ -0,0 +1,9 @@
1
+ type CalibrationDebugEvent = {
2
+ label: string;
3
+ payload: Record<string, unknown>;
4
+ source: string;
5
+ ts: number;
6
+ };
7
+ export declare function recordCalibrationDebugEvent(source: string, label: string, payload: Record<string, unknown>): void;
8
+ export declare function readCalibrationDebugEvents(limit?: number): CalibrationDebugEvent[];
9
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Convert a `data:` URL (e.g. a captured canvas frame) into a `File`, ready to
3
+ * upload or hand to the Web Share API. Parses the MIME from the URL header and
4
+ * base64-decodes the payload.
5
+ */
6
+ export declare const dataURLtoFile: (dataUrl: string, filename: string) => File;
@@ -0,0 +1,18 @@
1
+ import { XRMediaLayout } from '../types/compositor';
2
+ /**
3
+ * Blit an image/video onto a 2D canvas using a precomputed cover/contain
4
+ * layout, optionally mirrored. Resizes the canvas to the layout's
5
+ * destination size in place. The canvas is write-only (no getImageData),
6
+ * so it stays GPU-uploadable.
7
+ *
8
+ * `filter` applies a CSS canvas filter (e.g. brightness boost for a dark
9
+ * webcam feed) only for the duration of the draw, then resets. Off by
10
+ * default — opt in per call site.
11
+ */
12
+ export declare const drawMediaCover: ({ canvas, layout, media, mirrored, filter, }: {
13
+ canvas: HTMLCanvasElement;
14
+ layout: XRMediaLayout;
15
+ media: HTMLImageElement | HTMLVideoElement;
16
+ mirrored: boolean;
17
+ filter?: string;
18
+ }) => void;
@@ -0,0 +1,3 @@
1
+ export declare class XRError extends Error {
2
+ constructor(message: string);
3
+ }
@@ -0,0 +1,48 @@
1
+ import { NormalizedLandmark } from '@mediapipe/tasks-vision';
2
+ import * as THREE from "three";
3
+ export declare const rangeMinusOneToPositiveOne: (number: number) => number;
4
+ export declare const rangeZeroToOne: (number: number) => number;
5
+ export declare const rotationMatrixToEulerAngles: (facialTransformationMatrix: Array<number>) => {
6
+ x: number;
7
+ y: number;
8
+ z: number;
9
+ };
10
+ export declare const drawImageDataCover: (canvas: HTMLCanvasElement, input: ImageData | HTMLVideoElement) => void;
11
+ /**
12
+ * In-place variant of unprojectToZZero. Writes the world-space point into
13
+ * `out` and returns it; allocates nothing on the hot path. The
14
+ * trackers call this once per landmark per frame (~468× for face), so the
15
+ * allocating variant below shows up as a real GC pressure source.
16
+ */
17
+ export declare const unprojectToZZeroInto: ({ x, y, z }: NormalizedLandmark, camera: THREE.Camera, out: THREE.Vector3) => THREE.Vector3;
18
+ /**
19
+ * Allocating wrapper kept for backwards compatibility with any
20
+ * consumer-facing API surface. New call sites in the trackers use
21
+ * unprojectToZZeroInto with a pre-allocated out vector.
22
+ */
23
+ export declare const unprojectToZZero: (landmark: NormalizedLandmark, camera: THREE.Camera) => THREE.Vector3;
24
+ export declare const smoothScale: (originalScale: number, newScale: number, alpha?: number) => number;
25
+ /**
26
+ * Downscales the shared source canvas into a smaller reusable analysis canvas
27
+ * before handing it to MediaPipe. Landmark models don't need the full
28
+ * render-resolution frame — analyzing fewer pixels cuts per-inference cost (and
29
+ * the GPU→CPU readback scales with it) with no accuracy loss for
30
+ * hand/gesture/pose at these sizes. The analysis canvas is allocated once and
31
+ * only reallocated when the target dimensions change, so there's no per-frame
32
+ * churn.
33
+ *
34
+ * FaceTracker inlines its own equivalent (its analysis dimension depends on
35
+ * `numFaces`); Hand/Gesture/Body share this one.
36
+ */
37
+ export declare class AnalysisDownscaler {
38
+ private readonly maxDimension;
39
+ private canvas;
40
+ constructor(maxDimension: number);
41
+ /**
42
+ * Returns a downscaled copy of `source` capped to `maxDimension` on its
43
+ * longest side (or `source` unchanged if it's already within the cap, or if a
44
+ * 2D context can't be obtained). The returned canvas is reused across calls.
45
+ */
46
+ draw(source: OffscreenCanvas | HTMLCanvasElement): OffscreenCanvas | HTMLCanvasElement;
47
+ dispose(): void;
48
+ }
@@ -0,0 +1,85 @@
1
+ import { FaceTrackingResult } from '../types/face';
2
+ import { HeadBindingAdaptiveValues, HeadBindingCubeModel, HeadBindingCubePose, HeadBindingDebugMetrics, HeadBindingSolveResult, HeadBindingShape } from '../types/headBinding';
3
+ export declare const computeUniversalOccluderScale: ({ baseScale, pitchRadians, }: {
4
+ baseScale: [number, number, number];
5
+ pitchRadians: number;
6
+ }) => [number, number, number];
7
+ export declare const computeUniversalYawOffsetX: ({ yawRadians }: {
8
+ yawRadians: number;
9
+ }) => number;
10
+ export declare const computeUniversalPitchOffsetY: ({ pitchRadians, }: {
11
+ pitchRadians: number;
12
+ }) => number;
13
+ export declare const computeUniversalHeadDepthOffset: ({ baseOffset, yawRadians, }: {
14
+ baseOffset: number;
15
+ yawRadians: number;
16
+ }) => number;
17
+ export declare const computeAdaptiveVerticalFit: ({ baseOffsetY, baseScaleY, face, }: {
18
+ baseOffsetY: number;
19
+ baseScaleY: number;
20
+ face: FaceTrackingResult | null;
21
+ }) => HeadBindingAdaptiveValues;
22
+ export declare const computeHeadBindingShape: ({ face, offsetX, offsetY, scaleX, scaleY, }: {
23
+ face: FaceTrackingResult;
24
+ offsetX: number;
25
+ offsetY: number;
26
+ scaleX: number;
27
+ scaleY: number;
28
+ }) => HeadBindingShape;
29
+ export declare const computeHeadBindingCubePose: ({ lockDepth, lockRotation, model, face, height, headDepthOffset, offsetX, offsetY, rotationOffsetX, rotationOffsetY, rotationOffsetZ, scaleX, scaleY, scaleZ, skullCenterMode, skullVerticalBias, width, }: {
30
+ lockDepth: boolean;
31
+ lockRotation: boolean;
32
+ model: HeadBindingCubeModel;
33
+ face: FaceTrackingResult;
34
+ height: number;
35
+ headDepthOffset: number;
36
+ offsetX: number;
37
+ offsetY: number;
38
+ rotationOffsetX: number;
39
+ rotationOffsetY: number;
40
+ rotationOffsetZ: number;
41
+ scaleX: number;
42
+ scaleY: number;
43
+ scaleZ: number;
44
+ skullCenterMode: boolean;
45
+ skullVerticalBias: number;
46
+ width: number;
47
+ }) => HeadBindingCubePose;
48
+ export declare const solveHeadBinding: ({ lockDepth, lockRotation, model, face, height, headDepthOffset, offsetX, offsetY, rotationOffsetX, rotationOffsetY, rotationOffsetZ, scaleX, scaleY, scaleZ, skullCenterMode, skullVerticalBias, width, }: {
49
+ lockDepth: boolean;
50
+ lockRotation: boolean;
51
+ model: HeadBindingCubeModel;
52
+ face: FaceTrackingResult;
53
+ height: number;
54
+ headDepthOffset: number;
55
+ offsetX: number;
56
+ offsetY: number;
57
+ rotationOffsetX: number;
58
+ rotationOffsetY: number;
59
+ rotationOffsetZ: number;
60
+ scaleX: number;
61
+ scaleY: number;
62
+ scaleZ: number;
63
+ skullCenterMode: boolean;
64
+ skullVerticalBias: number;
65
+ width: number;
66
+ }) => HeadBindingSolveResult;
67
+ export declare const computeHeadBindingDebugMetrics: ({ lockDepth, lockRotation, model, face, height, headDepthOffset, offsetX, offsetY, rotationOffsetX, rotationOffsetY, rotationOffsetZ, scaleX, scaleY, scaleZ, skullCenterMode, skullVerticalBias, width, }: {
68
+ lockDepth: boolean;
69
+ lockRotation: boolean;
70
+ model: HeadBindingCubeModel;
71
+ face: FaceTrackingResult;
72
+ height: number;
73
+ headDepthOffset: number;
74
+ offsetX: number;
75
+ offsetY: number;
76
+ rotationOffsetX: number;
77
+ rotationOffsetY: number;
78
+ rotationOffsetZ: number;
79
+ scaleX: number;
80
+ scaleY: number;
81
+ scaleZ: number;
82
+ skullCenterMode: boolean;
83
+ skullVerticalBias: number;
84
+ width: number;
85
+ }) => HeadBindingDebugMetrics;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Subresource-integrity verification for CDN-hosted model + mesh assets.
3
+ *
4
+ * The SDK fetches MediaPipe tracker weights, the canonical face mesh, and
5
+ * the head-occluder GLB from `cdn.vincentt.studio`. None of those are
6
+ * served with SRI script-attributes (they aren't <script> tags) and the
7
+ * S3+CloudFront setup does not sign content, so a CDN/origin compromise
8
+ * would silently backdoor ML inference and 3D models for every viewer.
9
+ *
10
+ * This module verifies fetched bytes against a hash baked into the SDK
11
+ * at publish time. The manifest is generated by
12
+ * `scripts/generate-integrity-manifest.ts` — when an asset is republished
13
+ * on the CDN, re-run the script and commit the new hash with the new SDK
14
+ * version. The hash travels with the SDK code, not with the asset.
15
+ *
16
+ * Consumers: the canonical face OBJ, MediaPipe tracker .task files
17
+ * (fed via `modelAssetBuffer`), and the head-occluder GLB (parsed with
18
+ * GLTFLoader.parse from the verified buffer). drei's `useGLTF` is
19
+ * bypassed because it owns the fetch — we need to own it to verify.
20
+ */
21
+ export type IntegrityHash = `sha384-${string}`;
22
+ /**
23
+ * Keys are URL pathnames relative to the asset origin
24
+ * (e.g. "/assets/xr/canonical_face_model.obj"). Populated by
25
+ * `scripts/generate-integrity-manifest.ts`. An empty manifest puts
26
+ * fetchAndVerify in best-effort mode — once entries are added, missing
27
+ * entries can be promoted to hard errors.
28
+ */
29
+ export declare const INTEGRITY_MANIFEST: Record<string, IntegrityHash>;
30
+ /**
31
+ * Fetches a URL and verifies its body against the manifest. Returns the
32
+ * ArrayBuffer on success. Throws if the fetch fails or the manifest hash
33
+ * doesn't match the computed SHA-384.
34
+ *
35
+ * URLs with no manifest entry pass through with a warning so the SDK
36
+ * stays usable while the manifest is being populated.
37
+ */
38
+ export declare function fetchAndVerify(url: string): Promise<ArrayBuffer>;
39
+ /**
40
+ * fetchAndVerify variant that returns a Uint8Array view — what MediaPipe's
41
+ * `modelAssetBuffer` expects.
42
+ */
43
+ export declare function fetchAndVerifyBytes(url: string): Promise<Uint8Array>;
@@ -0,0 +1,11 @@
1
+ import { NormalizedLandmark } from '@mediapipe/tasks-vision';
2
+ import { FaceBounds } from '../types/face';
3
+ /**
4
+ * Axis-aligned bounds of a set of normalized face landmarks (0..1 across
5
+ * both axes). Returns min/max plus derived width/height/center so callers
6
+ * can adapt to whichever shape they need (`FaceBounds`, `BoundingBox`, ...).
7
+ *
8
+ * Empty input yields a zero-size box at the origin rather than the inverted
9
+ * `min=1,max=0` seed, so downstream width/height stay non-negative.
10
+ */
11
+ export declare const computeLandmarkBounds: (landmarks: NormalizedLandmark[]) => FaceBounds;
@@ -0,0 +1,7 @@
1
+ import { LoggerOptions } from '../types/logger';
2
+ export declare class Logger {
3
+ private options;
4
+ constructor(opts?: Partial<LoggerOptions>);
5
+ log(...params: any[]): void;
6
+ error(...params: any[]): void;
7
+ }
package/package.json ADDED
@@ -0,0 +1,113 @@
1
+ {
2
+ "name": "@vincentt-xr/sdk",
3
+ "version": "2.0.0-alpha.0",
4
+ "description": "XR SDK by Vincentt",
5
+ "license": "UNLICENSED",
6
+ "keywords": [
7
+ "Vincentt",
8
+ "Web AR",
9
+ "XR",
10
+ "SDK"
11
+ ],
12
+ "type": "module",
13
+ "sideEffects": false,
14
+ "files": [
15
+ "/dist",
16
+ "GROUNDING.md"
17
+ ],
18
+ "main": "./dist/main.cjs",
19
+ "module": "./dist/main.js",
20
+ "types": "./dist/entries/core.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/entries/core.d.ts",
24
+ "import": "./dist/main.js",
25
+ "require": "./dist/main.cjs"
26
+ },
27
+ "./tracking": {
28
+ "types": "./dist/entries/tracking.d.ts",
29
+ "import": "./dist/tracking.js",
30
+ "require": "./dist/tracking.cjs"
31
+ },
32
+ "./face-effects": {
33
+ "types": "./dist/entries/face-effects.d.ts",
34
+ "import": "./dist/face-effects.js",
35
+ "require": "./dist/face-effects.cjs"
36
+ },
37
+ "./low-level": {
38
+ "types": "./dist/entries/low-level.d.ts",
39
+ "import": "./dist/low-level.js",
40
+ "require": "./dist/low-level.cjs"
41
+ },
42
+ "./internal": {
43
+ "types": "./dist/entries/internal.d.ts",
44
+ "import": "./dist/internal.js",
45
+ "require": "./dist/internal.cjs"
46
+ }
47
+ },
48
+ "peerDependencies": {
49
+ "@mediapipe/tasks-vision": "^0.10.18",
50
+ "@react-three/drei": "9.105.6",
51
+ "@react-three/fiber": "8.18.0",
52
+ "react": "^18.2.0",
53
+ "react-dom": "^18.2.0",
54
+ "three": "0.173.0"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "@mediapipe/tasks-vision": {
58
+ "optional": false
59
+ }
60
+ },
61
+ "devDependencies": {
62
+ "@types/node": "^22.9.0",
63
+ "@types/react": "^18.3.12",
64
+ "@types/react-dom": "^18.3.1",
65
+ "@types/three": "0.173.0",
66
+ "@typescript-eslint/eslint-plugin": "^8.14.0",
67
+ "@typescript-eslint/parser": "^8.14.0",
68
+ "@vitejs/plugin-react": "^4.3.3",
69
+ "commit-and-tag-version": "^12.5.0",
70
+ "cz-conventional-changelog": "^3.3.0",
71
+ "eslint": "^9.14.0",
72
+ "eslint-config-airbnb": "^19.0.4",
73
+ "eslint-config-airbnb-typescript": "^18.0.0",
74
+ "eslint-config-prettier": "^9.1.0",
75
+ "eslint-plugin-prettier": "^5.2.1",
76
+ "eslint-plugin-react": "^7.37.2",
77
+ "eslint-plugin-react-hooks": "^5.0.0",
78
+ "eslint-plugin-react-refresh": "^0.4.14",
79
+ "husky": "^9.1.6",
80
+ "lint-staged": "^15.2.10",
81
+ "prettier": "3.3.3",
82
+ "qrcode-terminal": "^0.12.0",
83
+ "react-router-dom": "^6.28.0",
84
+ "typescript": "^5.6.3",
85
+ "vite": "^5.4.11",
86
+ "vite-plugin-dts": "^4.3.0",
87
+ "vite-tsconfig-paths": "^5.1.2",
88
+ "vitest": "^2.1.5"
89
+ },
90
+ "lint-staged": {
91
+ "**/*": "prettier --write --ignore-unknown"
92
+ },
93
+ "config": {
94
+ "commitizen": {
95
+ "path": "./node_modules/cz-conventional-changelog"
96
+ }
97
+ },
98
+ "scripts": {
99
+ "dev": "vite --config example/vite.config.ts --mode dev",
100
+ "preview:tunnel": "node example/scripts/preview.mjs",
101
+ "preview:local": "node example/scripts/preview.mjs --local",
102
+ "build": "vite build",
103
+ "build:watch": "vite build --watch",
104
+ "format:check": "prettier --check .",
105
+ "format": "prettier --write 'src/**/*.{js,jsx,ts,tsx}' 'lib/**/*.{js,jsx,ts,tsx}'",
106
+ "test": "vitest run",
107
+ "test:watch": "vitest",
108
+ "lint:check": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
109
+ "example:build": "vite build --config example/vite.config.ts",
110
+ "example:preview": "vite preview --config example/vite.config.ts --outDir dist-example",
111
+ "release": "commit-and-tag-version"
112
+ }
113
+ }