@@ -182,6 +182,10 @@
|
|
182
182
|
super.onEnable();
|
183
183
|
}
|
184
184
|
|
185
|
+
onDestroy(): void {
|
186
|
+
if (this._isHovered) this.context.input.setCursorNormal();
|
187
|
+
}
|
188
|
+
|
185
189
|
private _requestedAnimatorTrigger?: string;
|
186
190
|
private *setAnimatorTriggerAtEndOfFrame(requestedTriggerId: string) {
|
187
191
|
this._requestedAnimatorTrigger = requestedTriggerId;
|
@@ -572,14 +572,8 @@
|
|
572
572
|
}
|
573
573
|
// moveEvent?: Event;
|
574
574
|
private onMove(evt: NEPointerEvent) {
|
575
|
-
// when we get a pointer move event then the click state needs to be reset
|
576
|
-
// this happens for example if we use touch emulation in chrome and click on an object without using the mouse
|
577
|
-
// since the EventSystem queries `isPointerClicked` and we get an event for onMove we would then falsely emit
|
578
|
-
// two onPointerClick events for one click
|
579
575
|
const index = evt.button;
|
580
|
-
|
581
|
-
this._pointerDoubleClick[index] = false;
|
582
|
-
|
576
|
+
|
583
577
|
const isDown = this.getPointerPressed(index);
|
584
578
|
if (isDown === false && !this.isInRect(evt)) return;
|
585
579
|
if (evt.pointerType === PointerType.Touch && !isDown) return;
|
@@ -804,7 +804,6 @@
|
|
804
804
|
col.setDensity(1);
|
805
805
|
}
|
806
806
|
rigidbody.recomputeMassPropertiesFromColliders();
|
807
|
-
console.log(rigidbody.mass())
|
808
807
|
}
|
809
808
|
else {
|
810
809
|
rigidbody.setAdditionalMass(rb.mass, false);
|
@@ -813,7 +812,6 @@
|
|
813
812
|
col.setDensity(0);
|
814
813
|
}
|
815
814
|
rigidbody.recomputeMassPropertiesFromColliders();
|
816
|
-
console.log(rigidbody.mass())
|
817
815
|
}
|
818
816
|
|
819
817
|
// https://rapier.rs/docs/user_guides/javascript/rigid_bodies#mass-properties
|
@@ -227,7 +227,7 @@
|
|
227
227
|
|
228
228
|
data.inputSource = this.context.input;
|
229
229
|
data.pointerId = pointerId;
|
230
|
-
data.isClicked = this.context.input.getPointerClicked(pointerId)
|
230
|
+
data.isClicked = pointerEvent.type == InputEvents.PointerUp && this.context.input.getPointerClicked(pointerId)
|
231
231
|
// using the input type directly instead of input API -> otherwise onMove events can sometimes be getPointerUp() == true
|
232
232
|
data.isDown = pointerEvent.type == InputEvents.PointerDown;
|
233
233
|
data.isUp = pointerEvent.type == InputEvents.PointerUp;
|
@@ -403,7 +403,13 @@
|
|
403
403
|
}
|
404
404
|
|
405
405
|
// save hovered object
|
406
|
-
this.hoveredByID.
|
406
|
+
const entry = this.hoveredByID.get(args.pointerId);
|
407
|
+
if (!entry)
|
408
|
+
this.hoveredByID.set(args.pointerId, { obj: object, data: args });
|
409
|
+
else {
|
410
|
+
entry.obj = object;
|
411
|
+
entry.data = args;
|
412
|
+
}
|
407
413
|
|
408
414
|
// create / update pressed entry
|
409
415
|
if (args.isDown) {
|
@@ -191,7 +191,7 @@
|
|
191
191
|
this._isPlaying = false;
|
192
192
|
if (this._isPaused) return;
|
193
193
|
this._isPaused = true;
|
194
|
-
this.
|
194
|
+
this.internalEvaluate();
|
195
195
|
this.invokePauseChangedMethodsOnTracks();
|
196
196
|
this.invokeStateChangedMethodsOnTracks();
|
197
197
|
}
|
@@ -205,13 +205,13 @@
|
|
205
205
|
this._time = 0;
|
206
206
|
this._isPlaying = false;
|
207
207
|
this._isPaused = false;
|
208
|
-
this.
|
208
|
+
this.internalEvaluate();
|
209
209
|
if (pauseChanged) this.invokePauseChangedMethodsOnTracks();
|
210
210
|
}
|
211
211
|
this._isPlaying = false;
|
212
212
|
this._isPaused = false;
|
213
213
|
if (pauseChanged && !wasPlaying) this.invokePauseChangedMethodsOnTracks();
|
214
|
-
if(wasPlaying) this.invokeStateChangedMethodsOnTracks();
|
214
|
+
if (wasPlaying) this.invokeStateChangedMethodsOnTracks();
|
215
215
|
if (this._internalUpdateRoutine)
|
216
216
|
this.stopCoroutine(this._internalUpdateRoutine);
|
217
217
|
this._internalUpdateRoutine = null;
|
@@ -219,28 +219,7 @@
|
|
219
219
|
}
|
220
220
|
|
221
221
|
evaluate() {
|
222
|
-
|
223
|
-
let t = this._time;
|
224
|
-
switch (this.extrapolationMode) {
|
225
|
-
case DirectorWrapMode.Hold:
|
226
|
-
if (this._speed > 0)
|
227
|
-
t = Math.min(t, this._duration);
|
228
|
-
else if (this._speed < 0)
|
229
|
-
t = Math.max(t, 0);
|
230
|
-
this._time = t;
|
231
|
-
break;
|
232
|
-
case DirectorWrapMode.Loop:
|
233
|
-
t %= this._duration;
|
234
|
-
this._time = t;
|
235
|
-
break;
|
236
|
-
case DirectorWrapMode.None:
|
237
|
-
if (t > this._duration) {
|
238
|
-
this.stop();
|
239
|
-
return;
|
240
|
-
}
|
241
|
-
break;
|
242
|
-
}
|
243
|
-
this.internalEvaluate(t);
|
222
|
+
this.internalEvaluate(true);
|
244
223
|
}
|
245
224
|
|
246
225
|
isValid() {
|
@@ -303,14 +282,46 @@
|
|
303
282
|
while (this._isPlaying && this.activeAndEnabled) {
|
304
283
|
if (!this._isPaused && this._isPlaying) {
|
305
284
|
this._time += this.context.time.deltaTime * this.speed;
|
306
|
-
this.
|
285
|
+
this.internalEvaluate();
|
307
286
|
}
|
308
287
|
// for (let i = 0; i < 5; i++)
|
309
288
|
yield;
|
310
289
|
}
|
311
290
|
}
|
312
291
|
|
313
|
-
|
292
|
+
/**
|
293
|
+
* PlayableDirector lifecycle should always call this instead of "evaluate"
|
294
|
+
* @param called_by_user If true the evaluation is called by the user (e.g. via evaluate())
|
295
|
+
*/
|
296
|
+
private internalEvaluate(called_by_user: boolean = false) {
|
297
|
+
// when the timeline is called by a user via evaluate() we want to keep updating activation tracks
|
298
|
+
// because "isPlaying" might be false but the director is still active. See NE-3737
|
299
|
+
|
300
|
+
if (!this.isValid()) return;
|
301
|
+
|
302
|
+
let t = this._time;
|
303
|
+
switch (this.extrapolationMode) {
|
304
|
+
case DirectorWrapMode.Hold:
|
305
|
+
if (this._speed > 0)
|
306
|
+
t = Math.min(t, this._duration);
|
307
|
+
else if (this._speed < 0)
|
308
|
+
t = Math.max(t, 0);
|
309
|
+
this._time = t;
|
310
|
+
break;
|
311
|
+
case DirectorWrapMode.Loop:
|
312
|
+
t %= this._duration;
|
313
|
+
this._time = t;
|
314
|
+
break;
|
315
|
+
case DirectorWrapMode.None:
|
316
|
+
if (t > this._duration) {
|
317
|
+
this.stop();
|
318
|
+
return;
|
319
|
+
}
|
320
|
+
break;
|
321
|
+
}
|
322
|
+
|
323
|
+
const time = this._time;
|
324
|
+
|
314
325
|
for (const track of this.playableAsset!.tracks) {
|
315
326
|
if (track.muted) continue;
|
316
327
|
switch (track.type) {
|
@@ -319,7 +330,7 @@
|
|
319
330
|
// then we want to leave objects active state as they were
|
320
331
|
// see NE-3241
|
321
332
|
// TODO: support all "post-playback-state" settings an activation track has, this is just "Leave as is"
|
322
|
-
if (!this._isPlaying) continue;
|
333
|
+
if (!called_by_user && !this._isPlaying) continue;
|
323
334
|
|
324
335
|
for (let i = 0; i < track.outputs.length; i++) {
|
325
336
|
const binding = track.outputs[i];
|
@@ -518,9 +529,9 @@
|
|
518
529
|
}
|
519
530
|
// Try to share the mixer with the animator
|
520
531
|
if (binding instanceof Animator && binding.runtimeAnimatorController) {
|
521
|
-
if(!binding.__internalDidAwakeAndStart) binding.initializeRuntimeAnimatorController();
|
532
|
+
if (!binding.__internalDidAwakeAndStart) binding.initializeRuntimeAnimatorController();
|
522
533
|
// Call bind once to ensure the animator is setup and has a mixer
|
523
|
-
if(!binding.runtimeAnimatorController.mixer) binding.runtimeAnimatorController.bind(binding);
|
534
|
+
if (!binding.runtimeAnimatorController.mixer) binding.runtimeAnimatorController.bind(binding);
|
524
535
|
handler.mixer = binding.runtimeAnimatorController.mixer;
|
525
536
|
}
|
526
537
|
// If we can not get the mixer from the animator then create a new one
|
@@ -7,7 +7,7 @@
|
|
7
7
|
import { XRSessionMode } from "../../engine/engine_setup.js";
|
8
8
|
import { getWorldPosition, getWorldQuaternion, setWorldPosition, setWorldQuaternion } from "../../engine/engine_three_utils.js";
|
9
9
|
import { INeedleEngineComponent } from "../../engine/engine_types.js";
|
10
|
-
import { getParam, isMozillaXR, setOrAddParamsToUrl } from "../../engine/engine_utils.js";
|
10
|
+
import { getParam, isMozillaXR, isQuest, setOrAddParamsToUrl } from "../../engine/engine_utils.js";
|
11
11
|
|
12
12
|
import { Behaviour, GameObject } from "../Component.js";
|
13
13
|
import { noVoip } from "../Voip.js";
|
@@ -17,6 +17,7 @@
|
|
17
17
|
import { WebXRSync } from "./WebXRSync.js";
|
18
18
|
import { XRFlag, XRState, XRStateFlag } from "../XRFlag.js";
|
19
19
|
import { showBalloonWarning } from '../../engine/debug/index.js';
|
20
|
+
import { isDestroyed } from '../../engine/engine_gameobject.js';
|
20
21
|
|
21
22
|
const debugWebXR = getParam("debugwebxr");
|
22
23
|
|
@@ -154,7 +155,7 @@
|
|
154
155
|
}
|
155
156
|
|
156
157
|
public get Rig(): Object3D {
|
157
|
-
|
158
|
+
this.ensureRig();
|
158
159
|
return this.rig;
|
159
160
|
}
|
160
161
|
|
@@ -214,9 +215,7 @@
|
|
214
215
|
sync.webXR = this;
|
215
216
|
}
|
216
217
|
this.webAR = new WebAR(this);
|
217
|
-
}
|
218
218
|
|
219
|
-
start() {
|
220
219
|
if (location.protocol == 'http:' && location.host.indexOf('localhost') < 0) {
|
221
220
|
showBalloonWarning("WebXR only works on https");
|
222
221
|
console.warn("WebXR only works on https. https://engine.needle.tools/docs/xr.html");
|
@@ -284,7 +283,7 @@
|
|
284
283
|
private _currentHeadPose: XRViewerPose | null = null;
|
285
284
|
public get HeadPose(): XRViewerPose | null { return this._currentHeadPose; }
|
286
285
|
|
287
|
-
onBeforeRender(frame) {
|
286
|
+
onBeforeRender(frame:XRFrame | null | undefined) {
|
288
287
|
if (!frame) return;
|
289
288
|
// TODO: figure out why screen is black if we enable the code written here
|
290
289
|
// const referenceSpace = renderer.xr.getReferenceSpace();
|
@@ -292,7 +291,9 @@
|
|
292
291
|
|
293
292
|
|
294
293
|
if (session) {
|
295
|
-
const
|
294
|
+
const referenceSpace = this.context.renderer.xr.getReferenceSpace();
|
295
|
+
if(!referenceSpace) return;
|
296
|
+
const pose = frame.getViewerPose(referenceSpace);
|
296
297
|
if (!pose) return;
|
297
298
|
this._currentHeadPose = pose;
|
298
299
|
const transform: XRRigidTransform = pose?.transform;
|
@@ -303,6 +304,11 @@
|
|
303
304
|
if (WebXR._isInXr === false && session) {
|
304
305
|
this.onEnterXR(session, frame);
|
305
306
|
}
|
307
|
+
else if (this.IsInVR) {
|
308
|
+
if (this.context.mainCamera) {
|
309
|
+
this.ensureRig();
|
310
|
+
}
|
311
|
+
}
|
306
312
|
|
307
313
|
for (const ctrl of this.controllers) {
|
308
314
|
ctrl.onUpdate(session);
|
@@ -364,7 +370,7 @@
|
|
364
370
|
}
|
365
371
|
|
366
372
|
private ensureRig() {
|
367
|
-
if (!this.rig) {
|
373
|
+
if (!this.rig || isDestroyed(this.rig)) {
|
368
374
|
// currently just used for pose
|
369
375
|
const xrRig = GameObject.findObjectOfType(XRRig, this.context);
|
370
376
|
if (xrRig) {
|
@@ -381,6 +387,23 @@
|
|
381
387
|
this.context.scene.add(this.rig);
|
382
388
|
}
|
383
389
|
}
|
390
|
+
|
391
|
+
// Make sure the webxr camera is parented to the xr rig
|
392
|
+
if (this.context.isInXR && this.context.mainCamera && this.context.mainCamera.parent !== this.rig) {
|
393
|
+
this.rig.add(this.context.mainCamera);
|
394
|
+
|
395
|
+
// Hack: make sure we have the correct position and rotation (e.g. where we are dealing with an implicitly created rig)
|
396
|
+
// This handles the case where we switch between multiple scenes
|
397
|
+
if (this.IsInVR) {
|
398
|
+
const other = GameObject.findObjectOfType(XRRig);
|
399
|
+
if (other && other?.gameObject !== this.rig) {
|
400
|
+
this.rig.position.copy(other.gameObject.position);
|
401
|
+
this.rig.quaternion.copy(other.gameObject.quaternion);
|
402
|
+
this.rig.rotateY(Math.PI);
|
403
|
+
this.rig.scale.copy(other.gameObject.scale);
|
404
|
+
}
|
405
|
+
}
|
406
|
+
}
|
384
407
|
}
|
385
408
|
|
386
409
|
|
@@ -429,7 +452,6 @@
|
|
429
452
|
}
|
430
453
|
cam.layers.enableAll();
|
431
454
|
}
|
432
|
-
this.rig.add(this.context.mainCamera);
|
433
455
|
if (this._requestedAR) {
|
434
456
|
this.context.scene.add(this.rig);
|
435
457
|
}
|
@@ -597,7 +619,7 @@
|
|
597
619
|
this.didPlaceARSessionRoot = false;
|
598
620
|
this.getAROverlayContainer();
|
599
621
|
|
600
|
-
const deviceType =
|
622
|
+
const deviceType = isQuest() ? ControllerType.PhysicalDevice : ControllerType.Touch;
|
601
623
|
const controllerCount = deviceType === ControllerType.Touch ? 4 : 2;
|
602
624
|
for (let i = 0; i < controllerCount; i++) {
|
603
625
|
WebXRController.Create(this.webxr, i, this.webxr.gameObject as GameObject, deviceType)
|
@@ -56,12 +56,12 @@
|
|
56
56
|
|
57
57
|
onEnable(): void {
|
58
58
|
WebXR.addEventListener(WebXREvent.XRUpdate, this.onXRUpdate);
|
59
|
-
WebXR.addEventListener(
|
59
|
+
WebXR.addEventListener(WebXREvent.ModifyAROptions, this.onModifyAROptions);
|
60
60
|
}
|
61
61
|
|
62
62
|
onDisable(): void {
|
63
63
|
WebXR.removeEventListener(WebXREvent.XRUpdate, this.onXRUpdate);
|
64
|
-
WebXR.removeEventListener(
|
64
|
+
WebXR.removeEventListener(WebXREvent.ModifyAROptions, this.onModifyAROptions);
|
65
65
|
}
|
66
66
|
|
67
67
|
private onModifyAROptions = (event: any) => {
|