@needle-tools/engine 5.1.6 → 5.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-BcyvM7a6.js → needle-engine.bundle-CaD5rOJ0.js} +4839 -4791
- package/dist/{needle-engine.bundle-FRzWLdVf.umd.cjs → needle-engine.bundle-D8gP8Nd0.umd.cjs} +140 -140
- package/dist/{needle-engine.bundle-CadxfFMM.min.js → needle-engine.bundle-X7Y4A-EO.min.js} +140 -140
- package/dist/needle-engine.d.ts +72 -24
- package/dist/needle-engine.js +266 -266
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine/api.d.ts +1 -1
- package/lib/engine/api.js +1 -1
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/engine_init.js +2 -2
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_license.d.ts +7 -7
- package/lib/engine/engine_license.js +71 -71
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_networking_blob.js +3 -3
- package/lib/engine/engine_networking_blob.js.map +1 -1
- package/lib/engine/engine_utils.js +6 -0
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/engine_utils_qrcode.js +2 -2
- package/lib/engine/engine_utils_qrcode.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js +5 -5
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.js +2 -2
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
- package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.js +20 -2
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine/xr/TempXRContext.js +2 -2
- package/lib/engine/xr/TempXRContext.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/webxr/TeleportTarget.d.ts +21 -8
- package/lib/engine-components/webxr/TeleportTarget.js +21 -8
- package/lib/engine-components/webxr/TeleportTarget.js.map +1 -1
- package/lib/engine-components/webxr/WebARSessionRoot.d.ts +24 -6
- package/lib/engine-components/webxr/WebARSessionRoot.js +130 -39
- package/lib/engine-components/webxr/WebARSessionRoot.js.map +1 -1
- package/lib/engine-components/webxr/WebXR.d.ts +3 -3
- package/lib/engine-components/webxr/WebXR.js +4 -4
- package/lib/engine-components/webxr/WebXR.js.map +1 -1
- package/lib/engine-components/webxr/controllers/XRControllerMovement.d.ts +18 -1
- package/lib/engine-components/webxr/controllers/XRControllerMovement.js +18 -1
- package/lib/engine-components/webxr/controllers/XRControllerMovement.js.map +1 -1
- package/package.json +1 -1
- package/plugins/common/license.js +23 -5
- package/plugins/vite/dependencies.js +34 -0
- package/plugins/vite/license.js +4 -4
- package/plugins/vite/needle-app.js +13 -0
- package/src/engine/api.ts +1 -1
- package/src/engine/engine_init.ts +2 -2
- package/src/engine/engine_license.ts +68 -68
- package/src/engine/engine_networking_blob.ts +3 -3
- package/src/engine/engine_utils.ts +4 -0
- package/src/engine/engine_utils_qrcode.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +5 -5
- package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
- package/src/engine/webcomponents/needle-engine.ts +2 -2
- package/src/engine/xr/NeedleXRSession.ts +19 -2
- package/src/engine/xr/TempXRContext.ts +2 -2
- package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
- package/src/engine-components/webxr/TeleportTarget.ts +23 -10
- package/src/engine-components/webxr/WebARSessionRoot.ts +124 -38
- package/src/engine-components/webxr/WebXR.ts +4 -4
- package/src/engine-components/webxr/controllers/XRControllerMovement.ts +20 -3
|
@@ -2,26 +2,39 @@ import { Behaviour } from "../Component.js";
|
|
|
2
2
|
import type { XRControllerMovement } from "./controllers/XRControllerMovement.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Marks a GameObject as a valid teleportation target for VR locomotion.
|
|
6
|
-
* Add this component to objects or surfaces where users should be able to teleport.
|
|
5
|
+
* Marks a GameObject as a valid teleportation target for VR locomotion.
|
|
6
|
+
* Add this component to objects or surfaces where users should be able to teleport.
|
|
7
7
|
*
|
|
8
|
-
* **
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* **How targeting works (important):**
|
|
9
|
+
* The teleport ray uses a **visual raycast against rendered meshes** — NOT physics colliders.
|
|
10
|
+
* When {@link XRControllerMovement.useTeleportTarget} is enabled, the controller ray is cast
|
|
11
|
+
* against the rendered scene and the hit object (or any of its parents) is checked for a
|
|
12
|
+
* `TeleportTarget` component. You can only teleport onto an object if that object, or one of
|
|
13
|
+
* its ancestors, has this component.
|
|
14
|
+
*
|
|
15
|
+
* **Requirements for a target to be hittable:**
|
|
16
|
+
* - It MUST have a visible, rendered mesh (a `MeshRenderer` / geometry). The ray hits the
|
|
17
|
+
* rendered geometry directly.
|
|
18
|
+
* - **A collider is NOT required.** Teleport does not use physics colliders — adding a
|
|
19
|
+
* `BoxCollider` (or any collider) has no effect on whether the target can be teleported to.
|
|
20
|
+
* - It must NOT be on the IgnoreRaycast layer (three.js layer 2) and must not have
|
|
21
|
+
* `raycastAllowed` / `visible` set to false, otherwise the ray skips it.
|
|
22
|
+
* - The `TeleportTarget` component may be on the hit mesh itself or on any parent
|
|
23
|
+
* (resolved via `getComponentInParent`).
|
|
12
24
|
*
|
|
13
25
|
* **Setup:**
|
|
14
|
-
* 1. Add this component to GameObjects that should be teleport destinations
|
|
15
|
-
* 2.
|
|
26
|
+
* 1. Add this component to GameObjects (with a visible mesh) that should be teleport destinations
|
|
27
|
+
* 2. Enable "Use Teleport Target" on the {@link XRControllerMovement} component so teleporting
|
|
28
|
+
* is restricted to objects carrying this component
|
|
16
29
|
* 3. Test teleportation in VR mode
|
|
17
30
|
*
|
|
18
31
|
* @example
|
|
19
32
|
* ```ts
|
|
20
|
-
* // Make a platform teleportable
|
|
33
|
+
* // Make a platform teleportable (the platform already has a visible mesh)
|
|
21
34
|
* const platform = myPlatform.addComponent(TeleportTarget);
|
|
22
35
|
* ```
|
|
23
36
|
*
|
|
24
|
-
* @summary Marker component for valid VR teleportation destinations
|
|
37
|
+
* @summary Marker component for valid VR teleportation destinations (mesh hit-test, no collider needed)
|
|
25
38
|
* @category XR
|
|
26
39
|
* @group Components
|
|
27
40
|
* @see {@link XRControllerMovement} for VR locomotion and teleport configuration
|
|
@@ -18,6 +18,22 @@ const debug = getParam("debugwebxr");
|
|
|
18
18
|
|
|
19
19
|
const invertForwardMatrix = new Matrix4().makeRotationY(Math.PI);
|
|
20
20
|
|
|
21
|
+
/** a XRRigidTransform pose stored as plain values — XR hit test results and frames are only
|
|
22
|
+
* valid within the animation frame that produced them, so poses that need to survive until a
|
|
23
|
+
* later frame (or an input event) must be snapshotted */
|
|
24
|
+
type XRAnchorRefPose = { x: number, y: number, z: number, qx: number, qy: number, qz: number, qw: number };
|
|
25
|
+
|
|
26
|
+
const unitScale = new Vector3(1, 1, 1);
|
|
27
|
+
const tempAnchorPose = new Matrix4();
|
|
28
|
+
const tempAnchorDelta = new Matrix4();
|
|
29
|
+
|
|
30
|
+
function matrixApproximatelyEquals(a: Matrix4, b: Matrix4, epsilon: number = 1e-6): boolean {
|
|
31
|
+
for (let i = 0; i < 16; i++) {
|
|
32
|
+
if (Math.abs(a.elements[i] - b.elements[i]) > epsilon) return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
|
|
22
38
|
/**
|
|
23
39
|
* The WebARSessionRoot is the root object for a WebAR session and used to place the scene in AR.
|
|
@@ -101,11 +117,12 @@ export class WebARSessionRoot extends Behaviour {
|
|
|
101
117
|
/** When enabled the scene center will be automatically calculated from the content in the scene */
|
|
102
118
|
autoCenter: boolean = false;
|
|
103
119
|
|
|
104
|
-
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
120
|
+
/** When enabled a XR anchor is created at the scene placement position and the scene
|
|
121
|
+
* stays at that anchored point while tracking refines during the XR session.
|
|
122
|
+
* On platforms without WebXR anchor support this gracefully falls back to plain placement.
|
|
123
|
+
* @default true
|
|
107
124
|
**/
|
|
108
|
-
useXRAnchor: boolean =
|
|
125
|
+
useXRAnchor: boolean = true;
|
|
109
126
|
|
|
110
127
|
/** true if we're currently placing the scene */
|
|
111
128
|
private _isPlacing = true;
|
|
@@ -125,13 +142,18 @@ export class WebARSessionRoot extends Behaviour {
|
|
|
125
142
|
|
|
126
143
|
/** the reticles used for placement */
|
|
127
144
|
private readonly _reticle: IGameObject[] = [];
|
|
128
|
-
/**
|
|
129
|
-
|
|
145
|
+
/** reference-space hit poses, in sync with the reticles — captured while placing when {@link useXRAnchor}
|
|
146
|
+
* is enabled, because hit test results can not be used outside the animation frame that produced them */
|
|
147
|
+
private readonly _hitRefPoses: (XRAnchorRefPose | undefined)[] = [];
|
|
130
148
|
|
|
131
149
|
private _placementStartTime: number = -1;
|
|
132
150
|
private _rigPlacementMatrix?: Matrix4;
|
|
133
151
|
/** if useAnchor is enabled this is the anchor we have created on placing the scene using the placement hit */
|
|
134
152
|
private _anchor: XRAnchor | null = null;
|
|
153
|
+
/** the placement pose (reference space) waiting for anchor creation on the next animation frame */
|
|
154
|
+
private _pendingAnchorPose: XRAnchorRefPose | null = null;
|
|
155
|
+
/** the last anchor pose (in rig space) that has been applied to the rig */
|
|
156
|
+
private readonly _anchorLastLocalPose = new Matrix4();
|
|
135
157
|
/** user input is used for ar touch transform */
|
|
136
158
|
private userInput?: WebXRSessionRootUserInput;
|
|
137
159
|
|
|
@@ -147,6 +169,8 @@ export class WebARSessionRoot extends Behaviour {
|
|
|
147
169
|
if (debug) console.log("ENTER WEBXR: SessionRoot start...");
|
|
148
170
|
|
|
149
171
|
this._anchor = null;
|
|
172
|
+
this._pendingAnchorPose = null;
|
|
173
|
+
this._hitRefPoses.length = 0;
|
|
150
174
|
WebARSessionRoot._hasPlaced = false;
|
|
151
175
|
|
|
152
176
|
// if (_args.xr.session.enabledFeatures?.includes("image-tracking")) {
|
|
@@ -205,8 +229,9 @@ export class WebARSessionRoot extends Behaviour {
|
|
|
205
229
|
// TODO: WebARSessionRoot doesnt work when we enter passthrough and leave XR without having placed the session!!!
|
|
206
230
|
this.context.input.removeEventListener("pointerup", this.onPlaceScene, { queue: InputEventQueue.Early });
|
|
207
231
|
this.onRevertSceneChanges();
|
|
208
|
-
//
|
|
232
|
+
// no explicit anchor.delete() — anchors are released by the platform when the session ends
|
|
209
233
|
this._anchor = null;
|
|
234
|
+
this._pendingAnchorPose = null;
|
|
210
235
|
WebARSessionRoot._hasPlaced = false;
|
|
211
236
|
this._rigPlacementMatrix = undefined;
|
|
212
237
|
}
|
|
@@ -252,20 +277,8 @@ export class WebARSessionRoot extends Behaviour {
|
|
|
252
277
|
|
|
253
278
|
}
|
|
254
279
|
else {
|
|
255
|
-
//
|
|
256
|
-
if (this.
|
|
257
|
-
const pose = args.xr.frame.getPose(this._anchor.anchorSpace, args.xr.referenceSpace);
|
|
258
|
-
if (pose && this.context.time.frame % 20 === 0) {
|
|
259
|
-
// apply the anchor pose to one of the reticles
|
|
260
|
-
const converted = args.xr.convertSpace(pose.transform);
|
|
261
|
-
const reticle = this._reticle[0];
|
|
262
|
-
if (reticle) {
|
|
263
|
-
reticle.position.copy(converted.position);
|
|
264
|
-
reticle.quaternion.copy(converted.quaternion);
|
|
265
|
-
this.onApplyPose(reticle);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
280
|
+
// create the anchor (deferred from placement) and keep the scene glued to it
|
|
281
|
+
if (this.useXRAnchor) this.updateXRAnchor(args.xr);
|
|
269
282
|
|
|
270
283
|
// Scene has been placed
|
|
271
284
|
if (this.arTouchTransform) {
|
|
@@ -286,9 +299,20 @@ export class WebARSessionRoot extends Behaviour {
|
|
|
286
299
|
}
|
|
287
300
|
}
|
|
288
301
|
|
|
289
|
-
private updateReticleAndHits(
|
|
290
|
-
//
|
|
291
|
-
|
|
302
|
+
private updateReticleAndHits(xr: NeedleXRSession, i: number, hit: NeedleXRHitTestResult, scale: number) {
|
|
303
|
+
// capture the reference-space hit pose while the XRFrame that produced the hit is still
|
|
304
|
+
// active — it can not be queried anymore during placement (which runs from an input event)
|
|
305
|
+
if (this.useXRAnchor) {
|
|
306
|
+
const referenceSpace = xr.referenceSpace;
|
|
307
|
+
const rawPose = referenceSpace ? hit.hit.getPose(referenceSpace) : undefined;
|
|
308
|
+
if (rawPose) {
|
|
309
|
+
let pose = this._hitRefPoses[i];
|
|
310
|
+
if (!pose) pose = this._hitRefPoses[i] = { x: 0, y: 0, z: 0, qx: 0, qy: 0, qz: 0, qw: 1 };
|
|
311
|
+
const { position, orientation } = rawPose.transform;
|
|
312
|
+
pose.x = position.x; pose.y = position.y; pose.z = position.z;
|
|
313
|
+
pose.qx = orientation.x; pose.qy = orientation.y; pose.qz = orientation.z; pose.qw = orientation.w;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
292
316
|
let reticle = this._reticle[i];
|
|
293
317
|
if (!reticle) {
|
|
294
318
|
if (this.customReticle) {
|
|
@@ -392,14 +416,14 @@ export class WebARSessionRoot extends Behaviour {
|
|
|
392
416
|
return;
|
|
393
417
|
}
|
|
394
418
|
|
|
395
|
-
let
|
|
419
|
+
let hitIndex = 0;
|
|
396
420
|
|
|
397
421
|
if (evt && evt.origin instanceof NeedleXRController) {
|
|
398
422
|
// until we can use hit testing for both controllers and have multple reticles we only allow placement with the first controller
|
|
399
423
|
const controllerReticle = this._reticle[evt.origin.index];
|
|
400
424
|
if (controllerReticle) {
|
|
401
425
|
reticle = controllerReticle;
|
|
402
|
-
|
|
426
|
+
hitIndex = evt.origin.index;
|
|
403
427
|
}
|
|
404
428
|
}
|
|
405
429
|
|
|
@@ -423,7 +447,15 @@ export class WebARSessionRoot extends Behaviour {
|
|
|
423
447
|
WebARSessionRoot._hasPlaced = true;
|
|
424
448
|
|
|
425
449
|
if (this.useXRAnchor) {
|
|
426
|
-
|
|
450
|
+
// anchor creation is deferred to the next animation frame — see updateXRAnchor
|
|
451
|
+
const refPose = this._hitRefPoses[hitIndex];
|
|
452
|
+
if (refPose) {
|
|
453
|
+
this._pendingAnchorPose = { ...refPose };
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
console.warn("[WebARSessionRoot] can not create a XR anchor: no hit pose was captured for the placement");
|
|
457
|
+
if (isDevEnvironment()) showBalloonWarning("Can not create XR anchor: no hit pose available");
|
|
458
|
+
}
|
|
427
459
|
}
|
|
428
460
|
|
|
429
461
|
if (this.context.xr) {
|
|
@@ -464,19 +496,73 @@ export class WebARSessionRoot extends Behaviour {
|
|
|
464
496
|
}
|
|
465
497
|
}
|
|
466
498
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
499
|
+
/**
|
|
500
|
+
* Deferred anchor creation and per-frame anchor tracking. Called from {@link onUpdateXR} after the scene has been placed.
|
|
501
|
+
*
|
|
502
|
+
* Creation can not happen during placement: `XRHitTestResult.createAnchor` only works while the animation
|
|
503
|
+
* frame that produced the hit is active, and placement runs from an input event where that frame is already
|
|
504
|
+
* over (`XRFrame.createAnchor` equally requires an active animation frame). So {@link onPlaceScene} only
|
|
505
|
+
* records the reference-space pose and the anchor is created here, on the next animation frame.
|
|
506
|
+
*
|
|
507
|
+
* Once the anchor exists, its pose *changes* (delta in rig space) are applied to the XR rig: the world pose
|
|
508
|
+
* of the anchored point stays constant while tracking refines, without discarding other rig modifications
|
|
509
|
+
* ({@link arTouchTransform} user adjustments, {@link arScale}).
|
|
510
|
+
*/
|
|
511
|
+
private updateXRAnchor(xr: NeedleXRSession) {
|
|
512
|
+
const referenceSpace = xr.referenceSpace;
|
|
513
|
+
if (!referenceSpace) return;
|
|
514
|
+
const frame = xr.frame;
|
|
515
|
+
|
|
516
|
+
if (this._pendingAnchorPose) {
|
|
517
|
+
const pending = this._pendingAnchorPose;
|
|
518
|
+
this._pendingAnchorPose = null;
|
|
519
|
+
if (typeof frame.createAnchor !== "function" || xr.session.enabledFeatures?.includes("anchors") === false) {
|
|
520
|
+
// dev-only: useXRAnchor is enabled by default, so platforms without anchor
|
|
521
|
+
// support (e.g. iOS) would otherwise warn in every production session
|
|
522
|
+
if (debug || isDevEnvironment()) {
|
|
523
|
+
console.warn("[WebARSessionRoot] useXRAnchor is enabled but WebXR anchors are not supported by this session");
|
|
524
|
+
showBalloonWarning("WebXR anchors are not supported by this session");
|
|
525
|
+
}
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const transform = new XRRigidTransform({ x: pending.x, y: pending.y, z: pending.z }, { x: pending.qx, y: pending.qy, z: pending.qz, w: pending.qw });
|
|
529
|
+
// the anchor is created exactly at this pose — remember it (in rig space) as the baseline for deltas
|
|
530
|
+
const initialPose = xr.convertSpace(transform);
|
|
531
|
+
this._anchorLastLocalPose.compose(initialPose.position, initialPose.quaternion, unitScale);
|
|
532
|
+
frame.createAnchor(transform, referenceSpace).then(anchor => {
|
|
533
|
+
if (xr.running && !this._isPlacing) {
|
|
534
|
+
this._anchor = anchor;
|
|
535
|
+
if (debug) console.log("[WebARSessionRoot] created XR anchor", anchor);
|
|
536
|
+
}
|
|
537
|
+
else anchor.delete();
|
|
538
|
+
}).catch((err: unknown) => {
|
|
539
|
+
console.warn("[WebARSessionRoot] failed to create XR anchor", err);
|
|
540
|
+
if (isDevEnvironment()) showBalloonWarning("Failed to create XR anchor");
|
|
541
|
+
});
|
|
471
542
|
return;
|
|
472
543
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
544
|
+
|
|
545
|
+
if (!this._anchor) return;
|
|
546
|
+
const rig = xr.rig?.gameObject;
|
|
547
|
+
if (!rig) return;
|
|
548
|
+
const pose = frame.getPose(this._anchor.anchorSpace, referenceSpace);
|
|
549
|
+
// the anchor may temporarily not be tracked — keep the last applied correction then
|
|
550
|
+
if (!pose) return;
|
|
551
|
+
|
|
552
|
+
const current = xr.convertSpace(pose.transform);
|
|
553
|
+
tempAnchorPose.compose(current.position, current.quaternion, unitScale);
|
|
554
|
+
if (matrixApproximatelyEquals(tempAnchorPose, this._anchorLastLocalPose)) return;
|
|
555
|
+
|
|
556
|
+
// rig_new = rig_old · lastPose · currentPose⁻¹ — keeps the world pose of the anchored
|
|
557
|
+
// point (rigWorld · anchorRigSpacePose) constant across tracking refinements
|
|
558
|
+
tempAnchorDelta.copy(tempAnchorPose).invert().premultiply(this._anchorLastLocalPose);
|
|
559
|
+
this._anchorLastLocalPose.copy(tempAnchorPose);
|
|
560
|
+
rig.updateMatrix();
|
|
561
|
+
rig.matrix.multiply(tempAnchorDelta);
|
|
562
|
+
rig.matrix.decompose(rig.position, rig.quaternion, rig.scale);
|
|
563
|
+
if (debug) {
|
|
564
|
+
const dx = tempAnchorDelta.elements[12], dy = tempAnchorDelta.elements[13], dz = tempAnchorDelta.elements[14];
|
|
565
|
+
console.log(`[WebARSessionRoot] applied XR anchor correction: ${(Math.hypot(dx, dy, dz) * 1000).toFixed(2)}mm`);
|
|
480
566
|
}
|
|
481
567
|
}
|
|
482
568
|
|
|
@@ -152,12 +152,12 @@ export class WebXR extends Behaviour {
|
|
|
152
152
|
arScale: number = 1;
|
|
153
153
|
|
|
154
154
|
/**
|
|
155
|
-
* When enabled, an XRAnchor
|
|
156
|
-
*
|
|
157
|
-
* @
|
|
155
|
+
* When enabled, an XRAnchor is created at the AR placement position and the scene stays glued to it while tracking refines.
|
|
156
|
+
* On platforms without WebXR anchor support this gracefully falls back to plain placement.
|
|
157
|
+
* @default true
|
|
158
158
|
*/
|
|
159
159
|
@serializable()
|
|
160
|
-
useXRAnchor: boolean =
|
|
160
|
+
useXRAnchor: boolean = true;
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
163
|
* When enabled, the scene will be automatically placed as soon as a suitable surface is detected in AR,
|
|
@@ -28,8 +28,17 @@ declare type HitPointObject = Object3D & { material: Material & { opacity: numbe
|
|
|
28
28
|
*
|
|
29
29
|
* Additionally it supports teleporting using the right controller's thumbstick or by pinching the index finger tip in front of the hand (if hand tracking is enabled).
|
|
30
30
|
* It also visualizes controller rays and hit points in the scene.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
*
|
|
32
|
+
* The component operates on the active XR rig and controllers from the {@link NeedleXRSession} (`args.xr.rig`,
|
|
33
|
+
* `args.xr.leftController` / `rightController`) and receives `onUpdateXR` regardless of where it sits in the hierarchy.
|
|
34
|
+
* It does NOT need to be on the same GameObject as {@link WebXR}, and does not read {@link WebXR} from its parents.
|
|
35
|
+
*
|
|
36
|
+
* **Duplicate-handler gotcha:** {@link WebXR.setDefaultMovementEnabled} (called when `WebXR.useDefaultControls` is enabled,
|
|
37
|
+
* the default) adds an `XRControllerMovement` to the WebXR GameObject only if that object has none. If you place your own
|
|
38
|
+
* `XRControllerMovement` on a different object, WebXR still adds a second, default one at runtime — two handlers then run
|
|
39
|
+
* simultaneously (doubled movement, and your teleport settings such as {@link XRControllerMovement.useTeleportTarget} are
|
|
40
|
+
* bypassed by the default handler). Put your component on the WebXR GameObject, or disable `WebXR.useDefaultControls`.
|
|
41
|
+
*
|
|
33
42
|
* @summary Move the XR rig using controller input
|
|
34
43
|
* @category XR
|
|
35
44
|
* @group Components
|
|
@@ -61,7 +70,15 @@ export class XRControllerMovement extends Behaviour implements XRMovementBehavio
|
|
|
61
70
|
@serializable()
|
|
62
71
|
usePinchToTeleport: boolean = true;
|
|
63
72
|
|
|
64
|
-
/** Enable to only allow teleporting on objects with a
|
|
73
|
+
/** Enable to only allow teleporting on objects with a {@link TeleportTarget} component.
|
|
74
|
+
*
|
|
75
|
+
* The teleport ray is a **visual raycast against rendered meshes** (not physics colliders):
|
|
76
|
+
* it hits the rendered geometry and then looks for a {@link TeleportTarget} on the hit object
|
|
77
|
+
* or any of its parents. A collider is NOT required on the target — only a visible mesh that
|
|
78
|
+
* is not on the IgnoreRaycast layer.
|
|
79
|
+
*
|
|
80
|
+
* When enabled, teleporting is rejected if the ray does not hit a `TeleportTarget` (the
|
|
81
|
+
* ground-plane fallback used in free teleport mode is also disabled).
|
|
65
82
|
* @default false
|
|
66
83
|
*/
|
|
67
84
|
@serializable()
|