@realitycollective/native-interactions 0.1.1-preview.2 → 0.1.1-preview.3
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/CHANGELOG.md +11 -0
- package/dist/conformance.d.ts +4 -2
- package/dist/conformance.js +143 -1
- package/dist/conformance.js.map +1 -1
- package/dist/hit-tester.d.ts +7 -5
- package/dist/hit-tester.js +8 -0
- package/dist/hit-tester.js.map +1 -1
- package/dist/host.d.ts +29 -2
- package/dist/host.js +40 -2
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/native-types.d.ts +161 -17
- package/dist/native-types.js +7 -1
- package/dist/native-types.js.map +1 -1
- package/dist/physics-facility.d.ts +41 -0
- package/dist/physics-facility.js +63 -0
- package/dist/physics-facility.js.map +1 -0
- package/dist/provider.d.ts +31 -2
- package/dist/provider.js +48 -5
- package/dist/provider.js.map +1 -1
- package/dist/transform-port.d.ts +15 -4
- package/dist/transform-port.js +16 -1
- package/dist/transform-port.js.map +1 -1
- package/package.json +3 -3
package/dist/native-types.d.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* `[x, y, z, w]`, right handed, +Y up.
|
|
27
27
|
*/
|
|
28
28
|
import type { HeadPose, InputHitHint, InputSourceSnapshot, PoseTuple, QuatTuple, RayTuple, Unsubscribe, Vec3Tuple } from "@realitycollective/webxr-input";
|
|
29
|
-
import type { HoldRelease } from "@realitycollective/webxr-interactions";
|
|
29
|
+
import type { HoldRelease, PhysicsBodySpec, PhysicsBodyState, PhysicsShapeSpec, PhysicsVelocity, PointerVisuals } from "@realitycollective/webxr-interactions";
|
|
30
30
|
/**
|
|
31
31
|
* What the host knows about its session, from which this package derives
|
|
32
32
|
* `InputCapabilities` exactly as `IWSDKInputProvider.refreshCapabilities`
|
|
@@ -47,6 +47,18 @@ export interface NativeInputFacts {
|
|
|
47
47
|
* `"hand-tracking"`. A tracked hand source also counts, without this.
|
|
48
48
|
*/
|
|
49
49
|
handTracking: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* An eye-gaze source is present: OpenXR `XR_EXT_eye_gaze_interaction` is
|
|
52
|
+
* bound and its action is active (`isActive`), on a device with eye
|
|
53
|
+
* tracking and the eye-tracking permission granted; visionOS never
|
|
54
|
+
* reports one (gaze reaches an app only at the moment of a pinch). IWSDK:
|
|
55
|
+
* an `XRInputSource` with `targetRayMode === "gaze"`. While true the
|
|
56
|
+
* binding applies the eye-gaze rule (`@realitycollective/webxr-input`
|
|
57
|
+
* `eye-gaze.ts`): `capabilities.eyeGaze` is true, hand and controller far
|
|
58
|
+
* rays are dropped once a valid gaze pose has been seen, and a pinch
|
|
59
|
+
* selects what is gazed at. The host draws none of this; it reports.
|
|
60
|
+
*/
|
|
61
|
+
eyeTracking: boolean;
|
|
50
62
|
}
|
|
51
63
|
/**
|
|
52
64
|
* What one side's presence visuals should show, handed to the host. The host
|
|
@@ -85,6 +97,16 @@ export interface NativeInputHost {
|
|
|
85
97
|
sample(): readonly InputSourceSnapshot[];
|
|
86
98
|
/** The viewer's head pose this frame. Present on any host that tracks a head; capabilities `gaze` and `headPose` follow it. */
|
|
87
99
|
getHeadPose?(): HeadPose;
|
|
100
|
+
/**
|
|
101
|
+
* This frame's gaze target-ray pose, world space (`-Z` along the gaze),
|
|
102
|
+
* or null when the runtime has no valid pose this frame (a blink, an
|
|
103
|
+
* uncalibrated headset: OpenXR `XrEyeGazeSampleTimeEXT` not current, or
|
|
104
|
+
* the pose's `XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT` clear). Raw: the
|
|
105
|
+
* binding filters it, as IWSDK's `GazePointer` filters
|
|
106
|
+
* `xrOrigin.eyeSpace`. Read every frame while `eyeTracking` is true.
|
|
107
|
+
* Required when `eyeTracking` can be true; without it the fact is ignored.
|
|
108
|
+
*/
|
|
109
|
+
getEyeGazePose?(): PoseTuple | null;
|
|
88
110
|
/**
|
|
89
111
|
* Pre-resolved targeting hints, for a host with its own targeting. Frame
|
|
90
112
|
* fresh: the hints for the frame `sample()` just reported. A hint beats the
|
|
@@ -99,13 +121,28 @@ export interface NativeInputHost {
|
|
|
99
121
|
pulse?(sourceId: string, intensity: number, durationMs: number): boolean;
|
|
100
122
|
/**
|
|
101
123
|
* Show or hide one side's hand mesh and controller model, as decided here
|
|
102
|
-
* (`IWSDKInputProvider.applyPresence`). Presence is the MODELS only:
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* Without this member `capabilities.presence` is false.
|
|
124
|
+
* (`IWSDKInputProvider.applyPresence`). Presence is the MODELS only: it
|
|
125
|
+
* never touches the ray or the cursor, which `applyPointerVisuals` owns.
|
|
126
|
+
* Called only when a side's result changed. Without this member
|
|
127
|
+
* `capabilities.presence` is false.
|
|
107
128
|
*/
|
|
108
129
|
applyPresence?(side: "left" | "right", shown: NativePresenceShown): void;
|
|
130
|
+
/**
|
|
131
|
+
* Draw, or stop drawing, one source's ray and cursor, exactly as told.
|
|
132
|
+
* Decided here by the core near-pointer rule (`near-pointer.ts` in
|
|
133
|
+
* `@realitycollective/webxr-interactions`, IWSDK's `MultiPointer`): the
|
|
134
|
+
* ray shows only while the ray owns the source or no pointer does, never
|
|
135
|
+
* while the fingertip or grip owns it, and never for a source with no ray;
|
|
136
|
+
* the cursor disc shows exactly while the active pointer has a hit, at
|
|
137
|
+
* `cursorPoint` (world metres), the ray's hit or the surface point under
|
|
138
|
+
* the fingertip or grip. A host draws nothing for a source it was not told
|
|
139
|
+
* about, and never draws "a cursor at every ray hit" on its own, which is
|
|
140
|
+
* what this contract said before 28 September 2026 and what put a cursor
|
|
141
|
+
* on a touched object. Called every frame for every sampled source, with
|
|
142
|
+
* fresh objects the host may keep. IWSDK: `RayPointer.update` with
|
|
143
|
+
* `forceHideRay` and `CursorVisual.setVisible`.
|
|
144
|
+
*/
|
|
145
|
+
applyPointerVisuals?(sourceId: string, visuals: PointerVisuals): void;
|
|
109
146
|
}
|
|
110
147
|
/** What the host's ray or proximity query reports: the target it reached, if any. */
|
|
111
148
|
export interface NativeHit {
|
|
@@ -139,6 +176,22 @@ export interface NativeInteractionHost {
|
|
|
139
176
|
* Never the distance to the centre. IWSDK: `EntityHitTester.hitProximity`.
|
|
140
177
|
*/
|
|
141
178
|
hitProximity(point: Vec3Tuple, radius: number): NativeHit | null;
|
|
179
|
+
/**
|
|
180
|
+
* Eye-gaze targeting: the best shown target inside a cone of `halfAngle`
|
|
181
|
+
* radians about `ray`, no farther than `maxLength` metres, or null. A
|
|
182
|
+
* target the ray reaches (as `hitRay`) wins outright with the point where
|
|
183
|
+
* the ray enters it; otherwise the target whose silhouette is nearest the
|
|
184
|
+
* ray in angle, and within half a degree the nearer one, with `point` the
|
|
185
|
+
* point of the target nearest the ray and `distance` metres to it. For a
|
|
186
|
+
* sphere target this is `coneHitForSpheres` in
|
|
187
|
+
* `@realitycollective/webxr-interactions`; a host that tests meshes
|
|
188
|
+
* measures to the closest point on the mesh's bounds, as IWSDK's
|
|
189
|
+
* `GazeConecaster` does with an oriented bounding box. Optional: without
|
|
190
|
+
* it the binding targets eye gaze with `hitRay` alone, so a glance that
|
|
191
|
+
* misses a small target by a degree finds nothing. IWSDK:
|
|
192
|
+
* `GazeConecaster.findFrameBest`.
|
|
193
|
+
*/
|
|
194
|
+
hitCone?(ray: RayTuple, halfAngle: number, maxLength: number): NativeHit | null;
|
|
142
195
|
/**
|
|
143
196
|
* The radius, in metres, the host hit-tests a registered target with.
|
|
144
197
|
* Called once per registration with the app's `targetRadius`, or 0.1 when
|
|
@@ -168,22 +221,102 @@ export interface NativeInteractionHost {
|
|
|
168
221
|
emissive?: number;
|
|
169
222
|
}): void;
|
|
170
223
|
/**
|
|
171
|
-
* A pose-only grab started
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* `
|
|
224
|
+
* A pose-only grab started on a target that has NO body in the `physics`
|
|
225
|
+
* slice: the host lets the object rest where the hold leaves it. A target
|
|
226
|
+
* that has a body is held through the `physics` slice instead
|
|
227
|
+
* (`NativePhysicsHost.suspend`), and this member is not called for it.
|
|
228
|
+
* REQUIRED when grabs are pose-only (`nativeGrab` off). IWSDK: `beginHold`
|
|
229
|
+
* removes the `PhysicsBody`.
|
|
176
230
|
*/
|
|
177
231
|
beginHold(targetId: string): void;
|
|
178
232
|
/**
|
|
179
|
-
* The grab ended
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
233
|
+
* The grab ended on a target with no body in the `physics` slice: the
|
|
234
|
+
* object rests where it was released, as on IWSDK where there is no body
|
|
235
|
+
* to re-add. `release` is the velocity it would have carried (linear m/s
|
|
236
|
+
* and angular rad/s, world space; zeros for a synthesized release). A
|
|
237
|
+
* target with a body is resumed through `NativePhysicsHost.resume` instead.
|
|
184
238
|
*/
|
|
185
239
|
endHold(targetId: string, release: HoldRelease): void;
|
|
186
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* The native host's `physics` slice: the platform's physics engine behind
|
|
243
|
+
* the core `PhysicsFacility` contract (`physics.ts` in
|
|
244
|
+
* `@realitycollective/webxr-interactions`), one body per string id, the same
|
|
245
|
+
* ids the app registers interactables with. The host runs the platform's
|
|
246
|
+
* default engine, Jolt Physics on Quest and Android and RealityKit physics
|
|
247
|
+
* on visionOS, and applies these defaults exactly (IWSDK 1.0.0's, from
|
|
248
|
+
* `@iwsdk/core` `dist/physics/`): gravity `[0, -9.81, 0]` m/s², 60 steps
|
|
249
|
+
* per second with render interpolation, a body dynamic with linear and
|
|
250
|
+
* angular damping 0 and gravity factor 1, a shape "auto" (from the object's
|
|
251
|
+
* geometry) with density 1 kg/m³, restitution 0 and friction 0.5. An app
|
|
252
|
+
* may install its own `PhysicsFacility` here to replace the engine.
|
|
253
|
+
*
|
|
254
|
+
* Units: metres, seconds, radians. Poses are world space, quaternions
|
|
255
|
+
* `[x, y, z, w]`, +Y up. The binding copies every tuple it hands over and
|
|
256
|
+
* every tuple it reads, so the host may reuse its buffers.
|
|
257
|
+
*
|
|
258
|
+
* The host conformance kit runs the whole shared suite
|
|
259
|
+
* (`physicsFacilityContractCases()`) against this slice on the device.
|
|
260
|
+
*/
|
|
261
|
+
export interface NativePhysicsHost {
|
|
262
|
+
/** The engine behind the slice, for reports: `"jolt"`, `"realitykit"`, or an app's own name. Never empty. */
|
|
263
|
+
readonly engine: string;
|
|
264
|
+
/** World gravity, m/s². Starts at `[0, -9.81, 0]`. */
|
|
265
|
+
getGravity(): Vec3Tuple;
|
|
266
|
+
/** Set world gravity, m/s²; every dynamic body, sleeping ones included, sees it from the next step. */
|
|
267
|
+
setGravity(gravity: Vec3Tuple): void;
|
|
268
|
+
/**
|
|
269
|
+
* Add a body for `id` at `pose` with the specs given; a missing field takes
|
|
270
|
+
* the default above. `state`: `"dynamic"` responds to forces, collisions and
|
|
271
|
+
* gravity, `"static"` never moves, `"kinematic"` moves only by `setBodyPose`
|
|
272
|
+
* and pushes dynamic bodies. `shape.kind` `"auto"` is the host's collider
|
|
273
|
+
* for the object's geometry; `"box"` takes full extents in `dimensions`,
|
|
274
|
+
* `"sphere"` its radius in `dimensions[0]`, `"capsule"` radius and height.
|
|
275
|
+
* Adding an id that exists replaces it. IWSDK: `PhysicsBody` and `PhysicsShape`.
|
|
276
|
+
*/
|
|
277
|
+
addBody(id: string, pose: PoseTuple, body?: PhysicsBodySpec, shape?: PhysicsShapeSpec): void;
|
|
278
|
+
/** Remove the body; a missing id is ignored. */
|
|
279
|
+
removeBody(id: string): void;
|
|
280
|
+
hasBody(id: string): boolean;
|
|
281
|
+
/** Change how the body moves; a suspended body takes the new state when it resumes. */
|
|
282
|
+
setBodyState(id: string, state: PhysicsBodyState): void;
|
|
283
|
+
getBodyState(id: string): PhysicsBodyState;
|
|
284
|
+
/** Where the body is now, world space. Throws an error whose message contains `no physics body "<id>"` for an unknown id. */
|
|
285
|
+
getBodyPose(id: string): PoseTuple;
|
|
286
|
+
/**
|
|
287
|
+
* Teleport: the body is at `pose` from the next step with its velocity
|
|
288
|
+
* cleared, so it rests there rather than carrying what it did. While
|
|
289
|
+
* suspended the write is exact and carries nothing. IWSDK:
|
|
290
|
+
* `PhysicsSystem.setBodyTransform`.
|
|
291
|
+
*/
|
|
292
|
+
setBodyPose(id: string, pose: PoseTuple): void;
|
|
293
|
+
/** Linear m/s and angular rad/s (axis scaled), world space. */
|
|
294
|
+
getVelocity(id: string): PhysicsVelocity;
|
|
295
|
+
/** Set both velocities. IWSDK: `PhysicsManipulation`. */
|
|
296
|
+
setVelocity(id: string, velocity: PhysicsVelocity): void;
|
|
297
|
+
/**
|
|
298
|
+
* A hold began: from here until `resume` the body is not simulated (no
|
|
299
|
+
* gravity, no collision response) and `setBodyPose` writes are exact. A
|
|
300
|
+
* second `suspend` changes nothing. IWSDK: `beginHold` removes the body.
|
|
301
|
+
*/
|
|
302
|
+
suspend(id: string): void;
|
|
303
|
+
/**
|
|
304
|
+
* The hold ended: simulate the body again in the state it had, with
|
|
305
|
+
* `release` as its velocity so a throw carries through (zeros rest it).
|
|
306
|
+
* Resuming a body that is not suspended changes nothing. IWSDK:
|
|
307
|
+
* `endHold` re-adds the body with a `PhysicsManipulation`.
|
|
308
|
+
*/
|
|
309
|
+
resume(id: string, release: HoldRelease): void;
|
|
310
|
+
isSuspended(id: string): boolean;
|
|
311
|
+
/**
|
|
312
|
+
* Advance the world by `dtSeconds`. A host whose engine steps itself from
|
|
313
|
+
* its own loop may take this as a hint and return; the kit then reads the
|
|
314
|
+
* poses the engine wrote. The binding calls it once per `update(dt)`.
|
|
315
|
+
*/
|
|
316
|
+
step(dtSeconds: number): void;
|
|
317
|
+
/** Release the world and every body. */
|
|
318
|
+
dispose(): void;
|
|
319
|
+
}
|
|
187
320
|
/**
|
|
188
321
|
* Test-only readbacks a host provides so the host conformance kit
|
|
189
322
|
* (`nativeInteractionsHostConformanceCases`) can check what the host
|
|
@@ -200,6 +333,10 @@ export interface NativeInteractionsTestHost {
|
|
|
200
333
|
lastRelease(targetId: string): HoldRelease | undefined;
|
|
201
334
|
/** Every cursor disc the host draws now, as world positions. */
|
|
202
335
|
cursors(): Vec3Tuple[];
|
|
336
|
+
/** What the host draws for one source now, as last told through `applyPointerVisuals`; undefined for a source never told. */
|
|
337
|
+
pointerVisuals?(sourceId: string): PointerVisuals | undefined;
|
|
338
|
+
/** Hide or show a placed target with the host's ordinary visibility flag, for the hidden-target cone case. Optional. */
|
|
339
|
+
setTargetVisible?(targetId: string, visible: boolean): void;
|
|
203
340
|
}
|
|
204
341
|
/**
|
|
205
342
|
* The native app's frame callback, the root member of `__rcHost` that
|
|
@@ -208,10 +345,15 @@ export interface NativeInteractionsTestHost {
|
|
|
208
345
|
export interface NativeFrameSource {
|
|
209
346
|
onFrame(callback: (timestampMs: number, deltaS: number) => void): () => void;
|
|
210
347
|
}
|
|
211
|
-
/**
|
|
348
|
+
/**
|
|
349
|
+
* The slices this package reads off `globalThis.__rcHost`. `physics` is
|
|
350
|
+
* part of the contract on every native platform: a host without it fails
|
|
351
|
+
* the conformance kit, and a target cannot carry a body until it is there.
|
|
352
|
+
*/
|
|
212
353
|
export interface NativeHostSlices {
|
|
213
354
|
input: NativeInputHost;
|
|
214
355
|
interactions: NativeInteractionHost;
|
|
356
|
+
physics: NativePhysicsHost;
|
|
215
357
|
}
|
|
216
358
|
/**
|
|
217
359
|
* `globalThis.__rcHost`, read defensively: the root `NativeHost` interface
|
|
@@ -227,6 +369,8 @@ export declare function installedHost(): (Partial<NativeHostSlices> & Partial<Na
|
|
|
227
369
|
* time something calls a method that is not there.
|
|
228
370
|
*/
|
|
229
371
|
export declare function resolveHostSlice<K extends keyof NativeHostSlices>(name: K, injected: NativeHostSlices[K] | undefined): NativeHostSlices[K];
|
|
372
|
+
/** The value passed in, or `globalThis.__rcHost`'s slice of that name, or undefined. */
|
|
373
|
+
export declare function findHostSlice<K extends keyof NativeHostSlices>(name: K, injected: NativeHostSlices[K] | undefined): NativeHostSlices[K] | undefined;
|
|
230
374
|
/** Copy a position tuple. */
|
|
231
375
|
export declare function copyVec3(v: Vec3Tuple): Vec3Tuple;
|
|
232
376
|
/** Copy an orientation tuple. */
|
package/dist/native-types.js
CHANGED
|
@@ -14,12 +14,16 @@ export function installedHost() {
|
|
|
14
14
|
* time something calls a method that is not there.
|
|
15
15
|
*/
|
|
16
16
|
export function resolveHostSlice(name, injected) {
|
|
17
|
-
const slice = injected
|
|
17
|
+
const slice = findHostSlice(name, injected);
|
|
18
18
|
if (!slice) {
|
|
19
19
|
throw new Error(`@realitycollective/native-interactions: no "${name}" slice was supplied and globalThis.__rcHost.${name} is not installed. Pass one directly, or have the native app install it before this package is constructed.`);
|
|
20
20
|
}
|
|
21
21
|
return slice;
|
|
22
22
|
}
|
|
23
|
+
/** The value passed in, or `globalThis.__rcHost`'s slice of that name, or undefined. */
|
|
24
|
+
export function findHostSlice(name, injected) {
|
|
25
|
+
return injected ?? installedHost()?.[name];
|
|
26
|
+
}
|
|
23
27
|
// ---------------------------------------------------------------------------
|
|
24
28
|
// Copies. A host may reuse its own buffers across calls, so every tuple that
|
|
25
29
|
// crosses back into this package is copied on the way in, never referenced.
|
|
@@ -68,6 +72,8 @@ export function copySnapshot(source) {
|
|
|
68
72
|
copy.nativeGrabbing = source.nativeGrabbing;
|
|
69
73
|
if (source.hapticsAvailable !== undefined)
|
|
70
74
|
copy.hapticsAvailable = source.hapticsAvailable;
|
|
75
|
+
if (source.selectorPose)
|
|
76
|
+
copy.selectorPose = copyPose(source.selectorPose);
|
|
71
77
|
return copy;
|
|
72
78
|
}
|
|
73
79
|
//# sourceMappingURL=native-types.js.map
|
package/dist/native-types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-types.js","sourceRoot":"","sources":["../src/native-types.ts"],"names":[],"mappings":"AAsOA;;;;;GAKG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAQ,UAAoF,CAAC,QAAQ,CAAC;AACxG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAO,EACP,QAAyC;IAEzC,MAAM,KAAK,GAAG,QAAQ,IAAK,aAAa,EAA4C,EAAE,CAAC,IAAI,CAAC,CAAC;IAC7F,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,+CAA+C,IAAI,gDAAgD,IAAI,6GAA6G,CACrN,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAE9E,6BAA6B;AAC7B,MAAM,UAAU,QAAQ,CAAC,CAAY;IACnC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED,iCAAiC;AACjC,MAAM,UAAU,QAAQ,CAAC,CAAY;IACnC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,QAAQ,CAAC,IAAe;IACtC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;AACtF,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,OAAO,CAAC,GAAa;IACnC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;AAC9E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAA2B;IACtD,MAAM,IAAI,GAAwB;QAChC,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC;IACF,IAAI,MAAM,CAAC,GAAG;QAAE,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,MAAM,CAAC,QAAQ;QAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/D,IAAI,MAAM,CAAC,QAAQ;QAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/D,IAAI,MAAM,CAAC,cAAc;QAAE,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IACjF,IAAI,MAAM,CAAC,eAAe;QAAE,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACpF,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS;QAAE,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC;IACrF,IAAI,MAAM,CAAC,gBAAgB,KAAK,SAAS;QAAE,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,CAAC;IAC3F,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["/**\n * The `input` and `interactions` slices a native host (OpenXR on Quest,\n * CompositorServices on visionOS, or any other shell embedding a JavaScript\n * engine such as Hermes) installs on `globalThis.__rcHost`.\n *\n * A HOST IS HANDED RESULTS, NOT RULES. Every rule the IWSDK binding applies\n * in its provider runs in this package from the same inputs: capabilities\n * are derived here from the facts the host reports, the presence modality\n * is decided here and handed to the host per side, and a target's radius is\n * handed to the host at registration. The host measures, renders and\n * reports. Each member below states what the host does, in what units and\n * with what sign, and which IWSDK line it stands in for.\n *\n * `NativeInteractionHost` is `HitTester` plus `TransformPort` from\n * `@realitycollective/webxr-interactions`, with a `targetId` added to every\n * `TransformPort` member because that port is per object and the host is one\n * object serving every registered interactable. Declaring the shapes here,\n * rather than reusing the originals by reference, is deliberate: this file is\n * the one place that states what crosses the native boundary, in the same\n * structural-typing style as `babylon-types.ts` and the XR Blocks `XB*Like`\n * types.\n *\n * Values that cross are plain: numbers, strings, booleans and tuples. No\n * engine objects in either direction, and assets stay on the native side.\n * Units are metres, seconds and radians; poses are world space, quaternions\n * `[x, y, z, w]`, right handed, +Y up.\n */\nimport type {\n HeadPose,\n InputHitHint,\n InputSourceSnapshot,\n PoseTuple,\n QuatTuple,\n RayTuple,\n Unsubscribe,\n Vec3Tuple,\n} from \"@realitycollective/webxr-input\";\nimport type { HoldRelease } from \"@realitycollective/webxr-interactions\";\n\n/**\n * What the host knows about its session, from which this package derives\n * `InputCapabilities` exactly as `IWSDKInputProvider.refreshCapabilities`\n * does from the WebXR session.\n */\nexport interface NativeInputFacts {\n /** A session is presenting: OpenXR `SYNCHRONIZED`, `VISIBLE` or `FOCUSED`. IWSDK: `world.session` exists. */\n immersive: boolean;\n /**\n * The session has input focus: OpenXR `FOCUSED`. While false no sources are\n * sampled, as IWSDK's provider returns none unless the visibility state is\n * `Visible`.\n */\n focused: boolean;\n /**\n * Hand tracking is enabled on the session (`XR_EXT_hand_tracking` and the\n * system supports it). IWSDK: `session.enabledFeatures` includes\n * `\"hand-tracking\"`. A tracked hand source also counts, without this.\n */\n handTracking: boolean;\n}\n\n/**\n * What one side's presence visuals should show, handed to the host. The host\n * draws exactly this and decides nothing: which family is shown for\n * `\"auto\"`, and which side a request targets, are this package's.\n */\nexport interface NativePresenceShown {\n /** Draw this side's hand mesh. */\n hand: boolean;\n /** Draw this side's controller model. */\n controller: boolean;\n}\n\n/**\n * The native host's `input` slice. The host reports facts, sources and\n * signals; `NativeInputProvider` turns them into the `InputProvider`\n * contract.\n */\nexport interface NativeInputHost {\n /** This moment's session facts. Read at construction and on every signal. */\n getFacts(): NativeInputFacts;\n /** The facts changed: session start or end, focus gained or lost. */\n onFactsChanged(listener: () => void): Unsubscribe;\n /** A source connected or disconnected (WebXR `inputsourceschange`). Capabilities re-derive on it. */\n onSourcesChanged(listener: () => void): Unsubscribe;\n /**\n * This frame's tracked sources, in the `InputSourceSnapshot` shape. `kind`\n * is `\"hand\"` while hand joints are tracked, else `\"controller\"`.\n * `select` is the trigger value, or 1 while the runtime reports selecting\n * (a hand pinch), 0..1; `squeeze` is the grip value, 0 for hands.\n * `gripPose` is the WebXR GRIP frame, not a hand joint - see\n * `InputSourceSnapshot.gripPose`. `indexTip` is the index fingertip for a\n * hand and the ray origin for a controller. `hapticsAvailable` is true when\n * the source has an actuator. The host may reuse its buffers: this package\n * copies every snapshot.\n */\n sample(): readonly InputSourceSnapshot[];\n /** The viewer's head pose this frame. Present on any host that tracks a head; capabilities `gaze` and `headPose` follow it. */\n getHeadPose?(): HeadPose;\n /**\n * Pre-resolved targeting hints, for a host with its own targeting. Frame\n * fresh: the hints for the frame `sample()` just reported. A hint beats the\n * core's own hit tests, and is equivalent to `nativeGrabbing` for a grab.\n */\n sampleHints?(): readonly InputHitHint[];\n /**\n * Fire a haptic pulse on a source. `intensity` 0..1 (already clamped),\n * `durationMs` in milliseconds. Returns false when it could not be\n * delivered. IWSDK: `actuator.pulse(intensity, durationMs)`.\n */\n pulse?(sourceId: string, intensity: number, durationMs: number): boolean;\n /**\n * Show or hide one side's hand mesh and controller model, as decided here\n * (`IWSDKInputProvider.applyPresence`). Presence is the MODELS only: the\n * host never draws a ray for a hand, and it draws a cursor disc at every\n * ray's hit on a panel or an interactable whatever presence says, as\n * IWSDK's `CursorVisual` is. Called only when a side's result changed.\n * Without this member `capabilities.presence` is false.\n */\n applyPresence?(side: \"left\" | \"right\", shown: NativePresenceShown): void;\n}\n\n/** What the host's ray or proximity query reports: the target it reached, if any. */\nexport interface NativeHit {\n /** The target id the app registered with the native scene. */\n targetId: string;\n /** `hitRay`: the ray parameter t, metres. `hitProximity`: metres to the target's SURFACE, never negative. */\n distance: number;\n /** World-space hit point, or the target's centre. */\n point: Vec3Tuple;\n}\n\n/**\n * The native host's `interactions` slice: `HitTester` with its semantics\n * stated, and `TransformPort` with every member keyed by the target id the\n * app chose when it registered the object with the native scene.\n */\nexport interface NativeInteractionHost {\n /**\n * The nearest shown target along `ray` (origin in metres, direction\n * normalised). A target counts when its centre is within its radius of the\n * ray line and in front of the origin; `distance` is the ray parameter of\n * the closest point, and `t <= 0` never hits. A host that tests triangle\n * meshes instead may report the surface it hit; the contract cases accept\n * any answer within the target's radius plus 0.05 m of the sphere answer.\n * IWSDK: `EntityHitTester.hitRay`.\n */\n hitRay(ray: RayTuple): NativeHit | null;\n /**\n * The nearest shown target whose SURFACE is within `radius` metres of\n * `point`. `distance = max(0, |centre - point| - targetRadius)`: a point\n * 3 cm outside a 10 cm target reports 0.03, a point inside reports 0.\n * Never the distance to the centre. IWSDK: `EntityHitTester.hitProximity`.\n */\n hitProximity(point: Vec3Tuple, radius: number): NativeHit | null;\n /**\n * The radius, in metres, the host hit-tests a registered target with.\n * Called once per registration with the app's `targetRadius`, or 0.1 when\n * it gave none, as IWSDK registers a bare target as a 10 cm sphere\n * (`register.ts`, `options.targetRadius ?? 0.1`). A host never excludes a\n * target from hit testing because of its radius.\n */\n setTargetRadius(targetId: string, radius: number): void;\n /** Where the object is now. */\n getWorldPose(targetId: string): PoseTuple;\n /** The rest pose captured at registration, in world space. */\n getRestWorldPose(targetId: string): PoseTuple;\n /** The offset from rest last written, metres, in the rest frame. */\n getLocalOffset(targetId: string): Vec3Tuple;\n /** Offset the object from its rest pose, metres, in the rest frame. */\n setLocalOffset(targetId: string, offset: Vec3Tuple): void;\n /** Rotate the object from its rest orientation. */\n setLocalRotation(targetId: string, quaternion: QuatTuple): void;\n /** Place the object at a world pose (the pose-only grab carry). */\n setWorldPose?(targetId: string, pose: PoseTuple): void;\n /**\n * The pulse effect: `scale` multiplies the rest scale, `emissive` is added\n * to the base emissive intensity. Last write wins per field.\n */\n setEffect?(targetId: string, effect: { scale?: number; emissive?: number }): void;\n /**\n * A pose-only grab started: suspend this target's physics body, if it has\n * one, so it follows `setWorldPose` exactly. REQUIRED when grabs are\n * pose-only (`nativeGrab` off). A target with no body needs nothing.\n * IWSDK: `beginHold` removes the `PhysicsBody` (`register.ts`,\n * `physicsBindingFor`).\n */\n beginHold(targetId: string): void;\n /**\n * The grab ended: resume the body with `release` as its velocity (linear\n * m/s and angular rad/s, world space; zeros for a synthesized release,\n * which rests). A target with NO physics body rests where it was released,\n * as on IWSDK where there is no body to re-add. IWSDK: `endHold` re-adds\n * the `PhysicsBody` and, for a non-zero velocity, a `PhysicsManipulation`.\n */\n endHold(targetId: string, release: HoldRelease): void;\n}\n\n/**\n * Test-only readbacks a host provides so the host conformance kit\n * (`nativeInteractionsHostConformanceCases`) can check what the host\n * actually did. A shipping host may omit them.\n */\nexport interface NativeInteractionsTestHost {\n /** Put a shown, hit-testable target of `radius` metres at `position`, as the app's scene would. */\n placeTarget(targetId: string, position: Vec3Tuple, radius: number): void;\n /** Remove every target `placeTarget` put in. */\n clearTargets(): void;\n /** What the host draws for one side now. */\n presenceShown(side: \"left\" | \"right\"): NativePresenceShown | undefined;\n /** The last release the host received for a target through `endHold`. */\n lastRelease(targetId: string): HoldRelease | undefined;\n /** Every cursor disc the host draws now, as world positions. */\n cursors(): Vec3Tuple[];\n}\n\n/**\n * The native app's frame callback, the root member of `__rcHost` that\n * `NativeInteractions` attaches to when `attachToHost` is set.\n */\nexport interface NativeFrameSource {\n onFrame(callback: (timestampMs: number, deltaS: number) => void): () => void;\n}\n\n/** The two slices this package reads off `globalThis.__rcHost`. */\nexport interface NativeHostSlices {\n input: NativeInputHost;\n interactions: NativeInteractionHost;\n}\n\n/**\n * `globalThis.__rcHost`, read defensively: the root `NativeHost` interface\n * belongs to `service-framework-native`, which this package does not depend\n * on, so the global is read as an unknown bag of optional slices rather than\n * imported.\n */\nexport function installedHost(): (Partial<NativeHostSlices> & Partial<NativeFrameSource>) | undefined {\n return (globalThis as { __rcHost?: Partial<NativeHostSlices> & Partial<NativeFrameSource> }).__rcHost;\n}\n\n/**\n * Resolve one slice: the value passed in, or `globalThis.__rcHost`'s slice\n * of the same name. Throws one clear error naming the missing slice, so a\n * native-interactions class fails at construction rather than the first\n * time something calls a method that is not there.\n */\nexport function resolveHostSlice<K extends keyof NativeHostSlices>(\n name: K,\n injected: NativeHostSlices[K] | undefined,\n): NativeHostSlices[K] {\n const slice = injected ?? (installedHost() as Partial<NativeHostSlices> | undefined)?.[name];\n if (!slice) {\n throw new Error(\n `@realitycollective/native-interactions: no \"${name}\" slice was supplied and globalThis.__rcHost.${name} is not installed. Pass one directly, or have the native app install it before this package is constructed.`,\n );\n }\n return slice;\n}\n\n// ---------------------------------------------------------------------------\n// Copies. A host may reuse its own buffers across calls, so every tuple that\n// crosses back into this package is copied on the way in, never referenced.\n// ---------------------------------------------------------------------------\n\n/** Copy a position tuple. */\nexport function copyVec3(v: Vec3Tuple): Vec3Tuple {\n return [v[0], v[1], v[2]];\n}\n\n/** Copy an orientation tuple. */\nexport function copyQuat(q: QuatTuple): QuatTuple {\n return [q[0], q[1], q[2], q[3]];\n}\n\n/** Copy a pose (position + orientation). */\nexport function copyPose(pose: PoseTuple): PoseTuple {\n return { position: copyVec3(pose.position), quaternion: copyQuat(pose.quaternion) };\n}\n\n/** Copy a ray (origin + direction). */\nexport function copyRay(ray: RayTuple): RayTuple {\n return { origin: copyVec3(ray.origin), direction: copyVec3(ray.direction) };\n}\n\n/**\n * Copy one `InputSourceSnapshot` field by field, including every tuple it\n * carries, so a host that pools and refills its own snapshot objects still\n * meets the ownership rule: a snapshot handed to `sample()`'s caller is\n * never written to again.\n */\nexport function copySnapshot(source: InputSourceSnapshot): InputSourceSnapshot {\n const copy: InputSourceSnapshot = {\n id: source.id,\n kind: source.kind,\n handedness: source.handedness,\n select: source.select,\n squeeze: source.squeeze,\n };\n if (source.ray) copy.ray = copyRay(source.ray);\n if (source.gripPose) copy.gripPose = copyPose(source.gripPose);\n if (source.indexTip) copy.indexTip = copyVec3(source.indexTip);\n if (source.linearVelocity) copy.linearVelocity = copyVec3(source.linearVelocity);\n if (source.angularVelocity) copy.angularVelocity = copyVec3(source.angularVelocity);\n if (source.nativeGrabbing !== undefined) copy.nativeGrabbing = source.nativeGrabbing;\n if (source.hapticsAvailable !== undefined) copy.hapticsAvailable = source.hapticsAvailable;\n return copy;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"native-types.js","sourceRoot":"","sources":["../src/native-types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The `input` and `interactions` slices a native host (OpenXR on Quest,\n * CompositorServices on visionOS, or any other shell embedding a JavaScript\n * engine such as Hermes) installs on `globalThis.__rcHost`.\n *\n * A HOST IS HANDED RESULTS, NOT RULES. Every rule the IWSDK binding applies\n * in its provider runs in this package from the same inputs: capabilities\n * are derived here from the facts the host reports, the presence modality\n * is decided here and handed to the host per side, and a target's radius is\n * handed to the host at registration. The host measures, renders and\n * reports. Each member below states what the host does, in what units and\n * with what sign, and which IWSDK line it stands in for.\n *\n * `NativeInteractionHost` is `HitTester` plus `TransformPort` from\n * `@realitycollective/webxr-interactions`, with a `targetId` added to every\n * `TransformPort` member because that port is per object and the host is one\n * object serving every registered interactable. Declaring the shapes here,\n * rather than reusing the originals by reference, is deliberate: this file is\n * the one place that states what crosses the native boundary, in the same\n * structural-typing style as `babylon-types.ts` and the XR Blocks `XB*Like`\n * types.\n *\n * Values that cross are plain: numbers, strings, booleans and tuples. No\n * engine objects in either direction, and assets stay on the native side.\n * Units are metres, seconds and radians; poses are world space, quaternions\n * `[x, y, z, w]`, right handed, +Y up.\n */\nimport type {\n HeadPose,\n InputHitHint,\n InputSourceSnapshot,\n PoseTuple,\n QuatTuple,\n RayTuple,\n Unsubscribe,\n Vec3Tuple,\n} from \"@realitycollective/webxr-input\";\nimport type {\n HoldRelease,\n PhysicsBodySpec,\n PhysicsBodyState,\n PhysicsShapeSpec,\n PhysicsVelocity,\n PointerVisuals,\n} from \"@realitycollective/webxr-interactions\";\n\n/**\n * What the host knows about its session, from which this package derives\n * `InputCapabilities` exactly as `IWSDKInputProvider.refreshCapabilities`\n * does from the WebXR session.\n */\nexport interface NativeInputFacts {\n /** A session is presenting: OpenXR `SYNCHRONIZED`, `VISIBLE` or `FOCUSED`. IWSDK: `world.session` exists. */\n immersive: boolean;\n /**\n * The session has input focus: OpenXR `FOCUSED`. While false no sources are\n * sampled, as IWSDK's provider returns none unless the visibility state is\n * `Visible`.\n */\n focused: boolean;\n /**\n * Hand tracking is enabled on the session (`XR_EXT_hand_tracking` and the\n * system supports it). IWSDK: `session.enabledFeatures` includes\n * `\"hand-tracking\"`. A tracked hand source also counts, without this.\n */\n handTracking: boolean;\n /**\n * An eye-gaze source is present: OpenXR `XR_EXT_eye_gaze_interaction` is\n * bound and its action is active (`isActive`), on a device with eye\n * tracking and the eye-tracking permission granted; visionOS never\n * reports one (gaze reaches an app only at the moment of a pinch). IWSDK:\n * an `XRInputSource` with `targetRayMode === \"gaze\"`. While true the\n * binding applies the eye-gaze rule (`@realitycollective/webxr-input`\n * `eye-gaze.ts`): `capabilities.eyeGaze` is true, hand and controller far\n * rays are dropped once a valid gaze pose has been seen, and a pinch\n * selects what is gazed at. The host draws none of this; it reports.\n */\n eyeTracking: boolean;\n}\n\n/**\n * What one side's presence visuals should show, handed to the host. The host\n * draws exactly this and decides nothing: which family is shown for\n * `\"auto\"`, and which side a request targets, are this package's.\n */\nexport interface NativePresenceShown {\n /** Draw this side's hand mesh. */\n hand: boolean;\n /** Draw this side's controller model. */\n controller: boolean;\n}\n\n/**\n * The native host's `input` slice. The host reports facts, sources and\n * signals; `NativeInputProvider` turns them into the `InputProvider`\n * contract.\n */\nexport interface NativeInputHost {\n /** This moment's session facts. Read at construction and on every signal. */\n getFacts(): NativeInputFacts;\n /** The facts changed: session start or end, focus gained or lost. */\n onFactsChanged(listener: () => void): Unsubscribe;\n /** A source connected or disconnected (WebXR `inputsourceschange`). Capabilities re-derive on it. */\n onSourcesChanged(listener: () => void): Unsubscribe;\n /**\n * This frame's tracked sources, in the `InputSourceSnapshot` shape. `kind`\n * is `\"hand\"` while hand joints are tracked, else `\"controller\"`.\n * `select` is the trigger value, or 1 while the runtime reports selecting\n * (a hand pinch), 0..1; `squeeze` is the grip value, 0 for hands.\n * `gripPose` is the WebXR GRIP frame, not a hand joint - see\n * `InputSourceSnapshot.gripPose`. `indexTip` is the index fingertip for a\n * hand and the ray origin for a controller. `hapticsAvailable` is true when\n * the source has an actuator. The host may reuse its buffers: this package\n * copies every snapshot.\n */\n sample(): readonly InputSourceSnapshot[];\n /** The viewer's head pose this frame. Present on any host that tracks a head; capabilities `gaze` and `headPose` follow it. */\n getHeadPose?(): HeadPose;\n /**\n * This frame's gaze target-ray pose, world space (`-Z` along the gaze),\n * or null when the runtime has no valid pose this frame (a blink, an\n * uncalibrated headset: OpenXR `XrEyeGazeSampleTimeEXT` not current, or\n * the pose's `XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT` clear). Raw: the\n * binding filters it, as IWSDK's `GazePointer` filters\n * `xrOrigin.eyeSpace`. Read every frame while `eyeTracking` is true.\n * Required when `eyeTracking` can be true; without it the fact is ignored.\n */\n getEyeGazePose?(): PoseTuple | null;\n /**\n * Pre-resolved targeting hints, for a host with its own targeting. Frame\n * fresh: the hints for the frame `sample()` just reported. A hint beats the\n * core's own hit tests, and is equivalent to `nativeGrabbing` for a grab.\n */\n sampleHints?(): readonly InputHitHint[];\n /**\n * Fire a haptic pulse on a source. `intensity` 0..1 (already clamped),\n * `durationMs` in milliseconds. Returns false when it could not be\n * delivered. IWSDK: `actuator.pulse(intensity, durationMs)`.\n */\n pulse?(sourceId: string, intensity: number, durationMs: number): boolean;\n /**\n * Show or hide one side's hand mesh and controller model, as decided here\n * (`IWSDKInputProvider.applyPresence`). Presence is the MODELS only: it\n * never touches the ray or the cursor, which `applyPointerVisuals` owns.\n * Called only when a side's result changed. Without this member\n * `capabilities.presence` is false.\n */\n applyPresence?(side: \"left\" | \"right\", shown: NativePresenceShown): void;\n /**\n * Draw, or stop drawing, one source's ray and cursor, exactly as told.\n * Decided here by the core near-pointer rule (`near-pointer.ts` in\n * `@realitycollective/webxr-interactions`, IWSDK's `MultiPointer`): the\n * ray shows only while the ray owns the source or no pointer does, never\n * while the fingertip or grip owns it, and never for a source with no ray;\n * the cursor disc shows exactly while the active pointer has a hit, at\n * `cursorPoint` (world metres), the ray's hit or the surface point under\n * the fingertip or grip. A host draws nothing for a source it was not told\n * about, and never draws \"a cursor at every ray hit\" on its own, which is\n * what this contract said before 28 September 2026 and what put a cursor\n * on a touched object. Called every frame for every sampled source, with\n * fresh objects the host may keep. IWSDK: `RayPointer.update` with\n * `forceHideRay` and `CursorVisual.setVisible`.\n */\n applyPointerVisuals?(sourceId: string, visuals: PointerVisuals): void;\n}\n\n/** What the host's ray or proximity query reports: the target it reached, if any. */\nexport interface NativeHit {\n /** The target id the app registered with the native scene. */\n targetId: string;\n /** `hitRay`: the ray parameter t, metres. `hitProximity`: metres to the target's SURFACE, never negative. */\n distance: number;\n /** World-space hit point, or the target's centre. */\n point: Vec3Tuple;\n}\n\n/**\n * The native host's `interactions` slice: `HitTester` with its semantics\n * stated, and `TransformPort` with every member keyed by the target id the\n * app chose when it registered the object with the native scene.\n */\nexport interface NativeInteractionHost {\n /**\n * The nearest shown target along `ray` (origin in metres, direction\n * normalised). A target counts when its centre is within its radius of the\n * ray line and in front of the origin; `distance` is the ray parameter of\n * the closest point, and `t <= 0` never hits. A host that tests triangle\n * meshes instead may report the surface it hit; the contract cases accept\n * any answer within the target's radius plus 0.05 m of the sphere answer.\n * IWSDK: `EntityHitTester.hitRay`.\n */\n hitRay(ray: RayTuple): NativeHit | null;\n /**\n * The nearest shown target whose SURFACE is within `radius` metres of\n * `point`. `distance = max(0, |centre - point| - targetRadius)`: a point\n * 3 cm outside a 10 cm target reports 0.03, a point inside reports 0.\n * Never the distance to the centre. IWSDK: `EntityHitTester.hitProximity`.\n */\n hitProximity(point: Vec3Tuple, radius: number): NativeHit | null;\n /**\n * Eye-gaze targeting: the best shown target inside a cone of `halfAngle`\n * radians about `ray`, no farther than `maxLength` metres, or null. A\n * target the ray reaches (as `hitRay`) wins outright with the point where\n * the ray enters it; otherwise the target whose silhouette is nearest the\n * ray in angle, and within half a degree the nearer one, with `point` the\n * point of the target nearest the ray and `distance` metres to it. For a\n * sphere target this is `coneHitForSpheres` in\n * `@realitycollective/webxr-interactions`; a host that tests meshes\n * measures to the closest point on the mesh's bounds, as IWSDK's\n * `GazeConecaster` does with an oriented bounding box. Optional: without\n * it the binding targets eye gaze with `hitRay` alone, so a glance that\n * misses a small target by a degree finds nothing. IWSDK:\n * `GazeConecaster.findFrameBest`.\n */\n hitCone?(ray: RayTuple, halfAngle: number, maxLength: number): NativeHit | null;\n /**\n * The radius, in metres, the host hit-tests a registered target with.\n * Called once per registration with the app's `targetRadius`, or 0.1 when\n * it gave none, as IWSDK registers a bare target as a 10 cm sphere\n * (`register.ts`, `options.targetRadius ?? 0.1`). A host never excludes a\n * target from hit testing because of its radius.\n */\n setTargetRadius(targetId: string, radius: number): void;\n /** Where the object is now. */\n getWorldPose(targetId: string): PoseTuple;\n /** The rest pose captured at registration, in world space. */\n getRestWorldPose(targetId: string): PoseTuple;\n /** The offset from rest last written, metres, in the rest frame. */\n getLocalOffset(targetId: string): Vec3Tuple;\n /** Offset the object from its rest pose, metres, in the rest frame. */\n setLocalOffset(targetId: string, offset: Vec3Tuple): void;\n /** Rotate the object from its rest orientation. */\n setLocalRotation(targetId: string, quaternion: QuatTuple): void;\n /** Place the object at a world pose (the pose-only grab carry). */\n setWorldPose?(targetId: string, pose: PoseTuple): void;\n /**\n * The pulse effect: `scale` multiplies the rest scale, `emissive` is added\n * to the base emissive intensity. Last write wins per field.\n */\n setEffect?(targetId: string, effect: { scale?: number; emissive?: number }): void;\n /**\n * A pose-only grab started on a target that has NO body in the `physics`\n * slice: the host lets the object rest where the hold leaves it. A target\n * that has a body is held through the `physics` slice instead\n * (`NativePhysicsHost.suspend`), and this member is not called for it.\n * REQUIRED when grabs are pose-only (`nativeGrab` off). IWSDK: `beginHold`\n * removes the `PhysicsBody`.\n */\n beginHold(targetId: string): void;\n /**\n * The grab ended on a target with no body in the `physics` slice: the\n * object rests where it was released, as on IWSDK where there is no body\n * to re-add. `release` is the velocity it would have carried (linear m/s\n * and angular rad/s, world space; zeros for a synthesized release). A\n * target with a body is resumed through `NativePhysicsHost.resume` instead.\n */\n endHold(targetId: string, release: HoldRelease): void;\n}\n\n/**\n * The native host's `physics` slice: the platform's physics engine behind\n * the core `PhysicsFacility` contract (`physics.ts` in\n * `@realitycollective/webxr-interactions`), one body per string id, the same\n * ids the app registers interactables with. The host runs the platform's\n * default engine, Jolt Physics on Quest and Android and RealityKit physics\n * on visionOS, and applies these defaults exactly (IWSDK 1.0.0's, from\n * `@iwsdk/core` `dist/physics/`): gravity `[0, -9.81, 0]` m/s², 60 steps\n * per second with render interpolation, a body dynamic with linear and\n * angular damping 0 and gravity factor 1, a shape \"auto\" (from the object's\n * geometry) with density 1 kg/m³, restitution 0 and friction 0.5. An app\n * may install its own `PhysicsFacility` here to replace the engine.\n *\n * Units: metres, seconds, radians. Poses are world space, quaternions\n * `[x, y, z, w]`, +Y up. The binding copies every tuple it hands over and\n * every tuple it reads, so the host may reuse its buffers.\n *\n * The host conformance kit runs the whole shared suite\n * (`physicsFacilityContractCases()`) against this slice on the device.\n */\nexport interface NativePhysicsHost {\n /** The engine behind the slice, for reports: `\"jolt\"`, `\"realitykit\"`, or an app's own name. Never empty. */\n readonly engine: string;\n /** World gravity, m/s². Starts at `[0, -9.81, 0]`. */\n getGravity(): Vec3Tuple;\n /** Set world gravity, m/s²; every dynamic body, sleeping ones included, sees it from the next step. */\n setGravity(gravity: Vec3Tuple): void;\n /**\n * Add a body for `id` at `pose` with the specs given; a missing field takes\n * the default above. `state`: `\"dynamic\"` responds to forces, collisions and\n * gravity, `\"static\"` never moves, `\"kinematic\"` moves only by `setBodyPose`\n * and pushes dynamic bodies. `shape.kind` `\"auto\"` is the host's collider\n * for the object's geometry; `\"box\"` takes full extents in `dimensions`,\n * `\"sphere\"` its radius in `dimensions[0]`, `\"capsule\"` radius and height.\n * Adding an id that exists replaces it. IWSDK: `PhysicsBody` and `PhysicsShape`.\n */\n addBody(id: string, pose: PoseTuple, body?: PhysicsBodySpec, shape?: PhysicsShapeSpec): void;\n /** Remove the body; a missing id is ignored. */\n removeBody(id: string): void;\n hasBody(id: string): boolean;\n /** Change how the body moves; a suspended body takes the new state when it resumes. */\n setBodyState(id: string, state: PhysicsBodyState): void;\n getBodyState(id: string): PhysicsBodyState;\n /** Where the body is now, world space. Throws an error whose message contains `no physics body \"<id>\"` for an unknown id. */\n getBodyPose(id: string): PoseTuple;\n /**\n * Teleport: the body is at `pose` from the next step with its velocity\n * cleared, so it rests there rather than carrying what it did. While\n * suspended the write is exact and carries nothing. IWSDK:\n * `PhysicsSystem.setBodyTransform`.\n */\n setBodyPose(id: string, pose: PoseTuple): void;\n /** Linear m/s and angular rad/s (axis scaled), world space. */\n getVelocity(id: string): PhysicsVelocity;\n /** Set both velocities. IWSDK: `PhysicsManipulation`. */\n setVelocity(id: string, velocity: PhysicsVelocity): void;\n /**\n * A hold began: from here until `resume` the body is not simulated (no\n * gravity, no collision response) and `setBodyPose` writes are exact. A\n * second `suspend` changes nothing. IWSDK: `beginHold` removes the body.\n */\n suspend(id: string): void;\n /**\n * The hold ended: simulate the body again in the state it had, with\n * `release` as its velocity so a throw carries through (zeros rest it).\n * Resuming a body that is not suspended changes nothing. IWSDK:\n * `endHold` re-adds the body with a `PhysicsManipulation`.\n */\n resume(id: string, release: HoldRelease): void;\n isSuspended(id: string): boolean;\n /**\n * Advance the world by `dtSeconds`. A host whose engine steps itself from\n * its own loop may take this as a hint and return; the kit then reads the\n * poses the engine wrote. The binding calls it once per `update(dt)`.\n */\n step(dtSeconds: number): void;\n /** Release the world and every body. */\n dispose(): void;\n}\n\n/**\n * Test-only readbacks a host provides so the host conformance kit\n * (`nativeInteractionsHostConformanceCases`) can check what the host\n * actually did. A shipping host may omit them.\n */\nexport interface NativeInteractionsTestHost {\n /** Put a shown, hit-testable target of `radius` metres at `position`, as the app's scene would. */\n placeTarget(targetId: string, position: Vec3Tuple, radius: number): void;\n /** Remove every target `placeTarget` put in. */\n clearTargets(): void;\n /** What the host draws for one side now. */\n presenceShown(side: \"left\" | \"right\"): NativePresenceShown | undefined;\n /** The last release the host received for a target through `endHold`. */\n lastRelease(targetId: string): HoldRelease | undefined;\n /** Every cursor disc the host draws now, as world positions. */\n cursors(): Vec3Tuple[];\n /** What the host draws for one source now, as last told through `applyPointerVisuals`; undefined for a source never told. */\n pointerVisuals?(sourceId: string): PointerVisuals | undefined;\n /** Hide or show a placed target with the host's ordinary visibility flag, for the hidden-target cone case. Optional. */\n setTargetVisible?(targetId: string, visible: boolean): void;\n}\n\n/**\n * The native app's frame callback, the root member of `__rcHost` that\n * `NativeInteractions` attaches to when `attachToHost` is set.\n */\nexport interface NativeFrameSource {\n onFrame(callback: (timestampMs: number, deltaS: number) => void): () => void;\n}\n\n/**\n * The slices this package reads off `globalThis.__rcHost`. `physics` is\n * part of the contract on every native platform: a host without it fails\n * the conformance kit, and a target cannot carry a body until it is there.\n */\nexport interface NativeHostSlices {\n input: NativeInputHost;\n interactions: NativeInteractionHost;\n physics: NativePhysicsHost;\n}\n\n/**\n * `globalThis.__rcHost`, read defensively: the root `NativeHost` interface\n * belongs to `service-framework-native`, which this package does not depend\n * on, so the global is read as an unknown bag of optional slices rather than\n * imported.\n */\nexport function installedHost(): (Partial<NativeHostSlices> & Partial<NativeFrameSource>) | undefined {\n return (globalThis as { __rcHost?: Partial<NativeHostSlices> & Partial<NativeFrameSource> }).__rcHost;\n}\n\n/**\n * Resolve one slice: the value passed in, or `globalThis.__rcHost`'s slice\n * of the same name. Throws one clear error naming the missing slice, so a\n * native-interactions class fails at construction rather than the first\n * time something calls a method that is not there.\n */\nexport function resolveHostSlice<K extends keyof NativeHostSlices>(\n name: K,\n injected: NativeHostSlices[K] | undefined,\n): NativeHostSlices[K] {\n const slice = findHostSlice(name, injected);\n if (!slice) {\n throw new Error(\n `@realitycollective/native-interactions: no \"${name}\" slice was supplied and globalThis.__rcHost.${name} is not installed. Pass one directly, or have the native app install it before this package is constructed.`,\n );\n }\n return slice;\n}\n\n/** The value passed in, or `globalThis.__rcHost`'s slice of that name, or undefined. */\nexport function findHostSlice<K extends keyof NativeHostSlices>(\n name: K,\n injected: NativeHostSlices[K] | undefined,\n): NativeHostSlices[K] | undefined {\n return injected ?? (installedHost() as Partial<NativeHostSlices> | undefined)?.[name];\n}\n\n// ---------------------------------------------------------------------------\n// Copies. A host may reuse its own buffers across calls, so every tuple that\n// crosses back into this package is copied on the way in, never referenced.\n// ---------------------------------------------------------------------------\n\n/** Copy a position tuple. */\nexport function copyVec3(v: Vec3Tuple): Vec3Tuple {\n return [v[0], v[1], v[2]];\n}\n\n/** Copy an orientation tuple. */\nexport function copyQuat(q: QuatTuple): QuatTuple {\n return [q[0], q[1], q[2], q[3]];\n}\n\n/** Copy a pose (position + orientation). */\nexport function copyPose(pose: PoseTuple): PoseTuple {\n return { position: copyVec3(pose.position), quaternion: copyQuat(pose.quaternion) };\n}\n\n/** Copy a ray (origin + direction). */\nexport function copyRay(ray: RayTuple): RayTuple {\n return { origin: copyVec3(ray.origin), direction: copyVec3(ray.direction) };\n}\n\n/**\n * Copy one `InputSourceSnapshot` field by field, including every tuple it\n * carries, so a host that pools and refills its own snapshot objects still\n * meets the ownership rule: a snapshot handed to `sample()`'s caller is\n * never written to again.\n */\nexport function copySnapshot(source: InputSourceSnapshot): InputSourceSnapshot {\n const copy: InputSourceSnapshot = {\n id: source.id,\n kind: source.kind,\n handedness: source.handedness,\n select: source.select,\n squeeze: source.squeeze,\n };\n if (source.ray) copy.ray = copyRay(source.ray);\n if (source.gripPose) copy.gripPose = copyPose(source.gripPose);\n if (source.indexTip) copy.indexTip = copyVec3(source.indexTip);\n if (source.linearVelocity) copy.linearVelocity = copyVec3(source.linearVelocity);\n if (source.angularVelocity) copy.angularVelocity = copyVec3(source.angularVelocity);\n if (source.nativeGrabbing !== undefined) copy.nativeGrabbing = source.nativeGrabbing;\n if (source.hapticsAvailable !== undefined) copy.hapticsAvailable = source.hapticsAvailable;\n if (source.selectorPose) copy.selectorPose = copyPose(source.selectorPose);\n return copy;\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativePhysicsFacility - the core `PhysicsFacility` over the native host's
|
|
3
|
+
* `physics` slice. The host runs the platform's default engine (Jolt on
|
|
4
|
+
* Quest and Android, RealityKit on visionOS) behind exactly the contract's
|
|
5
|
+
* members; this class is a thin binding that copies every tuple across the
|
|
6
|
+
* boundary in both directions, so a host that reuses its own buffers still
|
|
7
|
+
* meets the ownership rule the shared suite checks, and a tuple the core
|
|
8
|
+
* hands over is never kept by the host.
|
|
9
|
+
*
|
|
10
|
+
* An app that replaces the default engine passes its own `PhysicsFacility`
|
|
11
|
+
* as the slice: the shapes are identical, so the override is the same path.
|
|
12
|
+
*/
|
|
13
|
+
import type { PoseTuple, Vec3Tuple } from "@realitycollective/webxr-input";
|
|
14
|
+
import type { HoldRelease, PhysicsBodySpec, PhysicsBodyState, PhysicsFacility, PhysicsShapeSpec, PhysicsVelocity } from "@realitycollective/webxr-interactions";
|
|
15
|
+
import { type NativePhysicsHost } from "./native-types.js";
|
|
16
|
+
export interface NativePhysicsFacilityOptions {
|
|
17
|
+
/** The `physics` slice, or an app's own facility. Omit to read `globalThis.__rcHost.physics`. */
|
|
18
|
+
physics?: NativePhysicsHost;
|
|
19
|
+
}
|
|
20
|
+
export declare class NativePhysicsFacility implements PhysicsFacility {
|
|
21
|
+
private readonly host;
|
|
22
|
+
constructor(options?: NativePhysicsFacilityOptions);
|
|
23
|
+
/** The engine the host runs, as it names it: `"jolt"`, `"realitykit"`, or an app's own. */
|
|
24
|
+
get engine(): string;
|
|
25
|
+
getGravity(): Vec3Tuple;
|
|
26
|
+
setGravity(gravity: Vec3Tuple): void;
|
|
27
|
+
addBody(id: string, pose: PoseTuple, body?: PhysicsBodySpec, shape?: PhysicsShapeSpec): void;
|
|
28
|
+
removeBody(id: string): void;
|
|
29
|
+
hasBody(id: string): boolean;
|
|
30
|
+
setBodyState(id: string, state: PhysicsBodyState): void;
|
|
31
|
+
getBodyState(id: string): PhysicsBodyState;
|
|
32
|
+
getBodyPose(id: string): PoseTuple;
|
|
33
|
+
setBodyPose(id: string, pose: PoseTuple): void;
|
|
34
|
+
getVelocity(id: string): PhysicsVelocity;
|
|
35
|
+
setVelocity(id: string, velocity: PhysicsVelocity): void;
|
|
36
|
+
suspend(id: string): void;
|
|
37
|
+
resume(id: string, release: HoldRelease): void;
|
|
38
|
+
isSuspended(id: string): boolean;
|
|
39
|
+
step(dtSeconds: number): void;
|
|
40
|
+
dispose(): void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { copyPose, copyVec3, resolveHostSlice } from "./native-types.js";
|
|
2
|
+
function copyVelocity(velocity) {
|
|
3
|
+
return { linear: copyVec3(velocity.linear), angular: copyVec3(velocity.angular) };
|
|
4
|
+
}
|
|
5
|
+
export class NativePhysicsFacility {
|
|
6
|
+
host;
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.host = resolveHostSlice("physics", options.physics);
|
|
9
|
+
}
|
|
10
|
+
/** The engine the host runs, as it names it: `"jolt"`, `"realitykit"`, or an app's own. */
|
|
11
|
+
get engine() {
|
|
12
|
+
return this.host.engine;
|
|
13
|
+
}
|
|
14
|
+
getGravity() {
|
|
15
|
+
return copyVec3(this.host.getGravity());
|
|
16
|
+
}
|
|
17
|
+
setGravity(gravity) {
|
|
18
|
+
this.host.setGravity(copyVec3(gravity));
|
|
19
|
+
}
|
|
20
|
+
addBody(id, pose, body, shape) {
|
|
21
|
+
this.host.addBody(id, copyPose(pose), body ? { ...body } : undefined, shape ? { ...shape, ...(shape.dimensions ? { dimensions: copyVec3(shape.dimensions) } : {}) } : undefined);
|
|
22
|
+
}
|
|
23
|
+
removeBody(id) {
|
|
24
|
+
this.host.removeBody(id);
|
|
25
|
+
}
|
|
26
|
+
hasBody(id) {
|
|
27
|
+
return this.host.hasBody(id);
|
|
28
|
+
}
|
|
29
|
+
setBodyState(id, state) {
|
|
30
|
+
this.host.setBodyState(id, state);
|
|
31
|
+
}
|
|
32
|
+
getBodyState(id) {
|
|
33
|
+
return this.host.getBodyState(id);
|
|
34
|
+
}
|
|
35
|
+
getBodyPose(id) {
|
|
36
|
+
return copyPose(this.host.getBodyPose(id));
|
|
37
|
+
}
|
|
38
|
+
setBodyPose(id, pose) {
|
|
39
|
+
this.host.setBodyPose(id, copyPose(pose));
|
|
40
|
+
}
|
|
41
|
+
getVelocity(id) {
|
|
42
|
+
return copyVelocity(this.host.getVelocity(id));
|
|
43
|
+
}
|
|
44
|
+
setVelocity(id, velocity) {
|
|
45
|
+
this.host.setVelocity(id, copyVelocity(velocity));
|
|
46
|
+
}
|
|
47
|
+
suspend(id) {
|
|
48
|
+
this.host.suspend(id);
|
|
49
|
+
}
|
|
50
|
+
resume(id, release) {
|
|
51
|
+
this.host.resume(id, { linearVelocity: copyVec3(release.linearVelocity), angularVelocity: copyVec3(release.angularVelocity) });
|
|
52
|
+
}
|
|
53
|
+
isSuspended(id) {
|
|
54
|
+
return this.host.isSuspended(id);
|
|
55
|
+
}
|
|
56
|
+
step(dtSeconds) {
|
|
57
|
+
this.host.step(dtSeconds);
|
|
58
|
+
}
|
|
59
|
+
dispose() {
|
|
60
|
+
this.host.dispose();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=physics-facility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"physics-facility.js","sourceRoot":"","sources":["../src/physics-facility.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NativePhysicsFacility - the core `PhysicsFacility` over the native host's\n * `physics` slice. The host runs the platform's default engine (Jolt on\n * Quest and Android, RealityKit on visionOS) behind exactly the contract's\n * members; this class is a thin binding that copies every tuple across the\n * boundary in both directions, so a host that reuses its own buffers still\n * meets the ownership rule the shared suite checks, and a tuple the core\n * hands over is never kept by the host.\n *\n * An app that replaces the default engine passes its own `PhysicsFacility`\n * as the slice: the shapes are identical, so the override is the same path.\n */\nimport type { PoseTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport type {\n HoldRelease,\n PhysicsBodySpec,\n PhysicsBodyState,\n PhysicsFacility,\n PhysicsShapeSpec,\n PhysicsVelocity,\n} from \"@realitycollective/webxr-interactions\";\nimport { copyPose, copyVec3, resolveHostSlice, type NativePhysicsHost } from \"./native-types.js\";\n\nexport interface NativePhysicsFacilityOptions {\n /** The `physics` slice, or an app's own facility. Omit to read `globalThis.__rcHost.physics`. */\n physics?: NativePhysicsHost;\n}\n\nfunction copyVelocity(velocity: PhysicsVelocity): PhysicsVelocity {\n return { linear: copyVec3(velocity.linear), angular: copyVec3(velocity.angular) };\n}\n\nexport class NativePhysicsFacility implements PhysicsFacility {\n private readonly host: NativePhysicsHost;\n\n constructor(options: NativePhysicsFacilityOptions = {}) {\n this.host = resolveHostSlice(\"physics\", options.physics);\n }\n\n /** The engine the host runs, as it names it: `\"jolt\"`, `\"realitykit\"`, or an app's own. */\n get engine(): string {\n return this.host.engine;\n }\n\n getGravity(): Vec3Tuple {\n return copyVec3(this.host.getGravity());\n }\n\n setGravity(gravity: Vec3Tuple): void {\n this.host.setGravity(copyVec3(gravity));\n }\n\n addBody(id: string, pose: PoseTuple, body?: PhysicsBodySpec, shape?: PhysicsShapeSpec): void {\n this.host.addBody(\n id,\n copyPose(pose),\n body ? { ...body } : undefined,\n shape ? { ...shape, ...(shape.dimensions ? { dimensions: copyVec3(shape.dimensions) } : {}) } : undefined,\n );\n }\n\n removeBody(id: string): void {\n this.host.removeBody(id);\n }\n\n hasBody(id: string): boolean {\n return this.host.hasBody(id);\n }\n\n setBodyState(id: string, state: PhysicsBodyState): void {\n this.host.setBodyState(id, state);\n }\n\n getBodyState(id: string): PhysicsBodyState {\n return this.host.getBodyState(id);\n }\n\n getBodyPose(id: string): PoseTuple {\n return copyPose(this.host.getBodyPose(id));\n }\n\n setBodyPose(id: string, pose: PoseTuple): void {\n this.host.setBodyPose(id, copyPose(pose));\n }\n\n getVelocity(id: string): PhysicsVelocity {\n return copyVelocity(this.host.getVelocity(id));\n }\n\n setVelocity(id: string, velocity: PhysicsVelocity): void {\n this.host.setVelocity(id, copyVelocity(velocity));\n }\n\n suspend(id: string): void {\n this.host.suspend(id);\n }\n\n resume(id: string, release: HoldRelease): void {\n this.host.resume(id, { linearVelocity: copyVec3(release.linearVelocity), angularVelocity: copyVec3(release.angularVelocity) });\n }\n\n isSuspended(id: string): boolean {\n return this.host.isSuspended(id);\n }\n\n step(dtSeconds: number): void {\n this.host.step(dtSeconds);\n }\n\n dispose(): void {\n this.host.dispose();\n }\n}\n"]}
|
package/dist/provider.d.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* the WebXR session: `rays`, `pokes` and grabs from immersion, `handJoints`
|
|
9
9
|
* and `pinch` from hand tracking or a tracked hand, `buttonsAxes` and
|
|
10
10
|
* `haptics` from the sources present, `gaze` and `headPose` from a head
|
|
11
|
-
* pose, `
|
|
11
|
+
* pose, `eyeGaze` from the eye-tracking fact and a host that can pose the
|
|
12
|
+
* gaze, `presence` from the host's presence member. They re-derive, and
|
|
12
13
|
* `onCapabilitiesChanged` fires, whenever the facts or the sources change.
|
|
13
14
|
* - Presence is decided here, as `applyPresence` decides it: the requested
|
|
14
15
|
* visibility per side, and the modality, with `"auto"` showing hands while
|
|
@@ -16,13 +17,18 @@
|
|
|
16
17
|
* draw per side.
|
|
17
18
|
* - No sources are sampled while the session lacks focus, as IWSDK samples
|
|
18
19
|
* none unless its visibility state is `Visible`.
|
|
20
|
+
* - The eye-gaze rule (`@realitycollective/webxr-input` `eye-gaze.ts`) runs
|
|
21
|
+
* here over the host's raw snapshots and its gaze pose, as IWSDK's
|
|
22
|
+
* `GazePointer` runs it: far rays drop once gaze has a valid pose, a pinch
|
|
23
|
+
* selects, and the pinching hand's ray pose travels as `selectorPose`. A
|
|
24
|
+
* host reports only `eyeTracking` and `getEyeGazePose()`.
|
|
19
25
|
* - A pulse's intensity is clamped to 0..1 before it reaches the host.
|
|
20
26
|
*
|
|
21
27
|
* Every snapshot, and every tuple inside it, is copied before it leaves this
|
|
22
28
|
* class, so a host that reuses its own sample buffers still meets the
|
|
23
29
|
* ownership rule the shared contract suite checks.
|
|
24
30
|
*/
|
|
25
|
-
import { type Handedness, type HeadPose, type InputCapabilities, type InputHitHint, type InputProvider, type InputSourceSnapshot, type PresenceModality, type Unsubscribe } from "@realitycollective/webxr-input";
|
|
31
|
+
import { type EyeGazeOptions, type Handedness, type HeadPose, type InputCapabilities, type InputHitHint, type InputProvider, type InputSourceSnapshot, type PresenceModality, type Unsubscribe } from "@realitycollective/webxr-input";
|
|
26
32
|
import { type NativeInputHost } from "./native-types.js";
|
|
27
33
|
export interface NativeInputProviderOptions {
|
|
28
34
|
/** The `input` slice. Omit to read `globalThis.__rcHost.input`. */
|
|
@@ -34,6 +40,17 @@ export interface NativeInputProviderOptions {
|
|
|
34
40
|
* `IWSDKProviderOptions.nativeGrab`.
|
|
35
41
|
*/
|
|
36
42
|
nativeGrab?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Tuning for the eye-gaze rule: the filter and the tracking-loss grace.
|
|
45
|
+
* Defaults are IWSDK 1.0.0's (`EYE_GAZE_DEFAULTS`).
|
|
46
|
+
*/
|
|
47
|
+
eyeGaze?: EyeGazeOptions;
|
|
48
|
+
/**
|
|
49
|
+
* Seconds between two `sample()` calls, for the eye-gaze filter and grace,
|
|
50
|
+
* when the host cannot say. Default 1/60. `NativeInteractions` passes the
|
|
51
|
+
* real frame delta through `setFrameDelta`.
|
|
52
|
+
*/
|
|
53
|
+
frameSeconds?: number;
|
|
37
54
|
}
|
|
38
55
|
export declare class NativeInputProvider implements InputProvider {
|
|
39
56
|
private readonly host;
|
|
@@ -46,6 +63,8 @@ export declare class NativeInputProvider implements InputProvider {
|
|
|
46
63
|
private readonly presenceVisible;
|
|
47
64
|
private presenceModality;
|
|
48
65
|
private readonly presenceApplied;
|
|
66
|
+
private readonly eyeGaze;
|
|
67
|
+
private frameSeconds;
|
|
49
68
|
readonly getHeadPose?: () => HeadPose;
|
|
50
69
|
readonly sampleHints?: () => readonly InputHitHint[];
|
|
51
70
|
readonly pulse?: (sourceId: string, intensity: number, durationMs: number) => boolean;
|
|
@@ -55,9 +74,19 @@ export declare class NativeInputProvider implements InputProvider {
|
|
|
55
74
|
getCapabilities(): InputCapabilities;
|
|
56
75
|
onCapabilitiesChanged(listener: (capabilities: InputCapabilities) => void): Unsubscribe;
|
|
57
76
|
onSourcesChanged(listener: () => void): Unsubscribe;
|
|
77
|
+
/** The seconds the next `sample()` integrates over; `NativeInteractions.update(dt)` sets it each frame. */
|
|
78
|
+
setFrameDelta(seconds: number): void;
|
|
58
79
|
sample(): readonly InputSourceSnapshot[];
|
|
59
80
|
/** Release the host subscriptions and every listener. */
|
|
60
81
|
dispose(): void;
|
|
82
|
+
/**
|
|
83
|
+
* The eye-gaze reading for this frame: the host's raw pose (copied), and
|
|
84
|
+
* each hand's ray-space pose. A host reports a hand's ray, not its
|
|
85
|
+
* ray-space orientation, so the pose is built from the ray with world up
|
|
86
|
+
* (`rayPoseFromRay`): a gaze-started drag follows the hand's position and
|
|
87
|
+
* direction, not its roll about the ray.
|
|
88
|
+
*/
|
|
89
|
+
private eyeGazeFrame;
|
|
61
90
|
/** Re-read the facts and re-derive; publish and re-apply presence on a change. */
|
|
62
91
|
private refresh;
|
|
63
92
|
/** `IWSDKInputProvider.refreshCapabilities`, over the host's facts and sources. */
|