@iwsdk/core 1.0.0-rc.1 → 1.0.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/dist/gaze/gaze-system.d.ts +207 -0
- package/dist/gaze/gaze-system.d.ts.map +1 -0
- package/dist/gaze/gaze-system.js +168 -0
- package/dist/gaze/gaze-system.js.map +1 -0
- package/dist/gaze/index.d.ts +8 -0
- package/dist/gaze/index.d.ts.map +1 -0
- package/dist/grab/grab-helpers.d.ts +7 -6
- package/dist/grab/grab-helpers.d.ts.map +1 -1
- package/dist/grab/grab-helpers.js +12 -7
- package/dist/grab/grab-helpers.js.map +1 -1
- package/dist/grab/grab-system.d.ts.map +1 -1
- package/dist/grab/grab-system.js +2 -1
- package/dist/grab/grab-system.js.map +1 -1
- package/dist/grab/handles.d.ts +10 -1
- package/dist/grab/handles.d.ts.map +1 -1
- package/dist/grab/handles.js +29 -6
- package/dist/grab/handles.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init/world-initializer.d.ts +49 -0
- package/dist/init/world-initializer.d.ts.map +1 -1
- package/dist/init/world-initializer.js +21 -8
- package/dist/init/world-initializer.js.map +1 -1
- package/dist/init/xr.d.ts +14 -0
- package/dist/init/xr.d.ts.map +1 -1
- package/dist/init/xr.js +19 -0
- package/dist/init/xr.js.map +1 -1
- package/dist/input/input-system.d.ts +3 -0
- package/dist/input/input-system.d.ts.map +1 -1
- package/dist/input/input-system.js +72 -6
- package/dist/input/input-system.js.map +1 -1
- package/dist/input/state-tags.d.ts.map +1 -1
- package/dist/input/state-tags.js +2 -0
- package/dist/input/state-tags.js.map +1 -1
- package/dist/project/normalize.d.ts.map +1 -1
- package/dist/project/normalize.js +11 -0
- package/dist/project/normalize.js.map +1 -1
- package/dist/project/types.d.ts +32 -2
- package/dist/project/types.d.ts.map +1 -1
- package/dist/project/types.js.map +1 -1
- package/dist/project/validation.d.ts.map +1 -1
- package/dist/project/validation.js +65 -1
- package/dist/project/validation.js.map +1 -1
- package/dist/schemas/iwsdk-project.v1.schema.json +45 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- 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
|
-
*
|
|
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
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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 *
|
|
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;
|
|
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"}
|
package/dist/grab/grab-system.js
CHANGED
|
@@ -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)) {
|