@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.
- package/GROUNDING.md +537 -0
- package/LICENSE +7 -0
- package/README.md +85 -0
- package/dist/DebuggerUI-CEfdddkZ.cjs +1 -0
- package/dist/DebuggerUI-Dgop6Q6o.js +269 -0
- package/dist/audio/AudioEngine.d.ts +98 -0
- package/dist/audio/index.d.ts +34 -0
- package/dist/components/AspectRatioContainer/index.d.ts +10 -0
- package/dist/components/BodyTracker/index.d.ts +15 -0
- package/dist/components/Debugger/DebuggerProvider.d.ts +22 -0
- package/dist/components/Debugger/DebuggerUI.d.ts +13 -0
- package/dist/components/Debugger/index.d.ts +38 -0
- package/dist/components/Debugger/styles.d.ts +19 -0
- package/dist/components/FaceMesh/FaceMesh.d.ts +26 -0
- package/dist/components/FaceMesh/canonical/CanonicalFaceMeshRenderer.d.ts +34 -0
- package/dist/components/FaceMesh/canonical/parseCanonicalFaceModel.d.ts +16 -0
- package/dist/components/FaceMesh/canonical/useCanonicalFaceMesh.d.ts +15 -0
- package/dist/components/FaceMesh/deform.d.ts +36 -0
- package/dist/components/FaceMesh/index.d.ts +6 -0
- package/dist/components/FaceMesh/indices.d.ts +1 -0
- package/dist/components/FaceMesh/material/blendModes.d.ts +2 -0
- package/dist/components/FaceMesh/material/defaults.d.ts +3 -0
- package/dist/components/FaceMesh/material/index.d.ts +5 -0
- package/dist/components/FaceMesh/material/options.d.ts +25 -0
- package/dist/components/FaceMesh/material/support.d.ts +3 -0
- package/dist/components/FaceMesh/material/types.d.ts +23 -0
- package/dist/components/FaceMesh/uv.d.ts +1 -0
- package/dist/components/FaceRetouch/Background.d.ts +16 -0
- package/dist/components/FaceRetouch/index.d.ts +6 -0
- package/dist/components/FaceRetouch/landmarkMask.d.ts +23 -0
- package/dist/components/FaceRetouch/retouchRegions.d.ts +23 -0
- package/dist/components/FaceRetouch/useFaceRetouchMask.d.ts +14 -0
- package/dist/components/FaceRetouch/useFaceRetouchRegions.d.ts +12 -0
- package/dist/components/FaceTracker/BoundingBoxOverlay.d.ts +20 -0
- package/dist/components/FaceTracker/index.d.ts +54 -0
- package/dist/components/GestureTracker/index.d.ts +31 -0
- package/dist/components/GestureTrigger/index.d.ts +3 -0
- package/dist/components/HandTracker/index.d.ts +24 -0
- package/dist/components/HeadBinding/index.d.ts +31 -0
- package/dist/components/HeadBindingOccluder/index.d.ts +36 -0
- package/dist/components/HeadOccluder/index.d.ts +13 -0
- package/dist/components/Panel/index.d.ts +71 -0
- package/dist/components/PerspectiveCamera/index.d.ts +14 -0
- package/dist/components/ScreenSpaceUI/ScreenTransform.d.ts +54 -0
- package/dist/components/ScreenSpaceUI/context.d.ts +29 -0
- package/dist/components/ScreenSpaceUI/index.d.ts +40 -0
- package/dist/components/Segmentation/index.d.ts +44 -0
- package/dist/components/SpriteAnimation/index.d.ts +52 -0
- package/dist/components/TextLabel/index.d.ts +62 -0
- package/dist/components/TrackingAnchor/index.d.ts +74 -0
- package/dist/components/VideoBackground/index.d.ts +38 -0
- package/dist/components/XRCompositor/index.d.ts +5 -0
- package/dist/components/XRDebugView/index.d.ts +6 -0
- package/dist/components/XRLayer/index.d.ts +6 -0
- package/dist/components/XRMediaSurface/index.d.ts +27 -0
- package/dist/components/XRProvider/index.d.ts +13 -0
- package/dist/components/XRScene/defaults.d.ts +4 -0
- package/dist/components/XRScene/index.d.ts +15 -0
- package/dist/components/XRViewport/index.d.ts +101 -0
- package/dist/components/index.d.ts +26 -0
- package/dist/config/index.d.ts +8 -0
- package/dist/constants/index.d.ts +25 -0
- package/dist/constants/renderLayers.d.ts +25 -0
- package/dist/entries/all.d.ts +15 -0
- package/dist/entries/core.d.ts +21 -0
- package/dist/entries/face-effects.d.ts +19 -0
- package/dist/entries/internal.d.ts +16 -0
- package/dist/entries/low-level.d.ts +30 -0
- package/dist/entries/tracking.d.ts +15 -0
- package/dist/face-effects.cjs +252 -0
- package/dist/face-effects.js +3542 -0
- package/dist/hasFullUnitUVBounds-BGvcFsQM.js +26 -0
- package/dist/hasFullUnitUVBounds-p1Dl0ejX.cjs +1 -0
- package/dist/hooks/faceResults.d.ts +39 -0
- package/dist/hooks/selectors.d.ts +33 -0
- package/dist/hooks/useCalibrationFaceTracker.d.ts +23 -0
- package/dist/hooks/useDebugger/index.d.ts +25 -0
- package/dist/hooks/useDebugger/types.d.ts +20 -0
- package/dist/hooks/useFaceBoundingBox.d.ts +11 -0
- package/dist/hooks/useFaceResults.d.ts +31 -0
- package/dist/hooks/useGestureEdge.d.ts +53 -0
- package/dist/hooks/useMediaRecorder.d.ts +40 -0
- package/dist/hooks/useTrackerContext.d.ts +35 -0
- package/dist/hooks/useXRModel.d.ts +15 -0
- package/dist/hooks/useXRSession/constants.d.ts +4 -0
- package/dist/hooks/useXRSession/facade.d.ts +39 -0
- package/dist/hooks/useXRSession/fns.d.ts +5 -0
- package/dist/hooks/useXRSession/index.d.ts +4 -0
- package/dist/hooks/useXRSession/registry.d.ts +52 -0
- package/dist/hooks/useXRSession/store.d.ts +40 -0
- package/dist/index-B5J4gzn2.cjs +1 -0
- package/dist/index-BD-JWMjE.js +1991 -0
- package/dist/index-BVBVg96G.cjs +1 -0
- package/dist/index-CYMj26Ej.js +1331 -0
- package/dist/index-DtwHElmG.js +157 -0
- package/dist/index-PtFmD6B8.cjs +1 -0
- package/dist/internal-DH5d1Xnq.cjs +1 -0
- package/dist/internal-KOKJDxCG.js +519 -0
- package/dist/internal.cjs +1 -0
- package/dist/internal.js +14 -0
- package/dist/low-level.cjs +1 -0
- package/dist/low-level.js +941 -0
- package/dist/main.cjs +1 -0
- package/dist/main.js +612 -0
- package/dist/models/Base/index.d.ts +25 -0
- package/dist/models/Body/index.d.ts +22 -0
- package/dist/models/Face/constants.d.ts +2 -0
- package/dist/models/Face/index.d.ts +45 -0
- package/dist/models/Face/landmarksData.d.ts +2 -0
- package/dist/models/Hand/GestureTracking.d.ts +21 -0
- package/dist/models/Hand/HandTracking.d.ts +21 -0
- package/dist/models/Segmentation/index.d.ts +75 -0
- package/dist/models/index.d.ts +5 -0
- package/dist/modules/XRClient/index.d.ts +9 -0
- package/dist/modules/index.d.ts +1 -0
- package/dist/selectors-BHO6TnoC.js +59 -0
- package/dist/selectors-Bub0o3qy.cjs +1 -0
- package/dist/services/ApiService.d.ts +12 -0
- package/dist/services/MetricsCollector/CameraFPSTracker.d.ts +25 -0
- package/dist/services/MetricsCollector/MemoryTracker.d.ts +43 -0
- package/dist/services/MetricsCollector/RenderTimeTracker.d.ts +10 -0
- package/dist/services/MetricsCollector/ResolutionTracker.d.ts +26 -0
- package/dist/services/MetricsCollector/index.d.ts +59 -0
- package/dist/services/MetricsCollector/types.d.ts +47 -0
- package/dist/services/MetricsCollector/utils.d.ts +65 -0
- package/dist/texture/computeContainScale.d.ts +25 -0
- package/dist/texture/hasFullUnitUVBounds.d.ts +37 -0
- package/dist/texture/types.d.ts +20 -0
- package/dist/tracking.cjs +1 -0
- package/dist/tracking.js +344 -0
- package/dist/types/body.d.ts +3 -0
- package/dist/types/client.d.ts +3 -0
- package/dist/types/common.d.ts +9 -0
- package/dist/types/compositor.d.ts +23 -0
- package/dist/types/debugger.d.ts +83 -0
- package/dist/types/face.d.ts +67 -0
- package/dist/types/gesture-tracking.d.ts +15 -0
- package/dist/types/hand-tracking.d.ts +5 -0
- package/dist/types/headBinding.d.ts +67 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/logger.d.ts +4 -0
- package/dist/types/model.d.ts +46 -0
- package/dist/types/nodes.d.ts +35 -0
- package/dist/types/segmentation.d.ts +31 -0
- package/dist/types/session.d.ts +102 -0
- package/dist/types/tracking.d.ts +30 -0
- package/dist/types/util-types.d.ts +1 -0
- package/dist/useFaceResults-cpsSmKKc.js +81 -0
- package/dist/useFaceResults-hVUJYSEY.cjs +1 -0
- package/dist/utils/calibrationDebug.d.ts +9 -0
- package/dist/utils/dataURL.d.ts +6 -0
- package/dist/utils/drawMediaCover.d.ts +18 -0
- package/dist/utils/errors.d.ts +3 -0
- package/dist/utils/fns.d.ts +48 -0
- package/dist/utils/headBinding.d.ts +85 -0
- package/dist/utils/integrity.d.ts +43 -0
- package/dist/utils/landmarkBounds.d.ts +11 -0
- package/dist/utils/logger.d.ts +7 -0
- package/package.json +113 -0
package/GROUNDING.md
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
# Vincentt XR SDK — Grounding Reference
|
|
2
|
+
|
|
3
|
+
The complete, authoritative reference for the `@vincentt-xr/sdk` package:
|
|
4
|
+
every component, hook, and prop you may use, plus the R3F conventions for
|
|
5
|
+
composing them. Use the components and props documented here; do not invent props
|
|
6
|
+
or spelunk `node_modules` for the API.
|
|
7
|
+
|
|
8
|
+
## Import doors — one per task
|
|
9
|
+
|
|
10
|
+
The API is split into task-domain entry points. Import each symbol from the door
|
|
11
|
+
this reference shows for it; the imports here are literal.
|
|
12
|
+
|
|
13
|
+
- `@vincentt-xr/sdk` — **core**: providers, scene, screen-space layout, `TextLabel`, `Panel`, `SpriteAnimation`, `VideoBackground`, `AspectRatioContainer`, capture/share (`useMediaRecorder`, `dataURLtoFile`), audio.
|
|
14
|
+
- `@vincentt-xr/sdk/tracking` — **trackers**: face/hand/gesture/body/segmentation, `TrackingAnchor`, `GestureTrigger`, the bare `FaceMesh`, `useFaceResults`.
|
|
15
|
+
- `@vincentt-xr/sdk/face-effects` — **face deep**: FaceMesh material config, retouch, canonical mesh, head-binding.
|
|
16
|
+
- `@vincentt-xr/sdk/low-level` — **escape hatch**: raw model-node reads, selector hooks, custom-tracker plumbing. Reach here only when core + tracking can't express it.
|
|
17
|
+
|
|
18
|
+
Trackers (`<FaceTracker>`, `<GestureTracker>`, `<HandTracker>`, `<BodyTracker>`)
|
|
19
|
+
**self-register** when mounted — no explicit `registerXRPipeline` call. Per-frame
|
|
20
|
+
logic is R3F `useFrame`; per-mount setup is `useEffect`.
|
|
21
|
+
|
|
22
|
+
> This file ships inside the SDK tarball. In a v2-template project it is combined
|
|
23
|
+
> with the template's own grounding (template-local helpers + project notes +
|
|
24
|
+
> patterns) into the project's `GROUNDING.md`. Edit the SDK API here, in the repo
|
|
25
|
+
> that owns the API.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Screen-space layout (the primary tool)
|
|
30
|
+
|
|
31
|
+
Most Vincentt projects use screen-space UI: decorative frames, prompts, badges, controls — overlaid on the live camera feed. This is more common than world-space landmark attachment.
|
|
32
|
+
|
|
33
|
+
### `<ScreenSpaceUI>` — Container for screen-space content
|
|
34
|
+
|
|
35
|
+
Wraps all screen-anchored children. Must be the parent of any `<ScreenTransform>`.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { ScreenSpaceUI, ScreenTransform } from "@vincentt-xr/sdk";
|
|
39
|
+
|
|
40
|
+
<ScreenSpaceUI>
|
|
41
|
+
<ScreenTransform anchors={{ left: -1, right: 1, top: 1, bottom: -1 }}>
|
|
42
|
+
{/* full-screen content */}
|
|
43
|
+
</ScreenTransform>
|
|
44
|
+
</ScreenSpaceUI>;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### `<ScreenTransform anchors={{ left, right, top, bottom }}>` — Place an element within a normalized screen rect
|
|
48
|
+
|
|
49
|
+
Coordinates are normalized: -1 to +1, with +y up. The center of the screen is `(0, 0)`. The anchors rect controls BOTH size and placement of the child — it stretches the child (plane, TextLabel, sprite) to fill the rect. Don't add `scale`/`position`/`planeGeometry args` to size or move a screen-anchored child; set the anchors.
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// Full-screen frame
|
|
53
|
+
<ScreenTransform anchors={{ left: -1, right: 1, top: 1, bottom: -1 }}>
|
|
54
|
+
<mesh name="frame"><planeGeometry args={[1, 1]} /><meshBasicMaterial map={frameTex} transparent /></mesh>
|
|
55
|
+
</ScreenTransform>
|
|
56
|
+
|
|
57
|
+
// Bottom-center prompt
|
|
58
|
+
<ScreenTransform anchors={{ left: -0.4, right: 0.4, top: -0.5, bottom: -0.9 }}>
|
|
59
|
+
<TextLabel name="prompt" text="Show your hand" fontSize={220} />
|
|
60
|
+
</ScreenTransform>
|
|
61
|
+
|
|
62
|
+
// Full-width strip pinned FLUSH to the top edge (e.g. a colored banner plane)
|
|
63
|
+
<ScreenTransform anchors={{ left: -1, right: 1, top: 1, bottom: 0.6 }}>
|
|
64
|
+
<mesh name="topBar"><planeGeometry args={[1, 1]} /><meshBasicMaterial color="#2255ff" /></mesh>
|
|
65
|
+
</ScreenTransform>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Converting a screen-region annotation (% coords) to anchors.** Annotation marks come as top-left percentages (x: 0%=left→100%=right, y: 0%=top→100%=bottom). Anchors are normalized -1..+1 with +y UP, so the y sign flips:
|
|
69
|
+
`left = x%/50 - 1`, `right = x%/50 - 1`, `top = 1 - y%/50`, `bottom = 1 - y%/50`. A mark at x 0–100%, y 0–28% → `anchors={{ left: -1, right: 1, top: 1, bottom: 0.44 }}`. **Snap a near-edge mark to that edge** (within ~10%): top→`top: 1`, bottom→`bottom: -1`, left→`left: -1`, right→`right: 1`. A strip drawn near the bottom (y 78–90%) → `anchors={{ left: -1, right: 1, top: -0.6, bottom: -1 }}` (flush at -1, not the literal -0.8).
|
|
70
|
+
|
|
71
|
+
### `useScreenRectAspect()` + `computeContainScale(rectAspect, contentAspect)` — Aspect-correct fitting
|
|
72
|
+
|
|
73
|
+
Use together when you need a texture or mesh to fit inside its anchor box without stretching.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { useScreenRectAspect, computeContainScale } from "@vincentt-xr/sdk";
|
|
77
|
+
|
|
78
|
+
const rectAspect = useScreenRectAspect();
|
|
79
|
+
const [sx, sy] = computeContainScale(rectAspect, 1200 / 800); // content w/h ratio
|
|
80
|
+
|
|
81
|
+
<mesh name="badge" scale={[sx * 0.5, sy * 0.5, 1]}>
|
|
82
|
+
<planeGeometry args={[1, 1]} />
|
|
83
|
+
<meshBasicMaterial map={tex} transparent />
|
|
84
|
+
</mesh>;
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `<SpriteAnimation sheet={{ url, cols, rows, frames, fps }}>` — Animated transparent overlay
|
|
88
|
+
|
|
89
|
+
The primitive for an animated transparent overlay (a frame border, a gesture-instruction graphic, an animated sticker). One transparent WebP atlas of `cols`×`rows` cells; the first `frames` play left-to-right, top-to-bottom at `fps`. It is a single static GPU texture with a per-frame UV offset — no video decode — so it works in every native and in-app browser and costs ~zero heat.
|
|
90
|
+
|
|
91
|
+
**Use this, not transparent video.** No single video codec plays everywhere with alpha (HEVC-alpha is iOS-Safari-only; VP8/VP9-alpha WebM renders blank as a WebGL texture on iOS Safari), and two video-texture decodes on top of face tracking overheat the phone. Export the atlas as a real 8-bit-alpha WebP (with `cwebp`; ffmpeg's `libwebp` drops alpha). Pair opaque H.264 video only for full-frame backgrounds.
|
|
92
|
+
|
|
93
|
+
Defaults to full-screen; pass `anchors` to place it and `contentAspect` (frame width/height) to contain-fit without stretching. Must live inside a `<ScreenSpaceUI>`.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { SpriteAnimation, ScreenSpaceUI } from "@vincentt-xr/sdk";
|
|
97
|
+
|
|
98
|
+
<ScreenSpaceUI>
|
|
99
|
+
{/* full-screen animated frame border */}
|
|
100
|
+
<SpriteAnimation sheet={{ url: frameUrl, cols: 8, rows: 6, frames: 48, fps: 8 }} name="frame" />
|
|
101
|
+
|
|
102
|
+
{/* a placed, contain-fit instruction clip */}
|
|
103
|
+
<SpriteAnimation
|
|
104
|
+
sheet={{ url: startUrl, cols: 6, rows: 4, frames: 24, fps: 12 }}
|
|
105
|
+
anchors={{ left: -0.6, right: 0.6, top: 0.4, bottom: -0.4 }}
|
|
106
|
+
contentAspect={1}
|
|
107
|
+
name="startHint"
|
|
108
|
+
/>
|
|
109
|
+
</ScreenSpaceUI>;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Trackers (self-registering)
|
|
115
|
+
|
|
116
|
+
Mount the tracker component; it registers the underlying ML model automatically. No `registerXRPipeline` call needed.
|
|
117
|
+
|
|
118
|
+
### `<FaceTracker>` — Face tracking (468 landmarks)
|
|
119
|
+
|
|
120
|
+
Detects faces and provides face transform to children via context. Supports `faceIndex` (default 0) and `scaleWithFace`.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { FaceTracker, TrackingAnchor } from "@vincentt-xr/sdk/tracking";
|
|
124
|
+
|
|
125
|
+
<FaceTracker>
|
|
126
|
+
<TrackingAnchor target="face.forehead">
|
|
127
|
+
<mesh name="crown" position={[0, 0.1, 0]}>
|
|
128
|
+
<planeGeometry args={[0.3, 0.15]} />
|
|
129
|
+
<meshBasicMaterial map={crownTex} transparent />
|
|
130
|
+
</mesh>
|
|
131
|
+
</TrackingAnchor>
|
|
132
|
+
</FaceTracker>;
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `<FaceTracker.Mesh />` / `<FaceMesh>` — Deforming 468-point face mesh
|
|
136
|
+
|
|
137
|
+
For masks, makeup, and full-face decoration that deforms with skin.
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { FaceTracker, FaceMesh } from "@vincentt-xr/sdk/tracking";
|
|
141
|
+
|
|
142
|
+
<FaceTracker>
|
|
143
|
+
<FaceMesh appearance={{ map: maskTex }} />
|
|
144
|
+
</FaceTracker>;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `<HandTracker>` — Hand skeleton (21 joints)
|
|
148
|
+
|
|
149
|
+
The hand-skeleton tracker. Use it for 21-joint hand landmarks — anchoring to `hand.indexTip`, `hand.wrist`, etc. It also exposes a crude open-vs-closed grip on `node.gesture` (only `closed_fist` / `open_palm`), but that is a side effect of the skeleton, not a gesture recognizer. **For any named gesture, use `<GestureTracker>`** (next section) — it is the gesture door; `<HandTracker>` is the skeleton door.
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
import { HandTracker } from "@vincentt-xr/sdk/tracking";
|
|
153
|
+
|
|
154
|
+
<HandTracker />;
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `<GestureTracker>` — Named gestures
|
|
158
|
+
|
|
159
|
+
The gesture door — the component that recognizes named gestures. Its `node.gesture` is one of these exact strings (copy verbatim — these are the only valid values):
|
|
160
|
+
|
|
161
|
+
`closed_fist`, `open_palm`, `pointing_up`, `thumb_down`, `thumb_up`, `victory`, `i_love_you`, `none`.
|
|
162
|
+
|
|
163
|
+
Catches people out:
|
|
164
|
+
|
|
165
|
+
- It is `thumb_up` / `thumb_down` — singular, no trailing "s" (not `thumbs_up`).
|
|
166
|
+
- The peace sign is `victory`.
|
|
167
|
+
- There is no `open_hand`; the open-hand gesture is `open_palm`.
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { GestureTracker } from "@vincentt-xr/sdk/tracking";
|
|
171
|
+
|
|
172
|
+
<GestureTracker />;
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `<BodyTracker>` — Pose tracking (33 landmarks)
|
|
176
|
+
|
|
177
|
+
Head, shoulders, elbows, wrists, hips, knees, ankles.
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
import { BodyTracker, TrackingAnchor } from "@vincentt-xr/sdk/tracking";
|
|
181
|
+
|
|
182
|
+
<BodyTracker>
|
|
183
|
+
<TrackingAnchor target="body.leftShoulder">
|
|
184
|
+
<mesh name="badge-left">
|
|
185
|
+
<sphereGeometry args={[0.05]} />
|
|
186
|
+
<meshBasicMaterial color="red" />
|
|
187
|
+
</mesh>
|
|
188
|
+
</TrackingAnchor>
|
|
189
|
+
</BodyTracker>;
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### `<Segmentation type="portrait">` — Background segmentation (declarative)
|
|
193
|
+
|
|
194
|
+
Registers the portrait segmentation pipeline. The resulting mask feeds into `<VideoBackground segmentationMask={...} />` for background replacement. Mount it alongside a ref or state that holds the mask texture.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Tracker performance & lifecycle
|
|
199
|
+
|
|
200
|
+
ML tracker models are the heaviest thing in an XR app — each one runs a MediaPipe pipeline on the camera feed. Two rules keep apps from overheating the device and from a scene-transition gesture bug.
|
|
201
|
+
|
|
202
|
+
### Mount shared trackers ONCE, above any scene/phase switch
|
|
203
|
+
|
|
204
|
+
A tracker is a refcounted model: mounting acquires it, unmounting disposes it. If you mount `<GestureTracker />` (or `<FaceTracker>`) **inside** each scene of a multi-scene flow (home → game → photo …), every transition tears the model down and rebuilds it. That (a) churns the GPU and (b) hits a dispose/re-acquire race where the gesture stops being reported after the transition (it works if you deep-link straight to the scene, but not when arriving from another scene).
|
|
205
|
+
|
|
206
|
+
Fix: mount trackers that more than one scene needs **once**, above the phase switch, so the model stays alive for the whole session:
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
// Scene.tsx
|
|
210
|
+
<>
|
|
211
|
+
<GestureTracker /> {/* persistent — every phase reads its gesture */}
|
|
212
|
+
{renderCurrentPhase()}
|
|
213
|
+
</>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Scope a tracker to a single scene only when just that scene uses it (e.g. a `<FaceTracker>` catcher that exists only during gameplay).
|
|
217
|
+
|
|
218
|
+
### `active` — pause an off-scene tracker without unmounting it
|
|
219
|
+
|
|
220
|
+
The "mount once" rule keeps a model alive across scenes, but a persistent tracker then runs its MediaPipe pipeline **every frame for the whole session** — even in scenes that don't use it. Each running model does a GPU→CPU readback per inference, and that readback is often the single largest cost in the app; two persistent trackers (e.g. gesture on the menus, face in gameplay) stack two readbacks the whole time when only one is ever needed at once.
|
|
221
|
+
|
|
222
|
+
Every tracker takes an `active` prop (default `true`). Set `active={false}` and the model **pauses inference** — it stays mounted and initialized (no unmount, so no dispose/re-acquire race), it just stops doing per-frame work. Flip it back to `true` and it resumes on the next frame with no re-init. Gate each tracker to the phases that use it:
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
// Scene.tsx — both trackers persistent (no race), but each only runs when needed.
|
|
226
|
+
<>
|
|
227
|
+
<GestureTracker active={phase !== "game"} /> {/* menus/photo, not gameplay */}
|
|
228
|
+
<FaceTracker active={phase === "game"}>{/* the catcher */}…</FaceTracker>
|
|
229
|
+
{renderCurrentPhase()}
|
|
230
|
+
</>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
When several consumers share one model, it runs while **any** of them is active. `active` gates whether the model runs; `perf.targetFps` (below) caps how often it runs while active — use both. `<Segmentation>` also takes `active` (distinct from its `visible` prop, which only gates mask exposure while inference keeps running).
|
|
234
|
+
|
|
235
|
+
### `perf` — cap inference rate + camera resolution
|
|
236
|
+
|
|
237
|
+
Set `perf` on `<XRProvider>` to trade tracking fidelity for lower load and heat. This is the single biggest lever if the device runs hot.
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import { XRProvider } from "@vincentt-xr/sdk";
|
|
241
|
+
|
|
242
|
+
<XRProvider perf={{ targetFps: 24, camRes: 720 }}>
|
|
243
|
+
<App />
|
|
244
|
+
</XRProvider>;
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
- `targetFps` — caps every tracker model's inference rate (fps). The model's RAF loop still runs, but the expensive detect call is skipped between intervals. Omit → Face/Hand run uncapped, Gesture stays at its 15fps default, Segmentation at 24fps.
|
|
248
|
+
- `camRes` — target width (px) of the image the models **analyze**. This is NOT the visible camera background the user sees — lowering it cuts ML work with no visible quality loss to the feed.
|
|
249
|
+
- `cameraZoom` — **framing** control (not perf): how tightly the webcam is cover-cropped into the viewport. `1` = full native field of view (most of the room visible); default `1.2` fills the frame like a native selfie / Instagram front camera. Applies to the visible feed, the captured photo, and what the trackers see (all read the same crop). Raise for a closer crop, set `1` for the widest shot.
|
|
250
|
+
|
|
251
|
+
Per-tracker override beats the session default:
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
<GestureTracker targetFps={30} /> {/* crisp gestures … */}
|
|
255
|
+
<FaceTracker targetFps={20}>…</FaceTracker> {/* … cheaper face */}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The base template ships `perf={{ targetFps: 24, camRes: 720 }}` by default — good for both dev laptops and end-user phones. Don't lower it further without a reason; don't remove it to "get full quality" unless you know the target device can take the heat.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Reading tracker state
|
|
263
|
+
|
|
264
|
+
### `<TrackingAnchor target="...">` — Declarative landmark following
|
|
265
|
+
|
|
266
|
+
Wraps children; the child's world position is updated to the tracked landmark each frame. Use for all decoration that simply follows a face/hand/body point.
|
|
267
|
+
|
|
268
|
+
Valid targets:
|
|
269
|
+
|
|
270
|
+
- **Face:** `face.forehead`, `face.noseTip`, `face.chin`, `face.leftEyeOuter`, `face.rightEyeOuter`, `face.leftCheek`, `face.rightCheek`, `face.mouthCenter`
|
|
271
|
+
- **Hand:** `hand.wrist`, `hand.thumbTip`, `hand.indexTip`, `hand.middleTip`, `hand.ringTip`, `hand.pinkyTip`, `hand.indexBase`, `hand.middleBase`, `hand.ringBase`, `hand.pinkyBase`
|
|
272
|
+
- **Body:** `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`
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
<FaceTracker>
|
|
276
|
+
<TrackingAnchor target="face.noseTip">
|
|
277
|
+
<mesh name="noseDot" scale={[0.04, 0.04, 0.04]}>
|
|
278
|
+
<sphereGeometry />
|
|
279
|
+
<meshBasicMaterial color="red" />
|
|
280
|
+
</mesh>
|
|
281
|
+
</TrackingAnchor>
|
|
282
|
+
</FaceTracker>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**`smoothing` — smooth motion from throttled inference.** Inference is throttled (the `perf` API caps it at 24fps by default) while rendering runs at 60-120fps, so a raw anchor holds then jumps on each new sample — visible stepping under head/hand movement. A plain lerp only hides it at rest; under motion it still steps. Pass `smoothing` (`~15` is a good default) and the anchor runs a **One Euro filter** — the standard adaptive low-pass for interactive tracking: it smooths hard when the landmark moves slowly (kills jitter) and loosens as it speeds up (kills lag), and never extrapolates so it can't overshoot or wobble. Higher `smoothing` is snappier (less lag, slightly more jitter); lower is smoother. It snaps (resets the filter) on first placement and on re-acquire, so it never slides in from a stale position. `0` (default) keeps the raw snap. Rotation is not smoothed. Prefer this over a hand-rolled `useFrame` lerp — a hand-rolled lerp reintroduces the stepping.
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
<FaceTracker>
|
|
289
|
+
<TrackingAnchor target="face.noseTip" smoothing={15}>
|
|
290
|
+
<mesh name="sticker">{/* follows the nose smoothly, even while moving */}</mesh>
|
|
291
|
+
</TrackingAnchor>
|
|
292
|
+
</FaceTracker>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### `useXRModelNode(XRModel.GESTURE_TRACKER)` — Read gesture state (the default read path)
|
|
296
|
+
|
|
297
|
+
The gen-3 selector — subscribe to the live gesture node from `useFrame` when you need the gesture name or landmark coordinates for a state machine or fine-grained control. This is the blessed read layer; prefer it over raw node reads.
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
import { XRModel } from "@vincentt-xr/sdk";
|
|
301
|
+
import { useXRModelNode } from "@vincentt-xr/sdk/low-level";
|
|
302
|
+
import type { XRModelNodeGestureTracking } from "@vincentt-xr/sdk/low-level";
|
|
303
|
+
import { useFrame } from "@react-three/fiber";
|
|
304
|
+
|
|
305
|
+
const gesture = useXRModelNode<XRModelNodeGestureTracking>(XRModel.GESTURE_TRACKER);
|
|
306
|
+
|
|
307
|
+
useFrame(() => {
|
|
308
|
+
if (gesture?.gesture === "victory") {
|
|
309
|
+
// handle peace sign
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
> **Escape hatch (rarely needed):** `useXRContext()` + `session.getModelNode(...)` from
|
|
315
|
+
> `@vincentt-xr/sdk/low-level` reads the raw node imperatively. Prefer the selector
|
|
316
|
+
> above; reach for the raw read only when you need the session handle for something the
|
|
317
|
+
> selector can't express.
|
|
318
|
+
|
|
319
|
+
### `<GestureTrigger gestures={["victory"]} onTrigger={(g) => ...}>` — Declarative one-shot gesture handler
|
|
320
|
+
|
|
321
|
+
Fires once per gesture recognition event. The simplest way to act on a gesture — prefer it over a manual `useFrame` poll. Two modes:
|
|
322
|
+
|
|
323
|
+
- **default (`mode="arm"`)** — fires the first time the gesture appears _after_ the user has _not_ been showing it for `debounceMs` (default 800). Use for "raise a NEW gesture to advance," where a stale frame from the prior scene must not fire.
|
|
324
|
+
- **`mode="hold"`** — fires as soon as the gesture is held for `holdMs` (default 200), with **no arm gate**. Use for an **instruction-driven scene** ("show ✌️ to start") where the UI tells the user to raise the gesture and the hand is already up on mount — the arm gate needs a not-showing stretch first, so in `arm` mode it would never fire.
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
import { GestureTracker, GestureTrigger } from "@vincentt-xr/sdk/tracking";
|
|
328
|
+
|
|
329
|
+
<GestureTracker />
|
|
330
|
+
|
|
331
|
+
// "show ✌️ to start" — hand is up on mount, so use hold mode
|
|
332
|
+
<GestureTrigger
|
|
333
|
+
mode="hold"
|
|
334
|
+
gestures={["victory"]}
|
|
335
|
+
onTrigger={() => startGame()}
|
|
336
|
+
/>
|
|
337
|
+
|
|
338
|
+
// "make a NEW gesture to advance" — default arm mode
|
|
339
|
+
<GestureTrigger
|
|
340
|
+
gestures={["thumb_up"]}
|
|
341
|
+
onTrigger={() => next()}
|
|
342
|
+
/>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Camera background
|
|
348
|
+
|
|
349
|
+
### `<VideoBackground customBackground="#6366f1" segmentationMask={maskTex} renderOrder={-999} />`
|
|
350
|
+
|
|
351
|
+
In a v2-template project this is already mounted in `src/App.tsx` — do NOT add a second one there. Mentioned here because `customBackground` is how you do background replacement: pair it with `<Segmentation type="portrait" />` and pass the segmentation mask texture as `segmentationMask`.
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Mesh primitives and textures
|
|
356
|
+
|
|
357
|
+
Use R3F intrinsic `<mesh>`, `<group>`, `<planeGeometry>`, `<meshBasicMaterial>`, etc. Load image textures with drei's `useTexture`.
|
|
358
|
+
|
|
359
|
+
**Always name your meshes** (`name="myLabel"`). Names enable runtime lookup AND visual-feedback editing by the editor.
|
|
360
|
+
|
|
361
|
+
**Materials accept `map`, not `alphaMap`, for image asset slots.**
|
|
362
|
+
|
|
363
|
+
```tsx
|
|
364
|
+
import { useTexture } from "@react-three/drei";
|
|
365
|
+
|
|
366
|
+
const tex = useTexture("https://cdn.../badge.webp");
|
|
367
|
+
|
|
368
|
+
<mesh name="badge" position={[0, 0.5, 0]}>
|
|
369
|
+
<planeGeometry args={[1, 1]} />
|
|
370
|
+
<meshBasicMaterial map={tex} transparent />
|
|
371
|
+
</mesh>;
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## `<TextLabel>` — Canvas-rendered text panel
|
|
377
|
+
|
|
378
|
+
Import from `@vincentt-xr/sdk`. Billboarded in 3D space.
|
|
379
|
+
|
|
380
|
+
| prop | type | default | notes |
|
|
381
|
+
| -------------- | ------------------- | ---------------------- | --------------------------------------------------------------------------------------------- |
|
|
382
|
+
| `text` | `string` | **required** | The text to render. |
|
|
383
|
+
| `color` | `string` | `#ffffff` | Text color. |
|
|
384
|
+
| `fontSize` | `number` | `48` | Canvas px at the default 512px canvas — NOT world units. Chips/badges typically need 200-320. |
|
|
385
|
+
| `bgColor` | `string` | `rgba(20,40,110,0.88)` | Panel background; use `"transparent"` for text-only. |
|
|
386
|
+
| `borderColor` | `string` | — | Border color; omit for no border. |
|
|
387
|
+
| `borderRadius` | `number` | `24` | Corner radius; 0 for square. |
|
|
388
|
+
| `aspect` | `number` | `2.5` | Panel shape (width ÷ height). >1 = wide/banner, <1 = tall. |
|
|
389
|
+
| `scale` | `[x,y,z] \| number` | `1` | World size. Combine with `aspect` to make wide banners. |
|
|
390
|
+
| `position` | `[x,y,z]` | `[0,0,0]` | World position. +Y up, -Y down. |
|
|
391
|
+
| `name` | `string` | — | Set on every element you create. |
|
|
392
|
+
|
|
393
|
+
**Gotchas:**
|
|
394
|
+
|
|
395
|
+
- `fontSize` is canvas px, not world units. Chips/badges need 200-320, not 16-48.
|
|
396
|
+
- `fontSize` does not change panel shape — use `aspect` (shape) and `scale` (size).
|
|
397
|
+
- `TextLabel` ignores the `visible` prop — wrap in `<group visible={false}>` to start hidden.
|
|
398
|
+
- **Inside a `<ScreenTransform>`, the parent stretches the label to fill the anchor rect — `aspect` must equal the rect's aspect or the banner distorts and leaves dead space around the text.** The rect's aspect is `(right − left) / (top − bottom)`. So for a full-width top banner `anchors={{ left: -1, right: 1, top: 1, bottom: 0.76 }}` → aspect `= 2 / 0.24 ≈ 8.3`. Set `borderRadius={0}` for a true edge-to-edge banner. (The rect controls size + placement — don't add `scale`/`position` to fill it; set the anchors instead.)
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
// Full-width banner pinned to the top, INSIDE a ScreenTransform.
|
|
402
|
+
// aspect = rect width (2) / rect height (0.24) ≈ 8.3 — matches the rect so it fills cleanly.
|
|
403
|
+
<ScreenTransform anchors={{ left: -1, right: 1, top: 1, bottom: 0.76 }}>
|
|
404
|
+
<TextLabel name="topBanner" text="Head-Tilt Trivia" aspect={8.3} borderRadius={0} fontSize={270} />
|
|
405
|
+
</ScreenTransform>
|
|
406
|
+
|
|
407
|
+
// Full-width banner via position+scale, NOT inside a ScreenTransform.
|
|
408
|
+
// Here aspect should match scale.x / scale.y (4 / 0.8 = 5).
|
|
409
|
+
<TextLabel name="topBanner" text="Welcome" position={[0, 1.4, 0]} scale={[4, 0.8, 1]} aspect={5} fontSize={180} />
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## `<Panel>` — 3D billboarded panel for grouping UI content
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
import { Panel, TextLabel } from "@vincentt-xr/sdk";
|
|
418
|
+
|
|
419
|
+
<Panel>
|
|
420
|
+
<TextLabel name="title" text="Score: 10" fontSize={240} />
|
|
421
|
+
</Panel>;
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## App shell
|
|
427
|
+
|
|
428
|
+
### `<AspectRatioContainer>` — Letterbox the app into a 9:16 portrait frame
|
|
429
|
+
|
|
430
|
+
Wrap the app root to keep the AR composition framed the same across phones and
|
|
431
|
+
desktop: on wide viewports it pillarboxes to the 9:16 ratio; on portrait it fills
|
|
432
|
+
the width.
|
|
433
|
+
|
|
434
|
+
```tsx
|
|
435
|
+
import { AspectRatioContainer } from "@vincentt-xr/sdk";
|
|
436
|
+
|
|
437
|
+
<AspectRatioContainer>
|
|
438
|
+
<XRProvider>{/* ... */}</XRProvider>
|
|
439
|
+
</AspectRatioContainer>;
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Capture & share
|
|
445
|
+
|
|
446
|
+
Grab a photo of the live render or record the canvas, then save or share it.
|
|
447
|
+
|
|
448
|
+
### `session.captureFrame()` + `dataURLtoFile()` — Single-shot photo
|
|
449
|
+
|
|
450
|
+
`captureFrame()` forces a fresh render and returns a `data:` URL of the current
|
|
451
|
+
frame. `dataURLtoFile(dataUrl, filename)` turns it into a `File` for upload or the
|
|
452
|
+
Web Share API.
|
|
453
|
+
|
|
454
|
+
```tsx
|
|
455
|
+
import { dataURLtoFile } from "@vincentt-xr/sdk";
|
|
456
|
+
import { useXRContext } from "@vincentt-xr/sdk/low-level";
|
|
457
|
+
|
|
458
|
+
const { session } = useXRContext();
|
|
459
|
+
const dataUrl = session.captureFrame();
|
|
460
|
+
const file = dataURLtoFile(dataUrl, "photo.png");
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### `useMediaRecorder()` — Record the canvas to a video Blob
|
|
464
|
+
|
|
465
|
+
Records a `MediaStream` (from the canvas's `captureStream()`) to a `Blob`. Picks
|
|
466
|
+
the first supported codec (mp4 → webm). Pass `audio: true` to mix in the mic, or
|
|
467
|
+
`audio: <MediaStream>` for your own audio track.
|
|
468
|
+
|
|
469
|
+
```tsx
|
|
470
|
+
import { useMediaRecorder } from "@vincentt-xr/sdk";
|
|
471
|
+
|
|
472
|
+
const rec = useMediaRecorder();
|
|
473
|
+
const canvas = document.querySelector("#vincentt-xr-canvas canvas");
|
|
474
|
+
await rec.start(canvas.captureStream(30));
|
|
475
|
+
// ...later
|
|
476
|
+
rec.stop(); // -> rec.recordedBlob is the video Blob
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Audio
|
|
482
|
+
|
|
483
|
+
A silent-switch-safe sound engine for AR filters. Audio on iOS has three traps
|
|
484
|
+
every app otherwise re-solves; this bundles the fix. Use it — do not hand-roll
|
|
485
|
+
`new Audio()` or Web Audio.
|
|
486
|
+
|
|
487
|
+
1. **Gesture-started audio is blocked.** Sound kicked off from a camera-detected
|
|
488
|
+
gesture (a peace sign to start) is not a user-input event, so iOS blocks it.
|
|
489
|
+
Run **muted by default** and let the tap that unmutes be the gesture that
|
|
490
|
+
unlocks playback (IG/Snap-style speaker toggle).
|
|
491
|
+
2. **Web Audio is silenced by the hardware silent switch.** The engine uses
|
|
492
|
+
`<audio>` elements (the switch-immune "playback" category), not Web Audio.
|
|
493
|
+
3. **The rewind seek janks a frame.** Pooled SFX rewind on their `ended` event,
|
|
494
|
+
off the hot path — so firing an effect never does a synchronous seek.
|
|
495
|
+
|
|
496
|
+
Two paths, deliberately separate: **background music** is one looping element
|
|
497
|
+
(plays/pauses on mute, never pooled); **interaction SFX** are short one-shots
|
|
498
|
+
through a per-sound voice pool (a concurrency cap bounds decode load) with a
|
|
499
|
+
per-URL coalesce window (the same sound twice in a frame plays once; two
|
|
500
|
+
different sounds at once both play).
|
|
501
|
+
|
|
502
|
+
The API is free functions over one default engine. Preload during the loading
|
|
503
|
+
screen (`registerSound` / `prefetchMusic` return promises to await) so the
|
|
504
|
+
unmute tap is instant.
|
|
505
|
+
|
|
506
|
+
```tsx
|
|
507
|
+
import {
|
|
508
|
+
registerSound,
|
|
509
|
+
prefetchMusic,
|
|
510
|
+
startMusic,
|
|
511
|
+
playOneShot,
|
|
512
|
+
toggleMuted,
|
|
513
|
+
useMuted,
|
|
514
|
+
} from "@vincentt-xr/sdk";
|
|
515
|
+
|
|
516
|
+
// During the loading gate: buffer every sound so unmute is instant.
|
|
517
|
+
await Promise.all([
|
|
518
|
+
prefetchMusic(musicUrl), // one looping track
|
|
519
|
+
registerSound(catchUrl), // an SFX voice pool
|
|
520
|
+
registerSound(hitUrl),
|
|
521
|
+
]);
|
|
522
|
+
startMusic(musicUrl); // requested now; actually plays on unmute
|
|
523
|
+
|
|
524
|
+
// Fire an effect from anywhere — including a non-React r3f collision callback.
|
|
525
|
+
// No-op while muted; coalesced per URL; no seek on the hot path.
|
|
526
|
+
playOneShot(catchUrl);
|
|
527
|
+
|
|
528
|
+
// The speaker toggle. The first unmute is the gesture that unlocks iOS audio.
|
|
529
|
+
function SoundToggle() {
|
|
530
|
+
const muted = useMuted();
|
|
531
|
+
return <button onClick={toggleMuted}>{muted ? "🔇" : "🔊"}</button>;
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Audio assets: m4a (AAC). A rare app needing a second, independent mixer can
|
|
536
|
+
`new AudioEngine()` and drive it directly; the free functions always target the
|
|
537
|
+
one default engine.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) Vincentt. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and its source code are proprietary and confidential. The
|
|
4
|
+
published package is provided for use with the Vincentt platform only. No
|
|
5
|
+
license is granted to copy, modify, redistribute, reverse-engineer, or create
|
|
6
|
+
derivative works, or to use this software to build a competing product, except
|
|
7
|
+
under a separate written agreement with Vincentt.
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# RTX AR Tracking SDK
|
|
2
|
+
|
|
3
|
+
This SDK is intended for implementing AI-powered tracking for WebAR projects by rangoon.tech.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
First, install the library by running the command
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
npm install @rangoon/track
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If you're using yarn,
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
yarn add @rangoon/track
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## How to use
|
|
19
|
+
The usage of `@rangoon/track` usually involves two steps.
|
|
20
|
+
|
|
21
|
+
### 1. Start a video stream
|
|
22
|
+
Initialize the SDK by attaching the `@rangoon/track`'s `Webcam` component to your React Project.
|
|
23
|
+
The `Webcam` component will start a video stream which will be used for tracking.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { Webcam } from "@rangoon/track"
|
|
27
|
+
import { useRef } from "react"
|
|
28
|
+
|
|
29
|
+
function App() {
|
|
30
|
+
|
|
31
|
+
const videoRef = useRef<HTMLVideoElement>(null)
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div>
|
|
35
|
+
<Webcam videoReference={videoRef}/>
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default App
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Initiate a tracking model
|
|
45
|
+
|
|
46
|
+
Currently, `@rangoon/track` supports three types of tracking models.
|
|
47
|
+
* Hand Tracking (Landmarks)
|
|
48
|
+
* Hand Gesture Tracking (Landmarks + Gestures)
|
|
49
|
+
* Face Tracking
|
|
50
|
+
|
|
51
|
+
By calling the Hook associated with the respective model, you can obtain the tracking data.
|
|
52
|
+
|
|
53
|
+
Below is an example of obtaining hand-tracking data with `useHandTracking` Hook.
|
|
54
|
+
```ts
|
|
55
|
+
import { Webcam, useHandTracking } from "@rangoon/track"
|
|
56
|
+
import { useEffect, useRef } from "react"
|
|
57
|
+
|
|
58
|
+
function App() {
|
|
59
|
+
|
|
60
|
+
const videoRef = useRef<HTMLVideoElement>(null)
|
|
61
|
+
|
|
62
|
+
//Initiate the model by passing the webcam reference
|
|
63
|
+
const handData = useHandTracking({
|
|
64
|
+
videoReference: videoRef
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
//Watch the data changes with useEffect
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
console.log(handData)
|
|
70
|
+
}, [handData])
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div>
|
|
74
|
+
<Webcam videoReference={videoRef}/>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default App
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
## Feedback
|
|
84
|
+
|
|
85
|
+
If you have any feedback, please reach out to us at waing@rangoon.tech
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),x=require("react"),C=require("react-dom"),s=require("./internal-DH5d1Xnq.cjs"),P={position:"fixed",top:"10px",left:"10px",zIndex:9999,fontFamily:"'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace",fontSize:"12px",color:"#fff",pointerEvents:"auto",userSelect:"none"},h={background:"rgba(0, 0, 0, 0.85)",borderRadius:"6px",padding:"12px 16px",boxShadow:"0 4px 12px rgba(0, 0, 0, 0.5)",backdropFilter:"blur(10px)",border:"1px solid rgba(255, 255, 255, 0.1)",minWidth:"240px"},k={...h,padding:"8px 12px",minWidth:"auto"},F={display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"12px",paddingBottom:"8px",borderBottom:"1px solid rgba(255, 255, 255, 0.2)"},M={fontSize:"13px",fontWeight:"bold",color:"#00ff9f",letterSpacing:"0.5px",textTransform:"uppercase"},z={display:"flex",gap:"6px"},f={background:"rgba(255, 255, 255, 0.1)",border:"1px solid rgba(255, 255, 255, 0.2)",borderRadius:"4px",color:"#fff",padding:"4px 8px",fontSize:"11px",cursor:"pointer",transition:"all 0.2s"},R={display:"grid",gridTemplateColumns:"1fr 1fr",gap:"10px"},i={display:"flex",flexDirection:"column",gap:"4px"},o={fontSize:"10px",color:"rgba(255, 255, 255, 0.6)",textTransform:"uppercase",letterSpacing:"0.5px"},d={fontSize:"16px",fontWeight:"bold",color:"#fff"},u={...d,color:"#00ff9f"},g={...d,color:"#ffaa00"},m={...d,color:"#ff4444"},l={fontSize:"11px",color:"rgba(255, 255, 255, 0.5)",marginLeft:"4px"},N={width:"100%",height:"4px",background:"rgba(255, 255, 255, 0.2)",borderRadius:"2px",overflow:"hidden",marginTop:"4px"},w=t=>({width:`${t}%`,height:"100%",background:t>90?"#ff4444":t>70?"#ffaa00":"#00ff9f",transition:"width 0.3s ease, background 0.3s ease"}),D={fontSize:"9px",color:"rgba(255, 255, 255, 0.4)",marginTop:"8px",textAlign:"center",fontStyle:"italic"};function a(t,n){return n==="fps"?t>=30?u:t>=20?g:m:t<=40?u:t<=60?g:m}function B({metrics:t,collapsed:n,onCollapse:y,onClose:b,position:c="top-left",keyboardShortcut:j="ctrl+shift+d"}){const[p,v]=x.useState(null);if(x.useEffect(()=>{const r=document.createElement("div");return r.id="xr-debugger-overlay",document.body.appendChild(r),v(r),()=>{document.body.removeChild(r)}},[]),!p)return null;const S={...P,...c==="top-right"&&{left:"auto",right:"10px"},...c==="bottom-left"&&{top:"auto",bottom:"10px"},...c==="bottom-right"&&{top:"auto",bottom:"10px",left:"auto",right:"10px"}};return C.createPortal(e.jsx("div",{style:S,children:e.jsxs("div",{style:n?k:h,children:[e.jsxs("div",{style:F,children:[e.jsx("div",{style:M,children:n?"AR Debugger":"AR Performance"}),e.jsxs("div",{style:z,children:[e.jsx("button",{style:f,onClick:()=>y(!n),onMouseEnter:r=>{r.currentTarget.style.background="rgba(255, 255, 255, 0.2)"},onMouseLeave:r=>{r.currentTarget.style.background="rgba(255, 255, 255, 0.1)"},children:n?"+":"−"}),e.jsx("button",{style:f,onClick:b,onMouseEnter:r=>{r.currentTarget.style.background="rgba(255, 255, 255, 0.2)"},onMouseLeave:r=>{r.currentTarget.style.background="rgba(255, 255, 255, 0.1)"},children:"✕"})]})]}),!n&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{style:R,children:[e.jsxs("div",{style:i,children:[e.jsx("div",{style:o,children:"Camera FPS"}),e.jsxs("div",{children:[e.jsx("span",{style:a(t.cameraFPS,"fps"),children:s.formatNumber(t.cameraFPS,0)}),e.jsx("span",{style:l,children:"fps"})]})]}),e.jsxs("div",{style:i,children:[e.jsx("div",{style:o,children:"Resolution"}),e.jsx("span",{style:d,children:t.resolution})]}),e.jsxs("div",{style:i,children:[e.jsx("div",{style:o,children:"Detection"}),e.jsxs("div",{children:[e.jsx("span",{style:a(t.detectionTime,"time"),children:s.formatNumber(t.detectionTime,1)}),e.jsx("span",{style:l,children:"ms"})]})]}),e.jsxs("div",{style:i,children:[e.jsx("div",{style:o,children:"Rendering"}),e.jsxs("div",{children:[e.jsx("span",{style:a(t.renderTime,"time"),children:s.formatNumber(t.renderTime,1)}),e.jsx("span",{style:l,children:"ms"})]})]}),e.jsxs("div",{style:i,children:[e.jsx("div",{style:o,children:"Effective FPS"}),e.jsxs("div",{children:[e.jsx("span",{style:a(t.effectiveFPS,"fps"),children:s.formatNumber(t.effectiveFPS,0)}),e.jsx("span",{style:l,children:"fps"})]})]}),e.jsxs("div",{style:i,children:[e.jsx("div",{style:o,children:"Memory"}),t.memory?e.jsxs(e.Fragment,{children:[e.jsxs("div",{children:[e.jsx("span",{style:d,children:s.formatNumber(t.memory.used,0)}),e.jsxs("span",{style:l,children:["/ ",s.formatNumber(t.memory.total,0)," MB"]})]}),e.jsx("div",{style:N,children:e.jsx("div",{style:w(t.memory.percentage)})})]}):e.jsx("span",{style:d,children:"N/A"})]})]}),t.modelPerformance.length>0&&e.jsx(e.Fragment,{children:e.jsxs("div",{style:{borderTop:"1px solid rgba(255, 255, 255, 0.15)",marginTop:"12px",paddingTop:"12px"},children:[e.jsx("div",{style:o,children:"Model Performance"}),e.jsx("div",{style:{marginTop:"8px",display:"flex",flexDirection:"column",gap:"6px"},children:t.modelPerformance.map((r,T)=>e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",fontSize:"11px"},children:[e.jsx("span",{style:{color:"rgba(255, 255, 255, 0.7)"},children:r.name}),e.jsxs("div",{children:[e.jsx("span",{style:a(r.processingTime,"time"),children:s.formatNumber(r.processingTime,1)}),e.jsx("span",{style:l,children:"ms"})]})]},T))})]})}),e.jsxs("div",{style:D,children:["Press ",j.toUpperCase()," to toggle"]})]})]})}),p)}exports.DebuggerUI=B;
|