@@ -136,6 +136,7 @@
|
|
136
136
|
|
137
137
|
onPointerDown(args: PointerEventData) {
|
138
138
|
if (!this.allowEdit(this.gameObject)) return;
|
139
|
+
if (args.used) return;
|
139
140
|
DragControls.lastHovered = args.object;
|
140
141
|
|
141
142
|
if (args.button === 0) {
|
@@ -58,16 +58,6 @@
|
|
58
58
|
|
59
59
|
export class NEPointerEvent extends PointerEvent {
|
60
60
|
|
61
|
-
/** Unique identifier for this input: a combination of the deviceIndex + button to uniquely identify the exact input (e.g. LeftController:Button0 = 0, RightController:Button1 = 101) */
|
62
|
-
override readonly pointerId!: number;
|
63
|
-
|
64
|
-
// this is set via the init arguments (we override it here for intellisense to show the string options)
|
65
|
-
override readonly pointerType!: PointerTypeNames;
|
66
|
-
|
67
|
-
// this is set via the init arguments (we override it here for intellisense to show the string options)
|
68
|
-
/** The input that raised this event like `pointerdown` */
|
69
|
-
override readonly type!: InputEventNames;
|
70
|
-
|
71
61
|
/** the device index: mouse and touch are always 0, otherwise e.g. index of the connected Gamepad or XRController */
|
72
62
|
readonly deviceIndex: number;
|
73
63
|
|
@@ -94,8 +84,27 @@
|
|
94
84
|
isDoubleClick: boolean = false;
|
95
85
|
|
96
86
|
|
87
|
+
/** Unique identifier for this input: a combination of the deviceIndex + button to uniquely identify the exact input (e.g. LeftController:Button0 = 0, RightController:Button1 = 101) */
|
88
|
+
override get pointerId(): number { return this._pointerid; }
|
89
|
+
private readonly _pointerid;
|
90
|
+
|
91
|
+
// this is set via the init arguments (we override it here for intellisense to show the string options)
|
92
|
+
override get pointerType(): PointerTypeNames { return this._pointerType; }
|
93
|
+
private readonly _pointerType: PointerTypeNames;
|
94
|
+
|
95
|
+
// this is set via the init arguments (we override it here for intellisense to show the string options)
|
96
|
+
/** The input that raised this event like `pointerdown` */
|
97
|
+
override get type(): InputEventNames { return this._type; }
|
98
|
+
private readonly _type: InputEventNames;
|
99
|
+
|
97
100
|
constructor(type: InputEvents | InputEventNames, source: Event | null, init: NEPointerEventInit) {
|
98
|
-
super(type, init)
|
101
|
+
super(type, init);
|
102
|
+
// apply the init arguments. Otherwise the arguments will be undefined in the bundled / published version of needle engine
|
103
|
+
// so we have to be careful if we override properties - we then also need to set them in the constructor
|
104
|
+
this._pointerid = init.pointerId;
|
105
|
+
this._pointerType = init.pointerType;
|
106
|
+
this._type = type;
|
107
|
+
|
99
108
|
this.deviceIndex = init.deviceIndex;
|
100
109
|
this.origin = init.origin;
|
101
110
|
this.source = source;
|
@@ -521,6 +521,7 @@
|
|
521
521
|
const comp = behaviour as any;
|
522
522
|
|
523
523
|
if (comp.interactable === false) return;
|
524
|
+
if (!comp.activeAndEnabled || !comp.enabled) return;
|
524
525
|
|
525
526
|
if (comp.onPointerEnter) {
|
526
527
|
if (hoveredObjectChanged) {
|
@@ -126,6 +126,7 @@
|
|
126
126
|
|
127
127
|
private readonly _gripPosition = new Vector3();
|
128
128
|
private readonly _gripQuaternion = new Quaternion();
|
129
|
+
private readonly _linearVelocity: Vector3 = new Vector3();
|
129
130
|
private readonly _rayPosition = new Vector3();
|
130
131
|
private readonly _rayQuaternion = new Quaternion();
|
131
132
|
|
@@ -133,6 +134,12 @@
|
|
133
134
|
get gripPosition() { return getTempVector(this._gripPosition).applyMatrix4(flipForwardMatrix) }
|
134
135
|
/** Grip rotation in rig space */
|
135
136
|
get gripQuaternion() { return getTempQuaternion(this._gripQuaternion).premultiply(flipForwardQuaternion) }
|
137
|
+
/** Grip linear velocity in rig space
|
138
|
+
* @link https://developer.mozilla.org/en-US/docs/Web/API/XRPose/linearVelocity
|
139
|
+
*/
|
140
|
+
get gripLinearVelocity() {
|
141
|
+
return getTempVector(this._linearVelocity).applyQuaternion(flipForwardQuaternion);
|
142
|
+
}
|
136
143
|
/** Ray position in rig space */
|
137
144
|
get rayPosition() { return getTempVector(this._rayPosition).applyMatrix4(flipForwardMatrix) }
|
138
145
|
/** Ray rotation in rig space */
|
@@ -140,38 +147,27 @@
|
|
140
147
|
|
141
148
|
/** Controller grip position in worldspace */
|
142
149
|
get gripWorldPosition() {
|
143
|
-
|
144
|
-
const space = this.xr.context.mainCamera?.parent;
|
145
|
-
if (!space) return v;
|
146
|
-
return v.applyMatrix4(space.matrixWorld);
|
150
|
+
return getTempVector(this._gripWorldPosition);
|
147
151
|
}
|
152
|
+
private readonly _gripWorldPosition: Vector3 = new Vector3();
|
153
|
+
|
148
154
|
/** Controller grip rotation in wordspace */
|
149
155
|
get gripWorldQuaternion() {
|
150
|
-
|
151
|
-
// flip forward because we want +Z to be forward
|
152
|
-
q.multiply(flipForwardQuaternion);
|
153
|
-
const space = this.xr.context.mainCamera?.parent;
|
154
|
-
if (!space) return q;
|
155
|
-
q.premultiply(getWorldQuaternion(space))
|
156
|
-
return q;
|
156
|
+
return getTempQuaternion(this._gripWorldQuaternion);
|
157
157
|
}
|
158
|
+
private readonly _gripWorldQuaternion: Quaternion = new Quaternion();
|
159
|
+
|
158
160
|
/** Controller ray position in worldspace */
|
159
161
|
get rayWorldPosition() {
|
160
|
-
|
161
|
-
const space = this.xr.context.mainCamera?.parent;
|
162
|
-
if (!space) return v;
|
163
|
-
return v.applyMatrix4(space.matrixWorld);
|
162
|
+
return getTempVector(this._rayWorldPosition);
|
164
163
|
}
|
164
|
+
private readonly _rayWorldPosition: Vector3 = new Vector3();
|
165
|
+
|
165
166
|
/** Controller ray rotation in wordspace */
|
166
167
|
get rayWorldQuaternion() {
|
167
|
-
|
168
|
-
// flip forward because we want +Z to be forward
|
169
|
-
.multiply(flipForwardQuaternion);
|
170
|
-
const space = this.xr.context.mainCamera?.parent;
|
171
|
-
if (!space) return q;
|
172
|
-
q.premultiply(getWorldQuaternion(space))
|
173
|
-
return q;
|
168
|
+
return getTempQuaternion(this._rayWorldQuaternion);
|
174
169
|
}
|
170
|
+
private readonly _rayWorldQuaternion: Quaternion = new Quaternion();
|
175
171
|
|
176
172
|
/** The controller ray in worldspace */
|
177
173
|
get ray(): Ray {
|
@@ -181,6 +177,7 @@
|
|
181
177
|
}
|
182
178
|
private readonly _ray;
|
183
179
|
|
180
|
+
|
184
181
|
/** The controller object space.
|
185
182
|
* You can use it to attach objects to the controller.
|
186
183
|
* Children will be automatically detached and put into the scene when the controller disconnects
|
@@ -246,8 +243,6 @@
|
|
246
243
|
return;
|
247
244
|
}
|
248
245
|
|
249
|
-
// TODO: we might actually want to apply the rotation here now already to avoid the matrix multiplications in the vector and quaternion getters since we now ALWAYS deal witht the rotated data (previously the camera was rotated before calling the update methods hence we needed other data etc but this has been changed in 99a8b96fe03676078e194f5504743576a19a9b1a and now the camera is rotated at the very end of the frame - or at least it should be - which also fixed the issue with selectstart controller events requiring other frame data etc)
|
250
|
-
|
251
246
|
const rayPose = frame.getPose(this.inputSource.targetRaySpace, this.xr.referenceSpace);
|
252
247
|
this._isTracking = rayPose != null;
|
253
248
|
|
@@ -263,6 +258,8 @@
|
|
263
258
|
const t = gripPose.transform;
|
264
259
|
this._gripPosition.set(t.position.x, t.position.y, t.position.z);
|
265
260
|
this._gripQuaternion.set(t.orientation.x, t.orientation.y, t.orientation.z, t.orientation.w);
|
261
|
+
if (gripPose.linearVelocity)
|
262
|
+
this._linearVelocity.set(gripPose.linearVelocity.x, gripPose.linearVelocity.y, gripPose.linearVelocity.z);
|
266
263
|
}
|
267
264
|
}
|
268
265
|
|
@@ -312,6 +309,27 @@
|
|
312
309
|
this._object.position.copy(this._rayPosition);
|
313
310
|
this._object.quaternion.copy(this._rayQuaternion).multiply(flipForwardQuaternion);
|
314
311
|
}
|
312
|
+
|
313
|
+
|
314
|
+
// UPDATE WORLD TRANSFORM DATA
|
315
|
+
const parent = this.xr.context.mainCamera?.parent;
|
316
|
+
const parentWorldQuaternion = parent ? getWorldQuaternion(parent) : undefined;
|
317
|
+
|
318
|
+
// GRIP
|
319
|
+
this._gripWorldPosition.copy(this._gripPosition);
|
320
|
+
if (parent) this._gripWorldPosition.applyMatrix4(parent.matrixWorld);
|
321
|
+
this._gripWorldQuaternion.copy(this._gripQuaternion);
|
322
|
+
// flip forward because we want +Z to be forward
|
323
|
+
this._gripWorldQuaternion.multiply(flipForwardQuaternion);
|
324
|
+
if (parentWorldQuaternion) this._gripWorldQuaternion.premultiply(parentWorldQuaternion)
|
325
|
+
|
326
|
+
// RAY
|
327
|
+
this._rayWorldPosition.copy(this._rayPosition);
|
328
|
+
if (parent) this._rayWorldPosition.applyMatrix4(parent.matrixWorld);
|
329
|
+
this._rayWorldQuaternion.copy(this._rayQuaternion)
|
330
|
+
// flip forward because we want +Z to be forward
|
331
|
+
.multiply(flipForwardQuaternion);
|
332
|
+
if (parentWorldQuaternion) this._rayWorldQuaternion.premultiply(parentWorldQuaternion)
|
315
333
|
}
|
316
334
|
|
317
335
|
/** Called when the input source disconnects */
|
@@ -278,7 +278,11 @@
|
|
278
278
|
}
|
279
279
|
}
|
280
280
|
|
281
|
-
/** start a new webXR session (make sure to stop already running sessions before calling this method)
|
281
|
+
/** start a new webXR session (make sure to stop already running sessions before calling this method)
|
282
|
+
* @param mode The XRSessionMode to start (e.g. `immersive-vr` or `immersive-ar`), docs: https://developer.mozilla.org/en-US/docs/Web/API/XRSessionMode
|
283
|
+
* @param init The XRSessionInit to use (optional), docs: https://developer.mozilla.org/en-US/docs/Web/API/XRSessionInit
|
284
|
+
* @param context The Needle Engine context to use
|
285
|
+
*/
|
282
286
|
static async start(mode: XRSessionMode, init?: XRSessionInit, context?: Context): Promise<NeedleXRSession | null> {
|
283
287
|
|
284
288
|
if (this._currentSessionRequest) {
|
@@ -177,6 +177,9 @@
|
|
177
177
|
*/
|
178
178
|
export function hasPointerEventComponent(obj: Object3D, event?: InputEventNames | null) {
|
179
179
|
const res = GameObject.foreachComponent(obj, comp => {
|
180
|
+
// ignore disabled components
|
181
|
+
if (!comp.enabled) return undefined;
|
182
|
+
|
180
183
|
const handler = comp as IPointerEventHandler;
|
181
184
|
// if a specific event is passed in, we only check for that event
|
182
185
|
if (event) {
|
@@ -107,7 +107,10 @@
|
|
107
107
|
|
108
108
|
/** @returns the quicklook button if it was created */
|
109
109
|
get quicklookButton() { return this.shadowRoot?.querySelector("[data-needle='quicklook-button']") as HTMLButtonElement | null; }
|
110
|
-
/** get or create the quicklook button
|
110
|
+
/** get or create the quicklook button
|
111
|
+
* Behaviour of the button:
|
112
|
+
* - if the button is clicked a USDZExporter component will be searched for in the scene and if found, it will be used to export the scene to USDZ / Quicklook
|
113
|
+
*/
|
111
114
|
createQuicklookButton(): HTMLButtonElement {
|
112
115
|
const existingButton = this.shadowRoot?.querySelector("[data-needle='quicklook-button']") as HTMLButtonElement | null;
|
113
116
|
if (existingButton) return existingButton;
|
@@ -119,6 +122,9 @@
|
|
119
122
|
if (usdzExporter) {
|
120
123
|
usdzExporter.exportAsync();
|
121
124
|
}
|
125
|
+
else {
|
126
|
+
console.warn("No USDZExporter component found in the scene");
|
127
|
+
}
|
122
128
|
});
|
123
129
|
this.shadowRoot?.appendChild(button);
|
124
130
|
return button;
|
@@ -126,7 +132,13 @@
|
|
126
132
|
|
127
133
|
/** @returns the WebXR AR button if it was created */
|
128
134
|
get arButton() { return this.shadowRoot?.querySelector("[data-needle='webxr-ar-button']") as HTMLButtonElement | null; }
|
129
|
-
/** get or create the WebXR AR button
|
135
|
+
/** get or create the WebXR AR button
|
136
|
+
* @param init optional session init options
|
137
|
+
* Behaviour of the button:
|
138
|
+
* - if the device supports AR, the button will be visible and clickable
|
139
|
+
* - if the device does not support AR, the button will be hidden
|
140
|
+
* - if the device changes and now supports AR, the button will be visible
|
141
|
+
*/
|
130
142
|
createARButton(init?: XRSessionInit): HTMLButtonElement {
|
131
143
|
const existingButton = this.shadowRoot?.querySelector("[data-needle='webxr-ar-button']") as HTMLButtonElement | null;
|
132
144
|
if (existingButton) return existingButton;
|
@@ -147,7 +159,13 @@
|
|
147
159
|
|
148
160
|
/** @returns the WebXR VR button if it was created */
|
149
161
|
get vrButton() { return this.shadowRoot?.querySelector("[data-needle='webxr-vr-button']") as HTMLButtonElement | null; }
|
150
|
-
/** get or create the WebXR VR button
|
162
|
+
/** get or create the WebXR VR button
|
163
|
+
* @param init optional session init options
|
164
|
+
* Behaviour of the button:
|
165
|
+
* - if the device supports VR, the button will be visible and clickable
|
166
|
+
* - if the device does not support VR, the button will be hidden
|
167
|
+
* - if the device changes and now supports VR, the button will be visible
|
168
|
+
*/
|
151
169
|
createVRButton(init?: XRSessionInit): HTMLButtonElement {
|
152
170
|
const hasButton = this.shadowRoot?.querySelector("[data-needle='webxr-vr-button']");
|
153
171
|
if (hasButton) return hasButton as HTMLButtonElement;
|
@@ -168,7 +186,10 @@
|
|
168
186
|
|
169
187
|
/** @returns the Send to Quest button */
|
170
188
|
get sendToQuestButton() { return this.shadowRoot?.querySelector("[data-needle='webxr-sendtoquest-button']") as HTMLButtonElement | null; }
|
171
|
-
/** get or create the Send To Quest button
|
189
|
+
/** get or create the Send To Quest button
|
190
|
+
* Behaviour of the button:
|
191
|
+
* - if the button is clicked, the current URL will be sent to the Oculus Browser on the Quest
|
192
|
+
*/
|
172
193
|
createSendToQuestButton(): HTMLButtonElement {
|
173
194
|
const hasButton = this.shadowRoot?.querySelector("[data-needle='webxr-sendtoquest-button']");
|
174
195
|
if (hasButton) return hasButton as HTMLButtonElement;
|