@iwsdk/core 1.0.0-rc.1 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/gaze/gaze-system.d.ts +207 -0
  2. package/dist/gaze/gaze-system.d.ts.map +1 -0
  3. package/dist/gaze/gaze-system.js +168 -0
  4. package/dist/gaze/gaze-system.js.map +1 -0
  5. package/dist/gaze/index.d.ts +8 -0
  6. package/dist/gaze/index.d.ts.map +1 -0
  7. package/dist/grab/grab-helpers.d.ts +7 -6
  8. package/dist/grab/grab-helpers.d.ts.map +1 -1
  9. package/dist/grab/grab-helpers.js +12 -7
  10. package/dist/grab/grab-helpers.js.map +1 -1
  11. package/dist/grab/grab-system.d.ts.map +1 -1
  12. package/dist/grab/grab-system.js +2 -1
  13. package/dist/grab/grab-system.js.map +1 -1
  14. package/dist/grab/handles.d.ts +10 -1
  15. package/dist/grab/handles.d.ts.map +1 -1
  16. package/dist/grab/handles.js +29 -6
  17. package/dist/grab/handles.js.map +1 -1
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/init/world-initializer.d.ts +49 -0
  23. package/dist/init/world-initializer.d.ts.map +1 -1
  24. package/dist/init/world-initializer.js +21 -8
  25. package/dist/init/world-initializer.js.map +1 -1
  26. package/dist/init/xr.d.ts +14 -0
  27. package/dist/init/xr.d.ts.map +1 -1
  28. package/dist/init/xr.js +19 -0
  29. package/dist/init/xr.js.map +1 -1
  30. package/dist/input/input-system.d.ts +3 -0
  31. package/dist/input/input-system.d.ts.map +1 -1
  32. package/dist/input/input-system.js +72 -6
  33. package/dist/input/input-system.js.map +1 -1
  34. package/dist/input/state-tags.d.ts.map +1 -1
  35. package/dist/input/state-tags.js +2 -0
  36. package/dist/input/state-tags.js.map +1 -1
  37. package/dist/project/normalize.d.ts.map +1 -1
  38. package/dist/project/normalize.js +11 -0
  39. package/dist/project/normalize.js.map +1 -1
  40. package/dist/project/types.d.ts +32 -2
  41. package/dist/project/types.d.ts.map +1 -1
  42. package/dist/project/types.js.map +1 -1
  43. package/dist/project/validation.d.ts.map +1 -1
  44. package/dist/project/validation.js +65 -1
  45. package/dist/project/validation.js.map +1 -1
  46. package/dist/schemas/iwsdk-project.v1.schema.json +45 -3
  47. package/dist/version.d.ts +1 -1
  48. package/dist/version.js +1 -1
  49. package/dist/version.js.map +1 -1
  50. package/package.json +4 -4
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { Types } from '../ecs/index.js';
8
+ declare const GazeSystem_base: import("elics").SystemConstructor<{
9
+ /**
10
+ * If true, gaze is suppressed whenever a near touch/grab pointer is in
11
+ * HOVER or SELECT. Set false to allow gaze to remain active concurrently.
12
+ * @default true
13
+ */
14
+ suppressWhenDirectPointerActive: {
15
+ type: Types.Boolean;
16
+ default: true;
17
+ };
18
+ /**
19
+ * 1€ filter min cutoff (lower = smoother, higher = more responsive).
20
+ * @default 1.5
21
+ */
22
+ filterMinCutoff: {
23
+ type: Types.Float32;
24
+ default: number;
25
+ };
26
+ /**
27
+ * 1€ filter beta (higher = less smoothing during fast saccades).
28
+ * @default 0.05
29
+ */
30
+ filterBeta: {
31
+ type: Types.Float32;
32
+ default: number;
33
+ };
34
+ /**
35
+ * Dwell window in seconds for consensus voting. `0` disables consensus.
36
+ * @default 0.15
37
+ */
38
+ dwellWindowSeconds: {
39
+ type: Types.Float32;
40
+ default: number;
41
+ };
42
+ /**
43
+ * Half-angle of the gaze selection cone, in degrees. Widening it makes
44
+ * small targets easier to acquire at the cost of more ambiguity between
45
+ * neighbours.
46
+ * @default 5
47
+ */
48
+ coneAngle: {
49
+ type: Types.Float32;
50
+ default: number;
51
+ };
52
+ /** Maximum gaze cone/raycast distance in meters. @default 30 */
53
+ maxRayLength: {
54
+ type: Types.Float32;
55
+ default: number;
56
+ };
57
+ /**
58
+ * On select commit, treat the pinching hand's ray space as the pointer
59
+ * origin instead of the eyes, so a gaze-initiated drag tracks the hand.
60
+ * @default true
61
+ */
62
+ pointerTransformFollowsHand: {
63
+ type: Types.Boolean;
64
+ default: true;
65
+ };
66
+ /**
67
+ * Emit `[iwsdk][gaze]` console diagnostics explaining why gaze is or isn't
68
+ * tracked, and whether the runtime's gaze ray actually separates from the
69
+ * head ray. Each message fires at most once per state change.
70
+ * @default true
71
+ */
72
+ logDiagnostics: {
73
+ type: Types.Boolean;
74
+ default: true;
75
+ };
76
+ /** Show a developer-only gaze hit reticle. @default false */
77
+ showDebugReticle: {
78
+ type: Types.Boolean;
79
+ default: false;
80
+ };
81
+ /** Keep gaze mode active briefly after tracking becomes invalid. @default 5 */
82
+ trackingLossGraceSeconds: {
83
+ type: Types.Float32;
84
+ default: number;
85
+ };
86
+ }, {}, import("../ecs/world.js").World, import("../ecs/system.js").System<{
87
+ /**
88
+ * If true, gaze is suppressed whenever a near touch/grab pointer is in
89
+ * HOVER or SELECT. Set false to allow gaze to remain active concurrently.
90
+ * @default true
91
+ */
92
+ suppressWhenDirectPointerActive: {
93
+ type: Types.Boolean;
94
+ default: true;
95
+ };
96
+ /**
97
+ * 1€ filter min cutoff (lower = smoother, higher = more responsive).
98
+ * @default 1.5
99
+ */
100
+ filterMinCutoff: {
101
+ type: Types.Float32;
102
+ default: number;
103
+ };
104
+ /**
105
+ * 1€ filter beta (higher = less smoothing during fast saccades).
106
+ * @default 0.05
107
+ */
108
+ filterBeta: {
109
+ type: Types.Float32;
110
+ default: number;
111
+ };
112
+ /**
113
+ * Dwell window in seconds for consensus voting. `0` disables consensus.
114
+ * @default 0.15
115
+ */
116
+ dwellWindowSeconds: {
117
+ type: Types.Float32;
118
+ default: number;
119
+ };
120
+ /**
121
+ * Half-angle of the gaze selection cone, in degrees. Widening it makes
122
+ * small targets easier to acquire at the cost of more ambiguity between
123
+ * neighbours.
124
+ * @default 5
125
+ */
126
+ coneAngle: {
127
+ type: Types.Float32;
128
+ default: number;
129
+ };
130
+ /** Maximum gaze cone/raycast distance in meters. @default 30 */
131
+ maxRayLength: {
132
+ type: Types.Float32;
133
+ default: number;
134
+ };
135
+ /**
136
+ * On select commit, treat the pinching hand's ray space as the pointer
137
+ * origin instead of the eyes, so a gaze-initiated drag tracks the hand.
138
+ * @default true
139
+ */
140
+ pointerTransformFollowsHand: {
141
+ type: Types.Boolean;
142
+ default: true;
143
+ };
144
+ /**
145
+ * Emit `[iwsdk][gaze]` console diagnostics explaining why gaze is or isn't
146
+ * tracked, and whether the runtime's gaze ray actually separates from the
147
+ * head ray. Each message fires at most once per state change.
148
+ * @default true
149
+ */
150
+ logDiagnostics: {
151
+ type: Types.Boolean;
152
+ default: true;
153
+ };
154
+ /** Show a developer-only gaze hit reticle. @default false */
155
+ showDebugReticle: {
156
+ type: Types.Boolean;
157
+ default: false;
158
+ };
159
+ /** Keep gaze mode active briefly after tracking becomes invalid. @default 5 */
160
+ trackingLossGraceSeconds: {
161
+ type: Types.Float32;
162
+ default: number;
163
+ };
164
+ }, {}>>;
165
+ /**
166
+ * GazeSystem — wires the global {@link GazePointer} to ECS interactables.
167
+ *
168
+ * @remarks
169
+ * - Owns configuration and diagnostics only. The pointer itself lives on
170
+ * {@link XRInputManager} (as `input.xr.gazePointer`) so `GrabSystem` can
171
+ * identify the selecting hand; registering this system is what flips
172
+ * `input.xr.gazeEnabled` and makes gaze cost anything at all.
173
+ * - Candidates come from the same ray-target snapshot used by hand rays.
174
+ * - The pointer samples the WebXR gaze target ray itself each frame;
175
+ * `logDiagnostics` explains missing sources and invalid poses.
176
+ * - Selection is committed by either hand's pinch, with per-hand mutual
177
+ * exclusion (the hand that started the pinch owns it until release).
178
+ * - Hover and selection flow through ordinary ray pointer events, so
179
+ * {@link InputSystem} owns the shared `Hovered` / `Pressed` state.
180
+ * - Hand/controller far rays are disabled while gaze is available. Gaze is
181
+ * optionally suppressed when a near touch/grab pointer is active, so direct
182
+ * manipulation wins without an incidental far-ray hit stealing focus.
183
+ *
184
+ * @category Input
185
+ */
186
+ export declare class GazeSystem extends GazeSystem_base {
187
+ /** One-shot latch for {@link GazeSystem.warnMissingFrame}. */
188
+ private warnedMissingFrame;
189
+ init(): void;
190
+ /**
191
+ * Push system config onto the shared pointer and its candidate provider.
192
+ *
193
+ * Cone geometry and dwell live on the provider rather than the pointer, and
194
+ * only the default {@link GazeConecaster} understands them — a custom
195
+ * provider installed via `setCandidateProvider` keeps its own tuning.
196
+ */
197
+ private applyGazeConfig;
198
+ update(): void;
199
+ /**
200
+ * There's a session but no frame or reference space, so the gaze pose can't
201
+ * be sampled at all this frame. Normally a one-frame startup blip; if it
202
+ * persists, gaze is unavailable and the developer should know.
203
+ */
204
+ private warnMissingFrame;
205
+ }
206
+ export {};
207
+ //# sourceMappingURL=gaze-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gaze-system.d.ts","sourceRoot":"","sources":["../../src/gaze/gaze-system.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAgB,KAAK,EAAE,MAAM,iBAAiB,CAAC;;IA0BlD;;;;OAIG;;;;;IAEH;;;OAGG;;;;;IAEH;;;OAGG;;;;;IAEH;;;OAGG;;;;;IAEH;;;;;OAKG;;;;;IAEH,gEAAgE;;;;;IAEhE;;;;OAIG;;;;;IAEH;;;;;OAKG;;;;;IAEH,6DAA6D;;;;;IAE7D,+EAA+E;;;;;;IA7C/E;;;;OAIG;;;;;IAEH;;;OAGG;;;;;IAEH;;;OAGG;;;;;IAEH;;;OAGG;;;;;IAEH;;;;;OAKG;;;;;IAEH,gEAAgE;;;;;IAEhE;;;;OAIG;;;;;IAEH;;;;;OAKG;;;;;IAEH,6DAA6D;;;;;IAE7D,+EAA+E;;;;;;AArEnF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,UAAW,SAAQ,eAmD/B;IACC,8DAA8D;IAC9D,OAAO,CAAC,kBAAkB,CAAS;IAEnC,IAAI,IAAI,IAAI;IA6BZ;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAsBvB,MAAM,IAAI,IAAI;IAWd;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;CAUzB"}
@@ -0,0 +1,168 @@
1
+ import { GazeConecaster } from '@iwsdk/xr-input';
2
+ import { createSystem } from '../ecs/system.js';
3
+ import '../ecs/world.js';
4
+ import { Types } from 'elics';
5
+ import '@iwsdk/scene-composition';
6
+ import '../ecs/builtin-components.js';
7
+ import '../ecs/entity.js';
8
+
9
+ /**
10
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
11
+ *
12
+ * This source code is licensed under the MIT license found in the
13
+ * LICENSE file in the root directory of this source tree.
14
+ */
15
+ /**
16
+ * GazeSystem — wires the global {@link GazePointer} to ECS interactables.
17
+ *
18
+ * @remarks
19
+ * - Owns configuration and diagnostics only. The pointer itself lives on
20
+ * {@link XRInputManager} (as `input.xr.gazePointer`) so `GrabSystem` can
21
+ * identify the selecting hand; registering this system is what flips
22
+ * `input.xr.gazeEnabled` and makes gaze cost anything at all.
23
+ * - Candidates come from the same ray-target snapshot used by hand rays.
24
+ * - The pointer samples the WebXR gaze target ray itself each frame;
25
+ * `logDiagnostics` explains missing sources and invalid poses.
26
+ * - Selection is committed by either hand's pinch, with per-hand mutual
27
+ * exclusion (the hand that started the pinch owns it until release).
28
+ * - Hover and selection flow through ordinary ray pointer events, so
29
+ * {@link InputSystem} owns the shared `Hovered` / `Pressed` state.
30
+ * - Hand/controller far rays are disabled while gaze is available. Gaze is
31
+ * optionally suppressed when a near touch/grab pointer is active, so direct
32
+ * manipulation wins without an incidental far-ray hit stealing focus.
33
+ *
34
+ * @category Input
35
+ */
36
+ class GazeSystem extends createSystem({}, {
37
+ /**
38
+ * If true, gaze is suppressed whenever a near touch/grab pointer is in
39
+ * HOVER or SELECT. Set false to allow gaze to remain active concurrently.
40
+ * @default true
41
+ */
42
+ suppressWhenDirectPointerActive: { type: Types.Boolean, default: true },
43
+ /**
44
+ * 1€ filter min cutoff (lower = smoother, higher = more responsive).
45
+ * @default 1.5
46
+ */
47
+ filterMinCutoff: { type: Types.Float32, default: 1.5 },
48
+ /**
49
+ * 1€ filter beta (higher = less smoothing during fast saccades).
50
+ * @default 0.05
51
+ */
52
+ filterBeta: { type: Types.Float32, default: 0.05 },
53
+ /**
54
+ * Dwell window in seconds for consensus voting. `0` disables consensus.
55
+ * @default 0.15
56
+ */
57
+ dwellWindowSeconds: { type: Types.Float32, default: 0.15 },
58
+ /**
59
+ * Half-angle of the gaze selection cone, in degrees. Widening it makes
60
+ * small targets easier to acquire at the cost of more ambiguity between
61
+ * neighbours.
62
+ * @default 5
63
+ */
64
+ coneAngle: { type: Types.Float32, default: 5 },
65
+ /** Maximum gaze cone/raycast distance in meters. @default 30 */
66
+ maxRayLength: { type: Types.Float32, default: 30 },
67
+ /**
68
+ * On select commit, treat the pinching hand's ray space as the pointer
69
+ * origin instead of the eyes, so a gaze-initiated drag tracks the hand.
70
+ * @default true
71
+ */
72
+ pointerTransformFollowsHand: { type: Types.Boolean, default: true },
73
+ /**
74
+ * Emit `[iwsdk][gaze]` console diagnostics explaining why gaze is or isn't
75
+ * tracked, and whether the runtime's gaze ray actually separates from the
76
+ * head ray. Each message fires at most once per state change.
77
+ * @default true
78
+ */
79
+ logDiagnostics: { type: Types.Boolean, default: true },
80
+ /** Show a developer-only gaze hit reticle. @default false */
81
+ showDebugReticle: { type: Types.Boolean, default: false },
82
+ /** Keep gaze mode active briefly after tracking becomes invalid. @default 5 */
83
+ trackingLossGraceSeconds: { type: Types.Float32, default: 5 },
84
+ }) {
85
+ constructor() {
86
+ super(...arguments);
87
+ /** One-shot latch for {@link GazeSystem.warnMissingFrame}. */
88
+ this.warnedMissingFrame = false;
89
+ }
90
+ init() {
91
+ // Registering this system is the opt-in: until now the pointer existed but
92
+ // never sampled a pose or cast a cone.
93
+ this.input.xr.gazeEnabled = true;
94
+ this.cleanupFuncs.push(() => {
95
+ this.input.xr.gazeEnabled = false;
96
+ });
97
+ this.applyGazeConfig();
98
+ // Re-push on every config change so the editor's live tweaks land without
99
+ // rebuilding the pointer.
100
+ for (const key of [
101
+ 'suppressWhenDirectPointerActive',
102
+ 'filterMinCutoff',
103
+ 'filterBeta',
104
+ 'dwellWindowSeconds',
105
+ 'coneAngle',
106
+ 'maxRayLength',
107
+ 'pointerTransformFollowsHand',
108
+ 'logDiagnostics',
109
+ 'showDebugReticle',
110
+ 'trackingLossGraceSeconds',
111
+ ]) {
112
+ this.cleanupFuncs.push(this.config[key].subscribe(() => this.applyGazeConfig()));
113
+ }
114
+ }
115
+ /**
116
+ * Push system config onto the shared pointer and its candidate provider.
117
+ *
118
+ * Cone geometry and dwell live on the provider rather than the pointer, and
119
+ * only the default {@link GazeConecaster} understands them — a custom
120
+ * provider installed via `setCandidateProvider` keeps its own tuning.
121
+ */
122
+ applyGazeConfig() {
123
+ const pointer = this.input.xr.gazePointer;
124
+ pointer.suppressWhenDirectPointerActive =
125
+ this.config.suppressWhenDirectPointerActive.peek();
126
+ pointer.pointerTransformFollowsHand =
127
+ this.config.pointerTransformFollowsHand.peek();
128
+ pointer.diagnosticsEnabled = this.config.logDiagnostics.peek();
129
+ pointer.showDebugReticle = this.config.showDebugReticle.peek();
130
+ pointer.trackingLossGraceSeconds =
131
+ this.config.trackingLossGraceSeconds.peek();
132
+ pointer.filter.minCutoff = this.config.filterMinCutoff.peek();
133
+ pointer.filter.beta = this.config.filterBeta.peek();
134
+ const provider = pointer.provider;
135
+ if (provider instanceof GazeConecaster) {
136
+ // The conecaster works in radians; the config is in degrees.
137
+ provider.coneAngle = (this.config.coneAngle.peek() * Math.PI) / 180;
138
+ provider.coneLength = this.config.maxRayLength.peek();
139
+ provider.dwellWindowSeconds = this.config.dwellWindowSeconds.peek();
140
+ }
141
+ }
142
+ update() {
143
+ const session = this.xrManager.getSession();
144
+ if (!session) {
145
+ this.warnedMissingFrame = false;
146
+ return;
147
+ }
148
+ if (!this.xrManager.getFrame() || !this.xrManager.getReferenceSpace()) {
149
+ this.warnMissingFrame();
150
+ }
151
+ }
152
+ /**
153
+ * There's a session but no frame or reference space, so the gaze pose can't
154
+ * be sampled at all this frame. Normally a one-frame startup blip; if it
155
+ * persists, gaze is unavailable and the developer should know.
156
+ */
157
+ warnMissingFrame() {
158
+ if (this.warnedMissingFrame || !this.config.logDiagnostics.peek()) {
159
+ return;
160
+ }
161
+ this.warnedMissingFrame = true;
162
+ console.warn('[iwsdk][gaze] in an XR session but the renderer has no XRFrame or ' +
163
+ 'reference space yet, so XR input cannot be sampled until this resolves.');
164
+ }
165
+ }
166
+
167
+ export { GazeSystem };
168
+ //# sourceMappingURL=gaze-system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gaze-system.js","sources":["../../src/gaze/gaze-system.ts"],"sourcesContent":["/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport { GazeConecaster } from '@iwsdk/xr-input';\nimport { createSystem, Types } from '../ecs/index.js';\n\n/**\n * GazeSystem — wires the global {@link GazePointer} to ECS interactables.\n *\n * @remarks\n * - Owns configuration and diagnostics only. The pointer itself lives on\n * {@link XRInputManager} (as `input.xr.gazePointer`) so `GrabSystem` can\n * identify the selecting hand; registering this system is what flips\n * `input.xr.gazeEnabled` and makes gaze cost anything at all.\n * - Candidates come from the same ray-target snapshot used by hand rays.\n * - The pointer samples the WebXR gaze target ray itself each frame;\n * `logDiagnostics` explains missing sources and invalid poses.\n * - Selection is committed by either hand's pinch, with per-hand mutual\n * exclusion (the hand that started the pinch owns it until release).\n * - Hover and selection flow through ordinary ray pointer events, so\n * {@link InputSystem} owns the shared `Hovered` / `Pressed` state.\n * - Hand/controller far rays are disabled while gaze is available. Gaze is\n * optionally suppressed when a near touch/grab pointer is active, so direct\n * manipulation wins without an incidental far-ray hit stealing focus.\n *\n * @category Input\n */\nexport class GazeSystem extends createSystem(\n {},\n {\n /**\n * If true, gaze is suppressed whenever a near touch/grab pointer is in\n * HOVER or SELECT. Set false to allow gaze to remain active concurrently.\n * @default true\n */\n suppressWhenDirectPointerActive: { type: Types.Boolean, default: true },\n /**\n * 1€ filter min cutoff (lower = smoother, higher = more responsive).\n * @default 1.5\n */\n filterMinCutoff: { type: Types.Float32, default: 1.5 },\n /**\n * 1€ filter beta (higher = less smoothing during fast saccades).\n * @default 0.05\n */\n filterBeta: { type: Types.Float32, default: 0.05 },\n /**\n * Dwell window in seconds for consensus voting. `0` disables consensus.\n * @default 0.15\n */\n dwellWindowSeconds: { type: Types.Float32, default: 0.15 },\n /**\n * Half-angle of the gaze selection cone, in degrees. Widening it makes\n * small targets easier to acquire at the cost of more ambiguity between\n * neighbours.\n * @default 5\n */\n coneAngle: { type: Types.Float32, default: 5 },\n /** Maximum gaze cone/raycast distance in meters. @default 30 */\n maxRayLength: { type: Types.Float32, default: 30 },\n /**\n * On select commit, treat the pinching hand's ray space as the pointer\n * origin instead of the eyes, so a gaze-initiated drag tracks the hand.\n * @default true\n */\n pointerTransformFollowsHand: { type: Types.Boolean, default: true },\n /**\n * Emit `[iwsdk][gaze]` console diagnostics explaining why gaze is or isn't\n * tracked, and whether the runtime's gaze ray actually separates from the\n * head ray. Each message fires at most once per state change.\n * @default true\n */\n logDiagnostics: { type: Types.Boolean, default: true },\n /** Show a developer-only gaze hit reticle. @default false */\n showDebugReticle: { type: Types.Boolean, default: false },\n /** Keep gaze mode active briefly after tracking becomes invalid. @default 5 */\n trackingLossGraceSeconds: { type: Types.Float32, default: 5 },\n },\n) {\n /** One-shot latch for {@link GazeSystem.warnMissingFrame}. */\n private warnedMissingFrame = false;\n\n init(): void {\n // Registering this system is the opt-in: until now the pointer existed but\n // never sampled a pose or cast a cone.\n this.input.xr.gazeEnabled = true;\n this.cleanupFuncs.push(() => {\n this.input.xr.gazeEnabled = false;\n });\n\n this.applyGazeConfig();\n // Re-push on every config change so the editor's live tweaks land without\n // rebuilding the pointer.\n for (const key of [\n 'suppressWhenDirectPointerActive',\n 'filterMinCutoff',\n 'filterBeta',\n 'dwellWindowSeconds',\n 'coneAngle',\n 'maxRayLength',\n 'pointerTransformFollowsHand',\n 'logDiagnostics',\n 'showDebugReticle',\n 'trackingLossGraceSeconds',\n ] as const) {\n this.cleanupFuncs.push(\n this.config[key].subscribe(() => this.applyGazeConfig()),\n );\n }\n }\n\n /**\n * Push system config onto the shared pointer and its candidate provider.\n *\n * Cone geometry and dwell live on the provider rather than the pointer, and\n * only the default {@link GazeConecaster} understands them — a custom\n * provider installed via `setCandidateProvider` keeps its own tuning.\n */\n private applyGazeConfig(): void {\n const pointer = this.input.xr.gazePointer;\n pointer.suppressWhenDirectPointerActive =\n this.config.suppressWhenDirectPointerActive.peek();\n pointer.pointerTransformFollowsHand =\n this.config.pointerTransformFollowsHand.peek();\n pointer.diagnosticsEnabled = this.config.logDiagnostics.peek();\n pointer.showDebugReticle = this.config.showDebugReticle.peek();\n pointer.trackingLossGraceSeconds =\n this.config.trackingLossGraceSeconds.peek();\n pointer.filter.minCutoff = this.config.filterMinCutoff.peek();\n pointer.filter.beta = this.config.filterBeta.peek();\n\n const provider = pointer.provider;\n if (provider instanceof GazeConecaster) {\n // The conecaster works in radians; the config is in degrees.\n provider.coneAngle = (this.config.coneAngle.peek() * Math.PI) / 180;\n provider.coneLength = this.config.maxRayLength.peek();\n provider.dwellWindowSeconds = this.config.dwellWindowSeconds.peek();\n }\n }\n\n update(): void {\n const session = this.xrManager.getSession();\n if (!session) {\n this.warnedMissingFrame = false;\n return;\n }\n if (!this.xrManager.getFrame() || !this.xrManager.getReferenceSpace()) {\n this.warnMissingFrame();\n }\n }\n\n /**\n * There's a session but no frame or reference space, so the gaze pose can't\n * be sampled at all this frame. Normally a one-frame startup blip; if it\n * persists, gaze is unavailable and the developer should know.\n */\n private warnMissingFrame(): void {\n if (this.warnedMissingFrame || !this.config.logDiagnostics.peek()) {\n return;\n }\n this.warnedMissingFrame = true;\n console.warn(\n '[iwsdk][gaze] in an XR session but the renderer has no XRFrame or ' +\n 'reference space yet, so XR input cannot be sampled until this resolves.',\n );\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ export * from './gaze-system.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/gaze/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,kBAAkB,CAAC"}
@@ -8,8 +8,8 @@ import { HandleStore } from '@pmndrs/handle';
8
8
  /**
9
9
  * Pure helpers backing {@link GrabSystem.forceRelease} and
10
10
  * {@link GrabSystem.getHolderHand}. Kept side-effect-free and dependency-light
11
- * so they can be unit-tested without spinning up the full ECS / xr-input
12
- * import chain.
11
+ * so they can be unit-tested without spinning up the full ECS / xr-input import
12
+ * chain.
13
13
  *
14
14
  * Not part of the public package API surface — `grab-system.ts` is the only
15
15
  * intended caller.
@@ -31,9 +31,10 @@ export declare function cancelGrabHandle(handle: HandleStore<unknown> | undefine
31
31
  * object). Passing both keeps `getHolderHand` correct across both grab
32
32
  * styles without leaking pointer-kind selection into callers.
33
33
  *
34
- * Returns `'left'` / `'right'` for single-hand grabs, `'left'` for two-hand
35
- * grabs (deterministic), and `null` when the entity has no active grabs or
36
- * none of the candidate IDs match.
34
+ * A capture from the gaze ray is attributed to the hand that initiated its
35
+ * current select. Returns `'left'` / `'right'` for single-hand grabs, `'left'`
36
+ * for two-hand grabs (deterministic), and `null` when the entity has no active
37
+ * grabs or none of the candidate IDs match.
37
38
  */
