@realitycollective/iwsdk-interactions 0.1.0-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +54 -0
- package/dist/provider.js +204 -0
- package/dist/provider.js.map +1 -0
- package/dist/register.d.ts +71 -0
- package/dist/register.js +193 -0
- package/dist/register.js.map +1 -0
- package/dist/transform-port.d.ts +33 -0
- package/dist/transform-port.js +100 -0
- package/dist/transform-port.js.map +1 -0
- package/package.json +61 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Change log for the Reality Collective WebXR Interaction Extensions packages. All four packages are versioned and released together; the version below is the one carried by the `v<version>` release tag.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Preview builds are not listed separately. The entry for a version accumulates while its previews are published, and is dated when that version is released.
|
|
6
|
+
|
|
7
|
+
## [0.1.0]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `@realitycollective/webxr-interactions` - engine-free core: interactables and interactors, the behaviour set (`press` including latching, `pulse`, `hinge`, `dial`, `slide`, `grab` in poseOnly and native modes, `tossScore`), gaze (`required` gating and dwell-to-press), the runtime/binder with hints > poke > ray targeting, hysteresis and lifecycle, capability negotiation with a visible `behaviourDisabled` outcome, events as the only outbound pathway, and feedback intents (haptics and audio remain the client's, with `routeHapticsToProvider` as an explicit opt-in).
|
|
12
|
+
- `@realitycollective/threejs-interactions` - the default standalone adapter: raw WebXR (`XRSession` sources, hand joints, select/squeeze plus gamepad analog, haptic pulse), three.js hit-testing and transform ports, and a desktop mouse fallback. No framework required.
|
|
13
|
+
- `@realitycollective/iwsdk-interactions` - Meta IWSDK adapter: player-rig poses and stateful gamepads as the provider, IWSDK's own targeting (`Pressed` / `Grabbed` tags) forwarded as pre-resolved hints, and native grab fulfilment when the app enables IWSDK grabbing/physics. One-call `registerInteractions(world)` setup.
|
|
14
|
+
- `@realitycollective/xrblocks-interactions` - EXPERIMENTAL Google XR Blocks adapter, structurally typed against xrblocks v0.20.0 (`Input.getFrame()` ray sources and direct touches, pooled structs copied, no haptics upstream).
|
|
15
|
+
- `demos/playground` - the standalone interaction playground: the full station set from one portable `InteractionDescriptor`, with client-side audio cues, opt-in controller haptics and client-side toss ballistics.
|
|
16
|
+
|
|
17
|
+
### Notes
|
|
18
|
+
|
|
19
|
+
- All four packages depend on `@realitycollective/webxr-input`, released independently from the [WebXR-Input](https://github.com/realitycollective/WebXR-Input) repository. That package must be published before this one.
|
|
20
|
+
|
|
21
|
+
[0.1.0]: https://github.com/realitycollective/WebXR-Interactions/commits/main
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Reality Collective
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @realitycollective/iwsdk-interactions
|
|
2
|
+
|
|
3
|
+
The Meta IWSDK adapter for the Reality Collective Interaction Extensions. It connects the [`@realitycollective/webxr-interactions`](https://www.npmjs.com/package/@realitycollective/webxr-interactions) core to Meta's [Immersive Web SDK](https://iwsdk.dev).
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @realitycollective/iwsdk-interactions @iwsdk/core
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
It re-exports everything from the core, so this is the only interaction package your app needs.
|
|
10
|
+
|
|
11
|
+
## What it binds
|
|
12
|
+
|
|
13
|
+
| Layer | Detail |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| **Input** | IWSDK's player rig, controller state and hand joints |
|
|
16
|
+
| **Targeting** | IWSDK already knows what is being pressed or grabbed, through its `Pressed` and `Grabbed` tags. The adapter passes that answer straight through instead of working it out again with rays |
|
|
17
|
+
| **Grab** | Where the app has IWSDK grabbing or physics turned on, IWSDK performs the grab itself |
|
|
18
|
+
| **Setup** | One call: `registerInteractions(world)` |
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { registerInteractions } from "@realitycollective/iwsdk-interactions";
|
|
24
|
+
|
|
25
|
+
const interactions = registerInteractions(world);
|
|
26
|
+
|
|
27
|
+
interactions.register({ id: "handle", behaviours: [{ kind: "grab" }] }, entity);
|
|
28
|
+
|
|
29
|
+
interactions.runtime.onEvent((event) => console.log(event.type));
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`registerInteractions` is idempotent per world and registers the bridge system for you; the render loop is driven by IWSDK, so there is no `update` call to make.
|
|
33
|
+
|
|
34
|
+
## Peer dependency
|
|
35
|
+
|
|
36
|
+
`@iwsdk/core >=0.4.0 <0.6.0`.
|
|
37
|
+
|
|
38
|
+
## Live demo
|
|
39
|
+
|
|
40
|
+
The interaction playground - the full station set, mouse-capable on desktop, VR button for headsets: **[webxr-interactions.pages.dev](https://webxr-interactions.pages.dev)**
|
|
41
|
+
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
See the [repository README](https://github.com/realitycollective/WebXR-Interactions#readme).
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT - see [LICENSE](./LICENSE).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @realitycollective/iwsdk-interactions - the Meta IWSDK adapter.
|
|
3
|
+
* Re-exports the engine-free core wholesale; apps depend on exactly this
|
|
4
|
+
* package.
|
|
5
|
+
*/
|
|
6
|
+
export * from "@realitycollective/webxr-interactions";
|
|
7
|
+
export * from "./provider.js";
|
|
8
|
+
export * from "./transform-port.js";
|
|
9
|
+
export * from "./register.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @realitycollective/iwsdk-interactions - the Meta IWSDK adapter.
|
|
3
|
+
* Re-exports the engine-free core wholesale; apps depend on exactly this
|
|
4
|
+
* package.
|
|
5
|
+
*/
|
|
6
|
+
export * from "@realitycollective/webxr-interactions";
|
|
7
|
+
export * from "./provider.js";
|
|
8
|
+
export * from "./transform-port.js";
|
|
9
|
+
export * from "./register.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,uCAAuC,CAAC;AAEtD,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC","sourcesContent":["/**\n * @realitycollective/iwsdk-interactions - the Meta IWSDK adapter.\n * Re-exports the engine-free core wholesale; apps depend on exactly this\n * package.\n */\nexport * from \"@realitycollective/webxr-interactions\";\n\nexport * from \"./provider.js\";\nexport * from \"./transform-port.js\";\nexport * from \"./register.js\";\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IWSDKInputProvider - feeds Meta IWSDK's input machinery into the shared
|
|
3
|
+
* input contracts, in place of the standalone WebXR provider.
|
|
4
|
+
*
|
|
5
|
+
* Poses come from the IWSDK player rig (`world.playerSpaceEntities`:
|
|
6
|
+
* ray/grip/index-tip spaces, head), signals from the stateful XR gamepads
|
|
7
|
+
* (`world.input.xr.gamepads` - analog trigger/squeeze with `getSelecting`
|
|
8
|
+
* fallback, which also covers hand pinch). Capabilities derive from the
|
|
9
|
+
* LIVE session and re-publish on visibility/input-source changes.
|
|
10
|
+
*
|
|
11
|
+
* Provider power: IWSDK resolves its own targeting (BVH raycasts →
|
|
12
|
+
* `Pressed`/`Grabbed` tags). The bridge system turns those tags into
|
|
13
|
+
* pre-resolved hints (`setHints`) and flags native grabbing, which the
|
|
14
|
+
* core consumes IN PLACE of its own hit-testing.
|
|
15
|
+
*/
|
|
16
|
+
import { type World } from "@iwsdk/core";
|
|
17
|
+
import { type HeadPose, type InputCapabilities, type InputHitHint, type InputProvider, type InputSourceSnapshot, type Unsubscribe } from "@realitycollective/webxr-input";
|
|
18
|
+
export interface IWSDKProviderOptions {
|
|
19
|
+
/**
|
|
20
|
+
* The app enabled IWSDK's `features.grabbing` (+ physics where needed),
|
|
21
|
+
* so grab behaviours are fulfilled NATIVELY by the engine.
|
|
22
|
+
*/
|
|
23
|
+
nativeGrab?: boolean;
|
|
24
|
+
}
|
|
25
|
+
type Side = "left" | "right";
|
|
26
|
+
export declare class IWSDKInputProvider implements InputProvider {
|
|
27
|
+
private readonly world;
|
|
28
|
+
private readonly nativeGrab;
|
|
29
|
+
private capabilities;
|
|
30
|
+
private readonly capsListeners;
|
|
31
|
+
private readonly sourceListeners;
|
|
32
|
+
private hints;
|
|
33
|
+
private readonly nativeGrabbing;
|
|
34
|
+
private readonly disposers;
|
|
35
|
+
private boundSession;
|
|
36
|
+
private readonly onSourcesChange;
|
|
37
|
+
constructor(world: World, options?: IWSDKProviderOptions);
|
|
38
|
+
private bindSession;
|
|
39
|
+
private hasTrackedHand;
|
|
40
|
+
private refreshCapabilities;
|
|
41
|
+
getCapabilities(): InputCapabilities;
|
|
42
|
+
onCapabilitiesChanged(listener: (c: InputCapabilities) => void): Unsubscribe;
|
|
43
|
+
onSourcesChanged(listener: () => void): Unsubscribe;
|
|
44
|
+
/** Bridge-system input: this frame's pre-resolved tag hints. */
|
|
45
|
+
setHints(hints: InputHitHint[]): void;
|
|
46
|
+
sampleHints(): readonly InputHitHint[];
|
|
47
|
+
/** Bridge-system input: a side is currently native-grabbing. */
|
|
48
|
+
setNativeGrabbing(side: Side, grabbing: boolean): void;
|
|
49
|
+
sample(): readonly InputSourceSnapshot[];
|
|
50
|
+
getHeadPose(): HeadPose;
|
|
51
|
+
pulse(sourceId: string, intensity: number, durationMs: number): boolean;
|
|
52
|
+
dispose(): void;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IWSDKInputProvider - feeds Meta IWSDK's input machinery into the shared
|
|
3
|
+
* input contracts, in place of the standalone WebXR provider.
|
|
4
|
+
*
|
|
5
|
+
* Poses come from the IWSDK player rig (`world.playerSpaceEntities`:
|
|
6
|
+
* ray/grip/index-tip spaces, head), signals from the stateful XR gamepads
|
|
7
|
+
* (`world.input.xr.gamepads` - analog trigger/squeeze with `getSelecting`
|
|
8
|
+
* fallback, which also covers hand pinch). Capabilities derive from the
|
|
9
|
+
* LIVE session and re-publish on visibility/input-source changes.
|
|
10
|
+
*
|
|
11
|
+
* Provider power: IWSDK resolves its own targeting (BVH raycasts →
|
|
12
|
+
* `Pressed`/`Grabbed` tags). The bridge system turns those tags into
|
|
13
|
+
* pre-resolved hints (`setHints`) and flags native grabbing, which the
|
|
14
|
+
* core consumes IN PLACE of its own hit-testing.
|
|
15
|
+
*/
|
|
16
|
+
import { InputComponent, VisibilityState } from "@iwsdk/core";
|
|
17
|
+
import { NO_CAPABILITIES, } from "@realitycollective/webxr-input";
|
|
18
|
+
const SIDES = ["left", "right"];
|
|
19
|
+
export class IWSDKInputProvider {
|
|
20
|
+
world;
|
|
21
|
+
nativeGrab;
|
|
22
|
+
capabilities;
|
|
23
|
+
capsListeners = new Set();
|
|
24
|
+
sourceListeners = new Set();
|
|
25
|
+
hints = [];
|
|
26
|
+
nativeGrabbing = { left: false, right: false };
|
|
27
|
+
disposers = [];
|
|
28
|
+
boundSession = null;
|
|
29
|
+
onSourcesChange = () => {
|
|
30
|
+
this.refreshCapabilities();
|
|
31
|
+
for (const listener of [...this.sourceListeners])
|
|
32
|
+
listener();
|
|
33
|
+
};
|
|
34
|
+
constructor(world, options = {}) {
|
|
35
|
+
this.world = world;
|
|
36
|
+
this.nativeGrab = options.nativeGrab ?? false;
|
|
37
|
+
this.capabilities = { ...NO_CAPABILITIES };
|
|
38
|
+
const unsubscribe = world.visibilityState.subscribe(() => {
|
|
39
|
+
this.bindSession();
|
|
40
|
+
this.refreshCapabilities();
|
|
41
|
+
});
|
|
42
|
+
this.disposers.push(unsubscribe);
|
|
43
|
+
this.bindSession();
|
|
44
|
+
this.refreshCapabilities();
|
|
45
|
+
}
|
|
46
|
+
bindSession() {
|
|
47
|
+
const session = this.world.session ?? null;
|
|
48
|
+
if (session === this.boundSession)
|
|
49
|
+
return;
|
|
50
|
+
this.boundSession?.removeEventListener("inputsourceschange", this.onSourcesChange);
|
|
51
|
+
this.boundSession = session;
|
|
52
|
+
session?.addEventListener("inputsourceschange", this.onSourcesChange);
|
|
53
|
+
}
|
|
54
|
+
hasTrackedHand() {
|
|
55
|
+
const session = this.world.session;
|
|
56
|
+
if (!session)
|
|
57
|
+
return false;
|
|
58
|
+
for (const source of session.inputSources) {
|
|
59
|
+
if (source.hand)
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
refreshCapabilities() {
|
|
65
|
+
const session = this.world.session;
|
|
66
|
+
const immersive = !!session;
|
|
67
|
+
const handTracking = immersive &&
|
|
68
|
+
((session?.enabledFeatures?.includes("hand-tracking") ?? false) || this.hasTrackedHand());
|
|
69
|
+
let buttonsAxes = false;
|
|
70
|
+
let haptics = false;
|
|
71
|
+
for (const side of SIDES) {
|
|
72
|
+
const gamepad = this.world.input.xr.gamepads[side];
|
|
73
|
+
if (gamepad?.gamepad)
|
|
74
|
+
buttonsAxes = true;
|
|
75
|
+
if ((gamepad?.gamepad?.hapticActuators?.length ?? 0) > 0)
|
|
76
|
+
haptics = true;
|
|
77
|
+
}
|
|
78
|
+
const next = {
|
|
79
|
+
...NO_CAPABILITIES,
|
|
80
|
+
rays: immersive,
|
|
81
|
+
// Index-tip spaces exist for controllers too (fall back to ray pose).
|
|
82
|
+
pokes: immersive,
|
|
83
|
+
grabs: immersive ? (this.nativeGrab ? "native" : "poseOnly") : "none",
|
|
84
|
+
handJoints: handTracking,
|
|
85
|
+
pinch: handTracking,
|
|
86
|
+
buttonsAxes,
|
|
87
|
+
gaze: true,
|
|
88
|
+
headPose: true,
|
|
89
|
+
haptics,
|
|
90
|
+
};
|
|
91
|
+
if (JSON.stringify(next) !== JSON.stringify(this.capabilities)) {
|
|
92
|
+
this.capabilities = next;
|
|
93
|
+
for (const listener of [...this.capsListeners])
|
|
94
|
+
listener(next);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
getCapabilities() {
|
|
98
|
+
return this.capabilities;
|
|
99
|
+
}
|
|
100
|
+
onCapabilitiesChanged(listener) {
|
|
101
|
+
this.capsListeners.add(listener);
|
|
102
|
+
return () => this.capsListeners.delete(listener);
|
|
103
|
+
}
|
|
104
|
+
onSourcesChanged(listener) {
|
|
105
|
+
this.sourceListeners.add(listener);
|
|
106
|
+
return () => this.sourceListeners.delete(listener);
|
|
107
|
+
}
|
|
108
|
+
/** Bridge-system input: this frame's pre-resolved tag hints. */
|
|
109
|
+
setHints(hints) {
|
|
110
|
+
this.hints = hints;
|
|
111
|
+
}
|
|
112
|
+
sampleHints() {
|
|
113
|
+
return this.hints;
|
|
114
|
+
}
|
|
115
|
+
/** Bridge-system input: a side is currently native-grabbing. */
|
|
116
|
+
setNativeGrabbing(side, grabbing) {
|
|
117
|
+
this.nativeGrabbing[side] = grabbing;
|
|
118
|
+
}
|
|
119
|
+
sample() {
|
|
120
|
+
if (!this.world.session)
|
|
121
|
+
return [];
|
|
122
|
+
if (this.world.visibilityState.peek() !== VisibilityState.Visible)
|
|
123
|
+
return [];
|
|
124
|
+
const spaces = this.world.playerSpaceEntities;
|
|
125
|
+
const snapshots = [];
|
|
126
|
+
for (const side of SIDES) {
|
|
127
|
+
const rayObject = spaces.raySpaces[side].object3D;
|
|
128
|
+
if (!rayObject)
|
|
129
|
+
continue;
|
|
130
|
+
const gamepad = this.world.input.xr.gamepads[side];
|
|
131
|
+
const hand = this.capabilities.handJoints;
|
|
132
|
+
rayObject.updateWorldMatrix(true, false);
|
|
133
|
+
const rayPos = rayObject.getWorldPosition(TEMP.v1);
|
|
134
|
+
const rayQuat = rayObject.getWorldQuaternion(TEMP.q1);
|
|
135
|
+
const forward = TEMP.v2.set(0, 0, -1).applyQuaternion(rayQuat);
|
|
136
|
+
const select = gamepad
|
|
137
|
+
? Math.max(gamepad.getButtonValue(InputComponent.Trigger), gamepad.getSelecting() ? 1 : 0)
|
|
138
|
+
: 0;
|
|
139
|
+
const squeeze = gamepad ? gamepad.getButtonValue(InputComponent.Squeeze) : 0;
|
|
140
|
+
const snapshot = {
|
|
141
|
+
id: `${side}-input`,
|
|
142
|
+
kind: hand ? "hand" : "controller",
|
|
143
|
+
handedness: side,
|
|
144
|
+
ray: {
|
|
145
|
+
origin: [rayPos.x, rayPos.y, rayPos.z],
|
|
146
|
+
direction: [forward.x, forward.y, forward.z],
|
|
147
|
+
},
|
|
148
|
+
select,
|
|
149
|
+
squeeze,
|
|
150
|
+
};
|
|
151
|
+
const gripObject = spaces.gripSpaces[side].object3D;
|
|
152
|
+
if (gripObject) {
|
|
153
|
+
const gripPos = gripObject.getWorldPosition(TEMP.v1);
|
|
154
|
+
const gripQuat = gripObject.getWorldQuaternion(TEMP.q1);
|
|
155
|
+
snapshot.gripPose = {
|
|
156
|
+
position: [gripPos.x, gripPos.y, gripPos.z],
|
|
157
|
+
quaternion: [gripQuat.x, gripQuat.y, gripQuat.z, gripQuat.w],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const tipObject = spaces.indexTipSpaces[side].object3D;
|
|
161
|
+
if (tipObject) {
|
|
162
|
+
const tipPos = tipObject.getWorldPosition(TEMP.v1);
|
|
163
|
+
snapshot.indexTip = [tipPos.x, tipPos.y, tipPos.z];
|
|
164
|
+
}
|
|
165
|
+
if (this.nativeGrabbing[side])
|
|
166
|
+
snapshot.nativeGrabbing = true;
|
|
167
|
+
if ((gamepad?.gamepad?.hapticActuators?.length ?? 0) > 0) {
|
|
168
|
+
snapshot.hapticsAvailable = true;
|
|
169
|
+
}
|
|
170
|
+
snapshots.push(snapshot);
|
|
171
|
+
}
|
|
172
|
+
return snapshots;
|
|
173
|
+
}
|
|
174
|
+
getHeadPose() {
|
|
175
|
+
const head = this.world.playerSpaceEntities.head.object3D;
|
|
176
|
+
if (!head)
|
|
177
|
+
return { position: [0, 0, 0], quaternion: [0, 0, 0, 1] };
|
|
178
|
+
const position = head.getWorldPosition(TEMP.v1);
|
|
179
|
+
const quaternion = head.getWorldQuaternion(TEMP.q1);
|
|
180
|
+
return {
|
|
181
|
+
position: [position.x, position.y, position.z],
|
|
182
|
+
quaternion: [quaternion.x, quaternion.y, quaternion.z, quaternion.w],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
pulse(sourceId, intensity, durationMs) {
|
|
186
|
+
const side = sourceId.startsWith("left") ? "left" : sourceId.startsWith("right") ? "right" : null;
|
|
187
|
+
if (!side)
|
|
188
|
+
return false;
|
|
189
|
+
const actuator = this.world.input.xr.gamepads[side]?.gamepad?.hapticActuators?.[0];
|
|
190
|
+
if (!actuator)
|
|
191
|
+
return false;
|
|
192
|
+
void actuator.pulse?.(Math.min(1, Math.max(0, intensity)), durationMs);
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
dispose() {
|
|
196
|
+
this.boundSession?.removeEventListener("inputsourceschange", this.onSourcesChange);
|
|
197
|
+
for (const dispose of this.disposers)
|
|
198
|
+
dispose();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Shared scratch objects (single-threaded frame sampling).
|
|
202
|
+
import { Quaternion, Vector3 } from "@iwsdk/core";
|
|
203
|
+
const TEMP = { v1: new Vector3(), v2: new Vector3(), q1: new Quaternion() };
|
|
204
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IWSDKInputProvider - feeds Meta IWSDK's input machinery into the shared\n * input contracts, in place of the standalone WebXR provider.\n *\n * Poses come from the IWSDK player rig (`world.playerSpaceEntities`:\n * ray/grip/index-tip spaces, head), signals from the stateful XR gamepads\n * (`world.input.xr.gamepads` - analog trigger/squeeze with `getSelecting`\n * fallback, which also covers hand pinch). Capabilities derive from the\n * LIVE session and re-publish on visibility/input-source changes.\n *\n * Provider power: IWSDK resolves its own targeting (BVH raycasts →\n * `Pressed`/`Grabbed` tags). The bridge system turns those tags into\n * pre-resolved hints (`setHints`) and flags native grabbing, which the\n * core consumes IN PLACE of its own hit-testing.\n */\nimport { InputComponent, VisibilityState, type World } from \"@iwsdk/core\";\nimport {\n NO_CAPABILITIES,\n type HeadPose,\n type InputCapabilities,\n type InputHitHint,\n type InputProvider,\n type InputSourceSnapshot,\n type Unsubscribe,\n} from \"@realitycollective/webxr-input\";\n\nexport interface IWSDKProviderOptions {\n /**\n * The app enabled IWSDK's `features.grabbing` (+ physics where needed),\n * so grab behaviours are fulfilled NATIVELY by the engine.\n */\n nativeGrab?: boolean;\n}\n\ntype Side = \"left\" | \"right\";\nconst SIDES: readonly Side[] = [\"left\", \"right\"];\n\nexport class IWSDKInputProvider implements InputProvider {\n private readonly world: World;\n private readonly nativeGrab: boolean;\n private capabilities: InputCapabilities;\n private readonly capsListeners = new Set<(c: InputCapabilities) => void>();\n private readonly sourceListeners = new Set<() => void>();\n private hints: InputHitHint[] = [];\n private readonly nativeGrabbing: Record<Side, boolean> = { left: false, right: false };\n private readonly disposers: Unsubscribe[] = [];\n private boundSession: XRSession | null = null;\n private readonly onSourcesChange = () => {\n this.refreshCapabilities();\n for (const listener of [...this.sourceListeners]) listener();\n };\n\n constructor(world: World, options: IWSDKProviderOptions = {}) {\n this.world = world;\n this.nativeGrab = options.nativeGrab ?? false;\n this.capabilities = { ...NO_CAPABILITIES };\n const unsubscribe = world.visibilityState.subscribe(() => {\n this.bindSession();\n this.refreshCapabilities();\n });\n this.disposers.push(unsubscribe);\n this.bindSession();\n this.refreshCapabilities();\n }\n\n private bindSession(): void {\n const session = this.world.session ?? null;\n if (session === this.boundSession) return;\n this.boundSession?.removeEventListener(\"inputsourceschange\", this.onSourcesChange);\n this.boundSession = session;\n session?.addEventListener(\"inputsourceschange\", this.onSourcesChange);\n }\n\n private hasTrackedHand(): boolean {\n const session = this.world.session;\n if (!session) return false;\n for (const source of session.inputSources) {\n if (source.hand) return true;\n }\n return false;\n }\n\n private refreshCapabilities(): void {\n const session = this.world.session;\n const immersive = !!session;\n const handTracking =\n immersive &&\n ((session?.enabledFeatures?.includes(\"hand-tracking\") ?? false) || this.hasTrackedHand());\n let buttonsAxes = false;\n let haptics = false;\n for (const side of SIDES) {\n const gamepad = this.world.input.xr.gamepads[side];\n if (gamepad?.gamepad) buttonsAxes = true;\n if ((gamepad?.gamepad?.hapticActuators?.length ?? 0) > 0) haptics = true;\n }\n const next: InputCapabilities = {\n ...NO_CAPABILITIES,\n rays: immersive,\n // Index-tip spaces exist for controllers too (fall back to ray pose).\n pokes: immersive,\n grabs: immersive ? (this.nativeGrab ? \"native\" : \"poseOnly\") : \"none\",\n handJoints: handTracking,\n pinch: handTracking,\n buttonsAxes,\n gaze: true,\n headPose: true,\n haptics,\n };\n if (JSON.stringify(next) !== JSON.stringify(this.capabilities)) {\n this.capabilities = next;\n for (const listener of [...this.capsListeners]) listener(next);\n }\n }\n\n getCapabilities(): InputCapabilities {\n return this.capabilities;\n }\n\n onCapabilitiesChanged(listener: (c: InputCapabilities) => void): Unsubscribe {\n this.capsListeners.add(listener);\n return () => this.capsListeners.delete(listener);\n }\n\n onSourcesChanged(listener: () => void): Unsubscribe {\n this.sourceListeners.add(listener);\n return () => this.sourceListeners.delete(listener);\n }\n\n /** Bridge-system input: this frame's pre-resolved tag hints. */\n setHints(hints: InputHitHint[]): void {\n this.hints = hints;\n }\n\n sampleHints(): readonly InputHitHint[] {\n return this.hints;\n }\n\n /** Bridge-system input: a side is currently native-grabbing. */\n setNativeGrabbing(side: Side, grabbing: boolean): void {\n this.nativeGrabbing[side] = grabbing;\n }\n\n sample(): readonly InputSourceSnapshot[] {\n if (!this.world.session) return [];\n if (this.world.visibilityState.peek() !== VisibilityState.Visible) return [];\n\n const spaces = this.world.playerSpaceEntities;\n const snapshots: InputSourceSnapshot[] = [];\n for (const side of SIDES) {\n const rayObject = spaces.raySpaces[side].object3D;\n if (!rayObject) continue;\n const gamepad = this.world.input.xr.gamepads[side];\n const hand = this.capabilities.handJoints;\n\n rayObject.updateWorldMatrix(true, false);\n const rayPos = rayObject.getWorldPosition(TEMP.v1);\n const rayQuat = rayObject.getWorldQuaternion(TEMP.q1);\n const forward = TEMP.v2.set(0, 0, -1).applyQuaternion(rayQuat);\n\n const select = gamepad\n ? Math.max(gamepad.getButtonValue(InputComponent.Trigger), gamepad.getSelecting() ? 1 : 0)\n : 0;\n const squeeze = gamepad ? gamepad.getButtonValue(InputComponent.Squeeze) : 0;\n\n const snapshot: InputSourceSnapshot = {\n id: `${side}-input`,\n kind: hand ? \"hand\" : \"controller\",\n handedness: side,\n ray: {\n origin: [rayPos.x, rayPos.y, rayPos.z],\n direction: [forward.x, forward.y, forward.z],\n },\n select,\n squeeze,\n };\n\n const gripObject = spaces.gripSpaces[side].object3D;\n if (gripObject) {\n const gripPos = gripObject.getWorldPosition(TEMP.v1);\n const gripQuat = gripObject.getWorldQuaternion(TEMP.q1);\n snapshot.gripPose = {\n position: [gripPos.x, gripPos.y, gripPos.z],\n quaternion: [gripQuat.x, gripQuat.y, gripQuat.z, gripQuat.w],\n };\n }\n const tipObject = spaces.indexTipSpaces[side].object3D;\n if (tipObject) {\n const tipPos = tipObject.getWorldPosition(TEMP.v1);\n snapshot.indexTip = [tipPos.x, tipPos.y, tipPos.z];\n }\n if (this.nativeGrabbing[side]) snapshot.nativeGrabbing = true;\n if ((gamepad?.gamepad?.hapticActuators?.length ?? 0) > 0) {\n snapshot.hapticsAvailable = true;\n }\n snapshots.push(snapshot);\n }\n return snapshots;\n }\n\n getHeadPose(): HeadPose {\n const head = this.world.playerSpaceEntities.head.object3D;\n if (!head) return { position: [0, 0, 0], quaternion: [0, 0, 0, 1] };\n const position = head.getWorldPosition(TEMP.v1);\n const quaternion = head.getWorldQuaternion(TEMP.q1);\n return {\n position: [position.x, position.y, position.z],\n quaternion: [quaternion.x, quaternion.y, quaternion.z, quaternion.w],\n };\n }\n\n pulse(sourceId: string, intensity: number, durationMs: number): boolean {\n const side = sourceId.startsWith(\"left\") ? \"left\" : sourceId.startsWith(\"right\") ? \"right\" : null;\n if (!side) return false;\n const actuator = this.world.input.xr.gamepads[side]?.gamepad?.hapticActuators?.[0];\n if (!actuator) return false;\n void (actuator as { pulse?: (i: number, d: number) => Promise<boolean> }).pulse?.(\n Math.min(1, Math.max(0, intensity)),\n durationMs,\n );\n return true;\n }\n\n dispose(): void {\n this.boundSession?.removeEventListener(\"inputsourceschange\", this.onSourcesChange);\n for (const dispose of this.disposers) dispose();\n }\n}\n\n// Shared scratch objects (single-threaded frame sampling).\nimport { Quaternion, Vector3 } from \"@iwsdk/core\";\nconst TEMP = { v1: new Vector3(), v2: new Vector3(), q1: new Quaternion() };\n"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-call setup: bind the interaction runtime onto an IWSDK world.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* const interactions = registerInteractions(world, { nativeGrab: false });
|
|
6
|
+
* interactions.register(
|
|
7
|
+
* { id: "beacon", behaviours: [{ kind: "press" }, { kind: "pulse" }] },
|
|
8
|
+
* beaconEntity,
|
|
9
|
+
* );
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* The bridge system runs once per frame: it turns IWSDK's own targeting
|
|
13
|
+
* (`Pressed`/`Grabbed` tags on registered entities) into provider hints -
|
|
14
|
+
* provider power wins over the core's approximate hit-testing - then
|
|
15
|
+
* ticks the runtime. A light proximity/ray hit-tester still serves gaze
|
|
16
|
+
* (dwell) and anything IWSDK's pipeline does not cover.
|
|
17
|
+
*/
|
|
18
|
+
import { type Entity, type World } from "@iwsdk/core";
|
|
19
|
+
import { InteractionRuntime, type DwellConfig, type InteractableDescriptor } from "@realitycollective/webxr-interactions";
|
|
20
|
+
import { IWSDKInputProvider, type IWSDKProviderOptions } from "./provider.js";
|
|
21
|
+
import { IWSDKTransformPort } from "./transform-port.js";
|
|
22
|
+
export interface IWSDKRegisterOptions extends IWSDKProviderOptions {
|
|
23
|
+
dwellDefaults?: DwellConfig;
|
|
24
|
+
}
|
|
25
|
+
export interface IWSDKRegisterEntityOptions {
|
|
26
|
+
/** Add `PokeInteractable`/`RayInteractable` for pressable targets (default true). */
|
|
27
|
+
addInteractables?: boolean;
|
|
28
|
+
/** Targeting radius for the approximate gaze/poke hit-tester (default 0.1 m). */
|
|
29
|
+
targetRadius?: number;
|
|
30
|
+
}
|
|
31
|
+
export declare class IWSDKInteractions {
|
|
32
|
+
readonly runtime: InteractionRuntime;
|
|
33
|
+
readonly provider: IWSDKInputProvider;
|
|
34
|
+
private readonly hitTester;
|
|
35
|
+
private readonly world;
|
|
36
|
+
private readonly entities;
|
|
37
|
+
private readonly ports;
|
|
38
|
+
constructor(world: World, options?: IWSDKRegisterOptions);
|
|
39
|
+
register(descriptor: InteractableDescriptor, entity: Entity, options?: IWSDKRegisterEntityOptions): IWSDKTransformPort | undefined;
|
|
40
|
+
unregister(id: string): void;
|
|
41
|
+
getPort(id: string): IWSDKTransformPort | undefined;
|
|
42
|
+
getEntity(id: string): Entity | undefined;
|
|
43
|
+
/** Called by the bridge system once per frame. */
|
|
44
|
+
tick(delta: number, pressedEntities: Iterable<Entity>, grabbedEntities: Iterable<Entity>): void;
|
|
45
|
+
/** Attribute an engine tag to the nearest input side. */
|
|
46
|
+
private attributeSide;
|
|
47
|
+
dispose(): void;
|
|
48
|
+
}
|
|
49
|
+
export declare function interactionsFor(world: World): IWSDKInteractions | undefined;
|
|
50
|
+
declare const InteractionBridgeSystem_base: import("elics").SystemConstructor<import("elics").SystemSchema, {
|
|
51
|
+
pressed: {
|
|
52
|
+
required: import("elics").Component<{}>[];
|
|
53
|
+
};
|
|
54
|
+
grabbed: {
|
|
55
|
+
required: import("elics").Component<{}>[];
|
|
56
|
+
};
|
|
57
|
+
}, World, import("@iwsdk/core").System<import("elics").SystemSchema, {
|
|
58
|
+
pressed: {
|
|
59
|
+
required: import("elics").Component<{}>[];
|
|
60
|
+
};
|
|
61
|
+
grabbed: {
|
|
62
|
+
required: import("elics").Component<{}>[];
|
|
63
|
+
};
|
|
64
|
+
}>>;
|
|
65
|
+
/** The per-frame bridge: IWSDK tags → provider hints → runtime tick. */
|
|
66
|
+
export declare class InteractionBridgeSystem extends InteractionBridgeSystem_base {
|
|
67
|
+
update(delta: number): void;
|
|
68
|
+
}
|
|
69
|
+
/** Register the Interaction Extensions on an IWSDK world (one call). */
|
|
70
|
+
export declare function registerInteractions(world: World, options?: IWSDKRegisterOptions): IWSDKInteractions;
|
|
71
|
+
export {};
|
package/dist/register.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-call setup: bind the interaction runtime onto an IWSDK world.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* const interactions = registerInteractions(world, { nativeGrab: false });
|
|
6
|
+
* interactions.register(
|
|
7
|
+
* { id: "beacon", behaviours: [{ kind: "press" }, { kind: "pulse" }] },
|
|
8
|
+
* beaconEntity,
|
|
9
|
+
* );
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* The bridge system runs once per frame: it turns IWSDK's own targeting
|
|
13
|
+
* (`Pressed`/`Grabbed` tags on registered entities) into provider hints -
|
|
14
|
+
* provider power wins over the core's approximate hit-testing - then
|
|
15
|
+
* ticks the runtime. A light proximity/ray hit-tester still serves gaze
|
|
16
|
+
* (dwell) and anything IWSDK's pipeline does not cover.
|
|
17
|
+
*/
|
|
18
|
+
import { createSystem, Grabbed, PokeInteractable, Pressed, RayInteractable, Vector3, } from "@iwsdk/core";
|
|
19
|
+
import { InteractionRuntime, rayPointDistance, } from "@realitycollective/webxr-interactions";
|
|
20
|
+
import { IWSDKInputProvider } from "./provider.js";
|
|
21
|
+
import { IWSDKTransformPort } from "./transform-port.js";
|
|
22
|
+
const TEMP_V = new Vector3();
|
|
23
|
+
/** Approximate hit-tester over registered entities (gaze/dwell targeting). */
|
|
24
|
+
class EntityHitTester {
|
|
25
|
+
entries = new Map();
|
|
26
|
+
register(id, entity, radius) {
|
|
27
|
+
this.entries.set(id, { entity, radius });
|
|
28
|
+
}
|
|
29
|
+
unregister(id) {
|
|
30
|
+
this.entries.delete(id);
|
|
31
|
+
}
|
|
32
|
+
hitRay(ray) {
|
|
33
|
+
let best = null;
|
|
34
|
+
for (const [id, { entity, radius }] of this.entries) {
|
|
35
|
+
const object = entity.object3D;
|
|
36
|
+
if (!object || !object.visible)
|
|
37
|
+
continue;
|
|
38
|
+
object.getWorldPosition(TEMP_V);
|
|
39
|
+
const point = [TEMP_V.x, TEMP_V.y, TEMP_V.z];
|
|
40
|
+
const { distance, t } = rayPointDistance(ray, point);
|
|
41
|
+
if (t > 0 && distance <= radius && (best === null || t < best.distance)) {
|
|
42
|
+
best = { interactableId: id, distance: t, point };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return best;
|
|
46
|
+
}
|
|
47
|
+
hitProximity(point, radius) {
|
|
48
|
+
let best = null;
|
|
49
|
+
for (const [id, { entity, radius: targetRadius }] of this.entries) {
|
|
50
|
+
const object = entity.object3D;
|
|
51
|
+
if (!object || !object.visible)
|
|
52
|
+
continue;
|
|
53
|
+
object.getWorldPosition(TEMP_V);
|
|
54
|
+
const distance = Math.hypot(TEMP_V.x - point[0], TEMP_V.y - point[1], TEMP_V.z - point[2]) - targetRadius;
|
|
55
|
+
if (distance <= radius && (best === null || distance < best.distance)) {
|
|
56
|
+
best = {
|
|
57
|
+
interactableId: id,
|
|
58
|
+
distance: Math.max(0, distance),
|
|
59
|
+
point: [TEMP_V.x, TEMP_V.y, TEMP_V.z],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return best;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export class IWSDKInteractions {
|
|
67
|
+
runtime;
|
|
68
|
+
provider;
|
|
69
|
+
hitTester = new EntityHitTester();
|
|
70
|
+
world;
|
|
71
|
+
entities = new Map();
|
|
72
|
+
ports = new Map();
|
|
73
|
+
constructor(world, options = {}) {
|
|
74
|
+
this.world = world;
|
|
75
|
+
this.provider = new IWSDKInputProvider(world, options);
|
|
76
|
+
this.runtime = new InteractionRuntime({
|
|
77
|
+
provider: this.provider,
|
|
78
|
+
hitTester: this.hitTester,
|
|
79
|
+
...(options.dwellDefaults ? { dwellDefaults: options.dwellDefaults } : {}),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
register(descriptor, entity, options = {}) {
|
|
83
|
+
const pressable = descriptor.behaviours.some((b) => b.kind === "press");
|
|
84
|
+
if ((options.addInteractables ?? true) && pressable) {
|
|
85
|
+
if (!entity.hasComponent(PokeInteractable))
|
|
86
|
+
entity.addComponent(PokeInteractable);
|
|
87
|
+
if (!entity.hasComponent(RayInteractable))
|
|
88
|
+
entity.addComponent(RayInteractable);
|
|
89
|
+
}
|
|
90
|
+
this.entities.set(descriptor.id, entity);
|
|
91
|
+
this.hitTester.register(descriptor.id, entity, options.targetRadius ?? 0.1);
|
|
92
|
+
const object = entity.object3D;
|
|
93
|
+
let port;
|
|
94
|
+
if (object) {
|
|
95
|
+
port = new IWSDKTransformPort(object);
|
|
96
|
+
this.ports.set(descriptor.id, port);
|
|
97
|
+
}
|
|
98
|
+
this.runtime.registerInteractable(descriptor, port ? { transform: port } : {});
|
|
99
|
+
return port;
|
|
100
|
+
}
|
|
101
|
+
unregister(id) {
|
|
102
|
+
this.runtime.unregisterInteractable(id);
|
|
103
|
+
this.hitTester.unregister(id);
|
|
104
|
+
this.entities.delete(id);
|
|
105
|
+
this.ports.delete(id);
|
|
106
|
+
}
|
|
107
|
+
getPort(id) {
|
|
108
|
+
return this.ports.get(id);
|
|
109
|
+
}
|
|
110
|
+
getEntity(id) {
|
|
111
|
+
return this.entities.get(id);
|
|
112
|
+
}
|
|
113
|
+
/** Called by the bridge system once per frame. */
|
|
114
|
+
tick(delta, pressedEntities, grabbedEntities) {
|
|
115
|
+
const hints = [];
|
|
116
|
+
const pressed = new Set(pressedEntities);
|
|
117
|
+
const grabbed = new Set(grabbedEntities);
|
|
118
|
+
let leftGrabbing = false;
|
|
119
|
+
let rightGrabbing = false;
|
|
120
|
+
for (const [id, entity] of this.entities) {
|
|
121
|
+
if (pressed.has(entity)) {
|
|
122
|
+
hints.push({ sourceId: this.attributeSide(entity), targetId: id, state: "press" });
|
|
123
|
+
}
|
|
124
|
+
if (grabbed.has(entity)) {
|
|
125
|
+
const sourceId = this.attributeSide(entity, "grip");
|
|
126
|
+
hints.push({ sourceId, targetId: id, state: "grab" });
|
|
127
|
+
if (sourceId === "left-input")
|
|
128
|
+
leftGrabbing = true;
|
|
129
|
+
else
|
|
130
|
+
rightGrabbing = true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
this.provider.setNativeGrabbing("left", leftGrabbing);
|
|
134
|
+
this.provider.setNativeGrabbing("right", rightGrabbing);
|
|
135
|
+
this.provider.setHints(hints);
|
|
136
|
+
this.runtime.update(delta);
|
|
137
|
+
}
|
|
138
|
+
/** Attribute an engine tag to the nearest input side. */
|
|
139
|
+
attributeSide(entity, space = "tip") {
|
|
140
|
+
const object = entity.object3D;
|
|
141
|
+
const spaces = this.world.playerSpaceEntities;
|
|
142
|
+
if (!object)
|
|
143
|
+
return "right-input";
|
|
144
|
+
object.getWorldPosition(TEMP_V);
|
|
145
|
+
const target = [TEMP_V.x, TEMP_V.y, TEMP_V.z];
|
|
146
|
+
let bestSide = "right";
|
|
147
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
148
|
+
for (const side of ["left", "right"]) {
|
|
149
|
+
const spaceEntity = space === "tip" ? spaces.indexTipSpaces[side] : spaces.gripSpaces[side];
|
|
150
|
+
const spaceObject = spaceEntity.object3D;
|
|
151
|
+
if (!spaceObject)
|
|
152
|
+
continue;
|
|
153
|
+
spaceObject.getWorldPosition(TEMP_V);
|
|
154
|
+
const distance = Math.hypot(TEMP_V.x - target[0], TEMP_V.y - target[1], TEMP_V.z - target[2]);
|
|
155
|
+
if (distance < bestDistance) {
|
|
156
|
+
bestDistance = distance;
|
|
157
|
+
bestSide = side;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return `${bestSide}-input`;
|
|
161
|
+
}
|
|
162
|
+
dispose() {
|
|
163
|
+
this.runtime.dispose();
|
|
164
|
+
this.provider.dispose();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const hosts = new WeakMap();
|
|
168
|
+
export function interactionsFor(world) {
|
|
169
|
+
return hosts.get(world);
|
|
170
|
+
}
|
|
171
|
+
/** The per-frame bridge: IWSDK tags → provider hints → runtime tick. */
|
|
172
|
+
export class InteractionBridgeSystem extends createSystem({
|
|
173
|
+
pressed: { required: [Pressed] },
|
|
174
|
+
grabbed: { required: [Grabbed] },
|
|
175
|
+
}) {
|
|
176
|
+
update(delta) {
|
|
177
|
+
const host = hosts.get(this.world);
|
|
178
|
+
if (!host)
|
|
179
|
+
return;
|
|
180
|
+
host.tick(delta, this.queries.pressed.entities, this.queries.grabbed.entities);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/** Register the Interaction Extensions on an IWSDK world (one call). */
|
|
184
|
+
export function registerInteractions(world, options = {}) {
|
|
185
|
+
const existing = hosts.get(world);
|
|
186
|
+
if (existing)
|
|
187
|
+
return existing;
|
|
188
|
+
const host = new IWSDKInteractions(world, options);
|
|
189
|
+
hosts.set(world, host);
|
|
190
|
+
world.registerSystem(InteractionBridgeSystem);
|
|
191
|
+
return host;
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One-call setup: bind the interaction runtime onto an IWSDK world.\n *\n * ```ts\n * const interactions = registerInteractions(world, { nativeGrab: false });\n * interactions.register(\n * { id: \"beacon\", behaviours: [{ kind: \"press\" }, { kind: \"pulse\" }] },\n * beaconEntity,\n * );\n * ```\n *\n * The bridge system runs once per frame: it turns IWSDK's own targeting\n * (`Pressed`/`Grabbed` tags on registered entities) into provider hints -\n * provider power wins over the core's approximate hit-testing - then\n * ticks the runtime. A light proximity/ray hit-tester still serves gaze\n * (dwell) and anything IWSDK's pipeline does not cover.\n */\nimport {\n createSystem,\n Grabbed,\n PokeInteractable,\n Pressed,\n RayInteractable,\n Vector3,\n type Entity,\n type World,\n} from \"@iwsdk/core\";\nimport type { InputHitHint, RayTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport {\n InteractionRuntime,\n rayPointDistance,\n type DwellConfig,\n type HitTester,\n type InteractableDescriptor,\n type InteractableHit,\n} from \"@realitycollective/webxr-interactions\";\nimport { IWSDKInputProvider, type IWSDKProviderOptions } from \"./provider.js\";\nimport { IWSDKTransformPort } from \"./transform-port.js\";\n\nconst TEMP_V = new Vector3();\n\n/** Approximate hit-tester over registered entities (gaze/dwell targeting). */\nclass EntityHitTester implements HitTester {\n private readonly entries = new Map<string, { entity: Entity; radius: number }>();\n\n register(id: string, entity: Entity, radius: number): void {\n this.entries.set(id, { entity, radius });\n }\n\n unregister(id: string): void {\n this.entries.delete(id);\n }\n\n hitRay(ray: RayTuple): InteractableHit | null {\n let best: InteractableHit | null = null;\n for (const [id, { entity, radius }] of this.entries) {\n const object = entity.object3D;\n if (!object || !object.visible) continue;\n object.getWorldPosition(TEMP_V);\n const point: Vec3Tuple = [TEMP_V.x, TEMP_V.y, TEMP_V.z];\n const { distance, t } = rayPointDistance(ray, point);\n if (t > 0 && distance <= radius && (best === null || t < best.distance)) {\n best = { interactableId: id, distance: t, point };\n }\n }\n return best;\n }\n\n hitProximity(point: Vec3Tuple, radius: number): InteractableHit | null {\n let best: InteractableHit | null = null;\n for (const [id, { entity, radius: targetRadius }] of this.entries) {\n const object = entity.object3D;\n if (!object || !object.visible) continue;\n object.getWorldPosition(TEMP_V);\n const distance =\n Math.hypot(TEMP_V.x - point[0], TEMP_V.y - point[1], TEMP_V.z - point[2]) - targetRadius;\n if (distance <= radius && (best === null || distance < best.distance)) {\n best = {\n interactableId: id,\n distance: Math.max(0, distance),\n point: [TEMP_V.x, TEMP_V.y, TEMP_V.z],\n };\n }\n }\n return best;\n }\n}\n\nexport interface IWSDKRegisterOptions extends IWSDKProviderOptions {\n dwellDefaults?: DwellConfig;\n}\n\nexport interface IWSDKRegisterEntityOptions {\n /** Add `PokeInteractable`/`RayInteractable` for pressable targets (default true). */\n addInteractables?: boolean;\n /** Targeting radius for the approximate gaze/poke hit-tester (default 0.1 m). */\n targetRadius?: number;\n}\n\nexport class IWSDKInteractions {\n readonly runtime: InteractionRuntime;\n readonly provider: IWSDKInputProvider;\n private readonly hitTester = new EntityHitTester();\n private readonly world: World;\n private readonly entities = new Map<string, Entity>();\n private readonly ports = new Map<string, IWSDKTransformPort>();\n\n constructor(world: World, options: IWSDKRegisterOptions = {}) {\n this.world = world;\n this.provider = new IWSDKInputProvider(world, options);\n this.runtime = new InteractionRuntime({\n provider: this.provider,\n hitTester: this.hitTester,\n ...(options.dwellDefaults ? { dwellDefaults: options.dwellDefaults } : {}),\n });\n }\n\n register(\n descriptor: InteractableDescriptor,\n entity: Entity,\n options: IWSDKRegisterEntityOptions = {},\n ): IWSDKTransformPort | undefined {\n const pressable = descriptor.behaviours.some((b) => b.kind === \"press\");\n if ((options.addInteractables ?? true) && pressable) {\n if (!entity.hasComponent(PokeInteractable)) entity.addComponent(PokeInteractable);\n if (!entity.hasComponent(RayInteractable)) entity.addComponent(RayInteractable);\n }\n this.entities.set(descriptor.id, entity);\n this.hitTester.register(descriptor.id, entity, options.targetRadius ?? 0.1);\n const object = entity.object3D;\n let port: IWSDKTransformPort | undefined;\n if (object) {\n port = new IWSDKTransformPort(object);\n this.ports.set(descriptor.id, port);\n }\n this.runtime.registerInteractable(descriptor, port ? { transform: port } : {});\n return port;\n }\n\n unregister(id: string): void {\n this.runtime.unregisterInteractable(id);\n this.hitTester.unregister(id);\n this.entities.delete(id);\n this.ports.delete(id);\n }\n\n getPort(id: string): IWSDKTransformPort | undefined {\n return this.ports.get(id);\n }\n\n getEntity(id: string): Entity | undefined {\n return this.entities.get(id);\n }\n\n /** Called by the bridge system once per frame. */\n tick(delta: number, pressedEntities: Iterable<Entity>, grabbedEntities: Iterable<Entity>): void {\n const hints: InputHitHint[] = [];\n const pressed = new Set(pressedEntities);\n const grabbed = new Set(grabbedEntities);\n let leftGrabbing = false;\n let rightGrabbing = false;\n\n for (const [id, entity] of this.entities) {\n if (pressed.has(entity)) {\n hints.push({ sourceId: this.attributeSide(entity), targetId: id, state: \"press\" });\n }\n if (grabbed.has(entity)) {\n const sourceId = this.attributeSide(entity, \"grip\");\n hints.push({ sourceId, targetId: id, state: \"grab\" });\n if (sourceId === \"left-input\") leftGrabbing = true;\n else rightGrabbing = true;\n }\n }\n this.provider.setNativeGrabbing(\"left\", leftGrabbing);\n this.provider.setNativeGrabbing(\"right\", rightGrabbing);\n this.provider.setHints(hints);\n this.runtime.update(delta);\n }\n\n /** Attribute an engine tag to the nearest input side. */\n private attributeSide(entity: Entity, space: \"tip\" | \"grip\" = \"tip\"): string {\n const object = entity.object3D;\n const spaces = this.world.playerSpaceEntities;\n if (!object) return \"right-input\";\n object.getWorldPosition(TEMP_V);\n const target: Vec3Tuple = [TEMP_V.x, TEMP_V.y, TEMP_V.z];\n let bestSide = \"right\";\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const side of [\"left\", \"right\"] as const) {\n const spaceEntity =\n space === \"tip\" ? spaces.indexTipSpaces[side] : spaces.gripSpaces[side];\n const spaceObject = spaceEntity.object3D;\n if (!spaceObject) continue;\n spaceObject.getWorldPosition(TEMP_V);\n const distance = Math.hypot(\n TEMP_V.x - target[0],\n TEMP_V.y - target[1],\n TEMP_V.z - target[2],\n );\n if (distance < bestDistance) {\n bestDistance = distance;\n bestSide = side;\n }\n }\n return `${bestSide}-input`;\n }\n\n dispose(): void {\n this.runtime.dispose();\n this.provider.dispose();\n }\n}\n\nconst hosts = new WeakMap<World, IWSDKInteractions>();\n\nexport function interactionsFor(world: World): IWSDKInteractions | undefined {\n return hosts.get(world);\n}\n\n/** The per-frame bridge: IWSDK tags → provider hints → runtime tick. */\nexport class InteractionBridgeSystem extends createSystem({\n pressed: { required: [Pressed] },\n grabbed: { required: [Grabbed] },\n}) {\n override update(delta: number): void {\n const host = hosts.get(this.world as unknown as World);\n if (!host) return;\n host.tick(delta, this.queries.pressed.entities, this.queries.grabbed.entities);\n }\n}\n\n/** Register the Interaction Extensions on an IWSDK world (one call). */\nexport function registerInteractions(\n world: World,\n options: IWSDKRegisterOptions = {},\n): IWSDKInteractions {\n const existing = hosts.get(world);\n if (existing) return existing;\n const host = new IWSDKInteractions(world, options);\n hosts.set(world, host);\n world.registerSystem(InteractionBridgeSystem);\n return host;\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IWSDKTransformPort - the core's TransformPort over an IWSDK entity's
|
|
3
|
+
* `object3D`, honouring FROM-REST semantics (rest captured at
|
|
4
|
+
* construction; `getWorldPose()` reports the rest pose in world space).
|
|
5
|
+
* Same math as the three.js adapter's port, expressed against IWSDK's
|
|
6
|
+
* re-exported three types so there is exactly one three instance.
|
|
7
|
+
*/
|
|
8
|
+
import { type Object3D } from "@iwsdk/core";
|
|
9
|
+
import type { PoseTuple, QuatTuple, Vec3Tuple } from "@realitycollective/webxr-input";
|
|
10
|
+
import type { TransformPort } from "@realitycollective/webxr-interactions";
|
|
11
|
+
export declare class IWSDKTransformPort implements TransformPort {
|
|
12
|
+
private readonly object;
|
|
13
|
+
private readonly restPosition;
|
|
14
|
+
private readonly restQuaternion;
|
|
15
|
+
private readonly restScale;
|
|
16
|
+
private baseEmissive;
|
|
17
|
+
private readonly v;
|
|
18
|
+
private readonly q;
|
|
19
|
+
private readonly q2;
|
|
20
|
+
private readonly m;
|
|
21
|
+
constructor(object: Object3D);
|
|
22
|
+
recaptureRest(): void;
|
|
23
|
+
getWorldPose(): PoseTuple;
|
|
24
|
+
getLocalOffset(): Vec3Tuple;
|
|
25
|
+
setLocalOffset(offset: Vec3Tuple): void;
|
|
26
|
+
setLocalRotation(quaternion: QuatTuple): void;
|
|
27
|
+
setWorldPose(pose: PoseTuple): void;
|
|
28
|
+
setEffect(effect: {
|
|
29
|
+
scale?: number;
|
|
30
|
+
emissive?: number;
|
|
31
|
+
}): void;
|
|
32
|
+
private firstEmissiveMaterial;
|
|
33
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IWSDKTransformPort - the core's TransformPort over an IWSDK entity's
|
|
3
|
+
* `object3D`, honouring FROM-REST semantics (rest captured at
|
|
4
|
+
* construction; `getWorldPose()` reports the rest pose in world space).
|
|
5
|
+
* Same math as the three.js adapter's port, expressed against IWSDK's
|
|
6
|
+
* re-exported three types so there is exactly one three instance.
|
|
7
|
+
*/
|
|
8
|
+
import { Matrix4, Quaternion, Vector3, } from "@iwsdk/core";
|
|
9
|
+
export class IWSDKTransformPort {
|
|
10
|
+
object;
|
|
11
|
+
restPosition = new Vector3();
|
|
12
|
+
restQuaternion = new Quaternion();
|
|
13
|
+
restScale = new Vector3();
|
|
14
|
+
baseEmissive = null;
|
|
15
|
+
v = new Vector3();
|
|
16
|
+
q = new Quaternion();
|
|
17
|
+
q2 = new Quaternion();
|
|
18
|
+
m = new Matrix4();
|
|
19
|
+
constructor(object) {
|
|
20
|
+
this.object = object;
|
|
21
|
+
this.recaptureRest();
|
|
22
|
+
}
|
|
23
|
+
recaptureRest() {
|
|
24
|
+
this.restPosition.copy(this.object.position);
|
|
25
|
+
this.restQuaternion.copy(this.object.quaternion);
|
|
26
|
+
this.restScale.copy(this.object.scale);
|
|
27
|
+
}
|
|
28
|
+
getWorldPose() {
|
|
29
|
+
const parent = this.object.parent;
|
|
30
|
+
if (parent) {
|
|
31
|
+
parent.updateWorldMatrix(true, false);
|
|
32
|
+
this.v.copy(this.restPosition).applyMatrix4(parent.matrixWorld);
|
|
33
|
+
parent.getWorldQuaternion(this.q2);
|
|
34
|
+
this.q.copy(this.q2).multiply(this.restQuaternion);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
this.v.copy(this.restPosition);
|
|
38
|
+
this.q.copy(this.restQuaternion);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
position: [this.v.x, this.v.y, this.v.z],
|
|
42
|
+
quaternion: [this.q.x, this.q.y, this.q.z, this.q.w],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
getLocalOffset() {
|
|
46
|
+
this.v.copy(this.object.position).sub(this.restPosition);
|
|
47
|
+
this.q.copy(this.restQuaternion).invert();
|
|
48
|
+
this.v.applyQuaternion(this.q);
|
|
49
|
+
return [this.v.x, this.v.y, this.v.z];
|
|
50
|
+
}
|
|
51
|
+
setLocalOffset(offset) {
|
|
52
|
+
this.v.set(offset[0], offset[1], offset[2]).applyQuaternion(this.restQuaternion);
|
|
53
|
+
this.object.position.copy(this.restPosition).add(this.v);
|
|
54
|
+
}
|
|
55
|
+
setLocalRotation(quaternion) {
|
|
56
|
+
this.q.set(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);
|
|
57
|
+
this.object.quaternion.copy(this.restQuaternion).multiply(this.q);
|
|
58
|
+
}
|
|
59
|
+
setWorldPose(pose) {
|
|
60
|
+
const parent = this.object.parent;
|
|
61
|
+
this.v.set(pose.position[0], pose.position[1], pose.position[2]);
|
|
62
|
+
this.q.set(pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3]);
|
|
63
|
+
if (parent) {
|
|
64
|
+
parent.updateWorldMatrix(true, false);
|
|
65
|
+
this.m.copy(parent.matrixWorld).invert();
|
|
66
|
+
this.v.applyMatrix4(this.m);
|
|
67
|
+
parent.getWorldQuaternion(this.q2).invert();
|
|
68
|
+
this.q.premultiply(this.q2);
|
|
69
|
+
}
|
|
70
|
+
this.object.position.copy(this.v);
|
|
71
|
+
this.object.quaternion.copy(this.q);
|
|
72
|
+
}
|
|
73
|
+
setEffect(effect) {
|
|
74
|
+
if (effect.scale !== undefined) {
|
|
75
|
+
this.object.scale.copy(this.restScale).multiplyScalar(effect.scale);
|
|
76
|
+
}
|
|
77
|
+
if (effect.emissive !== undefined) {
|
|
78
|
+
const material = this.firstEmissiveMaterial();
|
|
79
|
+
if (material) {
|
|
80
|
+
if (this.baseEmissive === null)
|
|
81
|
+
this.baseEmissive = material.emissiveIntensity;
|
|
82
|
+
material.emissiveIntensity = this.baseEmissive + effect.emissive;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
firstEmissiveMaterial() {
|
|
87
|
+
let found = null;
|
|
88
|
+
this.object.traverse((child) => {
|
|
89
|
+
if (found)
|
|
90
|
+
return;
|
|
91
|
+
const material = child.material;
|
|
92
|
+
const single = Array.isArray(material) ? material[0] : material;
|
|
93
|
+
if (single && "emissiveIntensity" in single) {
|
|
94
|
+
found = single;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
return found;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=transform-port.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform-port.js","sourceRoot":"","sources":["../src/transform-port.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IWSDKTransformPort - the core's TransformPort over an IWSDK entity's\n * `object3D`, honouring FROM-REST semantics (rest captured at\n * construction; `getWorldPose()` reports the rest pose in world space).\n * Same math as the three.js adapter's port, expressed against IWSDK's\n * re-exported three types so there is exactly one three instance.\n */\nimport {\n Matrix4,\n Quaternion,\n Vector3,\n type Material,\n type Mesh,\n type MeshStandardMaterial,\n type Object3D,\n} from \"@iwsdk/core\";\nimport type { PoseTuple, QuatTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport type { TransformPort } from \"@realitycollective/webxr-interactions\";\n\nexport class IWSDKTransformPort implements TransformPort {\n private readonly object: Object3D;\n private readonly restPosition = new Vector3();\n private readonly restQuaternion = new Quaternion();\n private readonly restScale = new Vector3();\n private baseEmissive: number | null = null;\n\n private readonly v = new Vector3();\n private readonly q = new Quaternion();\n private readonly q2 = new Quaternion();\n private readonly m = new Matrix4();\n\n constructor(object: Object3D) {\n this.object = object;\n this.recaptureRest();\n }\n\n recaptureRest(): void {\n this.restPosition.copy(this.object.position);\n this.restQuaternion.copy(this.object.quaternion);\n this.restScale.copy(this.object.scale);\n }\n\n getWorldPose(): PoseTuple {\n const parent = this.object.parent;\n if (parent) {\n parent.updateWorldMatrix(true, false);\n this.v.copy(this.restPosition).applyMatrix4(parent.matrixWorld);\n parent.getWorldQuaternion(this.q2);\n this.q.copy(this.q2).multiply(this.restQuaternion);\n } else {\n this.v.copy(this.restPosition);\n this.q.copy(this.restQuaternion);\n }\n return {\n position: [this.v.x, this.v.y, this.v.z],\n quaternion: [this.q.x, this.q.y, this.q.z, this.q.w],\n };\n }\n\n getLocalOffset(): Vec3Tuple {\n this.v.copy(this.object.position).sub(this.restPosition);\n this.q.copy(this.restQuaternion).invert();\n this.v.applyQuaternion(this.q);\n return [this.v.x, this.v.y, this.v.z];\n }\n\n setLocalOffset(offset: Vec3Tuple): void {\n this.v.set(offset[0], offset[1], offset[2]).applyQuaternion(this.restQuaternion);\n this.object.position.copy(this.restPosition).add(this.v);\n }\n\n setLocalRotation(quaternion: QuatTuple): void {\n this.q.set(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);\n this.object.quaternion.copy(this.restQuaternion).multiply(this.q);\n }\n\n setWorldPose(pose: PoseTuple): void {\n const parent = this.object.parent;\n this.v.set(pose.position[0], pose.position[1], pose.position[2]);\n this.q.set(pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3]);\n if (parent) {\n parent.updateWorldMatrix(true, false);\n this.m.copy(parent.matrixWorld).invert();\n this.v.applyMatrix4(this.m);\n parent.getWorldQuaternion(this.q2).invert();\n this.q.premultiply(this.q2);\n }\n this.object.position.copy(this.v);\n this.object.quaternion.copy(this.q);\n }\n\n setEffect(effect: { scale?: number; emissive?: number }): void {\n if (effect.scale !== undefined) {\n this.object.scale.copy(this.restScale).multiplyScalar(effect.scale);\n }\n if (effect.emissive !== undefined) {\n const material = this.firstEmissiveMaterial();\n if (material) {\n if (this.baseEmissive === null) this.baseEmissive = material.emissiveIntensity;\n material.emissiveIntensity = this.baseEmissive + effect.emissive;\n }\n }\n }\n\n private firstEmissiveMaterial(): MeshStandardMaterial | null {\n let found: MeshStandardMaterial | null = null;\n this.object.traverse((child) => {\n if (found) return;\n const material = (child as Mesh).material as Material | Material[] | undefined;\n const single = Array.isArray(material) ? material[0] : material;\n if (single && \"emissiveIntensity\" in single) {\n found = single as MeshStandardMaterial;\n }\n });\n return found;\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@realitycollective/iwsdk-interactions",
|
|
3
|
+
"version": "0.1.0-preview.0",
|
|
4
|
+
"description": "Meta IWSDK adapter for the Reality Collective Interaction Extensions - feeds IWSDK's input machinery (Pressed/Grabbed tags, poke/ray interactables, grabbables, XR gamepads, hand joints) into the engine-free @realitycollective/webxr-interactions core, with native grab fulfilment. Re-exports the core.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"realitycollective",
|
|
7
|
+
"iwsdk",
|
|
8
|
+
"webxr",
|
|
9
|
+
"xr",
|
|
10
|
+
"interaction",
|
|
11
|
+
"input",
|
|
12
|
+
"typescript"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Reality Collective",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"CHANGELOG.md"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.build.json",
|
|
32
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@realitycollective/webxr-input": "^0.1.0",
|
|
36
|
+
"@realitycollective/webxr-interactions": "^0.1.0-preview.0"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@iwsdk/core": ">=0.4.0 <0.6.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@iwsdk/core": "0.4.2",
|
|
43
|
+
"three": "npm:super-three@0.181.0",
|
|
44
|
+
"@types/three": "*"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/realitycollective/WebXR-Interactions.git",
|
|
49
|
+
"directory": "packages/iwsdk-interactions"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/realitycollective/WebXR-Interactions#readme",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/realitycollective/WebXR-Interactions/issues"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=20.19.0 <21.0.0-0 || >=22.12.0 <23.0.0-0 || >=24.0.0"
|
|
60
|
+
}
|
|
61
|
+
}
|