@realitycollective/native-interactions 0.1.1-preview.1 → 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 +29 -0
- package/README.md +16 -4
- package/dist/conformance.d.ts +16 -0
- package/dist/conformance.js +293 -0
- package/dist/conformance.js.map +1 -0
- 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 +44 -4
- package/dist/host.js +52 -4
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/native-types.d.ts +307 -36
- 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 +78 -11
- package/dist/provider.js +177 -30
- package/dist/provider.js.map +1 -1
- package/dist/transform-port.d.ts +21 -3
- package/dist/transform-port.js +23 -10
- package/dist/transform-port.js.map +1 -1
- package/package.json +3 -3
package/dist/native-types.d.ts
CHANGED
|
@@ -3,76 +3,340 @@
|
|
|
3
3
|
* CompositorServices on visionOS, or any other shell embedding a JavaScript
|
|
4
4
|
* engine such as Hermes) installs on `globalThis.__rcHost`.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* A HOST IS HANDED RESULTS, NOT RULES. Every rule the IWSDK binding applies
|
|
7
|
+
* in its provider runs in this package from the same inputs: capabilities
|
|
8
|
+
* are derived here from the facts the host reports, the presence modality
|
|
9
|
+
* is decided here and handed to the host per side, and a target's radius is
|
|
10
|
+
* handed to the host at registration. The host measures, renders and
|
|
11
|
+
* reports. Each member below states what the host does, in what units and
|
|
12
|
+
* with what sign, and which IWSDK line it stands in for.
|
|
13
|
+
*
|
|
8
14
|
* `NativeInteractionHost` is `HitTester` plus `TransformPort` from
|
|
9
|
-
* `@realitycollective/webxr-interactions
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* packages open.
|
|
15
|
+
* `@realitycollective/webxr-interactions`, with a `targetId` added to every
|
|
16
|
+
* `TransformPort` member because that port is per object and the host is one
|
|
17
|
+
* object serving every registered interactable. Declaring the shapes here,
|
|
18
|
+
* rather than reusing the originals by reference, is deliberate: this file is
|
|
19
|
+
* the one place that states what crosses the native boundary, in the same
|
|
20
|
+
* structural-typing style as `babylon-types.ts` and the XR Blocks `XB*Like`
|
|
21
|
+
* types.
|
|
17
22
|
*
|
|
18
23
|
* Values that cross are plain: numbers, strings, booleans and tuples. No
|
|
19
|
-
* engine objects in either direction, and assets stay
|
|
20
|
-
*
|
|
24
|
+
* engine objects in either direction, and assets stay on the native side.
|
|
25
|
+
* Units are metres, seconds and radians; poses are world space, quaternions
|
|
26
|
+
* `[x, y, z, w]`, right handed, +Y up.
|
|
27
|
+
*/
|
|
28
|
+
import type { HeadPose, InputHitHint, InputSourceSnapshot, PoseTuple, QuatTuple, RayTuple, Unsubscribe, Vec3Tuple } from "@realitycollective/webxr-input";
|
|
29
|
+
import type { HoldRelease, PhysicsBodySpec, PhysicsBodyState, PhysicsShapeSpec, PhysicsVelocity, PointerVisuals } from "@realitycollective/webxr-interactions";
|
|
30
|
+
/**
|
|
31
|
+
* What the host knows about its session, from which this package derives
|
|
32
|
+
* `InputCapabilities` exactly as `IWSDKInputProvider.refreshCapabilities`
|
|
33
|
+
* does from the WebXR session.
|
|
34
|
+
*/
|
|
35
|
+
export interface NativeInputFacts {
|
|
36
|
+
/** A session is presenting: OpenXR `SYNCHRONIZED`, `VISIBLE` or `FOCUSED`. IWSDK: `world.session` exists. */
|
|
37
|
+
immersive: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* The session has input focus: OpenXR `FOCUSED`. While false no sources are
|
|
40
|
+
* sampled, as IWSDK's provider returns none unless the visibility state is
|
|
41
|
+
* `Visible`.
|
|
42
|
+
*/
|
|
43
|
+
focused: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Hand tracking is enabled on the session (`XR_EXT_hand_tracking` and the
|
|
46
|
+
* system supports it). IWSDK: `session.enabledFeatures` includes
|
|
47
|
+
* `"hand-tracking"`. A tracked hand source also counts, without this.
|
|
48
|
+
*/
|
|
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;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* What one side's presence visuals should show, handed to the host. The host
|
|
65
|
+
* draws exactly this and decides nothing: which family is shown for
|
|
66
|
+
* `"auto"`, and which side a request targets, are this package's.
|
|
67
|
+
*/
|
|
68
|
+
export interface NativePresenceShown {
|
|
69
|
+
/** Draw this side's hand mesh. */
|
|
70
|
+
hand: boolean;
|
|
71
|
+
/** Draw this side's controller model. */
|
|
72
|
+
controller: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The native host's `input` slice. The host reports facts, sources and
|
|
76
|
+
* signals; `NativeInputProvider` turns them into the `InputProvider`
|
|
77
|
+
* contract.
|
|
21
78
|
*/
|
|
22
|
-
import type { Handedness, HeadPose, InputCapabilities, InputHitHint, InputSourceSnapshot, PoseTuple, PresenceModality, QuatTuple, RayTuple, Unsubscribe, Vec3Tuple } from "@realitycollective/webxr-input";
|
|
23
|
-
import type { HoldRelease } from "@realitycollective/webxr-interactions";
|
|
24
|
-
/** The native host's `input` slice. Mirrors `InputProvider` member for member. */
|
|
25
79
|
export interface NativeInputHost {
|
|
26
|
-
|
|
27
|
-
|
|
80
|
+
/** This moment's session facts. Read at construction and on every signal. */
|
|
81
|
+
getFacts(): NativeInputFacts;
|
|
82
|
+
/** The facts changed: session start or end, focus gained or lost. */
|
|
83
|
+
onFactsChanged(listener: () => void): Unsubscribe;
|
|
84
|
+
/** A source connected or disconnected (WebXR `inputsourceschange`). Capabilities re-derive on it. */
|
|
28
85
|
onSourcesChanged(listener: () => void): Unsubscribe;
|
|
86
|
+
/**
|
|
87
|
+
* This frame's tracked sources, in the `InputSourceSnapshot` shape. `kind`
|
|
88
|
+
* is `"hand"` while hand joints are tracked, else `"controller"`.
|
|
89
|
+
* `select` is the trigger value, or 1 while the runtime reports selecting
|
|
90
|
+
* (a hand pinch), 0..1; `squeeze` is the grip value, 0 for hands.
|
|
91
|
+
* `gripPose` is the WebXR GRIP frame, not a hand joint - see
|
|
92
|
+
* `InputSourceSnapshot.gripPose`. `indexTip` is the index fingertip for a
|
|
93
|
+
* hand and the ray origin for a controller. `hapticsAvailable` is true when
|
|
94
|
+
* the source has an actuator. The host may reuse its buffers: this package
|
|
95
|
+
* copies every snapshot.
|
|
96
|
+
*/
|
|
29
97
|
sample(): readonly InputSourceSnapshot[];
|
|
98
|
+
/** The viewer's head pose this frame. Present on any host that tracks a head; capabilities `gaze` and `headPose` follow it. */
|
|
30
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;
|
|
110
|
+
/**
|
|
111
|
+
* Pre-resolved targeting hints, for a host with its own targeting. Frame
|
|
112
|
+
* fresh: the hints for the frame `sample()` just reported. A hint beats the
|
|
113
|
+
* core's own hit tests, and is equivalent to `nativeGrabbing` for a grab.
|
|
114
|
+
*/
|
|
31
115
|
sampleHints?(): readonly InputHitHint[];
|
|
116
|
+
/**
|
|
117
|
+
* Fire a haptic pulse on a source. `intensity` 0..1 (already clamped),
|
|
118
|
+
* `durationMs` in milliseconds. Returns false when it could not be
|
|
119
|
+
* delivered. IWSDK: `actuator.pulse(intensity, durationMs)`.
|
|
120
|
+
*/
|
|
32
121
|
pulse?(sourceId: string, intensity: number, durationMs: number): boolean;
|
|
33
|
-
|
|
34
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Show or hide one side's hand mesh and controller model, as decided here
|
|
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.
|
|
128
|
+
*/
|
|
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;
|
|
35
146
|
}
|
|
36
|
-
/** What the host's ray
|
|
147
|
+
/** What the host's ray or proximity query reports: the target it reached, if any. */
|
|
37
148
|
export interface NativeHit {
|
|
38
|
-
/** The target id the app registered with
|
|
149
|
+
/** The target id the app registered with the native scene. */
|
|
39
150
|
targetId: string;
|
|
40
|
-
/**
|
|
151
|
+
/** `hitRay`: the ray parameter t, metres. `hitProximity`: metres to the target's SURFACE, never negative. */
|
|
41
152
|
distance: number;
|
|
42
|
-
/** World-space hit or
|
|
153
|
+
/** World-space hit point, or the target's centre. */
|
|
43
154
|
point: Vec3Tuple;
|
|
44
155
|
}
|
|
45
156
|
/**
|
|
46
|
-
* The native host's `interactions` slice: `HitTester`
|
|
47
|
-
* `TransformPort` with every member keyed by the target id the
|
|
48
|
-
* when it registered the object with the native scene
|
|
49
|
-
* object serves every interactable rather than one port per object.
|
|
157
|
+
* The native host's `interactions` slice: `HitTester` with its semantics
|
|
158
|
+
* stated, and `TransformPort` with every member keyed by the target id the
|
|
159
|
+
* app chose when it registered the object with the native scene.
|
|
50
160
|
*/
|
|
51
161
|
export interface NativeInteractionHost {
|
|
162
|
+
/**
|
|
163
|
+
* The nearest shown target along `ray` (origin in metres, direction
|
|
164
|
+
* normalised). A target counts when its centre is within its radius of the
|
|
165
|
+
* ray line and in front of the origin; `distance` is the ray parameter of
|
|
166
|
+
* the closest point, and `t <= 0` never hits. A host that tests triangle
|
|
167
|
+
* meshes instead may report the surface it hit; the contract cases accept
|
|
168
|
+
* any answer within the target's radius plus 0.05 m of the sphere answer.
|
|
169
|
+
* IWSDK: `EntityHitTester.hitRay`.
|
|
170
|
+
*/
|
|
52
171
|
hitRay(ray: RayTuple): NativeHit | null;
|
|
172
|
+
/**
|
|
173
|
+
* The nearest shown target whose SURFACE is within `radius` metres of
|
|
174
|
+
* `point`. `distance = max(0, |centre - point| - targetRadius)`: a point
|
|
175
|
+
* 3 cm outside a 10 cm target reports 0.03, a point inside reports 0.
|
|
176
|
+
* Never the distance to the centre. IWSDK: `EntityHitTester.hitProximity`.
|
|
177
|
+
*/
|
|
53
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;
|
|
195
|
+
/**
|
|
196
|
+
* The radius, in metres, the host hit-tests a registered target with.
|
|
197
|
+
* Called once per registration with the app's `targetRadius`, or 0.1 when
|
|
198
|
+
* it gave none, as IWSDK registers a bare target as a 10 cm sphere
|
|
199
|
+
* (`register.ts`, `options.targetRadius ?? 0.1`). A host never excludes a
|
|
200
|
+
* target from hit testing because of its radius.
|
|
201
|
+
*/
|
|
202
|
+
setTargetRadius(targetId: string, radius: number): void;
|
|
54
203
|
/** Where the object is now. */
|
|
55
204
|
getWorldPose(targetId: string): PoseTuple;
|
|
56
205
|
/** The rest pose captured at registration, in world space. */
|
|
57
206
|
getRestWorldPose(targetId: string): PoseTuple;
|
|
207
|
+
/** The offset from rest last written, metres, in the rest frame. */
|
|
58
208
|
getLocalOffset(targetId: string): Vec3Tuple;
|
|
209
|
+
/** Offset the object from its rest pose, metres, in the rest frame. */
|
|
59
210
|
setLocalOffset(targetId: string, offset: Vec3Tuple): void;
|
|
211
|
+
/** Rotate the object from its rest orientation. */
|
|
60
212
|
setLocalRotation(targetId: string, quaternion: QuatTuple): void;
|
|
213
|
+
/** Place the object at a world pose (the pose-only grab carry). */
|
|
61
214
|
setWorldPose?(targetId: string, pose: PoseTuple): void;
|
|
215
|
+
/**
|
|
216
|
+
* The pulse effect: `scale` multiplies the rest scale, `emissive` is added
|
|
217
|
+
* to the base emissive intensity. Last write wins per field.
|
|
218
|
+
*/
|
|
62
219
|
setEffect?(targetId: string, effect: {
|
|
63
220
|
scale?: number;
|
|
64
221
|
emissive?: number;
|
|
65
222
|
}): void;
|
|
66
223
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
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`.
|
|
72
230
|
*/
|
|
73
|
-
beginHold
|
|
74
|
-
/**
|
|
75
|
-
|
|
231
|
+
beginHold(targetId: string): void;
|
|
232
|
+
/**
|
|
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.
|
|
238
|
+
*/
|
|
239
|
+
endHold(targetId: string, release: HoldRelease): void;
|
|
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
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Test-only readbacks a host provides so the host conformance kit
|
|
322
|
+
* (`nativeInteractionsHostConformanceCases`) can check what the host
|
|
323
|
+
* actually did. A shipping host may omit them.
|
|
324
|
+
*/
|
|
325
|
+
export interface NativeInteractionsTestHost {
|
|
326
|
+
/** Put a shown, hit-testable target of `radius` metres at `position`, as the app's scene would. */
|
|
327
|
+
placeTarget(targetId: string, position: Vec3Tuple, radius: number): void;
|
|
328
|
+
/** Remove every target `placeTarget` put in. */
|
|
329
|
+
clearTargets(): void;
|
|
330
|
+
/** What the host draws for one side now. */
|
|
331
|
+
presenceShown(side: "left" | "right"): NativePresenceShown | undefined;
|
|
332
|
+
/** The last release the host received for a target through `endHold`. */
|
|
333
|
+
lastRelease(targetId: string): HoldRelease | undefined;
|
|
334
|
+
/** Every cursor disc the host draws now, as world positions. */
|
|
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;
|
|
76
340
|
}
|
|
77
341
|
/**
|
|
78
342
|
* The native app's frame callback, the root member of `__rcHost` that
|
|
@@ -81,10 +345,15 @@ export interface NativeInteractionHost {
|
|
|
81
345
|
export interface NativeFrameSource {
|
|
82
346
|
onFrame(callback: (timestampMs: number, deltaS: number) => void): () => void;
|
|
83
347
|
}
|
|
84
|
-
/**
|
|
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
|
+
*/
|
|
85
353
|
export interface NativeHostSlices {
|
|
86
354
|
input: NativeInputHost;
|
|
87
355
|
interactions: NativeInteractionHost;
|
|
356
|
+
physics: NativePhysicsHost;
|
|
88
357
|
}
|
|
89
358
|
/**
|
|
90
359
|
* `globalThis.__rcHost`, read defensively: the root `NativeHost` interface
|
|
@@ -100,6 +369,8 @@ export declare function installedHost(): (Partial<NativeHostSlices> & Partial<Na
|
|
|
100
369
|
* time something calls a method that is not there.
|
|
101
370
|
*/
|
|
102
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;
|
|
103
374
|
/** Copy a position tuple. */
|
|
104
375
|
export declare function copyVec3(v: Vec3Tuple): Vec3Tuple;
|
|
105
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":"AAuGA;;;;;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 * These are NOT new contracts: `NativeInputHost` is the exact shape of\n * `InputProvider` from `@realitycollective/webxr-input`, and\n * `NativeInteractionHost` is `HitTester` plus `TransformPort` from\n * `@realitycollective/webxr-interactions/ports`, with a `targetId` added to\n * every `TransformPort` member because that port is per object and the host\n * is one object serving every registered interactable. Declaring them again\n * here, rather than reusing the originals by reference, is deliberate: this\n * file is the one place that states what crosses the native boundary, kept\n * in the same structural-typing style as `babylon-types.ts` and the XR\n * Blocks `XB*Like` types, so it reads correctly even without the other two\n * packages open.\n *\n * Values that cross are plain: numbers, strings, booleans and tuples. No\n * engine objects in either direction, and assets stay entirely on the\n * native side.\n */\nimport type {\n Handedness,\n HeadPose,\n InputCapabilities,\n InputHitHint,\n InputSourceSnapshot,\n PoseTuple,\n PresenceModality,\n QuatTuple,\n RayTuple,\n Unsubscribe,\n Vec3Tuple,\n} from \"@realitycollective/webxr-input\";\nimport type { HoldRelease } from \"@realitycollective/webxr-interactions\";\n\n/** The native host's `input` slice. Mirrors `InputProvider` member for member. */\nexport interface NativeInputHost {\n getCapabilities(): InputCapabilities;\n onCapabilitiesChanged(listener: (capabilities: InputCapabilities) => void): Unsubscribe;\n onSourcesChanged(listener: () => void): Unsubscribe;\n sample(): readonly InputSourceSnapshot[];\n getHeadPose?(): HeadPose;\n sampleHints?(): readonly InputHitHint[];\n pulse?(sourceId: string, intensity: number, durationMs: number): boolean;\n setPresenceVisible?(target: Handedness | \"all\", visible: boolean): boolean;\n setPresenceModality?(mode: PresenceModality): boolean;\n}\n\n/** What the host's ray/proximity query reports: the target it reached, if any. */\nexport interface NativeHit {\n /** The target id the app registered with `NativeTransformPort`/its own scene. */\n targetId: string;\n /** Distance from the query origin (ray origin / probe point). */\n distance: number;\n /** World-space hit or closest point. */\n point: Vec3Tuple;\n}\n\n/**\n * The native host's `interactions` slice: `HitTester` unchanged, and\n * `TransformPort` with every member keyed by the target id the app chose\n * when it registered the object with the native scene, because one host\n * object serves every interactable rather than one port per object.\n */\nexport interface NativeInteractionHost {\n hitRay(ray: RayTuple): NativeHit | null;\n hitProximity(point: Vec3Tuple, radius: number): NativeHit | null;\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 getLocalOffset(targetId: string): Vec3Tuple;\n setLocalOffset(targetId: string, offset: Vec3Tuple): void;\n setLocalRotation(targetId: string, quaternion: QuatTuple): void;\n setWorldPose?(targetId: string, pose: PoseTuple): void;\n setEffect?(targetId: string, effect: { scale?: number; emissive?: number }): void;\n /**\n * Suspend physics for `targetId` - see `TransformPort.beginHold` in\n * `@realitycollective/webxr-interactions`. Present only on a host that\n * can drive its own physics this way; a host with no physics, or a\n * native app that fulfils grabs itself through its own engine, never\n * needs it - see this package's README.\n */\n beginHold?(targetId: string): void;\n /** Resume physics for `targetId` with `release` as its new velocity. */\n endHold?(targetId: string, release: HoldRelease): 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/** 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":"AAqBA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAA0B,MAAM,mBAAmB,CAAC;AAOjG,SAAS,YAAY,CAAC,QAAyB;IAC7C,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;AACpF,CAAC;AAED,MAAM,OAAO,qBAAqB;IACf,IAAI,CAAoB;IAEzC,YAAY,UAAwC,EAAE;QACpD,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,CAAC;IAED,2FAA2F;IAC3F,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IAC1B,CAAC;IAED,UAAU;QACR,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,UAAU,CAAC,OAAkB;QAC3B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,CAAC,EAAU,EAAE,IAAe,EAAE,IAAsB,EAAE,KAAwB;QACnF,IAAI,CAAC,IAAI,CAAC,OAAO,CACf,EAAE,EACF,QAAQ,CAAC,IAAI,CAAC,EACd,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,EAC9B,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAC1G,CAAC;IACJ,CAAC;IAED,UAAU,CAAC,EAAU;QACnB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,CAAC,EAAU;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,YAAY,CAAC,EAAU,EAAE,KAAuB;QAC9C,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC;IAED,YAAY,CAAC,EAAU;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC;IAED,WAAW,CAAC,EAAU;QACpB,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,WAAW,CAAC,EAAU,EAAE,IAAe;QACrC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,WAAW,CAAC,EAAU;QACpB,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,WAAW,CAAC,EAAU,EAAE,QAAyB;QAC/C,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,CAAC,EAAU;QAChB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;IAED,MAAM,CAAC,EAAU,EAAE,OAAoB;QACrC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,eAAe,EAAE,QAAQ,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;IACjI,CAAC;IAED,WAAW,CAAC,EAAU;QACpB,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,CAAC,SAAiB;QACpB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO;QACL,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;CACF","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"]}
|