38
- export declare function findHolderHand(handle: HandleStore<unknown> | undefined, leftPointerIds: readonly number[], rightPointerIds: readonly number[]): 'left' | 'right' | null;
39
+ export declare function findHolderHand(handle: HandleStore<unknown> | undefined, leftPointerIds: readonly number[], rightPointerIds: readonly number[], gazePointerId?: number, gazeHand?: 'left' | 'right' | null): 'left' | 'right' | null;
39
40
  //# sourceMappingURL=grab-helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grab-helpers.d.ts","sourceRoot":"","sources":["../../src/grab/grab-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,SAAS,GACvC,IAAI,CAON;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,SAAS,EACxC,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,eAAe,EAAE,SAAS,MAAM,EAAE,GACjC,MAAM,GAAG,OAAO,GAAG,IAAI,CAWzB"}
1
+ {"version":3,"file":"grab-helpers.d.ts","sourceRoot":"","sources":["../../src/grab/grab-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,SAAS,GACvC,IAAI,CAON;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,SAAS,EACxC,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,aAAa,CAAC,EAAE,MAAM,EACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,GACjC,MAAM,GAAG,OAAO,GAAG,IAAI,CAezB"}
@@ -7,8 +7,8 @@
7
7
  /**
8
8
  * Pure helpers backing {@link GrabSystem.forceRelease} and
9
9
  * {@link GrabSystem.getHolderHand}. Kept side-effect-free and dependency-light
10
- * so they can be unit-tested without spinning up the full ECS / xr-input
11
- * import chain.
10
+ * so they can be unit-tested without spinning up the full ECS / xr-input import
11
+ * chain.
12
12
  *
13
13
  * Not part of the public package API surface — `grab-system.ts` is the only
14
14
  * intended caller.
@@ -38,11 +38,12 @@ function cancelGrabHandle(handle) {
38
38
  * object). Passing both keeps `getHolderHand` correct across both grab
39
39
  * styles without leaking pointer-kind selection into callers.
40
40
  *
41
- * Returns `'left'` / `'right'` for single-hand grabs, `'left'` for two-hand
42
- * grabs (deterministic), and `null` when the entity has no active grabs or
43
- * none of the candidate IDs match.
41
+ * A capture from the gaze ray is attributed to the hand that initiated its
42
+ * current select. Returns `'left'` / `'right'` for single-hand grabs, `'left'`
43
+ * for two-hand grabs (deterministic), and `null` when the entity has no active
44
+ * grabs or none of the candidate IDs match.
44
45
  */
45
- function findHolderHand(handle, leftPointerIds, rightPointerIds) {
46
+ function findHolderHand(handle, leftPointerIds, rightPointerIds, gazePointerId, gazeHand) {
46
47
  if (!handle || handle.inputState.size === 0) {
47
48
  return null;
48
49
  }
@@ -52,7 +53,11 @@ function findHolderHand(handle, leftPointerIds, rightPointerIds) {
52
53
  if (rightPointerIds.some((id) => handle.inputState.has(id))) {
53
54
  return 'right';
54
55
  }
55
- return null;
56
+ return gazeHand != null && gazePointerId != null
57
+ ? handle.inputState.has(gazePointerId)
58
+ ? gazeHand
59
+ : null
60
+ : null;
56
61
  }
57
62
 
58
63
  export { cancelGrabHandle, findHolderHand };
@@ -1 +1 @@
1
- {"version":3,"file":"grab-helpers.js","sources":["../../src/grab/grab-helpers.ts"],"sourcesContent":["/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport { HandleStore } from '@pmndrs/handle';\n\n/**\n * Pure helpers backing {@link GrabSystem.forceRelease} and\n * {@link GrabSystem.getHolderHand}. Kept side-effect-free and dependency-light\n * so they can be unit-tested without spinning up the full ECS / xr-input\n * import chain.\n *\n * Not part of the public package API surface — `grab-system.ts` is the only\n * intended caller.\n */\n\n/**\n * Cancel every active pointer on a grab `handle`. Safe to call when `handle`\n * is `undefined`, when the entity is not currently held, or when the\n * underlying `cancel()` throws (errors are swallowed to match the cleanup\n * semantics already used inside `GrabSystem` on Handle-component disqualify).\n */\nexport function cancelGrabHandle(\n handle: HandleStore<unknown> | undefined,\n): void {\n if (!handle) {\n return;\n }\n try {\n handle.cancel();\n } catch {}\n}\n\n/**\n * Determine which controller is currently holding the entity backing\n * `handle`, given the candidate pointer IDs per hand.\n *\n * Each hand contributes the IDs of every pointer kind that can capture a\n * grab handle today — near grabs use the `grab` sub-pointer, distance grabs\n * use the `ray` sub-pointer (per `pointerEventsType` on the handle's\n * object). Passing both keeps `getHolderHand` correct across both grab\n * styles without leaking pointer-kind selection into callers.\n *\n * Returns `'left'` / `'right'` for single-hand grabs, `'left'` for two-hand\n * grabs (deterministic), and `null` when the entity has no active grabs or\n * none of the candidate IDs match.\n */\nexport function findHolderHand(\n handle: HandleStore<unknown> | undefined,\n leftPointerIds: readonly number[],\n rightPointerIds: readonly number[],\n): 'left' | 'right' | null {\n if (!handle || handle.inputState.size === 0) {\n return null;\n }\n if (leftPointerIds.some((id) => handle.inputState.has(id))) {\n return 'left';\n }\n if (rightPointerIds.some((id) => handle.inputState.has(id))) {\n return 'right';\n }\n return null;\n}\n"],"names":[],"mappings":"AAAA;;;;;AAKG;AAIH;;;;;;;;AAQG;AAEH;;;;;AAKG;AACG,SAAU,gBAAgB,CAC9B,MAAwC,EAAA;IAExC,IAAI,CAAC,MAAM,EAAE;QACX;IACF;AACA,IAAA,IAAI;QACF,MAAM,CAAC,MAAM,EAAE;IACjB;IAAE,OAAA,EAAA,EAAM,EAAC;AACX;AAEA;;;;;;;;;;;;;AAaG;SACa,cAAc,CAC5B,MAAwC,EACxC,cAAiC,EACjC,eAAkC,EAAA;IAElC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,EAAE;AAC3C,QAAA,OAAO,IAAI;IACb;AACA,IAAA,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE;AAC1D,QAAA,OAAO,MAAM;IACf;AACA,IAAA,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE;AAC3D,QAAA,OAAO,OAAO;IAChB;AACA,IAAA,OAAO,IAAI;AACb;;;;"}
1
+ {"version":3,"file":"grab-helpers.js","sources":["../../src/grab/grab-helpers.ts"],"sourcesContent":["/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport { HandleStore } from '@pmndrs/handle';\n\n/**\n * Pure helpers backing {@link GrabSystem.forceRelease} and\n * {@link GrabSystem.getHolderHand}. Kept side-effect-free and dependency-light\n * so they can be unit-tested without spinning up the full ECS / xr-input import\n * chain.\n *\n * Not part of the public package API surface — `grab-system.ts` is the only\n * intended caller.\n */\n\n/**\n * Cancel every active pointer on a grab `handle`. Safe to call when `handle`\n * is `undefined`, when the entity is not currently held, or when the\n * underlying `cancel()` throws (errors are swallowed to match the cleanup\n * semantics already used inside `GrabSystem` on Handle-component disqualify).\n */\nexport function cancelGrabHandle(\n handle: HandleStore<unknown> | undefined,\n): void {\n if (!handle) {\n return;\n }\n try {\n handle.cancel();\n } catch {}\n}\n\n/**\n * Determine which controller is currently holding the entity backing\n * `handle`, given the candidate pointer IDs per hand.\n *\n * Each hand contributes the IDs of every pointer kind that can capture a\n * grab handle today — near grabs use the `grab` sub-pointer, distance grabs\n * use the `ray` sub-pointer (per `pointerEventsType` on the handle's\n * object). Passing both keeps `getHolderHand` correct across both grab\n * styles without leaking pointer-kind selection into callers.\n *\n * A capture from the gaze ray is attributed to the hand that initiated its\n * current select. Returns `'left'` / `'right'` for single-hand grabs, `'left'`\n * for two-hand grabs (deterministic), and `null` when the entity has no active\n * grabs or none of the candidate IDs match.\n */\nexport function findHolderHand(\n handle: HandleStore<unknown> | undefined,\n leftPointerIds: readonly number[],\n rightPointerIds: readonly number[],\n gazePointerId?: number,\n gazeHand?: 'left' | 'right' | null,\n): 'left' | 'right' | null {\n if (!handle || handle.inputState.size === 0) {\n return null;\n }\n if (leftPointerIds.some((id) => handle.inputState.has(id))) {\n return 'left';\n }\n if (rightPointerIds.some((id) => handle.inputState.has(id))) {\n return 'right';\n }\n return gazeHand != null && gazePointerId != null\n ? handle.inputState.has(gazePointerId)\n ? gazeHand\n : null\n : null;\n}\n"],"names":[],"mappings":"AAAA;;;;;AAKG;AAIH;;;;;;;;AAQG;AAEH;;;;;AAKG;AACG,SAAU,gBAAgB,CAC9B,MAAwC,EAAA;IAExC,IAAI,CAAC,MAAM,EAAE;QACX;IACF;AACA,IAAA,IAAI;QACF,MAAM,CAAC,MAAM,EAAE;IACjB;IAAE,OAAA,EAAA,EAAM,EAAC;AACX;AAEA;;;;;;;;;;;;;;AAcG;AACG,SAAU,cAAc,CAC5B,MAAwC,EACxC,cAAiC,EACjC,eAAkC,EAClC,aAAsB,EACtB,QAAkC,EAAA;IAElC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,EAAE;AAC3C,QAAA,OAAO,IAAI;IACb;AACA,IAAA,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE;AAC1D,QAAA,OAAO,MAAM;IACf;AACA,IAAA,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE;AAC3D,QAAA,OAAO,OAAO;IAChB;AACA,IAAA,OAAO,QAAQ,IAAI,IAAI,IAAI,aAAa,IAAI;UACxC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,aAAa;AACnC,cAAE;AACF,cAAE;UACF,IAAI;AACV;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"grab-system.d.ts","sourceRoot":"","sources":["../../src/grab/grab-system.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;;IAmEtC;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAJH;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAtDP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,UAAW,SAAQ,eAuB/B;IACC,IAAI;IAmDJ,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IA4CzC;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAMlC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI;IActD,OAAO,CAAC,uBAAuB;IAqC/B,OAAO,CAAC,wBAAwB;IAoDhC,OAAO,CAAC,wBAAwB;CA0FjC"}
1
+ {"version":3,"file":"grab-system.d.ts","sourceRoot":"","sources":["../../src/grab/grab-system.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;;IAmEtC;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAJH;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAtDP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,UAAW,SAAQ,eAuB/B;IACC,IAAI;IAmDJ,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IA4CzC;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAMlC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI;IAiBtD,OAAO,CAAC,uBAAuB;IAqC/B,OAAO,CAAC,wBAAwB;IAoDhC,OAAO,CAAC,wBAAwB;CA0FjC"}
@@ -210,12 +210,13 @@ class GrabSystem extends createSystem({
210
210
  getHolderHand(entity) {
211
211
  const left = this.input.xr.multiPointers.left;
212
212
  const right = this.input.xr.multiPointers.right;
213
+ const gaze = this.input.xr.gazePointer;
213
214
  return findHolderHand(Handle.data.instance[entity.index],
214
215
  // Near grabs capture via the `grab` sub-pointer; distance grabs capture
215
216
  // via `ray` (see `initializeDistanceHandle` setting
216
217
  // `pointerEventsType = { deny: 'grab' }`). Check both per hand so
217
218
  // distance-held entities don't silently report `null`.
218
- [left.getPointer('grab').id, left.getPointer('ray').id], [right.getPointer('grab').id, right.getPointer('ray').id]);
219
+ [left.getPointer('grab').id, left.getPointer('ray').id], [right.getPointer('grab').id, right.getPointer('ray').id], gaze.pointer.id, gaze.getHeldByHand());
219
220
  }
220
221
  initializeOneHandHandle(entity) {
221
222
  if (entity.hasComponent(Handle)) {